Cache user token
This commit is contained in:
parent
91411dba92
commit
f201a74992
|
@ -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 })
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
Loading…
Reference in New Issue