64 lines
2.6 KiB
TypeScript
64 lines
2.6 KiB
TypeScript
import { and, eq, gt } from "drizzle-orm";
|
|
import * as z from "zod";
|
|
import { providers, sessions } from "~~/drizzle/schema";
|
|
import { lockAccount } from "~~/server/lib/account-security";
|
|
import { db } from "~~/server/lib/db";
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const { user, session } = await protectRoute(event);
|
|
const result = await readValidatedBody(event, (body) => z.object({
|
|
config: z.object({
|
|
apiKey: z.string().optional(),
|
|
apiProxyUrl: z.string().optional(),
|
|
}).optional(),
|
|
expectedUpdatedAt: z.iso.datetime().optional(),
|
|
enabled: z.boolean().optional(),
|
|
}).refine(body => body.config === undefined || body.expectedUpdatedAt !== undefined, {
|
|
message: "Provider config updates require a revision",
|
|
}).safeParse(body));
|
|
|
|
if (!result.success) {
|
|
throw createError({ statusCode: 400, message: result.error.issues[0]?.message });
|
|
}
|
|
|
|
const providerId = getRouterParam(event, "providerId");
|
|
if (!providerId) {
|
|
throw createError({ statusCode: 400, statusMessage: "Invalid provider" });
|
|
}
|
|
const { config, enabled, expectedUpdatedAt } = result.data;
|
|
|
|
return await db.transaction(async (tx) => {
|
|
await lockAccount(tx, user.id);
|
|
// A request may have waited behind a password rotation or recovery.
|
|
const [activeSession] = await tx.select({ id: sessions.id }).from(sessions).where(and(
|
|
eq(sessions.id, session.id), eq(sessions.userId, user.id), gt(sessions.expiresAt, new Date()),
|
|
));
|
|
if (!activeSession) {
|
|
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
|
|
}
|
|
|
|
const [existing] = await tx.select().from(providers).where(and(
|
|
eq(providers.id, providerId), eq(providers.userId, user.id),
|
|
));
|
|
if (!existing) {
|
|
throw createError({ statusCode: 404, statusMessage: "Invalid provider" });
|
|
}
|
|
if (config !== undefined && existing.updatedAt.getTime() !== new Date(expectedUpdatedAt ?? "").getTime()) {
|
|
throw createError({
|
|
statusCode: 409,
|
|
statusMessage: "Provider config out of date",
|
|
data: { code: "PROVIDER_KEYS_OUT_OF_DATE" },
|
|
});
|
|
}
|
|
|
|
const updatedAt = new Date(Math.max(Date.now(), existing.updatedAt.getTime() + 1));
|
|
await tx.update(providers).set({
|
|
config: config === undefined ? undefined : { ...existing.config, ...config },
|
|
enabled,
|
|
updatedAt,
|
|
}).where(and(eq(providers.id, providerId), eq(providers.userId, user.id)));
|
|
|
|
return { ok: true, updatedAt };
|
|
});
|
|
});
|