Handle oauth2 errors
This commit is contained in:
1 parent
93d7382a46
commit
9328183ce4
3 files changed
+79
-5
No files matched your search
+48
-3
@@ -156,12 +156,14 @@ pub(super) async fn request_is_allowed(pool: &PgPool, user: &WebUser, path: &str
|
|||||||
channel_id.is_some_and(|channel_id| user.channel_ids.contains(&channel_id))
|
channel_id.is_some_and(|channel_id| user.channel_ids.contains(&channel_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn login(session: Session) -> Response {
|
pub(super) async fn login(session: Session, Query(query): Query<LoginQuery>) -> Response {
|
||||||
match session.get::<WebUser>("user").await {
|
match session.get::<WebUser>("user").await {
|
||||||
Ok(Some(_)) => Redirect::to("/").into_response(),
|
Ok(Some(_)) => Redirect::to("/").into_response(),
|
||||||
Ok(None) => Html(render_page(
|
Ok(None) => Html(render_page(
|
||||||
"Log in",
|
"Log in",
|
||||||
&render_template(&LoginTemplate),
|
&render_template(&LoginTemplate {
|
||||||
|
error: query.error.as_deref(),
|
||||||
|
}),
|
||||||
false,
|
false,
|
||||||
))
|
))
|
||||||
.into_response(),
|
.into_response(),
|
||||||
@@ -354,6 +356,9 @@ pub(super) async fn discord_callback(
|
|||||||
session: Session,
|
session: Session,
|
||||||
Query(query): Query<OAuthQuery>,
|
Query(query): Query<OAuthQuery>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
|
if !query.code.is_some() {
|
||||||
|
return oauth2_error_handling(query.error.as_deref().unwrap_or("none"));
|
||||||
|
}
|
||||||
let state = session.remove::<String>("oauth_state").await;
|
let state = session.remove::<String>("oauth_state").await;
|
||||||
if !matches!(state, Ok(Some(state)) if state == query.state) {
|
if !matches!(state, Ok(Some(state)) if state == query.state) {
|
||||||
return (StatusCode::BAD_REQUEST, "Invalid OAuth state").into_response();
|
return (StatusCode::BAD_REQUEST, "Invalid OAuth state").into_response();
|
||||||
@@ -365,7 +370,7 @@ pub(super) async fn discord_callback(
|
|||||||
("client_id", data.client_id.as_str()),
|
("client_id", data.client_id.as_str()),
|
||||||
("client_secret", data.client_secret.as_str()),
|
("client_secret", data.client_secret.as_str()),
|
||||||
("grant_type", "authorization_code"),
|
("grant_type", "authorization_code"),
|
||||||
("code", query.code.as_str()),
|
("code", query.code.expect("Missing code in query").as_str()),
|
||||||
("redirect_uri", data.redirect_uri.as_str()),
|
("redirect_uri", data.redirect_uri.as_str()),
|
||||||
])
|
])
|
||||||
.send()
|
.send()
|
||||||
@@ -424,6 +429,46 @@ pub(super) async fn discord_callback(
|
|||||||
Redirect::to("/").into_response()
|
Redirect::to("/").into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn oauth2_error_handling(error_code: &str) -> Response {
|
||||||
|
return match error_code {
|
||||||
|
"access_denied" => Redirect::to(
|
||||||
|
"/login?error=You denied the request. Please log in again and hit Authorize to continue."
|
||||||
|
).into_response(),
|
||||||
|
|
||||||
|
"invalid_request" => Redirect::to(
|
||||||
|
"/login?error=Error code: invalid_request. Please try again. If this keeps happening, report this issue."
|
||||||
|
).into_response(),
|
||||||
|
|
||||||
|
"unauthorized_client" => Redirect::to(
|
||||||
|
"/login?error=Error code: unauthorized_client. Please try again. If this keeps happening, report this issue."
|
||||||
|
).into_response(),
|
||||||
|
|
||||||
|
"unsupported_response_type" => Redirect::to(
|
||||||
|
"/login?error=Error code: unsupported_response_type. Please try again. If this keeps happening, report this issue."
|
||||||
|
).into_response(),
|
||||||
|
|
||||||
|
"invalid_scope" => Redirect::to(
|
||||||
|
"/login?error=Error code: invalid_scope. Please try again. If this keeps happening, report this issue. Also if this is just you messing with the oauth2 scope, stop."
|
||||||
|
).into_response(),
|
||||||
|
|
||||||
|
"server_error" => Redirect::to(
|
||||||
|
"/login?error=Discord encountered an internal error. Please try again."
|
||||||
|
).into_response(),
|
||||||
|
|
||||||
|
"temporarily_unavailable" => Redirect::to(
|
||||||
|
"/login?error=Discord authentication is temporarily unavailable. Please try again later and check discordstatus.com for updates."
|
||||||
|
).into_response(),
|
||||||
|
|
||||||
|
"none" => Redirect::to(
|
||||||
|
"/login?error=Error code: none. Please try again. If this keeps happening, report this issue. Also if this is just you messing with the oauth2 scope, stop."
|
||||||
|
).into_response(),
|
||||||
|
|
||||||
|
_ => Redirect::to(
|
||||||
|
"/login?error=Error code: unknown. Please try again. If this keeps happening, report this issue."
|
||||||
|
).into_response(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) async fn logout(session: Session) -> Redirect {
|
pub(super) async fn logout(session: Session) -> Redirect {
|
||||||
let _ = session.delete().await;
|
let _ = session.delete().await;
|
||||||
Redirect::to("/login")
|
Redirect::to("/login")
|
||||||
|
|||||||
+20
-2
@@ -90,7 +90,9 @@ struct TimezoneContext {
|
|||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "login.html")]
|
#[template(path = "login.html")]
|
||||||
struct LoginTemplate;
|
struct LoginTemplate<'a> {
|
||||||
|
error: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "privacy.html")]
|
#[template(path = "privacy.html")]
|
||||||
@@ -432,9 +434,16 @@ struct ErrorTemplate<'a> {
|
|||||||
message: &'a str,
|
message: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct LoginQuery {
|
||||||
|
error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct OAuthQuery {
|
struct OAuthQuery {
|
||||||
code: String,
|
code: Option<String>,
|
||||||
|
error: Option<String>,
|
||||||
|
error_description: Option<String>,
|
||||||
state: String,
|
state: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -991,6 +1000,15 @@ mod tests {
|
|||||||
assert!(html.contains("<new>"));
|
assert!(html.contains("<new>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn login_page_renders_and_escapes_query_error() {
|
||||||
|
let html = render_template(&LoginTemplate {
|
||||||
|
error: Some("Login failed: <try again>"),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(html.contains("Login failed: <try again>"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn renders_short_pagination_without_arbitrary_page_form() {
|
fn renders_short_pagination_without_arbitrary_page_form() {
|
||||||
let pagination = Pagination::new(2, ITEMS_PER_PAGE * 3);
|
let pagination = Pagination::new(2, ITEMS_PER_PAGE * 3);
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
<h2>Log in</h2>
|
<h2>Log in</h2>
|
||||||
|
{% if let Some(error) = error %}
|
||||||
|
<div style="
|
||||||
|
background-color: #fee2e2;
|
||||||
|
color: #991b1b;
|
||||||
|
border: 1px solid #ef4444;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
margin: 12px 0;">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<p>It is required to log in with Discord to view the archive.</p>
|
<p>It is required to log in with Discord to view the archive.</p>
|
||||||
<p>The login happens on the official Discord website. This site does not have access to your email or password.</p>
|
<p>The login happens on the official Discord website. This site does not have access to your email or password.</p>
|
||||||
<form method="get" action="/login/discord">
|
<form method="get" action="/login/discord">
|
||||||
|
|||||||
Reference in new issue
Block a user