Skip to content

Commit

Permalink
Merge pull request #68 from sebadob/create-generic-error-page
Browse files Browse the repository at this point in the history
Create generic error page
  • Loading branch information
sebadob committed Oct 5, 2023
2 parents 260169d + 3ab6c46 commit 0e476ab
Show file tree
Hide file tree
Showing 12 changed files with 329 additions and 66 deletions.
1 change: 1 addition & 0 deletions frontend/src/routes/404.html
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
<h1>The requested site was not found</h1>

60 changes: 60 additions & 0 deletions frontend/src/routes/error/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<script>
import { slide } from 'svelte/transition';
import IconChevronRight from "$lib/icons/IconChevronRight.svelte";
import WithI18n from "$lib/WithI18n.svelte";
import BrowserCheck from "../../components/BrowserCheck.svelte";
import LangSelector from "$lib/LangSelector.svelte";
let t;
let showDetails = false;
</script>

<BrowserCheck>
<WithI18n bind:t content="error">
<h1>{t.error}</h1>
<p>{t.errorText}</p>

{#if t.detailsText}
<div class="showDetails" on:click={() => showDetails = !showDetails}>
{t.details}
<div
class="chevron"
style:margin-top={showDetails ? '' : '-5px'}
style:transform={showDetails ? 'rotate(90deg)' : 'rotate(180deg)'}
>
<IconChevronRight
color="var(--col-act2)"
width=16
/>
</div>
</div>

{#if showDetails}
<div transition:slide class="details">
{t.detailsText}
</div>
{/if}
{/if}

<LangSelector absolute />
</WithI18n>
</BrowserCheck>

<style>
.chevron {
transition: all 250ms;
}
.showDetails {
display: inline-flex;
align-items: center;
gap: .1rem;
cursor: pointer;
color: var(--col-act2);
}
.showDetails:hover {
color: var(--col-act2a);
}
</style>
20 changes: 14 additions & 6 deletions rauthy-common/src/error_response.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::constants::APPLICATION_JSON;
use crate::constants::{APPLICATION_JSON, HEADER_HTML};
use crate::utils::build_csp_header;
use actix_multipart::MultipartError;
use actix_web::error::BlockingError;
use actix_web::http::StatusCode;
Expand Down Expand Up @@ -52,15 +53,16 @@ impl ErrorResponse {
message,
}
}
}

