Compare commits

...

4 Commits

Author SHA1 Message Date
409
2435e25aee create admin user on first start 2025-08-29 16:17:33 +02:00
409
d74531e2e1 AUTH_ALLOW_REGISTRATION env variable 2025-08-29 16:17:13 +02:00
409
8bf6de1682 reuse register toast id 2025-08-29 16:16:57 +02:00
409
0f57799aaf fix frontend using different email validation 2025-08-29 16:16:45 +02:00
6 changed files with 54 additions and 3 deletions

View File

@@ -0,0 +1,11 @@
INSERT INTO users (
name,
email,
hash,
admin
) VALUES (
'admin',
'admin@example.com',
'$argon2id$v=19$m=19456,t=2,p=1$H1WsElL4921/WD5oPkY7JQ$aHudNG8z0ns3pRULfuDpuEkxPUbGxq9AHC4QGyt5odc',
true
);

View File

@@ -43,6 +43,8 @@ impl From<RegisterUserRequest> for CreateUserRequest {
pub enum RegisterUserError {
#[error(transparent)]
CreateUser(#[from] CreateUserError),
#[error("Registration is disabled")]
Disabled,
#[error(transparent)]
Unknown(#[from] anyhow::Error),
}

View File

@@ -52,6 +52,7 @@ use crate::{
};
const AUTH_SESSION_EXPIRATION_KEY: &str = "AUTH_SESSION_EXPIRATION";
const ALLOW_REGISTRATION_KEY: &str = "AUTH_ALLOW_REGISTRATION";
/// The authentication service configuration
///
@@ -59,6 +60,7 @@ const AUTH_SESSION_EXPIRATION_KEY: &str = "AUTH_SESSION_EXPIRATION";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AuthConfig {
session_lifetime: SessionExpirationTime,
allow_registration: bool,
}
impl AuthConfig {
@@ -71,12 +73,27 @@ impl AuthConfig {
}
};
Ok(Self { session_lifetime })
let allow_registration = match Config::load_env(ALLOW_REGISTRATION_KEY)
.map(|v| v.to_lowercase())
.as_deref()
{
Ok("true") => true,
Ok("false") | Ok(_) | Err(_) => false,
};
Ok(Self {
session_lifetime,
allow_registration,
})
}
pub fn session_lifetime(&self) -> SessionExpirationTime {
self.session_lifetime
}
pub fn allow_registration(&self) -> bool {
self.allow_registration
}
}
#[derive(Debug, Clone)]
@@ -232,6 +249,11 @@ where
}
async fn register_user(&self, request: RegisterUserRequest) -> Result<User, RegisterUserError> {
if !self.config.allow_registration {
self.metrics.record_user_registration_failure().await;
return Err(RegisterUserError::Disabled);
}
let result = self.repository.create_user(request.into()).await;
if let Ok(user) = result.as_ref() {

View File

@@ -112,6 +112,9 @@ impl From<RegisterUserError> for ApiError {
fn from(value: RegisterUserError) -> Self {
match value {
RegisterUserError::CreateUser(err) => err.into(),
RegisterUserError::Disabled => {
Self::BadRequest("User registration is disabled".to_string())
}
RegisterUserError::Unknown(error) => Self::InternalServerError(error.to_string()),
}
}

View File

@@ -1,6 +1,8 @@
import { toast } from 'vue-sonner';
import type { ApiResponse } from '#shared/types/api';
const REGISTER_TOAST_ID = 'REGISTER_USER_TOAST' as const;
export async function registerUser(
username: string,
email: string,
@@ -24,6 +26,7 @@ export async function registerUser(
if (data.value == null) {
toast.error('Register', {
id: REGISTER_TOAST_ID,
description: error.value?.data ?? 'Failed to register',
});
@@ -33,6 +36,7 @@ export async function registerUser(
}
toast.success('Register', {
id: REGISTER_TOAST_ID,
description: `Successfully registered user ${username}`,
});

View File

@@ -1,13 +1,22 @@
import { object, string } from 'yup';
const EMAIL_REGEX: RegExp =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
export const registerSchema = object({
name: string().trim().min(1).required('required'),
email: string().trim().email('Expected a valid email').required('required'),
email: string()
.trim()
.matches(EMAIL_REGEX, 'Expected a valid email')
.required('required'),
password: string().trim().min(12).max(32).required('required'),
});
export const loginSchema = object({
email: string().trim().email('Expected a valid email').required('required'),
email: string()
.trim()
.matches(EMAIL_REGEX, 'Expected a valid email')
.required('required'),
// Don't include the min and max here to let bad actors waste their time
password: string().trim().required('required'),
});