summaryrefslogtreecommitdiff
path: root/packages/backend
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-08-28 18:25:31 +0900
committerGitHub <noreply@github.com>2023-08-28 18:25:31 +0900
commit257c4fccf1193f111686f039e06cc4d00b9dce37 (patch)
treeb502d371495bc5a6c18349eb9fd9089cee4f4fa0 /packages/backend
parentMerge branch 'develop' of https://github.com/misskey-dev/misskey into develop (diff)
downloadmisskey-257c4fccf1193f111686f039e06cc4d00b9dce37.tar.gz
misskey-257c4fccf1193f111686f039e06cc4d00b9dce37.tar.bz2
misskey-257c4fccf1193f111686f039e06cc4d00b9dce37.zip
feat: Refine 2fa (#11766)
* wip * Update 2fa.qrdialog.vue * Update 2fa.vue * Update CHANGELOG.md * tweak * :v:
Diffstat (limited to 'packages/backend')
-rw-r--r--packages/backend/migration/1690569881926-user-2fa-backup-codes.js11
-rw-r--r--packages/backend/src/core/entities/UserEntityService.ts1
-rw-r--r--packages/backend/src/models/entities/UserProfile.ts5
-rw-r--r--packages/backend/src/models/json-schema/user.ts5
-rw-r--r--packages/backend/src/server/api/SigninApiService.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/done.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/unregister.ts1
-rw-r--r--packages/backend/test/e2e/2fa.ts12
-rw-r--r--packages/backend/test/e2e/users.ts2
9 files changed, 45 insertions, 6 deletions
diff --git a/packages/backend/migration/1690569881926-user-2fa-backup-codes.js b/packages/backend/migration/1690569881926-user-2fa-backup-codes.js
new file mode 100644
index 0000000000..2049df8ea2
--- /dev/null
+++ b/packages/backend/migration/1690569881926-user-2fa-backup-codes.js
@@ -0,0 +1,11 @@
+export class User2faBackupCodes1690569881926 {
+ name = 'User2faBackupCodes1690569881926'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "twoFactorBackupSecret" character varying array`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "twoFactorBackupSecret"`);
+ }
+}
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index 236ee9f0b4..d12fd1b620 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -434,6 +434,7 @@ export class UserEntityService implements OnModuleInit {
preventAiLearning: profile!.preventAiLearning,
isExplorable: user.isExplorable,
isDeleted: user.isDeleted,
+ twoFactorBackupCodesStock: profile?.twoFactorBackupSecret?.length === 5 ? 'full' : (profile?.twoFactorBackupSecret?.length ?? 0) > 0 ? 'partial' : 'none',
hideOnlineStatus: user.hideOnlineStatus,
hasUnreadSpecifiedNotes: this.noteUnreadsRepository.count({
where: { userId: user.id, isSpecified: true },
diff --git a/packages/backend/src/models/entities/UserProfile.ts b/packages/backend/src/models/entities/UserProfile.ts
index 54144cb429..0fd26f4d63 100644
--- a/packages/backend/src/models/entities/UserProfile.ts
+++ b/packages/backend/src/models/entities/UserProfile.ts
@@ -101,6 +101,11 @@ export class MiUserProfile {
})
public twoFactorSecret: string | null;
+ @Column('varchar', {
+ nullable: true, array: true,
+ })
+ public twoFactorBackupSecret: string[] | null;
+
@Column('boolean', {
default: false,
})
diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts
index 0c205654eb..3314464c31 100644
--- a/packages/backend/src/models/json-schema/user.ts
+++ b/packages/backend/src/models/json-schema/user.ts
@@ -321,6 +321,11 @@ export const packedMeDetailedOnlySchema = {
type: 'boolean',
nullable: false, optional: false,
},
+ twoFactorBackupCodesStock: {
+ type: 'string',
+ enum: ['full', 'partial', 'none'],
+ nullable: false, optional: false,
+ },
hideOnlineStatus: {
type: 'boolean',
nullable: false, optional: false,
diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts
index d68b2617e3..58a5cca4fc 100644
--- a/packages/backend/src/server/api/SigninApiService.ts
+++ b/packages/backend/src/server/api/SigninApiService.ts
@@ -160,6 +160,13 @@ export class SigninApiService {
});
}
+ if (profile.twoFactorBackupSecret?.includes(token)) {
+ await this.userProfilesRepository.update({ userId: profile.userId }, {
+ twoFactorBackupSecret: profile.twoFactorBackupSecret.filter((secret) => secret !== token),
+ });
+ return this.signinService.signin(request, reply, user);
+ }
+
const delta = OTPAuth.TOTP.validate({
secret: OTPAuth.Secret.fromBase32(profile.twoFactorSecret!),
digits: 6,
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts
index e508a28cc0..2d1457b9b5 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts
@@ -54,8 +54,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new Error('not verified');
}
+ const backupCodes = Array.from({ length: 5 }, () => new OTPAuth.Secret().base32);
+
await this.userProfilesRepository.update(me.id, {
twoFactorSecret: profile.twoFactorTempSecret,
+ twoFactorBackupSecret: backupCodes,
twoFactorEnabled: true,
});
@@ -64,6 +67,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
detail: true,
includeSecrets: true,
}));
+
+ return {
+ backupCodes: backupCodes,
+ };
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
index ee58fb2af4..e017e2ef53 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
@@ -46,6 +46,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
await this.userProfilesRepository.update(me.id, {
twoFactorSecret: null,
+ twoFactorBackupSecret: null,
twoFactorEnabled: false,
usePasswordLessLogin: false,
});
diff --git a/packages/backend/test/e2e/2fa.ts b/packages/backend/test/e2e/2fa.ts
index 387249871e..0aa7427da8 100644
--- a/packages/backend/test/e2e/2fa.ts
+++ b/packages/backend/test/e2e/2fa.ts
@@ -191,7 +191,7 @@ describe('2要素認証', () => {
const doneResponse = await api('/i/2fa/done', {
token: otpToken(registerResponse.body.secret),
}, alice);
- assert.strictEqual(doneResponse.status, 204);
+ assert.strictEqual(doneResponse.status, 200);
const usersShowResponse = await api('/users/show', {
username,
@@ -216,7 +216,7 @@ describe('2要素認証', () => {
const doneResponse = await api('/i/2fa/done', {
token: otpToken(registerResponse.body.secret),
}, alice);
- assert.strictEqual(doneResponse.status, 204);
+ assert.strictEqual(doneResponse.status, 200);
const registerKeyResponse = await api('/i/2fa/register-key', {
password,
@@ -272,7 +272,7 @@ describe('2要素認証', () => {
const doneResponse = await api('/i/2fa/done', {
token: otpToken(registerResponse.body.secret),
}, alice);
- assert.strictEqual(doneResponse.status, 204);
+ assert.strictEqual(doneResponse.status, 200);
const registerKeyResponse = await api('/i/2fa/register-key', {
password,
@@ -329,7 +329,7 @@ describe('2要素認証', () => {
const doneResponse = await api('/i/2fa/done', {
token: otpToken(registerResponse.body.secret),
}, alice);
- assert.strictEqual(doneResponse.status, 204);
+ assert.strictEqual(doneResponse.status, 200);
const registerKeyResponse = await api('/i/2fa/register-key', {
password,
@@ -371,7 +371,7 @@ describe('2要素認証', () => {
const doneResponse = await api('/i/2fa/done', {
token: otpToken(registerResponse.body.secret),
}, alice);
- assert.strictEqual(doneResponse.status, 204);
+ assert.strictEqual(doneResponse.status, 200);
const registerKeyResponse = await api('/i/2fa/register-key', {
password,
@@ -423,7 +423,7 @@ describe('2要素認証', () => {
const doneResponse = await api('/i/2fa/done', {
token: otpToken(registerResponse.body.secret),
}, alice);
- assert.strictEqual(doneResponse.status, 204);
+ assert.strictEqual(doneResponse.status, 200);
const usersShowResponse = await api('/users/show', {
username,
diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts
index 8afbcbe322..2c396813ff 100644
--- a/packages/backend/test/e2e/users.ts
+++ b/packages/backend/test/e2e/users.ts
@@ -152,6 +152,7 @@ describe('ユーザー', () => {
preventAiLearning: user.preventAiLearning,
isExplorable: user.isExplorable,
isDeleted: user.isDeleted,
+ twoFactorBackupCodesStock: user.twoFactorBackupCodesStock,
hideOnlineStatus: user.hideOnlineStatus,
hasUnreadSpecifiedNotes: user.hasUnreadSpecifiedNotes,
hasUnreadMentions: user.hasUnreadMentions,
@@ -398,6 +399,7 @@ describe('ユーザー', () => {
assert.strictEqual(response.preventAiLearning, true);
assert.strictEqual(response.isExplorable, true);
assert.strictEqual(response.isDeleted, false);
+ assert.strictEqual(response.twoFactorBackupCodesStock, 'none');
assert.strictEqual(response.hideOnlineStatus, false);
assert.strictEqual(response.hasUnreadSpecifiedNotes, false);
assert.strictEqual(response.hasUnreadMentions, false);