Files
website/server/src/model/settings/settings.model.js
Claude f8bcc7f6a3 Player accounts backend: schema, registration, self-service, SSO provision
- 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
2026-07-06 01:36:51 -05:00

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,
}