Cache user token

This commit is contained in:
Chocobozzz 2018-09-20 11:31:48 +02:00
parent 91411dba92
commit f201a74992
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
7 changed files with 79 additions and 19 deletions

View File

@ -37,6 +37,7 @@ import { UserModel } from '../../../models/account/user'
import { OAuthTokenModel } from '../../../models/oauth/oauth-token' import { OAuthTokenModel } from '../../../models/oauth/oauth-token'
import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
import { meRouter } from './me' import { meRouter } from './me'
import { deleteUserToken } from '../../../lib/oauth-model'
const auditLogger = auditLoggerFactory('users') const auditLogger = auditLoggerFactory('users')
@ -267,7 +268,7 @@ async function updateUser (req: express.Request, res: express.Response, next: ex
const user = await userToUpdate.save() const user = await userToUpdate.save()
// Destroy user token to refresh rights // Destroy user token to refresh rights
if (roleChanged) await OAuthTokenModel.deleteUserToken(userToUpdate.id) if (roleChanged) await deleteUserToken(userToUpdate.id)
auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)
@ -330,7 +331,7 @@ async function changeUserBlock (res: express.Response, user: UserModel, block: b
user.blockedReason = reason || null user.blockedReason = reason || null
await sequelizeTypescript.transaction(async t => { await sequelizeTypescript.transaction(async t => {
await OAuthTokenModel.deleteUserToken(user.id, t) await deleteUserToken(user.id, t)
await user.save({ transaction: t }) await user.save({ transaction: t })
}) })

View File

@ -353,7 +353,7 @@ async function updateMyAvatar (req: express.Request, res: express.Response, next
const userAccount = await AccountModel.load(user.Account.id) const userAccount = await AccountModel.load(user.Account.id)
const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount.Actor, userAccount) const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount)
auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)

View File

