122 lines
3.5 KiB
TypeScript
122 lines
3.5 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 { 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<Doc<'users'>> => {
|
|
const user = await ctx.db.get(userId);
|
|
if (!user) throw new ConvexError('User not found.');
|
|
return user;
|
|
};
|
|
const isSignedIn = async (ctx: QueryCtx): Promise<Doc<'users'> | 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 };
|
|
},
|
|
});
|