87 lines
2.9 KiB
JavaScript
87 lines
2.9 KiB
JavaScript
import { randomBytes } from 'node:crypto';
|
|
import { writeFileSync } from 'node:fs';
|
|
import { PrismaPg } from '../../api/node_modules/@prisma/adapter-pg/dist/index.js';
|
|
import { PrismaClient } from '../../api/node_modules/@prisma/client/index.js';
|
|
|
|
const databaseUrl = process.env.DATABASE_URL;
|
|
if (!databaseUrl) {
|
|
throw new Error('DATABASE_URL is required');
|
|
}
|
|
|
|
const prisma = new PrismaClient({ adapter: new PrismaPg(databaseUrl) });
|
|
const username = process.env.PROD_ADMIN_USERNAME || 'prod_admin';
|
|
const email = process.env.PROD_ADMIN_EMAIL || 'admin@example.com';
|
|
const configuredPassword = process.env.PROD_ADMIN_PASSWORD;
|
|
const credentialFile = process.env.PROD_ADMIN_CREDENTIAL_FILE;
|
|
|
|
async function main() {
|
|
// production-deploy.sh builds the API before invoking this helper, so every
|
|
// administrative password write shares the same versioned hasher as runtime APIs.
|
|
const { hashPassword } = await import('../../api/dist/auth/password-hasher.js');
|
|
const existingUser = await prisma.user.findFirst({
|
|
where: { username, deletedAt: null },
|
|
});
|
|
const createPassword = configuredPassword || randomBytes(18).toString('base64url');
|
|
const updatePassword = configuredPassword;
|
|
const passwordHash = await hashPassword(updatePassword || createPassword);
|
|
const role = await prisma.role.upsert({
|
|
where: { code: 'platform_admin' },
|
|
update: { name: '平台管理员', scope: 'platform' },
|
|
create: { code: 'platform_admin', name: '平台管理员', scope: 'platform' },
|
|
});
|
|
const userData = {
|
|
email,
|
|
displayName: '生产平台管理员',
|
|
...(updatePassword ? { passwordHash } : {}),
|
|
status: 'active',
|
|
failedLoginCount: 0,
|
|
lockedUntil: null,
|
|
deletedAt: null,
|
|
tenantId: null,
|
|
};
|
|
const user = existingUser
|
|
? await prisma.user.update({
|
|
where: { id: existingUser.id },
|
|
data: userData,
|
|
})
|
|
: await prisma.user.create({
|
|
data: {
|
|
username,
|
|
email,
|
|
displayName: '生产平台管理员',
|
|
passwordHash,
|
|
status: 'active',
|
|
},
|
|
});
|
|
await prisma.userRole.upsert({
|
|
where: { userId_roleId: { userId: user.id, roleId: role.id } },
|
|
update: {},
|
|
create: { userId: user.id, roleId: role.id },
|
|
});
|
|
|
|
const message = [
|
|
'CMPP production admin account',
|
|
`username=${username}`,
|
|
`email=${email}`,
|
|
existingUser
|
|
? (updatePassword ? `password=${updatePassword}` : 'password=unchanged')
|
|
: `password=${createPassword}`,
|
|
`generatedAt=${new Date().toISOString()}`,
|
|
'',
|
|
].join('\n');
|
|
if (credentialFile) {
|
|
writeFileSync(credentialFile, message, { mode: 0o600 });
|
|
}
|
|
console.log(`Production admin is ready: ${email}`);
|
|
if (!credentialFile) {
|
|
console.log(existingUser && !updatePassword ? 'Password unchanged' : `Temporary password: ${existingUser ? updatePassword : createPassword}`);
|
|
}
|
|
}
|
|
|
|
main()
|
|
.finally(() => prisma.$disconnect())
|
|
.catch((error) => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|