aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib/auth
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-12-30 10:12:20 +0100
committerChocobozzz <me@florianbigard.com>2023-01-04 11:41:29 +0100
commit60b880acdfa85eab5c9ec09ba1283f82ae58ec85 (patch)
tree3c89db53ea9a00e61121d76672bd931eb6d1a84a /server/lib/auth
parent7e0c26066a5c59af742ae56bddaff9635debe034 (diff)
downloadPeerTube-60b880acdfa85eab5c9ec09ba1283f82ae58ec85.tar.gz
PeerTube-60b880acdfa85eab5c9ec09ba1283f82ae58ec85.tar.zst
PeerTube-60b880acdfa85eab5c9ec09ba1283f82ae58ec85.zip
External auth can update user on login
Diffstat (limited to 'server/lib/auth')
-rw-r--r--server/lib/auth/external-auth.ts18
-rw-r--r--server/lib/auth/oauth-model.ts53
2 files changed, 66 insertions, 5 deletions
diff --git a/server/lib/auth/external-auth.ts b/server/lib/auth/external-auth.ts
index 155ec03d8..bc5b74257 100644
--- a/server/lib/auth/external-auth.ts
+++ b/server/lib/auth/external-auth.ts
@@ -19,6 +19,7 @@ import {
19 RegisterServerExternalAuthenticatedResult 19 RegisterServerExternalAuthenticatedResult
20} from '@server/types/plugins/register-server-auth.model' 20} from '@server/types/plugins/register-server-auth.model'
21import { UserAdminFlag, UserRole } from '@shared/models' 21import { UserAdminFlag, UserRole } from '@shared/models'
22import { BypassLogin } from './oauth-model'
22 23
23export type ExternalUser = 24export type ExternalUser =
24 Pick<MUser, 'username' | 'email' | 'role' | 'adminFlags' | 'videoQuotaDaily' | 'videoQuota'> & 25 Pick<MUser, 'username' | 'email' | 'role' | 'adminFlags' | 'videoQuotaDaily' | 'videoQuota'> &
@@ -28,6 +29,7 @@ export type ExternalUser =
28const authBypassTokens = new Map<string, { 29const authBypassTokens = new Map<string, {
29 expires: Date 30 expires: Date
30 user: ExternalUser 31 user: ExternalUser
32 userUpdater: RegisterServerAuthenticatedResult['userUpdater']
31 authName: string 33 authName: string
32 npmName: string 34 npmName: string
33}>() 35}>()
@@ -63,7 +65,8 @@ async function onExternalUserAuthenticated (options: {
63 expires, 65 expires,
64 user, 66 user,
65 npmName, 67 npmName,
66 authName 68 authName,
69 userUpdater: authResult.userUpdater
67 }) 70 })
68 71
69 // Cleanup expired tokens 72 // Cleanup expired tokens
@@ -85,7 +88,7 @@ async function getAuthNameFromRefreshGrant (refreshToken?: string) {
85 return tokenModel?.authName 88 return tokenModel?.authName
86} 89}
87 90
88async function getBypassFromPasswordGrant (username: string, password: string) { 91async function getBypassFromPasswordGrant (username: string, password: string): Promise<BypassLogin> {
89 const plugins = PluginManager.Instance.getIdAndPassAuths() 92 const plugins = PluginManager.Instance.getIdAndPassAuths()
90 const pluginAuths: { npmName?: string, registerAuthOptions: RegisterServerAuthPassOptions }[] = [] 93 const pluginAuths: { npmName?: string, registerAuthOptions: RegisterServerAuthPassOptions }[] = []
91 94
@@ -140,7 +143,8 @@ async function getBypassFromPasswordGrant (username: string, password: string) {
140 bypass: true, 143 bypass: true,
141 pluginName: pluginAuth.npmName, 144 pluginName: pluginAuth.npmName,
142 authName: authOptions.authName, 145 authName: authOptions.authName,
143 user: buildUserResult(loginResult) 146 user: buildUserResult(loginResult),
147 userUpdater: loginResult.userUpdater
144 } 148 }
145 } catch (err) { 149 } catch (err) {
146 logger.error('Error in auth method %s of plugin %s', authOptions.authName, pluginAuth.npmName, { err }) 150 logger.error('Error in auth method %s of plugin %s', authOptions.authName, pluginAuth.npmName, { err })
@@ -150,7 +154,7 @@ async function getBypassFromPasswordGrant (username: string, password: string) {
150 return undefined 154 return undefined
151} 155}
152 156
153function getBypassFromExternalAuth (username: string, externalAuthToken: string) { 157function getBypassFromExternalAuth (username: string, externalAuthToken: string): BypassLogin {
154 const obj = authBypassTokens.get(externalAuthToken) 158 const obj = authBypassTokens.get(externalAuthToken)
155 if (!obj) throw new Error('Cannot authenticate user with unknown bypass token') 159 if (!obj) throw new Error('Cannot authenticate user with unknown bypass token')
156 160
@@ -174,6 +178,7 @@ function getBypassFromExternalAuth (username: string, externalAuthToken: string)
174 bypass: true, 178 bypass: true,
175 pluginName: npmName, 179 pluginName: npmName,
176 authName, 180 authName,
181 userUpdater: obj.userUpdater,
177 user 182 user
178 } 183 }
179} 184}
@@ -194,6 +199,11 @@ function isAuthResultValid (npmName: string, authName: string, result: RegisterS
194 if (result.videoQuota && !isUserVideoQuotaValid(result.videoQuota + '')) return returnError('videoQuota') 199 if (result.videoQuota && !isUserVideoQuotaValid(result.videoQuota + '')) return returnError('videoQuota')
195 if (result.videoQuotaDaily && !isUserVideoQuotaDailyValid(result.videoQuotaDaily + '')) return returnError('videoQuotaDaily') 200 if (result.videoQuotaDaily && !isUserVideoQuotaDailyValid(result.videoQuotaDaily + '')) return returnError('videoQuotaDaily')
196 201
202 if (result.userUpdater && typeof result.userUpdater !== 'function') {
203 logger.error('Auth method %s of plugin %s did not provide a valid user updater function.', authName, npmName)
204 return false
205 }
206
197 return true 207 return true
198} 208}
199 209
diff --git a/server/lib/auth/oauth-model.ts b/server/lib/auth/oauth-model.ts
index 603cc0f5f..43909284f 100644
--- a/server/lib/auth/oauth-model.ts
+++ b/server/lib/auth/oauth-model.ts
@@ -1,10 +1,13 @@
1import express from 'express' 1import express from 'express'
2import { AccessDeniedError } from '@node-oauth/oauth2-server' 2import { AccessDeniedError } from '@node-oauth/oauth2-server'
3import { PluginManager } from '@server/lib/plugins/plugin-manager' 3import { PluginManager } from '@server/lib/plugins/plugin-manager'
4import { AccountModel } from '@server/models/account/account'
5import { AuthenticatedResultUpdaterFieldName, RegisterServerAuthenticatedResult } from '@server/types'
4import { MOAuthClient } from '@server/types/models' 6import { MOAuthClient } from '@server/types/models'
5import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token' 7import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token'
6import { MUser } from '@server/types/models/user/user' 8import { MUser, MUserDefault } from '@server/types/models/user/user'
7import { pick } from '@shared/core-utils' 9import { pick } from '@shared/core-utils'
10import { AttributesOnly } from '@shared/typescript-utils'
8import { logger } from '../../helpers/logger' 11import { logger } from '../../helpers/logger'
9import { CONFIG } from '../../initializers/config' 12import { CONFIG } from '../../initializers/config'
10import { OAuthClientModel } from '../../models/oauth/oauth-client' 13import { OAuthClientModel } from '../../models/oauth/oauth-client'
@@ -27,6 +30,7 @@ export type BypassLogin = {
27 pluginName: string 30 pluginName: string
28 authName?: string 31 authName?: string
29 user: ExternalUser 32 user: ExternalUser
33 userUpdater: RegisterServerAuthenticatedResult['userUpdater']
30} 34}
31 35
32async function getAccessToken (bearerToken: string) { 36async function getAccessToken (bearerToken: string) {
@@ -84,7 +88,9 @@ async function getUser (usernameOrEmail?: string, password?: string, bypassLogin
84 logger.info('Bypassing oauth login by plugin %s.', bypassLogin.pluginName) 88 logger.info('Bypassing oauth login by plugin %s.', bypassLogin.pluginName)
85 89
86 let user = await UserModel.loadByEmail(bypassLogin.user.email) 90 let user = await UserModel.loadByEmail(bypassLogin.user.email)
91
87 if (!user) user = await createUserFromExternal(bypassLogin.pluginName, bypassLogin.user) 92 if (!user) user = await createUserFromExternal(bypassLogin.pluginName, bypassLogin.user)
93 else user = await updateUserFromExternal(user, bypassLogin.user, bypassLogin.userUpdater)
88 94
89 // Cannot create a user 95 // Cannot create a user
90 if (!user) throw new AccessDeniedError('Cannot create such user: an actor with that name already exists.') 96 if (!user) throw new AccessDeniedError('Cannot create such user: an actor with that name already exists.')
@@ -234,6 +240,51 @@ async function createUserFromExternal (pluginAuth: string, userOptions: External
234 return user 240 return user
235} 241}
236 242
243async function updateUserFromExternal (
244 user: MUserDefault,
245 userOptions: ExternalUser,
246 userUpdater: RegisterServerAuthenticatedResult['userUpdater']
247) {
248 if (!userUpdater) return user
249
250 {
251 type UserAttributeKeys = keyof AttributesOnly<UserModel>
252 const mappingKeys: { [ id in UserAttributeKeys ]?: AuthenticatedResultUpdaterFieldName } = {
253 role: 'role',
254 adminFlags: 'adminFlags',
255 videoQuota: 'videoQuota',
256 videoQuotaDaily: 'videoQuotaDaily'
257 }
258
259 for (const modelKey of Object.keys(mappingKeys)) {
260 const pluginOptionKey = mappingKeys[modelKey]
261
262 const newValue = userUpdater({ fieldName: pluginOptionKey, currentValue: user[modelKey], newValue: userOptions[pluginOptionKey] })
263 user.set(modelKey, newValue)
264 }
265 }
266
267 {
268 type AccountAttributeKeys = keyof Partial<AttributesOnly<AccountModel>>
269 const mappingKeys: { [ id in AccountAttributeKeys ]?: AuthenticatedResultUpdaterFieldName } = {
270 name: 'displayName'
271 }
272
273 for (const modelKey of Object.keys(mappingKeys)) {
274 const optionKey = mappingKeys[modelKey]
275
276 const newValue = userUpdater({ fieldName: optionKey, currentValue: user.Account[modelKey], newValue: userOptions[optionKey] })
277 user.Account.set(modelKey, newValue)
278 }
279 }
280
281 logger.debug('Updated user %s with plugin userUpdated function.', user.email, { user, userOptions })
282
283 user.Account = await user.Account.save()
284
285 return user.save()
286}
287
237function checkUserValidityOrThrow (user: MUser) { 288function checkUserValidityOrThrow (user: MUser) {
238 if (user.blocked) throw new AccessDeniedError('User is blocked.') 289 if (user.blocked) throw new AccessDeniedError('User is blocked.')
239} 290}