auth.rs
⎇
Raw
1use std::sync::Arc;
2
3use api_types::{Credentials, Me, OkResp, RootInfo, UserInfo};
4use axum::Json;
5use axum::extract::State;
6use axum::http::{HeaderMap, StatusCode, header};
7use axum::response::{IntoResponse, Response};
8
9use crate::api::common::{display_name, hash_password, validate_account_name, validate_password};
10use crate::auth::{self, clear_session_cookie, parse_session_cookie, session_cookie};
11use crate::error::{ApiError, AppState};
12
13/// GET /api/auth/me
14///
15/// - No users at all → `200 {"first_boot": true}`
16/// - No/invalid session → `401`
17/// - Valid session → user info + visible roots
18pub async fn me(
19 State(state): State<Arc<AppState>>,
20 headers: HeaderMap,
21) -> Result<Json<Me>, ApiError> {
22 if state.db.user_count().await? == 0 {
23 return Ok(Json(Me {
24 first_boot: true,
25 user: None,
26 roots: Vec::new(),
27 allow_writable_shares: false,
28 }));
29 }
30
31 let Some(token) = parse_session_cookie(&headers) else {
32 return Err(ApiError::new(StatusCode::UNAUTHORIZED, "not signed in"));
33 };
34 let Some(user) = state.db.session_user(&token).await? else {
35 return Err(ApiError::new(
36 StatusCode::UNAUTHORIZED,
37 "session expired, please sign in again",
38 ));
39 };
40
41 let roots: Vec<RootInfo> = state
42 .db
43 .user_roots(user.id)
44 .await?
45 .into_iter()
46 .map(|r| RootInfo {
47 id: r.id,
48 name: display_name(&state.root, &r.path),
49 path: r.path,
50 mode: r.mode,
51 })
52 .collect();
53
54 Ok(Json(Me {
55 first_boot: false,
56 user: Some(UserInfo {
57 id: user.id,
58 name: user.name,
59 is_admin: user.is_admin,
60 }),
61 roots,
62 allow_writable_shares: state.db.allow_writable_shares().await?,
63 }))
64}
65
66/// POST /api/auth/setup — create the first admin account.
67/// Only available while no users exist.
68pub async fn setup(
69 State(state): State<Arc<AppState>>,
70 Json(body): Json<Credentials>,
71) -> Result<Response, ApiError> {
72 let name = body.name.trim();
73 validate_account_name(name)?;
74 validate_password(&body.password)?;
75 if state.db.user_count().await? > 0 {
76 return Err(ApiError::new(
77 StatusCode::CONFLICT,
78 "server is already set up",
79 ));
80 }
81
82 let pass_hash = hash_password(&body.password).await?;
83 let user = state.db.create_admin(name, &pass_hash).await?;
84
85 let token = auth::random_token();
86 state.db.create_session(user.id, &token).await?;
87
88 let mut res = Json(OkResp { ok: true }).into_response();
89 res.headers_mut().insert(
90 header::SET_COOKIE,
91 session_cookie(&token, state.https).parse().unwrap(),
92 );
93 Ok(res)
94}
95
96/// POST /api/auth/login
97pub async fn login(
98 State(state): State<Arc<AppState>>,
99 Json(body): Json<Credentials>,
100) -> Result<Response, ApiError> {
101 let Some(user) = state.db.verify_password(&body.name, &body.password).await? else {
102 return Err(ApiError::new(
103 StatusCode::UNAUTHORIZED,
104 "invalid name or password",
105 ));
106 };
107
108 let token = auth::random_token();
109 state.db.create_session(user.id, &token).await?;
110
111 let mut res = Json(OkResp { ok: true }).into_response();
112 res.headers_mut().insert(
113 header::SET_COOKIE,
114 session_cookie(&token, state.https).parse().unwrap(),
115 );
116 Ok(res)
117}
118
119/// POST /api/auth/logout
120pub async fn logout(State(state): State<Arc<AppState>>, headers: HeaderMap) -> Response {
121 if let Some(token) = parse_session_cookie(&headers) {
122 let _ = state.db.delete_session(&token).await;
123 }
124 let mut res = Json(OkResp { ok: true }).into_response();
125 res.headers_mut().insert(
126 header::SET_COOKIE,
127 clear_session_cookie(state.https).parse().unwrap(),
128 );
129 res
130}
131