Skip to content

Commit cf23d28

Browse files
authored
Merge pull request #38 from piotrpdev/OKO-110-Admin-Page-for-Accounts
2 parents 4b888d5 + d9f9f54 commit cf23d28

9 files changed

Lines changed: 562 additions & 101 deletions

File tree

backend/src/users.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@ pub struct Credentials {
1515
pub password: String,
1616
}
1717

18-
// TODO: Make db private again and pass db state to Router as layer/state
1918
#[derive(Debug, Clone)]
2019
pub struct Backend {
21-
pub db: SqlitePool,
20+
db: SqlitePool,
2221
}
2322

2423
impl Backend {

backend/src/web/app.rs

Lines changed: 24 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@ const SQLITE_URL: &str = "sqlite://data.db";
6363
const VIDEO_PATH: &str = "./videos/";
6464
const DEFAULT_ADMIN_USERNAME: &str = "admin";
6565
const DEFAULT_ADMIN_PASS_HASH: &str = "$argon2id$v=19$m=19456,t=2,p=1$VE0e3g7DalWHgDwou3nuRA$uC6TER156UQpk0lNQ5+jHM0l5poVjPA1he/Tyn9J4Zw";
66-
const DEFAULT_GUEST_USERNAME: &str = "guest";
67-
const DEFAULT_GUEST_PASS_HASH: &str = "$argon2id$v=19$m=19456,t=2,p=1$VE0e3g7DalWHgDwou3nuRA$uC6TER156UQpk0lNQ5+jHM0l5poVjPA1he/Tyn9J4Zw";
6866
const EXPIRED_SESSION_DELETION_INTERVAL: tokio::time::Duration =
6967
tokio::time::Duration::from_secs(60);
7068
const SESSION_DURATION: Duration = Duration::days(1);
@@ -83,6 +81,7 @@ pub struct AppState {
8381
pub mdns_channel: watch::Sender<MdnsChannelMessage>,
8482
pub shutdown_token: CancellationToken,
8583
pub oko_private_socket_addr: Option<SocketAddr>,
84+
pub db_pool: SqlitePool,
8685
}
8786

8887
pub struct App {
@@ -129,6 +128,7 @@ impl App {
129128
})
130129
}
131130

