Compare commits
4 Commits
284d805590
...
2435e25aee
| Author | SHA1 | Date | |
|---|---|---|---|
| 2435e25aee | |||
| d74531e2e1 | |||
| 8bf6de1682 | |||
| 0f57799aaf |
11
backend/migrations/20250829140914_create_admin_user.sql
Normal file
11
backend/migrations/20250829140914_create_admin_user.sql
Normal 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
|
||||
);
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}`,
|
||||
});
|
||||
|
||||
|
||||
@@ -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'),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user