]>
Commit | Line | Data |
---|---|---|
cef534ed | 1 | import * as Bluebird from 'bluebird' |
7fed6375 | 2 | import * as express from 'express' |
141b177d | 3 | import { AccessDeniedError } from 'oauth2-server' |
da854ddd | 4 | import { logger } from '../helpers/logger' |
3fd3ab2d C |
5 | import { UserModel } from '../models/account/user' |
6 | import { OAuthClientModel } from '../models/oauth/oauth-client' | |
7 | import { OAuthTokenModel } from '../models/oauth/oauth-token' | |
557b13ae | 8 | import { LRU_CACHE } from '../initializers/constants' |
f201a749 | 9 | import { Transaction } from 'sequelize' |
6dd9de95 | 10 | import { CONFIG } from '../initializers/config' |
557b13ae | 11 | import * as LRUCache from 'lru-cache' |
453e83ea | 12 | import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token' |
7fed6375 C |
13 | import { MUser } from '@server/typings/models/user/user' |
14 | import { UserAdminFlag } from '@shared/models/users/user-flag.model' | |
15 | import { createUserAccountAndChannelAndPlaylist } from './user' | |
16 | import { UserRole } from '@shared/models/users/user-role' | |
e1c55031 | 17 | import { PluginManager } from '@server/lib/plugins/plugin-manager' |
69b0a27c | 18 | |
69818c93 | 19 | type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } |
557b13ae | 20 | |
453e83ea | 21 | const accessTokenCache = new LRUCache<string, MOAuthTokenUser>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE }) |
557b13ae | 22 | const userHavingToken = new LRUCache<number, string>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE }) |
69818c93 | 23 | |
69b0a27c C |
24 | // --------------------------------------------------------------------------- |
25 | ||
f201a749 C |
26 | function deleteUserToken (userId: number, t?: Transaction) { |
27 | clearCacheByUserId(userId) | |
28 | ||
29 | return OAuthTokenModel.deleteUserToken(userId, t) | |
30 | } | |
31 | ||
32 | function clearCacheByUserId (userId: number) { | |
557b13ae C |
33 | const token = userHavingToken.get(userId) |
34 | ||
f201a749 | 35 | if (token !== undefined) { |
557b13ae C |
36 | accessTokenCache.del(token) |
37 | userHavingToken.del(userId) | |
f201a749 C |
38 | } |
39 | } | |
40 | ||
41 | function clearCacheByToken (token: string) { | |
557b13ae C |
42 | const tokenModel = accessTokenCache.get(token) |
43 | ||
f201a749 | 44 | if (tokenModel !== undefined) { |
557b13ae C |
45 | userHavingToken.del(tokenModel.userId) |
46 | accessTokenCache.del(token) | |
f201a749 C |
47 | } |
48 | } | |
49 | ||
69818c93 | 50 | function getAccessToken (bearerToken: string) { |
69b0a27c C |
51 | logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') |
52 | ||
3acc5084 C |
53 | if (!bearerToken) return Bluebird.resolve(undefined) |
54 | ||
557b13ae | 55 | if (accessTokenCache.has(bearerToken)) return Bluebird.resolve(accessTokenCache.get(bearerToken)) |
f201a749 | 56 | |
3fd3ab2d | 57 | return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken) |
7fed6375 C |
58 | .then(tokenModel => { |
59 | if (tokenModel) { | |
60 | accessTokenCache.set(bearerToken, tokenModel) | |
61 | userHavingToken.set(tokenModel.userId, tokenModel.accessToken) | |
62 | } | |
63 | ||
64 | return tokenModel | |
65 | }) | |
69b0a27c C |
66 | } |
67 | ||
69818c93 | 68 | function getClient (clientId: string, clientSecret: string) { |
69b0a27c C |
69 | logger.debug('Getting Client (clientId: ' + clientId + ', clientSecret: ' + clientSecret + ').') |
70 | ||
3fd3ab2d | 71 | return OAuthClientModel.getByIdAndSecret(clientId, clientSecret) |
69b0a27c C |
72 | } |
73 | ||
69818c93 | 74 | function getRefreshToken (refreshToken: string) { |
69b0a27c C |
75 | logger.debug('Getting RefreshToken (refreshToken: ' + refreshToken + ').') |
76 | ||
3fd3ab2d | 77 | return OAuthTokenModel.getByRefreshTokenAndPopulateClient(refreshToken) |
69b0a27c C |
78 | } |
79 | ||
ba12e8b3 | 80 | async function getUser (usernameOrEmail: string, password: string) { |
7fed6375 C |
81 | const res: express.Response = this.request.res |
82 | if (res.locals.bypassLogin && res.locals.bypassLogin.bypass === true) { | |
83 | const obj = res.locals.bypassLogin | |
84 | logger.info('Bypassing oauth login by plugin %s.', obj.pluginName) | |
85 | ||
e1c55031 | 86 | let user = await UserModel.loadByEmail(obj.user.email) |
7fed6375 C |
87 | if (!user) user = await createUserFromExternal(obj.pluginName, obj.user) |
88 | ||
89 | // This user does not belong to this plugin, skip it | |
90 | if (user.pluginAuth !== obj.pluginName) return null | |
91 | ||
92 | return user | |
93 | } | |
94 | ||
ba12e8b3 | 95 | logger.debug('Getting User (username/email: ' + usernameOrEmail + ', password: ******).') |
69b0a27c | 96 | |
ba12e8b3 | 97 | const user = await UserModel.loadByUsernameOrEmail(usernameOrEmail) |
e1c55031 C |
98 | // If we don't find the user, or if the user belongs to a plugin |
99 | if (!user || user.pluginAuth !== null) return null | |
26d7d31b | 100 | |
f5028693 C |
101 | const passwordMatch = await user.isPasswordMatch(password) |
102 | if (passwordMatch === false) return null | |
26d7d31b | 103 | |
e6921918 C |
104 | if (user.blocked) throw new AccessDeniedError('User is blocked.') |
105 | ||
d9eaee39 JM |
106 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION && user.emailVerified === false) { |
107 | throw new AccessDeniedError('User email is not verified.') | |
108 | } | |
109 | ||
f5028693 | 110 | return user |
2f372a86 C |
111 | } |
112 | ||
f5028693 | 113 | async function revokeToken (tokenInfo: TokenInfo) { |
e1c55031 | 114 | const res: express.Response = this.request.res |
3fd3ab2d | 115 | const token = await OAuthTokenModel.getByRefreshTokenAndPopulateUser(tokenInfo.refreshToken) |
e1c55031 | 116 | |
bfcef50d | 117 | if (token) { |
e1c55031 C |
118 | if (res.locals.explicitLogout === true && token.User.pluginAuth && token.authName) { |
119 | PluginManager.Instance.onLogout(token.User.pluginAuth, token.authName) | |
120 | } | |
121 | ||
f201a749 C |
122 | clearCacheByToken(token.accessToken) |
123 | ||
bfcef50d C |
124 | token.destroy() |
125 | .catch(err => logger.error('Cannot destroy token when revoking token.', { err })) | |
7fed6375 C |
126 | |
127 | return true | |
bfcef50d | 128 | } |
f5028693 | 129 | |
7fed6375 | 130 | return false |
69b0a27c C |
131 | } |
132 | ||
3fd3ab2d | 133 | async function saveToken (token: TokenInfo, client: OAuthClientModel, user: UserModel) { |
e1c55031 C |
134 | const res: express.Response = this.request.res |
135 | ||
136 | const authName = res.locals.bypassLogin?.bypass === true | |
137 | ? res.locals.bypassLogin.authName | |
138 | : null | |
139 | ||
32bb4156 | 140 | logger.debug('Saving token ' + token.accessToken + ' for client ' + client.id + ' and user ' + user.id + '.') |
69b0a27c | 141 | |
feb4bdfd | 142 | const tokenToCreate = { |
69b0a27c | 143 | accessToken: token.accessToken, |
2f372a86 | 144 | accessTokenExpiresAt: token.accessTokenExpiresAt, |
69b0a27c | 145 | refreshToken: token.refreshToken, |
2f372a86 | 146 | refreshTokenExpiresAt: token.refreshTokenExpiresAt, |
e1c55031 | 147 | authName, |
feb4bdfd C |
148 | oAuthClientId: client.id, |
149 | userId: user.id | |
150 | } | |
69b0a27c | 151 | |
3fd3ab2d | 152 | const tokenCreated = await OAuthTokenModel.create(tokenToCreate) |
e6921918 | 153 | return Object.assign(tokenCreated, { client, user }) |
69b0a27c C |
154 | } |
155 | ||
156 | // --------------------------------------------------------------------------- | |
157 | ||
65fcc311 C |
158 | // See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications |
159 | export { | |
f201a749 C |
160 | deleteUserToken, |
161 | clearCacheByUserId, | |
162 | clearCacheByToken, | |
65fcc311 C |
163 | getAccessToken, |
164 | getClient, | |
165 | getRefreshToken, | |
166 | getUser, | |
167 | revokeToken, | |
168 | saveToken | |
169 | } | |
7fed6375 C |
170 | |
171 | async function createUserFromExternal (pluginAuth: string, options: { | |
172 | username: string | |
173 | email: string | |
174 | role: UserRole | |
175 | displayName: string | |
176 | }) { | |
177 | const userToCreate = new UserModel({ | |
178 | username: options.username, | |
179 | password: null, | |
180 | email: options.email, | |
181 | nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, | |
182 | autoPlayVideo: true, | |
183 | role: options.role, | |
184 | videoQuota: CONFIG.USER.VIDEO_QUOTA, | |
185 | videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY, | |
186 | adminFlags: UserAdminFlag.NONE, | |
187 | pluginAuth | |
188 | }) as MUser | |
189 | ||
190 | const { user } = await createUserAccountAndChannelAndPlaylist({ | |
191 | userToCreate, | |
192 | userDisplayName: options.displayName | |
193 | }) | |
194 | ||
195 | return user | |
196 | } |