svelte-guardian
Version:
Batteries included authentication for SvelteKit applications.
96 lines (95 loc) • 3.65 kB
JavaScript
import { sendEmail } from '../email';
import { hashPassword } from '../utils/security';
import { validatePassword } from '../utils/validation';
export class PasswordResetService {
options;
adapter;
emailProvider;
constructor(options, adapter, emailProvider) {
this.options = options;
this.adapter = adapter;
this.emailProvider = emailProvider;
}
async initiatePasswordReset(email) {
const token = crypto.randomUUID();
// Store password reset token in database
await this.adapter.createVerificationToken({
identifier: email,
token,
expires: new Date(Date.now() + (this.options.tokenExpiration || 15) * 60 * 1000) // 15 minutes
});
// Send password reset email
const resetLink = `${process.env.APP_URL}/reset-password?token=${token}&email=${email}`;
console.log(resetLink);
await sendEmail({
to: email,
subject: 'Password Reset Request',
type: 'passwordReset',
url: resetLink
}, this.emailProvider);
return token;
}
async resetPassword(email, token, newPassword, passwordPolicy) {
const validPassword = validatePassword(newPassword, passwordPolicy);
if (!validPassword?.success)
return { success: false, error: validPassword.message };
// get token and delete from db
const resetRequest = await this.adapter.useVerificationToken({
token,
identifier: email
});
if (!resetRequest) {
return { success: false, error: 'Invalid or expired link!' };
}
const hasExpired = new Date(resetRequest.expires) < new Date();
if (hasExpired) {
return { success: false, error: 'Link has expired!' };
}
// Hash new password
const hashedPassword = await hashPassword(newPassword);
const existingUser = await this.adapter.getUserByEmail(resetRequest.identifier);
if (!existingUser) {
return { success: false, error: 'User not registered!' };
}
// Update user password
await this.adapter.updateUser({
id: existingUser.id,
password: hashedPassword
});
return { success: true };
}
}
export const getResetPasswordActions = (options = {}, adapter, emailProvider, passwordPolicy) => {
if (!adapter)
throw new Error('Adapter is required');
if (!emailProvider)
throw new Error('Email Provider is required');
const service = new PasswordResetService(options, adapter, emailProvider);
const resetPasswordActions = {
initiateReset: async ({ request }) => {
const data = await request.formData();
const email = data.get('email');
try {
await service.initiatePasswordReset(email);
return { success: true };
}
catch (error) {
return { success: false, error: error.message };
}
},
resetPassword: async ({ request }) => {
const data = await request.formData();
const token = data.get('token');
const email = data.get('email');
const newPassword = data.get('newPassword');
try {
await service.resetPassword(email, token, newPassword, passwordPolicy);
return { success: true };
}
catch (error) {
return { success: false, error: error.message };
}
}
};
return resetPasswordActions;
};