Public Access
feat: enhance security with token blacklisting and input sanitization; add helmet for security headers
This commit is contained in:
1 parent
4935251125
commit
1b5fd04040
25 files changed
+294
-90
No files matched your search
@@ -20,7 +20,7 @@ describe('updateUserRoleSchema', () => {
|
||||
|
||||
describe('updateSettingSchema', () => {
|
||||
it('accepts valid setting', () => {
|
||||
const result = updateSettingSchema.safeParse({ key: 'app_name', value: 'BrainWave' })
|
||||
const result = updateSettingSchema.safeParse({ key: 'site_title', value: 'BrainWave' })
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('updateSettingSchema', () => {
|
||||
})
|
||||
|
||||
it('rejects empty value', () => {
|
||||
const result = updateSettingSchema.safeParse({ key: 'app_name', value: '' })
|
||||
const result = updateSettingSchema.safeParse({ key: 'site_title', value: '' })
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,8 +5,26 @@ export const updateUserRoleSchema = z.object({
|
||||
role: z.enum(['user', 'admin']),
|
||||
})
|
||||
|
||||
const ALLOWED_SETTING_KEYS = [
|
||||
'site_title',
|
||||
'site_language',
|
||||
'maintenance_mode',
|
||||
'maintenance_message',
|
||||
'allow_guest_mode',
|
||||
'registration_enabled',
|
||||
'default_theme',
|
||||
'smtp_host',
|
||||
'smtp_port',
|
||||
'smtp_user',
|
||||
'smtp_pass',
|
||||
'smtp_from',
|
||||
'smtp_secure',
|
||||
'app_url',
|
||||
'max_upload_mb',
|
||||
] as const
|
||||
|
||||
export const updateSettingSchema = z.object({
|
||||
key: z.string().min(1),
|
||||
key: z.enum(ALLOWED_SETTING_KEYS),
|
||||
value: z.string().min(1),
|
||||
})
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ describe('registerSchema', () => {
|
||||
it('accepts valid registration', () => {
|
||||
const result = registerSchema.safeParse({
|
||||
email: 'test@example.com',
|
||||
password: 'password123',
|
||||
password: 'Password123!',
|
||||
displayName: 'Test User',
|
||||
})
|
||||
expect(result.success).toBe(true)
|
||||
@@ -14,7 +14,7 @@ describe('registerSchema', () => {
|
||||
it('rejects invalid email', () => {
|
||||
const result = registerSchema.safeParse({
|
||||
email: 'not-an-email',
|
||||
password: 'password123',
|
||||
password: 'Password123!',
|
||||
displayName: 'Test User',
|
||||
})
|
||||
expect(result.success).toBe(false)
|
||||
@@ -32,7 +32,7 @@ describe('registerSchema', () => {
|
||||
it('rejects empty display name', () => {
|
||||
const result = registerSchema.safeParse({
|
||||
email: 'test@example.com',
|
||||
password: 'password123',
|
||||
password: 'Password123!',
|
||||
displayName: '',
|
||||
})
|
||||
expect(result.success).toBe(false)
|
||||
@@ -65,7 +65,7 @@ describe('verifyEmailSchema', () => {
|
||||
|
||||
describe('resetPasswordSchema', () => {
|
||||
it('accepts valid reset', () => {
|
||||
const result = resetPasswordSchema.safeParse({ token: 'abc', newPassword: 'newpass123' })
|
||||
const result = resetPasswordSchema.safeParse({ token: 'abc', newPassword: 'NewPass123!' })
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
@@ -91,7 +91,7 @@ describe('changePasswordSchema', () => {
|
||||
it('accepts valid change', () => {
|
||||
const result = changePasswordSchema.safeParse({
|
||||
currentPassword: 'oldpass',
|
||||
newPassword: 'newpass123',
|
||||
newPassword: 'NewPass123!',
|
||||
})
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/
|
||||
|
||||
export const registerSchema = z.object({
|
||||
email: z.string().email().max(255),
|
||||
password: z.string().min(8, 'Password must be at least 8 characters'),
|
||||
password: z.string()
|
||||
.min(8, 'Password must be at least 8 characters')
|
||||
.regex(passwordRegex, 'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character (@$!%*?&)'),
|
||||
displayName: z.string().min(1, 'Display name is required').max(100),
|
||||
})
|
||||
|
||||
@@ -17,7 +21,9 @@ export const verifyEmailSchema = z.object({
|
||||
|
||||
export const resetPasswordSchema = z.object({
|
||||
token: z.string().min(1, 'Token is required'),
|
||||
newPassword: z.string().min(8, 'Password must be at least 8 characters'),
|
||||
newPassword: z.string()
|
||||
.min(8, 'Password must be at least 8 characters')
|
||||
.regex(passwordRegex, 'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character (@$!%*?&)'),
|
||||
})
|
||||
|
||||
export const updateProfileSchema = z.object({
|
||||
@@ -26,7 +32,9 @@ export const updateProfileSchema = z.object({
|
||||
|
||||
export const changePasswordSchema = z.object({
|
||||
currentPassword: z.string(),
|
||||
newPassword: z.string().min(8, 'Password must be at least 8 characters'),
|
||||
newPassword: z.string()
|
||||
.min(8, 'Password must be at least 8 characters')
|
||||
.regex(passwordRegex, 'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character (@$!%*?&)'),
|
||||
})
|
||||
|
||||
export const forgotPasswordSchema = z.object({
|
||||
|
||||
@@ -5,7 +5,7 @@ describe('setupSchema', () => {
|
||||
it('accepts valid setup', () => {
|
||||
const result = setupSchema.safeParse({
|
||||
email: 'admin@example.com',
|
||||
password: 'adminpass123',
|
||||
password: 'AdminPass123!',
|
||||
displayName: 'Admin',
|
||||
siteTitle: 'BrainWave',
|
||||
language: 'en',
|
||||
@@ -16,7 +16,7 @@ describe('setupSchema', () => {
|
||||
it('rejects invalid email', () => {
|
||||
const result = setupSchema.safeParse({
|
||||
email: 'bad',
|
||||
password: 'adminpass123',
|
||||
password: 'AdminPass123!',
|
||||
displayName: 'Admin',
|
||||
})
|
||||
expect(result.success).toBe(false)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/
|
||||
|
||||
export const setupSchema = z.object({
|
||||
email: z.string().email().max(255),
|
||||
password: z.string().min(8, 'Password must be at least 8 characters'),
|
||||
password: z.string()
|
||||
.min(8, 'Password must be at least 8 characters')
|
||||
.regex(passwordRegex, 'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character (@$!%*?&)'),
|
||||
displayName: z.string().min(1, 'Display name is required').max(100),
|
||||
siteTitle: z.string().min(1, 'Site title is required').max(255).optional(),
|
||||
language: z.enum(['en', 'nl']).optional(),
|
||||
|
||||
Reference in new issue
Block a user