Phase 6: the UI for the Phase 5 provisioning backend. - CreateGameAccountForm: reusable game-account form (own username + password), mapping the sidecar errors (409/429/403/503) to friendly messages. Wired into GameAccounts (self-serve) — shown alongside the [link flow when the game_account_signup flag is on (exposed via public settings), so a registered player can create + link a game account from their portal. - Admin Invites view (/admin/invites, admin-only): send an invite at a chosen access level, list invites with status, revoke pending ones. When email isn't configured the create response's accept link is surfaced to copy manually. - Public accept page (/invite/:token): validates the invite, sets username + password (email + role pre-assigned), creates the account at that role and logs in; for a player invite it then offers the built-in "create game account" step before the portal. Honeypot-guarded like registration. - Admin unlink wired into UserDetail via GameAccounts (per-account Unlink button, confirm + reconcile). - Backend: expose gameAccountSignup availability in public settings. Client build clean; server 193/193. Refs .plans/protocol2-integration.md (Phase 6). Completes the Protocol 2.0/2.1 integration. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
94 lines
2.8 KiB
JavaScript
94 lines
2.8 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',
|
|
}
|
|
}
|
|
|
|
// Game-account signup (Protocol 2.0 hybrid mode). Off unless an admin opts in;
|
|
// the shard's own signup mode still has the final say when we call the sidecar.
|
|
const GAME_SIGNUP_KEY = 'game_account_signup'
|
|
async function isGameAccountSignupEnabled() {
|
|
return (await settingsDb.get(GAME_SIGNUP_KEY)) === 'enabled'
|
|
}
|
|
|
|
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)
|
|
// Whether the site offers game-account creation (the shard's own mode still has
|
|
// the final say when the call is made). Lets the portal show/hide the form.
|
|
out.gameAccountSignup = all[GAME_SIGNUP_KEY] === 'enabled'
|
|
return out
|
|
}
|
|
|
|
module.exports = {
|
|
get,
|
|
set,
|
|
setMany,
|
|
getAll,
|
|
getPublic,
|
|
PUBLIC_KEYS,
|
|
REGISTRATION_KEY,
|
|
REGISTRATION_MODES,
|
|
getRegistrationMode,
|
|
registrationFlags,
|
|
GAME_SIGNUP_KEY,
|
|
isGameAccountSignupEnabled,
|
|
}
|