diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/users/index.ts | 5 | ||||
-rw-r--r-- | server/controllers/api/users/me.ts | 2 | ||||
-rw-r--r-- | server/controllers/api/video-channel.ts | 10 | ||||
-rw-r--r-- | server/lib/avatar.ts | 11 | ||||
-rw-r--r-- | server/lib/oauth-model.ts | 40 | ||||
-rw-r--r-- | server/models/account/user.ts | 9 | ||||
-rw-r--r-- | server/models/oauth/oauth-token.ts | 21 |
7 files changed, 79 insertions, 19 deletions
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index d1163900b..8b8ebcd23 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts | |||
@@ -37,6 +37,7 @@ import { UserModel } from '../../../models/account/user' | |||
37 | import { OAuthTokenModel } from '../../../models/oauth/oauth-token' | 37 | import { OAuthTokenModel } from '../../../models/oauth/oauth-token' |
38 | import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' | 38 | import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' |
39 | import { meRouter } from './me' | 39 | import { meRouter } from './me' |
40 | import { deleteUserToken } from '../../../lib/oauth-model' | ||
40 | 41 | ||
41 | const auditLogger = auditLoggerFactory('users') | 42 | const auditLogger = auditLoggerFactory('users') |
42 | 43 | ||
@@ -267,7 +268,7 @@ async function updateUser (req: express.Request, res: express.Response, next: ex | |||
267 | const user = await userToUpdate.save() | 268 | const user = await userToUpdate.save() |
268 | 269 | ||
269 | // Destroy user token to refresh rights | 270 | // Destroy user token to refresh rights |
270 | if (roleChanged) await OAuthTokenModel.deleteUserToken(userToUpdate.id) | 271 | if (roleChanged) await deleteUserToken(userToUpdate.id) |
271 | 272 | ||
272 | auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) | 273 | auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) |
273 | 274 | ||
@@ -330,7 +331,7 @@ async function changeUserBlock (res: express.Response, user: UserModel, block: b | |||
330 | user.blockedReason = reason || null | 331 | user.blockedReason = reason || null |
331 | 332 | ||
332 | await sequelizeTypescript.transaction(async t => { | 333 | await sequelizeTypescript.transaction(async t => { |
333 | await OAuthTokenModel.deleteUserToken(user.id, t) | 334 | await deleteUserToken(user.id, t) |
334 | 335 | ||
335 | await user.save({ transaction: t }) | 336 | await user.save({ transaction: t }) |
336 | }) | 337 | }) |
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts index eba1e7edd..ff3a87b7f 100644 --- a/server/controllers/api/users/me.ts +++ b/server/controllers/api/users/me.ts | |||
@@ -353,7 +353,7 @@ async function updateMyAvatar (req: express.Request, res: express.Response, next | |||
353 | 353 | ||
354 | const userAccount = await AccountModel.load(user.Account.id) | 354 | const userAccount = await AccountModel.load(user.Account.id) |
355 | 355 | ||
356 | const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount.Actor, userAccount) | 356 | const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount) |
357 | 357 | ||
358 | auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) | 358 | auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) |
359 | 359 | ||
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts index 8fc340224..ff6bbe44c 100644 --- a/server/controllers/api/video-channel.ts +++ b/server/controllers/api/video-channel.ts | |||
@@ -56,7 +56,7 @@ videoChannelRouter.post('/:nameWithHost/avatar/pick', | |||
56 | // Check the rights | 56 | // Check the rights |
57 | asyncMiddleware(videoChannelsUpdateValidator), | 57 | asyncMiddleware(videoChannelsUpdateValidator), |
58 | updateAvatarValidator, | 58 | updateAvatarValidator, |
59 | asyncMiddleware(updateVideoChannelAvatar) | 59 | asyncRetryTransactionMiddleware(updateVideoChannelAvatar) |
60 | ) | 60 | ) |
61 | 61 | ||
62 | videoChannelRouter.put('/:nameWithHost', | 62 | videoChannelRouter.put('/:nameWithHost', |
@@ -107,13 +107,9 @@ async function updateVideoChannelAvatar (req: express.Request, res: express.Resp | |||
107 | const videoChannel = res.locals.videoChannel as VideoChannelModel | 107 | const videoChannel = res.locals.videoChannel as VideoChannelModel |
108 | const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON()) | 108 | const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON()) |
109 | 109 | ||
110 | const avatar = await updateActorAvatarFile(avatarPhysicalFile, videoChannel.Actor, videoChannel) | 110 | const avatar = await updateActorAvatarFile(avatarPhysicalFile, videoChannel) |
111 | 111 | ||
112 | auditLogger.update( | 112 | auditLogger.update(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannel.toFormattedJSON()), oldVideoChannelAuditKeys) |
113 | getAuditIdFromRes(res), | ||
114 | new VideoChannelAuditView(videoChannel.toFormattedJSON()), | ||
115 | oldVideoChannelAuditKeys | ||
116 | ) | ||
117 | 113 | ||
118 | return res | 114 | return res |
119 | .json({ | 115 | .json({ |
diff --git a/server/lib/avatar.ts b/server/lib/avatar.ts index 5cfb81fc7..14f0a05f5 100644 --- a/server/lib/avatar.ts +++ b/server/lib/avatar.ts | |||
@@ -3,23 +3,18 @@ import { sendUpdateActor } from './activitypub/send' | |||
3 | import { AVATARS_SIZE, CONFIG, sequelizeTypescript } from '../initializers' | 3 | import { AVATARS_SIZE, CONFIG, sequelizeTypescript } from '../initializers' |
4 | import { updateActorAvatarInstance } from './activitypub' | 4 | import { updateActorAvatarInstance } from './activitypub' |
5 | import { processImage } from '../helpers/image-utils' | 5 | import { processImage } from '../helpers/image-utils' |
6 | import { ActorModel } from '../models/activitypub/actor' | ||
7 | import { AccountModel } from '../models/account/account' | 6 | import { AccountModel } from '../models/account/account' |
8 | import { VideoChannelModel } from '../models/video/video-channel' | 7 | import { VideoChannelModel } from '../models/video/video-channel' |
9 | import { extname, join } from 'path' | 8 | import { extname, join } from 'path' |
10 | 9 | ||
11 | async function updateActorAvatarFile ( | 10 | async function updateActorAvatarFile (avatarPhysicalFile: Express.Multer.File, accountOrChannel: AccountModel | VideoChannelModel) { |
12 | avatarPhysicalFile: Express.Multer.File, | ||
13 | actor: ActorModel, | ||
14 | accountOrChannel: AccountModel | VideoChannelModel | ||
15 | ) { | ||
16 | const extension = extname(avatarPhysicalFile.filename) | 11 | const extension = extname(avatarPhysicalFile.filename) |
17 | const avatarName = actor.uuid + extension | 12 | const avatarName = accountOrChannel.Actor.uuid + extension |
18 | const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) | 13 | const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) |
19 | await processImage(avatarPhysicalFile, destination, AVATARS_SIZE) | 14 | await processImage(avatarPhysicalFile, destination, AVATARS_SIZE) |
20 | 15 | ||
21 | return sequelizeTypescript.transaction(async t => { | 16 | return sequelizeTypescript.transaction(async t => { |
22 | const updatedActor = await updateActorAvatarInstance(actor, avatarName, t) | 17 | const updatedActor = await updateActorAvatarInstance(accountOrChannel.Actor, avatarName, t) |
23 | await updatedActor.save({ transaction: t }) | 18 | await updatedActor.save({ transaction: t }) |
24 | 19 | ||
25 | await sendUpdateActor(accountOrChannel, t) | 20 | await sendUpdateActor(accountOrChannel, t) |
diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts index 2f8667e19..5cbe60b82 100644 --- a/server/lib/oauth-model.ts +++ b/server/lib/oauth-model.ts | |||
@@ -4,15 +4,50 @@ import { UserModel } from '../models/account/user' | |||
4 | import { OAuthClientModel } from '../models/oauth/oauth-client' | 4 | import { OAuthClientModel } from '../models/oauth/oauth-client' |
5 | import { OAuthTokenModel } from '../models/oauth/oauth-token' | 5 | import { OAuthTokenModel } from '../models/oauth/oauth-token' |
6 | import { CONFIG } from '../initializers/constants' | 6 | import { CONFIG } from '../initializers/constants' |
7 | import { Transaction } from 'sequelize' | ||
7 | 8 | ||
8 | type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } | 9 | type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } |
10 | const accessTokenCache: { [ accessToken: string ]: OAuthTokenModel } = {} | ||
11 | const userHavingToken: { [ userId: number ]: string } = {} | ||
9 | 12 | ||
10 | // --------------------------------------------------------------------------- | 13 | // --------------------------------------------------------------------------- |
11 | 14 | ||
15 | function deleteUserToken (userId: number, t?: Transaction) { | ||
16 | clearCacheByUserId(userId) | ||
17 | |||
18 | return OAuthTokenModel.deleteUserToken(userId, t) | ||
19 | } | ||
20 | |||
21 | function clearCacheByUserId (userId: number) { | ||
22 | const token = userHavingToken[userId] | ||
23 | if (token !== undefined) { | ||
24 | accessTokenCache[ token ] = undefined | ||
25 | userHavingToken[ userId ] = undefined | ||
26 | } | ||
27 | } | ||
28 | |||
29 | function clearCacheByToken (token: string) { | ||
30 | const tokenModel = accessTokenCache[ token ] | ||
31 | if (tokenModel !== undefined) { | ||
32 | userHavingToken[tokenModel.userId] = undefined | ||
33 | accessTokenCache[ token ] = undefined | ||
34 | } | ||
35 | } | ||
36 | |||
12 | function getAccessToken (bearerToken: string) { | 37 | function getAccessToken (bearerToken: string) { |
13 | logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') | 38 | logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') |
14 | 39 | ||
40 | if (accessTokenCache[bearerToken] !== undefined) return accessTokenCache[bearerToken] | ||
41 | |||
15 | return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken) | 42 | return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken) |
43 | .then(tokenModel => { | ||
44 | if (tokenModel) { | ||
45 | accessTokenCache[ bearerToken ] = tokenModel | ||
46 | userHavingToken[ tokenModel.userId ] = tokenModel.accessToken | ||
47 | } | ||
48 | |||
49 | return tokenModel | ||
50 | }) | ||
16 | } | 51 | } |
17 | 52 | ||
18 | function getClient (clientId: string, clientSecret: string) { | 53 | function getClient (clientId: string, clientSecret: string) { |
@@ -48,6 +83,8 @@ async function getUser (usernameOrEmail: string, password: string) { | |||
48 | async function revokeToken (tokenInfo: TokenInfo) { | 83 | async function revokeToken (tokenInfo: TokenInfo) { |
49 | const token = await OAuthTokenModel.getByRefreshTokenAndPopulateUser(tokenInfo.refreshToken) | 84 | const token = await OAuthTokenModel.getByRefreshTokenAndPopulateUser(tokenInfo.refreshToken) |
50 | if (token) { | 85 | if (token) { |
86 | clearCacheByToken(token.accessToken) | ||
87 | |||
51 | token.destroy() | 88 | token.destroy() |
52 | .catch(err => logger.error('Cannot destroy token when revoking token.', { err })) | 89 | .catch(err => logger.error('Cannot destroy token when revoking token.', { err })) |
53 | } | 90 | } |
@@ -85,6 +122,9 @@ async function saveToken (token: TokenInfo, client: OAuthClientModel, user: User | |||
85 | 122 | ||
86 | // See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications | 123 | // See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications |
87 | export { | 124 | export { |
125 | deleteUserToken, | ||
126 | clearCacheByUserId, | ||
127 | clearCacheByToken, | ||
88 | getAccessToken, | 128 | getAccessToken, |
89 | getClient, | 129 | getClient, |
90 | getRefreshToken, | 130 | getRefreshToken, |
diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 680b1d52d..e56b0bf40 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts | |||
@@ -1,5 +1,7 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { | 2 | import { |
3 | AfterDelete, | ||
4 | AfterUpdate, | ||
3 | AllowNull, | 5 | AllowNull, |
4 | BeforeCreate, | 6 | BeforeCreate, |
5 | BeforeUpdate, | 7 | BeforeUpdate, |
@@ -39,6 +41,7 @@ import { AccountModel } from './account' | |||
39 | import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type' | 41 | import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type' |
40 | import { values } from 'lodash' | 42 | import { values } from 'lodash' |
41 | import { NSFW_POLICY_TYPES } from '../../initializers' | 43 | import { NSFW_POLICY_TYPES } from '../../initializers' |
44 | import { clearCacheByUserId } from '../../lib/oauth-model' | ||
42 | 45 | ||
43 | enum ScopeNames { | 46 | enum ScopeNames { |
44 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' | 47 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' |
@@ -168,6 +171,12 @@ export class UserModel extends Model<UserModel> { | |||
168 | } | 171 | } |
169 | } | 172 | } |
170 | 173 | ||
174 | @AfterUpdate | ||
175 | @AfterDelete | ||
176 | static removeTokenCache (instance: UserModel) { | ||
177 | return clearCacheByUserId(instance.id) | ||
178 | } | ||
179 | |||
171 | static countTotal () { | 180 | static countTotal () { |
172 | return this.count() | 181 | return this.count() |
173 | } | 182 | } |
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts index 1dd5e0289..ef9592c04 100644 --- a/server/models/oauth/oauth-token.ts +++ b/server/models/oauth/oauth-token.ts | |||
@@ -1,10 +1,23 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { |
2 | AfterDelete, | ||
3 | AfterUpdate, | ||
4 | AllowNull, | ||
5 | BelongsTo, | ||
6 | Column, | ||
7 | CreatedAt, | ||
8 | ForeignKey, | ||
9 | Model, | ||
10 | Scopes, | ||
11 | Table, | ||
12 | UpdatedAt | ||
13 | } from 'sequelize-typescript' | ||
2 | import { logger } from '../../helpers/logger' | 14 | import { logger } from '../../helpers/logger' |
3 | import { UserModel } from '../account/user' | 15 | import { UserModel } from '../account/user' |
4 | import { OAuthClientModel } from './oauth-client' | 16 | import { OAuthClientModel } from './oauth-client' |
5 | import { Transaction } from 'sequelize' | 17 | import { Transaction } from 'sequelize' |
6 | import { AccountModel } from '../account/account' | 18 | import { AccountModel } from '../account/account' |
7 | import { ActorModel } from '../activitypub/actor' | 19 | import { ActorModel } from '../activitypub/actor' |
20 | import { clearCacheByToken } from '../../lib/oauth-model' | ||
8 | 21 | ||
9 | export type OAuthTokenInfo = { | 22 | export type OAuthTokenInfo = { |
10 | refreshToken: string | 23 | refreshToken: string |
@@ -112,6 +125,12 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> { | |||
112 | }) | 125 | }) |
113 | OAuthClients: OAuthClientModel[] | 126 | OAuthClients: OAuthClientModel[] |
114 | 127 | ||
128 | @AfterUpdate | ||
129 | @AfterDelete | ||
130 | static removeTokenCache (token: OAuthTokenModel) { | ||
131 | return clearCacheByToken(token.accessToken) | ||
132 | } | ||
133 | |||
115 | static getByRefreshTokenAndPopulateClient (refreshToken: string) { | 134 | static getByRefreshTokenAndPopulateClient (refreshToken: string) { |
116 | const query = { | 135 | const query = { |
117 | where: { | 136 | where: { |