diff --git a/crates/database/src/sqlite.rs b/crates/database/src/sqlite.rs index f8e1af4..3906af6 100644 --- a/crates/database/src/sqlite.rs +++ b/crates/database/src/sqlite.rs @@ -123,7 +123,8 @@ impl Database for SqliteDatabase { } async fn get_media_items(&self, _user_id: &str) -> Result> { - unimplemented!() + error!("get_media_items not implemented!"); + Ok(vec![]) } async fn create_media_item( &self, diff --git a/crates/oauth_authorization_server/README.md b/crates/oauth_authorization_server/README.md index 3e166d2..60d907a 100644 --- a/crates/oauth_authorization_server/README.md +++ b/crates/oauth_authorization_server/README.md @@ -2,4 +2,53 @@ This crate offers an **OAuth Authorization Server** for [Photos.network](https://photos.network). -Especially for data privacy or offline support, this can interact as its own authorization server to handle user accounts locally without using any third-party authorization like Google, Okta or Microsoft. +Especially for data privacy and offline support, this can interact as its own authorization server to handle user accounts locally without using any third-party authorization like Google, Okta or Microsoft. + +To identify users and granting them access to the applications content, the Open Authorization (OAuth) standard is used so users can login without sharing credentials theirselfs. +Since public clients (e.g. native mobile applications and single-page applications) cannot securely store client secrets. +Clients need to implement [PKCE](https://datatracker.ietf.org/doc/html/rfc7636) and create a pair of secrets (Code Verifier & Code Challenge) and send it to this **OAuth Authorization Server** over https. +This way a malicious attacker can only intercept the Authorization Code but can't exchange it for a token without knowing the Code Verifier. + + +## Authorization code flow with PKCE +```mermaid +sequenceDiagram; + participant U as Users + participant A as App + participant AM as Authorization Server + participant R as Resource Server + + U->>A: User clicks login + A->>AM: GET http://127.0.0.1/.well-known/openid-configuration + + AM->>A: JSON meta-data document + Note right of A: { "authorization_endpoint":"http://localhost:7777/oidc/authorize",
"token_endpoint":"http://localhost:7777/oidc/token" ... + + A->>A: Generate Code Verifier & Challenge + + A->>AM: GET http://localhost:7777/oidc/authorize?[...] + Note right of A: GET /oidc/authorize parameters:
response_type=id_token%20token
client_id=mobile-app (identifier)
redirect_uri=photosapp://authenticate
state=xxxx (CRFS protection)
nonce=xyz (server-side replay protection)
scope=openid email profile library:read
code_challenge=elU6u5zyqQT2f92GRQUq6PautAeNDf4DQPayy
code_challenge_method=S256 + + AM->>U: show login prompt + U->>AM: perform login and grant consent + AM->>A: 302 Redirect to http://127.0.0.1/callback?[...] (redirect_uri) + Note right of A: GET /callback
state=xxx
code=xxx + + A->>AM: GET http://localhost:7777/oidc/token + Note right of A: GET /oidc/token parameters:
client_id=xxx (identifier)
redirect_uri=http://127.0.0.1/callback
code_verifier=xxxx (generated verifier)
code=xyz (authorization_code)
grant_type=authorization_code + AM->>AM: Validate code verifier and challenge + AM->>A: ID Token and Access token + Note right of A: {
"token_type": "Bearer",
"expires_in": 3600,
"access_token": "eyJraWQiOiI3bFV0aGJyR2hWVmx...",
"id_token": "eyJraWQiOiI3bFV0aGJyR2hWVmx...",
"scope": "profile openid email"
} + + %% R->>A: return user attributes + %% U->>O: GET http://127.0.0.1/callback?[...] + %% O->>A: POST http://127.0.0.1:7777/oidc/token + %% Note right of O: POST /oidc/token
client_id=xxx
grant_type=authorization_code
code=xxx
state=xxx + %% A->>O: JSON { "base64(id_token)", access_token" } + %% O->>O: verify id_token signature is valid and signed + %% O->>U: GET 302 Redirect to http://127.0.0.1 + Note over A: User is authenticate to http://127.0.0.1 + A->>R: Request user data with Access Token + R->>A: responds with requested data +``` + diff --git a/crates/oauth_authorization_server/src/handler/login.rs b/crates/oauth_authorization_server/src/handler/login.rs index d845b49..beb340b 100644 --- a/crates/oauth_authorization_server/src/handler/login.rs +++ b/crates/oauth_authorization_server/src/handler/login.rs @@ -20,22 +20,73 @@ use axum::Form; use serde::Deserialize; +static LOGIN_FORM_TEMPLATE: &str = r#" + + + + + + + + + + +
+ + + + + + + + +
+ + +"#; + pub(crate) async fn get_realm_login_form( - axum::extract::Path(_realm): axum::extract::Path, - // Query(query): Query, + axum::extract::Path(realm): axum::extract::Path, + axum::extract::Query(query): axum::extract::Query, ) -> Html { // create a VirtualDom with the app component // rebuild the VirtualDom before rendering - // tracing::debug!( - // "Rendering form for request_id={} and realm={}", - // query.request_id, - // realm - // ); + tracing::debug!( + "Rendering form for request_id={} and realm={}", + query.request_id, + realm + ); // render the VirtualDom to HTML // Html(dioxus_ssr::render(&app)) - Html("Login".to_string()) + Html(LOGIN_FORM_TEMPLATE.to_string()) + //Html("

Login