diff options
author | Chocobozzz <me@florianbigard.com> | 2022-10-05 15:37:15 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2022-10-07 10:51:16 +0200 |
commit | 56f47830758ff8e92abcfcc5f35d474ab12fe215 (patch) | |
tree | 854e57ec1b800d6ad740c8e42bee00cbd21e1724 /server/lib | |
parent | 7dd7ff4cebc290b09fe00d82046bb58e4e8a800d (diff) | |
download | PeerTube-56f47830758ff8e92abcfcc5f35d474ab12fe215.tar.gz PeerTube-56f47830758ff8e92abcfcc5f35d474ab12fe215.tar.zst PeerTube-56f47830758ff8e92abcfcc5f35d474ab12fe215.zip |
Support two factor authentication in backend
Diffstat (limited to 'server/lib')
-rw-r--r-- | server/lib/auth/oauth.ts | 27 | ||||
-rw-r--r-- | server/lib/redis.ts | 25 |
2 files changed, 48 insertions, 4 deletions
diff --git a/server/lib/auth/oauth.ts b/server/lib/auth/oauth.ts index fa1887315..b541142a5 100644 --- a/server/lib/auth/oauth.ts +++ b/server/lib/auth/oauth.ts | |||
@@ -11,8 +11,20 @@ import OAuth2Server, { | |||
11 | import { randomBytesPromise } from '@server/helpers/core-utils' | 11 | import { randomBytesPromise } from '@server/helpers/core-utils' |
12 | import { MOAuthClient } from '@server/types/models' | 12 | import { MOAuthClient } from '@server/types/models' |
13 | import { sha1 } from '@shared/extra-utils' | 13 | import { sha1 } from '@shared/extra-utils' |
14 | import { OAUTH_LIFETIME } from '../../initializers/constants' | 14 | import { HttpStatusCode } from '@shared/models' |
15 | import { OAUTH_LIFETIME, OTP } from '../../initializers/constants' | ||
15 | import { BypassLogin, getClient, getRefreshToken, getUser, revokeToken, saveToken } from './oauth-model' | 16 | import { BypassLogin, getClient, getRefreshToken, getUser, revokeToken, saveToken } from './oauth-model' |
17 | import { isOTPValid } from '@server/helpers/otp' | ||
18 | |||
19 | class MissingTwoFactorError extends Error { | ||
20 | code = HttpStatusCode.UNAUTHORIZED_401 | ||
21 | name = 'missing_two_factor' | ||
22 | } | ||
23 | |||
24 | class InvalidTwoFactorError extends Error { | ||
25 | code = HttpStatusCode.BAD_REQUEST_400 | ||
26 | name = 'invalid_two_factor' | ||
27 | } | ||
16 | 28 | ||
17 | /** | 29 | /** |
18 | * | 30 | * |
@@ -94,6 +106,9 @@ function handleOAuthAuthenticate ( | |||
94 | } | 106 | } |
95 | 107 | ||
96 | export { | 108 | export { |
109 | MissingTwoFactorError, | ||
110 | InvalidTwoFactorError, | ||
111 | |||
97 | handleOAuthToken, | 112 | handleOAuthToken, |
98 | handleOAuthAuthenticate | 113 | handleOAuthAuthenticate |
99 | } | 114 | } |
@@ -118,6 +133,16 @@ async function handlePasswordGrant (options: { | |||
118 | const user = await getUser(request.body.username, request.body.password, bypassLogin) | 133 | const user = await getUser(request.body.username, request.body.password, bypassLogin) |
119 | if (!user) throw new InvalidGrantError('Invalid grant: user credentials are invalid') | 134 | if (!user) throw new InvalidGrantError('Invalid grant: user credentials are invalid') |
120 | 135 | ||
136 | if (user.otpSecret) { | ||
137 | if (!request.headers[OTP.HEADER_NAME]) { | ||
138 | throw new MissingTwoFactorError('Missing two factor header') | ||
139 | } | ||
140 | |||
141 | if (isOTPValid({ secret: user.otpSecret, token: request.headers[OTP.HEADER_NAME] }) !== true) { | ||
142 | throw new InvalidTwoFactorError('Invalid two factor header') | ||
143 | } | ||
144 | } | ||
145 | |||
121 | const token = await buildToken() | 146 | const token = await buildToken() |
122 | 147 | ||
123 | return saveToken(token, client, user, { bypassLogin }) | 148 | return saveToken(token, client, user, { bypassLogin }) |
diff --git a/server/lib/redis.ts b/server/lib/redis.ts index 9b3c72300..b7523492a 100644 --- a/server/lib/redis.ts +++ b/server/lib/redis.ts | |||
@@ -9,6 +9,7 @@ import { | |||
9 | CONTACT_FORM_LIFETIME, | 9 | CONTACT_FORM_LIFETIME, |
10 | RESUMABLE_UPLOAD_SESSION_LIFETIME, | 10 | RESUMABLE_UPLOAD_SESSION_LIFETIME, |
11 | TRACKER_RATE_LIMITS, | 11 | TRACKER_RATE_LIMITS, |
12 | TWO_FACTOR_AUTH_REQUEST_TOKEN_LIFETIME, | ||
12 | USER_EMAIL_VERIFY_LIFETIME, | 13 | USER_EMAIL_VERIFY_LIFETIME, |
13 | USER_PASSWORD_CREATE_LIFETIME, | 14 | USER_PASSWORD_CREATE_LIFETIME, |
14 | USER_PASSWORD_RESET_LIFETIME, | 15 | USER_PASSWORD_RESET_LIFETIME, |
@@ -108,10 +109,24 @@ class Redis { | |||
108 | return this.removeValue(this.generateResetPasswordKey(userId)) | 109 | return this.removeValue(this.generateResetPasswordKey(userId)) |
109 | } | 110 | } |
110 | 111 | ||
111 | async getResetPasswordLink (userId: number) { | 112 | async getResetPasswordVerificationString (userId: number) { |
112 | return this.getValue(this.generateResetPasswordKey(userId)) | 113 | return this.getValue(this.generateResetPasswordKey(userId)) |
113 | } | 114 | } |
114 | 115 | ||
116 | /* ************ Two factor auth request ************ */ | ||
117 | |||
118 | async setTwoFactorRequest (userId: number, otpSecret: string) { | ||
119 | const requestToken = await generateRandomString(32) | ||
120 | |||
121 | await this.setValue(this.generateTwoFactorRequestKey(userId, requestToken), otpSecret, TWO_FACTOR_AUTH_REQUEST_TOKEN_LIFETIME) | ||
122 | |||
123 | return requestToken | ||
124 | } | ||
125 | |||
126 | async getTwoFactorRequestToken (userId: number, requestToken: string) { | ||
127 | return this.getValue(this.generateTwoFactorRequestKey(userId, requestToken)) | ||
128 | } | ||
129 | |||
115 | /* ************ Email verification ************ */ | 130 | /* ************ Email verification ************ */ |
116 | 131 | ||
117 | async setVerifyEmailVerificationString (userId: number) { | 132 | async setVerifyEmailVerificationString (userId: number) { |
@@ -342,6 +357,10 @@ class Redis { | |||
342 | return 'reset-password-' + userId | 357 | return 'reset-password-' + userId |
343 | } | 358 | } |
344 | 359 | ||
360 | private generateTwoFactorRequestKey (userId: number, token: string) { | ||
361 | return 'two-factor-request-' + userId + '-' + token | ||
362 | } | ||
363 | |||
345 | private generateVerifyEmailKey (userId: number) { | 364 | private generateVerifyEmailKey (userId: number) { |
346 | return 'verify-email-' + userId | 365 | return 'verify-email-' + userId |
347 | } | 366 | } |
@@ -391,8 +410,8 @@ class Redis { | |||
391 | return JSON.parse(value) | 410 | return JSON.parse(value) |
392 | } | 411 | } |
393 | 412 | ||
394 | private setObject (key: string, value: { [ id: string ]: number | string }) { | 413 | private setObject (key: string, value: { [ id: string ]: number | string }, expirationMilliseconds?: number) { |
395 | return this.setValue(key, JSON.stringify(value)) | 414 | return this.setValue(key, JSON.stringify(value), expirationMilliseconds) |
396 | } | 415 | } |
397 | 416 | ||
398 | private async setValue (key: string, value: string, expirationMilliseconds?: number) { | 417 | private async setValue (key: string, value: string, expirationMilliseconds?: number) { |