parent
6a47b867a4
commit
d703501b78
12 changed files with 4062 additions and 72 deletions
64
src/auth.rs
64
src/auth.rs
|
|
@ -13,7 +13,6 @@ use uuid::Uuid;
|
|||
|
||||
use crate::{AppState, db};
|
||||
|
||||
const SESSION_COOKIE: &str = "chattz_session";
|
||||
const OAUTH_STATE_COOKIE: &str = "chattz_oauth_state";
|
||||
const SESSION_TTL_SECS: u64 = 60 * 60 * 24 * 7;
|
||||
|
||||
|
|
@ -65,7 +64,13 @@ struct ErrorBody<'a> {
|
|||
|
||||
impl IntoResponse for ApiError {
|
||||
fn into_response(self) -> Response {
|
||||
(self.status, Json(ErrorBody { error: &self.message })).into_response()
|
||||
(
|
||||
self.status,
|
||||
Json(ErrorBody {
|
||||
error: &self.message,
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -84,9 +89,13 @@ where
|
|||
|
||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||
let app = AppState::from_ref(state);
|
||||
let token = read_cookie(parts, SESSION_COOKIE).ok_or_else(|| ApiError::unauthorized("missing session"))?;
|
||||
|
||||
let token = read_bearer_token(parts)
|
||||
.or_else(|| read_query_token(parts))
|
||||
.ok_or_else(|| ApiError::unauthorized("missing jwt token"))?;
|
||||
|
||||
let user_id = verify_session(&token, &app.settings.session_secret)
|
||||
.map_err(|_| ApiError::unauthorized("invalid session"))?;
|
||||
.map_err(|_| ApiError::unauthorized("invalid token"))?;
|
||||
|
||||
let exists = db::user_exists(&app.db, user_id)
|
||||
.await
|
||||
|
|
@ -129,7 +138,7 @@ pub fn read_oauth_state_from_headers(headers: &axum::http::HeaderMap) -> Option<
|
|||
})
|
||||
}
|
||||
|
||||
pub fn new_session_cookie(user_id: Uuid, secret: &str, secure: bool) -> Result<String> {
|
||||
pub fn new_jwt_token(user_id: Uuid, secret: &str) -> Result<String> {
|
||||
let now = now_ts();
|
||||
let claims = SessionClaims {
|
||||
sub: user_id.to_string(),
|
||||
|
|
@ -144,20 +153,7 @@ pub fn new_session_cookie(user_id: Uuid, secret: &str, secure: bool) -> Result<S
|
|||
)
|
||||
.context("failed to encode session token")?;
|
||||
|
||||
Ok(format!(
|
||||
"{name}={token}; Path=/; HttpOnly; SameSite=Lax; Max-Age={ttl}{secure_flag}",
|
||||
name = SESSION_COOKIE,
|
||||
ttl = SESSION_TTL_SECS,
|
||||
secure_flag = if secure { "; Secure" } else { "" }
|
||||
))
|
||||
}
|
||||
|
||||
pub fn clear_session_cookie(secure: bool) -> String {
|
||||
format!(
|
||||
"{name}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0{secure_flag}",
|
||||
name = SESSION_COOKIE,
|
||||
secure_flag = if secure { "; Secure" } else { "" }
|
||||
)
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
fn verify_session(token: &str, secret: &str) -> Result<Uuid> {
|
||||
|
|
@ -183,14 +179,32 @@ pub fn validate_oauth_state(expected_cookie: Option<String>, query_state: &str)
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn read_cookie(parts: &Parts, name: &str) -> Option<String> {
|
||||
let raw = parts.headers.get(axum::http::header::COOKIE)?.to_str().ok()?;
|
||||
raw.split(';').find_map(|pair| {
|
||||
let mut kv = pair.trim().splitn(2, '=');
|
||||
fn read_bearer_token(parts: &Parts) -> Option<String> {
|
||||
let raw = parts
|
||||
.headers
|
||||
.get(axum::http::header::AUTHORIZATION)?
|
||||
.to_str()
|
||||
.ok()?;
|
||||
if raw.starts_with("Bearer ") {
|
||||
Some(raw["Bearer ".len()..].trim().to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn read_query_token(parts: &Parts) -> Option<String> {
|
||||
let query = parts.uri.query()?;
|
||||
|
||||
// Simple query param parsing without pulling in url::Url overhead
|
||||
for pair in query.split('&') {
|
||||
let mut kv = pair.splitn(2, '=');
|
||||
let key = kv.next()?;
|
||||
let value = kv.next()?;
|
||||
(key == name).then(|| value.to_string())
|
||||
})
|
||||
if key == "token" {
|
||||
return Some(value.to_string());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn now_ts() -> u64 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue