2024-06-07 18:46:49 +03:00
|
|
|
import { env } from '$env/dynamic/private';
|
2024-06-10 20:20:25 +03:00
|
|
|
import { AuditAction } from '$lib/server/audit';
|
|
|
|
import { Audit } from '$lib/server/audit/audit.js';
|
2024-05-18 19:07:56 +03:00
|
|
|
import { Changesets } from '$lib/server/changesets.js';
|
|
|
|
import { Users } from '$lib/server/users/index.js';
|
|
|
|
import { emailRegex, passwordRegex, usernameRegex } from '$lib/validators.js';
|
2024-05-20 20:25:46 +03:00
|
|
|
import { error, fail, redirect } from '@sveltejs/kit';
|
|
|
|
import { RateLimiter } from 'sveltekit-rate-limiter/server';
|
2024-05-18 19:07:56 +03:00
|
|
|
|
|
|
|
interface RegisterData {
|
|
|
|
username: string;
|
|
|
|
displayName: string;
|
|
|
|
email: string;
|
|
|
|
password: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
const fields: (keyof RegisterData)[] = ['username', 'displayName', 'email', 'password'];
|
|
|
|
|
2024-05-20 20:25:46 +03:00
|
|
|
const limiter = new RateLimiter({
|
|
|
|
IP: [6, 'm']
|
|
|
|
});
|
|
|
|
|
2024-05-18 19:07:56 +03:00
|
|
|
export const actions = {
|
2024-05-20 20:25:46 +03:00
|
|
|
default: async (event) => {
|
|
|
|
const { request, locals } = event;
|
|
|
|
if (await limiter.isLimited(event)) throw error(429);
|
|
|
|
|
2024-05-18 19:07:56 +03:00
|
|
|
// Logged in users cannot make more accounts
|
2024-06-07 18:46:49 +03:00
|
|
|
if (locals.session.data?.user || env.REGISTRATIONS === 'false') {
|
2024-05-18 19:07:56 +03:00
|
|
|
return redirect(303, '/');
|
|
|
|
}
|
|
|
|
|
|
|
|
const body = await request.formData();
|
|
|
|
const changes = Changesets.take<RegisterData>(fields, body);
|
|
|
|
const { username, displayName, email, password } = changes;
|
|
|
|
// Each field must be present
|
|
|
|
if (!username || !displayName || !email || !password) {
|
|
|
|
return fail(400, {
|
|
|
|
username,
|
|
|
|
displayName,
|
|
|
|
email,
|
|
|
|
errors: ['required'],
|
|
|
|
fields: fields.reduce<string[]>(
|
|
|
|
(missing, field) => (!changes[field] ? [...missing, field] : missing),
|
|
|
|
[]
|
|
|
|
)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!usernameRegex.test(username)) {
|
|
|
|
return fail(400, {
|
|
|
|
username,
|
|
|
|
displayName,
|
|
|
|
email,
|
|
|
|
errors: ['invalidUsername'],
|
|
|
|
fields: ['username']
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (displayName.length < 3 || displayName.length > 32) {
|
|
|
|
return fail(400, {
|
|
|
|
username,
|
|
|
|
displayName,
|
|
|
|
email,
|
|
|
|
errors: ['invalidDisplayName'],
|
|
|
|
fields: ['displayName']
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!emailRegex.test(email)) {
|
|
|
|
return fail(400, {
|
|
|
|
username,
|
|
|
|
displayName,
|
|
|
|
email,
|
|
|
|
errors: ['invalidEmail'],
|
|
|
|
fields: ['email']
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!passwordRegex.test(password)) {
|
|
|
|
return fail(400, {
|
|
|
|
username,
|
|
|
|
displayName,
|
|
|
|
email,
|
|
|
|
errors: ['invalidPassword'],
|
|
|
|
fields: ['password']
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(await Users.checkRegistration(username, email))) {
|
|
|
|
return fail(400, {
|
|
|
|
username,
|
|
|
|
displayName,
|
|
|
|
email,
|
|
|
|
errors: ['existingRegistration'],
|
|
|
|
fields: ['username', 'email']
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: check for registration token
|
|
|
|
const newUser = await Users.register({ username, displayName, password, email });
|
|
|
|
|
2024-06-10 20:20:25 +03:00
|
|
|
await Audit.insertRequest(AuditAction.REGISTRATION, event, newUser);
|
|
|
|
|
2024-05-18 19:07:56 +03:00
|
|
|
return {
|
|
|
|
success: newUser.activated ? 'userCreated' : 'emailSent'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export const load = ({ locals }) => {
|
|
|
|
if (locals.session.data?.user) {
|
|
|
|
return redirect(301, '/');
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
2024-06-07 18:46:49 +03:00
|
|
|
enabled: env.REGISTRATIONS === 'true'
|
2024-05-18 19:07:56 +03:00
|
|
|
};
|
|
|
|
};
|