d8785a964d
Every AGENT now gets an auto-generated API key on creation, shown once in a modal. AGENTs log in with password and authenticate to the API with X-Api-Key. pre-push.sql defensively migrates any residual SERVICE rows to AGENT before Prisma rewrites the enum. Goddard is no longer baked into the seed — create agents via Admin → Users. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
91 lines
2.4 KiB
TypeScript
91 lines
2.4 KiB
TypeScript
import bcrypt from 'bcryptjs';
|
|
import crypto from 'crypto';
|
|
import { Prisma } from '@prisma/client';
|
|
import prisma from '../lib/prisma';
|
|
import { HttpError } from '../lib/httpError';
|
|
import type { CreateUserInput, UpdateUserInput } from '../../../shared/schemas/user';
|
|
|
|
const userSelect = {
|
|
id: true,
|
|
username: true,
|
|
displayName: true,
|
|
email: true,
|
|
role: true,
|
|
apiKey: true,
|
|
createdAt: true,
|
|
} as const;
|
|
|
|
const userListSelect = {
|
|
id: true,
|
|
username: true,
|
|
displayName: true,
|
|
email: true,
|
|
role: true,
|
|
createdAt: true,
|
|
} as const;
|
|
|
|
export function listUsers() {
|
|
return prisma.user.findMany({
|
|
select: userListSelect,
|
|
orderBy: { displayName: 'asc' },
|
|
});
|
|
}
|
|
|
|
export async function getCurrentUser(id: string) {
|
|
const user = await prisma.user.findUnique({
|
|
where: { id },
|
|
select: userListSelect,
|
|
});
|
|
if (!user) throw new HttpError(404, 'User not found');
|
|
return user;
|
|
}
|
|
|
|
export async function createUser(data: CreateUserInput) {
|
|
const passwordHash = await bcrypt.hash(data.password, 12);
|
|
|
|
const apiKey =
|
|
data.role === 'AGENT' ? `sk_${crypto.randomBytes(32).toString('hex')}` : undefined;
|
|
|
|
return prisma.user.create({
|
|
data: {
|
|
username: data.username,
|
|
email: data.email,
|
|
displayName: data.displayName,
|
|
passwordHash,
|
|
role: data.role,
|
|
apiKey,
|
|
},
|
|
select: userSelect,
|
|
});
|
|
}
|
|
|
|
export async function updateUser(id: string, data: UpdateUserInput) {
|
|
const update: Prisma.UserUpdateInput = {};
|
|
if (data.displayName) update.displayName = data.displayName;
|
|
if (data.email) update.email = data.email;
|
|
if (data.password) update.passwordHash = await bcrypt.hash(data.password, 12);
|
|
if (data.role) {
|
|
update.role = data.role;
|
|
if (data.role === 'AGENT') {
|
|
const existing = await prisma.user.findUnique({ where: { id }, select: { apiKey: true } });
|
|
if (!existing?.apiKey) {
|
|
update.apiKey = `sk_${crypto.randomBytes(32).toString('hex')}`;
|
|
}
|
|
} else {
|
|
update.apiKey = null;
|
|
}
|
|
}
|
|
if (data.regenerateApiKey) {
|
|
update.apiKey = `sk_${crypto.randomBytes(32).toString('hex')}`;
|
|
}
|
|
|
|
return prisma.user.update({ where: { id }, data: update, select: userSelect });
|
|
}
|
|
|
|
export async function deleteUser(id: string, actorId: string) {
|
|
if (id === actorId) {
|
|
throw new HttpError(400, 'Cannot delete your own account');
|
|
}
|
|
await prisma.user.delete({ where: { id } });
|
|
}
|