···22CREATE TABLE [user] (
33 [uuid] TEXT NOT NULL,
44 [username] TEXT NOT NULL UNIQUE,
55- [password] TEXT NOT NULL,
55+ -- Password will be null for users that have been invited but haven't set a password yet
66+ [password] TEXT NULL,
77+ [join_token] TEXT NULL UNIQUE,
68 PRIMARY KEY ([uuid])
79);
810911-- Create session table
1012CREATE TABLE [session] (
1111- [token] TEXT NOT NULL,
1313+ [uuid] TEXT NOT NULL,
1414+ [token] TEXT NOT NULL UNIQUE,
1215 [user] TEXT NOT NULL,
1316 [user_agent] TEXT NOT NULL,
1414- PRIMARY KEY ([token]),
1717+ PRIMARY KEY ([uuid]),
1518 FOREIGN KEY ([user]) REFERENCES [user]([uuid]) ON DELETE CASCADE
1619)
+44-7
packages/node/src/api/auth.rs
···1212use salvo::{oapi::extract::JsonBody, prelude::*};
1313use serde::Deserialize;
1414use sqlx::{SqlitePool, prelude::FromRow};
1515+use uuid::Uuid;
15161617use crate::{api::response::LuminaryResponse, eyre_fmt, obtain};
1718···70717172 // Verifying the password is blocking and pretty slow (~600ms), so run on a separate thread
7273 let user = tokio::task::spawn_blocking(move || {
7373- user.filter(|user| verify_password(&credentials.password, &user.password).is_ok())
7474+ user.filter(|user| {
7575+ user.password
7676+ .as_ref()
7777+ .is_some_and(|password| verify_password(&credentials.password, &password).is_ok())
7878+ })
7479 })
7580 .await
7681 .wrap_err("Password verification task failed")?;
···8186 Some(u) => u,
8287 };
83888484- // Generate a secure bearer token using ChaCha12
8585- let mut token_bytes = [0u8; 32];
8686- ChaCha12Rng::from_entropy().fill_bytes(&mut token_bytes);
8787- let token = hex::encode(token_bytes);
8989+ let token = generate_token();
88908991 // Store the token in the database, associated with the user
9292+ let uuid = Uuid::new_v4().to_string();
9093 sqlx::query!(
9191- "INSERT INTO [session] ([token], [user_agent], [user]) VALUES (?, ?, ?)",
9494+ "INSERT INTO [session] ([uuid], [token], [user_agent], [user]) VALUES (?, ?, ?, ?)",
9595+ uuid,
9296 token,
9397 "todo", // todo: capture user agent from request and store it here
9498 user.uuid
···122126123127 return Ok(user);
124128 }
129129+130130+ async fn create_user(&self, username: &str) -> Result<String> {
131131+ let uuid = Uuid::new_v4().to_string();
132132+ let token = generate_token();
133133+134134+ sqlx::query!(
135135+ "INSERT INTO [user] ([uuid], [username], [join_token]) VALUES (?, ?, ?)",
136136+ uuid,
137137+ username,
138138+ token
139139+ )
140140+ .execute(&self.pool)
141141+ .await
142142+ .wrap_err("Failed to create user")?;
143143+144144+ return Ok(token);
145145+ }
125146}
126147127148/// Salvo middleware for validating authentication.
···188209pub struct LuminaryUser {
189210 uuid: String,
190211 username: String,
191191- password: String,
212212+ password: Option<String>,
213213+ join_token: Option<String>,
192214}
193215194216impl Debug for LuminaryUser {
···197219 .field("id", &self.uuid)
198220 .field("username", &self.username)
199221 .field("password", &"***")
222222+ .field(
223223+ "join_token",
224224+ if self.join_token.is_none() {
225225+ &"None"
226226+ } else {
227227+ &"Some(***)"
228228+ },
229229+ )
200230 .finish()
201231 }
202232}
233233+234234+/// Generates a secure random token for authentication purposes.
235235+fn generate_token() -> String {
236236+ let mut token_bytes = [0u8; 32];
237237+ ChaCha12Rng::from_entropy().fill_bytes(&mut token_bytes);
238238+ return hex::encode(token_bytes);
239239+}