diff --git a/crates/app/src/langs/en-US.toml b/crates/app/src/langs/en-US.toml index bac4974..30164fe 100644 --- a/crates/app/src/langs/en-US.toml +++ b/crates/app/src/langs/en-US.toml @@ -17,6 +17,8 @@ version = "1.0.0" "general:action.back" = "Back" "general:action.report" = "Report" "general:action.manage" = "Manage" +"general:action.add_account" = "Add account" +"general:action.switch_account" = "Switch account" "general:label.mod" = "Mod" "general:label.file_report" = "File report" "general:label.account_banned" = "Account banned" diff --git a/crates/app/src/public/html/auth/login.html b/crates/app/src/public/html/auth/login.html index 0aea70a..68757d4 100644 --- a/crates/app/src/public/html/auth/login.html +++ b/crates/app/src/public/html/auth/login.html @@ -48,6 +48,12 @@ ]); if (res.ok) { + // update tokens + const new_tokens = ns("me").LOGIN_ACCOUNT_TOKENS; + new_tokens[e.target.username.value] = res.message; + trigger("me::set_login_account_tokens", [new_tokens]); + + // redirect setTimeout(() => { window.location.href = "/"; }, 150); diff --git a/crates/app/src/public/html/auth/register.html b/crates/app/src/public/html/auth/register.html index 1fcf70b..83c46fc 100644 --- a/crates/app/src/public/html/auth/register.html +++ b/crates/app/src/public/html/auth/register.html @@ -100,6 +100,12 @@ ]); if (res.ok) { + // update tokens + const new_tokens = ns("me").LOGIN_ACCOUNT_TOKENS; + new_tokens[e.target.username.value] = res.message; + trigger("me::set_login_account_tokens", [new_tokens]); + + // redirect setTimeout(() => { window.location.href = "/"; }, 150); diff --git a/crates/app/src/public/html/macros.html b/crates/app/src/public/html/macros.html index a90103f..18a6f6a 100644 --- a/crates/app/src/public/html/macros.html +++ b/crates/app/src/public/html/macros.html @@ -107,6 +107,11 @@ show_lhs=true) -%}
+ + + + + + + + {% endif %} diff --git a/crates/app/src/public/js/me.js b/crates/app/src/public/js/me.js index db25c2b..3942020 100644 --- a/crates/app/src/public/js/me.js +++ b/crates/app/src/public/js/me.js @@ -1,6 +1,10 @@ (() => { const self = reg_ns("me"); + self.LOGIN_ACCOUNT_TOKENS = JSON.parse( + window.localStorage.getItem("atto:login_account_tokens") || "{}", + ); + self.define("logout", async () => { if ( !(await trigger("atto::confirm", [ @@ -162,4 +166,48 @@ } }); }); + + // token switcher + self.define( + "set_login_account_tokens", + ({ $ }, value) => { + $.LOGIN_ACCOUNT_TOKENS = value; + window.localStorage.setItem( + "atto:login_account_tokens", + JSON.stringify(value), + ); + }, + ["object"], + ); + + self.define("login", ({ $ }, username) => { + const token = self.LOGIN_ACCOUNT_TOKENS[username]; + + if (!token) { + return; + } + + window.location.href = `/api/v1/auth/token?token=${token}`; + }); + + self.define("render_token_picker", ({ $ }, element) => { + element.innerHTML = ""; + for (const token of Object.entries($.LOGIN_ACCOUNT_TOKENS)) { + element.innerHTML += ``; + } + }); + + self.define("switch_account", () => { + document.getElementById("tokens_dialog").showModal(); + }); })(); diff --git a/crates/app/src/routes/api/v1/auth/mod.rs b/crates/app/src/routes/api/v1/auth/mod.rs index 4aab726..0d51c02 100644 --- a/crates/app/src/routes/api/v1/auth/mod.rs +++ b/crates/app/src/routes/api/v1/auth/mod.rs @@ -9,11 +9,13 @@ use crate::{ model::{ApiReturn, Error, auth::User}, }; use axum::{ - Extension, Json, + extract::Query, http::{HeaderMap, HeaderValue}, - response::IntoResponse, + response::{IntoResponse, Redirect}, + Extension, Json, }; use axum_extra::extract::CookieJar; +use serde::Deserialize; use tetratto_shared::hash::hash; use cf_turnstile::{SiteVerifyRequest, TurnstileClient}; @@ -21,23 +23,23 @@ use cf_turnstile::{SiteVerifyRequest, TurnstileClient}; /// `/api/v1/auth/register` pub async fn register_request( headers: HeaderMap, - jar: CookieJar, + // jar: CookieJar, Extension(data): Extension, Json(props): Json, ) -> impl IntoResponse { let data = &(data.read().await).0; - let user = get_user_from_token!(jar, data); + // let user = get_user_from_token!(jar, data); - if user.is_some() { - return ( - None, - Json(ApiReturn { - ok: false, - message: Error::AlreadyAuthenticated.to_string(), - payload: (), - }), - ); - } + // if user.is_some() { + // return ( + // None, + // Json(ApiReturn { + // ok: false, + // message: Error::AlreadyAuthenticated.to_string(), + // payload: (), + // }), + // ); + // } // get real ip let real_ip = headers @@ -93,7 +95,7 @@ pub async fn register_request( )]), Json(ApiReturn { ok: true, - message: "User created".to_string(), + message: initial_token, payload: (), }), ), @@ -104,16 +106,16 @@ pub async fn register_request( /// `/api/v1/auth/login` pub async fn login_request( headers: HeaderMap, - jar: CookieJar, + // jar: CookieJar, Extension(data): Extension, Json(props): Json, ) -> impl IntoResponse { let data = &(data.read().await).0; - let user = get_user_from_token!(jar, data); + // let user = get_user_from_token!(jar, data); - if user.is_some() { - return (None, Json(Error::AlreadyAuthenticated.into())); - } + // if user.is_some() { + // return (None, Json(Error::AlreadyAuthenticated.into())); + // } // get real ip let real_ip = headers @@ -211,3 +213,32 @@ pub async fn logout_request( }), ) } + +#[derive(Deserialize)] +pub struct SetTokenQuery { + #[serde(default)] + pub token: String, +} + +/// Set the current user token. +pub async fn set_token_request(Query(props): Query) -> impl IntoResponse { + ( + { + let mut headers = HeaderMap::new(); + + headers.insert( + "Set-Cookie", + format!( + "__Secure-atto-token={}; SameSite=Lax; Secure; Path=/; HostOnly=true; HttpOnly=true; Max-Age={}", + props.token, + 60* 60 * 24 * 365 + ) + .parse() + .unwrap(), + ); + + headers + }, + Redirect::to("/"), + ) +} diff --git a/crates/app/src/routes/api/v1/mod.rs b/crates/app/src/routes/api/v1/mod.rs index ecfe4f9..70cff74 100644 --- a/crates/app/src/routes/api/v1/mod.rs +++ b/crates/app/src/routes/api/v1/mod.rs @@ -94,6 +94,7 @@ pub fn routes() -> Router { .route("/auth/register", post(auth::register_request)) .route("/auth/login", post(auth::login_request)) .route("/auth/logout", post(auth::logout_request)) + .route("/auth/token", get(auth::set_token_request)) .route( "/auth/upload/avatar", post(auth::images::upload_avatar_request), diff --git a/crates/app/src/routes/api/v1/util.rs b/crates/app/src/routes/api/v1/util.rs index 44057ab..3b5152c 100644 --- a/crates/app/src/routes/api/v1/util.rs +++ b/crates/app/src/routes/api/v1/util.rs @@ -109,7 +109,7 @@ pub struct LangFileQuery { pub id: String, } -/// Set the current language +/// Set the current language. pub async fn set_langfile_request(Query(props): Query) -> impl IntoResponse { ( { diff --git a/crates/app/src/routes/pages/auth.rs b/crates/app/src/routes/pages/auth.rs index b814c44..adf1253 100644 --- a/crates/app/src/routes/pages/auth.rs +++ b/crates/app/src/routes/pages/auth.rs @@ -1,7 +1,7 @@ use crate::{State, assets::initial_context, get_lang, get_user_from_token}; use axum::{ Extension, - response::{Html, IntoResponse, Redirect}, + response::{Html, IntoResponse}, }; use axum_extra::extract::CookieJar; @@ -10,14 +10,14 @@ pub async fn login_request(jar: CookieJar, Extension(data): Extension) -> let data = data.read().await; let user = get_user_from_token!(jar, data.0); - if user.is_some() { - return Err(Redirect::to("/")); - } + // if user.is_some() { + // return Err(Redirect::to("/")); + // } let lang = get_lang!(jar, data.0); let context = initial_context(&data.0.0, lang, &user).await; - Ok(Html(data.1.render("auth/login.html", &context).unwrap())) + Html(data.1.render("auth/login.html", &context).unwrap()) } /// `/auth/register` @@ -28,12 +28,12 @@ pub async fn register_request( let data = data.read().await; let user = get_user_from_token!(jar, data.0); - if user.is_some() { - return Err(Redirect::to("/")); - } + // if user.is_some() { + // return Err(Redirect::to("/")); + // } let lang = get_lang!(jar, data.0); let context = initial_context(&data.0.0, lang, &user).await; - Ok(Html(data.1.render("auth/register.html", &context).unwrap())) + Html(data.1.render("auth/register.html", &context).unwrap()) } diff --git a/crates/core/src/database/communities.rs b/crates/core/src/database/communities.rs index 3a52a67..8f8d9cf 100644 --- a/crates/core/src/database/communities.rs +++ b/crates/core/src/database/communities.rs @@ -133,7 +133,7 @@ impl DataManager { let res = query_rows!( &conn, - "SELECT * FROM communities ORDER BY likes DESC LIMIT 12", + "SELECT * FROM communities ORDER BY member_count DESC LIMIT 12", empty, |x| { Self::get_community_from_row(x) } ); diff --git a/crates/core/src/database/reactions.rs b/crates/core/src/database/reactions.rs index 760c197..3ee489a 100644 --- a/crates/core/src/database/reactions.rs +++ b/crates/core/src/database/reactions.rs @@ -107,8 +107,8 @@ impl DataManager { .create_notification(Notification::new( "Your community has received a like!".to_string(), format!( - "[@{}](/api/v1/auth/profile/find/{}) has liked your community!", - user.username, user.id + "[@{}](/api/v1/auth/profile/find/{}) has liked your [community](/api/v1/communities/find/{})!", + user.username, user.id, community.id ), community.owner, ))