summaryrefslogtreecommitdiffstats
path: root/crates/auth/src/server/routes
diff options
context:
space:
mode:
authorrtkay123 <dev@kanjala.com>2025-07-26 19:24:38 +0200
committerrtkay123 <dev@kanjala.com>2025-07-26 19:24:38 +0200
commite26d87f4fa18999c6bcfbcf32cfa85adab11acdd (patch)
tree603c6dacb6c448984bdcc5fa2b4a9314f1a23960 /crates/auth/src/server/routes
parent236876f1d0539ac22a3977fd8599933725ad0f90 (diff)
downloadsellershut-e26d87f4fa18999c6bcfbcf32cfa85adab11acdd.tar.bz2
sellershut-e26d87f4fa18999c6bcfbcf32cfa85adab11acdd.zip
feat(auth): create user call
Diffstat (limited to 'crates/auth/src/server/routes')
-rw-r--r--crates/auth/src/server/routes/authorised.rs232
-rw-r--r--crates/auth/src/server/routes/discord.rs10
-rw-r--r--crates/auth/src/server/routes/discord/discord_auth.rs58
3 files changed, 0 insertions, 300 deletions
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<String>,
- 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<AuthRequest>,
- State(state): State<AppHandle>,
- TypedHeader(cookies): TypedHeader<headers::Cookie>,
-) -> Result<impl IntoResponse, AppError> {
- 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::<serde_json::Value>()
- .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<AppHandle>) -> Result<impl IntoResponse, AppError> {
- 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())))
-}