A focused Docker Compose management web application.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: begin implementing join tokens

Brooke 65b91752 1d791b5b

+88 -24
+12
packages/node/.sqlx/query-0be40f162895ca1857bbe625156a92cb8f19bd0f6fd9820449de859d3985055d.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "INSERT INTO [session] ([uuid], [token], [user_agent], [user]) VALUES (?, ?, ?, ?)", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Right": 4 8 + }, 9 + "nullable": [] 10 + }, 11 + "hash": "0be40f162895ca1857bbe625156a92cb8f19bd0f6fd9820449de859d3985055d" 12 + }
+7 -1
packages/node/.sqlx/query-3dffe540f6323cca6f6a98668e4576727b3c837fe0f25832b9548f402a4e5549.json
··· 17 17 "name": "password", 18 18 "ordinal": 2, 19 19 "type_info": "Text" 20 + }, 21 + { 22 + "name": "join_token", 23 + "ordinal": 3, 24 + "type_info": "Text" 20 25 } 21 26 ], 22 27 "parameters": { ··· 25 30 "nullable": [ 26 31 false, 27 32 false, 28 - false 33 + true, 34 + true 29 35 ] 30 36 }, 31 37 "hash": "3dffe540f6323cca6f6a98668e4576727b3c837fe0f25832b9548f402a4e5549"
+12
packages/node/.sqlx/query-40ff407deaa3bad99cbb27b3640dff0bada0659ac8ef3b662c7befb4cb481b8a.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "INSERT INTO [user] ([uuid], [username], [join_token]) VALUES (?, ?, ?)", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Right": 3 8 + }, 9 + "nullable": [] 10 + }, 11 + "hash": "40ff407deaa3bad99cbb27b3640dff0bada0659ac8ef3b662c7befb4cb481b8a" 12 + }
+7 -1
packages/node/.sqlx/query-59a97bc1bac68db673fe258f28078370d3bfa5374a71d8b9f465a224ecb5ad17.json
··· 17 17 "name": "password", 18 18 "ordinal": 2, 19 19 "type_info": "Text" 20 + }, 21 + { 22 + "name": "join_token", 23 + "ordinal": 3, 24 + "type_info": "Text" 20 25 } 21 26 ], 22 27 "parameters": { ··· 25 30 "nullable": [ 26 31 false, 27 32 false, 28 - false 33 + true, 34 + true 29 35 ] 30 36 }, 31 37 "hash": "59a97bc1bac68db673fe258f28078370d3bfa5374a71d8b9f465a224ecb5ad17"
-12
packages/node/.sqlx/query-c51462eb686ec05b1a160d51a074511fd85afa0301b055b40b4523d09c21192f.json
··· 1 - { 2 - "db_name": "SQLite", 3 - "query": "INSERT INTO [session] ([token], [user_agent], [user]) VALUES (?, ?, ?)", 4 - "describe": { 5 - "columns": [], 6 - "parameters": { 7 - "Right": 3 8 - }, 9 - "nullable": [] 10 - }, 11 - "hash": "c51462eb686ec05b1a160d51a074511fd85afa0301b055b40b4523d09c21192f" 12 - }
+6 -3
packages/node/migrations/20260223001530_init.sql
··· 2 2 CREATE TABLE [user] ( 3 3 [uuid] TEXT NOT NULL, 4 4 [username] TEXT NOT NULL UNIQUE, 5 - [password] TEXT NOT NULL, 5 + -- Password will be null for users that have been invited but haven't set a password yet 6 + [password] TEXT NULL, 7 + [join_token] TEXT NULL UNIQUE, 6 8 PRIMARY KEY ([uuid]) 7 9 ); 8 10 9 11 -- Create session table 10 12 CREATE TABLE [session] ( 11 - [token] TEXT NOT NULL, 13 + [uuid] TEXT NOT NULL, 14 + [token] TEXT NOT NULL UNIQUE, 12 15 [user] TEXT NOT NULL, 13 16 [user_agent] TEXT NOT NULL, 14 - PRIMARY KEY ([token]), 17 + PRIMARY KEY ([uuid]), 15 18 FOREIGN KEY ([user]) REFERENCES [user]([uuid]) ON DELETE CASCADE 16 19 )
+44 -7
packages/node/src/api/auth.rs
··· 12 12 use salvo::{oapi::extract::JsonBody, prelude::*}; 13 13 use serde::Deserialize; 14 14 use sqlx::{SqlitePool, prelude::FromRow}; 15 + use uuid::Uuid; 15 16 16 17 use crate::{api::response::LuminaryResponse, eyre_fmt, obtain}; 17 18 ··· 70 71 71 72 // Verifying the password is blocking and pretty slow (~600ms), so run on a separate thread 72 73 let user = tokio::task::spawn_blocking(move || { 73 - user.filter(|user| verify_password(&credentials.password, &user.password).is_ok()) 74 + user.filter(|user| { 75 + user.password 76 + .as_ref() 77 + .is_some_and(|password| verify_password(&credentials.password, &password).is_ok()) 78 + }) 74 79 }) 75 80 .await 76 81 .wrap_err("Password verification task failed")?; ··· 81 86 Some(u) => u, 82 87 }; 83 88 84 - // Generate a secure bearer token using ChaCha12 85 - let mut token_bytes = [0u8; 32]; 86 - ChaCha12Rng::from_entropy().fill_bytes(&mut token_bytes); 87 - let token = hex::encode(token_bytes); 89 + let token = generate_token(); 88 90 89 91 // Store the token in the database, associated with the user 92 + let uuid = Uuid::new_v4().to_string(); 90 93 sqlx::query!( 91 - "INSERT INTO [session] ([token], [user_agent], [user]) VALUES (?, ?, ?)", 94 + "INSERT INTO [session] ([uuid], [token], [user_agent], [user]) VALUES (?, ?, ?, ?)", 95 + uuid, 92 96 token, 93 97 "todo", // todo: capture user agent from request and store it here 94 98 user.uuid ··· 122 126 123 127 return Ok(user); 124 128 } 129 + 130 + async fn create_user(&self, username: &str) -> Result<String> { 131 + let uuid = Uuid::new_v4().to_string(); 132 + let token = generate_token(); 133 + 134 + sqlx::query!( 135 + "INSERT INTO [user] ([uuid], [username], [join_token]) VALUES (?, ?, ?)", 136 + uuid, 137 + username, 138 + token 139 + ) 140 + .execute(&self.pool) 141 + .await 142 + .wrap_err("Failed to create user")?; 143 + 144 + return Ok(token); 145 + } 125 146 } 126 147 127 148 /// Salvo middleware for validating authentication. ··· 188 209 pub struct LuminaryUser { 189 210 uuid: String, 190 211 username: String, 191 - password: String, 212 + password: Option<String>, 213 + join_token: Option<String>, 192 214 } 193 215 194 216 impl Debug for LuminaryUser { ··· 197 219 .field("id", &self.uuid) 198 220 .field("username", &self.username) 199 221 .field("password", &"***") 222 + .field( 223 + "join_token", 224 + if self.join_token.is_none() { 225 + &"None" 226 + } else { 227 + &"Some(***)" 228 + }, 229 + ) 200 230 .finish() 201 231 } 202 232 } 233 + 234 + /// Generates a secure random token for authentication purposes. 235 + fn generate_token() -> String { 236 + let mut token_bytes = [0u8; 32]; 237 + ChaCha12Rng::from_entropy().fill_bytes(&mut token_bytes); 238 + return hex::encode(token_bytes); 239 + }