- Widen users.role enum to include 'player'; make password_hash nullable; add email/email_verified/status/last_login_ip; pin username _ci collation. - POST /auth/register (honeypot + registerLimiter + botScore, reserved-name blocklist, duplicate->409, auto-login). player_registration setting gates it. - SSO auto-provision in finishLogin (setting-gated); return/portal-aware SSO redirects for the player portal; status refusal on login + requireAuth. - New /player self-service group (account, change username/password, TOTP, identities), reusing account.controller; accountChangeLimiter. - Admin: 'player' role + status/email on user create/update, role/status audit, player_registration enum validation, derived public registration flags. - usernamePolicy module (reserved, sanitize, derive, dedup) + unit tests; extend SSO callback tests. 133 server tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019rao86n5cXpwAyjdBFEshV
82 lines
2.2 KiB
JavaScript
82 lines
2.2 KiB
JavaScript
const settingsDb = require('./settings.db')
|
|
|
|
// Keys safe to expose on the public site.
|
|
const PUBLIC_KEYS = [
|
|
'site_mode',
|
|
'maintenance_message',
|
|
'status_message',
|
|
'homepage_teaser',
|
|
'contact_email',
|
|
'site_title',
|
|
'hero_layout', // portal hero composition (JSON). Draft key stays admin-only.
|
|
]
|
|
|
|
// Player self-registration mode. Stored under the 'player_registration' key.
|
|
// NOTE: the raw value is never exposed publicly — getPublic() derives boolean
|
|
// availability flags from it instead (see below).
|
|
const REGISTRATION_KEY = 'player_registration'
|
|
const REGISTRATION_MODES = ['disabled', 'password', 'sso', 'both']
|
|
|
|
// Resolve the registration mode, defaulting to 'disabled' (and coercing any
|
|
// unexpected stored value back to 'disabled' so a bad row can't open sign-up).
|
|
async function getRegistrationMode() {
|
|
const value = await settingsDb.get(REGISTRATION_KEY)
|
|
return REGISTRATION_MODES.includes(value) ? value : 'disabled'
|
|
}
|
|
|
|
// Derived, public-safe availability flags for the register page.
|
|
function registrationFlags(mode) {
|
|
return {
|
|
password: mode === 'password' || mode === 'both',
|
|
sso: mode === 'sso' || mode === 'both',
|
|
}
|
|
}
|
|
|
|
async function get(key) {
|
|
return settingsDb.get(key)
|
|
}
|
|
|
|
async function set(key, value, updatedBy = null) {
|
|
return settingsDb.set(key, value, updatedBy)
|
|
}
|
|
|
|
async function setMany(obj, updatedBy = null) {
|
|
for (const [key, value] of Object.entries(obj)) {
|
|
await settingsDb.set(key, value, updatedBy)
|
|
}
|
|
}
|
|
|
|
async function getAll() {
|
|
const rows = await settingsDb.getAll()
|
|
return rows.reduce((acc, row) => {
|
|
acc[row.key] = row.value
|
|
return acc
|
|
}, {})
|
|
}
|
|
|
|
async function getPublic() {
|
|
const all = await getAll()
|
|
const out = PUBLIC_KEYS.reduce((acc, key) => {
|
|
if (all[key] !== undefined) acc[key] = all[key]
|
|
return acc
|
|
}, {})
|
|
// Derived registration availability (never the raw mode). Lets the register
|
|
// page show/hide the password form and SSO buttons.
|
|
const mode = REGISTRATION_MODES.includes(all[REGISTRATION_KEY]) ? all[REGISTRATION_KEY] : 'disabled'
|
|
out.registration = registrationFlags(mode)
|
|
return out
|
|
}
|
|
|
|
module.exports = {
|
|
get,
|
|
set,
|
|
setMany,
|
|
getAll,
|
|
getPublic,
|
|
PUBLIC_KEYS,
|
|
REGISTRATION_KEY,
|
|
REGISTRATION_MODES,
|
|
getRegistrationMode,
|
|
registrationFlags,
|
|
}
|