diff options
25 files changed, 273 insertions, 101 deletions
diff --git a/scripts/update-host.ts b/scripts/update-host.ts index 54b31d786..7b07dea04 100755 --- a/scripts/update-host.ts +++ b/scripts/update-host.ts | |||
@@ -11,15 +11,15 @@ import { | |||
11 | getVideoAnnounceActivityPubUrl, | 11 | getVideoAnnounceActivityPubUrl, |
12 | getVideoChannelActivityPubUrl, | 12 | getVideoChannelActivityPubUrl, |
13 | getVideoCommentActivityPubUrl | 13 | getVideoCommentActivityPubUrl |
14 | } from '../server/lib/activitypub' | 14 | } from '../server/lib/activitypub/url' |
15 | import { VideoShareModel } from '../server/models/video/video-share' | 15 | import { VideoShareModel } from '../server/models/video/video-share' |
16 | import { VideoCommentModel } from '../server/models/video/video-comment' | 16 | import { VideoCommentModel } from '../server/models/video/video-comment' |
17 | import { getServerActor } from '../server/helpers/utils' | ||
18 | import { AccountModel } from '../server/models/account/account' | 17 | import { AccountModel } from '../server/models/account/account' |
19 | import { VideoChannelModel } from '../server/models/video/video-channel' | 18 | import { VideoChannelModel } from '../server/models/video/video-channel' |
20 | import { VideoStreamingPlaylistModel } from '../server/models/video/video-streaming-playlist' | 19 | import { VideoStreamingPlaylistModel } from '../server/models/video/video-streaming-playlist' |
21 | import { initDatabaseModels } from '../server/initializers' | 20 | import { initDatabaseModels } from '../server/initializers' |
22 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' | 21 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' |
22 | import { getServerActor } from '@server/models/application/application' | ||
23 | 23 | ||
24 | run() | 24 | run() |
25 | .then(() => process.exit(0)) | 25 | .then(() => process.exit(0)) |
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts index 3bbb0a43e..ccdc610a2 100644 --- a/server/controllers/api/accounts.ts +++ b/server/controllers/api/accounts.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { getFormattedObjects} from '../../helpers/utils' | 2 | import { getFormattedObjects } from '../../helpers/utils' |
3 | import { | 3 | import { |
4 | asyncMiddleware, | 4 | asyncMiddleware, |
5 | authenticate, | 5 | authenticate, |
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts index 82e9ef898..23823c9fb 100644 --- a/server/controllers/api/server/follows.ts +++ b/server/controllers/api/server/follows.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserRight } from '../../../../shared/models/users' | 2 | import { UserRight } from '../../../../shared/models/users' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { getFormattedObjects} from '../../../helpers/utils' | 4 | import { getFormattedObjects } from '../../../helpers/utils' |
5 | import { SERVER_ACTOR_NAME } from '../../../initializers/constants' | 5 | import { SERVER_ACTOR_NAME } from '../../../initializers/constants' |
6 | import { sendAccept, sendReject, sendUndoFollow } from '../../../lib/activitypub/send' | 6 | import { sendAccept, sendReject, sendUndoFollow } from '../../../lib/activitypub/send' |
7 | import { | 7 | import { |
diff --git a/server/controllers/api/server/server-blocklist.ts b/server/controllers/api/server/server-blocklist.ts index 008b8d4ea..f849b15c7 100644 --- a/server/controllers/api/server/server-blocklist.ts +++ b/server/controllers/api/server/server-blocklist.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import 'multer' | 2 | import 'multer' |
3 | import { getFormattedObjects} from '../../../helpers/utils' | 3 | import { getFormattedObjects } from '../../../helpers/utils' |
4 | import { | 4 | import { |
5 | asyncMiddleware, | 5 | asyncMiddleware, |
6 | asyncRetryTransactionMiddleware, | 6 | asyncRetryTransactionMiddleware, |
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index b30f42b43..c488f720b 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts | |||
@@ -26,12 +26,12 @@ import { | |||
26 | usersUpdateValidator | 26 | usersUpdateValidator |
27 | } from '../../../middlewares' | 27 | } from '../../../middlewares' |
28 | import { | 28 | import { |
29 | ensureCanManageUser, | ||
29 | usersAskResetPasswordValidator, | 30 | usersAskResetPasswordValidator, |
30 | usersAskSendVerifyEmailValidator, | 31 | usersAskSendVerifyEmailValidator, |
31 | usersBlockingValidator, | 32 | usersBlockingValidator, |
32 | usersResetPasswordValidator, | 33 | usersResetPasswordValidator, |
33 | usersVerifyEmailValidator, | 34 | usersVerifyEmailValidator |
34 | ensureCanManageUser | ||
35 | } from '../../../middlewares/validators' | 35 | } from '../../../middlewares/validators' |
36 | import { UserModel } from '../../../models/account/user' | 36 | import { UserModel } from '../../../models/account/user' |
37 | import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' | 37 | import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' |
@@ -49,15 +49,10 @@ import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' | |||
49 | import { UserRegister } from '../../../../shared/models/users/user-register.model' | 49 | import { UserRegister } from '../../../../shared/models/users/user-register.model' |
50 | import { MUser, MUserAccountDefault } from '@server/typings/models' | 50 | import { MUser, MUserAccountDefault } from '@server/typings/models' |
51 | import { Hooks } from '@server/lib/plugins/hooks' | 51 | import { Hooks } from '@server/lib/plugins/hooks' |
52 | import { handleIdAndPassLogin } from '@server/lib/auth' | 52 | import { tokensRouter } from '@server/controllers/api/users/token' |
53 | 53 | ||
54 | const auditLogger = auditLoggerFactory('users') | 54 | const auditLogger = auditLoggerFactory('users') |
55 | 55 | ||
56 | const loginRateLimiter = RateLimit({ | ||
57 | windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS, | ||
58 | max: CONFIG.RATES_LIMIT.LOGIN.MAX | ||
59 | }) | ||
60 | |||
61 | // @ts-ignore | 56 | // @ts-ignore |
62 | const signupRateLimiter = RateLimit({ | 57 | const signupRateLimiter = RateLimit({ |
63 | windowMs: CONFIG.RATES_LIMIT.SIGNUP.WINDOW_MS, | 58 | windowMs: CONFIG.RATES_LIMIT.SIGNUP.WINDOW_MS, |
@@ -72,6 +67,7 @@ const askSendEmailLimiter = new RateLimit({ | |||
72 | }) | 67 | }) |
73 | 68 | ||
74 | const usersRouter = express.Router() | 69 | const usersRouter = express.Router() |
70 | usersRouter.use('/', tokensRouter) | ||
75 | usersRouter.use('/', myNotificationsRouter) | 71 | usersRouter.use('/', myNotificationsRouter) |
76 | usersRouter.use('/', mySubscriptionsRouter) | 72 | usersRouter.use('/', mySubscriptionsRouter) |
77 | usersRouter.use('/', myBlocklistRouter) | 73 | usersRouter.use('/', myBlocklistRouter) |
@@ -168,23 +164,6 @@ usersRouter.post('/:id/verify-email', | |||
168 | asyncMiddleware(verifyUserEmail) | 164 | asyncMiddleware(verifyUserEmail) |
169 | ) | 165 | ) |
170 | 166 | ||
171 | usersRouter.post('/token', | ||
172 | loginRateLimiter, | ||
173 | handleIdAndPassLogin, | ||
174 | tokenSuccess | ||
175 | ) | ||
176 | usersRouter.post('/token', | ||
177 | loginRateLimiter, | ||
178 | handleIdAndPassLogin, | ||
179 | tokenSuccess | ||
180 | ) | ||
181 | usersRouter.post('/revoke-token', | ||
182 | loginRateLimiter, | ||
183 | handleIdAndPassLogin, | ||
184 | tokenSuccess | ||
185 | ) | ||
186 | // TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged, implement revoke token route | ||
187 | |||
188 | // --------------------------------------------------------------------------- | 167 | // --------------------------------------------------------------------------- |
189 | 168 | ||
190 | export { | 169 | export { |
@@ -391,12 +370,6 @@ async function verifyUserEmail (req: express.Request, res: express.Response) { | |||
391 | return res.status(204).end() | 370 | return res.status(204).end() |
392 | } | 371 | } |
393 | 372 | ||
394 | function tokenSuccess (req: express.Request) { | ||
395 | const username = req.body.username | ||
396 | |||
397 | Hooks.runAction('action:api.user.oauth2-got-token', { username, ip: req.ip }) | ||
398 | } | ||
399 | |||
400 | async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) { | 373 | async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) { |
401 | const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) | 374 | const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) |
402 | 375 | ||
diff --git a/server/controllers/api/users/token.ts b/server/controllers/api/users/token.ts new file mode 100644 index 000000000..9694f9e5e --- /dev/null +++ b/server/controllers/api/users/token.ts | |||
@@ -0,0 +1,38 @@ | |||
1 | import { handleIdAndPassLogin, handleTokenRevocation } from '@server/lib/auth' | ||
2 | import * as RateLimit from 'express-rate-limit' | ||
3 | import { CONFIG } from '@server/initializers/config' | ||
4 | import * as express from 'express' | ||
5 | import { Hooks } from '@server/lib/plugins/hooks' | ||
6 | import { asyncMiddleware, authenticate } from '@server/middlewares' | ||
7 | |||
8 | const tokensRouter = express.Router() | ||
9 | |||
10 | const loginRateLimiter = RateLimit({ | ||
11 | windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS, | ||
12 | max: CONFIG.RATES_LIMIT.LOGIN.MAX | ||
13 | }) | ||
14 | |||
15 | tokensRouter.post('/token', | ||
16 | loginRateLimiter, | ||
17 | handleIdAndPassLogin, | ||
18 | tokenSuccess | ||
19 | ) | ||
20 | |||
21 | tokensRouter.post('/revoke-token', | ||
22 | authenticate, | ||
23 | asyncMiddleware(handleTokenRevocation), | ||
24 | tokenSuccess | ||
25 | ) | ||
26 | |||
27 | // --------------------------------------------------------------------------- | ||
28 | |||
29 | export { | ||
30 | tokensRouter | ||
31 | } | ||
32 | // --------------------------------------------------------------------------- | ||
33 | |||
34 | function tokenSuccess (req: express.Request) { | ||
35 | const username = req.body.username | ||
36 | |||
37 | Hooks.runAction('action:api.user.oauth2-got-token', { username, ip: req.ip }) | ||
38 | } | ||
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts index faef5ba4b..d779f1aab 100644 --- a/server/controllers/api/video-channel.ts +++ b/server/controllers/api/video-channel.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { getFormattedObjects} from '../../helpers/utils' | 2 | import { getFormattedObjects } from '../../helpers/utils' |
3 | import { | 3 | import { |
4 | asyncMiddleware, | 4 | asyncMiddleware, |
5 | asyncRetryTransactionMiddleware, | 5 | asyncRetryTransactionMiddleware, |
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts index 49ac3c80e..375d711fd 100644 --- a/server/controllers/api/video-playlist.ts +++ b/server/controllers/api/video-playlist.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { getFormattedObjects} from '../../helpers/utils' | 2 | import { getFormattedObjects } from '../../helpers/utils' |
3 | import { | 3 | import { |
4 | asyncMiddleware, | 4 | asyncMiddleware, |
5 | asyncRetryTransactionMiddleware, | 5 | asyncRetryTransactionMiddleware, |
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' | |||
5 | import { RegisterServerAuthPassOptions } from '@shared/models/plugins/register-server-auth.model' | 5 | import { RegisterServerAuthPassOptions } from '@shared/models/plugins/register-server-auth.model' |
6 | import { logger } from '@server/helpers/logger' | 6 | import { logger } from '@server/helpers/logger' |
7 | import { UserRole } from '@shared/models' | 7 | import { UserRole } from '@shared/models' |
8 | import { revokeToken } from '@server/lib/oauth-model' | ||
8 | 9 | ||
9 | const oAuthServer = new OAuthServer({ | 10 | const 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 | ||
86 | async 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 | ||
80 | export { | 115 | export { |
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 { | |||
88 | function localLogin (req: express.Request, res: express.Response, next: express.NextFunction) { | 124 | function 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' | |||
2 | import { | 2 | import { |
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' |
9 | import { logger } from '../../helpers/logger' | 16 | import { logger } from '../../helpers/logger' |
10 | import { Redis } from '../redis' | 17 | import { Redis } from '../redis' |
@@ -13,13 +20,13 @@ import { processActivityPubHttpBroadcast } from './handlers/activitypub-http-bro | |||
13 | import { processActivityPubHttpFetcher } from './handlers/activitypub-http-fetcher' | 20 | import { processActivityPubHttpFetcher } from './handlers/activitypub-http-fetcher' |
14 | import { processActivityPubHttpUnicast } from './handlers/activitypub-http-unicast' | 21 | import { processActivityPubHttpUnicast } from './handlers/activitypub-http-unicast' |
15 | import { processEmail } from './handlers/email' | 22 | import { processEmail } from './handlers/email' |
16 | import { processVideoTranscoding} from './handlers/video-transcoding' | 23 | import { processVideoTranscoding } from './handlers/video-transcoding' |
17 | import { processActivityPubFollow } from './handlers/activitypub-follow' | 24 | import { processActivityPubFollow } from './handlers/activitypub-follow' |
18 | import { processVideoImport} from './handlers/video-import' | 25 | import { processVideoImport } from './handlers/video-import' |
19 | import { processVideosViews } from './handlers/video-views' | 26 | import { processVideosViews } from './handlers/video-views' |
20 | import { refreshAPObject} from './handlers/activitypub-refresher' | 27 | import { refreshAPObject } from './handlers/activitypub-refresher' |
21 | import { processVideoFileImport} from './handlers/video-file-import' | 28 | import { processVideoFileImport } from './handlers/video-file-import' |
22 | import { processVideoRedundancy} from '@server/lib/job-queue/handlers/video-redundancy' | 29 | import { processVideoRedundancy } from '@server/lib/job-queue/handlers/video-redundancy' |
23 | 30 | ||
24 | type CreateJobArgument = | 31 | type 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' | |||
14 | import { UserAdminFlag } from '@shared/models/users/user-flag.model' | 14 | import { UserAdminFlag } from '@shared/models/users/user-flag.model' |
15 | import { createUserAccountAndChannelAndPlaylist } from './user' | 15 | import { createUserAccountAndChannelAndPlaylist } from './user' |
16 | import { UserRole } from '@shared/models/users/user-role' | 16 | import { UserRole } from '@shared/models/users/user-role' |
17 | import { PluginManager } from '@server/lib/plugins/plugin-manager' | ||
17 | 18 | ||
18 | type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } | 19 | type 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 | ||
111 | async function revokeToken (tokenInfo: TokenInfo) { | 113 | async 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 | ||
125 | async function saveToken (token: TokenInfo, client: OAuthClientModel, user: UserModel) { | 133 | async 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 | } |
diff --git a/server/middlewares/oauth.ts b/server/middlewares/oauth.ts index 4ae7f18c2..9d0eaa51f 100644 --- a/server/middlewares/oauth.ts +++ b/server/middlewares/oauth.ts | |||
@@ -2,7 +2,7 @@ import * as express from 'express' | |||
2 | import { logger } from '../helpers/logger' | 2 | import { logger } from '../helpers/logger' |
3 | import { Socket } from 'socket.io' | 3 | import { Socket } from 'socket.io' |
4 | import { getAccessToken } from '../lib/oauth-model' | 4 | import { getAccessToken } from '../lib/oauth-model' |
5 | import { handleIdAndPassLogin, oAuthServer } from '@server/lib/auth' | 5 | import { oAuthServer } from '@server/lib/auth' |
6 | 6 | ||
7 | function authenticate (req: express.Request, res: express.Response, next: express.NextFunction, authenticateInQuery = false) { | 7 | function authenticate (req: express.Request, res: express.Response, next: express.NextFunction, authenticateInQuery = false) { |
8 | const options = authenticateInQuery ? { allowBearerTokensInQueryString: true } : {} | 8 | const options = authenticateInQuery ? { allowBearerTokensInQueryString: true } : {} |
diff --git a/server/middlewares/validators/themes.ts b/server/middlewares/validators/themes.ts index 24a9673f7..82794656d 100644 --- a/server/middlewares/validators/themes.ts +++ b/server/middlewares/validators/themes.ts | |||
@@ -16,7 +16,7 @@ const serveThemeCSSValidator = [ | |||
16 | 16 | ||
17 | if (areValidationErrors(req, res)) return | 17 | if (areValidationErrors(req, res)) return |
18 | 18 | ||
19 | const theme = PluginManager.Instance.getRegisteredTheme(req.params.themeName) | 19 | const theme = PluginManager.Instance.getRegisteredThemeByShortName(req.params.themeName) |
20 | 20 | ||
21 | if (!theme || theme.version !== req.params.themeVersion) { | 21 | if (!theme || theme.version !== req.params.themeVersion) { |
22 | return res.sendStatus(404) | 22 | return res.sendStatus(404) |
diff --git a/server/models/account/user.ts b/server/models/account/user.ts index d0d9a0508..1bff955df 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts | |||
@@ -222,7 +222,7 @@ enum ScopeNames { | |||
222 | export class UserModel extends Model<UserModel> { | 222 | export class UserModel extends Model<UserModel> { |
223 | 223 | ||
224 | @AllowNull(true) | 224 | @AllowNull(true) |
225 | @Is('UserPassword', value => throwIfNotValid(value, isUserPasswordValid, 'user password')) | 225 | @Is('UserPassword', value => throwIfNotValid(value, isUserPasswordValid, 'user password', true)) |
226 | @Column | 226 | @Column |
227 | password: string | 227 | password: string |
228 | 228 | ||
@@ -388,7 +388,7 @@ export class UserModel extends Model<UserModel> { | |||
388 | @BeforeCreate | 388 | @BeforeCreate |
389 | @BeforeUpdate | 389 | @BeforeUpdate |
390 | static cryptPasswordIfNeeded (instance: UserModel) { | 390 | static cryptPasswordIfNeeded (instance: UserModel) { |
391 | if (instance.changed('password')) { | 391 | if (instance.changed('password') && instance.password) { |
392 | return cryptPassword(instance.password) | 392 | return cryptPassword(instance.password) |
393 | .then(hash => { | 393 | .then(hash => { |
394 | instance.password = hash | 394 | instance.password = hash |
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts index d2101ce86..e73c4be7d 100644 --- a/server/models/oauth/oauth-token.ts +++ b/server/models/oauth/oauth-token.ts | |||
@@ -97,6 +97,9 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> { | |||
97 | @Column | 97 | @Column |
98 | refreshTokenExpiresAt: Date | 98 | refreshTokenExpiresAt: Date |
99 | 99 | ||
100 | @Column | ||
101 | authName: string | ||
102 | |||
100 | @CreatedAt | 103 | @CreatedAt |
101 | createdAt: Date | 104 | createdAt: Date |
102 | 105 | ||
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index 7ba04a4ca..60fbd2a20 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts | |||
@@ -2,8 +2,9 @@ | |||
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { MyUser, User, UserRole, Video, VideoPlaylistType, VideoAbuseState, VideoAbuseUpdate } from '../../../../shared/index' | 5 | import { MyUser, User, UserRole, Video, VideoAbuseState, VideoAbuseUpdate, VideoPlaylistType } from '../../../../shared/index' |
6 | import { | 6 | import { |
7 | addVideoCommentThread, | ||
7 | blockUser, | 8 | blockUser, |
8 | cleanupTests, | 9 | cleanupTests, |
9 | createUser, | 10 | createUser, |
@@ -11,12 +12,14 @@ import { | |||
11 | flushAndRunServer, | 12 | flushAndRunServer, |
12 | getAccountRatings, | 13 | getAccountRatings, |
13 | getBlacklistedVideosList, | 14 | getBlacklistedVideosList, |
15 | getCustomConfig, | ||
14 | getMyUserInformation, | 16 | getMyUserInformation, |
15 | getMyUserVideoQuotaUsed, | 17 | getMyUserVideoQuotaUsed, |
16 | getMyUserVideoRating, | 18 | getMyUserVideoRating, |
17 | getUserInformation, | 19 | getUserInformation, |
18 | getUsersList, | 20 | getUsersList, |
19 | getUsersListPaginationAndSort, | 21 | getUsersListPaginationAndSort, |
22 | getVideoAbusesList, | ||
20 | getVideoChannel, | 23 | getVideoChannel, |
21 | getVideosList, | 24 | getVideosList, |
22 | installPlugin, | 25 | installPlugin, |
@@ -26,21 +29,21 @@ import { | |||
26 | registerUserWithChannel, | 29 | registerUserWithChannel, |
27 | removeUser, | 30 | removeUser, |
28 | removeVideo, | 31 | removeVideo, |
32 | reportVideoAbuse, | ||
29 | ServerInfo, | 33 | ServerInfo, |
30 | testImage, | 34 | testImage, |
31 | unblockUser, | 35 | unblockUser, |
36 | updateCustomSubConfig, | ||
32 | updateMyAvatar, | 37 | updateMyAvatar, |
33 | updateMyUser, | 38 | updateMyUser, |
34 | updateUser, | 39 | updateUser, |
40 | updateVideoAbuse, | ||
35 | uploadVideo, | 41 | uploadVideo, |
36 | userLogin, | 42 | userLogin, |
37 | reportVideoAbuse, | 43 | waitJobs |
38 | addVideoCommentThread, | ||
39 | updateVideoAbuse, | ||
40 | getVideoAbusesList, updateCustomSubConfig, getCustomConfig, waitJobs | ||
41 | } from '../../../../shared/extra-utils' | 44 | } from '../../../../shared/extra-utils' |
42 | import { follow } from '../../../../shared/extra-utils/server/follows' | 45 | import { follow } from '../../../../shared/extra-utils/server/follows' |
43 | import { setAccessTokensToServers, logout } from '../../../../shared/extra-utils/users/login' | 46 | import { logout, serverLogin, setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' |
44 | import { getMyVideos } from '../../../../shared/extra-utils/videos/videos' | 47 | import { getMyVideos } from '../../../../shared/extra-utils/videos/videos' |
45 | import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' | 48 | import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' |
46 | import { CustomConfig } from '@shared/models/server' | 49 | import { CustomConfig } from '@shared/models/server' |
@@ -60,7 +63,14 @@ describe('Test users', function () { | |||
60 | 63 | ||
61 | before(async function () { | 64 | before(async function () { |
62 | this.timeout(30000) | 65 | this.timeout(30000) |
63 | server = await flushAndRunServer(1) | 66 | |
67 | server = await flushAndRunServer(1, { | ||
68 | rates_limit: { | ||
69 | login: { | ||
70 | max: 30 | ||
71 | } | ||
72 | } | ||
73 | }) | ||
64 | 74 | ||
65 | await setAccessTokensToServers([ server ]) | 75 | await setAccessTokensToServers([ server ]) |
66 | 76 | ||
@@ -217,8 +227,6 @@ describe('Test users', function () { | |||
217 | await uploadVideo(server.url, server.accessToken, { name: 'video' }, 401) | 227 | await uploadVideo(server.url, server.accessToken, { name: 'video' }, 401) |
218 | }) | 228 | }) |
219 | 229 | ||
220 | it('Should not be able to remove a video') | ||
221 | |||
222 | it('Should not be able to rate a video', async function () { | 230 | it('Should not be able to rate a video', async function () { |
223 | const path = '/api/v1/videos/' | 231 | const path = '/api/v1/videos/' |
224 | const data = { | 232 | const data = { |
@@ -235,13 +243,17 @@ describe('Test users', function () { | |||
235 | await makePutBodyRequest(options) | 243 | await makePutBodyRequest(options) |
236 | }) | 244 | }) |
237 | 245 | ||
238 | it('Should be able to login again') | 246 | it('Should be able to login again', async function () { |
247 | server.accessToken = await serverLogin(server) | ||
248 | }) | ||
239 | 249 | ||
240 | it('Should have an expired access token') | 250 | it('Should have an expired access token') |
241 | 251 | ||
242 | it('Should refresh the token') | 252 | it('Should refresh the token') |
243 | 253 | ||
244 | it('Should be able to upload a video again') | 254 | it('Should be able to get my user information again', async function () { |
255 | await getMyUserInformation(server.url, server.accessToken) | ||
256 | }) | ||
245 | }) | 257 | }) |
246 | 258 | ||
247 | describe('Creating a user', function () { | 259 | describe('Creating a user', function () { |
diff --git a/server/tests/fixtures/peertube-plugin-test-id-pass-auth-one/main.js b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-one/main.js index 4755ed643..9fc12a3e3 100644 --- a/server/tests/fixtures/peertube-plugin-test-id-pass-auth-one/main.js +++ b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-one/main.js | |||
@@ -3,7 +3,7 @@ async function register ({ | |||
3 | peertubeHelpers | 3 | peertubeHelpers |
4 | }) { | 4 | }) { |
5 | registerIdAndPassAuth({ | 5 | registerIdAndPassAuth({ |
6 | type: 'id-and-pass', | 6 | authName: 'spyro-auth', |
7 | 7 | ||
8 | onLogout: () => { | 8 | onLogout: () => { |
9 | peertubeHelpers.logger.info('On logout for auth 1 - 1') | 9 | peertubeHelpers.logger.info('On logout for auth 1 - 1') |
@@ -16,7 +16,7 @@ async function register ({ | |||
16 | return Promise.resolve({ | 16 | return Promise.resolve({ |
17 | username: 'spyro', | 17 | username: 'spyro', |
18 | email: 'spyro@example.com', | 18 | email: 'spyro@example.com', |
19 | role: 0, | 19 | role: 2, |
20 | displayName: 'Spyro the Dragon' | 20 | displayName: 'Spyro the Dragon' |
21 | }) | 21 | }) |
22 | } | 22 | } |
@@ -26,7 +26,7 @@ async function register ({ | |||
26 | }) | 26 | }) |
27 | 27 | ||
28 | registerIdAndPassAuth({ | 28 | registerIdAndPassAuth({ |
29 | type: 'id-and-pass', | 29 | authName: 'crash-auth', |
30 | 30 | ||
31 | onLogout: () => { | 31 | onLogout: () => { |
32 | peertubeHelpers.logger.info('On logout for auth 1 - 2') | 32 | peertubeHelpers.logger.info('On logout for auth 1 - 2') |
@@ -39,7 +39,7 @@ async function register ({ | |||
39 | return Promise.resolve({ | 39 | return Promise.resolve({ |
40 | username: 'crash', | 40 | username: 'crash', |
41 | email: 'crash@example.com', | 41 | email: 'crash@example.com', |
42 | role: 2, | 42 | role: 1, |
43 | displayName: 'Crash Bandicoot' | 43 | displayName: 'Crash Bandicoot' |
44 | }) | 44 | }) |
45 | } | 45 | } |
diff --git a/server/tests/fixtures/peertube-plugin-test-id-pass-auth-three/main.js b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-three/main.js index 2a15b3754..372f3fa0c 100644 --- a/server/tests/fixtures/peertube-plugin-test-id-pass-auth-three/main.js +++ b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-three/main.js | |||
@@ -3,7 +3,7 @@ async function register ({ | |||
3 | peertubeHelpers | 3 | peertubeHelpers |
4 | }) { | 4 | }) { |
5 | registerIdAndPassAuth({ | 5 | registerIdAndPassAuth({ |
6 | type: 'id-and-pass', | 6 | authName: 'laguna-bad-auth', |
7 | 7 | ||
8 | onLogout: () => { | 8 | onLogout: () => { |
9 | peertubeHelpers.logger.info('On logout for auth 3 - 1') | 9 | peertubeHelpers.logger.info('On logout for auth 3 - 1') |
diff --git a/server/tests/fixtures/peertube-plugin-test-id-pass-auth-two/main.js b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-two/main.js index edfc870c0..c0e560019 100644 --- a/server/tests/fixtures/peertube-plugin-test-id-pass-auth-two/main.js +++ b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-two/main.js | |||
@@ -3,7 +3,7 @@ async function register ({ | |||
3 | peertubeHelpers | 3 | peertubeHelpers |
4 | }) { | 4 | }) { |
5 | registerIdAndPassAuth({ | 5 | registerIdAndPassAuth({ |
6 | type: 'id-and-pass', | 6 | authName: 'laguna-auth', |
7 | 7 | ||
8 | onLogout: () => { | 8 | onLogout: () => { |
9 | peertubeHelpers.logger.info('On logout for auth 2 - 1') | 9 | peertubeHelpers.logger.info('On logout for auth 2 - 1') |
diff --git a/server/tests/plugins/id-and-pass-auth.ts b/server/tests/plugins/id-and-pass-auth.ts index 5b4d1a1db..45fa7856c 100644 --- a/server/tests/plugins/id-and-pass-auth.ts +++ b/server/tests/plugins/id-and-pass-auth.ts | |||
@@ -1,11 +1,23 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers' | 4 | import { cleanupTests, flushAndRunServer, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers' |
5 | import { getPluginTestPath, installPlugin, setAccessTokensToServers } from '../../../shared/extra-utils' | 5 | import { |
6 | getMyUserInformation, | ||
7 | getPluginTestPath, | ||
8 | installPlugin, | ||
9 | logout, | ||
10 | setAccessTokensToServers, | ||
11 | uninstallPlugin, | ||
12 | updateMyUser, | ||
13 | userLogin | ||
14 | } from '../../../shared/extra-utils' | ||
15 | import { User, UserRole } from '@shared/models' | ||
16 | import { expect } from 'chai' | ||
6 | 17 | ||
7 | describe('Test id and pass auth plugins', function () { | 18 | describe('Test id and pass auth plugins', function () { |
8 | let server: ServerInfo | 19 | let server: ServerInfo |
20 | let crashToken: string | ||
9 | 21 | ||
10 | before(async function () { | 22 | before(async function () { |
11 | this.timeout(30000) | 23 | this.timeout(30000) |
@@ -13,54 +25,97 @@ describe('Test id and pass auth plugins', function () { | |||
13 | server = await flushAndRunServer(1) | 25 | server = await flushAndRunServer(1) |
14 | await setAccessTokensToServers([ server ]) | 26 | await setAccessTokensToServers([ server ]) |
15 | 27 | ||
16 | await installPlugin({ | 28 | for (const suffix of [ 'one', 'two', 'three' ]) { |
17 | url: server.url, | 29 | await installPlugin({ |
18 | accessToken: server.accessToken, | 30 | url: server.url, |
19 | path: getPluginTestPath('-id-pass-auth-one') | 31 | accessToken: server.accessToken, |
20 | }) | 32 | path: getPluginTestPath('-id-pass-auth-' + suffix) |
21 | 33 | }) | |
22 | await installPlugin({ | 34 | } |
23 | url: server.url, | ||
24 | accessToken: server.accessToken, | ||
25 | path: getPluginTestPath('-id-pass-auth-two') | ||
26 | }) | ||
27 | }) | 35 | }) |
28 | 36 | ||
29 | it('Should not login', async function() { | 37 | it('Should not login', async function () { |
30 | 38 | await userLogin(server, { username: 'toto', password: 'password' }, 400) | |
31 | }) | 39 | }) |
32 | 40 | ||
33 | it('Should login Spyro, create the user and use the token', async function() { | 41 | it('Should login Spyro, create the user and use the token', async function () { |
42 | const accessToken = await userLogin(server, { username: 'spyro', password: 'spyro password' }) | ||
34 | 43 | ||
44 | const res = await getMyUserInformation(server.url, accessToken) | ||
45 | |||
46 | const body: User = res.body | ||
47 | expect(body.username).to.equal('spyro') | ||
48 | expect(body.account.displayName).to.equal('Spyro the Dragon') | ||
49 | expect(body.role).to.equal(UserRole.USER) | ||
35 | }) | 50 | }) |
36 | 51 | ||
37 | it('Should login Crash, create the user and use the token', async function() { | 52 | it('Should login Crash, create the user and use the token', async function () { |
53 | crashToken = await userLogin(server, { username: 'crash', password: 'crash password' }) | ||
54 | |||
55 | const res = await getMyUserInformation(server.url, crashToken) | ||
38 | 56 | ||
57 | const body: User = res.body | ||
58 | expect(body.username).to.equal('crash') | ||
59 | expect(body.account.displayName).to.equal('Crash Bandicoot') | ||
60 | expect(body.role).to.equal(UserRole.MODERATOR) | ||
39 | }) | 61 | }) |
40 | 62 | ||
41 | it('Should login the first Laguna, create the user and use the token', async function() { | 63 | it('Should login the first Laguna, create the user and use the token', async function () { |
64 | const accessToken = await userLogin(server, { username: 'laguna', password: 'laguna password' }) | ||
42 | 65 | ||
66 | const res = await getMyUserInformation(server.url, accessToken) | ||
67 | |||
68 | const body: User = res.body | ||
69 | expect(body.username).to.equal('laguna') | ||
70 | expect(body.account.displayName).to.equal('laguna') | ||
71 | expect(body.role).to.equal(UserRole.USER) | ||
43 | }) | 72 | }) |
44 | 73 | ||
45 | it('Should update Crash profile', async function () { | 74 | it('Should update Crash profile', async function () { |
75 | await updateMyUser({ | ||
76 | url: server.url, | ||
77 | accessToken: crashToken, | ||
78 | displayName: 'Beautiful Crash', | ||
79 | description: 'Mutant eastern barred bandicoot' | ||
80 | }) | ||
46 | 81 | ||
82 | const res = await getMyUserInformation(server.url, crashToken) | ||
83 | |||
84 | const body: User = res.body | ||
85 | expect(body.account.displayName).to.equal('Beautiful Crash') | ||
86 | expect(body.account.description).to.equal('Mutant eastern barred bandicoot') | ||
47 | }) | 87 | }) |
48 | 88 | ||
49 | it('Should logout Crash', async function () { | 89 | it('Should logout Crash', async function () { |
50 | 90 | await logout(server.url, crashToken) | |
51 | // test token | ||
52 | }) | 91 | }) |
53 | 92 | ||
54 | it('Should have logged the Crash logout', async function () { | 93 | it('Should have logged out Crash', async function () { |
94 | await getMyUserInformation(server.url, crashToken, 401) | ||
55 | 95 | ||
96 | await waitUntilLog(server, 'On logout for auth 1 - 2') | ||
56 | }) | 97 | }) |
57 | 98 | ||
58 | it('Should login Crash and keep the old existing profile', async function () { | 99 | it('Should login Crash and keep the old existing profile', async function () { |
100 | crashToken = await userLogin(server, { username: 'crash', password: 'crash password' }) | ||
59 | 101 | ||
102 | const res = await getMyUserInformation(server.url, crashToken) | ||
103 | |||
104 | const body: User = res.body | ||
105 | expect(body.username).to.equal('crash') | ||
106 | expect(body.account.displayName).to.equal('Beautiful Crash') | ||
107 | expect(body.account.description).to.equal('Mutant eastern barred bandicoot') | ||
108 | expect(body.role).to.equal(UserRole.MODERATOR) | ||
60 | }) | 109 | }) |
61 | 110 | ||
62 | it('Should uninstall the plugin one and do not login existing Crash', async function () { | 111 | it('Should uninstall the plugin one and do not login existing Crash', async function () { |
112 | await uninstallPlugin({ | ||
113 | url: server.url, | ||
114 | accessToken: server.accessToken, | ||
115 | npmName: 'peertube-plugin-test-id-pass-auth-one' | ||
116 | }) | ||
63 | 117 | ||
118 | await userLogin(server, { username: 'crash', password: 'crash password' }, 400) | ||
64 | }) | 119 | }) |
65 | 120 | ||
66 | after(async function () { | 121 | after(async function () { |
diff --git a/server/typings/express.ts b/server/typings/express.ts index ebccf7f7d..2d12a486a 100644 --- a/server/typings/express.ts +++ b/server/typings/express.ts | |||
@@ -37,6 +37,7 @@ declare module 'express' { | |||
37 | bypassLogin?: { | 37 | bypassLogin?: { |
38 | bypass: boolean | 38 | bypass: boolean |
39 | pluginName: string | 39 | pluginName: string |
40 | authName?: string | ||
40 | user: { | 41 | user: { |
41 | username: string | 42 | username: string |
42 | email: string | 43 | email: string |
@@ -45,6 +46,8 @@ declare module 'express' { | |||
45 | } | 46 | } |
46 | } | 47 | } |
47 | 48 | ||
49 | explicitLogout: boolean | ||
50 | |||
48 | videoAll?: MVideoFullLight | 51 | videoAll?: MVideoFullLight |
49 | onlyImmutableVideo?: MVideoImmutable | 52 | onlyImmutableVideo?: MVideoImmutable |
50 | onlyVideo?: MVideoThumbnail | 53 | onlyVideo?: MVideoThumbnail |
diff --git a/server/typings/plugins/register-server-option.model.ts b/server/typings/plugins/register-server-option.model.ts index 0c0993c14..bcabf2fec 100644 --- a/server/typings/plugins/register-server-option.model.ts +++ b/server/typings/plugins/register-server-option.model.ts | |||
@@ -9,7 +9,11 @@ import { Logger } from 'winston' | |||
9 | import { Router } from 'express' | 9 | import { Router } from 'express' |
10 | import { PluginVideoPrivacyManager } from '@shared/models/plugins/plugin-video-privacy-manager.model' | 10 | import { PluginVideoPrivacyManager } from '@shared/models/plugins/plugin-video-privacy-manager.model' |
11 | import { PluginPlaylistPrivacyManager } from '@shared/models/plugins/plugin-playlist-privacy-manager.model' | 11 | import { PluginPlaylistPrivacyManager } from '@shared/models/plugins/plugin-playlist-privacy-manager.model' |
12 | import { RegisterServerAuthPassOptions, RegisterServerAuthExternalOptions, RegisterServerAuthExternalResult } from '@shared/models/plugins/register-server-auth.model' | 12 | import { |
13 | RegisterServerAuthExternalOptions, | ||
14 | RegisterServerAuthExternalResult, | ||
15 | RegisterServerAuthPassOptions | ||
16 | } from '@shared/models/plugins/register-server-auth.model' | ||
13 | 17 | ||
14 | export type PeerTubeHelpers = { | 18 | export type PeerTubeHelpers = { |
15 | logger: Logger | 19 | logger: Logger |
diff --git a/shared/models/plugins/register-server-auth.model.ts b/shared/models/plugins/register-server-auth.model.ts index 34ebbe712..dc46dcbc8 100644 --- a/shared/models/plugins/register-server-auth.model.ts +++ b/shared/models/plugins/register-server-auth.model.ts | |||
@@ -3,10 +3,12 @@ import { UserRole } from '@shared/models' | |||
3 | export type RegisterServerAuthOptions = RegisterServerAuthPassOptions | RegisterServerAuthExternalOptions | 3 | export type RegisterServerAuthOptions = RegisterServerAuthPassOptions | RegisterServerAuthExternalOptions |
4 | 4 | ||
5 | export interface RegisterServerAuthPassOptions { | 5 | export interface RegisterServerAuthPassOptions { |
6 | type: 'id-and-pass' | 6 | // Authentication name (a plugin can register multiple auth strategies) |
7 | authName: string | ||
7 | 8 | ||
8 | onLogout?: Function | 9 | onLogout?: Function |
9 | 10 | ||
11 | // Weight of this authentication so PeerTube tries the auth methods in DESC weight order | ||
10 | getWeight(): number | 12 | getWeight(): number |
11 | 13 | ||
12 | // Used by PeerTube to login a user | 14 | // Used by PeerTube to login a user |
@@ -23,7 +25,8 @@ export interface RegisterServerAuthPassOptions { | |||
23 | } | 25 | } |
24 | 26 | ||
25 | export interface RegisterServerAuthExternalOptions { | 27 | export interface RegisterServerAuthExternalOptions { |
26 | type: 'external' | 28 | // Authentication name (a plugin can register multiple auth strategies) |
29 | authName: string | ||
27 | 30 | ||
28 | onLogout?: Function | 31 | onLogout?: Function |
29 | } | 32 | } |