Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/utils/db/seed/localCredentials.server.ts
T

142 lines
4.3 KiB
TypeScript

import type { DbClient } from "@hivegrid/db/client.server";
import { syncDefaultRolesAndPermissions } from "@hivegrid/db/permissions-seed.server";
import { syncAdminRoleCatalog } from "@hivegrid/db/admin-rbac.server";
import argon2 from "argon2";
import {
assertLocalCredentialsDatabaseMarker,
assertLocalCredentialsEnvironment,
type LocalCredentialsEnvironment,
} from "./localCredentialsEnvironment.server";
export const LOCAL_ADMIN_EMAIL = "admin@email.com";
export const LOCAL_ADMIN_PASSWORD = "password";
export type LocalCredentialsClient = Pick<DbClient, "$queryRaw" | "$transaction">;
export type LocalCredentialsResult = {
adminUserId: string;
normalizedPasswordIdentityCount: number;
};
export async function ensureLocalCredentials({
client,
env = process.env,
hashPassword = hashLocalPassword,
syncPermissions = async () => {
await syncDefaultRolesAndPermissions({ client: client as DbClient });
},
syncAdminPermissions = async () => {
await syncAdminRoleCatalog(client as DbClient);
},
}: {
client: LocalCredentialsClient;
env?: LocalCredentialsEnvironment;
hashPassword?: (password: string) => Promise<string>;
syncPermissions?: () => Promise<void>;
syncAdminPermissions?: () => Promise<void>;
}): Promise<LocalCredentialsResult> {
assertLocalCredentialsEnvironment(env);
await assertLocalCredentialsDatabaseMarker(client);
const passwordHash = await hashPassword(LOCAL_ADMIN_PASSWORD);
await syncPermissions();
await syncAdminPermissions();
return client.$transaction(async (transaction) => {
const admin = await transaction.user.upsert({
where: { email: LOCAL_ADMIN_EMAIL },
create: {
email: LOCAL_ADMIN_EMAIL,
passwordHash,
firstName: "Admin",
lastName: "User",
avatar: "",
phone: "",
active: true,
},
update: {
passwordHash,
active: true,
pendingDeletion: false,
scheduledDeletionAt: null,
deletionRequestedAt: null,
pseudonymized: false,
pseudonymizedAt: null,
deletionRequestId: null,
totpSecretEncrypted: null,
totpPendingSecretEncrypted: null,
totpEnabledAt: null,
totpPendingCreatedAt: null,
},
select: { id: true },
});
await transaction.adminUser.updateMany({
where: { email: { not: LOCAL_ADMIN_EMAIL }, status: "active" },
data: {
status: "disabled",
disabledAt: new Date(),
disabledReason: "Local canonical admin normalization",
},
});
const adminIdentity = await transaction.adminUser.upsert({
where: { email: LOCAL_ADMIN_EMAIL },
create: {
id: admin.id,
email: LOCAL_ADMIN_EMAIL,
firstName: "Admin",
lastName: "User",
status: "active",
authentikGroups: [],
},
update: {
firstName: "Admin",
lastName: "User",
status: "active",
disabledAt: null,
disabledReason: null,
},
select: { id: true },
});
const adminRole = await transaction.adminRole.findUnique({
where: { systemKey: "super_admin" },
select: { id: true },
});
if (!adminRole) {
throw new Error("Local credential setup could not resolve the canonical SuperAdmin role.");
}
// Delete and recreate so this remains idempotent even after `prisma db push`,
// where migration-only partial unique indexes may not exist yet.
await transaction.adminUserRole.deleteMany({
where: { adminUserId: adminIdentity.id },
});
await transaction.adminUserRole.create({
data: { adminUserId: adminIdentity.id, adminRoleId: adminRole.id, grantedByAdminUserId: adminIdentity.id },
});
const normalizedUsers = await transaction.user.updateMany({
where: { passwordHash: { not: null } },
data: { passwordHash },
});
const normalizedPortalUsers = await transaction.portalUser.updateMany({
where: { passwordHash: { not: null } },
data: { passwordHash },
});
return {
adminUserId: adminIdentity.id,
normalizedPasswordIdentityCount: normalizedUsers.count + normalizedPortalUsers.count,
};
});
}
async function hashLocalPassword(password: string): Promise<string> {
return argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 19_456,
timeCost: 2,
parallelism: 1,
});
}