From e26d87f4fa18999c6bcfbcf32cfa85adab11acdd Mon Sep 17 00:00:00 2001 From: rtkay123 Date: Sat, 26 Jul 2025 19:24:38 +0200 Subject: feat(auth): create user call --- crates/auth/src/server/routes/authorised.rs | 232 --------------------- crates/auth/src/server/routes/discord.rs | 10 - .../auth/src/server/routes/discord/discord_auth.rs | 58 ------ 3 files changed, 300 deletions(-) delete mode 100644 crates/auth/src/server/routes/authorised.rs delete mode 100644 crates/auth/src/server/routes/discord.rs delete mode 100644 crates/auth/src/server/routes/discord/discord_auth.rs (limited to 'crates/auth/src/server/routes') diff --git a/crates/auth/src/server/routes/authorised.rs b/crates/auth/src/server/routes/authorised.rs deleted file mode 100644 index 32dd929..0000000 --- a/crates/auth/src/server/routes/authorised.rs +++ /dev/null @@ -1,232 +0,0 @@ -use std::{str::FromStr, time::Duration}; - -use anyhow::Context; -use axum::{ - extract::{Query, State}, - http::HeaderMap, - response::{IntoResponse, Redirect}, -}; -use axum_extra::{TypedHeader, headers}; -use oauth2::{AuthorizationCode, TokenResponse}; -use reqwest::{StatusCode, header::SET_COOKIE}; -use sellershut_core::profile::CreateUserRequest; -use serde::{Deserialize, Serialize}; -use sqlx::types::uuid; -use time::OffsetDateTime; -use tower_sessions::{ - SessionStore, - session::{Id, Record}, -}; -use uuid::Uuid; - -use crate::{ - auth::Claims, - error::AppError, - server::{ - OAUTH_CSRF_COOKIE, csrf_token_validation::csrf_token_validation_workflow, routes::Provider, - }, - state::AppHandle, -}; - -#[derive(Debug, Deserialize)] -pub struct AuthRequest { - provider: Provider, - code: String, - pub state: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct User { - id: String, - avatar: Option, - username: String, - discriminator: String, - verified: bool, - email: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct DbUser { - id: Uuid, - email: String, - created_at: OffsetDateTime, - updated_at: OffsetDateTime, -} - -/// The cookie to store the session id for user information. -const SESSION_COOKIE: &str = "info"; -const SESSION_DATA_KEY: &str = "data"; - -pub async fn login_authorised( - Query(query): Query, - State(state): State, - TypedHeader(cookies): TypedHeader, -) -> Result { - let provider = query.provider.to_string(); - let oauth_session_id = Id::from_str( - cookies - .get(OAUTH_CSRF_COOKIE) - .context("missing session cookie")?, - )?; - csrf_token_validation_workflow(&query, &state.session_store, oauth_session_id).await?; - - let client = state.http_client.clone(); - let store = state.session_store.clone(); - - // Get an auth token - let token = state - .discord_client - .exchange_code(AuthorizationCode::new(query.code.clone())) - .request_async(&client) - .await - .context("failed in sending request request to authorisation server")?; - - let user_data = client - // https://discord.com/developers/docs/resources/user#get-current-user - .get("https://discordapp.com/api/users/@me") - .bearer_auth(token.access_token().secret()) - .send() - .await - .context("failed in sending request to target Url")? - .json::() - .await - .context("failed to deserialise response as JSON")?; - - dbg!(&user_data); - - let user_data: User = serde_json::from_value(user_data)?; - - if !user_data.verified { - return Ok((StatusCode::UNAUTHORIZED, "email is not verified").into_response()); - } - - // Create a new session filled with user data - let session_id = Id(i128::from_le_bytes(uuid::Uuid::new_v4().to_bytes_le())); - - let mut transaction = state.services.postgres.begin().await?; - - let user = sqlx::query_as!( - DbUser, - " - select - p.* - from - auth_user p - inner join - oauth_account a - on - p.id=a.user_id - where a.provider_id = $1 and a.provider_user_id = $2 - ", - provider, - user_data.id - ) - .fetch_optional(&mut *transaction) - .await?; - - let user = if let Some(user) = user { - user - } else { - let uuid = uuid::Uuid::now_v7(); - let user = sqlx::query_as!( - DbUser, - "insert into auth_user (id, email) values ($1, $2) - on conflict (email) do update - set email = excluded.email - returning *; - ", - uuid, - user_data.email, - ) - .fetch_one(&mut *transaction) - .await?; - - sqlx::query_as!( - DbUser, - "with upsert as ( - insert into oauth_account (provider_id, provider_user_id, user_id) values ($1, $2, $3) - on conflict (provider_id, provider_user_id) do update - set provider_id = excluded.provider_id -- no-op - returning user_id - ) - select u.* - from upsert - join auth_user u on u.id = upsert.user_id; - ", - provider, - user_data.id, - user.id - ) - .fetch_one(&mut *transaction) - .await? - }; - - let exp = OffsetDateTime::now_utc() + Duration::from_secs(15 * 60); - - let claims = Claims { - sub: user.id, - exp: exp.unix_timestamp(), - iss: "sellershut".to_owned(), - sid: session_id.to_string(), - aud: "sellershut".to_owned(), - iat: OffsetDateTime::now_utc().unix_timestamp(), - }; - - let token = jsonwebtoken::encode( - &jsonwebtoken::Header::default(), - &claims, - &jsonwebtoken::EncodingKey::from_secret( - state.local_config.oauth.jwt_encoding_key.as_bytes(), - ), - )?; - - let user_request = CreateUserRequest{ - email: user_data.email.to_owned(), - avatar: user_data.avatar.as_ref().map(|value| { - format!( - "https://cdn.discordapp.com/avatars/{}/{value}", - user_data.id - ) - }) - }; - - - store - .create(&mut Record { - id: session_id, - data: [( - SESSION_DATA_KEY.to_string(), - serde_json::to_value(user_data).unwrap(), - )] - .into(), - expiry_date: time::OffsetDateTime::now_utc() - + Duration::from_secs(state.local_config.oauth.session_lifespan), - }) - .await - .context("failed in inserting serialised value into session")?; - - sqlx::query!( - "insert into token (user_id, token, session_id) values ($1, $2, $3)", - user.id, - token, - session_id.to_string() - ) - .execute(&mut *transaction) - .await?; - - let cookie = format!("{SESSION_COOKIE}={session_id}; SameSite=Lax; HttpOnly; Secure; Path=/"); - - let mut profile_client = state.profile_client.clone(); - let resp = profile_client.create_user(user_request).await?.into_inner(); - let user_id = resp.temp_id; - - let mut headers = HeaderMap::new(); - headers.insert( - SET_COOKIE, - cookie.parse().context("failed to parse cookie")?, - ); - - transaction.commit().await?; - - Ok((headers, Redirect::to(&format!("/?user={user_id}&token={token}"))).into_response()) -} diff --git a/crates/auth/src/server/routes/discord.rs b/crates/auth/src/server/routes/discord.rs deleted file mode 100644 index e1a834f..0000000 --- a/crates/auth/src/server/routes/discord.rs +++ /dev/null @@ -1,10 +0,0 @@ -mod discord_auth; -use axum::{Router, routing::get}; - -use crate::state::AppHandle; - -pub fn discord_router(state: AppHandle) -> Router { - Router::new() - .route("/auth/discord", get(discord_auth::discord_auth)) - .with_state(state) -} diff --git a/crates/auth/src/server/routes/discord/discord_auth.rs b/crates/auth/src/server/routes/discord/discord_auth.rs deleted file mode 100644 index a45de86..0000000 --- a/crates/auth/src/server/routes/discord/discord_auth.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::time::Duration; - -use anyhow::Context; -use axum::{ - extract::State, - http::HeaderMap, - response::{IntoResponse, Redirect}, -}; -use oauth2::{CsrfToken, Scope}; -use reqwest::header::SET_COOKIE; -use sqlx::types::uuid; -use tower_sessions::{ - SessionStore, - session::{Id, Record}, -}; - -use crate::{ - error::AppError, - server::{CSRF_TOKEN, OAUTH_CSRF_COOKIE}, - state::AppHandle, -}; - -pub async fn discord_auth(State(state): State) -> Result { - let (auth_url, csrf_token) = state - .discord_client - .authorize_url(CsrfToken::new_random) - .add_scope(Scope::new("identify".to_string())) - .url(); - - // Store the token in the session and retrieve the session cookie. - let session_id = Id(i128::from_le_bytes(uuid::Uuid::new_v4().to_bytes_le())); - let store = state.session_store.clone(); - - store - .create(&mut Record { - id: session_id, - data: [( - CSRF_TOKEN.to_string(), - serde_json::to_value(csrf_token).unwrap(), - )] - .into(), - expiry_date: time::OffsetDateTime::now_utc() - + Duration::from_secs(state.local_config.oauth.session_lifespan), - }) - .await - .context("failed in inserting CSRF token into session")?; - - // Attach the session cookie to the response header - let cookie = - format!("{OAUTH_CSRF_COOKIE}={session_id}; SameSite=Lax; HttpOnly; Secure; Path=/"); - let mut headers = HeaderMap::new(); - headers.insert( - SET_COOKIE, - cookie.parse().context("failed to parse cookie")?, - ); - - Ok((headers, Redirect::to(auth_url.as_ref()))) -} -- cgit v1.2.3