feat(auth): sign in with Steam (MODULE_API 1.17.0)
Steam is a built-in provider beside Google and Discord (STEAM_SIGNIN.md, D392-D396). OpenID 2.0: the assertion is verified server to server with check_authentication, return_to must match byte for byte, and the claimed id must be a 17-digit SteamID64. A Steam Web API key is required to enable it (D394); persona name and avatar are stored on the identity and refreshed at each sign-in, and a failed lookup never blocks one. Steam never signs anybody up (D395), on the web or the mobile bridge. Several Steam accounts may be linked; DELETE /auth/me/account/identities/:provider takes ?id= to unlink one. MODULE_API 1.17.0: ctx.identities.list, api.onIdentityLinking (refuse-only, fails closed, module text never shown) and api.onIdentityLinked. No unlink hook (D393). Schema additive; OpenAPI, routes and engagement manifests regenerated with no modules installed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -339,20 +339,36 @@ async function totpDisable(req, res) {
|
||||
async function listIdentities(req, res) {
|
||||
try {
|
||||
const rows = await userIdentities.listForUser(req.user.id)
|
||||
return res.json(rows.map((r) => ({ provider: r.provider, email: r.email, linked_at: r.created_at })))
|
||||
// `id` lets one identity be unlinked when a provider has several (a player
|
||||
// may link more than one Steam account). `subject` is the user's OWN external
|
||||
// id — for Steam, the SteamID64 shown beside the persona name (D394).
|
||||
return res.json(
|
||||
rows.map((r) => ({
|
||||
id: r.id,
|
||||
provider: r.provider,
|
||||
subject: r.subject,
|
||||
email: r.email,
|
||||
display_name: r.display_name || null,
|
||||
avatar_url: r.avatar_url || null,
|
||||
linked_at: r.created_at,
|
||||
})),
|
||||
)
|
||||
} catch (err) {
|
||||
log.error('listIdentities', err)
|
||||
return res.status(500).json({ message: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a linked SSO identity from the current user's account.
|
||||
// Remove a linked SSO identity from the current user's account: every identity
|
||||
// of that provider, or with `?id=` the one identity named. Only the sign-in goes —
|
||||
// a module's own link made from it (Rust's, D392) is the module's to remove (D393).
|
||||
async function unlinkIdentity(req, res) {
|
||||
const { provider } = req.params
|
||||
const identityId = req.query.id != null ? Number(req.query.id) : null
|
||||
try {
|
||||
const removed = await userIdentities.unlink(req.user.id, provider)
|
||||
const removed = await userIdentities.unlink(req.user.id, provider, identityId)
|
||||
if (!removed) return res.status(404).json({ message: 'No linked account for that provider.' })
|
||||
await activity.log({ req, action: 'auth.sso.unlink', detail: { provider } })
|
||||
await activity.log({ req, action: 'auth.sso.unlink', detail: { provider, ...(identityId != null ? { identityId } : {}) } })
|
||||
log.info('sso identity unlinked', { provider, id: req.user.id })
|
||||
return res.json({ unlinked: true })
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user