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 { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
import { meRouter } from './me'
import { deleteUserToken } from '../../../lib/oauth-model'
const auditLogger = auditLoggerFactory('users')
@ -267,7 +268,7 @@ async function updateUser (req: express.Request, res: express.Response, next: ex
const user = await userToUpdate.save()
// 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)
@ -330,7 +331,7 @@ async function changeUserBlock (res: express.Response, user: UserModel, block: b
user.blockedReason = reason || null
await sequelizeTypescript.transaction(async t => {
await OAuthTokenModel.deleteUserToken(user.id, t)
await deleteUserToken(user.id, 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 avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount.Actor, userAccount)
const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount)
auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)

View File

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

View File

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

View File

@ -4,15 +4,50 @@ import { UserModel } from '../models/account/user'
import { OAuthClientModel } from '../models/oauth/oauth-client'
import { OAuthTokenModel } from '../models/oauth/oauth-token'
import { CONFIG } from '../initializers/constants'
import { Transaction } from 'sequelize'
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) {
logger.debug('Getting access token (bearerToken: ' + bearerToken + ').')
if (accessTokenCache[bearerToken] !== undefined) return accessTokenCache[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) {
@ -48,6 +83,8 @@ async function getUser (usernameOrEmail: string, password: string) {
async function revokeToken (tokenInfo: TokenInfo) {
const token = await OAuthTokenModel.getByRefreshTokenAndPopulateUser(tokenInfo.refreshToken)
if (token) {
clearCacheByToken(token.accessToken)
token.destroy()
.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
export {
deleteUserToken,
clearCacheByUserId,
clearCacheByToken,
getAccessToken,
getClient,
getRefreshToken,

View File

@ -1,5 +1,7 @@
import * as Sequelize from 'sequelize'
import {
AfterDelete,
AfterUpdate,
AllowNull,
BeforeCreate,
BeforeUpdate,
@ -39,6 +41,7 @@ import { AccountModel } from './account'
import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type'
import { values } from 'lodash'
import { NSFW_POLICY_TYPES } from '../../initializers'
import { clearCacheByUserId } from '../../lib/oauth-model'
enum ScopeNames {
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 () {
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 { UserModel } from '../account/user'
import { OAuthClientModel } from './oauth-client'
import { Transaction } from 'sequelize'
import { AccountModel } from '../account/account'
import { ActorModel } from '../activitypub/actor'
import { clearCacheByToken } from '../../lib/oauth-model'
export type OAuthTokenInfo = {
refreshToken: string
@ -112,6 +125,12 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> {
})
OAuthClients: OAuthClientModel[]
@AfterUpdate
@AfterDelete
static removeTokenCache (token: OAuthTokenModel) {
return clearCacheByToken(token.accessToken)
}
static getByRefreshTokenAndPopulateClient (refreshToken: string) {
const query = {
where: {