summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/routes/auth/login.ts23
-rw-r--r--src/api/routes/auth/mfa/totp.ts5
-rw-r--r--src/api/routes/auth/mfa/webauthn.ts5
-rw-r--r--src/api/routes/users/@me/mfa/totp/enable.ts10
-rw-r--r--src/api/routes/users/@me/mfa/webauthn/credentials/index.ts34
-rw-r--r--src/util/config/types/subconfigurations/security/TwoFactor.ts2
-rw-r--r--src/util/entities/User.ts9
-rw-r--r--src/util/util/WebAuthn.ts9
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",
 		});
 	},
 };