···1616cid = "0.11.1"
1717dotenvy = "0.15.7"
1818futures = "0.3.30"
1919+hkdf = "0.12"
2020+hmac = "0.12"
2121+aes-gcm = "0.10"
1922jacquard = { version = "0.9.3", default-features = false, features = ["api", "api_bluesky", "api_full", "derive", "dns"] }
2023jacquard-axum = "0.9.2"
2124jacquard-repo = "0.9.2"
···3033serde_ipld_dagcbor = "0.6.4"
3134serde_json = "1.0.145"
3235sha2 = "0.10.9"
3636+subtle = "2.5"
3737+p256 = { version = "0.13", features = ["ecdsa"] }
3838+p384 = { version = "0.13", features = ["ecdsa"] }
3939+ed25519-dalek = { version = "2.1", features = ["pkcs8"] }
3340sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "postgres", "uuid", "chrono", "json"] }
3441thiserror = "2.0.17"
3542tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread", "time", "signal", "process"] }
3643tracing = "0.1.43"
3744tracing-subscriber = "0.3.22"
3845tokio-tungstenite = { version = "0.28.0", features = ["native-tls"] }
4646+urlencoding = "2.1"
3947uuid = { version = "1.19.0", features = ["v4", "fast-rng"] }
40484149[dev-dependencies]
···4452testcontainers = "0.26.0"
4553testcontainers-modules = { version = "0.14.0", features = ["postgres"] }
4654wiremock = "0.6.5"
5555+5656+# urlencoding is also in dependencies, but tests use it directly
+47-17
TODO.md
···110110## Temp Namespace (`com.atproto.temp`)
111111- [ ] Implement `com.atproto.temp.checkSignupQueue` (signup queue status for gated signups).
112112113113-## OAuth 2.0 Support
114114-The reference PDS implements full OAuth 2.0 provider functionality for native app authentication.
115115-- [ ] OAuth Provider Core
116116- - [ ] Implement `/.well-known/oauth-protected-resource` metadata endpoint.
117117- - [ ] Implement `/.well-known/oauth-authorization-server` metadata endpoint.
118118- - [ ] Implement `/oauth/authorize` authorization endpoint.
119119- - [ ] Implement `/oauth/par` Pushed Authorization Request endpoint.
120120- - [ ] Implement `/oauth/token` token endpoint.
121121- - [ ] Implement `/oauth/jwks` JSON Web Key Set endpoint.
122122-- [ ] OAuth Database Tables
123123- - [ ] Device table for tracking authorized devices.
124124- - [ ] Authorization request table.
125125- - [ ] Authorized client table.
126126- - [ ] Token table for OAuth tokens.
127127- - [ ] Used refresh token table.
128128-- [ ] DPoP (Demonstrating Proof-of-Possession) support.
129129-- [ ] Client metadata fetching and validation.
113113+## OAuth 2.1 Support
114114+Full OAuth 2.1 provider for ATProto native app authentication.
115115+- [x] OAuth Provider Core
116116+ - [x] Implement `/.well-known/oauth-protected-resource` metadata endpoint.
117117+ - [x] Implement `/.well-known/oauth-authorization-server` metadata endpoint.
118118+ - [x] Implement `/oauth/authorize` authorization endpoint (headless JSON mode).
119119+ - [x] Implement `/oauth/par` Pushed Authorization Request endpoint.
120120+ - [x] Implement `/oauth/token` token endpoint (authorization_code + refresh_token grants).
121121+ - [x] Implement `/oauth/jwks` JSON Web Key Set endpoint.
122122+ - [x] Implement `/oauth/revoke` token revocation endpoint.
123123+ - [x] Implement `/oauth/introspect` token introspection endpoint.
124124+- [x] OAuth Database Tables
125125+ - [x] Device table for tracking authorized devices.
126126+ - [x] Authorization request table.
127127+ - [x] Authorized client table.
128128+ - [x] Token table for OAuth tokens.
129129+ - [x] Used refresh token table (replay protection).
130130+ - [x] DPoP JTI tracking table.
131131+- [x] DPoP (Demonstrating Proof-of-Possession) support.
132132+- [x] Client metadata fetching and validation.
133133+- [x] PKCE (S256) enforcement.
134134+- [x] OAuth token verification extractor for protected resources.
135135+- [ ] Authorization UI templates (currently headless-only, returns JSON for programmatic flows).
136136+- [ ] Implement `private_key_jwt` signature verification (currently rejects with clear error).
137137+138138+## OAuth Security Notes
139139+140140+I've tried to ensure that this codebase is not vulnerable to the following:
141141+142142+- Constant-time comparison for signature verification (prevents timing attacks)
143143+- HMAC-SHA256 for access token signing with configurable secret
144144+- Production secrets require 32+ character minimum
145145+- DPoP JTI replay protection via database
146146+- DPoP nonce validation with HMAC-based timestamps (5 min validity)
147147+- Refresh token rotation with reuse detection (revokes token family on reuse)
148148+- PKCE S256 enforced (plain not allowed)
149149+- Authorization code single-use enforcement
150150+- URL encoding for redirect parameters (prevents injection)
151151+- All database queries use parameterized statements (no SQL injection)
152152+- Deactivated/taken-down accounts blocked from OAuth authorization
153153+- Client ID validation on token exchange (defense-in-depth against cross-client attacks)
154154+155155+### Auth Notes
156156+- Algorithm choice: Using ES256K (secp256k1 ECDSA) with per-user keys. Ref PDS uses HS256 (HMAC) with single server key. Our approach provides better key isolation but differs from reference implementation.
157157+ - [ ] Support the ref PDS HS256 system too.
158158+- Token storage: Now storing only token JTIs in session_tokens table (defense in depth against DB breaches). Refresh token family tracking enables detection of token reuse attacks.
159159+- Key encryption: User signing keys encrypted at rest using AES-256-GCM with keys derived via HKDF from MASTER_KEY environment variable. Migration-safe: supports both encrypted (version 1) and plaintext (version 0) keys.
130160131161## PDS-Level App Endpoints
132162These endpoints need to be implemented at the PDS level (not just proxied to appview).
+16-21
justfile
···27272828lint: fmt-check clippy
29293030-test:
3131- cargo test
3030+# Run tests (auto-starts and auto-cleans containers)
3131+test *args:
3232+ ./scripts/run-tests.sh {{args}}
32333333-test-verbose:
3434- cargo test -- --nocapture
3434+# Run a specific test file
3535+test-file file:
3636+ ./scripts/run-tests.sh --test {{file}}
35373636-test-repo:
3737- cargo test --test repo
3838+# Run tests with testcontainers (slower, no shared infra)
3939+test-standalone:
4040+ BSPDS_ALLOW_INSECURE_SECRETS=1 cargo test
38413939-test-lifecycle:
4040- cargo test --test lifecycle
4242+# Manually manage test infrastructure (for debugging)
4343+test-infra-start:
4444+ ./scripts/test-infra.sh start
41454242-test-proxy:
4343- cargo test --test proxy
4444-4545-test-sync:
4646- cargo test --test sync
4747-4848-test-server:
4949- cargo test --test server
4646+test-infra-stop:
4747+ ./scripts/test-infra.sh stop
50485151-test-identity:
5252- cargo test --test identity
5353-5454-test-auth:
5555- cargo test --test auth
4949+test-infra-status:
5050+ ./scripts/test-infra.sh status
56515752clean:
5853 cargo clean
+125-15
migrations/202512211400_initial_schema.sql
···1818 created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
1919 updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
20202121- -- status & moderation
2221 deactivated_at TIMESTAMPTZ,
2322 invites_disabled BOOLEAN DEFAULT FALSE,
2423 takedown_ref TEXT,
25242626- -- notifs
2725 preferred_notification_channel notification_channel NOT NULL DEFAULT 'email',
28262929- -- auth & verification
3027 password_reset_code TEXT,
3128 password_reset_code_expires_at TIMESTAMPTZ,
3229···5451 UNIQUE(code, used_by_user)
5552);
56535757--- TODO: encrypt at rest!
5854CREATE TABLE IF NOT EXISTS user_keys (
5955 user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
6056 key_bytes BYTEA NOT NULL,
6161- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
5757+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
5858+ encrypted_at TIMESTAMPTZ,
5959+ encryption_version INTEGER DEFAULT 0
6260);
63616462CREATE TABLE IF NOT EXISTS repos (
···6866 updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
6967);
70687171--- content addressable storage
7269CREATE TABLE IF NOT EXISTS blocks (
7370 cid BYTEA PRIMARY KEY,
7471 data BYTEA NOT NULL,
7572 created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
7673);
77747878--- denormalized index for fast queries
7975CREATE TABLE IF NOT EXISTS records (
8076 id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
8177 repo_id UUID NOT NULL REFERENCES repos(user_id) ON DELETE CASCADE,
···9793 created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
9894);
9995100100-CREATE TABLE IF NOT EXISTS sessions (
101101- access_jwt TEXT PRIMARY KEY,
102102- refresh_jwt TEXT NOT NULL UNIQUE,
103103- did TEXT NOT NULL REFERENCES users(did) ON DELETE CASCADE,
104104- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
105105-);
106106-10796CREATE TABLE IF NOT EXISTS app_passwords (
10897 id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
10998 user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
···114103 UNIQUE(user_id, name)
115104);
116105117117--- naughty list
118106CREATE TABLE reports (
119107 id BIGINT PRIMARY KEY,
120108 reason_type TEXT NOT NULL,
···155143 WHERE status = 'pending';
156144157145CREATE INDEX idx_notification_queue_user_id ON notification_queue(user_id);
146146+147147+CREATE TABLE IF NOT EXISTS reserved_signing_keys (
148148+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
149149+ did TEXT,
150150+ public_key_did_key TEXT NOT NULL,
151151+ private_key_bytes BYTEA NOT NULL,
152152+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
153153+ expires_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + INTERVAL '24 hours',
154154+ used_at TIMESTAMPTZ
155155+);
156156+157157+CREATE INDEX IF NOT EXISTS idx_reserved_signing_keys_did ON reserved_signing_keys(did) WHERE did IS NOT NULL;
158158+CREATE INDEX IF NOT EXISTS idx_reserved_signing_keys_expires ON reserved_signing_keys(expires_at) WHERE used_at IS NULL;
159159+160160+CREATE TABLE repo_seq (
161161+ seq BIGSERIAL PRIMARY KEY,
162162+ did TEXT NOT NULL,
163163+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
164164+ event_type TEXT NOT NULL,
165165+ commit_cid TEXT,
166166+ prev_cid TEXT,
167167+ ops JSONB,
168168+ blobs TEXT[],
169169+ blocks_cids TEXT[]
170170+);
171171+172172+CREATE INDEX idx_repo_seq_seq ON repo_seq(seq);
173173+CREATE INDEX idx_repo_seq_did ON repo_seq(did);
174174+175175+CREATE TABLE IF NOT EXISTS session_tokens (
176176+ id SERIAL PRIMARY KEY,
177177+ did TEXT NOT NULL REFERENCES users(did) ON DELETE CASCADE,
178178+ access_jti TEXT NOT NULL UNIQUE,
179179+ refresh_jti TEXT NOT NULL UNIQUE,
180180+ access_expires_at TIMESTAMPTZ NOT NULL,
181181+ refresh_expires_at TIMESTAMPTZ NOT NULL,
182182+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
183183+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
184184+);
185185+186186+CREATE INDEX idx_session_tokens_did ON session_tokens(did);
187187+CREATE INDEX idx_session_tokens_access_jti ON session_tokens(access_jti);
188188+CREATE INDEX idx_session_tokens_refresh_jti ON session_tokens(refresh_jti);
189189+190190+CREATE TABLE IF NOT EXISTS used_refresh_tokens (
191191+ refresh_jti TEXT PRIMARY KEY,
192192+ session_id INTEGER NOT NULL REFERENCES session_tokens(id) ON DELETE CASCADE,
193193+ used_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
194194+);
195195+196196+CREATE INDEX idx_used_refresh_tokens_session_id ON used_refresh_tokens(session_id);
197197+198198+CREATE TABLE IF NOT EXISTS oauth_device (
199199+ id TEXT PRIMARY KEY,
200200+ session_id TEXT NOT NULL UNIQUE,
201201+ user_agent TEXT,
202202+ ip_address TEXT NOT NULL,
203203+ last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
204204+);
205205+206206+CREATE TABLE IF NOT EXISTS oauth_authorization_request (
207207+ id TEXT PRIMARY KEY,
208208+ did TEXT REFERENCES users(did) ON DELETE CASCADE,
209209+ device_id TEXT REFERENCES oauth_device(id) ON DELETE SET NULL,
210210+ client_id TEXT NOT NULL,
211211+ client_auth JSONB,
212212+ parameters JSONB NOT NULL,
213213+ expires_at TIMESTAMPTZ NOT NULL,
214214+ code TEXT UNIQUE
215215+);
216216+217217+CREATE INDEX idx_oauth_auth_request_expires ON oauth_authorization_request(expires_at);
218218+CREATE INDEX idx_oauth_auth_request_code ON oauth_authorization_request(code) WHERE code IS NOT NULL;
219219+220220+CREATE TABLE IF NOT EXISTS oauth_token (
221221+ id SERIAL PRIMARY KEY,
222222+ did TEXT NOT NULL REFERENCES users(did) ON DELETE CASCADE,
223223+ token_id TEXT NOT NULL UNIQUE,
224224+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
225225+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
226226+ expires_at TIMESTAMPTZ NOT NULL,
227227+ client_id TEXT NOT NULL,
228228+ client_auth JSONB NOT NULL,
229229+ device_id TEXT REFERENCES oauth_device(id) ON DELETE SET NULL,
230230+ parameters JSONB NOT NULL,
231231+ details JSONB,
232232+ code TEXT UNIQUE,
233233+ current_refresh_token TEXT UNIQUE,
234234+ scope TEXT
235235+);
236236+237237+CREATE INDEX idx_oauth_token_did ON oauth_token(did);
238238+CREATE INDEX idx_oauth_token_code ON oauth_token(code) WHERE code IS NOT NULL;
239239+240240+CREATE TABLE IF NOT EXISTS oauth_account_device (
241241+ did TEXT NOT NULL REFERENCES users(did) ON DELETE CASCADE,
242242+ device_id TEXT NOT NULL REFERENCES oauth_device(id) ON DELETE CASCADE,
243243+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
244244+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
245245+ PRIMARY KEY (did, device_id)
246246+);
247247+248248+CREATE TABLE IF NOT EXISTS oauth_authorized_client (
249249+ did TEXT NOT NULL REFERENCES users(did) ON DELETE CASCADE,
250250+ client_id TEXT NOT NULL,
251251+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
252252+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
253253+ data JSONB NOT NULL,
254254+ PRIMARY KEY (did, client_id)
255255+);
256256+257257+CREATE TABLE IF NOT EXISTS oauth_used_refresh_token (
258258+ refresh_token TEXT PRIMARY KEY,
259259+ token_id INTEGER NOT NULL REFERENCES oauth_token(id) ON DELETE CASCADE
260260+);
261261+262262+CREATE TABLE oauth_dpop_jti (
263263+ jti TEXT PRIMARY KEY,
264264+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
265265+);
266266+267267+CREATE INDEX idx_oauth_dpop_jti_created_at ON oauth_dpop_jti(created_at);
-12
migrations/202512211401_reserved_signing_keys.sql
···11-CREATE TABLE IF NOT EXISTS reserved_signing_keys (
22- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
33- did TEXT,
44- public_key_did_key TEXT NOT NULL,
55- private_key_bytes BYTEA NOT NULL,
66- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
77- expires_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + INTERVAL '24 hours',
88- used_at TIMESTAMPTZ
99-);
1010-1111-CREATE INDEX IF NOT EXISTS idx_reserved_signing_keys_did ON reserved_signing_keys(did) WHERE did IS NOT NULL;
1212-CREATE INDEX IF NOT EXISTS idx_reserved_signing_keys_expires ON reserved_signing_keys(expires_at) WHERE used_at IS NULL;
-13
migrations/202512211402_repo_sequencer.sql
···11-CREATE TABLE repo_seq (
22- seq BIGSERIAL PRIMARY KEY,
33- did TEXT NOT NULL,
44- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
55- event_type TEXT NOT NULL,
66- commit_cid TEXT,
77- prev_cid TEXT,
88- ops JSONB,
99- blobs TEXT[]
1010-);
1111-1212-CREATE INDEX idx_repo_seq_seq ON repo_seq(seq);
1313-CREATE INDEX idx_repo_seq_did ON repo_seq(did);
···11+#!/usr/bin/env bash
22+set -euo pipefail
33+44+INFRA_FILE="${TMPDIR:-/tmp}/bspds_test_infra.env"
55+CONTAINER_PREFIX="bspds-test"
66+77+command_exists() {
88+ command -v "$1" >/dev/null 2>&1
99+}
1010+1111+if command_exists podman; then
1212+ CONTAINER_CMD="podman"
1313+ if [[ -z "${DOCKER_HOST:-}" ]]; then
1414+ RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
1515+ PODMAN_SOCK="$RUNTIME_DIR/podman/podman.sock"
1616+ if [[ -S "$PODMAN_SOCK" ]]; then
1717+ export DOCKER_HOST="unix://$PODMAN_SOCK"
1818+ fi
1919+ fi
2020+elif command_exists docker; then
2121+ CONTAINER_CMD="docker"
2222+else
2323+ echo "Error: Neither podman nor docker found" >&2
2424+ exit 1
2525+fi
2626+2727+start_infra() {
2828+ echo "Starting test infrastructure..."
2929+3030+ if [[ -f "$INFRA_FILE" ]]; then
3131+ source "$INFRA_FILE"
3232+ if $CONTAINER_CMD ps --format '{{.Names}}' 2>/dev/null | grep -q "^${CONTAINER_PREFIX}-postgres$"; then
3333+ echo "Infrastructure already running (found $INFRA_FILE)"
3434+ cat "$INFRA_FILE"
3535+ return 0
3636+ fi
3737+ echo "Stale infra file found, cleaning up..."
3838+ rm -f "$INFRA_FILE"
3939+ fi
4040+4141+ $CONTAINER_CMD rm -f "${CONTAINER_PREFIX}-postgres" "${CONTAINER_PREFIX}-minio" 2>/dev/null || true
4242+4343+ echo "Starting PostgreSQL..."
4444+ $CONTAINER_CMD run -d \
4545+ --name "${CONTAINER_PREFIX}-postgres" \
4646+ -e POSTGRES_PASSWORD=postgres \
4747+ -e POSTGRES_USER=postgres \
4848+ -e POSTGRES_DB=postgres \
4949+ -P \
5050+ --label bspds_test=true \
5151+ postgres:18-alpine >/dev/null
5252+5353+ echo "Starting MinIO..."
5454+ $CONTAINER_CMD run -d \
5555+ --name "${CONTAINER_PREFIX}-minio" \
5656+ -e MINIO_ROOT_USER=minioadmin \
5757+ -e MINIO_ROOT_PASSWORD=minioadmin \
5858+ -P \
5959+ --label bspds_test=true \
6060+ minio/minio:latest server /data >/dev/null
6161+6262+ echo "Waiting for services to be ready..."
6363+ sleep 2
6464+6565+ PG_PORT=$($CONTAINER_CMD port "${CONTAINER_PREFIX}-postgres" 5432 | head -1 | cut -d: -f2)
6666+ MINIO_PORT=$($CONTAINER_CMD port "${CONTAINER_PREFIX}-minio" 9000 | head -1 | cut -d: -f2)
6767+6868+ for i in {1..30}; do
6969+ if $CONTAINER_CMD exec "${CONTAINER_PREFIX}-postgres" pg_isready -U postgres >/dev/null 2>&1; then
7070+ break
7171+ fi
7272+ echo "Waiting for PostgreSQL... ($i/30)"
7373+ sleep 1
7474+ done
7575+7676+ for i in {1..30}; do
7777+ if curl -s "http://127.0.0.1:${MINIO_PORT}/minio/health/live" >/dev/null 2>&1; then
7878+ break
7979+ fi
8080+ echo "Waiting for MinIO... ($i/30)"
8181+ sleep 1
8282+ done
8383+8484+ echo "Creating MinIO bucket..."
8585+ $CONTAINER_CMD run --rm --network host \
8686+ -e MC_HOST_minio="http://minioadmin:minioadmin@127.0.0.1:${MINIO_PORT}" \
8787+ minio/mc:latest mb minio/test-bucket --ignore-existing >/dev/null 2>&1 || true
8888+8989+ cat > "$INFRA_FILE" << EOF
9090+export DATABASE_URL="postgres://postgres:postgres@127.0.0.1:${PG_PORT}/postgres"
9191+export TEST_DB_PORT="${PG_PORT}"
9292+export S3_ENDPOINT="http://127.0.0.1:${MINIO_PORT}"
9393+export S3_BUCKET="test-bucket"
9494+export AWS_ACCESS_KEY_ID="minioadmin"
9595+export AWS_SECRET_ACCESS_KEY="minioadmin"
9696+export AWS_REGION="us-east-1"
9797+export BSPDS_TEST_INFRA_READY="1"
9898+export BSPDS_ALLOW_INSECURE_SECRETS="1"
9999+EOF
100100+101101+ echo ""
102102+ echo "Infrastructure ready!"
103103+ echo "Config written to: $INFRA_FILE"
104104+ echo ""
105105+ cat "$INFRA_FILE"
106106+}
107107+108108+stop_infra() {
109109+ echo "Stopping test infrastructure..."
110110+ $CONTAINER_CMD rm -f "${CONTAINER_PREFIX}-postgres" "${CONTAINER_PREFIX}-minio" 2>/dev/null || true
111111+ rm -f "$INFRA_FILE"
112112+ echo "Infrastructure stopped."
113113+}
114114+115115+status_infra() {
116116+ echo "Test Infrastructure Status:"
117117+ echo "============================"
118118+119119+ if [[ -f "$INFRA_FILE" ]]; then
120120+ echo "Config file: $INFRA_FILE"
121121+ source "$INFRA_FILE"
122122+ echo "Database URL: $DATABASE_URL"
123123+ echo "S3 Endpoint: $S3_ENDPOINT"
124124+ else
125125+ echo "Config file: NOT FOUND"
126126+ fi
127127+128128+ echo ""
129129+ echo "Containers:"
130130+ $CONTAINER_CMD ps -a --filter "label=bspds_test=true" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || echo " (none)"
131131+}
132132+133133+case "${1:-}" in
134134+ start)
135135+ start_infra
136136+ ;;
137137+ stop)
138138+ stop_infra
139139+ ;;
140140+ restart)
141141+ stop_infra
142142+ start_infra
143143+ ;;
144144+ status)
145145+ status_infra
146146+ ;;
147147+ env)
148148+ if [[ -f "$INFRA_FILE" ]]; then
149149+ cat "$INFRA_FILE"
150150+ else
151151+ echo "Infrastructure not running. Run: $0 start" >&2
152152+ exit 1
153153+ fi
154154+ ;;
155155+ *)
156156+ echo "Usage: $0 {start|stop|restart|status|env}"
157157+ echo ""
158158+ echo "Commands:"
159159+ echo " start - Start test infrastructure (Postgres, MinIO)"
160160+ echo " stop - Stop and remove test containers"
161161+ echo " restart - Stop then start infrastructure"
162162+ echo " status - Show infrastructure status"
163163+ echo " env - Output environment variables for sourcing"
164164+ exit 1
165165+ ;;
166166+esac
+1-1
src/api/admin/account.rs
···214214 }
215215 };
216216217217- let _ = sqlx::query!("DELETE FROM sessions WHERE did = $1", did)
217217+ let _ = sqlx::query!("DELETE FROM session_tokens WHERE did = $1", did)
218218 .execute(&state.db)
219219 .await;
220220
+16-33
src/api/feed/timeline.rs
···4444 State(state): State<AppState>,
4545 headers: axum::http::HeaderMap,
4646) -> Response {
4747- let auth_header = headers.get("Authorization");
4848- if auth_header.is_none() {
4949- return (
5050- StatusCode::UNAUTHORIZED,
5151- Json(json!({"error": "AuthenticationRequired"})),
5252- )
5353- .into_response();
5454- }
5555- let token = auth_header
5656- .unwrap()
5757- .to_str()
5858- .unwrap_or("")
5959- .replace("Bearer ", "");
4747+ let token = match crate::auth::extract_bearer_token_from_header(
4848+ headers.get("Authorization").and_then(|h| h.to_str().ok())
4949+ ) {
5050+ Some(t) => t,
5151+ None => {
5252+ return (
5353+ StatusCode::UNAUTHORIZED,
5454+ Json(json!({"error": "AuthenticationRequired"})),
5555+ )
5656+ .into_response();
5757+ }
5858+ };
60596161- let session = sqlx::query!(
6262- "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.access_jwt = $1",
6363- token
6464- )
6565- .fetch_optional(&state.db)
6666- .await
6767- .unwrap_or(None);
6868-6969- let (did, key_bytes) = match session {
7070- Some(row) => (row.did, row.key_bytes),
7171- None => {
6060+ let auth_user = match crate::auth::validate_bearer_token(&state.db, &token).await {
6161+ Ok(user) => user,
6262+ Err(_) => {
7263 return (
7364 StatusCode::UNAUTHORIZED,
7465 Json(json!({"error": "AuthenticationFailed"})),
···7768 }
7869 };
79708080- if crate::auth::verify_token(&token, &key_bytes).is_err() {
8181- return (
8282- StatusCode::UNAUTHORIZED,
8383- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
8484- )
8585- .into_response();
8686- }
8787-8888- let user_query = sqlx::query!("SELECT id FROM users WHERE did = $1", did)
7171+ let user_query = sqlx::query!("SELECT id FROM users WHERE did = $1", auth_user.did)
8972 .fetch_optional(&state.db)
9073 .await;
9174
···4343 let mut auth_header_val = headers.get("Authorization").map(|h| h.clone());
44444545 if let Some(aud) = &proxy_header {
4646- if let Some(auth_val) = &auth_header_val {
4747- if let Ok(token) = auth_val.to_str() {
4848- let token = token.replace("Bearer ", "");
4949- if let Ok(did) = crate::auth::get_did_from_token(&token) {
5050- let key_row = sqlx::query!("SELECT k.key_bytes FROM user_keys k JOIN users u ON k.user_id = u.id WHERE u.did = $1", did)
5151- .fetch_optional(&state.db)
5252- .await;
4646+ if let Some(token) = crate::auth::extract_bearer_token_from_header(
4747+ headers.get("Authorization").and_then(|h| h.to_str().ok())
4848+ ) {
4949+ if let Ok(did) = crate::auth::get_did_from_token(&token) {
5050+ let key_row = sqlx::query!("SELECT k.key_bytes, k.encryption_version FROM user_keys k JOIN users u ON k.user_id = u.id WHERE u.did = $1", did)
5151+ .fetch_optional(&state.db)
5252+ .await;
53535454- if let Ok(Some(row)) = key_row {
5454+ if let Ok(Some(row)) = key_row {
5555+ if let Ok(decrypted_key) = crate::config::decrypt_key(&row.key_bytes, row.encryption_version) {
5556 if let Ok(new_token) =
5656- crate::auth::create_service_token(&did, aud, &method, &row.key_bytes)
5757+ crate::auth::create_service_token(&did, aud, &method, &decrypted_key)
5758 {
5859 if let Ok(val) =
5960 axum::http::HeaderValue::from_str(&format!("Bearer {}", new_token))
+32-64
src/api/repo/blob.rs
···2020 headers: axum::http::HeaderMap,
2121 body: Bytes,
2222) -> Response {
2323- let auth_header = headers.get("Authorization");
2424- if auth_header.is_none() {
2525- return (
2626- StatusCode::UNAUTHORIZED,
2727- Json(json!({"error": "AuthenticationRequired"})),
2828- )
2929- .into_response();
3030- }
3131- let token = auth_header
3232- .unwrap()
3333- .to_str()
3434- .unwrap_or("")
3535- .replace("Bearer ", "");
3636-3737- let session = sqlx::query!(
3838- "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.access_jwt = $1",
3939- token
4040- )
4141- .fetch_optional(&state.db)
4242- .await
4343- .unwrap_or(None);
4444-4545- let (did, key_bytes) = match session {
4646- Some(row) => (row.did, row.key_bytes),
2323+ let token = match crate::auth::extract_bearer_token_from_header(
2424+ headers.get("Authorization").and_then(|h| h.to_str().ok())
2525+ ) {
2626+ Some(t) => t,
4727 None => {
4828 return (
4929 StatusCode::UNAUTHORIZED,
5050- Json(json!({"error": "AuthenticationFailed"})),
3030+ Json(json!({"error": "AuthenticationRequired"})),
5131 )
5232 .into_response();
5333 }
5434 };
55355656- if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
5757- return (
5858- StatusCode::UNAUTHORIZED,
5959- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
6060- )
6161- .into_response();
6262- }
3636+ let auth_user = match crate::auth::validate_bearer_token(&state.db, &token).await {
3737+ Ok(user) => user,
3838+ Err(_) => {
3939+ return (
4040+ StatusCode::UNAUTHORIZED,
4141+ Json(json!({"error": "AuthenticationFailed"})),
4242+ )
4343+ .into_response();
4444+ }
4545+ };
4646+ let did = auth_user.did;
63476448 let mime_type = headers
6549 .get("content-type")
···182166 headers: axum::http::HeaderMap,
183167 Query(params): Query<ListMissingBlobsParams>,
184168) -> Response {
185185- let auth_header = headers.get("Authorization");
186186- if auth_header.is_none() {
187187- return (
188188- StatusCode::UNAUTHORIZED,
189189- Json(json!({"error": "AuthenticationRequired"})),
190190- )
191191- .into_response();
192192- }
193193-194194- let token = auth_header
195195- .unwrap()
196196- .to_str()
197197- .unwrap_or("")
198198- .replace("Bearer ", "");
199199-200200- let session = sqlx::query!(
201201- "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.access_jwt = $1",
202202- token
203203- )
204204- .fetch_optional(&state.db)
205205- .await
206206- .unwrap_or(None);
207207-208208- let (did, key_bytes) = match session {
209209- Some(row) => (row.did, row.key_bytes),
169169+ let token = match crate::auth::extract_bearer_token_from_header(
170170+ headers.get("Authorization").and_then(|h| h.to_str().ok())
171171+ ) {
172172+ Some(t) => t,
210173 None => {
211174 return (
212175 StatusCode::UNAUTHORIZED,
176176+ Json(json!({"error": "AuthenticationRequired"})),
177177+ )
178178+ .into_response();
179179+ }
180180+ };
181181+182182+ let auth_user = match crate::auth::validate_bearer_token(&state.db, &token).await {
183183+ Ok(user) => user,
184184+ Err(_) => {
185185+ return (
186186+ StatusCode::UNAUTHORIZED,
213187 Json(json!({"error": "AuthenticationFailed"})),
214188 )
215189 .into_response();
216190 }
217191 };
218192219219- if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
220220- return (
221221- StatusCode::UNAUTHORIZED,
222222- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
223223- )
224224- .into_response();
225225- }
193193+ let did = auth_user.did;
226194227195 let user_query = sqlx::query!("SELECT id FROM users WHERE did = $1", did)
228196 .fetch_optional(&state.db)
+16-31
src/api/repo/record/batch.rs
···7373 headers: axum::http::HeaderMap,
7474 Json(input): Json<ApplyWritesInput>,
7575) -> Response {
7676- let auth_header = headers.get("Authorization");
7777- if auth_header.is_none() {
7878- return (
7979- StatusCode::UNAUTHORIZED,
8080- Json(json!({"error": "AuthenticationRequired"})),
8181- )
8282- .into_response();
8383- }
8484- let token = auth_header
8585- .unwrap()
8686- .to_str()
8787- .unwrap_or("")
8888- .replace("Bearer ", "");
7676+ let token = match crate::auth::extract_bearer_token_from_header(
7777+ headers.get("Authorization").and_then(|h| h.to_str().ok())
7878+ ) {
7979+ Some(t) => t,
8080+ None => {
8181+ return (
8282+ StatusCode::UNAUTHORIZED,
8383+ Json(json!({"error": "AuthenticationRequired"})),
8484+ )
8585+ .into_response();
8686+ }
8787+ };
89889090- let session = sqlx::query!(
9191- "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.access_jwt = $1",
9292- token
9393- )
9494- .fetch_optional(&state.db)
9595- .await
9696- .unwrap_or(None);
9797-9898- let (did, key_bytes) = match session {
9999- Some(row) => (row.did, row.key_bytes),
100100- None => {
8989+ let auth_user = match crate::auth::validate_bearer_token(&state.db, &token).await {
9090+ Ok(user) => user,
9191+ Err(_) => {
10192 return (
10293 StatusCode::UNAUTHORIZED,
10394 Json(json!({"error": "AuthenticationFailed"})),
···10697 }
10798 };
10899109109- if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
110110- return (
111111- StatusCode::UNAUTHORIZED,
112112- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
113113- )
114114- .into_response();
115115- }
100100+ let did = auth_user.did;
116101117102 if input.repo != did {
118103 return (
+15-33
src/api/repo/record/write.rs
···2323 headers: &HeaderMap,
2424 repo_did: &str,
2525) -> Result<(String, Uuid, Cid), Response> {
2626- let auth_header = headers.get("Authorization").ok_or_else(|| {
2626+ let token = crate::auth::extract_bearer_token_from_header(
2727+ headers.get("Authorization").and_then(|h| h.to_str().ok())
2828+ ).ok_or_else(|| {
2729 (
2830 StatusCode::UNAUTHORIZED,
2931 Json(json!({"error": "AuthenticationRequired"})),
3032 )
3133 .into_response()
3234 })?;
3333- let token = auth_header
3434- .to_str()
3535- .unwrap_or("")
3636- .replace("Bearer ", "");
37353838- let session = sqlx::query!(
3939- "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.access_jwt = $1",
4040- token
4141- )
4242- .fetch_optional(&state.db)
4343- .await
4444- .map_err(|e| {
4545- error!("DB error fetching session: {}", e);
4646- (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response()
4747- })?
4848- .ok_or_else(|| {
4949- (
5050- StatusCode::UNAUTHORIZED,
5151- Json(json!({"error": "AuthenticationFailed"})),
5252- )
5353- .into_response()
5454- })?;
5555-5656- crate::auth::verify_token(&token, &session.key_bytes).map_err(|_| {
5757- (
5858- StatusCode::UNAUTHORIZED,
5959- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
6060- )
6161- .into_response()
6262- })?;
3636+ let auth_user = crate::auth::validate_bearer_token(&state.db, &token)
3737+ .await
3838+ .map_err(|_| {
3939+ (
4040+ StatusCode::UNAUTHORIZED,
4141+ Json(json!({"error": "AuthenticationFailed"})),
4242+ )
4343+ .into_response()
4444+ })?;
63456464- if repo_did != session.did {
4646+ if repo_did != auth_user.did {
6547 return Err((
6648 StatusCode::FORBIDDEN,
6749 Json(json!({"error": "InvalidRepo", "message": "Repo does not match authenticated user"})),
···6951 .into_response());
7052 }
71537272- let user_id = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", session.did)
5454+ let user_id = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", auth_user.did)
7355 .fetch_optional(&state.db)
7456 .await
7557 .map_err(|e| {
···10890 .into_response()
10991 })?;
11092111111- Ok((session.did, user_id, current_root_cid))
9393+ Ok((auth_user.did, user_id, current_root_cid))
11294}
1139511496#[derive(Deserialize)]
+80-169
src/api/server/account_status.rs
···3030 State(state): State<AppState>,
3131 headers: axum::http::HeaderMap,
3232) -> Response {
3333- let auth_header = headers.get("Authorization");
3434- if auth_header.is_none() {
3535- return (
3636- StatusCode::UNAUTHORIZED,
3737- Json(json!({"error": "AuthenticationRequired"})),
3838- )
3939- .into_response();
4040- }
4141-4242- let token = auth_header
4343- .unwrap()
4444- .to_str()
4545- .unwrap_or("")
4646- .replace("Bearer ", "");
3333+ let token = match crate::auth::extract_bearer_token_from_header(
3434+ headers.get("Authorization").and_then(|h| h.to_str().ok())
3535+ ) {
3636+ Some(t) => t,
3737+ None => {
3838+ return (
3939+ StatusCode::UNAUTHORIZED,
4040+ Json(json!({"error": "AuthenticationRequired"})),
4141+ )
4242+ .into_response();
4343+ }
4444+ };
47454848- let session = sqlx::query!(
4949- r#"
5050- SELECT s.did, k.key_bytes, u.id as user_id
5151- FROM sessions s
5252- JOIN users u ON s.did = u.did
5353- JOIN user_keys k ON u.id = k.user_id
5454- WHERE s.access_jwt = $1
5555- "#,
5656- token
5757- )
5858- .fetch_optional(&state.db)
5959- .await;
6060-6161- let (did, key_bytes, user_id) = match session {
6262- Ok(Some(row)) => (row.did, row.key_bytes, row.user_id),
6363- Ok(None) => {
4646+ let auth_result = crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await;
4747+ let did = match auth_result {
4848+ Ok(user) => user.did,
4949+ Err(e) => {
6450 return (
6551 StatusCode::UNAUTHORIZED,
6666- Json(json!({"error": "AuthenticationFailed"})),
5252+ Json(json!({"error": e})),
6753 )
6854 .into_response();
6955 }
7070- Err(e) => {
7171- error!("DB error in check_account_status: {:?}", e);
5656+ };
5757+5858+ let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
5959+ .fetch_optional(&state.db)
6060+ .await
6161+ {
6262+ Ok(Some(id)) => id,
6363+ _ => {
7264 return (
7365 StatusCode::INTERNAL_SERVER_ERROR,
7466 Json(json!({"error": "InternalError"})),
···7769 }
7870 };
79718080- if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
8181- return (
8282- StatusCode::UNAUTHORIZED,
8383- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
8484- )
8585- .into_response();
8686- }
8787-8872 let user_status = sqlx::query!("SELECT deactivated_at FROM users WHERE did = $1", did)
8973 .fetch_optional(&state.db)
9074 .await;
···139123 State(state): State<AppState>,
140124 headers: axum::http::HeaderMap,
141125) -> Response {
142142- let auth_header = headers.get("Authorization");
143143- if auth_header.is_none() {
144144- return (
145145- StatusCode::UNAUTHORIZED,
146146- Json(json!({"error": "AuthenticationRequired"})),
147147- )
148148- .into_response();
149149- }
150150-151151- let token = auth_header
152152- .unwrap()
153153- .to_str()
154154- .unwrap_or("")
155155- .replace("Bearer ", "");
156156-157157- let session = sqlx::query!(
158158- r#"
159159- SELECT s.did, k.key_bytes
160160- FROM sessions s
161161- JOIN users u ON s.did = u.did
162162- JOIN user_keys k ON u.id = k.user_id
163163- WHERE s.access_jwt = $1
164164- "#,
165165- token
166166- )
167167- .fetch_optional(&state.db)
168168- .await;
169169-170170- let (did, key_bytes) = match session {
171171- Ok(Some(row)) => (row.did, row.key_bytes),
172172- Ok(None) => {
126126+ let token = match crate::auth::extract_bearer_token_from_header(
127127+ headers.get("Authorization").and_then(|h| h.to_str().ok())
128128+ ) {
129129+ Some(t) => t,
130130+ None => {
173131 return (
174132 StatusCode::UNAUTHORIZED,
175175- Json(json!({"error": "AuthenticationFailed"})),
133133+ Json(json!({"error": "AuthenticationRequired"})),
176134 )
177135 .into_response();
178136 }
137137+ };
138138+139139+ let auth_result = crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await;
140140+ let did = match auth_result {
141141+ Ok(user) => user.did,
179142 Err(e) => {
180180- error!("DB error in activate_account: {:?}", e);
181143 return (
182182- StatusCode::INTERNAL_SERVER_ERROR,
183183- Json(json!({"error": "InternalError"})),
144144+ StatusCode::UNAUTHORIZED,
145145+ Json(json!({"error": e})),
184146 )
185147 .into_response();
186148 }
187149 };
188188-189189- if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
190190- return (
191191- StatusCode::UNAUTHORIZED,
192192- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
193193- )
194194- .into_response();
195195- }
196150197151 let result = sqlx::query!("UPDATE users SET deactivated_at = NULL WHERE did = $1", did)
198152 .execute(&state.db)
···222176 headers: axum::http::HeaderMap,
223177 Json(_input): Json<DeactivateAccountInput>,
224178) -> Response {
225225- let auth_header = headers.get("Authorization");
226226- if auth_header.is_none() {
227227- return (
228228- StatusCode::UNAUTHORIZED,
229229- Json(json!({"error": "AuthenticationRequired"})),
230230- )
231231- .into_response();
232232- }
233233-234234- let token = auth_header
235235- .unwrap()
236236- .to_str()
237237- .unwrap_or("")
238238- .replace("Bearer ", "");
239239-240240- let session = sqlx::query!(
241241- r#"
242242- SELECT s.did, k.key_bytes
243243- FROM sessions s
244244- JOIN users u ON s.did = u.did
245245- JOIN user_keys k ON u.id = k.user_id
246246- WHERE s.access_jwt = $1
247247- "#,
248248- token
249249- )
250250- .fetch_optional(&state.db)
251251- .await;
252252-253253- let (did, key_bytes) = match session {
254254- Ok(Some(row)) => (row.did, row.key_bytes),
255255- Ok(None) => {
179179+ let token = match crate::auth::extract_bearer_token_from_header(
180180+ headers.get("Authorization").and_then(|h| h.to_str().ok())
181181+ ) {
182182+ Some(t) => t,
183183+ None => {
256184 return (
257185 StatusCode::UNAUTHORIZED,
258258- Json(json!({"error": "AuthenticationFailed"})),
186186+ Json(json!({"error": "AuthenticationRequired"})),
259187 )
260188 .into_response();
261189 }
190190+ };
191191+192192+ let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
193193+ let did = match auth_result {
194194+ Ok(user) => user.did,
262195 Err(e) => {
263263- error!("DB error in deactivate_account: {:?}", e);
264196 return (
265265- StatusCode::INTERNAL_SERVER_ERROR,
266266- Json(json!({"error": "InternalError"})),
197197+ StatusCode::UNAUTHORIZED,
198198+ Json(json!({"error": e})),
267199 )
268200 .into_response();
269201 }
270202 };
271203272272- if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
273273- return (
274274- StatusCode::UNAUTHORIZED,
275275- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
276276- )
277277- .into_response();
278278- }
279279-280204 let result = sqlx::query!("UPDATE users SET deactivated_at = NOW() WHERE did = $1", did)
281205 .execute(&state.db)
282206 .await;
···298222 State(state): State<AppState>,
299223 headers: axum::http::HeaderMap,
300224) -> Response {
301301- let auth_header = headers.get("Authorization");
302302- if auth_header.is_none() {
303303- return (
304304- StatusCode::UNAUTHORIZED,
305305- Json(json!({"error": "AuthenticationRequired"})),
306306- )
307307- .into_response();
308308- }
225225+ let token = match crate::auth::extract_bearer_token_from_header(
226226+ headers.get("Authorization").and_then(|h| h.to_str().ok())
227227+ ) {
228228+ Some(t) => t,
229229+ None => {
230230+ return (
231231+ StatusCode::UNAUTHORIZED,
232232+ Json(json!({"error": "AuthenticationRequired"})),
233233+ )
234234+ .into_response();
235235+ }
236236+ };
309237310310- let token = auth_header
311311- .unwrap()
312312- .to_str()
313313- .unwrap_or("")
314314- .replace("Bearer ", "");
315315-316316- let session = sqlx::query!(
317317- r#"
318318- SELECT s.did, u.id as user_id, u.email, u.handle, k.key_bytes
319319- FROM sessions s
320320- JOIN users u ON s.did = u.did
321321- JOIN user_keys k ON u.id = k.user_id
322322- WHERE s.access_jwt = $1
323323- "#,
324324- token
325325- )
326326- .fetch_optional(&state.db)
327327- .await;
328328-329329- let (did, user_id, email, handle, key_bytes) = match session {
330330- Ok(Some(row)) => (row.did, row.user_id, row.email, row.handle, row.key_bytes),
331331- Ok(None) => {
238238+ let auth_result = crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await;
239239+ let did = match auth_result {
240240+ Ok(user) => user.did,
241241+ Err(e) => {
332242 return (
333243 StatusCode::UNAUTHORIZED,
334334- Json(json!({"error": "AuthenticationFailed"})),
244244+ Json(json!({"error": e})),
335245 )
336246 .into_response();
337247 }
338338- Err(e) => {
339339- error!("DB error in request_account_delete: {:?}", e);
248248+ };
249249+250250+ let user = match sqlx::query!("SELECT id, email, handle FROM users WHERE did = $1", did)
251251+ .fetch_optional(&state.db)
252252+ .await
253253+ {
254254+ Ok(Some(row)) => row,
255255+ _ => {
340256 return (
341257 StatusCode::INTERNAL_SERVER_ERROR,
342258 Json(json!({"error": "InternalError"})),
···344260 .into_response();
345261 }
346262 };
347347-348348- if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
349349- return (
350350- StatusCode::UNAUTHORIZED,
351351- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
352352- )
353353- .into_response();
354354- }
263263+ let user_id = user.id;
264264+ let email = user.email;
265265+ let handle = user.handle;
355266356267 let confirmation_token = Uuid::new_v4().to_string();
357268 let expires_at = Utc::now() + Duration::minutes(15);
···541452 };
542453543454 let deletion_result: Result<(), sqlx::Error> = async {
544544- sqlx::query!("DELETE FROM sessions WHERE did = $1", did)
455455+ sqlx::query!("DELETE FROM session_tokens WHERE did = $1", did)
545456 .execute(&mut *tx)
546457 .await?;
547458
+75-123
src/api/server/app_password.rs
···2626 State(state): State<AppState>,
2727 headers: axum::http::HeaderMap,
2828) -> Response {
2929- let auth_header = headers.get("Authorization");
3030- if auth_header.is_none() {
3131- return (
3232- StatusCode::UNAUTHORIZED,
3333- Json(json!({"error": "AuthenticationRequired"})),
3434- )
3535- .into_response();
3636- }
3737-3838- let token = auth_header
3939- .unwrap()
4040- .to_str()
4141- .unwrap_or("")
4242- .replace("Bearer ", "");
4343-4444- let session = sqlx::query!(
4545- r#"
4646- SELECT s.did, k.key_bytes, u.id as user_id
4747- FROM sessions s
4848- JOIN users u ON s.did = u.did
4949- JOIN user_keys k ON u.id = k.user_id
5050- WHERE s.access_jwt = $1
5151- "#,
5252- token
5353- )
5454- .fetch_optional(&state.db)
5555- .await;
2929+ let token = match crate::auth::extract_bearer_token_from_header(
3030+ headers.get("Authorization").and_then(|h| h.to_str().ok())
3131+ ) {
3232+ Some(t) => t,
3333+ None => {
3434+ return (
3535+ StatusCode::UNAUTHORIZED,
3636+ Json(json!({"error": "AuthenticationRequired"})),
3737+ )
3838+ .into_response();
3939+ }
4040+ };
56415757- let (_did, key_bytes, user_id) = match session {
5858- Ok(Some(row)) => (row.did, row.key_bytes, row.user_id),
5959- Ok(None) => {
4242+ let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
4343+ let did = match auth_result {
4444+ Ok(user) => user.did,
4545+ Err(e) => {
6046 return (
6147 StatusCode::UNAUTHORIZED,
6262- Json(json!({"error": "AuthenticationFailed"})),
4848+ Json(json!({"error": e})),
6349 )
6450 .into_response();
6551 }
6666- Err(e) => {
6767- error!("DB error in list_app_passwords: {:?}", e);
5252+ };
5353+5454+ let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
5555+ .fetch_optional(&state.db)
5656+ .await
5757+ {
5858+ Ok(Some(id)) => id,
5959+ _ => {
6860 return (
6961 StatusCode::INTERNAL_SERVER_ERROR,
7062 Json(json!({"error": "InternalError"})),
···7264 .into_response();
7365 }
7466 };
7575-7676- if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
7777- return (
7878- StatusCode::UNAUTHORIZED,
7979- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
8080- )
8181- .into_response();
8282- }
83678468 let result = sqlx::query!("SELECT name, created_at, privileged FROM app_passwords WHERE user_id = $1 ORDER BY created_at DESC", user_id)
8569 .fetch_all(&state.db)
···131115 headers: axum::http::HeaderMap,
132116 Json(input): Json<CreateAppPasswordInput>,
133117) -> Response {
134134- let auth_header = headers.get("Authorization");
135135- if auth_header.is_none() {
136136- return (
137137- StatusCode::UNAUTHORIZED,
138138- Json(json!({"error": "AuthenticationRequired"})),
139139- )
140140- .into_response();
141141- }
142142-143143- let token = auth_header
144144- .unwrap()
145145- .to_str()
146146- .unwrap_or("")
147147- .replace("Bearer ", "");
148148-149149- let session = sqlx::query!(
150150- r#"
151151- SELECT s.did, k.key_bytes, u.id as user_id
152152- FROM sessions s
153153- JOIN users u ON s.did = u.did
154154- JOIN user_keys k ON u.id = k.user_id
155155- WHERE s.access_jwt = $1
156156- "#,
157157- token
158158- )
159159- .fetch_optional(&state.db)
160160- .await;
161161-162162- let (_did, key_bytes, user_id) = match session {
163163- Ok(Some(row)) => (row.did, row.key_bytes, row.user_id),
164164- Ok(None) => {
118118+ let token = match crate::auth::extract_bearer_token_from_header(
119119+ headers.get("Authorization").and_then(|h| h.to_str().ok())
120120+ ) {
121121+ Some(t) => t,
122122+ None => {
165123 return (
166124 StatusCode::UNAUTHORIZED,
167167- Json(json!({"error": "AuthenticationFailed"})),
125125+ Json(json!({"error": "AuthenticationRequired"})),
168126 )
169127 .into_response();
170128 }
129129+ };
130130+131131+ let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
132132+ let did = match auth_result {
133133+ Ok(user) => user.did,
171134 Err(e) => {
172172- error!("DB error in create_app_password: {:?}", e);
135135+ return (
136136+ StatusCode::UNAUTHORIZED,
137137+ Json(json!({"error": e})),
138138+ )
139139+ .into_response();
140140+ }
141141+ };
142142+143143+ let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
144144+ .fetch_optional(&state.db)
145145+ .await
146146+ {
147147+ Ok(Some(id)) => id,
148148+ _ => {
173149 return (
174150 StatusCode::INTERNAL_SERVER_ERROR,
175151 Json(json!({"error": "InternalError"})),
···177153 .into_response();
178154 }
179155 };
180180-181181- if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
182182- return (
183183- StatusCode::UNAUTHORIZED,
184184- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
185185- )
186186- .into_response();
187187- }
188156189157 let name = input.name.trim();
190158 if name.is_empty() {
···275243 headers: axum::http::HeaderMap,
276244 Json(input): Json<RevokeAppPasswordInput>,
277245) -> Response {
278278- let auth_header = headers.get("Authorization");
279279- if auth_header.is_none() {
280280- return (
281281- StatusCode::UNAUTHORIZED,
282282- Json(json!({"error": "AuthenticationRequired"})),
283283- )
284284- .into_response();
285285- }
286286-287287- let token = auth_header
288288- .unwrap()
289289- .to_str()
290290- .unwrap_or("")
291291- .replace("Bearer ", "");
292292-293293- let session = sqlx::query!(
294294- r#"
295295- SELECT s.did, k.key_bytes, u.id as user_id
296296- FROM sessions s
297297- JOIN users u ON s.did = u.did
298298- JOIN user_keys k ON u.id = k.user_id
299299- WHERE s.access_jwt = $1
300300- "#,
301301- token
302302- )
303303- .fetch_optional(&state.db)
304304- .await;
305305-306306- let (_did, key_bytes, user_id) = match session {
307307- Ok(Some(row)) => (row.did, row.key_bytes, row.user_id),
308308- Ok(None) => {
246246+ let token = match crate::auth::extract_bearer_token_from_header(
247247+ headers.get("Authorization").and_then(|h| h.to_str().ok())
248248+ ) {
249249+ Some(t) => t,
250250+ None => {
309251 return (
310252 StatusCode::UNAUTHORIZED,
311311- Json(json!({"error": "AuthenticationFailed"})),
253253+ Json(json!({"error": "AuthenticationRequired"})),
312254 )
313255 .into_response();
314256 }
257257+ };
258258+259259+ let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
260260+ let did = match auth_result {
261261+ Ok(user) => user.did,
315262 Err(e) => {
316316- error!("DB error in revoke_app_password: {:?}", e);
263263+ return (
264264+ StatusCode::UNAUTHORIZED,
265265+ Json(json!({"error": e})),
266266+ )
267267+ .into_response();
268268+ }
269269+ };
270270+271271+ let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
272272+ .fetch_optional(&state.db)
273273+ .await
274274+ {
275275+ Ok(Some(id)) => id,
276276+ _ => {
317277 return (
318278 StatusCode::INTERNAL_SERVER_ERROR,
319279 Json(json!({"error": "InternalError"})),
···321281 .into_response();
322282 }
323283 };
324324-325325- if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
326326- return (
327327- StatusCode::UNAUTHORIZED,
328328- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
329329- )
330330- .into_response();
331331- }
332284333285 let name = input.name.trim();
334286 if name.is_empty() {
+92-148
src/api/server/email.rs
···3030 headers: axum::http::HeaderMap,
3131 Json(input): Json<RequestEmailUpdateInput>,
3232) -> Response {
3333- let auth_header = headers.get("Authorization");
3434- if auth_header.is_none() {
3535- return (
3636- StatusCode::UNAUTHORIZED,
3737- Json(json!({"error": "AuthenticationRequired"})),
3838- )
3939- .into_response();
4040- }
4141-4242- let token = auth_header
4343- .unwrap()
4444- .to_str()
4545- .unwrap_or("")
4646- .replace("Bearer ", "");
4747-4848- let session = sqlx::query!(
4949- r#"
5050- SELECT s.did, k.key_bytes, u.id as user_id, u.handle
5151- FROM sessions s
5252- JOIN users u ON s.did = u.did
5353- JOIN user_keys k ON u.id = k.user_id
5454- WHERE s.access_jwt = $1
5555- "#,
5656- token
5757- )
5858- .fetch_optional(&state.db)
5959- .await;
6060-6161- let (_did, key_bytes, user_id, handle) = match session {
6262- Ok(Some(row)) => (row.did, row.key_bytes, row.user_id, row.handle),
6363- Ok(None) => {
3333+ let token = match crate::auth::extract_bearer_token_from_header(
3434+ headers.get("Authorization").and_then(|h| h.to_str().ok())
3535+ ) {
3636+ Some(t) => t,
3737+ None => {
6438 return (
6539 StatusCode::UNAUTHORIZED,
6666- Json(json!({"error": "AuthenticationFailed"})),
4040+ Json(json!({"error": "AuthenticationRequired"})),
6741 )
6842 .into_response();
6943 }
4444+ };
4545+4646+ let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
4747+ let did = match auth_result {
4848+ Ok(user) => user.did,
7049 Err(e) => {
7171- error!("DB error in request_email_update: {:?}", e);
5050+ return (
5151+ StatusCode::UNAUTHORIZED,
5252+ Json(json!({"error": e})),
5353+ )
5454+ .into_response();
5555+ }
5656+ };
5757+5858+ let user = match sqlx::query!("SELECT id, handle FROM users WHERE did = $1", did)
5959+ .fetch_optional(&state.db)
6060+ .await
6161+ {
6262+ Ok(Some(row)) => row,
6363+ _ => {
7264 return (
7365 StatusCode::INTERNAL_SERVER_ERROR,
7466 Json(json!({"error": "InternalError"})),
···7668 .into_response();
7769 }
7870 };
7979-8080- if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
8181- return (
8282- StatusCode::UNAUTHORIZED,
8383- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
8484- )
8585- .into_response();
8686- }
7171+ let user_id = user.id;
7272+ let handle = user.handle;
87738874 let email = input.email.trim().to_lowercase();
8975 if email.is_empty() {
···159145 headers: axum::http::HeaderMap,
160146 Json(input): Json<ConfirmEmailInput>,
161147) -> Response {
162162- let auth_header = headers.get("Authorization");
163163- if auth_header.is_none() {
164164- return (
165165- StatusCode::UNAUTHORIZED,
166166- Json(json!({"error": "AuthenticationRequired"})),
167167- )
168168- .into_response();
169169- }
170170-171171- let token = auth_header
172172- .unwrap()
173173- .to_str()
174174- .unwrap_or("")
175175- .replace("Bearer ", "");
176176-177177- let session = sqlx::query!(
178178- r#"
179179- SELECT s.did, k.key_bytes, u.id as user_id, u.email_confirmation_code, u.email_confirmation_code_expires_at, u.email_pending_verification
180180- FROM sessions s
181181- JOIN users u ON s.did = u.did
182182- JOIN user_keys k ON u.id = k.user_id
183183- WHERE s.access_jwt = $1
184184- "#,
185185- token
186186- )
187187- .fetch_optional(&state.db)
188188- .await;
189189-190190- let (_did, key_bytes, user_id, stored_code, expires_at, email_pending_verification) = match session {
191191- Ok(Some(row)) => (
192192- row.did,
193193- row.key_bytes,
194194- row.user_id,
195195- row.email_confirmation_code,
196196- row.email_confirmation_code_expires_at,
197197- row.email_pending_verification,
198198- ),
199199- Ok(None) => {
148148+ let token = match crate::auth::extract_bearer_token_from_header(
149149+ headers.get("Authorization").and_then(|h| h.to_str().ok())
150150+ ) {
151151+ Some(t) => t,
152152+ None => {
200153 return (
201154 StatusCode::UNAUTHORIZED,
202202- Json(json!({"error": "AuthenticationFailed"})),
155155+ Json(json!({"error": "AuthenticationRequired"})),
203156 )
204157 .into_response();
205158 }
159159+ };
160160+161161+ let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
162162+ let did = match auth_result {
163163+ Ok(user) => user.did,
206164 Err(e) => {
207207- error!("DB error in confirm_email: {:?}", e);
165165+ return (
166166+ StatusCode::UNAUTHORIZED,
167167+ Json(json!({"error": e})),
168168+ )
169169+ .into_response();
170170+ }
171171+ };
172172+173173+ let user = match sqlx::query!(
174174+ "SELECT id, email_confirmation_code, email_confirmation_code_expires_at, email_pending_verification FROM users WHERE did = $1",
175175+ did
176176+ )
177177+ .fetch_optional(&state.db)
178178+ .await
179179+ {
180180+ Ok(Some(row)) => row,
181181+ _ => {
208182 return (
209183 StatusCode::INTERNAL_SERVER_ERROR,
210184 Json(json!({"error": "InternalError"})),
···212186 .into_response();
213187 }
214188 };
215215-216216- if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
217217- return (
218218- StatusCode::UNAUTHORIZED,
219219- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
220220- )
221221- .into_response();
222222- }
189189+ let user_id = user.id;
190190+ let stored_code = user.email_confirmation_code;
191191+ let expires_at = user.email_confirmation_code_expires_at;
192192+ let email_pending_verification = user.email_pending_verification;
223193224194 let email = input.email.trim().to_lowercase();
225195 let confirmation_code = input.token.trim();
···301271 headers: axum::http::HeaderMap,
302272 Json(input): Json<UpdateEmailInput>,
303273) -> Response {
304304- let auth_header = headers.get("Authorization");
305305- if auth_header.is_none() {
306306- return (
307307- StatusCode::UNAUTHORIZED,
308308- Json(json!({"error": "AuthenticationRequired"})),
309309- )
310310- .into_response();
311311- }
312312-313313- let token = auth_header
314314- .unwrap()
315315- .to_str()
316316- .unwrap_or("")
317317- .replace("Bearer ", "");
318318-319319- let session = sqlx::query!(
320320- r#"
321321- SELECT s.did, k.key_bytes, u.id as user_id, u.email as current_email,
322322- u.email_confirmation_code, u.email_confirmation_code_expires_at,
323323- u.email_pending_verification
324324- FROM sessions s
325325- JOIN users u ON s.did = u.did
326326- JOIN user_keys k ON u.id = k.user_id
327327- WHERE s.access_jwt = $1
328328- "#,
329329- token
330330- )
331331- .fetch_optional(&state.db)
332332- .await;
333333-334334- let (
335335- _did,
336336- key_bytes,
337337- user_id,
338338- current_email,
339339- stored_code,
340340- expires_at,
341341- email_pending_verification,
342342- ) = match session {
343343- Ok(Some(row)) => (
344344- row.did,
345345- row.key_bytes,
346346- row.user_id,
347347- row.current_email,
348348- row.email_confirmation_code,
349349- row.email_confirmation_code_expires_at,
350350- row.email_pending_verification,
351351- ),
352352- Ok(None) => {
274274+ let token = match crate::auth::extract_bearer_token_from_header(
275275+ headers.get("Authorization").and_then(|h| h.to_str().ok())
276276+ ) {
277277+ Some(t) => t,
278278+ None => {
353279 return (
354280 StatusCode::UNAUTHORIZED,
355355- Json(json!({"error": "AuthenticationFailed"})),
281281+ Json(json!({"error": "AuthenticationRequired"})),
356282 )
357283 .into_response();
358284 }
285285+ };
286286+287287+ let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
288288+ let did = match auth_result {
289289+ Ok(user) => user.did,
359290 Err(e) => {
360360- error!("DB error in update_email: {:?}", e);
291291+ return (
292292+ StatusCode::UNAUTHORIZED,
293293+ Json(json!({"error": e})),
294294+ )
295295+ .into_response();
296296+ }
297297+ };
298298+299299+ let user = match sqlx::query!(
300300+ "SELECT id, email, email_confirmation_code, email_confirmation_code_expires_at, email_pending_verification FROM users WHERE did = $1",
301301+ did
302302+ )
303303+ .fetch_optional(&state.db)
304304+ .await
305305+ {
306306+ Ok(Some(row)) => row,
307307+ _ => {
361308 return (
362309 StatusCode::INTERNAL_SERVER_ERROR,
363310 Json(json!({"error": "InternalError"})),
···365312 .into_response();
366313 }
367314 };
368368-369369- if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
370370- return (
371371- StatusCode::UNAUTHORIZED,
372372- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
373373- )
374374- .into_response();
375375- }
315315+ let user_id = user.id;
316316+ let current_email = user.email;
317317+ let stored_code = user.email_confirmation_code;
318318+ let expires_at = user.email_confirmation_code_expires_at;
319319+ let email_pending_verification = user.email_pending_verification;
376320377321 let new_email = input.email.trim().to_lowercase();
378322 if new_email.is_empty() {
+75-123
src/api/server/invite.rs
···2727 headers: axum::http::HeaderMap,
2828 Json(input): Json<CreateInviteCodeInput>,
2929) -> Response {
3030- let auth_header = headers.get("Authorization");
3131- if auth_header.is_none() {
3232- return (
3333- StatusCode::UNAUTHORIZED,
3434- Json(json!({"error": "AuthenticationRequired"})),
3535- )
3636- .into_response();
3737- }
3030+ let token = match crate::auth::extract_bearer_token_from_header(
3131+ headers.get("Authorization").and_then(|h| h.to_str().ok())
3232+ ) {
3333+ Some(t) => t,
3434+ None => {
3535+ return (
3636+ StatusCode::UNAUTHORIZED,
3737+ Json(json!({"error": "AuthenticationRequired"})),
3838+ )
3939+ .into_response();
4040+ }
4141+ };
38423943 if input.use_count < 1 {
4044 return (
···4448 .into_response();
4549 }
46504747- let token = auth_header
4848- .unwrap()
4949- .to_str()
5050- .unwrap_or("")
5151- .replace("Bearer ", "");
5252-5353- let session = sqlx::query!(
5454- r#"
5555- SELECT s.did, k.key_bytes, u.id as user_id
5656- FROM sessions s
5757- JOIN users u ON s.did = u.did
5858- JOIN user_keys k ON u.id = k.user_id
5959- WHERE s.access_jwt = $1
6060- "#,
6161- token
6262- )
6363- .fetch_optional(&state.db)
6464- .await;
6565-6666- let (did, key_bytes, user_id) = match session {
6767- Ok(Some(row)) => (row.did, row.key_bytes, row.user_id),
6868- Ok(None) => {
5151+ let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
5252+ let did = match auth_result {
5353+ Ok(user) => user.did,
5454+ Err(e) => {
6955 return (
7056 StatusCode::UNAUTHORIZED,
7171- Json(json!({"error": "AuthenticationFailed"})),
5757+ Json(json!({"error": e})),
7258 )
7359 .into_response();
7460 }
7575- Err(e) => {
7676- error!("DB error in create_invite_code: {:?}", e);
6161+ };
6262+6363+ let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
6464+ .fetch_optional(&state.db)
6565+ .await
6666+ {
6767+ Ok(Some(id)) => id,
6868+ _ => {
7769 return (
7870 StatusCode::INTERNAL_SERVER_ERROR,
7971 Json(json!({"error": "InternalError"})),
···8173 .into_response();
8274 }
8375 };
8484-8585- if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
8686- return (
8787- StatusCode::UNAUTHORIZED,
8888- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
8989- )
9090- .into_response();
9191- }
92769377 let creator_user_id = if let Some(for_account) = &input.for_account {
9478 let target = sqlx::query!("SELECT id FROM users WHERE did = $1", for_account)
···184168 headers: axum::http::HeaderMap,
185169 Json(input): Json<CreateInviteCodesInput>,
186170) -> Response {
187187- let auth_header = headers.get("Authorization");
188188- if auth_header.is_none() {
189189- return (
190190- StatusCode::UNAUTHORIZED,
191191- Json(json!({"error": "AuthenticationRequired"})),
192192- )
193193- .into_response();
194194- }
171171+ let token = match crate::auth::extract_bearer_token_from_header(
172172+ headers.get("Authorization").and_then(|h| h.to_str().ok())
173173+ ) {
174174+ Some(t) => t,
175175+ None => {
176176+ return (
177177+ StatusCode::UNAUTHORIZED,
178178+ Json(json!({"error": "AuthenticationRequired"})),
179179+ )
180180+ .into_response();
181181+ }
182182+ };
195183196184 if input.use_count < 1 {
197185 return (
···201189 .into_response();
202190 }
203191204204- let token = auth_header
205205- .unwrap()
206206- .to_str()
207207- .unwrap_or("")
208208- .replace("Bearer ", "");
209209-210210- let session = sqlx::query!(
211211- r#"
212212- SELECT s.did, k.key_bytes, u.id as user_id
213213- FROM sessions s
214214- JOIN users u ON s.did = u.did
215215- JOIN user_keys k ON u.id = k.user_id
216216- WHERE s.access_jwt = $1
217217- "#,
218218- token
219219- )
220220- .fetch_optional(&state.db)
221221- .await;
222222-223223- let (_did, key_bytes, user_id) = match session {
224224- Ok(Some(row)) => (row.did, row.key_bytes, row.user_id),
225225- Ok(None) => {
192192+ let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
193193+ let did = match auth_result {
194194+ Ok(user) => user.did,
195195+ Err(e) => {
226196 return (
227197 StatusCode::UNAUTHORIZED,
228228- Json(json!({"error": "AuthenticationFailed"})),
198198+ Json(json!({"error": e})),
229199 )
230200 .into_response();
231201 }
232232- Err(e) => {
233233- error!("DB error in create_invite_codes: {:?}", e);
202202+ };
203203+204204+ let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
205205+ .fetch_optional(&state.db)
206206+ .await
207207+ {
208208+ Ok(Some(id)) => id,
209209+ _ => {
234210 return (
235211 StatusCode::INTERNAL_SERVER_ERROR,
236212 Json(json!({"error": "InternalError"})),
···238214 .into_response();
239215 }
240216 };
241241-242242- if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
243243- return (
244244- StatusCode::UNAUTHORIZED,
245245- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
246246- )
247247- .into_response();
248248- }
249217250218 let code_count = input.code_count.unwrap_or(1).max(1);
251219 let for_accounts = input.for_accounts.unwrap_or_default();
···374342 headers: axum::http::HeaderMap,
375343 axum::extract::Query(params): axum::extract::Query<GetAccountInviteCodesParams>,
376344) -> Response {
377377- let auth_header = headers.get("Authorization");
378378- if auth_header.is_none() {
379379- return (
380380- StatusCode::UNAUTHORIZED,
381381- Json(json!({"error": "AuthenticationRequired"})),
382382- )
383383- .into_response();
384384- }
385385-386386- let token = auth_header
387387- .unwrap()
388388- .to_str()
389389- .unwrap_or("")
390390- .replace("Bearer ", "");
391391-392392- let session = sqlx::query!(
393393- r#"
394394- SELECT s.did, k.key_bytes, u.id as user_id
395395- FROM sessions s
396396- JOIN users u ON s.did = u.did
397397- JOIN user_keys k ON u.id = k.user_id
398398- WHERE s.access_jwt = $1
399399- "#,
400400- token
401401- )
402402- .fetch_optional(&state.db)
403403- .await;
404404-405405- let (did, key_bytes, user_id) = match session {
406406- Ok(Some(row)) => (row.did, row.key_bytes, row.user_id),
407407- Ok(None) => {
345345+ let token = match crate::auth::extract_bearer_token_from_header(
346346+ headers.get("Authorization").and_then(|h| h.to_str().ok())
347347+ ) {
348348+ Some(t) => t,
349349+ None => {
408350 return (
409351 StatusCode::UNAUTHORIZED,
410410- Json(json!({"error": "AuthenticationFailed"})),
352352+ Json(json!({"error": "AuthenticationRequired"})),
411353 )
412354 .into_response();
413355 }
356356+ };
357357+358358+ let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
359359+ let did = match auth_result {
360360+ Ok(user) => user.did,
414361 Err(e) => {
415415- error!("DB error in get_account_invite_codes: {:?}", e);
362362+ return (
363363+ StatusCode::UNAUTHORIZED,
364364+ Json(json!({"error": e})),
365365+ )
366366+ .into_response();
367367+ }
368368+ };
369369+370370+ let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
371371+ .fetch_optional(&state.db)
372372+ .await
373373+ {
374374+ Ok(Some(id)) => id,
375375+ _ => {
416376 return (
417377 StatusCode::INTERNAL_SERVER_ERROR,
418378 Json(json!({"error": "InternalError"})),
···420380 .into_response();
421381 }
422382 };
423423-424424- if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
425425- return (
426426- StatusCode::UNAUTHORIZED,
427427- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
428428- )
429429- .into_response();
430430- }
431383432384 let include_used = params.include_used.unwrap_or(true);
433385
+1-1
src/api/server/password.rs
···211211 .into_response();
212212 }
213213214214- let _ = sqlx::query!("DELETE FROM sessions WHERE did = (SELECT did FROM users WHERE id = $1)", user_id)
214214+ let _ = sqlx::query!("DELETE FROM session_tokens WHERE did = (SELECT did FROM users WHERE id = $1)", user_id)
215215 .execute(&state.db)
216216 .await;
217217
+314-161
src/api/server/session.rs
···2727 headers: axum::http::HeaderMap,
2828 Query(params): Query<GetServiceAuthParams>,
2929) -> Response {
3030- let auth_header = headers.get("Authorization");
3131- if auth_header.is_none() {
3232- return (
3333- StatusCode::UNAUTHORIZED,
3434- Json(json!({"error": "AuthenticationRequired"})),
3535- )
3636- .into_response();
3737- }
3838-3939- let token = auth_header
4040- .unwrap()
4141- .to_str()
4242- .unwrap_or("")
4343- .replace("Bearer ", "");
4444-4545- let session = sqlx::query!(
4646- r#"
4747- SELECT s.did, k.key_bytes
4848- FROM sessions s
4949- JOIN users u ON s.did = u.did
5050- JOIN user_keys k ON u.id = k.user_id
5151- WHERE s.access_jwt = $1
5252- "#,
5353- token
5454- )
5555- .fetch_optional(&state.db)
5656- .await;
5757-5858- let (did, key_bytes) = match session {
5959- Ok(Some(row)) => (row.did, row.key_bytes),
6060- Ok(None) => {
3030+ let token = match crate::auth::extract_bearer_token_from_header(
3131+ headers.get("Authorization").and_then(|h| h.to_str().ok())
3232+ ) {
3333+ Some(t) => t,
3434+ None => {
6135 return (
6236 StatusCode::UNAUTHORIZED,
6363- Json(json!({"error": "AuthenticationFailed"})),
3737+ Json(json!({"error": "AuthenticationRequired"})),
6438 )
6539 .into_response();
6640 }
4141+ };
4242+4343+ let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
4444+ let (did, key_bytes) = match auth_result {
4545+ Ok(user) => {
4646+ let kb = match user.key_bytes {
4747+ Some(kb) => kb,
4848+ None => {
4949+ return (
5050+ StatusCode::UNAUTHORIZED,
5151+ Json(json!({"error": "AuthenticationFailed", "message": "OAuth tokens cannot create service auth"})),
5252+ )
5353+ .into_response();
5454+ }
5555+ };
5656+ (user.did, kb)
5757+ }
6758 Err(e) => {
6868- error!("DB error in get_service_auth: {:?}", e);
6959 return (
7070- StatusCode::INTERNAL_SERVER_ERROR,
7171- Json(json!({"error": "InternalError"})),
6060+ StatusCode::UNAUTHORIZED,
6161+ Json(json!({"error": e})),
7262 )
7363 .into_response();
7464 }
7565 };
76667777- if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
7878- return (
7979- StatusCode::UNAUTHORIZED,
8080- Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
8181- )
8282- .into_response();
8383- }
8484-8567 let lxm = params.lxm.as_deref().unwrap_or("*");
86688769 let service_token = match crate::auth::create_service_token(&did, ¶ms.aud, lxm, &key_bytes)
···122104 info!("create_session: identifier='{}'", input.identifier);
123105124106 let user_row = sqlx::query!(
125125- "SELECT u.id, u.did, u.handle, u.password_hash, k.key_bytes FROM users u JOIN user_keys k ON u.id = k.user_id WHERE u.handle = $1 OR u.email = $1",
107107+ "SELECT u.id, u.did, u.handle, u.password_hash, k.key_bytes, k.encryption_version FROM users u JOIN user_keys k ON u.id = k.user_id WHERE u.handle = $1 OR u.email = $1",
126108 input.identifier
127109 )
128110 .fetch_optional(&state.db)
···134116 let stored_hash = &row.password_hash;
135117 let did = &row.did;
136118 let handle = &row.handle;
137137- let key_bytes = &row.key_bytes;
119119+ let key_bytes = match crate::config::decrypt_key(&row.key_bytes, row.encryption_version) {
120120+ Ok(k) => k,
121121+ Err(e) => {
122122+ error!("Failed to decrypt user key: {:?}", e);
123123+ return (
124124+ StatusCode::INTERNAL_SERVER_ERROR,
125125+ Json(json!({"error": "InternalError"})),
126126+ )
127127+ .into_response();
128128+ }
129129+ };
138130139131 let password_valid = if verify(&input.password, stored_hash).unwrap_or(false) {
140132 true
···150142 };
151143152144 if password_valid {
153153- let access_jwt = match crate::auth::create_access_token(&did, &key_bytes) {
154154- Ok(t) => t,
145145+ let access_meta = match crate::auth::create_access_token_with_metadata(did, &key_bytes) {
146146+ Ok(m) => m,
155147 Err(e) => {
156148 error!("Failed to create access token: {:?}", e);
157149 return (
···162154 }
163155 };
164156165165- let refresh_jwt = match crate::auth::create_refresh_token(&did, &key_bytes) {
166166- Ok(t) => t,
157157+ let refresh_meta = match crate::auth::create_refresh_token_with_metadata(did, &key_bytes) {
158158+ Ok(m) => m,
167159 Err(e) => {
168160 error!("Failed to create refresh token: {:?}", e);
169161 return (
···175167 };
176168177169 let session_insert = sqlx::query!(
178178- "INSERT INTO sessions (access_jwt, refresh_jwt, did) VALUES ($1, $2, $3)",
179179- access_jwt,
180180- refresh_jwt,
181181- did
170170+ "INSERT INTO session_tokens (did, access_jti, refresh_jti, access_expires_at, refresh_expires_at) VALUES ($1, $2, $3, $4, $5)",
171171+ did,
172172+ access_meta.jti,
173173+ refresh_meta.jti,
174174+ access_meta.expires_at,
175175+ refresh_meta.expires_at
182176 )
183177 .execute(&state.db)
184178 .await;
···188182 return (
189183 StatusCode::OK,
190184 Json(CreateSessionOutput {
191191- access_jwt,
192192- refresh_jwt,
185185+ access_jwt: access_meta.token,
186186+ refresh_jwt: refresh_meta.token,
193187 handle: handle.clone(),
194188 did: did.clone(),
195189 }),
···236230 State(state): State<AppState>,
237231 headers: axum::http::HeaderMap,
238232) -> Response {
239239- let auth_header = headers.get("Authorization");
240240- if auth_header.is_none() {
241241- return (
242242- StatusCode::UNAUTHORIZED,
243243- Json(json!({"error": "AuthenticationRequired"})),
244244- )
245245- .into_response();
246246- }
233233+ let token = match crate::auth::extract_bearer_token_from_header(
234234+ headers.get("Authorization").and_then(|h| h.to_str().ok())
235235+ ) {
236236+ Some(t) => t,
237237+ None => {
238238+ return (
239239+ StatusCode::UNAUTHORIZED,
240240+ Json(json!({"error": "AuthenticationRequired", "message": "Invalid Authorization header format"})),
241241+ )
242242+ .into_response();
243243+ }
244244+ };
247245248248- let token = auth_header
249249- .unwrap()
250250- .to_str()
251251- .unwrap_or("")
252252- .replace("Bearer ", "");
246246+ let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
247247+ let did = match auth_result {
248248+ Ok(user) => user.did,
249249+ Err(e) => {
250250+ return (
251251+ StatusCode::UNAUTHORIZED,
252252+ Json(json!({"error": e})),
253253+ )
254254+ .into_response();
255255+ }
256256+ };
253257254254- let result = sqlx::query!(
255255- r#"
256256- SELECT u.handle, u.did, u.email, k.key_bytes
257257- FROM sessions s
258258- JOIN users u ON s.did = u.did
259259- JOIN user_keys k ON u.id = k.user_id
260260- WHERE s.access_jwt = $1
261261- "#,
262262- token
258258+ let user = sqlx::query!(
259259+ "SELECT handle, email FROM users WHERE did = $1",
260260+ did
263261 )
264262 .fetch_optional(&state.db)
265263 .await;
266264267267- match result {
265265+ match user {
268266 Ok(Some(row)) => {
269269- if let Err(_) = crate::auth::verify_token(&token, &row.key_bytes) {
270270- return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"}))).into_response();
271271- }
272272-273267 return (
274268 StatusCode::OK,
275269 Json(json!({
276270 "handle": row.handle,
277277- "did": row.did,
271271+ "did": did,
278272 "email": row.email,
279273 "didDoc": {}
280274 })),
···303297 State(state): State<AppState>,
304298 headers: axum::http::HeaderMap,
305299) -> Response {
306306- let auth_header = headers.get("Authorization");
307307- if auth_header.is_none() {
308308- return (
309309- StatusCode::UNAUTHORIZED,
310310- Json(json!({"error": "AuthenticationRequired"})),
311311- )
312312- .into_response();
313313- }
300300+ let token = match crate::auth::extract_bearer_token_from_header(
301301+ headers.get("Authorization").and_then(|h| h.to_str().ok())
302302+ ) {
303303+ Some(t) => t,
304304+ None => {
305305+ return (
306306+ StatusCode::UNAUTHORIZED,
307307+ Json(json!({"error": "AuthenticationRequired"})),
308308+ )
309309+ .into_response();
310310+ }
311311+ };
314312315315- let token = auth_header
316316- .unwrap()
317317- .to_str()
318318- .unwrap_or("")
319319- .replace("Bearer ", "");
313313+ let jti = match crate::auth::get_did_from_token(&token) {
314314+ Ok(_) => {
315315+ let parts: Vec<&str> = token.split('.').collect();
316316+ if parts.len() != 3 {
317317+ return (
318318+ StatusCode::UNAUTHORIZED,
319319+ Json(json!({"error": "AuthenticationFailed"})),
320320+ )
321321+ .into_response();
322322+ }
323323+ use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
324324+ let claims_json = match URL_SAFE_NO_PAD.decode(parts[1]) {
325325+ Ok(bytes) => bytes,
326326+ Err(_) => {
327327+ return (
328328+ StatusCode::UNAUTHORIZED,
329329+ Json(json!({"error": "AuthenticationFailed"})),
330330+ )
331331+ .into_response();
332332+ }
333333+ };
334334+ let claims: serde_json::Value = match serde_json::from_slice(&claims_json) {
335335+ Ok(c) => c,
336336+ Err(_) => {
337337+ return (
338338+ StatusCode::UNAUTHORIZED,
339339+ Json(json!({"error": "AuthenticationFailed"})),
340340+ )
341341+ .into_response();
342342+ }
343343+ };
344344+ match claims.get("jti").and_then(|j| j.as_str()) {
345345+ Some(jti) => jti.to_string(),
346346+ None => {
347347+ return (
348348+ StatusCode::UNAUTHORIZED,
349349+ Json(json!({"error": "AuthenticationFailed"})),
350350+ )
351351+ .into_response();
352352+ }
353353+ }
354354+ }
355355+ Err(_) => {
356356+ return (
357357+ StatusCode::UNAUTHORIZED,
358358+ Json(json!({"error": "AuthenticationFailed"})),
359359+ )
360360+ .into_response();
361361+ }
362362+ };
320363321321- let result = sqlx::query!("DELETE FROM sessions WHERE access_jwt = $1", token)
364364+ let result = sqlx::query!("DELETE FROM session_tokens WHERE access_jti = $1", jti)
322365 .execute(&state.db)
323366 .await;
324367···344387 State(state): State<AppState>,
345388 headers: axum::http::HeaderMap,
346389) -> Response {
347347- let auth_header = headers.get("Authorization");
348348- if auth_header.is_none() {
390390+ use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
391391+392392+ let refresh_token = match crate::auth::extract_bearer_token_from_header(
393393+ headers.get("Authorization").and_then(|h| h.to_str().ok())
394394+ ) {
395395+ Some(t) => t,
396396+ None => {
397397+ return (
398398+ StatusCode::UNAUTHORIZED,
399399+ Json(json!({"error": "AuthenticationRequired"})),
400400+ )
401401+ .into_response();
402402+ }
403403+ };
404404+405405+ let refresh_jti = {
406406+ let parts: Vec<&str> = refresh_token.split('.').collect();
407407+ if parts.len() != 3 {
408408+ return (
409409+ StatusCode::UNAUTHORIZED,
410410+ Json(json!({"error": "AuthenticationFailed", "message": "Invalid token format"})),
411411+ )
412412+ .into_response();
413413+ }
414414+ let claims_bytes = match URL_SAFE_NO_PAD.decode(parts[1]) {
415415+ Ok(b) => b,
416416+ Err(_) => {
417417+ return (
418418+ StatusCode::UNAUTHORIZED,
419419+ Json(json!({"error": "AuthenticationFailed"})),
420420+ )
421421+ .into_response();
422422+ }
423423+ };
424424+ let claims: serde_json::Value = match serde_json::from_slice(&claims_bytes) {
425425+ Ok(c) => c,
426426+ Err(_) => {
427427+ return (
428428+ StatusCode::UNAUTHORIZED,
429429+ Json(json!({"error": "AuthenticationFailed"})),
430430+ )
431431+ .into_response();
432432+ }
433433+ };
434434+ match claims.get("jti").and_then(|j| j.as_str()) {
435435+ Some(jti) => jti.to_string(),
436436+ None => {
437437+ return (
438438+ StatusCode::UNAUTHORIZED,
439439+ Json(json!({"error": "AuthenticationFailed"})),
440440+ )
441441+ .into_response();
442442+ }
443443+ }
444444+ };
445445+446446+ let reuse_check = sqlx::query_scalar!(
447447+ "SELECT session_id FROM used_refresh_tokens WHERE refresh_jti = $1",
448448+ refresh_jti
449449+ )
450450+ .fetch_optional(&state.db)
451451+ .await;
452452+453453+ if let Ok(Some(session_id)) = reuse_check {
454454+ warn!("Refresh token reuse detected! Revoking token family for session_id: {}", session_id);
455455+ let _ = sqlx::query!("DELETE FROM session_tokens WHERE id = $1", session_id)
456456+ .execute(&state.db)
457457+ .await;
349458 return (
350459 StatusCode::UNAUTHORIZED,
351351- Json(json!({"error": "AuthenticationRequired"})),
460460+ Json(json!({"error": "ExpiredToken", "message": "Refresh token has been revoked due to suspected compromise"})),
352461 )
353462 .into_response();
354463 }
355464356356- let refresh_token = auth_header
357357- .unwrap()
358358- .to_str()
359359- .unwrap_or("")
360360- .replace("Bearer ", "");
361361-362465 let session = sqlx::query!(
363363- "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.refresh_jwt = $1",
364364- refresh_token
365365- )
366366- .fetch_optional(&state.db)
367367- .await;
466466+ r#"SELECT st.id, st.did, k.key_bytes, k.encryption_version
467467+ FROM session_tokens st
468468+ JOIN users u ON st.did = u.did
469469+ JOIN user_keys k ON u.id = k.user_id
470470+ WHERE st.refresh_jti = $1 AND st.refresh_expires_at > NOW()"#,
471471+ refresh_jti
472472+ )
473473+ .fetch_optional(&state.db)
474474+ .await;
368475369476 match session {
370477 Ok(Some(session_row)) => {
478478+ let session_id = session_row.id;
371479 let did = &session_row.did;
372372- let key_bytes = &session_row.key_bytes;
480480+ let key_bytes = match crate::config::decrypt_key(&session_row.key_bytes, session_row.encryption_version) {
481481+ Ok(k) => k,
482482+ Err(e) => {
483483+ error!("Failed to decrypt user key: {:?}", e);
484484+ return (
485485+ StatusCode::INTERNAL_SERVER_ERROR,
486486+ Json(json!({"error": "InternalError"})),
487487+ )
488488+ .into_response();
489489+ }
490490+ };
373491374374- if let Err(_) = crate::auth::verify_token(&refresh_token, &key_bytes) {
375375- return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid refresh token signature"}))).into_response();
492492+ if let Err(_) = crate::auth::verify_refresh_token(&refresh_token, &key_bytes) {
493493+ return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid refresh token"}))).into_response();
376494 }
377495378378- let new_access_jwt = match crate::auth::create_access_token(&did, &key_bytes) {
379379- Ok(t) => t,
496496+ let new_access_meta = match crate::auth::create_access_token_with_metadata(did, &key_bytes) {
497497+ Ok(m) => m,
380498 Err(e) => {
381499 error!("Failed to create access token: {:?}", e);
382500 return (
···386504 .into_response();
387505 }
388506 };
389389- let new_refresh_jwt = match crate::auth::create_refresh_token(&did, &key_bytes) {
390390- Ok(t) => t,
507507+ let new_refresh_meta = match crate::auth::create_refresh_token_with_metadata(did, &key_bytes) {
508508+ Ok(m) => m,
391509 Err(e) => {
392510 error!("Failed to create refresh token: {:?}", e);
393511 return (
···398516 }
399517 };
400518401401- let update = sqlx::query!(
402402- "UPDATE sessions SET access_jwt = $1, refresh_jwt = $2 WHERE refresh_jwt = $3",
403403- new_access_jwt,
404404- new_refresh_jwt,
405405- refresh_token
519519+ let mut tx = match state.db.begin().await {
520520+ Ok(tx) => tx,
521521+ Err(e) => {
522522+ error!("Failed to begin transaction: {:?}", e);
523523+ return (
524524+ StatusCode::INTERNAL_SERVER_ERROR,
525525+ Json(json!({"error": "InternalError"})),
526526+ )
527527+ .into_response();
528528+ }
529529+ };
530530+531531+ if let Err(e) = sqlx::query!(
532532+ "INSERT INTO used_refresh_tokens (refresh_jti, session_id) VALUES ($1, $2)",
533533+ refresh_jti,
534534+ session_id
406535 )
407407- .execute(&state.db)
408408- .await;
536536+ .execute(&mut *tx)
537537+ .await
538538+ {
539539+ error!("Failed to record used refresh token: {:?}", e);
540540+ return (
541541+ StatusCode::INTERNAL_SERVER_ERROR,
542542+ Json(json!({"error": "InternalError"})),
543543+ )
544544+ .into_response();
545545+ }
409546410410- match update {
411411- Ok(_) => {
412412- let user = sqlx::query!("SELECT handle FROM users WHERE did = $1", did)
413413- .fetch_optional(&state.db)
414414- .await;
547547+ if let Err(e) = sqlx::query!(
548548+ "UPDATE session_tokens SET access_jti = $1, refresh_jti = $2, access_expires_at = $3, refresh_expires_at = $4, updated_at = NOW() WHERE id = $5",
549549+ new_access_meta.jti,
550550+ new_refresh_meta.jti,
551551+ new_access_meta.expires_at,
552552+ new_refresh_meta.expires_at,
553553+ session_id
554554+ )
555555+ .execute(&mut *tx)
556556+ .await
557557+ {
558558+ error!("Database error updating session: {:?}", e);
559559+ return (
560560+ StatusCode::INTERNAL_SERVER_ERROR,
561561+ Json(json!({"error": "InternalError"})),
562562+ )
563563+ .into_response();
564564+ }
415565416416- match user {
417417- Ok(Some(u)) => {
418418- return (
419419- StatusCode::OK,
420420- Json(json!({
421421- "accessJwt": new_access_jwt,
422422- "refreshJwt": new_refresh_jwt,
423423- "handle": u.handle,
424424- "did": did
425425- })),
426426- )
427427- .into_response();
428428- }
429429- Ok(None) => {
430430- error!("User not found for existing session: {}", did);
431431- return (
432432- StatusCode::INTERNAL_SERVER_ERROR,
433433- Json(json!({"error": "InternalError"})),
434434- )
435435- .into_response();
436436- }
437437- Err(e) => {
438438- error!("Database error fetching user: {:?}", e);
439439- return (
440440- StatusCode::INTERNAL_SERVER_ERROR,
441441- Json(json!({"error": "InternalError"})),
442442- )
443443- .into_response();
444444- }
445445- }
566566+ if let Err(e) = tx.commit().await {
567567+ error!("Failed to commit transaction: {:?}", e);
568568+ return (
569569+ StatusCode::INTERNAL_SERVER_ERROR,
570570+ Json(json!({"error": "InternalError"})),
571571+ )
572572+ .into_response();
573573+ }
574574+575575+ let user = sqlx::query!("SELECT handle FROM users WHERE did = $1", did)
576576+ .fetch_optional(&state.db)
577577+ .await;
578578+579579+ match user {
580580+ Ok(Some(u)) => {
581581+ return (
582582+ StatusCode::OK,
583583+ Json(json!({
584584+ "accessJwt": new_access_meta.token,
585585+ "refreshJwt": new_refresh_meta.token,
586586+ "handle": u.handle,
587587+ "did": did
588588+ })),
589589+ )
590590+ .into_response();
591591+ }
592592+ Ok(None) => {
593593+ error!("User not found for existing session: {}", did);
594594+ return (
595595+ StatusCode::INTERNAL_SERVER_ERROR,
596596+ Json(json!({"error": "InternalError"})),
597597+ )
598598+ .into_response();
446599 }
447600 Err(e) => {
448448- error!("Database error updating session: {:?}", e);
601601+ error!("Database error fetching user: {:?}", e);
449602 return (
450603 StatusCode::INTERNAL_SERVER_ERROR,
451604 Json(json!({"error": "InternalError"})),
···11+pub mod metadata;
22+pub mod par;
33+pub mod authorize;
44+pub mod token;
55+66+pub use metadata::*;
77+pub use par::*;
88+pub use authorize::*;
99+pub use token::*;
···11+pub mod types;
22+pub mod db;
33+pub mod dpop;
44+pub mod jwks;
55+pub mod client;
66+pub mod endpoints;
77+pub mod error;
88+pub mod verify;
99+1010+pub use types::*;
1111+pub use error::OAuthError;
1212+pub use verify::{verify_oauth_access_token, generate_dpop_nonce, VerifyResult, OAuthUser, OAuthAuthError};