@ -56,7 +56,7 @@ videoChannelRouter.post('/:nameWithHost/avatar/pick',
// Check the rights // Check the rights
asyncMiddleware(videoChannelsUpdateValidator), asyncMiddleware(videoChannelsUpdateValidator),
updateAvatarValidator, updateAvatarValidator,
asyncMiddleware(updateVideoChannelAvatar) asyncRetryTransactionMiddleware(updateVideoChannelAvatar)
) )
videoChannelRouter.put('/:nameWithHost', videoChannelRouter.put('/:nameWithHost',
@ -107,13 +107,9 @@ async function updateVideoChannelAvatar (req: express.Request, res: express.Resp
const videoChannel = res.locals.videoChannel as VideoChannelModel const videoChannel = res.locals.videoChannel as VideoChannelModel
const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON()) const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON())
const avatar = await updateActorAvatarFile(avatarPhysicalFile, videoChannel.Actor, videoChannel) const avatar = await updateActorAvatarFile(avatarPhysicalFile, videoChannel)
auditLogger.update( auditLogger.update(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannel.toFormattedJSON()), oldVideoChannelAuditKeys)
getAuditIdFromRes(res),
new VideoChannelAuditView(videoChannel.toFormattedJSON()),
oldVideoChannelAuditKeys
)
return res return res
.json({ .json({

View File

@ -3,23 +3,18 @@ import { sendUpdateActor } from './activitypub/send'
import { AVATARS_SIZE, CONFIG, sequelizeTypescript } from '../initializers' import { AVATARS_SIZE, CONFIG, sequelizeTypescript } from '../initializers'
import { updateActorAvatarInstance } from './activitypub' import { updateActorAvatarInstance } from './activitypub'
import { processImage } from '../helpers/image-utils' import { processImage } from '../helpers/image-utils'
import { ActorModel } from '../models/activitypub/actor'
import { AccountModel } from '../models/account/account' import { AccountModel } from '../models/account/account'
import { VideoChannelModel } from '../models/video/video-channel' import { VideoChannelModel } from '../models/video/video-channel'
import { extname, join } from 'path' import { extname, join } from 'path'
async function updateActorAvatarFile ( async function updateActorAvatarFile (avatarPhysicalFile: Express.Multer.File, accountOrChannel: AccountModel | VideoChannelModel) {
avatarPhysicalFile: Express.Multer.File,
actor: ActorModel,
accountOrChannel: AccountModel | VideoChannelModel
) {
const extension = extname(avatarPhysicalFile.filename) const extension = extname(avatarPhysicalFile.filename)
const avatarName = actor.uuid + extension const avatarName = accountOrChannel.Actor.uuid + extension
const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName)
await processImage(avatarPhysicalFile, destination, AVATARS_SIZE) await processImage(avatarPhysicalFile, destination, AVATARS_SIZE)
return sequelizeTypescript.transaction(async t => { return sequelizeTypescript.transaction(async t => {
const updatedActor = await updateActorAvatarInstance(actor, avatarName, t) const updatedActor = await updateActorAvatarInstance(accountOrChannel.Actor, avatarName, t)
await updatedActor.save({ transaction: t }) await updatedActor.save({ transaction: t })
await sendUpdateActor(accountOrChannel, t) await sendUpdateActor(accountOrChannel, t)

View File

@ -4,15 +4,50 @@ import { UserModel } from '../models/account/user'
import { OAuthClientModel } from '../models/oauth/oauth-client' import { OAuthClientModel } from '../models/oauth/oauth-client'
import { OAuthTokenModel } from '../models/oauth/oauth-token' import { OAuthTokenModel } from '../models/oauth/oauth-token'
import { CONFIG } from '../initializers/constants' import { CONFIG } from '../initializers/constants'
import { Transaction } from 'sequelize'
type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date }
const accessTokenCache: { [ accessToken: string ]: OAuthTokenModel } = {}
const userHavingToken: { [ userId: number ]: string } = {}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function deleteUserToken (userId: number, t?: Transaction) {
clearCacheByUserId(userId)
return OAuthTokenModel.deleteUserToken(userId, t)
}
function clearCacheByUserId (userId: number) {
const token = userHavingToken[userId]
if (token !== undefined) {
accessTokenCache[ token ] = undefined
userHavingToken[ userId ] = undefined
}
}
function clearCacheByToken (token: string) {
const tokenModel = accessTokenCache[ token ]
if (tokenModel !== undefined) {
userHavingToken[tokenModel.userId] = undefined
accessTokenCache[ token ] = undefined
}
}
function getAccessToken (bearerToken: string) { function getAccessToken (bearerToken: string) {
logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') logger.debug('Getting access token (bearerToken: ' + bearerToken + ').')
if (accessTokenCache[bearerToken] !== undefined) return accessTokenCache[bearerToken]
return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken) return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken)
.then(tokenModel => {
if (tokenModel) {
accessTokenCache[ bearerToken ] = tokenModel
userHavingToken[ tokenModel.userId ] = tokenModel.accessToken
}
return tokenModel
})
} }
function getClient (clientId: string, clientSecret: string) { function getClient (clientId: string, clientSecret: string) {
@ -48,6 +83,8 @@ async function getUser (usernameOrEmail: string, password: string) {
async function revokeToken (tokenInfo: TokenInfo) { async function revokeToken (tokenInfo: TokenInfo) {
const token = await OAuthTokenModel.getByRefreshTokenAndPopulateUser(tokenInfo.refreshToken) const token = await OAuthTokenModel.getByRefreshTokenAndPopulateUser(tokenInfo.refreshToken)
if (token) { if (token) {
clearCacheByToken(token.accessToken)
token.destroy() token.destroy()
.catch(err => logger.error('Cannot destroy token when revoking token.', { err })) .catch(err => logger.error('Cannot destroy token when revoking token.', { err }))
} }
@ -85,6 +122,9 @@ async function saveToken (token: TokenInfo, client: OAuthClientModel, user: User
// See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications // See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications
export { export {
deleteUserToken,
clearCacheByUserId,
clearCacheByToken,
getAccessToken, getAccessToken,
getClient, getClient,
getRefreshToken, getRefreshToken,

View File

@ -1,5 +1,7 @@
import * as Sequelize from 'sequelize' import * as Sequelize from 'sequelize'
import { import {
AfterDelete,
AfterUpdate,
AllowNull, AllowNull,
BeforeCreate, BeforeCreate,
BeforeUpdate, BeforeUpdate,
@ -39,6 +41,7 @@ import { AccountModel } from './account'
import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type' import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type'
import { values } from 'lodash' import { values } from 'lodash'
import { NSFW_POLICY_TYPES } from '../../initializers' import { NSFW_POLICY_TYPES } from '../../initializers'
import { clearCacheByUserId } from '../../lib/oauth-model'
enum ScopeNames { enum ScopeNames {
WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
@ -168,6 +171,12 @@ export class UserModel extends Model<UserModel> {
} }
} }
@AfterUpdate
@AfterDelete
static removeTokenCache (instance: UserModel) {
return clearCacheByUserId(instance.id)
}
static countTotal () { static countTotal () {
return this.count() return this.count()
} }

View File

@ -1,10 +1,23 @@
import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' import {
AfterDelete,
AfterUpdate,
AllowNull,
BelongsTo,
Column,
CreatedAt,
ForeignKey,
Model,
Scopes,
Table,
UpdatedAt
} from 'sequelize-typescript'
import { logger } from '../../helpers/logger' import { logger } from '../../helpers/logger'
import { UserModel } from '../account/user' import { UserModel } from '../account/user'
import { OAuthClientModel } from './oauth-client' import { OAuthClientModel } from './oauth-client'
import { Transaction } from 'sequelize' import { Transaction } from 'sequelize'
import { AccountModel } from '../account/account' import { AccountModel } from '../account/account'
import { ActorModel } from '../activitypub/actor' import { ActorModel } from '../activitypub/actor'
import { clearCacheByToken } from '../../lib/oauth-model'
export type OAuthTokenInfo = { export type OAuthTokenInfo = {
refreshToken: string refreshToken: string
@ -112,6 +125,12 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> {
}) })
OAuthClients: OAuthClientModel[] OAuthClients: OAuthClientModel[]
@AfterUpdate
@AfterDelete
static removeTokenCache (token: OAuthTokenModel) {
return clearCacheByToken(token.accessToken)
}
static getByRefreshTokenAndPopulateClient (refreshToken: string) { static getByRefreshTokenAndPopulateClient (refreshToken: string) {
const query = { const query = {
where: { where: {