Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
321 changes: 311 additions & 10 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ sha2 = "0.10.8"
base64 = "0.22"
aes-gcm = "0.10"
bcrypt = "0.15"
reqwest = { version = "0.12", default-features = false, features = ["json", "stream", "rustls-tls"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
futures-util = "0.3"
sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio-rustls", "macros", "any", "postgres", "sqlite", "uuid", "time", "json", "migrate"] }
thiserror = "2"
tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal", "net"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal", "net", "sync", "time"] }
tower-http = { version = "0.6", features = ["trace"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt", "json"] }
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ runtime limit engine before building a large dashboard.

Mizan is in active bootstrap-to-MVP delivery. Milestone 3 (auth/API keys) and
Milestone 4 (provider/model management + `GET /v1/models`) are implemented.
Milestone 5 has a first `POST /v1/chat/completions` route with non-streaming
flow and model routing in place; streaming, upstream error shaping, and request
trace propagation are still in-progress.
Milestone 2 (SQLite-first database foundation) is also implemented with
idempotent migrations.
Milestone 5 has a `POST /v1/chat/completions` route with routing and upstream
error shaping in place, and OpenAI-compatible streaming upstream chunks now flow
through SSE as `chat.completion.chunk`.

## MVP Scope

Expand Down
1 change: 1 addition & 0 deletions crates/mizan-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ axum.workspace = true
aes-gcm.workspace = true
base64.workspace = true
bcrypt.workspace = true
futures-util.workspace = true
redis.workspace = true
sha2.workspace = true
serde.workspace = true
Expand Down
34 changes: 34 additions & 0 deletions crates/mizan-api/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ pub struct ApiKeyRevokeResponse {
pub api_key_id: Uuid,
}

#[derive(Debug, Clone, Serialize)]
pub struct SessionRevokeResponse {
pub revoked: bool,
}

#[derive(Debug, Clone)]
pub struct ApiKeyIdentity {
pub api_key_id: Uuid,
Expand Down Expand Up @@ -261,6 +266,35 @@ pub async fn login(
}))
}

pub async fn logout(
State(state): State<AppState>,
headers: HeaderMap,
) -> AuthHttpResult<Json<SessionRevokeResponse>> {
let backend = state.database_backend();
let raw_token = session_token_from_headers(&headers)
.ok_or_else(|| map_error(StatusCode::UNAUTHORIZED, AppError::Unauthorized))?;
let token_hash = hash_value(raw_token);
let updated_at = unix_timestamp_string();

let result = query(&prepare_sql(
backend,
"UPDATE sessions
SET revoked = 1,
updated_at = ?
WHERE session_token_hash = ? AND revoked = 0",
))
.bind(updated_at)
.bind(token_hash)
.execute(&state.database)
.await
.map_err(|error| AppError::infrastructure(error.to_string()))
.map_err(from_app_error)?;

Ok(Json(SessionRevokeResponse {
revoked: result.rows_affected() > 0,
}))
}

pub async fn create_api_key(
State(state): State<AppState>,
headers: HeaderMap,
Expand Down
Loading
Loading