feat(auth): add WebAuthn passkey support with migrations, routes, and tests

This commit is contained in:
2025-10-29 17:50:14 +01:00
parent ec6e7f04f2
commit ca30b20873
23 changed files with 1511 additions and 83 deletions
+75
View File
@@ -14,6 +14,10 @@ use uuid::Uuid;
doc::me,
doc::list_tenants,
doc::select_tenant,
doc::passkey_register_start,
doc::passkey_register_finish,
doc::passkey_login_start,
doc::passkey_login_finish,
doc::list_documents,
doc::check_document,
doc::upload_document,
@@ -50,6 +54,8 @@ use uuid::Uuid;
doc::list_webdav_tokens,
doc::create_webdav_token,
doc::delete_webdav_token,
doc::list_passkeys,
doc::delete_passkey,
),
components(
schemas(
@@ -61,6 +67,12 @@ use uuid::Uuid;
schemas::TenantSelectionRequest,
schemas::TenantListResponse,
schemas::LoginResponseVariants,
schemas::RegistrationChallengeResponse,
schemas::AuthenticationChallengeResponse,
schemas::PasskeySummary,
schemas::PasskeyRegistrationFinishPayload,
schemas::PasskeyLoginStartPayload,
schemas::PasskeyLoginFinishPayload,
schemas::DocumentResponse,
schemas::DocumentDetailResponse,
schemas::DocumentVersionResponse,
@@ -210,6 +222,44 @@ mod doc {
)]
pub(super) fn select_tenant() {}
#[utoipa::path(
post,
path = "/api/auth/passkeys/register/start",
responses((status = 200, description = "Passkey registration challenge", body = RegistrationChallengeResponse)),
tag = "Auth"
)]
pub(super) fn passkey_register_start() {}
#[utoipa::path(
post,
path = "/api/auth/passkeys/register/finish",
request_body = PasskeyRegistrationFinishPayload,
responses((status = 200, description = "Passkey registered", body = PasskeySummary)),
tag = "Auth"
)]
pub(super) fn passkey_register_finish() {}
#[utoipa::path(
post,
path = "/api/auth/passkeys/login/start",
request_body = PasskeyLoginStartPayload,
responses((status = 200, description = "Passkey authentication challenge", body = AuthenticationChallengeResponse)),
tag = "Auth"
)]
pub(super) fn passkey_login_start() {}
#[utoipa::path(
post,
path = "/api/auth/passkeys/login/finish",
request_body = PasskeyLoginFinishPayload,
responses(
(status = 200, description = "Passkey login successful", body = LoginResponseVariants),
(status = 401, description = "Authentication failed")
),
tag = "Auth"
)]
pub(super) fn passkey_login_finish() {}
#[utoipa::path(
get,
path = "/api/documents",
@@ -587,6 +637,26 @@ mod doc {
tag = "Profile"
)]
pub(super) fn delete_webdav_token() {}
#[utoipa::path(
get,
path = "/api/profile/passkeys",
responses((status = 200, description = "List registered passkeys", body = [PasskeySummary])),
tag = "Profile"
)]
pub(super) fn list_passkeys() {}
#[utoipa::path(
delete,
path = "/api/profile/passkeys/{id}",
params(
("id" = Uuid, Path, description = "Passkey ID"),
("reason" = Option<String>, Query, description = "Optional reason for revoking the passkey")
),
responses((status = 204, description = "Passkey revoked")),
tag = "Profile"
)]
pub(super) fn delete_passkey() {}
}
#[cfg(test)]
@@ -604,6 +674,11 @@ mod tests {
pub mod schemas {
use super::*;
pub use crate::auth::passkeys::{
AuthenticationChallengeResponse, PasskeyLoginFinishPayload, PasskeyLoginStartPayload,
PasskeyRegistrationFinishPayload, PasskeySummary, RegistrationChallengeResponse,
};
#[derive(Serialize, Deserialize, ToSchema)]
pub struct LoginRequest {
pub username: String,