From 7cb886d220641e8c3a67c6110d36b83ad108d75e Mon Sep 17 00:00:00 2001 From: rob Date: Tue, 5 Jul 2022 16:58:27 -0400 Subject: [PATCH] OAuth2 access token cleanup --- app/models/oauth2-access-token.js | 17 +++++++-- app/services/oauth2.js | 61 ++++++++++++++++++++++--------- 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/app/models/oauth2-access-token.js b/app/models/oauth2-access-token.js index b0dad96..9fb5819 100644 --- a/app/models/oauth2-access-token.js +++ b/app/models/oauth2-access-token.js @@ -8,11 +8,20 @@ const mongoose = require('mongoose'); const Schema = mongoose.Schema; -const OAuth2AccessTokenSchema = new Schema({ - token: { type: String, required: true, unique: true, index: 1 }, +const OAuth2TokenSchema = new Schema({ + type: { type: String, enum: ['access','refresh'], required: true }, + token: { type: String, required: true }, user: { type: Schema.ObjectId, required: true, index: 1, ref: 'User' }, client: { type: Schema.ObjectId, required: true, index: 1, ref: 'OAuth2Client' }, scope: { type: [String], required: true }, -}); +}); -module.exports = mongoose.model('OAuth2AccessToken', OAuth2AccessTokenSchema); \ No newline at end of file +OAuth2TokenSchema.index({ + type: 1, + token: 1, +}, { + unique: true, + name: 'oauth2_token_unique', +}); + +module.exports = mongoose.model('OAuth2Token', OAuth2TokenSchema); \ No newline at end of file diff --git a/app/services/oauth2.js b/app/services/oauth2.js index 6b093e3..e75c310 100644 --- a/app/services/oauth2.js +++ b/app/services/oauth2.js @@ -8,7 +8,7 @@ const mongoose = require('mongoose'); const OAuth2Client = mongoose.model('OAuth2Client'); const OAuth2AuthorizationCode = mongoose.model('OAuth2AuthorizationCode'); -const OAuth2AccessToken = mongoose.model('OAuth2AccessToken'); +const OAuth2Token = mongoose.model('OAuth2Token'); const uuidv4 = require('uuid').v4; const striptags = require('striptags'); @@ -28,7 +28,7 @@ class OAuth2Service extends SiteService { constructor (dtp) { super(dtp, module.exports); - this.populateOAuth2AccessToken = [ + this.populateOAuth2Token = [ { path: 'user', select: 'username username_lc displayName picture', @@ -47,7 +47,7 @@ class OAuth2Service extends SiteService { this.log.info('registering OAuth2 action handlers'); this.server.grant(oauth2orize.grant.code(this.processGrant.bind(this))); - this.server.exchange(oauth2orize.exchange.code(this.processExchange.bind(this))); + this.server.exchange(oauth2orize.exchange.code(this.processExchangeCode.bind(this))); this.log.info('registering OAuth2 serialization routines'); this.server.serializeClient(this.serializeClient.bind(this)); @@ -168,7 +168,40 @@ class OAuth2Service extends SiteService { } } - async processExchange (client, code, redirectUri, done) { + async issueTokens (authCode) { + const response = { + params: { + coreUserId: authCode.user._id, + username: authCode.user.username, + username_lc: authCode.user.username_lc, + displayName: authCode.user.displayName, + bio: authCode.user.bio, + permissions: authCode.user.permissions, + flags: authCode.user.flags, + }, + accessToken: generatePassword(256, false), + refreshToken: generatePassword(256, false), + }; + await Promise.all([ + OAuth2Token.create({ + type: 'access', + token: response.accessToken, + user: authCode.user._id, + client: authCode.client._id, + scope: authCode.scope, + }), + OAuth2Token.create({ + type: 'refresh', + token: response.refreshToken, + user: authCode.user._id, + client: authCode.client._id, + scope: authCode.scope, + }), + ]); + return response; + } + + async processExchangeCode (client, code, redirectUri, done) { try { const ac = await OAuth2AuthorizationCode .findOne({ code }) @@ -178,7 +211,7 @@ class OAuth2Service extends SiteService { }, { path: 'user', - select: 'username username_lc displayName picture', + select: 'username username_lc displayName picture bio permissions flags', }, ]); @@ -190,18 +223,10 @@ class OAuth2Service extends SiteService { this.log.alert('OAuth2 redirect mismatch', { provided: redirectUri, onfile: ac.redirectUri }); return done(null, false); } - - var token = uuidv4(); - var at = new OAuth2AccessToken({ - token, - user: ac.user._id, - client: ac.client._id, - scope: ac.scope, - }); - await at.save(); + const response = await this.issueTokens(ac); this.log.info('OAuth2 grant exchanged for token', { clientID: client._id }); - return done(null, token); + return done(null, response.accessToken, response.refreshToken, response.params); } catch (error) { this.log.error('failed to process OAuth2 exchange', { error }); return done(error); @@ -306,9 +331,9 @@ class OAuth2Service extends SiteService { } async getAccessToken (accessToken) { - const token = await OAuth2AccessToken - .findOne({ token: accessToken }) - .populate(this.populateOAuth2AccessToken) + const token = await OAuth2Token + .findOne({ type: 'access', token: accessToken }) + .populate(this.populateOAuth2Token) .lean(); return token; }