Skip to content

feat: add admin seed script with default admin account#5

Open
haphantran wants to merge 2 commits intomainfrom
feature/admin-seed
Open

feat: add admin seed script with default admin account#5
haphantran wants to merge 2 commits intomainfrom
feature/admin-seed

Conversation

@haphantran
Copy link
Copy Markdown
Collaborator

Summary

  • Created backend/prisma/seed.ts to bootstrap a default admin account on deploy
  • Handles existing users: promotes to ADMIN if they already registered
  • Handles new deploys: creates admin account from env vars
  • Deploy workflow now runs prisma migrate deploy + prisma db seed after build
  • Added ADMIN_EMAIL and ADMIN_PASSWORD env vars to docker-compose.prod.yml

Setup

Add to .env on VPS:

ADMIN_EMAIL=haphantran@gmail.com
ADMIN_PASSWORD=<password>

Test plan

  • Verify seed promotes existing user to ADMIN
  • Verify seed creates new admin if user doesn't exist
  • Verify seed skips gracefully when ADMIN_PASSWORD is not set
  • Verify deploy workflow runs seed after build

- Create prisma/seed.ts to bootstrap default admin account
- If user already exists, promotes to ADMIN role
- If user doesn't exist, creates new admin account
- Reads ADMIN_EMAIL and ADMIN_PASSWORD from env vars
- Deploy workflow now runs migrations + seed after build
- Pass ADMIN_EMAIL and ADMIN_PASSWORD through docker-compose.prod.yml
Copilot AI review requested due to automatic review settings March 30, 2026 19:29
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Prisma seed script to ensure a default admin account exists (or is promoted) during production deploys, and wires the deploy workflow to run migrations + seeding.

Changes:

  • Introduces backend/prisma/seed.ts and registers it as Prisma’s seed command.
  • Updates production deploy workflow to run prisma migrate deploy and prisma db seed after bringing containers up.
  • Adds ADMIN_EMAIL / ADMIN_PASSWORD environment variables to production compose + example env file.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
docker-compose.prod.yml Injects ADMIN_* env vars into backend container (currently with a real-email default).
backend/prisma/seed.ts Implements admin bootstrap: promote existing user or create a new ADMIN user.
backend/package.json Registers Prisma seed command (prisma.seed) for prisma db seed.
backend/.env.production.example Documents ADMIN_* variables (currently with a real email).
.github/workflows/deploy.yml Runs migrations + seed in the deployed backend container.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

LOG_LEVEL: info
JWT_SECRET: ${JWT_SECRET}
JWT_EXPIRES_IN: 7d
ADMIN_EMAIL: ${ADMIN_EMAIL:-haphantran@gmail.com}
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid hardcoding a real email address as the default ADMIN_EMAIL in production. With open registration and no email ownership verification, an attacker can pre-register that email and later be promoted to ADMIN when ops sets ADMIN_PASSWORD. Recommend removing the default entirely (require ADMIN_EMAIL explicitly), and consider failing the container start/seed when ADMIN_EMAIL is unset.

Suggested change
ADMIN_EMAIL: ${ADMIN_EMAIL:-haphantran@gmail.com}
ADMIN_EMAIL: ${ADMIN_EMAIL:?ADMIN_EMAIL must be set}

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +9
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || 'haphantran@gmail.com';
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD;

async function main() {
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ADMIN_EMAIL defaults to a real address here. For production safety, avoid a real default; require ADMIN_EMAIL to be explicitly set (and consider validating it) so you can’t accidentally create/promote an unintended account.

Suggested change
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || 'haphantran@gmail.com';
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD;
async function main() {
const ADMIN_EMAIL = process.env.ADMIN_EMAIL;
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD;
async function main() {
if (!ADMIN_EMAIL) {
console.log('ADMIN_EMAIL not set, skipping admin seed.');
return;
}

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +30
if (existing) {
if (existing.role !== 'ADMIN') {
await prisma.user.update({
where: { id: existing.id },
data: { role: 'ADMIN' },
});
console.log(`Promoted ${ADMIN_EMAIL} to ADMIN.`);
} else {
console.log(`${ADMIN_EMAIL} is already ADMIN.`);
}
return;
}
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When an existing user is found, the seed only updates role and keeps the existing password. Because registration doesn’t verify email ownership, a malicious user could register with the ADMIN_EMAIL first and then retain their password after being promoted. Consider also resetting the user’s password hash to ADMIN_PASSWORD when promoting (or require verified-email accounts before allowing promotion).

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +40
await prisma.user.create({
data: {
email: ADMIN_EMAIL.toLowerCase(),
password: hashedPassword,
role: 'ADMIN',
},
});
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New admin users created via the seed don’t get the same bootstrap data as normal registration (e.g., initializeCoreEcore). On a fresh deploy this can leave the seeded admin account missing required/expected per-user records. Consider reusing the same initialization logic used in AuthService.register for newly created seeded users.

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +33
const hashedPassword = await bcrypt.hash(ADMIN_PASSWORD, 12);

Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Password hashing cost differs from the rest of the codebase (seed uses 12 rounds, AuthService/AdminService use SALT_ROUNDS=10). Consider reusing the shared constant/config for salt rounds so hashes are consistent and tuning is centralized.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +4
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs';

const prisma = new PrismaClient();
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seed script lives under prisma/, but backend tsconfig only includes src//* and shared//; CI typecheck won’t catch type errors in this file. Consider adding prisma/**/.ts to the backend tsconfig include (or adding a dedicated typecheck step for the seed) so deploy-time scripts are typechecked.

Copilot uses AI. Check for mistakes.
JWT_EXPIRES_IN=7d

# Default admin account (created on first seed run)
ADMIN_EMAIL=haphantran@gmail.com
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The production env example includes a specific personal email address. Prefer using a placeholder value (e.g., admin@example.com) so documentation/examples don’t embed personal data and don’t encourage copying a potentially unsafe default into production.

Suggested change
ADMIN_EMAIL=haphantran@gmail.com
ADMIN_EMAIL=admin@example.com

Copilot uses AI. Check for mistakes.
Comment on lines 29 to 31
docker compose -f docker-compose.prod.yml exec -T backend npx prisma migrate deploy
docker compose -f docker-compose.prod.yml exec -T backend npx prisma db seed
docker image prune -f
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy workflow runs prisma migrate deploy inside the backend container after docker compose up -d --build, but the backend image already runs npx prisma migrate deploy on container start (see backend/Dockerfile CMD). This redundancy can also introduce a race where migrations run concurrently. Recommend running migrations in exactly one place (either keep it in container startup and drop this step, or remove it from CMD and keep it here) and ensure seed runs after migrations deterministically.

Suggested change
docker compose -f docker-compose.prod.yml exec -T backend npx prisma migrate deploy
docker compose -f docker-compose.prod.yml exec -T backend npx prisma db seed
docker image prune -f
docker compose -f docker-compose.prod.yml exec -T backend npx prisma db seed
docker image prune -f

Copilot uses AI. Check for mistakes.
- Deploy workflow passes ADMIN_EMAIL and ADMIN_PASSWORD from GitHub secrets via SSH
- Remove hardcoded default email from seed script and docker-compose
- Seed skips if either ADMIN_EMAIL or ADMIN_PASSWORD is not set
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants