mirror of
https://github.com/Nutomic/ibis.git
synced 2025-02-06 04:14:40 +00:00
121 lines
3.9 KiB
Rust
121 lines
3.9 KiB
Rust
use crate::backend::database::{read_jwt_secret, MyDataHandle};
|
|
use crate::backend::error::MyResult;
|
|
use crate::common::{DbLocalUser, DbPerson, LocalUserView, LoginUserData, RegisterUserData};
|
|
use activitypub_federation::config::Data;
|
|
use anyhow::anyhow;
|
|
use axum::{Form, Json};
|
|
use axum_extra::extract::cookie::{Cookie, CookieJar, Expiration, SameSite};
|
|
use axum_macros::debug_handler;
|
|
use bcrypt::verify;
|
|
use chrono::Utc;
|
|
use jsonwebtoken::DecodingKey;
|
|
use jsonwebtoken::Validation;
|
|
use jsonwebtoken::{decode, get_current_timestamp};
|
|
use jsonwebtoken::{encode, EncodingKey, Header};
|
|
use serde::{Deserialize, Serialize};
|
|
use time::{Duration, OffsetDateTime};
|
|
|
|
pub static AUTH_COOKIE: &str = "auth";
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
struct Claims {
|
|
/// local_user.id
|
|
pub sub: String,
|
|
/// hostname
|
|
pub iss: String,
|
|
/// Creation time as unix timestamp
|
|
pub iat: i64,
|
|
/// Expiration time
|
|
pub exp: u64,
|
|
}
|
|
|
|
fn generate_login_token(local_user: &DbLocalUser, data: &Data<MyDataHandle>) -> MyResult<String> {
|
|
let hostname = data.domain().to_string();
|
|
let claims = Claims {
|
|
sub: local_user.id.to_string(),
|
|
iss: hostname,
|
|
iat: Utc::now().timestamp(),
|
|
exp: get_current_timestamp() + 60 * 60 * 24 * 365,
|
|
};
|
|
|
|
let secret = read_jwt_secret(data)?;
|
|
let key = EncodingKey::from_secret(secret.as_bytes());
|
|
let jwt = encode(&Header::default(), &claims, &key)?;
|
|
Ok(jwt)
|
|
}
|
|
|
|
pub async fn validate(jwt: &str, data: &Data<MyDataHandle>) -> MyResult<LocalUserView> {
|
|
let validation = Validation::default();
|
|
let secret = read_jwt_secret(data)?;
|
|
let key = DecodingKey::from_secret(secret.as_bytes());
|
|
let claims = decode::<Claims>(jwt, &key, &validation)?;
|
|
DbPerson::read_local_from_id(claims.claims.sub.parse()?, data)
|
|
}
|
|
|
|
#[debug_handler]
|
|
pub(in crate::backend::api) async fn register_user(
|
|
data: Data<MyDataHandle>,
|
|
jar: CookieJar,
|
|
Form(form): Form<RegisterUserData>,
|
|
) -> MyResult<(CookieJar, Json<LocalUserView>)> {
|
|
let user = DbPerson::create_local(form.username, form.password, &data)?;
|
|
let token = generate_login_token(&user.local_user, &data)?;
|
|
let jar = jar.add(create_cookie(token, &data));
|
|
Ok((jar, Json(user)))
|
|
}
|
|
|
|
#[debug_handler]
|
|
pub(in crate::backend::api) async fn login_user(
|
|
data: Data<MyDataHandle>,
|
|
jar: CookieJar,
|
|
Form(form): Form<LoginUserData>,
|
|
) -> MyResult<(CookieJar, Json<LocalUserView>)> {
|
|
let user = DbPerson::read_local_from_name(&form.username, &data)?;
|
|
let valid = verify(&form.password, &user.local_user.password_encrypted)?;
|
|
if !valid {
|
|
return Err(anyhow!("Invalid login").into());
|
|
}
|
|
let token = generate_login_token(&user.local_user, &data)?;
|
|
let jar = jar.add(create_cookie(token, &data));
|
|
Ok((jar, Json(user)))
|
|
}
|
|
|
|
fn create_cookie(jwt: String, data: &Data<MyDataHandle>) -> Cookie<'static> {
|
|
let mut domain = data.domain().to_string();
|
|
// remove port from domain
|
|
if domain.contains(':') {
|
|
domain = domain.split(':').collect::<Vec<_>>()[0].to_string();
|
|
}
|
|
Cookie::build(AUTH_COOKIE, jwt)
|
|
.domain(domain)
|
|
.same_site(SameSite::Strict)
|
|
.path("/")
|
|
.http_only(true)
|
|
.secure(true)
|
|
.expires(Expiration::DateTime(
|
|
OffsetDateTime::now_utc() + Duration::weeks(52),
|
|
))
|
|
.finish()
|
|
}
|
|
|
|
#[debug_handler]
|
|
pub(in crate::backend::api) async fn my_profile(
|
|
data: Data<MyDataHandle>,
|
|
jar: CookieJar,
|
|
) -> MyResult<Json<LocalUserView>> {
|
|
let jwt = jar.get(AUTH_COOKIE).map(|c| c.value());
|
|
if let Some(jwt) = jwt {
|
|
Ok(Json(validate(jwt, &data).await?))
|
|
} else {
|
|
Err(anyhow!("invalid/missing auth").into())
|
|
}
|
|
}
|
|
|
|
#[debug_handler]
|
|
pub(in crate::backend::api) async fn logout_user(
|
|
data: Data<MyDataHandle>,
|
|
jar: CookieJar,
|
|
) -> MyResult<CookieJar> {
|
|
let jar = jar.remove(create_cookie(String::new(), &data));
|
|
Ok(jar)
|
|
}
|