X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Flib%2Fauth%2Foauth-model.ts;h=d3a5eccd5c255e20a6437f5fa68c0c23b495d111;hb=0c302acb3c358b4d4d8dee45aed1de1108ea37ea;hp=b9c69eb2db59f4b2fb7fd8dbc834ae2b8a889e34;hpb=97aeb3cc46c2e03c3187accd7c4561209be8be89;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/lib/auth/oauth-model.ts b/server/lib/auth/oauth-model.ts index b9c69eb2d..d3a5eccd5 100644 --- a/server/lib/auth/oauth-model.ts +++ b/server/lib/auth/oauth-model.ts @@ -1,18 +1,21 @@ -import * as express from 'express' -import { AccessDeniedError } from 'oauth2-server' +import express from 'express' +import { AccessDeniedError } from '@node-oauth/oauth2-server' import { PluginManager } from '@server/lib/plugins/plugin-manager' -import { ActorModel } from '@server/models/activitypub/actor' +import { AccountModel } from '@server/models/account/account' +import { AuthenticatedResultUpdaterFieldName, RegisterServerAuthenticatedResult } from '@server/types' import { MOAuthClient } from '@server/types/models' import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token' -import { MUser } from '@server/types/models/user/user' -import { UserAdminFlag } from '@shared/models/users/user-flag.model' -import { UserRole } from '@shared/models/users/user-role' +import { MUser, MUserDefault } from '@server/types/models/user/user' +import { pick } from '@shared/core-utils' +import { AttributesOnly } from '@shared/typescript-utils' import { logger } from '../../helpers/logger' import { CONFIG } from '../../initializers/config' -import { UserModel } from '../../models/account/user' import { OAuthClientModel } from '../../models/oauth/oauth-client' import { OAuthTokenModel } from '../../models/oauth/oauth-token' -import { createUserAccountAndChannelAndPlaylist } from '../user' +import { UserModel } from '../../models/user/user' +import { findAvailableLocalActorName } from '../local-actor' +import { buildUser, createUserAccountAndChannelAndPlaylist } from '../user' +import { ExternalUser } from './external-auth' import { TokensCache } from './tokens-cache' type TokenInfo = { @@ -26,16 +29,12 @@ export type BypassLogin = { bypass: boolean pluginName: string authName?: string - user: { - username: string - email: string - displayName: string - role: UserRole - } + user: ExternalUser + userUpdater: RegisterServerAuthenticatedResult['userUpdater'] } async function getAccessToken (bearerToken: string) { - logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') + logger.debug('Getting access token.') if (!bearerToken) return undefined @@ -89,7 +88,9 @@ async function getUser (usernameOrEmail?: string, password?: string, bypassLogin logger.info('Bypassing oauth login by plugin %s.', bypassLogin.pluginName) let user = await UserModel.loadByEmail(bypassLogin.user.email) + if (!user) user = await createUserFromExternal(bypassLogin.pluginName, bypassLogin.user) + else user = await updateUserFromExternal(user, bypassLogin.user, bypassLogin.userUpdater) // Cannot create a user if (!user) throw new AccessDeniedError('Cannot create such user: an actor with that name already exists.') @@ -98,7 +99,14 @@ async function getUser (usernameOrEmail?: string, password?: string, bypassLogin // Then we just go through a regular login process if (user.pluginAuth !== null) { // This user does not belong to this plugin, skip it - if (user.pluginAuth !== bypassLogin.pluginName) return null + if (user.pluginAuth !== bypassLogin.pluginName) { + logger.info( + 'Cannot bypass oauth login by plugin %s because %s has another plugin auth method (%s).', + bypassLogin.pluginName, bypassLogin.user.email, user.pluginAuth + ) + + return null + } checkUserValidityOrThrow(user) @@ -109,6 +117,7 @@ async function getUser (usernameOrEmail?: string, password?: string, bypassLogin logger.debug('Getting User (username/email: ' + usernameOrEmail + ', password: ******).') const user = await UserModel.loadByUsernameOrEmail(usernameOrEmail) + // If we don't find the user, or if the user belongs to a plugin if (!user || user.pluginAuth !== null || !password) return null @@ -211,37 +220,71 @@ export { // --------------------------------------------------------------------------- -async function createUserFromExternal (pluginAuth: string, options: { - username: string - email: string - role: UserRole - displayName: string -}) { - // Check an actor does not already exists with that name (removed user) - const actor = await ActorModel.loadLocalByName(options.username) - if (actor) return null - - const userToCreate = new UserModel({ - username: options.username, +async function createUserFromExternal (pluginAuth: string, userOptions: ExternalUser) { + const username = await findAvailableLocalActorName(userOptions.username) + + const userToCreate = buildUser({ + ...pick(userOptions, [ 'email', 'role', 'adminFlags', 'videoQuota', 'videoQuotaDaily' ]), + + username, + emailVerified: null, password: null, - email: options.email, - nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, - autoPlayVideo: true, - role: options.role, - videoQuota: CONFIG.USER.VIDEO_QUOTA, - videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY, - adminFlags: UserAdminFlag.NONE, pluginAuth - }) as MUser + }) const { user } = await createUserAccountAndChannelAndPlaylist({ userToCreate, - userDisplayName: options.displayName + userDisplayName: userOptions.displayName }) return user } +async function updateUserFromExternal ( + user: MUserDefault, + userOptions: ExternalUser, + userUpdater: RegisterServerAuthenticatedResult['userUpdater'] +) { + if (!userUpdater) return user + + { + type UserAttributeKeys = keyof AttributesOnly + const mappingKeys: { [ id in UserAttributeKeys ]?: AuthenticatedResultUpdaterFieldName } = { + role: 'role', + adminFlags: 'adminFlags', + videoQuota: 'videoQuota', + videoQuotaDaily: 'videoQuotaDaily' + } + + for (const modelKey of Object.keys(mappingKeys)) { + const pluginOptionKey = mappingKeys[modelKey] + + const newValue = userUpdater({ fieldName: pluginOptionKey, currentValue: user[modelKey], newValue: userOptions[pluginOptionKey] }) + user.set(modelKey, newValue) + } + } + + { + type AccountAttributeKeys = keyof Partial> + const mappingKeys: { [ id in AccountAttributeKeys ]?: AuthenticatedResultUpdaterFieldName } = { + name: 'displayName' + } + + for (const modelKey of Object.keys(mappingKeys)) { + const optionKey = mappingKeys[modelKey] + + const newValue = userUpdater({ fieldName: optionKey, currentValue: user.Account[modelKey], newValue: userOptions[optionKey] }) + user.Account.set(modelKey, newValue) + } + } + + logger.debug('Updated user %s with plugin userUpdated function.', user.email, { user, userOptions }) + + user.Account = await user.Account.save() + + return user.save() +} + function checkUserValidityOrThrow (user: MUser) { if (user.blocked) throw new AccessDeniedError('User is blocked.') }