131+
#[allow(clippy::too_many_lines)] // TODO: Refactor
132132
pub async fn serve(self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
133133
// ? Maybe make this optional just in case
134134
let admin_exists = User::get_using_username(&self.db, DEFAULT_ADMIN_USERNAME)
@@ -145,21 +145,6 @@ impl App {
145145
admin.create_using_self(&self.db).await?;
146146
}
147147

148-
// ? Maybe make this optional just in case
149-
let guest_exists = User::get_using_username(&self.db, DEFAULT_GUEST_USERNAME)
150-
.await
151-
.is_ok();
152-
if !guest_exists {
153-
let mut guest = User {
154-
user_id: User::DEFAULT.user_id,
155-
username: "guest".to_string(),
156-
password_hash: DEFAULT_GUEST_PASS_HASH.to_owned(),
157-
created_at: User::DEFAULT.created_at(),
158-
};
159-
160-
guest.create_using_self(&self.db).await?;
161-
}
162-
163148
// Session layer.
164149
//
165150
// This uses `tower-sessions` to establish a layer that will provide the session
@@ -185,7 +170,7 @@ impl App {
185170
//
186171
// This combines the session layer with our backend to establish the auth
187172
// service which will provide the auth session as a request extension.
188-
let backend = Backend::new(self.db);
173+
let backend = Backend::new(self.db.clone());
189174
let auth_layer = AuthManagerLayerBuilder::new(backend, session_layer).build();
190175

191176
let embedded_assets_service = ServeEmbed::<EmbeddedAssets>::new();
@@ -209,6 +194,7 @@ impl App {
209194
mdns_channel: mdns_channel.clone(),
210195
shutdown_token: shutdown_token.clone(),
211196
oko_private_socket_addr: self.oko_private_socket_addr,
197+
db_pool: self.db,
212198
});
213199

214200
let mdns_task = tokio::spawn(async move {
@@ -238,6 +224,7 @@ impl App {
238224

239225
let main_router = Router::new()
240226
.route("/api/ws", axum::routing::any(ws_handler))
227+
.route("/api/guest_exists", axum::routing::get(guest_exists_route))
241228
.with_state(app_state.clone());
242229

243230
// TODO: Order of merge matters here, make sure the correct routes are protected and that fallback works as intended.
@@ -269,6 +256,15 @@ impl App {
269256
}
270257
}
271258

259+
pub async fn guest_exists_route(state: State<Arc<AppState>>) -> impl IntoResponse {
260+
let guest_user_result = User::get_using_username(&state.db_pool, "guest").await;
261+
if guest_user_result.is_err() {
262+
return http::StatusCode::INTERNAL_SERVER_ERROR.into_response();
263+
}
264+
265+
http::StatusCode::OK.into_response()
266+
}
267+
272268
// ? Maybe move all functions below into App impl block, then use `self` for db pool
273269

274270
async fn shutdown_signal(
@@ -397,7 +393,7 @@ async fn handle_socket(
397393
// TODO: Maybe find a better way to handle this
398394
if camera_any_port {
399395
let Ok(db_camera) =
400-
Camera::get_using_ip(&auth_session.backend.db, who.ip().to_string() + ":*").await
396+
Camera::get_using_ip(&state.db_pool, who.ip().to_string() + ":*").await
401397
else {
402398
// TODO: Inform client/db if camera not found (both web user and ws connection), also find better way to exit here?
403399
error!("Camera (any port) not found in DB, aborting...");
@@ -406,9 +402,7 @@ async fn handle_socket(
406402

407403
camera_id = db_camera.camera_id;
408404
} else {
409-
let Ok(db_camera) =
410-
Camera::get_using_ip(&auth_session.backend.db, who.to_string()).await
411-
else {
405+
let Ok(db_camera) = Camera::get_using_ip(&state.db_pool, who.to_string()).await else {
412406
// TODO: Inform client/db if camera not found (both web user and ws connection), also find better way to exit here?
413407
error!("Camera not found in DB, aborting...");
414408
return;
@@ -417,8 +411,7 @@ async fn handle_socket(
417411
camera_id = db_camera.camera_id;
418412
}
419413

420-
let Ok(camera_settings) =
421-
CameraSetting::get_for_camera(&auth_session.backend.db, camera_id).await
414+
let Ok(camera_settings) = CameraSetting::get_for_camera(&state.db_pool, camera_id).await
422415
else {
423416
error!("Error getting initial camera settings for camera {camera_id}, aborting...");
424417
return;
@@ -434,8 +427,7 @@ async fn handle_socket(
434427

435428
user_id = Some(user.user_id);
436429

437-
let Ok(i_cameras) =
438-
Camera::list_accessible_to_user(&auth_session.backend.db, user.user_id).await
430+
let Ok(i_cameras) = Camera::list_accessible_to_user(&state.db_pool, user.user_id).await
439431
else {
440432
error!("Error listing cameras for user...");
441433
return;
@@ -454,7 +446,7 @@ async fn handle_socket(
454446
// ! Camera restart does not guarantee new recording, frames will keep going to the same video unless socket times out?
455447
let mut recording_task: JoinHandle<Result<(), Box<dyn std::error::Error + Send + Sync>>> =
456448
if is_camera {
457-
let auth_session_clone = auth_session.clone();
449+
let state_clone = state.clone();
458450
// TODO: Check if errors are returned properly here, had some issues with the ? operator being silent
459451
tracker.spawn(async move {
460452
let now = Video::DEFAULT.start_time();
@@ -473,9 +465,7 @@ async fn handle_socket(
473465
};
474466

475467
// ? Maybe don't create video until first frame (or maybe doing this is actually a good approach)?
476-
video
477-
.create_using_self(&auth_session_clone.backend.db)
478-
.await?;
468+
video.create_using_self(&state_clone.db_pool).await?;
479469

480470
let (frame_width, frame_height, framerate) = match initial_camera_settings_clone {
481471
#[allow(clippy::match_same_arms)] // readability
@@ -547,9 +537,7 @@ async fn handle_socket(
547537
video.end_time = Some(OffsetDateTime::now_utc());
548538
video.file_size = Some(total_bytes.try_into()?);
549539

550-
video
551-
.update_using_self(&auth_session_clone.backend.db)
552-
.await?;
540+
video.update_using_self(&state_clone.db_pool).await?;
553541
info!("Recording finished for {who}...");
554542

555543
Ok(())
@@ -781,11 +769,9 @@ async fn handle_socket(
781769
error!("Error sending API WebSocket message to {who}: {e:?}");
782770
}
783771

784-
let Ok(new_cameras) = Camera::list_accessible_to_user(
785-
&auth_session.backend.db,
786-
user_id_some,
787-
)
788-
.await
772+
let Ok(new_cameras) =
773+
Camera::list_accessible_to_user(&state.db_pool, user_id_some)
774+
.await
789775
else {
790776
// TODO: prevent potential endless loop here from the DB call always failing
791777
error!("Error listing new cameras for {who}...");

0 commit comments

Comments
 (0)