aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2020-04-23 11:36:50 +0200
committerChocobozzz <chocobozzz@cpy.re>2020-05-04 16:21:39 +0200
commite1c5503114deef954731904695cd40dccfcef555 (patch)
tree72cec4ee691a3362a7d024dc830d215a6b2c800a /server/lib
parent8dc8a34ee8428e7657414115d1c137592efa174d (diff)
downloadPeerTube-e1c5503114deef954731904695cd40dccfcef555.tar.gz
PeerTube-e1c5503114deef954731904695cd40dccfcef555.tar.zst
PeerTube-e1c5503114deef954731904695cd40dccfcef555.zip
Support logout and add id and pass tests
Diffstat (limited to 'server/lib')
-rw-r--r--server/lib/auth.ts50
-rw-r--r--server/lib/job-queue/job-queue.ts23
-rw-r--r--server/lib/oauth-model.ts19
-rw-r--r--server/lib/plugins/plugin-manager.ts20
-rw-r--r--server/lib/plugins/register-helpers-store.ts5
5 files changed, 99 insertions, 18 deletions
diff --git a/server/lib/auth.ts b/server/lib/auth.ts
index 18d52fa5a..3495571db 100644
--- a/server/lib/auth.ts
+++ b/server/lib/auth.ts
@@ -5,6 +5,7 @@ import { PluginManager } from '@server/lib/plugins/plugin-manager'
5import { RegisterServerAuthPassOptions } from '@shared/models/plugins/register-server-auth.model' 5import { RegisterServerAuthPassOptions } from '@shared/models/plugins/register-server-auth.model'
6import { logger } from '@server/helpers/logger' 6import { logger } from '@server/helpers/logger'
7import { UserRole } from '@shared/models' 7import { UserRole } from '@shared/models'
8import { revokeToken } from '@server/lib/oauth-model'
8 9
9const oAuthServer = new OAuthServer({ 10const oAuthServer = new OAuthServer({
10 useErrorHandler: true, 11 useErrorHandler: true,
@@ -37,8 +38,9 @@ async function handleIdAndPassLogin (req: express.Request, res: express.Response
37 const aWeight = a.registerAuthOptions.getWeight() 38 const aWeight = a.registerAuthOptions.getWeight()
38 const bWeight = b.registerAuthOptions.getWeight() 39 const bWeight = b.registerAuthOptions.getWeight()
39 40
41 // DESC weight order
40 if (aWeight === bWeight) return 0 42 if (aWeight === bWeight) return 0
41 if (aWeight > bWeight) return 1 43 if (aWeight < bWeight) return 1
42 return -1 44 return -1
43 }) 45 })
44 46
@@ -48,18 +50,24 @@ async function handleIdAndPassLogin (req: express.Request, res: express.Response
48 } 50 }
49 51
50 for (const pluginAuth of pluginAuths) { 52 for (const pluginAuth of pluginAuths) {
53 const authOptions = pluginAuth.registerAuthOptions
54
51 logger.debug( 55 logger.debug(
52 'Using auth method of %s to login %s with weight %d.', 56 'Using auth method %s of plugin %s to login %s with weight %d.',
53 pluginAuth.npmName, loginOptions.id, pluginAuth.registerAuthOptions.getWeight() 57 authOptions.authName, pluginAuth.npmName, loginOptions.id, authOptions.getWeight()
54 ) 58 )
55 59
56 const loginResult = await pluginAuth.registerAuthOptions.login(loginOptions) 60 const loginResult = await authOptions.login(loginOptions)
57 if (loginResult) { 61 if (loginResult) {
58 logger.info('Login success with plugin %s for %s.', pluginAuth.npmName, loginOptions.id) 62 logger.info(
63 'Login success with auth method %s of plugin %s for %s.',
64 authOptions.authName, pluginAuth.npmName, loginOptions.id
65 )
59 66
60 res.locals.bypassLogin = { 67 res.locals.bypassLogin = {
61 bypass: true, 68 bypass: true,
62 pluginName: pluginAuth.npmName, 69 pluginName: pluginAuth.npmName,
70 authName: authOptions.authName,
63 user: { 71 user: {
64 username: loginResult.username, 72 username: loginResult.username,
65 email: loginResult.email, 73 email: loginResult.email,
@@ -75,12 +83,40 @@ async function handleIdAndPassLogin (req: express.Request, res: express.Response
75 return localLogin(req, res, next) 83 return localLogin(req, res, next)
76} 84}
77 85
86async function handleTokenRevocation (req: express.Request, res: express.Response) {
87 const token = res.locals.oauth.token
88
89 PluginManager.Instance.onLogout(token.User.pluginAuth, token.authName)
90
91 await revokeToken(token)
92 .catch(err => {
93 logger.error('Cannot revoke token.', err)
94 })
95
96 // FIXME: uncomment when https://github.com/oauthjs/node-oauth2-server/pull/289 is released
97 // oAuthServer.revoke(req, res, err => {
98 // if (err) {
99 // logger.warn('Error in revoke token handler.', { err })
100 //
101 // return res.status(err.status)
102 // .json({
103 // error: err.message,
104 // code: err.name
105 // })
106 // .end()
107 // }
108 // })
109
110 return res.sendStatus(200)
111}
112
78// --------------------------------------------------------------------------- 113// ---------------------------------------------------------------------------
79 114
80export { 115export {
81 oAuthServer, 116 oAuthServer,
82 handleIdAndPassLogin, 117 handleIdAndPassLogin,
83 onExternalAuthPlugin 118 onExternalAuthPlugin,
119 handleTokenRevocation
84} 120}
85 121
86// --------------------------------------------------------------------------- 122// ---------------------------------------------------------------------------
@@ -88,6 +124,8 @@ export {
88function localLogin (req: express.Request, res: express.Response, next: express.NextFunction) { 124function localLogin (req: express.Request, res: express.Response, next: express.NextFunction) {
89 return oAuthServer.token()(req, res, err => { 125 return oAuthServer.token()(req, res, err => {
90 if (err) { 126 if (err) {
127 logger.warn('Login error.', { err })
128
91 return res.status(err.status) 129 return res.status(err.status)
92 .json({ 130 .json({
93 error: err.message, 131 error: err.message,
diff --git a/server/lib/job-queue/job-queue.ts b/server/lib/job-queue/job-queue.ts
index d8d64caaf..14e181835 100644
--- a/server/lib/job-queue/job-queue.ts
+++ b/server/lib/job-queue/job-queue.ts
@@ -2,9 +2,16 @@ import * as Bull from 'bull'
2import { 2import {
3 ActivitypubFollowPayload, 3 ActivitypubFollowPayload,
4 ActivitypubHttpBroadcastPayload, 4 ActivitypubHttpBroadcastPayload,
5 ActivitypubHttpFetcherPayload, ActivitypubHttpUnicastPayload, EmailPayload, 5 ActivitypubHttpFetcherPayload,
6 ActivitypubHttpUnicastPayload,
7 EmailPayload,
6 JobState, 8 JobState,
7 JobType, RefreshPayload, VideoFileImportPayload, VideoImportPayload, VideoRedundancyPayload, VideoTranscodingPayload 9 JobType,
10 RefreshPayload,
11 VideoFileImportPayload,
12 VideoImportPayload,
13 VideoRedundancyPayload,
14 VideoTranscodingPayload
8} from '../../../shared/models' 15} from '../../../shared/models'
9import { logger } from '../../helpers/logger' 16import { logger } from '../../helpers/logger'
10import { Redis } from '../redis' 17import { Redis } from '../redis'
@@ -13,13 +20,13 @@ import { processActivityPubHttpBroadcast } from './handlers/activitypub-http-bro
13import { processActivityPubHttpFetcher } from './handlers/activitypub-http-fetcher' 20import { processActivityPubHttpFetcher } from './handlers/activitypub-http-fetcher'
14import { processActivityPubHttpUnicast } from './handlers/activitypub-http-unicast' 21import { processActivityPubHttpUnicast } from './handlers/activitypub-http-unicast'
15import { processEmail } from './handlers/email' 22import { processEmail } from './handlers/email'
16import { processVideoTranscoding} from './handlers/video-transcoding' 23import { processVideoTranscoding } from './handlers/video-transcoding'
17import { processActivityPubFollow } from './handlers/activitypub-follow' 24import { processActivityPubFollow } from './handlers/activitypub-follow'
18import { processVideoImport} from './handlers/video-import' 25import { processVideoImport } from './handlers/video-import'
19import { processVideosViews } from './handlers/video-views' 26import { processVideosViews } from './handlers/video-views'
20import { refreshAPObject} from './handlers/activitypub-refresher' 27import { refreshAPObject } from './handlers/activitypub-refresher'
21import { processVideoFileImport} from './handlers/video-file-import' 28import { processVideoFileImport } from './handlers/video-file-import'
22import { processVideoRedundancy} from '@server/lib/job-queue/handlers/video-redundancy' 29import { processVideoRedundancy } from '@server/lib/job-queue/handlers/video-redundancy'
23 30
24type CreateJobArgument = 31type CreateJobArgument =
25 { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } | 32 { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } |
@@ -117,7 +124,7 @@ class JobQueue {
117 124
118 createJob (obj: CreateJobArgument): void { 125 createJob (obj: CreateJobArgument): void {
119 this.createJobWithPromise(obj) 126 this.createJobWithPromise(obj)
120 .catch(err => logger.error('Cannot create job.', { err, obj })) 127 .catch(err => logger.error('Cannot create job.', { err, obj }))
121 } 128 }
122 129
123 createJobWithPromise (obj: CreateJobArgument) { 130 createJobWithPromise (obj: CreateJobArgument) {
diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts
index ea4a67802..7a6ed63be 100644
--- a/server/lib/oauth-model.ts
+++ b/server/lib/oauth-model.ts
@@ -14,6 +14,7 @@ import { MUser } from '@server/typings/models/user/user'
14import { UserAdminFlag } from '@shared/models/users/user-flag.model' 14import { UserAdminFlag } from '@shared/models/users/user-flag.model'
15import { createUserAccountAndChannelAndPlaylist } from './user' 15import { createUserAccountAndChannelAndPlaylist } from './user'
16import { UserRole } from '@shared/models/users/user-role' 16import { UserRole } from '@shared/models/users/user-role'
17import { PluginManager } from '@server/lib/plugins/plugin-manager'
17 18
18type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } 19type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date }
19 20
@@ -82,7 +83,7 @@ async function getUser (usernameOrEmail: string, password: string) {
82 const obj = res.locals.bypassLogin 83 const obj = res.locals.bypassLogin
83 logger.info('Bypassing oauth login by plugin %s.', obj.pluginName) 84 logger.info('Bypassing oauth login by plugin %s.', obj.pluginName)
84 85
85 let user = await UserModel.loadByEmail(obj.user.username) 86 let user = await UserModel.loadByEmail(obj.user.email)
86 if (!user) user = await createUserFromExternal(obj.pluginName, obj.user) 87 if (!user) user = await createUserFromExternal(obj.pluginName, obj.user)
87 88
88 // This user does not belong to this plugin, skip it 89 // This user does not belong to this plugin, skip it
@@ -94,7 +95,8 @@ async function getUser (usernameOrEmail: string, password: string) {
94 logger.debug('Getting User (username/email: ' + usernameOrEmail + ', password: ******).') 95 logger.debug('Getting User (username/email: ' + usernameOrEmail + ', password: ******).')
95 96
96 const user = await UserModel.loadByUsernameOrEmail(usernameOrEmail) 97 const user = await UserModel.loadByUsernameOrEmail(usernameOrEmail)
97 if (!user) return null 98 // If we don't find the user, or if the user belongs to a plugin
99 if (!user || user.pluginAuth !== null) return null
98 100
99 const passwordMatch = await user.isPasswordMatch(password) 101 const passwordMatch = await user.isPasswordMatch(password)
100 if (passwordMatch === false) return null 102 if (passwordMatch === false) return null
@@ -109,8 +111,14 @@ async function getUser (usernameOrEmail: string, password: string) {
109} 111}
110 112
111async function revokeToken (tokenInfo: TokenInfo) { 113async function revokeToken (tokenInfo: TokenInfo) {
114 const res: express.Response = this.request.res
112 const token = await OAuthTokenModel.getByRefreshTokenAndPopulateUser(tokenInfo.refreshToken) 115 const token = await OAuthTokenModel.getByRefreshTokenAndPopulateUser(tokenInfo.refreshToken)
116
113 if (token) { 117 if (token) {
118 if (res.locals.explicitLogout === true && token.User.pluginAuth && token.authName) {
119 PluginManager.Instance.onLogout(token.User.pluginAuth, token.authName)
120 }
121
114 clearCacheByToken(token.accessToken) 122 clearCacheByToken(token.accessToken)
115 123
116 token.destroy() 124 token.destroy()
@@ -123,6 +131,12 @@ async function revokeToken (tokenInfo: TokenInfo) {
123} 131}
124 132
125async function saveToken (token: TokenInfo, client: OAuthClientModel, user: UserModel) { 133async function saveToken (token: TokenInfo, client: OAuthClientModel, user: UserModel) {
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
126 logger.debug('Saving token ' + token.accessToken + ' for client ' + client.id + ' and user ' + user.id + '.') 140 logger.debug('Saving token ' + token.accessToken + ' for client ' + client.id + ' and user ' + user.id + '.')
127 141
128 const tokenToCreate = { 142 const tokenToCreate = {
@@ -130,6 +144,7 @@ async function saveToken (token: TokenInfo, client: OAuthClientModel, user: User
130 accessTokenExpiresAt: token.accessTokenExpiresAt, 144 accessTokenExpiresAt: token.accessTokenExpiresAt,
131 refreshToken: token.refreshToken, 145 refreshToken: token.refreshToken,
132 refreshTokenExpiresAt: token.refreshTokenExpiresAt, 146 refreshTokenExpiresAt: token.refreshTokenExpiresAt,
147 authName,
133 oAuthClientId: client.id, 148 oAuthClientId: client.id,
134 userId: user.id 149 userId: user.id
135 } 150 }
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts
index f78b989f5..9d646b689 100644
--- a/server/lib/plugins/plugin-manager.ts
+++ b/server/lib/plugins/plugin-manager.ts
@@ -76,7 +76,7 @@ export class PluginManager implements ServerHook {
76 return this.registeredPlugins[npmName] 76 return this.registeredPlugins[npmName]
77 } 77 }
78 78
79 getRegisteredPlugin (name: string) { 79 getRegisteredPluginByShortName (name: string) {
80 const npmName = PluginModel.buildNpmName(name, PluginType.PLUGIN) 80 const npmName = PluginModel.buildNpmName(name, PluginType.PLUGIN)
81 const registered = this.getRegisteredPluginOrTheme(npmName) 81 const registered = this.getRegisteredPluginOrTheme(npmName)
82 82
@@ -85,7 +85,7 @@ export class PluginManager implements ServerHook {
85 return registered 85 return registered
86 } 86 }
87 87
88 getRegisteredTheme (name: string) { 88 getRegisteredThemeByShortName (name: string) {
89 const npmName = PluginModel.buildNpmName(name, PluginType.THEME) 89 const npmName = PluginModel.buildNpmName(name, PluginType.THEME)
90 const registered = this.getRegisteredPluginOrTheme(npmName) 90 const registered = this.getRegisteredPluginOrTheme(npmName)
91 91
@@ -132,6 +132,22 @@ export class PluginManager implements ServerHook {
132 return this.translations[locale] || {} 132 return this.translations[locale] || {}
133 } 133 }
134 134
135 onLogout (npmName: string, authName: string) {
136 const plugin = this.getRegisteredPluginOrTheme(npmName)
137 if (!plugin || plugin.type !== PluginType.PLUGIN) return
138
139 const auth = plugin.registerHelpersStore.getIdAndPassAuths()
140 .find(a => a.authName === authName)
141
142 if (auth.onLogout) {
143 try {
144 auth.onLogout()
145 } catch (err) {
146 logger.warn('Cannot run onLogout function from auth %s of plugin %s.', authName, npmName, { err })
147 }
148 }
149 }
150
135 // ###################### Hooks ###################### 151 // ###################### Hooks ######################
136 152
137 async runHook<T> (hookName: ServerHookName, result?: T, params?: any): Promise<T> { 153 async runHook<T> (hookName: ServerHookName, result?: T, params?: any): Promise<T> {
diff --git a/server/lib/plugins/register-helpers-store.ts b/server/lib/plugins/register-helpers-store.ts
index 7e827401f..679ed3650 100644
--- a/server/lib/plugins/register-helpers-store.ts
+++ b/server/lib/plugins/register-helpers-store.ts
@@ -171,6 +171,11 @@ export class RegisterHelpersStore {
171 171
172 private buildRegisterIdAndPassAuth () { 172 private buildRegisterIdAndPassAuth () {
173 return (options: RegisterServerAuthPassOptions) => { 173 return (options: RegisterServerAuthPassOptions) => {
174 if (!options.authName || typeof options.getWeight !== 'function' || typeof options.login !== 'function') {
175 logger.error('Cannot register auth plugin %s: authName of getWeight or login are not valid.', this.npmName)
176 return
177 }
178
174 this.idAndPassAuths.push(options) 179 this.idAndPassAuths.push(options)
175 } 180 }
176 } 181 }