feat: Implement flexible authentication supporting JWT, OIDC, and session modes, alongside new configuration options and refactored auth layer setup.

This commit is contained in:
2026-01-06 20:31:57 +01:00
parent 82a6c08790
commit a5f9e8ae9e
18 changed files with 1022 additions and 414 deletions

View File

@@ -5,34 +5,29 @@ use axum::{
extract::{Path, Query, State},
http::StatusCode,
};
use axum_login::AuthSession;
use uuid::Uuid;
use validator::Validate;
use axum_login::AuthUser;
use notes_domain::{
CreateNoteRequest as DomainCreateNote, NoteTitle, TagName,
UpdateNoteRequest as DomainUpdateNote,
};
use crate::auth::AuthBackend;
use crate::dto::{CreateNoteRequest, ListNotesQuery, NoteResponse, SearchQuery, UpdateNoteRequest};
use crate::error::{ApiError, ApiResult};
use crate::state::AppState;
use crate::{
dto::{CreateNoteRequest, ListNotesQuery, NoteResponse, SearchQuery, UpdateNoteRequest},
extractors::CurrentUser,
};
/// List notes with optional filtering
/// GET /api/v1/notes
pub async fn list_notes(
State(state): State<AppState>,
auth: AuthSession<AuthBackend>,
CurrentUser(user): CurrentUser,
Query(query): Query<ListNotesQuery>,
) -> ApiResult<Json<Vec<NoteResponse>>> {
let user = auth
.user
.ok_or(ApiError::Domain(notes_domain::DomainError::Unauthorized(
"Login required".to_string(),
)))?;
let user_id = user.id();
let user_id = user.id;
// Build the filter, looking up tag_id by name if needed
let mut filter = notes_domain::NoteFilter::new();
@@ -59,15 +54,10 @@ pub async fn list_notes(
/// POST /api/v1/notes
pub async fn create_note(
State(state): State<AppState>,
auth: AuthSession<AuthBackend>,
CurrentUser(user): CurrentUser,
Json(payload): Json<CreateNoteRequest>,
) -> ApiResult<(StatusCode, Json<NoteResponse>)> {
let user = auth
.user
.ok_or(ApiError::Domain(notes_domain::DomainError::Unauthorized(
"Login required".to_string(),
)))?;
let user_id = user.id();
let user_id = user.id;
// Validate input
payload
@@ -113,15 +103,10 @@ pub async fn create_note(
/// GET /api/v1/notes/:id
pub async fn get_note(
State(state): State<AppState>,
auth: AuthSession<AuthBackend>,
CurrentUser(user): CurrentUser,
Path(id): Path<Uuid>,
) -> ApiResult<Json<NoteResponse>> {
let user = auth
.user
.ok_or(ApiError::Domain(notes_domain::DomainError::Unauthorized(
"Login required".to_string(),
)))?;
let user_id = user.id();
let user_id = user.id;
let note = state.note_service.get_note(id, user_id).await?;
@@ -132,16 +117,11 @@ pub async fn get_note(
/// PATCH /api/v1/notes/:id
pub async fn update_note(
State(state): State<AppState>,
auth: AuthSession<AuthBackend>,
CurrentUser(user): CurrentUser,
Path(id): Path<Uuid>,
Json(payload): Json<UpdateNoteRequest>,
) -> ApiResult<Json<NoteResponse>> {
let user = auth
.user
.ok_or(ApiError::Domain(notes_domain::DomainError::Unauthorized(
"Login required".to_string(),
)))?;
let user_id = user.id();
let user_id = user.id;
// Validate input
payload
@@ -195,15 +175,10 @@ pub async fn update_note(
/// DELETE /api/v1/notes/:id
pub async fn delete_note(
State(state): State<AppState>,
auth: AuthSession<AuthBackend>,
CurrentUser(user): CurrentUser,
Path(id): Path<Uuid>,
) -> ApiResult<StatusCode> {
let user = auth
.user
.ok_or(ApiError::Domain(notes_domain::DomainError::Unauthorized(
"Login required".to_string(),
)))?;
let user_id = user.id();
let user_id = user.id;
state.note_service.delete_note(id, user_id).await?;
@@ -214,15 +189,10 @@ pub async fn delete_note(
/// GET /api/v1/notes/search
pub async fn search_notes(
State(state): State<AppState>,
auth: AuthSession<AuthBackend>,
CurrentUser(user): CurrentUser,
Query(query): Query<SearchQuery>,
) -> ApiResult<Json<Vec<NoteResponse>>> {
let user = auth
.user
.ok_or(ApiError::Domain(notes_domain::DomainError::Unauthorized(
"Login required".to_string(),
)))?;
let user_id = user.id();
let user_id = user.id;
let notes = state.note_service.search_notes(user_id, &query.q).await?;
let response: Vec<NoteResponse> = notes.into_iter().map(NoteResponse::from).collect();
@@ -234,15 +204,10 @@ pub async fn search_notes(
/// GET /api/v1/notes/:id/versions
pub async fn list_note_versions(
State(state): State<AppState>,
auth: AuthSession<AuthBackend>,
CurrentUser(user): CurrentUser,
Path(id): Path<Uuid>,
) -> ApiResult<Json<Vec<crate::dto::NoteVersionResponse>>> {
let user = auth
.user
.ok_or(ApiError::Domain(notes_domain::DomainError::Unauthorized(
"Login required".to_string(),
)))?;
let user_id = user.id();
let user_id = user.id;
let versions = state.note_service.list_note_versions(id, user_id).await?;
let response: Vec<crate::dto::NoteVersionResponse> = versions
@@ -260,15 +225,10 @@ pub async fn list_note_versions(
#[cfg(feature = "smart-features")]
pub async fn get_related_notes(
State(state): State<AppState>,
auth: AuthSession<AuthBackend>,
CurrentUser(user): CurrentUser,
Path(id): Path<Uuid>,
) -> ApiResult<Json<Vec<crate::dto::NoteLinkResponse>>> {
let user = auth
.user
.ok_or(ApiError::Domain(notes_domain::DomainError::Unauthorized(
"Login required".to_string(),
)))?;
let user_id = user.id();
let user_id = user.id;
// Verify access to the source note
state.note_service.get_note(id, user_id).await?;