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
63 lines
2.4 KiB
JavaScript
63 lines
2.4 KiB
JavaScript
const { query } = require('../../utils/db')
|
|
|
|
// Find the identity row for an external (provider, subject) pair. This is the
|
|
// link-only login lookup: no row → no account → login refused.
|
|
async function findByProviderSubject(provider, subject) {
|
|
const rows = await query(
|
|
'SELECT * FROM user_identities WHERE provider = ? AND subject = ? LIMIT 1',
|
|
[provider, subject],
|
|
)
|
|
return rows[0] || null
|
|
}
|
|
|
|
// All identities linked to a given internal user (for the Account page).
|
|
async function listForUser(userId) {
|
|
return query(
|
|
`SELECT id, provider, subject, email, display_name, avatar_url, created_at
|
|
FROM user_identities WHERE user_id = ? ORDER BY provider, created_at`,
|
|
[userId],
|
|
)
|
|
}
|
|
|
|
async function insert({ userId, provider, subject, email = null, displayName = null, avatarUrl = null }) {
|
|
const res = await query(
|
|
`INSERT INTO user_identities (user_id, provider, subject, email, display_name, avatar_url)
|
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
[userId, provider, subject, email, displayName, avatarUrl],
|
|
)
|
|
return res.insertId
|
|
}
|
|
|
|
// Refresh the name and avatar a provider reported at sign-in (Steam, D394). Only
|
|
// what the provider sent is written: a lookup that failed leaves the last good
|
|
// values in place rather than blanking them.
|
|
async function updateProfile(id, { displayName, avatarUrl }) {
|
|
const sets = []
|
|
const vals = []
|
|
if (displayName) { sets.push('display_name = ?'); vals.push(displayName) }
|
|
if (avatarUrl) { sets.push('avatar_url = ?'); vals.push(avatarUrl) }
|
|
if (!sets.length) return 0
|
|
const res = await query(`UPDATE user_identities SET ${sets.join(', ')} WHERE id = ?`, [...vals, id])
|
|
return Number(res.affectedRows || 0)
|
|
}
|
|
|
|
// Remove a user's link to a provider — every identity of it, or, given an
|
|
// identity id, that one only (a user may hold several Steam accounts). Returns
|
|
// rows deleted.
|
|
async function deleteForUserProvider(userId, provider, identityId = null) {
|
|
if (identityId != null) {
|
|
const res = await query(
|
|
'DELETE FROM user_identities WHERE user_id = ? AND provider = ? AND id = ?',
|
|
[userId, provider, identityId],
|
|
)
|
|
return Number(res.affectedRows || 0)
|
|
}
|
|
const res = await query(
|
|
'DELETE FROM user_identities WHERE user_id = ? AND provider = ?',
|
|
[userId, provider],
|
|
)
|
|
return Number(res.affectedRows || 0)
|
|
}
|
|
|
|
module.exports = { findByProviderSubject, listForUser, insert, updateProfile, deleteForUserProvider }
|