Files
techtracker/packages/backend/convex/auth.ts
2025-09-20 09:49:54 -05:00

207 lines
6.4 KiB
TypeScript

import { ConvexError, v } from 'convex/values';
import {
convexAuth,
getAuthUserId,
retrieveAccount,
modifyAccountCredentials,
} from '@convex-dev/auth/server';
import { api } from './_generated/api';
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],
});
export const PASSWORD_MIN = 8;
export const PASSWORD_MAX = 100;
export const PASSWORD_REGEX =
/^(?=.{8,100}$)(?!.*\s)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\p{P}\p{S}]).*$/u;
export const getUser = query(async (ctx) => {
const userId = await getAuthUserId(ctx);
if (!userId) return null;
const user = await ctx.db.get(userId);
if (!user) throw new ConvexError('User not found.');
const image: Id<'_storage'> | null =
typeof user.image === 'string' && user.image.length > 0
? (user.image as Id<'_storage'>)
: null;
const authAccount = await ctx.db
.query('authAccounts')
.withIndex('userIdAndProvider', (q) => q.eq('userId', userId))
.first();
return {
id: user._id,
email: user?.email,
name: user?.name,
image,
lunchTime: user?.lunchTime,
automaticLunch: user.automaticLunch,
provider: authAccount?.provider,
};
});
export const getUserAuthAccount = query(async (ctx) => {
const userId = await getAuthUserId(ctx);
if (!userId) return null;
const authAccount = await ctx.db
.query('authAccounts')
.withIndex('userIdAndProvider', (q) => q.eq('userId', userId))
.first();
return authAccount;
});
export const getAllUsers = query(async (ctx) => {
const users = await ctx.db.query('users').collect();
return users.map((u) => ({
id: u._id,
email: u.email ?? null,
name: u.name ?? null,
image: u.image ?? null,
lunchTime: u.lunchTime ?? null,
automaticLUnch: u.automaticLunch ?? (false as boolean),
}));
});
export const getAllUserIds = query(async (ctx) => {
const users = await ctx.db.query('users').collect();
const userIds = users.map((u) => u._id);
return userIds;
});
export const updateUserName = mutation({
args: {
name: v.string(),
},
handler: async (ctx, { name }) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new ConvexError('Not authenticated.');
const user = await ctx.db.get(userId);
if (!user) throw new ConvexError('User not found.');
await ctx.db.patch(userId, { name });
return { success: true };
},
});
export const updateUserEmail = mutation({
args: {
email: v.string(),
},
handler: async (ctx, { email }) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new ConvexError('Not authenticated.');
const user = await ctx.db.get(userId);
if (!user) throw new ConvexError('User not found.');
await ctx.db.patch(userId, { email });
return { success: true };
},
});
export const updateUserImage = mutation({
args: {
storageId: v.id('_storage'),
},
handler: async (ctx, { storageId }) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new ConvexError('Not authenticated.');
const user = await ctx.db.get(userId);
if (!user) throw new ConvexError('User not found.');
const oldImage = user.image as Id<'_storage'> | undefined;
await ctx.db.patch(userId, { image: storageId });
if (oldImage && oldImage !== storageId) await ctx.storage.delete(oldImage);
return { success: true };
},
});
export const updateUserLunchtime = mutation({
args: {
lunchTime: v.string(),
},
handler: async (ctx, { lunchTime }) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new ConvexError('Not authenticated.');
const user = await ctx.db.get(userId);
if (!user) throw new ConvexError('User not found.');
if (!lunchTime.includes(':'))
throw new ConvexError('Lunch time is invalid.');
await ctx.db.patch(userId, { lunchTime });
return { success: true };
},
});
export const updateUserAutomaticLunch = mutation({
args: { automaticLunch: v.boolean() },
handler: async (ctx, { automaticLunch }) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new ConvexError('Not authenticated.');
const user = await ctx.db.get(userId);
if (!user) throw new ConvexError('User not found.');
if (user.automaticLunch === automaticLunch) return { success: true };
await ctx.db.patch(userId, { automaticLunch });
return { success: true };
},
});
export const updateUserPassword = action({
args: {
currentPassword: v.string(),
newPassword: v.string(),
},
handler: async (ctx, { currentPassword, newPassword }) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new ConvexError('Not authenticated.');
const user = await ctx.runQuery(api.auth.getUser);
if (!user?.email) throw new ConvexError('User not found.');
const verified = await retrieveAccount(ctx, {
provider: 'password',
account: { id: user.email, secret: currentPassword },
});
if (!verified) throw new ConvexError('Current password is incorrect.');
if (!validatePassword(newPassword))
throw new ConvexError('Invalid password.');
await modifyAccountCredentials(ctx, {
provider: 'password',
account: { id: user.email, secret: newPassword },
});
return { success: true };
},
});
//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");
//}
//},
//});