import { ConvexError, v } from 'convex/values'; import { convexAuth, getAuthUserId, retrieveAccount, modifyAccountCredentials, } from '@convex-dev/auth/server'; import { api } from './_generated/api'; import type { Doc, Id } from './_generated/dataModel'; import { action, mutation, query, type MutationCtx, type QueryCtx, } from './_generated/server'; import Authentik from '@auth/core/providers/authentik'; import { Password, validatePassword } from './custom/auth'; export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({ providers: [ Authentik({ allowDangerousEmailAccountLinking: true }), Password, ], }); const getUserById = async ( ctx: QueryCtx, userId: Id<'users'> ): Promise> => { const user = await ctx.db.get(userId); if (!user) throw new ConvexError('User not found.'); return user; }; const isSignedIn = async (ctx: QueryCtx): Promise | null> => { const userId = await getAuthUserId(ctx); if (!userId) return null; const user = await ctx.db.get(userId); if (!user) return null; return user; }; export const getUser = query({ args: { userId: v.optional(v.id('users')) }, handler: async (ctx, args) => { const user = await isSignedIn(ctx); const userId = args.userId ?? user?._id; if (!userId) throw new ConvexError('Not authenticated or no ID provided.'); return getUserById(ctx, userId); }, }); export const getAllUsers = query(async (ctx) => { const users = await ctx.db.query('users').collect(); return users ?? null; }); export const getAllUserIds = query(async (ctx) => { const users = await ctx.db.query('users').collect(); return users.map((u) => u._id); }); export const updateUser = mutation({ args: { name: v.optional(v.string()), email: v.optional(v.string()), image: v.optional(v.id('_storage')), }, handler: async (ctx, args) => { 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 patch: Partial<{ name: string; email: string; image: Id<'_storage'>; }> = {}; if (args.name !== undefined) patch.name = args.name; if (args.email !== undefined) patch.email = args.email; if (args.image !== undefined) { const oldImage = user.image as Id<'_storage'> | undefined; patch.image = args.image; if (oldImage && oldImage !== args.image) { await ctx.storage.delete(oldImage); } } if (Object.keys(patch).length > 0) { await ctx.db.patch(userId, patch); } 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, { userId }); 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 }; }, });