Files
ar.badieiandCursor c6243caa35 Add read-only Noya admin panel with assignments and UX polish.
Ship admin auth, user overview, assessments, chats, assignment filters/export, security hardening, and a separate desktop admin shell with clearer assessment answers.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-21 08:00:24 +03:30

131 lines
2.9 KiB
TypeScript

/**
* Promote an existing password user to admin by email.
* Does not create users. Idempotent. Does not revoke sessions or mutate other data.
*
* Usage: npm run admin:promote -- admin@example.com
*/
import { AuthProvider, UserRole } from '@prisma/client';
import { prisma } from '../src/lib/prisma.ts';
import { AUDIT_ACTIONS } from '../src/modules/admin/auditActions.ts';
import { AuditPersistError, recordAudit } from '../src/modules/admin/audit.service.ts';
import { normalizeEmail } from '../src/modules/admin/adminBootstrap.ts';
const rawEmail = process.argv[2];
if (!rawEmail) {
console.error('Usage: npm run admin:promote -- <email>');
process.exit(1);
}
const email = normalizeEmail(rawEmail);
if (!email.includes('@')) {
console.error('Invalid email address');
process.exit(1);
}
const identity = await prisma.authIdentity.findFirst({
where: {
provider: AuthProvider.password,
OR: [{ email }, { providerSubject: email }],
},
include: {
user: {
select: {
id: true,
externalId: true,
role: true,
isGuest: true,
updatedAt: true,
},
},
},
});
if (!identity?.user) {
console.error(
JSON.stringify(
{
ok: false,
email,
error: 'USER_NOT_FOUND',
message: 'No password account found for this email. No user was created.',
},
null,
2,
),
);
await prisma.$disconnect();
process.exit(1);
}
const user = identity.user;
const alreadyAdmin = user.role === UserRole.admin;
if (!alreadyAdmin) {
await prisma.user.update({
where: { id: user.id },
data: { role: UserRole.admin },
});
}
try {
await recordAudit({
actorUserId: user.id,
action: AUDIT_ACTIONS.ADMIN_PROMOTE_USER,
targetUserId: user.id,
targetType: 'user',
targetId: user.externalId,
metadata: {
source: 'admin_promote_script',
email,
alreadyAdmin,
previousRole: user.role,
nextRole: UserRole.admin,
},
});
} catch (err) {
const message = err instanceof AuditPersistError ? err.message : 'audit failed';
console.error(
JSON.stringify(
{
ok: false,
email,
externalId: user.externalId,
role: UserRole.admin,
promoted: !alreadyAdmin,
alreadyAdmin,
auditRecorded: false,
error: 'AUDIT_FAILED',
message,
},
null,
2,
),
);
await prisma.$disconnect();
process.exit(1);
}
console.log(
JSON.stringify(
{
ok: true,
email,
externalId: user.externalId,
role: UserRole.admin,
promoted: !alreadyAdmin,
alreadyAdmin,
auditRecorded: true,
sessionsRevoked: false,
message: alreadyAdmin
? 'User was already admin; no data changes besides audit log.'
: 'User role updated to admin. Other user data unchanged; sessions not revoked.',
},
null,
2,
),
);
await prisma.$disconnect();