diff --git a/src/oidcserver.js b/src/oidcserver.js index 148da6ddf..42f9f04aa 100644 --- a/src/oidcserver.js +++ b/src/oidcserver.js @@ -3,7 +3,7 @@ exports = module.exports = { start, stop, - revokeByUserId, + revokeByUsername, consumeAuthCode, cleanupExpired, @@ -96,7 +96,11 @@ class StorageAdapter { if (this.name === 'AccessToken' && (payload.clientId === oidcClients.ID_WEBADMIN || payload.clientId === oidcClients.ID_DEVELOPMENT)) { const expires = Date.now() + constants.DEFAULT_TOKEN_EXPIRATION_MSECS; - const [error] = await safe(tokens.add({ clientId: payload.clientId, identifier: payload.accountId, expires, accessToken: id, allowedIpRanges: '' })); + // oidc uses the username as accountId but accesstoken identifiers are userIds + const user = await users.getByUsername(payload.accountId); + if (!user) throw new Error(`user for username ${payload.accountId} not found`); + + const [error] = await safe(tokens.add({ clientId: payload.clientId, identifier: user.id, expires, accessToken: id, allowedIpRanges: '' })); if (error) { console.log('Error adding access token', error); throw error; @@ -150,10 +154,14 @@ class StorageAdapter { // dashboard AccessToken are in the db. the app tokens are in the json files const [error, result] = await safe(tokens.getByAccessToken(id)); if (!error && result) { - return { - accountId: result.identifier, - clientId: result.clientId - }; + // translate from userId in the token to username for oidc + const user = await users.get(result.identifier); + if (user) { + return { + accountId: user.username, + clientId: result.clientId + }; + } } } else if (this.name === 'Session') { const data = await StorageAdapter.getData(this.name); @@ -162,7 +170,7 @@ class StorageAdapter { if (session.payload.accountId) { // check if the session user still exists and is active - const user = await users.get(session.payload.accountId); + const user = await users.getByUsername(session.payload.accountId); if (!user || !user.active) return null; } @@ -217,14 +225,14 @@ class StorageAdapter { } // Session, Grant and Token management. This is based on the same storage as the below CloudronAdapter -async function revokeByUserId(userId) { - assert.strictEqual(typeof userId, 'string'); +async function revokeByUsername(username) { + assert.strictEqual(typeof username, 'string'); const types = [ 'Session', 'Grant', 'AuthorizationCode', 'AccessToken' ]; for (const type of types) { await StorageAdapter.updateData(type, (data) => { for (const id in data) { - if (data[id].payload?.accountId === userId) delete data[id]; + if (data[id].payload?.accountId === username) delete data[id]; } }); } @@ -321,7 +329,7 @@ async function renderInteractionPage(req, res, next) { // check if user has access to the app if client refers to an app if (app) { - const user = await users.get(session.accountId); + const user = await users.getByUsername(session.accountId); options.NAME = app.label || app.fqdn; options.ICON_URL = app.iconUrl; @@ -356,13 +364,13 @@ async function interactionLogin(req, res, next) { const token = await tokens.getByAccessToken(req.body.autoLoginToken); if (!token) return next(new HttpError(401, 'No such token')); - const user = await users.get(token.identifier); + const user = await users.getByUsername(token.identifier); if (!user) return next(new HttpError(401,'User not found')); if (!user.active) return next(new HttpError(401,'User not active')); const result = { login: { - accountId: user.id, + accountId: user.username, }, }; @@ -391,7 +399,7 @@ async function interactionLogin(req, res, next) { // this is saved as part of interaction.lastSubmission const result = { login: { - accountId: user.id, + accountId: user.username, }, ghost: !!user.ghost }; @@ -411,7 +419,7 @@ async function interactionConfirm(req, res, next) { const client = await oidcClients.get(params.client_id); if (!client) return next(new Error('Client not found')); - const user = await users.get(accountId); + const user = await users.getByUsername(accountId); if (!user) return next(new Error('User not found')); user.ghost = !!lastSubmission?.ghost; // restore ghost flag. lastSubmission can be empty if login interaction was skipped (already logged in) @@ -476,8 +484,8 @@ async function interactionAbort(req, res, next) { if (error) return next(error); } -async function getClaims(userId/*, use, scope*/) { - const [error, user] = await safe(users.get(userId)); +async function getClaims(username/*, use, scope*/) { + const [error, user] = await safe(users.getByUsername(username)); if (error) return { error: 'user not found' }; const [groupsError, allGroups] = await safe(groups.listWithMembers()); diff --git a/src/routes/profile.js b/src/routes/profile.js index 9bae77590..bdfdce214 100644 --- a/src/routes/profile.js +++ b/src/routes/profile.js @@ -236,7 +236,7 @@ async function setNotificationConfig(req, res, next) { async function destroyUserSession(req, res, next) { assert.strictEqual(typeof req.user, 'object'); - const [error] = await safe(oidcServer.revokeByUserId(req.user.id)); + const [error] = await safe(oidcServer.revokeByUsername(req.user.username)); if (error) return next(BoxError.toHttpError(error)); await safe(tokens.del(req.token.id)); diff --git a/src/user-directory.js b/src/user-directory.js index d4098a11e..b3aa129ea 100644 --- a/src/user-directory.js +++ b/src/user-directory.js @@ -43,7 +43,7 @@ async function setProfileConfig(profileConfig, options, auditSource) { if (options.persistUserIdSessions === user.id) continue; // do not logout the API caller await tokens.delByUserIdAndType(user.id, oidcClients.ID_WEBADMIN); - await oidcServer.revokeByUserId(user.id); + await oidcServer.revokeByUsername(user.username); } } }