Follow-ups from live testing: - Game-account creation is now an admin Settings control (disabled / website / hybrid / game) instead of a hidden on/off flag. The site offers creation for website+hybrid; help text notes the shard's SignupMode (Bridge.cfg) has the final say. game_account_signup setting widened to a 4-value enum + validated on save. - Invites: the accept link is ALWAYS returned and shown with a Copy button, and a "Email the invitation" toggle lets an admin create a link-only invite (no email) or email it. Backend takes sendEmail (default true) and always returns acceptUrl. - Staff can create a game account from their own /admin/characters page too (POST /admin/shard/account → the shared createGameAccount controller), so the form is reachable in both the player and admin portals. Note: the admin Houses view (/admin/houses) already worked; the earlier failure was a stale Vite HMR state for the new route (needs a hard refresh). Client build clean; server routes load; swagger regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
111 lines
3.7 KiB
JavaScript
111 lines
3.7 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). The admin picks who mints game accounts:
|
|
// disabled — the site never offers game-account creation (link-only).
|
|
// website — the site is the authority (offer creation; pair with the shard in
|
|
// website mode + AutoCreateAccounts=false).
|
|
// hybrid — either side may create (the site offers creation).
|
|
// game — the game server is the authority; the site does NOT offer creation.
|
|
// The site OFFERS creation only for 'website'/'hybrid'; the shard's own SignupMode
|
|
// (Bridge.cfg) still has the final say and may 403 a call regardless.
|
|
const GAME_SIGNUP_KEY = 'game_account_signup'
|
|
const GAME_SIGNUP_MODES = ['disabled', 'website', 'hybrid', 'game']
|
|
const GAME_SIGNUP_OFFER = ['website', 'hybrid']
|
|
|
|
async function getGameSignupMode() {
|
|
const v = await settingsDb.get(GAME_SIGNUP_KEY)
|
|
return GAME_SIGNUP_MODES.includes(v) ? v : 'disabled'
|
|
}
|
|
|
|
async function isGameAccountSignupEnabled() {
|
|
return GAME_SIGNUP_OFFER.includes(await getGameSignupMode())
|
|
}
|
|
|
|
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.
|
|
const gsMode = GAME_SIGNUP_MODES.includes(all[GAME_SIGNUP_KEY]) ? all[GAME_SIGNUP_KEY] : 'disabled'
|
|
out.gameAccountSignup = GAME_SIGNUP_OFFER.includes(gsMode)
|
|
return out
|
|
}
|
|
|
|
module.exports = {
|
|
get,
|
|
set,
|
|
setMany,
|
|
getAll,
|
|
getPublic,
|
|
PUBLIC_KEYS,
|
|
REGISTRATION_KEY,
|
|
REGISTRATION_MODES,
|
|
getRegistrationMode,
|
|
registrationFlags,
|
|
GAME_SIGNUP_KEY,
|
|
GAME_SIGNUP_MODES,
|
|
getGameSignupMode,
|
|
isGameAccountSignupEnabled,
|
|
}
|