diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/api/routes/auth/login.ts | 23 | ||||
-rw-r--r-- | src/api/routes/auth/mfa/totp.ts | 5 | ||||
-rw-r--r-- | src/api/routes/auth/mfa/webauthn.ts | 5 | ||||
-rw-r--r-- | src/api/routes/users/@me/mfa/totp/enable.ts | 10 | ||||
-rw-r--r-- | src/api/routes/users/@me/mfa/webauthn/credentials/index.ts | 34 | ||||
-rw-r--r-- | src/util/config/types/subconfigurations/security/TwoFactor.ts | 2 | ||||
-rw-r--r-- | src/util/entities/User.ts | 9 | ||||
-rw-r--r-- | src/util/util/WebAuthn.ts | 9 |
8 files changed, 85 insertions, 12 deletions
diff --git a/src/api/routes/auth/login.ts b/src/api/routes/auth/login.ts index a2100333..d2384337 100644 --- a/src/api/routes/auth/login.ts +++ b/src/api/routes/auth/login.ts @@ -139,7 +139,9 @@ router.post( ticket: ticket, mfa: true, sms: false, // TODO - token: null, + totp: true, + backup: true, + user_id: user.id, }); } @@ -172,7 +174,9 @@ router.post( ticket: ticket, mfa: true, sms: false, // TODO - token: null, + totp: true, + backup: true, + user_id: user.id, webauthn: challenge, }); } @@ -202,7 +206,13 @@ router.post( // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png - res.json({ token, settings: { ...user.settings, index: undefined } }); + res.json({ + token, + settings: { + locale: user.settings.locale, + theme: user.settings.theme, + }, + }); }, ); @@ -211,15 +221,12 @@ router.post( * @argument { login: "email@gmail.com", password: "cleartextpassword", undelete: false, captcha_key: null, login_source: null, gift_code_sku_id: null, } * MFA required: - * @returns {"token": null, "mfa": true, "sms": true, "ticket": "SOME TICKET JWT TOKEN"} - - * WebAuthn MFA required: - * @returns {"token": null, "mfa": true, "webauthn": true, "sms": true, "ticket": "SOME TICKET JWT TOKEN"} + * @returns {"mfa": true, "sms": boolean, "totp": boolean, "backup": true, "ticket": "SOME TICKET JWT TOKEN", "user_id": "USER ID", "webauthn": "WEBAUTHN DATA or null"} * Captcha required: * @returns {"captcha_key": ["captcha-required"], "captcha_sitekey": null, "captcha_service": "recaptcha"} * Sucess: - * @returns {"token": "USERTOKEN", "settings": {"locale": "en", "theme": "dark"}} + * @returns {"token": "USERTOKEN", "user_id": "USER ID", "settings": {"locale": "en-US", "theme": "dark"}} */ diff --git a/src/api/routes/auth/mfa/totp.ts b/src/api/routes/auth/mfa/totp.ts index 4df408f9..20ecd7a1 100644 --- a/src/api/routes/auth/mfa/totp.ts +++ b/src/api/routes/auth/mfa/totp.ts @@ -73,7 +73,10 @@ router.post( return res.json({ token: await generateToken(user.id), - settings: { ...user.settings, index: undefined }, + settings: { + locale: user.settings.locale, + theme: user.settings.theme, + }, }); }, ); diff --git a/src/api/routes/auth/mfa/webauthn.ts b/src/api/routes/auth/mfa/webauthn.ts index b58d2944..687fdcd8 100644 --- a/src/api/routes/auth/mfa/webauthn.ts +++ b/src/api/routes/auth/mfa/webauthn.ts @@ -114,7 +114,10 @@ router.post( return res.json({ token: await generateToken(user.id), - user_settings: user.settings, + user_settings: { + locale: user.settings.locale, + theme: user.settings.theme, + }, }); }, ); diff --git a/src/api/routes/users/@me/mfa/totp/enable.ts b/src/api/routes/users/@me/mfa/totp/enable.ts index 19836e4d..5471e0b5 100644 --- a/src/api/routes/users/@me/mfa/totp/enable.ts +++ b/src/api/routes/users/@me/mfa/totp/enable.ts @@ -18,6 +18,7 @@ import { route } from "@spacebar/api"; import { + AuthenticatorType, TotpEnableSchema, User, generateMfaBackupCodes, @@ -74,7 +75,14 @@ router.post( await Promise.all(backup_codes.map((x) => x.save())); await User.update( { id: req.user_id }, - { mfa_enabled: true, totp_secret: body.secret }, + { + mfa_enabled: true, + totp_secret: body.secret, + authenticator_types: [ + ...user.authenticator_types, + AuthenticatorType.TOTP, + ], + }, ); res.send({ diff --git a/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts b/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts index f383ffb7..c8e5b67a 100644 --- a/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts +++ b/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts @@ -18,9 +18,12 @@ import { route } from "@spacebar/api"; import { + AuthenticatorType, + BackupCode, CreateWebAuthnCredentialSchema, DiscordApiErrors, FieldErrors, + generateMfaBackupCodes, GenerateWebAuthnCredentialsSchema, generateWebAuthnTicket, SecurityKey, @@ -193,12 +196,41 @@ router.post( await Promise.all([ securityKey.save(), - User.update({ id: req.user_id }, { webauthn_enabled: true }), + User.update( + { id: req.user_id }, + { + webauthn_enabled: true, + authenticator_types: [ + ...user.authenticator_types, + AuthenticatorType.WEBAUTHN, + ], + }, + ), ]); + // try and get the users existing backup codes + let backup_codes = await BackupCode.find({ + where: { + user: { + id: req.user_id, + }, + }, + }); + + // if there arent any, create them + if (!backup_codes.length) { + backup_codes = generateMfaBackupCodes(req.user_id); + await Promise.all(backup_codes.map((x) => x.save())); + } + return res.json({ name, id: securityKey.id, + type: AuthenticatorType.WEBAUTHN, // I think thats what this is? + backup_codes: backup_codes.map((x) => ({ + ...x, + expired: undefined, + })), }); } else { throw DiscordApiErrors.INVALID_AUTHENTICATION_TOKEN; diff --git a/src/util/config/types/subconfigurations/security/TwoFactor.ts b/src/util/config/types/subconfigurations/security/TwoFactor.ts index 75757124..dfa493a7 100644 --- a/src/util/config/types/subconfigurations/security/TwoFactor.ts +++ b/src/util/config/types/subconfigurations/security/TwoFactor.ts @@ -18,4 +18,6 @@ export class TwoFactorConfiguration { generateBackupCodes: boolean = true; + webauthnAttestation: "none" | "indirect" | "direct" = "none"; + webauthnTimeout: number = 60000; } diff --git a/src/util/entities/User.ts b/src/util/entities/User.ts index c6582b00..25586793 100644 --- a/src/util/entities/User.ts +++ b/src/util/entities/User.ts @@ -85,6 +85,12 @@ export interface UserPrivate extends Pick<User, PrivateUserKeys> { locale: string; } +export enum AuthenticatorType { + WEBAUTHN = 1, + TOTP = 2, + SMS = 3, +} + @Entity("users") export class User extends BaseClass { @Column() @@ -231,6 +237,9 @@ export class User extends BaseClass { @OneToMany(() => SecurityKey, (key: SecurityKey) => key.user) security_keys: SecurityKey[]; + @Column({ type: "simple-array", select: false }) + authenticator_types: AuthenticatorType[] = []; + // TODO: I don't like this method? validate() { if (this.discriminator) { diff --git a/src/util/util/WebAuthn.ts b/src/util/util/WebAuthn.ts index b0027b13..599efe33 100644 --- a/src/util/util/WebAuthn.ts +++ b/src/util/util/WebAuthn.ts @@ -33,6 +33,15 @@ export const WebAuthn: { init: function () { this.fido2 = new Fido2Lib({ challengeSize: 128, + rpName: Config.get().general.instanceName, + rpId: + Config.get().general.frontPage ?? + Config.get().general.instanceName.toLowerCase(), + attestation: Config.get().security.twoFactor.webauthnAttestation, + // rpIcon: + timeout: Config.get().security.twoFactor.webauthnTimeout, + authenticatorRequireResidentKey: false, + authenticatorUserVerification: "preferred", }); }, }; |