impl ResponseError for ErrorResponse {
fn error_response(&self) -> HttpResponse {
pub fn error_response_html(&self, body: String, nonce: &str) -> HttpResponse {
HttpResponseBuilder::new(self.status_code())
.content_type(APPLICATION_JSON)
.body(serde_json::to_string(&self).unwrap())
.append_header(HEADER_HTML)
.append_header(build_csp_header(nonce))
.body(body)
}
}

impl ResponseError for ErrorResponse {
fn status_code(&self) -> StatusCode {
match self.error {
ErrorResponseType::BadRequest => StatusCode::BAD_REQUEST,
Expand All @@ -76,6 +78,12 @@ impl ResponseError for ErrorResponse {
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}

fn error_response(&self) -> HttpResponse {
HttpResponseBuilder::new(self.status_code())
.content_type(APPLICATION_JSON)
.body(serde_json::to_string(&self).unwrap())
}
}

impl From<argon2::Error> for ErrorResponse {
Expand Down
9 changes: 9 additions & 0 deletions rauthy-common/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ use tracing::error;
const B64_URL_SAFE: engine::GeneralPurpose = general_purpose::URL_SAFE_NO_PAD;
const B64_STD: engine::GeneralPurpose = general_purpose::STANDARD;

pub fn build_csp_header(nonce: &str) -> (&str, String) {
let value = format!(
"default-src 'self'; script-src 'strict-dynamic' 'nonce-{}'; style-src 'self' 'unsafe-inline'; \
frame-ancestors 'none'; object-src 'none'; img-src 'self' data:;",
nonce,
);
("content-security-policy", value)
}

// Decrypts a `&Vec<u8>` which was [encrypted](encrypt) before with the same key.
pub fn decrypt(ciphertext: &[u8], key: &[u8]) -> Result<Vec<u8>, ErrorResponse> {
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
Expand Down
21 changes: 17 additions & 4 deletions rauthy-handlers/src/generic.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use crate::{build_csp_header, Assets};
use actix_web::http::header;
use crate::Assets;
use actix_web::http::header::{HeaderValue, CONTENT_TYPE};
use actix_web::http::{header, StatusCode};
use actix_web::web::Json;
use actix_web::{get, post, put, web, HttpRequest, HttpResponse, Responder};
use actix_web_grants::proc_macro::{has_any_permission, has_permissions, has_roles};
use rauthy_common::constants::{
APPLICATION_JSON, CACHE_NAME_LOGIN_DELAY, HEADER_HTML, IDX_LOGIN_TIME,
};
use rauthy_common::error_response::{ErrorResponse, ErrorResponseType};
use rauthy_common::utils::build_csp_header;
use rauthy_models::app_state::AppState;
use rauthy_models::entity::colors::ColorEntity;
use rauthy_models::entity::password::{PasswordHashTimes, PasswordPolicy};
Expand All @@ -18,6 +19,7 @@ use rauthy_models::entity::users::User;
use rauthy_models::i18n::account::I18nAccount;
use rauthy_models::i18n::authorize::I18nAuthorize;
use rauthy_models::i18n::email_confirm_change_html::I18nEmailConfirmChangeHtml;
use rauthy_models::i18n::error::I18nError;
use rauthy_models::i18n::index::I18nIndex;
use rauthy_models::i18n::logout::I18nLogout;
use rauthy_models::i18n::password_reset::I18nPasswordReset;
Expand All @@ -34,7 +36,7 @@ use rauthy_models::response::{
use rauthy_models::templates::{
AccountHtml, AdminAttributesHtml, AdminClientsHtml, AdminConfigHtml, AdminDocsHtml,
AdminGroupsHtml, AdminHtml, AdminRolesHtml, AdminScopesHtml, AdminSessionsHtml, AdminUsersHtml,
IndexHtml,
ErrorHtml, IndexHtml,
};
use rauthy_service::encryption;
use redhac::{cache_get, cache_get_from, cache_get_value};
Expand All @@ -59,8 +61,10 @@ pub async fn get_index(
#[get("/{_:.*}")]
#[has_permissions("all")]
pub async fn get_static_assets(
data: web::Data<AppState>,
path: web::Path<String>,
accept_encoding: web::Header<header::AcceptEncoding>,
req: HttpRequest,
) -> HttpResponse {
let path = path.into_inner();
let accept_encoding = accept_encoding.into_inner();
Expand All @@ -80,7 +84,11 @@ pub async fn get_static_assets(
.insert_header(("content-encoding", encoding))
.content_type(mime.first_or_octet_stream().as_ref())
.body(content.data.into_owned()),
None => HttpResponse::NotFound().body("404 Not Found"),
None => {
let colors = ColorEntity::find_rauthy(&data).await.unwrap_or_default();
let lang = Language::try_from(&req).unwrap_or_default();
ErrorHtml::response(&colors, &lang, StatusCode::NOT_FOUND, None)
}
}
}

Expand All @@ -95,6 +103,11 @@ pub async fn post_i18n(
I18nContent::Authorize => I18nAuthorize::build(&lang).as_json(),
I18nContent::Account => I18nAccount::build(&lang).as_json(),
I18nContent::EmailChangeConfirm => I18nEmailConfirmChangeHtml::build(&lang).as_json(),
// Just return some default values for local dev -> dynamically built during prod
I18nContent::Error => {
I18nError::build_with(&lang, StatusCode::NOT_FOUND, Some("<empty>".to_string()))
.as_json()
}
I18nContent::Index => I18nIndex::build(&lang).as_json(),
I18nContent::Logout => I18nLogout::build(&lang).as_json(),
I18nContent::PasswordReset => I18nPasswordReset::build(&lang).as_json(),
Expand Down
18 changes: 0 additions & 18 deletions rauthy-handlers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,24 +109,6 @@ fn add_req_mfa_cookie(
Ok(())
}

pub fn build_csp_header(nonce: &str) -> (&str, String) {
// Note: The unsafe-inline for the style-src currently has an open issue on the svelte repo.
// As soon as this is fixed, we can get rid of it:
// https://github.com/sveltejs/svelte/issues/6662

// let value = format!(
// "default-src 'self'; script-src 'strict-dynamic' 'nonce-{}'; style-src 'self' 'nonce-{}'; \
// frame-ancestors 'self'; object-src 'none'; img-src 'self' data:;",
// nonce, nonce,
// );
let value = format!(
"default-src 'self'; script-src 'strict-dynamic' 'nonce-{}'; style-src 'self' 'unsafe-inline'; \
frame-ancestors 'none'; object-src 'none'; img-src 'self' data:;",
nonce,
);
("content-security-policy", value)
}

pub fn real_ip_from_req(req: &HttpRequest) -> Option<String> {
if *PROXY_MODE {
// TODO maybe make this configurable and extract headers in user-configured order?
Expand Down
34 changes: 24 additions & 10 deletions rauthy-handlers/src/oidc.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use crate::{build_csp_header, map_auth_step, real_ip_from_req};
use std::ops::Add;
use std::time::{SystemTime, UNIX_EPOCH};

use actix_web::cookie::time::OffsetDateTime;
use actix_web::http::{header, StatusCode};
use actix_web::{get, post, web, HttpRequest, HttpResponse};
use actix_web_grants::proc_macro::{has_any_permission, has_permissions, has_roles};

use rauthy_common::constants::{COOKIE_MFA, HEADER_HTML, SESSION_LIFETIME};
use rauthy_common::error_response::ErrorResponse;
use rauthy_common::utils::build_csp_header;
use rauthy_models::app_state::AppState;
use rauthy_models::entity::colors::ColorEntity;
use rauthy_models::entity::jwk::{JWKSPublicKey, JwkKeyPair, JWKS};
Expand All @@ -18,11 +22,11 @@ use rauthy_models::request::{
TokenRequest, TokenValidationRequest,
};
use rauthy_models::response::{JWKSCerts, JWKSPublicKeyCerts, SessionInfoResponse};
use rauthy_models::templates::{AuthorizeHtml, CallbackHtml, FrontendAction};
use rauthy_models::templates::{AuthorizeHtml, CallbackHtml, ErrorHtml, FrontendAction};
use rauthy_models::JwtCommonClaims;
use rauthy_service::auth;
use std::ops::Add;
use std::time::{SystemTime, UNIX_EPOCH};

use crate::{map_auth_step, real_ip_from_req};

/// OIDC Authorization HTML
///
Expand All @@ -46,18 +50,26 @@ pub async fn get_authorize(
req_data: actix_web_validator::Query<AuthRequest>,
session: web::ReqData<Option<Session>>,
) -> Result<HttpResponse, ErrorResponse> {
let (client, origin_header) = auth::validate_auth_req_param(
let colors = ColorEntity::find(&data, &req_data.client_id)
.await
.unwrap_or_default();
let lang = Language::try_from(&req).unwrap_or_default();

let (client, origin_header) = match auth::validate_auth_req_param(
&data,
&req,
&req_data.client_id,
&req_data.redirect_uri,
&req_data.code_challenge,
&req_data.code_challenge_method,
)
.await?;

let lang = Language::try_from(&req).unwrap_or_default();
let colors = ColorEntity::find(&data, &req_data.client_id).await?;
.await
{
Ok(res) => res,
Err(err) => {
return Ok(ErrorHtml::response_from_err(&colors, &lang, err));
}
};

if session.is_some() && session.as_ref().unwrap().state == SessionState::Auth {
let (body, nonce) = AuthorizeHtml::build(
Expand All @@ -83,7 +95,9 @@ pub async fn get_authorize(
}

let session = Session::new(*SESSION_LIFETIME, real_ip_from_req(&req));
session.save(&data).await?;
if let Err(err) = session.save(&data).await {
return Ok(ErrorHtml::response_from_err(&colors, &lang, err));
}

let mut action = FrontendAction::None;

Expand Down
61 changes: 35 additions & 26 deletions rauthy-handlers/src/users.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::build_csp_header;
use actix_web::http::StatusCode;
use actix_web::{cookie, delete, get, post, put, web, HttpRequest, HttpResponse};
use actix_web_grants::proc_macro::{has_any_permission, has_permissions, has_roles};
use rauthy_common::constants::{
COOKIE_MFA, HEADER_HTML, OPEN_USER_REG, PWD_RESET_COOKIE, USER_REG_DOMAIN_RESTRICTION,
};
use rauthy_common::error_response::{ErrorResponse, ErrorResponseType};
use rauthy_common::utils::build_csp_header;
use rauthy_models::app_state::AppState;
use rauthy_models::entity::colors::ColorEntity;
use rauthy_models::entity::password::PasswordPolicy;
Expand All @@ -26,7 +26,7 @@ use rauthy_models::response::{
PasskeyResponse, UserAttrConfigResponse, UserAttrValueResponse, UserAttrValuesResponse,
UserResponse,
};
use rauthy_models::templates::UserRegisterHtml;
use rauthy_models::templates::{ErrorHtml, UserRegisterHtml};
use rauthy_service::password_reset;
use std::ops::Add;
use time::OffsetDateTime;
Expand Down Expand Up @@ -235,15 +235,18 @@ pub async fn get_users_register(
data: web::Data<AppState>,
req: HttpRequest,
) -> Result<HttpResponse, ErrorResponse> {
let colors = ColorEntity::find_rauthy(&data).await?;
let lang = Language::try_from(&req).unwrap_or_default();

if !*OPEN_USER_REG {
return Err(ErrorResponse::new(
ErrorResponseType::Forbidden,
"Open User Registration is not allowed".to_string(),
return Ok(ErrorHtml::response(
&colors,
&lang,
StatusCode::NOT_FOUND,
Some("Open User Registration is disabled".to_string()),
));
}

let lang = Language::try_from(&req).unwrap_or_default();
let colors = ColorEntity::find_rauthy(&data).await?;
let (body, nonce) = UserRegisterHtml::build(&colors, &lang);
Ok(HttpResponse::Ok()
.insert_header(HEADER_HTML)
Expand Down Expand Up @@ -408,16 +411,19 @@ pub async fn get_user_email_confirm(
data: web::Data<AppState>,
path: web::Path<(String, String)>,
req: HttpRequest,
) -> Result<HttpResponse, ErrorResponse> {
) -> HttpResponse {
let lang = Language::try_from(&req).unwrap_or_default();
let (user_id, confirm_id) = path.into_inner();
User::confirm_email_address(&data, req, user_id, confirm_id)
.await
.map(|(html, nonce)| {
HttpResponse::Ok()
.insert_header(HEADER_HTML)
.insert_header(build_csp_header(&nonce))
.body(html)
})
match User::confirm_email_address(&data, req, user_id, confirm_id).await {
Ok((html, nonce)) => HttpResponse::Ok()
.insert_header(HEADER_HTML)
.insert_header(build_csp_header(&nonce))
.body(html),
Err(err) => {
let colors = ColorEntity::find_rauthy(&data).await.unwrap_or_default();
ErrorHtml::response_from_err(&colors, &lang, err)
}
}
}

/// Endpoint for resetting passwords
Expand All @@ -440,17 +446,20 @@ pub async fn get_user_password_reset(
data: web::Data<AppState>,
path: web::Path<(String, String)>,
req: HttpRequest,
) -> Result<HttpResponse, ErrorResponse> {
) -> HttpResponse {
let lang = Language::try_from(&req).unwrap_or_default();
let (user_id, reset_id) = path.into_inner();
password_reset::handle_get_pwd_reset(&data, req, user_id, reset_id)
.await
.map(|(html, nonce, cookie)| {
HttpResponse::Ok()
.cookie(cookie)
.insert_header(HEADER_HTML)
.insert_header(build_csp_header(&nonce))
.body(html)
})
match password_reset::handle_get_pwd_reset(&data, req, user_id, reset_id).await {
Ok((html, nonce, cookie)) => HttpResponse::Ok()
.cookie(cookie)
.insert_header(HEADER_HTML)
.insert_header(build_csp_header(&nonce))
.body(html),
Err(err) => {
let colors = ColorEntity::find_rauthy(&data).await.unwrap_or_default();
ErrorHtml::response_from_err(&colors, &lang, err)
}
}
}

/// Endpoint for resetting passwords
Expand Down
Loading

0 comments on commit 0e476ab

Please sign in to comment.