mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-11-14 04:37:00 +00:00
197 lines
4.8 KiB
Rust
197 lines
4.8 KiB
Rust
use std::time::SystemTime;
|
|
|
|
use axum::{
|
|
Extension, Router,
|
|
extract::{Query, State},
|
|
response::Redirect,
|
|
routing::on,
|
|
};
|
|
use conduwuit_service::{oauth::grant::AuthorizationCodeResponse, oidc::SessionCompletionStatus};
|
|
use futures::FutureExt;
|
|
use ruma::{OwnedServerName, UserId};
|
|
use serde::{Deserialize, de::IgnoredAny};
|
|
use tower_sessions::Session;
|
|
|
|
use crate::{
|
|
WebError,
|
|
extract::{Expect, PostForm},
|
|
pages::{
|
|
GET_POST, Result, TemplateContext,
|
|
components::UserCard,
|
|
oidc::{OIDC_SESSION_ID_KEY, OidcSession, OidcSessionState},
|
|
},
|
|
response,
|
|
session::{User, UserSession},
|
|
template,
|
|
};
|
|
|
|
pub(crate) fn build() -> Router<crate::State> {
|
|
Router::new().route("/", on(GET_POST, route_complete))
|
|
}
|
|
|
|
template! {
|
|
struct OidcComplete use "oidc_complete.html.j2" {
|
|
body: OidcCompleteBody
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum OidcCompleteBody {
|
|
UsernamePrompt {
|
|
server_name: OwnedServerName,
|
|
username_error: Option<String>,
|
|
},
|
|
PasswordPrompt {
|
|
username: String,
|
|
user_card: UserCard,
|
|
password_error: bool,
|
|
},
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct LoginForm {
|
|
username: String,
|
|
password: Option<String>,
|
|
}
|
|
|
|
async fn route_complete(
|
|
State(services): State<crate::State>,
|
|
Extension(context): Extension<TemplateContext>,
|
|
Expect(Query(query)): Expect<Query<AuthorizationCodeResponse>>,
|
|
session_store: Session,
|
|
user: User<true>,
|
|
PostForm(form): PostForm<LoginForm>,
|
|
) -> Result {
|
|
let user_id = user.into_session().map(|session| session.user_id);
|
|
|
|
let Some(session) = session_store
|
|
.get::<OidcSession>(OIDC_SESSION_ID_KEY)
|
|
.await
|
|
.expect("should be able to deserialize oidc session")
|
|
else {
|
|
return response!(WebError::BadRequest(
|
|
"No OIDC session found. What are you doing here?".to_owned()
|
|
));
|
|
};
|
|
|
|
let session_completion_status = match session.state {
|
|
| OidcSessionState::CodeExchange { expected_user, session: pending_session } => {
|
|
if let (Some(user_id), Some(expected_user)) = (&user_id, &expected_user)
|
|
&& user_id != expected_user
|
|
{
|
|
return response!(WebError::BadRequest(
|
|
"Identity mismatch. You may have switched accounts at your identity \
|
|
provider. Please log out and back in to continue."
|
|
.to_owned()
|
|
));
|
|
}
|
|
|
|
let claims = services
|
|
.oidc
|
|
.exchange_code(pending_session, query)
|
|
.boxed()
|
|
.await
|
|
.map_err(|err| WebError::BadRequest(err.to_owned()))?;
|
|
|
|
session_store
|
|
.insert(OIDC_SESSION_ID_KEY, OidcSession {
|
|
next: session.next.clone(),
|
|
state: OidcSessionState::Authorized { claims: Box::new(claims.clone()) },
|
|
})
|
|
.await
|
|
.expect("Should be able to serialize oidc session");
|
|
|
|
services.oidc.complete_session(&claims, None).await
|
|
},
|
|
| OidcSessionState::Authorized { claims } => {
|
|
let supplied_user_id = if let Some(form) = form {
|
|
if let Ok(user_id) = UserId::parse(format!(
|
|
"@{}:{}",
|
|
&form.username,
|
|
services.globals.server_name()
|
|
)) && services.users.status(&user_id).await.is_active()
|
|
{
|
|
let user_card = UserCard::for_local_user(&services, user_id.clone()).await;
|
|
|
|
if let Some(password) = form.password {
|
|
if services
|
|
.users
|
|
.check_password(&user_id, &password)
|
|
.await
|
|
.is_ok()
|
|
{
|
|
Some(user_id)
|
|
} else {
|
|
return response!(OidcComplete::new(
|
|
context,
|
|
OidcCompleteBody::PasswordPrompt {
|
|
username: form.username,
|
|
user_card,
|
|
password_error: true
|
|
}
|
|
));
|
|
}
|
|
} else {
|
|
return response!(OidcComplete::new(
|
|
context,
|
|
OidcCompleteBody::PasswordPrompt {
|
|
username: form.username,
|
|
user_card,
|
|
password_error: false,
|
|
}
|
|
));
|
|
}
|
|
} else {
|
|
match services
|
|
.users
|
|
.determine_registration_user_id(Some(form.username), None, None)
|
|
.await
|
|
{
|
|
| Ok(user_id) => Some(user_id),
|
|
| Err(err) => {
|
|
return response!(OidcComplete::new(
|
|
context,
|
|
OidcCompleteBody::UsernamePrompt {
|
|
server_name: services.globals.server_name().to_owned(),
|
|
username_error: Some(err.message()),
|
|
}
|
|
));
|
|
},
|
|
}
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
|
|
services
|
|
.oidc
|
|
.complete_session(&claims, supplied_user_id)
|
|
.await
|
|
},
|
|
}
|
|
.map_err(|err| WebError::BadRequest(err.to_owned()))?;
|
|
|
|
match session_completion_status {
|
|
| SessionCompletionStatus::Complete(user_id) => {
|
|
let _ = session_store
|
|
.remove::<IgnoredAny>(OIDC_SESSION_ID_KEY)
|
|
.await;
|
|
|
|
let user_session = UserSession { user_id, last_login: SystemTime::now() };
|
|
|
|
session_store
|
|
.insert(User::KEY, user_session)
|
|
.await
|
|
.expect("should be able to serialize user session");
|
|
|
|
response!(Redirect::to(&session.next.target_path()))
|
|
},
|
|
| SessionCompletionStatus::NeedsUserId => {
|
|
response!(OidcComplete::new(context, OidcCompleteBody::UsernamePrompt {
|
|
server_name: services.globals.server_name().to_owned(),
|
|
username_error: None
|
|
}))
|
|
},
|
|
}
|
|
}
|