Add Usesend but havent tested it just yet
This commit is contained in:
4
packages/backend/convex/_generated/api.d.ts
vendored
4
packages/backend/convex/_generated/api.d.ts
vendored
@@ -16,9 +16,11 @@ import type {
|
||||
import type * as auth from "../auth.js";
|
||||
import type * as crons from "../crons.js";
|
||||
import type * as custom_auth_index from "../custom/auth/index.js";
|
||||
import type * as custom_auth_password_reset from "../custom/auth/password/reset.js";
|
||||
import type * as custom_auth_password_validate from "../custom/auth/password/validate.js";
|
||||
import type * as custom_auth_providers_entra from "../custom/auth/providers/entra.js";
|
||||
import type * as custom_auth_providers_password from "../custom/auth/providers/password.js";
|
||||
import type * as custom_auth_providers_usesend from "../custom/auth/providers/usesend.js";
|
||||
import type * as files from "../files.js";
|
||||
import type * as http from "../http.js";
|
||||
import type * as statuses from "../statuses.js";
|
||||
@@ -35,9 +37,11 @@ declare const fullApi: ApiFromModules<{
|
||||
auth: typeof auth;
|
||||
crons: typeof crons;
|
||||
"custom/auth/index": typeof custom_auth_index;
|
||||
"custom/auth/password/reset": typeof custom_auth_password_reset;
|
||||
"custom/auth/password/validate": typeof custom_auth_password_validate;
|
||||
"custom/auth/providers/entra": typeof custom_auth_providers_entra;
|
||||
"custom/auth/providers/password": typeof custom_auth_providers_password;
|
||||
"custom/auth/providers/usesend": typeof custom_auth_providers_usesend;
|
||||
files: typeof files;
|
||||
http: typeof http;
|
||||
statuses: typeof statuses;
|
||||
|
@@ -10,12 +10,13 @@ import { type Id } from './_generated/dataModel';
|
||||
import { action, mutation, query } from './_generated/server';
|
||||
import Authentik from '@auth/core/providers/authentik';
|
||||
import { Entra, Password, validatePassword } from './custom/auth';
|
||||
import Resend from '@auth/core/providers/resend';
|
||||
import { Resend as ResendAPI } from 'resend';
|
||||
import { generateRandomString, RandomReader } from '@oslojs/crypto/random';
|
||||
|
||||
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
|
||||
providers: [Authentik, Entra, Password],
|
||||
providers: [
|
||||
Authentik({ allowDangerousEmailAccountLinking: true }),
|
||||
Entra,
|
||||
Password,
|
||||
],
|
||||
});
|
||||
|
||||
export const PASSWORD_MIN = 8;
|
||||
@@ -176,31 +177,3 @@ export const updateUserPassword = action({
|
||||
},
|
||||
});
|
||||
|
||||
//export const ResendOTPPasswordReset = Resend({
|
||||
//id: "resend-otp",
|
||||
//apiKey: process.env.AUTH_RESEND_KEY,
|
||||
//async generateVerificationToken() {
|
||||
//const random: RandomReader = {
|
||||
//read(bytes) {
|
||||
//crypto.getRandomValues(bytes);
|
||||
//},
|
||||
//};
|
||||
|
||||
//const alphabet = "0123456789";
|
||||
//const length = 8;
|
||||
//return generateRandomString(random, alphabet, length);
|
||||
//},
|
||||
//async sendVerificationRequest({ identifier: email, provider, token }) {
|
||||
//const resend = new ResendAPI(provider.apiKey);
|
||||
//const { error } = await resend.emails.send({
|
||||
//from: "My App <onboarding@resend.dev>",
|
||||
//to: [email],
|
||||
//subject: `Reset your password in My App`,
|
||||
//text: "Your password reset code is " + token,
|
||||
//});
|
||||
|
||||
//if (error) {
|
||||
//throw new Error("Could not send");
|
||||
//}
|
||||
//},
|
||||
//});
|
||||
|
@@ -1,3 +1,4 @@
|
||||
export { validatePassword } from './password/validate';
|
||||
export { Entra } from './providers/entra';
|
||||
export { Password } from './providers/password';
|
||||
export { Usesend } from './providers/usesend';
|
||||
|
28
packages/backend/convex/custom/auth/password/reset.ts
Normal file
28
packages/backend/convex/custom/auth/password/reset.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Usesend } from '..';
|
||||
import { UseSend } from 'usesend-js';
|
||||
import { generateRandomString, RandomReader } from '@oslojs/crypto/random';
|
||||
|
||||
export const UsesendOTPPasswordReset = Usesend({
|
||||
id: 'unsend-otp',
|
||||
apiKey: process.env.AUTH_USESEND_API_KEY,
|
||||
async generateVerificationToken() {
|
||||
const random: RandomReader = {
|
||||
read(bytes) {
|
||||
crypto.getRandomValues(bytes);
|
||||
},
|
||||
};
|
||||
const alphabet = '0123456789';
|
||||
const length = 8;
|
||||
return generateRandomString(random, alphabet, length);
|
||||
},
|
||||
async sendVerificationRequest({ identifier: email, provider, token }) {
|
||||
const useSend = new UseSend(provider.apiKey, 'https://usesend.gbrown.org');
|
||||
const { error } = await useSend.emails.send({
|
||||
to: [email],
|
||||
from: provider.from ?? 'TechTracker Admin <admin@mail.techtracker.gbrown.org>',
|
||||
subject: `Reset your password - TechTracker`,
|
||||
text: `Your password reset code is ${token}`,
|
||||
});
|
||||
if (error) throw new Error("Usesend error: " + error.message)
|
||||
},
|
||||
});
|
@@ -19,11 +19,13 @@ export const Entra: AuthProviderMaterializedConfig = {
|
||||
token:
|
||||
'https://login.microsoftonline.com/16200986-86f1-44d2-974c-cfa99352722c/oauth2/v2.0/token',
|
||||
userinfo: 'https://graph.microsoft.com/oidc/userinfo',
|
||||
allowDangerousEmailAccountLinking: true,
|
||||
profile(profile) {
|
||||
return {
|
||||
id: profile.sub,
|
||||
name: profile.name,
|
||||
email: profile.email,
|
||||
//image: profile.picture,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
@@ -2,6 +2,7 @@ import { ConvexError } from 'convex/values';
|
||||
import { Password as DefaultPassword } from '@convex-dev/auth/providers/Password';
|
||||
import { validatePassword } from '../password/validate';
|
||||
import type { DataModel } from '../../../_generated/dataModel';
|
||||
import { UsesendOTPPasswordReset } from '../password/reset';
|
||||
|
||||
export const Password = DefaultPassword<DataModel>({
|
||||
profile(params, ctx) {
|
||||
@@ -15,4 +16,5 @@ export const Password = DefaultPassword<DataModel>({
|
||||
throw new ConvexError('Invalid password.');
|
||||
}
|
||||
},
|
||||
reset: UsesendOTPPasswordReset,
|
||||
});
|
||||
|
89
packages/backend/convex/custom/auth/providers/usesend.ts
Normal file
89
packages/backend/convex/custom/auth/providers/usesend.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import type { EmailConfig, EmailUserConfig } from '@auth/core/providers/email';
|
||||
import { UseSend } from 'usesend-js';
|
||||
|
||||
/** @todo Document this */
|
||||
export const Usesend = (config: EmailUserConfig): EmailConfig => {
|
||||
return {
|
||||
id: "usesend",
|
||||
type: "email",
|
||||
name: "Usesend",
|
||||
from: "TechTracker Admin <admin@mail.techtracker.gbrown.org>",
|
||||
maxAge: 24 * 60 * 60,
|
||||
async sendVerificationRequest(params) {
|
||||
const { identifier: to, provider, url, theme } = params
|
||||
const { host } = new URL(url)
|
||||
const useSend = new UseSend(provider.apiKey, 'https://usesend.gbrown.org')
|
||||
const { error } = await useSend.emails.send({
|
||||
to,
|
||||
from: provider.from ?? 'TechTracker Admin <admin@mail.techtracker.gbrown.org>',
|
||||
subject: `Sign in to ${host}`,
|
||||
html: html({ url, host, theme }),
|
||||
text: text({ url, host })
|
||||
});
|
||||
if (error) throw new Error("Usesend error: " + error.message)
|
||||
},
|
||||
options: config,
|
||||
};
|
||||
};
|
||||
|
||||
type Theme = {
|
||||
colorScheme?: "auto" | "dark" | "light"
|
||||
logo?: string
|
||||
brandColor?: string
|
||||
buttonText?: string
|
||||
};
|
||||
|
||||
const text = ({ url, host }: { url: string; host: string }) => {
|
||||
return `Sign in to ${host}\n${url}\n\n`
|
||||
};
|
||||
|
||||
const html = (params: { url: string; host: string; theme: Theme }) => {
|
||||
const { url, host, theme } = params;
|
||||
|
||||
const escapedHost = host.replace(/\./g, "​.");
|
||||
|
||||
const brandColor = theme.brandColor || "#346df1";
|
||||
|
||||
const buttonText = theme.buttonText || "#fff";
|
||||
|
||||
const color = {
|
||||
background: "#f9f9f9",
|
||||
text: "#444",
|
||||
mainBackground: "#fff",
|
||||
buttonBackground: brandColor,
|
||||
buttonBorder: brandColor,
|
||||
buttonText,
|
||||
};
|
||||
|
||||
return `
|
||||
<body style="background: ${color.background};">
|
||||
<table width="100%" border="0" cellspacing="20" cellpadding="0"
|
||||
style="background: ${color.mainBackground}; max-width: 600px; margin: auto; border-radius: 10px;">
|
||||
<tr>
|
||||
<td align="center"
|
||||
style="padding: 10px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
|
||||
Sign in to <strong>${escapedHost}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="padding: 20px 0;">
|
||||
<table border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td align="center" style="border-radius: 5px;" bgcolor="${color.buttonBackground}"><a href="${url}"
|
||||
target="_blank"
|
||||
style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${color.buttonText}; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${color.buttonBorder}; display: inline-block; font-weight: bold;">Sign
|
||||
in</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"
|
||||
style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
|
||||
If you did not request this email you can safely ignore it.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
`
|
||||
};
|
@@ -12,8 +12,8 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@oslojs/crypto": "^1.0.1",
|
||||
"convex": "^1.27.0",
|
||||
"resend": "^6.1.0"
|
||||
"convex": "^1.27.1",
|
||||
"usesend-js": "^1.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "5.9.2"
|
||||
|
Reference in New Issue
Block a user