feat: Add ALLOW_REGISTRATION configuration to enable/disable user registration and introduce a Forbidden API error type.

This commit is contained in:
2025-12-25 23:05:26 +01:00
parent bb15181817
commit 0af7294468
7 changed files with 39 additions and 0 deletions

View File

@@ -60,8 +60,18 @@ The frontend is automatically configured to talk to the backend.
cargo run -p notes-api cargo run -p notes-api
``` ```
By default, this uses the **SQLite** backend. By default, this uses the **SQLite** backend.
#### Configuration
The application is configured via environment variables (or `.env` file):
- `ALLOW_REGISTRATION`: Set to `false` to disable new user registration (default: `true`).
- `DATABASE_URL`: Connection string for the database.
- `SESSION_SECRET`: Secret key for session encryption.
- `CORS_ALLOWED_ORIGINS`: Comma-separated list of allowed origins.
**Running with Postgres:** **Running with Postgres:**
To use PostgreSQL, build with the `postgres` feature: To use PostgreSQL, build with the `postgres` feature:

View File

@@ -10,6 +10,7 @@ services:
- CORS_ALLOWED_ORIGINS=http://localhost:8080,http://localhost:5173 - CORS_ALLOWED_ORIGINS=http://localhost:8080,http://localhost:5173
- HOST=0.0.0.0 - HOST=0.0.0.0
- PORT=3000 - PORT=3000
- ALLOW_REGISTRATION=true
volumes: volumes:
- ./data:/app/data - ./data:/app/data

View File

@@ -8,6 +8,7 @@ pub struct Config {
pub database_url: String, pub database_url: String,
pub session_secret: String, pub session_secret: String,
pub cors_allowed_origins: Vec<String>, pub cors_allowed_origins: Vec<String>,
pub allow_registration: bool,
} }
impl Default for Config { impl Default for Config {
@@ -19,6 +20,7 @@ impl Default for Config {
session_secret: "k-notes-super-secret-key-must-be-at-least-64-bytes-long!!!!" session_secret: "k-notes-super-secret-key-must-be-at-least-64-bytes-long!!!!"
.to_string(), .to_string(),
cors_allowed_origins: vec!["http://localhost:5173".to_string()], cors_allowed_origins: vec!["http://localhost:5173".to_string()],
allow_registration: true,
} }
} }
} }
@@ -50,12 +52,17 @@ impl Config {
.filter(|s| !s.is_empty()) .filter(|s| !s.is_empty())
.collect(); .collect();
let allow_registration = env::var("ALLOW_REGISTRATION")
.map(|s| s.to_lowercase() == "true")
.unwrap_or(true);
Self { Self {
host, host,
port, port,
database_url, database_url,
session_secret, session_secret,
cors_allowed_origins, cors_allowed_origins,
allow_registration,
} }
} }
} }

View File

@@ -23,6 +23,9 @@ pub enum ApiError {
#[error("Internal server error")] #[error("Internal server error")]
Internal(String), Internal(String),
#[error("Forbidden: {0}")]
Forbidden(String),
} }
/// Error response body /// Error response body
@@ -83,6 +86,14 @@ impl IntoResponse for ApiError {
}, },
) )
} }
ApiError::Forbidden(msg) => (
StatusCode::FORBIDDEN,
ErrorResponse {
error: "Forbidden".to_string(),
details: Some(msg.clone()),
},
),
}; };
(status, Json(error_response)).into_response() (status, Json(error_response)).into_response()

View File

@@ -88,6 +88,7 @@ async fn main() -> anyhow::Result<()> {
note_service, note_service,
tag_service, tag_service,
user_service, user_service,
config.clone(),
); );
// Auth backend // Auth backend

View File

@@ -22,6 +22,11 @@ pub async fn register(
.validate() .validate()
.map_err(|e| ApiError::validation(e.to_string()))?; .map_err(|e| ApiError::validation(e.to_string()))?;
// Check if registration is allowed
if !state.config.allow_registration {
return Err(ApiError::Forbidden("Registration is disabled".to_string()));
}
// Check if user exists // Check if user exists
if state if state
.user_repo .user_repo

View File

@@ -1,5 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use crate::config::Config;
use notes_domain::{ use notes_domain::{
NoteRepository, NoteService, TagRepository, TagService, UserRepository, UserService, NoteRepository, NoteService, TagRepository, TagService, UserRepository, UserService,
}; };
@@ -13,6 +14,7 @@ pub struct AppState {
pub note_service: Arc<NoteService>, pub note_service: Arc<NoteService>,
pub tag_service: Arc<TagService>, pub tag_service: Arc<TagService>,
pub user_service: Arc<UserService>, pub user_service: Arc<UserService>,
pub config: Config,
} }
impl AppState { impl AppState {
@@ -23,6 +25,7 @@ impl AppState {
note_service: Arc<NoteService>, note_service: Arc<NoteService>,
tag_service: Arc<TagService>, tag_service: Arc<TagService>,
user_service: Arc<UserService>, user_service: Arc<UserService>,
config: Config,
) -> Self { ) -> Self {
Self { Self {
note_repo, note_repo,
@@ -31,6 +34,7 @@ impl AppState {
note_service, note_service,
tag_service, tag_service,
user_service, user_service,
config,
} }
} }
} }