2026-02-07 12:20:12 -08:00

219 lines
7.5 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Seed script — creates the initial Admin role and admin user.
*
* Usage:
* npx tsx scripts/seed.ts
*
* Requires a valid MONGODB_URI in .env (or .env.local).
*/
import mongoose from 'mongoose'
import bcrypt from 'bcryptjs'
import * as readline from 'readline'
import * as dotenv from 'dotenv'
// Load environment variables
dotenv.config({ path: '.env.local' })
dotenv.config({ path: '.env' })
const MONGODB_URI = process.env.MONGODB_URI
if (!MONGODB_URI) {
console.error('❌ MONGODB_URI is not defined. Create a .env or .env.local file first.')
process.exit(1)
}
// ─── Inline Schemas (avoid importing from src/ which needs TS paths) ─────
const permissionSchema = new mongoose.Schema(
{
resource: { type: String, required: true },
actions: [{ type: String, required: true }],
},
{ _id: false }
)
const roleSchema = new mongoose.Schema(
{
name: { type: String, required: true, unique: true, trim: true },
permissions: [permissionSchema],
description: { type: String, default: '' },
isDefault: { type: Boolean, default: false },
},
{ timestamps: true }
)
const userSchema = new mongoose.Schema(
{
username: { type: String, required: true, unique: true, trim: true },
email: { type: String, required: true, unique: true, trim: true, lowercase: true },
passwordHash: { type: String, required: true },
roles: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Role' }],
twoFactorCode: { type: String, default: null },
twoFactorExpiry: { type: Date, default: null },
loginAttempts: { type: Number, default: 0 },
lockUntil: { type: Date, default: null },
status: { type: String, enum: ['active', 'inactive', 'locked'], default: 'active' },
lastLogin: { type: Date, default: null },
},
{ timestamps: true }
)
const Role = mongoose.models.Role || mongoose.model('Role', roleSchema)
const User = mongoose.models.User || mongoose.model('User', userSchema)
// ─── Helpers ─────────────────────────────────────────────────────
function prompt(question: string): Promise<string> {
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
return new Promise(resolve => {
rl.question(question, answer => {
rl.close()
resolve(answer.trim())
})
})
}
// ─── Admin Permissions (wildcard on every resource) ──────────────
const ADMIN_PERMISSIONS = [
{ resource: '*', actions: ['*'] },
]
// ─── Main ────────────────────────────────────────────────────────
async function main() {
console.log('🌱 MC-Manager Seed Script\n')
await mongoose.connect(MONGODB_URI!)
console.log('✅ Connected to MongoDB\n')
// ── 1. Create or update Admin Role ──────────────────────────────
let adminRole = await Role.findOne({ name: 'Admin' })
if (adminRole) {
console.log(' Admin role already exists — updating permissions...')
adminRole.permissions = ADMIN_PERMISSIONS
adminRole.description = 'Full system administrator with all permissions'
await adminRole.save()
} else {
adminRole = await Role.create({
name: 'Admin',
permissions: ADMIN_PERMISSIONS,
description: 'Full system administrator with all permissions',
isDefault: false,
})
console.log('✅ Admin role created')
}
// ── 2. Create default Viewer role ───────────────────────────────
let viewerRole = await Role.findOne({ name: 'Viewer' })
if (!viewerRole) {
viewerRole = await Role.create({
name: 'Viewer',
permissions: [
{ resource: 'servers', actions: ['view'] },
{ resource: 'backups', actions: ['view'] },
{ resource: 'plugins', actions: ['view'] },
{ resource: 'mods', actions: ['view'] },
{ resource: 'players', actions: ['view'] },
],
description: 'Read-only access to servers and related resources',
isDefault: true,
})
console.log('✅ Viewer role created')
} else {
console.log(' Viewer role already exists — skipped')
}
// ── 3. Create Operator role ─────────────────────────────────────
let operatorRole = await Role.findOne({ name: 'Operator' })
if (!operatorRole) {
operatorRole = await Role.create({
name: 'Operator',
permissions: [
{ resource: 'servers', actions: ['view', 'start', 'stop', 'restart', 'console'] },
{ resource: 'backups', actions: ['view', 'create'] },
{ resource: 'plugins', actions: ['view', 'install', 'remove', 'toggle'] },
{ resource: 'mods', actions: ['view', 'install', 'remove', 'toggle'] },
{ resource: 'players', actions: ['view', 'whitelist', 'op', 'ban'] },
],
description: 'Can operate servers but cannot create/delete them or manage users',
isDefault: false,
})
console.log('✅ Operator role created')
} else {
console.log(' Operator role already exists — skipped')
}
// ── 4. Create Admin User ────────────────────────────────────────
const existingAdmin = await User.findOne({ username: 'admin' })
if (existingAdmin) {
console.log('\n Admin user already exists.')
const reset = await prompt('Reset admin password? (y/N): ')
if (reset.toLowerCase() === 'y') {
const newPassword = await prompt('New password (min 8 chars): ')
if (newPassword.length < 8) {
console.error('❌ Password must be at least 8 characters.')
await mongoose.disconnect()
process.exit(1)
}
existingAdmin.passwordHash = await bcrypt.hash(newPassword, 12)
existingAdmin.status = 'active'
existingAdmin.loginAttempts = 0
existingAdmin.lockUntil = null
// Ensure admin has the Admin role
if (!existingAdmin.roles.some((r: mongoose.Types.ObjectId) => r.equals(adminRole._id))) {
existingAdmin.roles.push(adminRole._id)
}
await existingAdmin.save()
console.log('✅ Admin password reset successfully')
}
} else {
console.log('\n📝 Create initial admin user:\n')
const email = await prompt('Admin email: ')
if (!email || !email.includes('@')) {
console.error('❌ A valid email is required.')
await mongoose.disconnect()
process.exit(1)
}
const password = await prompt('Admin password (min 8 chars): ')
if (password.length < 8) {
console.error('❌ Password must be at least 8 characters.')
await mongoose.disconnect()
process.exit(1)
}
const passwordHash = await bcrypt.hash(password, 12)
await User.create({
username: 'admin',
email,
passwordHash,
roles: [adminRole._id],
status: 'active',
})
console.log('✅ Admin user created (username: admin)')
}
// ── Done ────────────────────────────────────────────────────────
console.log('\n🎉 Seed complete! You can now log in at http://localhost:3000')
await mongoose.disconnect()
process.exit(0)
}
main().catch(err => {
console.error('❌ Seed failed:', err)
process.exit(1)
})