Initial commit for project Spoon!
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import { Password as DefaultPassword } from '@convex-dev/auth/providers/Password';
|
||||
import { ConvexError } from 'convex/values';
|
||||
|
||||
import { UseSendOTP, UseSendOTPPasswordReset } from '..';
|
||||
import { DataModel } from '../../../_generated/dataModel';
|
||||
|
||||
export const Password = DefaultPassword<DataModel>({
|
||||
profile: (params) => ({
|
||||
email: params.email as string,
|
||||
name: params.name as string,
|
||||
}),
|
||||
validatePasswordRequirements: (password: string) => {
|
||||
if (!validatePassword(password)) {
|
||||
throw new ConvexError('Invalid password.');
|
||||
}
|
||||
},
|
||||
reset: UseSendOTPPasswordReset,
|
||||
verify: UseSendOTP,
|
||||
});
|
||||
|
||||
export const validatePassword = (password: string): boolean => {
|
||||
if (
|
||||
password.length < 8 ||
|
||||
password.length > 100 ||
|
||||
/\s/.test(password) ||
|
||||
!/\d/.test(password) ||
|
||||
!/[a-z]/.test(password) ||
|
||||
!/[A-Z]/.test(password) ||
|
||||
!/[\p{P}\p{S}]/u.test(password)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
@@ -0,0 +1,94 @@
|
||||
import type { EmailConfig, EmailUserConfig } from '@auth/core/providers/email';
|
||||
import { generateRandomString, RandomReader } from '@oslojs/crypto/random';
|
||||
import { alphabet } from 'oslo/crypto';
|
||||
import { UseSend } from 'usesend-js';
|
||||
|
||||
export default function UseSendProvider(config: EmailUserConfig): EmailConfig {
|
||||
return {
|
||||
id: 'usesend',
|
||||
type: 'email',
|
||||
name: 'UseSend',
|
||||
from: process.env.USESEND_FROM_EMAIL ?? 'noreply@example.com',
|
||||
maxAge: 24 * 60 * 60, // 24 hours
|
||||
|
||||
generateVerificationToken: () => {
|
||||
const random: RandomReader = {
|
||||
read: (bytes) => {
|
||||
crypto.getRandomValues(bytes as Uint8Array<ArrayBuffer>);
|
||||
},
|
||||
};
|
||||
return generateRandomString(random, alphabet('0-9'), 6);
|
||||
},
|
||||
|
||||
sendVerificationRequest: async (params) => {
|
||||
const { identifier: to, provider, url, token } = params;
|
||||
// Derive a display name from the site URL, fallback to 'App'
|
||||
const siteUrl = process.env.USESEND_FROM_EMAIL ?? '';
|
||||
const appName = siteUrl.split('@')[1]?.split('.')[0] ?? 'App';
|
||||
|
||||
const apiKey = process.env.USESEND_API_KEY;
|
||||
const useSendUrl = process.env.USESEND_URL;
|
||||
if (!apiKey || !useSendUrl) {
|
||||
throw new Error('USESEND_API_KEY and USESEND_URL must be set.');
|
||||
}
|
||||
|
||||
const useSend = new UseSend(apiKey, useSendUrl);
|
||||
|
||||
// For password reset, we want to send the code, not the magic link
|
||||
const isPasswordReset =
|
||||
url.includes('reset') || provider.id.includes('reset');
|
||||
|
||||
const result = await useSend.emails.send({
|
||||
from: provider.from ?? 'noreply@example.com',
|
||||
to: [to],
|
||||
subject: isPasswordReset
|
||||
? `Reset your password - ${appName}`
|
||||
: `Sign in to ${appName}`,
|
||||
text: isPasswordReset
|
||||
? `Your password reset code is ${token}`
|
||||
: `Your sign in code is ${token}`,
|
||||
html: isPasswordReset
|
||||
? `
|
||||
<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
|
||||
<h2>Password Reset Request</h2>
|
||||
<p>You requested a password reset. Your reset code is:</p>
|
||||
<div style="font-size: 32px; font-weight: bold; text-align: center; padding: 20px; background: #f5f5f5; margin: 20px 0; border-radius: 8px;">
|
||||
${token}
|
||||
</div>
|
||||
<p>This code expires in 1 hour.</p>
|
||||
<p>If you didn't request this, please ignore this email.</p>
|
||||
</div>
|
||||
`
|
||||
: `
|
||||
<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
|
||||
<h2>Your Sign In Code</h2>
|
||||
<p>Your verification code is:</p>
|
||||
<div style="font-size: 32px; font-weight: bold; text-align: center; padding: 20px; background: #f5f5f5; margin: 20px 0; border-radius: 8px;">
|
||||
${token}
|
||||
</div>
|
||||
<p>This code expires in 24 hours.</p>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
throw new Error('UseSend error: ' + JSON.stringify(result.error));
|
||||
}
|
||||
},
|
||||
|
||||
options: config,
|
||||
};
|
||||
}
|
||||
|
||||
// Create specific instances for password reset and email verification
|
||||
export const UseSendOTPPasswordReset = UseSendProvider({
|
||||
id: 'usesend-otp-password-reset',
|
||||
apiKey: process.env.USESEND_API_KEY,
|
||||
maxAge: 60 * 60, // 1 hour
|
||||
});
|
||||
|
||||
export const UseSendOTP = UseSendProvider({
|
||||
id: 'usesend-otp',
|
||||
apiKey: process.env.USESEND_API_KEY,
|
||||
maxAge: 60 * 20, // 20 minutes
|
||||
});
|
||||
Reference in New Issue
Block a user