mirror of
https://github.com/rmoren97/mc-manager.git
synced 2026-02-10 17:40:30 -08:00
219 lines
7.5 KiB
TypeScript
219 lines
7.5 KiB
TypeScript
/**
|
||
* 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)
|
||
})
|