summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/endpoints/i/2fa
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2022-09-18 03:27:08 +0900
committerGitHub <noreply@github.com>2022-09-18 03:27:08 +0900
commitb75184ec8e3436200bacdcd832e3324702553d20 (patch)
tree8b7e316f29e95df921db57289c8b8da476d18f07 /packages/backend/src/server/api/endpoints/i/2fa
parentUpdate ROADMAP.md (diff)
downloadmisskey-b75184ec8e3436200bacdcd832e3324702553d20.tar.gz
misskey-b75184ec8e3436200bacdcd832e3324702553d20.tar.bz2
misskey-b75184ec8e3436200bacdcd832e3324702553d20.zip
なんかもうめっちゃ変えた
Diffstat (limited to 'packages/backend/src/server/api/endpoints/i/2fa')
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/done.ts52
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/key-done.ts216
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/password-less.ts24
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/register-key.ts84
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/register.ts79
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts62
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/unregister.ts38
7 files changed, 327 insertions, 228 deletions
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 35806b2bc3..bcf3931b04 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts
@@ -1,6 +1,8 @@
import * as speakeasy from 'speakeasy';
-import define from '../../../define.js';
-import { UserProfiles } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserProfilesRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -17,27 +19,35 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const token = ps.token.replace(/\s/g, '');
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const token = ps.token.replace(/\s/g, '');
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
- if (profile.twoFactorTempSecret == null) {
- throw new Error('二段階認証の設定が開始されていません');
- }
+ if (profile.twoFactorTempSecret == null) {
+ throw new Error('二段階認証の設定が開始されていません');
+ }
- const verified = (speakeasy as any).totp.verify({
- secret: profile.twoFactorTempSecret,
- encoding: 'base32',
- token: token,
- });
+ const verified = (speakeasy as any).totp.verify({
+ secret: profile.twoFactorTempSecret,
+ encoding: 'base32',
+ token: token,
+ });
- if (!verified) {
- throw new Error('not verified');
- }
+ if (!verified) {
+ throw new Error('not verified');
+ }
- await UserProfiles.update(user.id, {
- twoFactorSecret: profile.twoFactorTempSecret,
- twoFactorEnabled: true,
- });
-});
+ await this.userProfilesRepository.update(me.id, {
+ twoFactorSecret: profile.twoFactorTempSecret,
+ twoFactorEnabled: true,
+ });
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
index 1afb34bfda..f2f4c2044e 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
@@ -1,19 +1,16 @@
-import bcrypt from 'bcryptjs';
import { promisify } from 'node:util';
+import bcrypt from 'bcryptjs';
import * as cbor from 'cbor';
-import define from '../../../define.js';
-import {
- UserProfiles,
- UserSecurityKeys,
- AttestationChallenges,
- Users,
-} from '@/models/index.js';
-import config from '@/config/index.js';
-import { procedures, hash } from '../../../2fa.js';
-import { publishMainStream } from '@/services/stream.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { Config } from '@/config.js';
+import { DI } from '@/di-symbols.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js';
+import { AttestationChallengesRepository, UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js';
const cborDecodeFirst = promisify(cbor.decodeFirst) as any;
-const rpIdHashReal = hash(Buffer.from(config.hostname, 'utf-8'));
export const meta = {
requireCredential: true,
@@ -34,110 +31,135 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.config)
+ private config: Config,
- // Compare password
- const same = await bcrypt.compare(ps.password, profile.password!);
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- if (!same) {
- throw new Error('incorrect password');
- }
+ @Inject(DI.userSecurityKeysRepository)
+ private userSecurityKeysRepository: UserSecurityKeysRepository,
- if (!profile.twoFactorEnabled) {
- throw new Error('2fa not enabled');
- }
+ @Inject(DI.attestationChallengesRepository)
+ private attestationChallengesRepository: AttestationChallengesRepository,
- const clientData = JSON.parse(ps.clientDataJSON);
+ private userEntityService: UserEntityService,
+ private globalEventService: GlobalEventService,
+ private twoFactorAuthenticationService: TwoFactorAuthenticationService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const rpIdHashReal = this.twoFactorAuthenticationService.hash(Buffer.from(this.config.hostname, 'utf-8'));
- if (clientData.type !== 'webauthn.create') {
- throw new Error('not a creation attestation');
- }
- if (clientData.origin !== config.scheme + '://' + config.host) {
- throw new Error('origin mismatch');
- }
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
- const clientDataJSONHash = hash(Buffer.from(ps.clientDataJSON, 'utf-8'));
+ // Compare password
+ const same = await bcrypt.compare(ps.password, profile.password!);
- const attestation = await cborDecodeFirst(ps.attestationObject);
+ if (!same) {
+ throw new Error('incorrect password');
+ }
- const rpIdHash = attestation.authData.slice(0, 32);
- if (!rpIdHashReal.equals(rpIdHash)) {
- throw new Error('rpIdHash mismatch');
- }
+ if (!profile.twoFactorEnabled) {
+ throw new Error('2fa not enabled');
+ }
- const flags = attestation.authData[32];
+ const clientData = JSON.parse(ps.clientDataJSON);
- // eslint:disable-next-line:no-bitwise
- if (!(flags & 1)) {
- throw new Error('user not present');
- }
+ if (clientData.type !== 'webauthn.create') {
+ throw new Error('not a creation attestation');
+ }
+ if (clientData.origin !== this.config.scheme + '://' + this.config.host) {
+ throw new Error('origin mismatch');
+ }
- const authData = Buffer.from(attestation.authData);
- const credentialIdLength = authData.readUInt16BE(53);
- const credentialId = authData.slice(55, 55 + credentialIdLength);
- const publicKeyData = authData.slice(55 + credentialIdLength);
- const publicKey: Map<number, any> = await cborDecodeFirst(publicKeyData);
- if (publicKey.get(3) !== -7) {
- throw new Error('alg mismatch');
- }
+ const clientDataJSONHash = this.twoFactorAuthenticationService.hash(Buffer.from(ps.clientDataJSON, 'utf-8'));
- if (!(procedures as any)[attestation.fmt]) {
- throw new Error('unsupported fmt');
- }
+ const attestation = await cborDecodeFirst(ps.attestationObject);
- const verificationData = (procedures as any)[attestation.fmt].verify({
- attStmt: attestation.attStmt,
- authenticatorData: authData,
- clientDataHash: clientDataJSONHash,
- credentialId,
- publicKey,
- rpIdHash,
- });
- if (!verificationData.valid) throw new Error('signature invalid');
+ const rpIdHash = attestation.authData.slice(0, 32);
+ if (!rpIdHashReal.equals(rpIdHash)) {
+ throw new Error('rpIdHash mismatch');
+ }
- const attestationChallenge = await AttestationChallenges.findOneBy({
- userId: user.id,
- id: ps.challengeId,
- registrationChallenge: true,
- challenge: hash(clientData.challenge).toString('hex'),
- });
+ const flags = attestation.authData[32];
- if (!attestationChallenge) {
- throw new Error('non-existent challenge');
- }
+ // eslint:disable-next-line:no-bitwise
+ if (!(flags & 1)) {
+ throw new Error('user not present');
+ }
+
+ const authData = Buffer.from(attestation.authData);
+ const credentialIdLength = authData.readUInt16BE(53);
+ const credentialId = authData.slice(55, 55 + credentialIdLength);
+ const publicKeyData = authData.slice(55 + credentialIdLength);
+ const publicKey: Map<number, any> = await cborDecodeFirst(publicKeyData);
+ if (publicKey.get(3) !== -7) {
+ throw new Error('alg mismatch');
+ }
+
+ const procedures = this.twoFactorAuthenticationService.getProcedures();
+
+ if (!(procedures as any)[attestation.fmt]) {
+ throw new Error('unsupported fmt');
+ }
+
+ const verificationData = (procedures as any)[attestation.fmt].verify({
+ attStmt: attestation.attStmt,
+ authenticatorData: authData,
+ clientDataHash: clientDataJSONHash,
+ credentialId,
+ publicKey,
+ rpIdHash,
+ });
+ if (!verificationData.valid) throw new Error('signature invalid');
- await AttestationChallenges.delete({
- userId: user.id,
- id: ps.challengeId,
- });
+ const attestationChallenge = await this.attestationChallengesRepository.findOneBy({
+ userId: me.id,
+ id: ps.challengeId,
+ registrationChallenge: true,
+ challenge: this.twoFactorAuthenticationService.hash(clientData.challenge).toString('hex'),
+ });
- // Expired challenge (> 5min old)
- if (
- new Date().getTime() - attestationChallenge.createdAt.getTime() >=
+ if (!attestationChallenge) {
+ throw new Error('non-existent challenge');
+ }
+
+ await this.attestationChallengesRepository.delete({
+ userId: me.id,
+ id: ps.challengeId,
+ });
+
+ // Expired challenge (> 5min old)
+ if (
+ new Date().getTime() - attestationChallenge.createdAt.getTime() >=
5 * 60 * 1000
- ) {
- throw new Error('expired challenge');
- }
+ ) {
+ throw new Error('expired challenge');
+ }
- const credentialIdString = credentialId.toString('hex');
+ const credentialIdString = credentialId.toString('hex');
- await UserSecurityKeys.insert({
- userId: user.id,
- id: credentialIdString,
- lastUsed: new Date(),
- name: ps.name,
- publicKey: verificationData.publicKey.toString('hex'),
- });
+ await this.userSecurityKeysRepository.insert({
+ userId: me.id,
+ id: credentialIdString,
+ lastUsed: new Date(),
+ name: ps.name,
+ publicKey: verificationData.publicKey.toString('hex'),
+ });
- // Publish meUpdated event
- publishMainStream(user.id, 'meUpdated', await Users.pack(user.id, user, {
- detail: true,
- includeSecrets: true,
- }));
+ // Publish meUpdated event
+ this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
+ detail: true,
+ includeSecrets: true,
+ }));
- return {
- id: credentialIdString,
- name: ps.name,
- };
-});
+ return {
+ id: credentialIdString,
+ name: ps.name,
+ };
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts
index 4bfa24f97f..3eb9f43c2b 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts
@@ -1,5 +1,7 @@
-import define from '../../../define.js';
-import { UserProfiles } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserProfilesRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -16,8 +18,16 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- await UserProfiles.update(user.id, {
- usePasswordLessLogin: ps.value,
- });
-});
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ await this.userProfilesRepository.update(me.id, {
+ usePasswordLessLogin: ps.value,
+ });
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts
index e906b82043..df37db4c6a 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts
@@ -1,10 +1,12 @@
-import bcrypt from 'bcryptjs';
-import define from '../../../define.js';
-import { UserProfiles, AttestationChallenges } from '@/models/index.js';
import { promisify } from 'node:util';
import * as crypto from 'node:crypto';
-import { genId } from '@/misc/gen-id.js';
-import { hash } from '../../../2fa.js';
+import bcrypt from 'bcryptjs';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserProfilesRepository, AttestationChallengesRepository } from '@/models/index.js';
+import { IdService } from '@/core/IdService.js';
+import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js';
+import { DI } from '@/di-symbols.js';
const randomBytes = promisify(crypto.randomBytes);
@@ -23,39 +25,53 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- // Compare password
- const same = await bcrypt.compare(ps.password, profile.password!);
+ @Inject(DI.attestationChallengesRepository)
+ private attestationChallengesRepository: AttestationChallengesRepository,
- if (!same) {
- throw new Error('incorrect password');
- }
+ private idService: IdService,
+ private twoFactorAuthenticationService: TwoFactorAuthenticationService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
- if (!profile.twoFactorEnabled) {
- throw new Error('2fa not enabled');
- }
+ // Compare password
+ const same = await bcrypt.compare(ps.password, profile.password!);
- // 32 byte challenge
- const entropy = await randomBytes(32);
- const challenge = entropy.toString('base64')
- .replace(/=/g, '')
- .replace(/\+/g, '-')
- .replace(/\//g, '_');
+ if (!same) {
+ throw new Error('incorrect password');
+ }
- const challengeId = genId();
+ if (!profile.twoFactorEnabled) {
+ throw new Error('2fa not enabled');
+ }
- await AttestationChallenges.insert({
- userId: user.id,
- id: challengeId,
- challenge: hash(Buffer.from(challenge, 'utf-8')).toString('hex'),
- createdAt: new Date(),
- registrationChallenge: true,
- });
+ // 32 byte challenge
+ const entropy = await randomBytes(32);
+ const challenge = entropy.toString('base64')
+ .replace(/=/g, '')
+ .replace(/\+/g, '-')
+ .replace(/\//g, '_');
- return {
- challengeId,
- challenge,
- };
-});
+ const challengeId = this.idService.genId();
+
+ await this.attestationChallengesRepository.insert({
+ userId: me.id,
+ id: challengeId,
+ challenge: this.twoFactorAuthenticationService.hash(Buffer.from(challenge, 'utf-8')).toString('hex'),
+ createdAt: new Date(),
+ registrationChallenge: true,
+ });
+
+ return {
+ challengeId,
+ challenge,
+ };
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts
index 33f5717728..e20911f35e 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts
@@ -1,9 +1,11 @@
import bcrypt from 'bcryptjs';
import * as speakeasy from 'speakeasy';
import * as QRCode from 'qrcode';
-import config from '@/config/index.js';
-import { UserProfiles } from '@/models/index.js';
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserProfilesRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
+import { Config } from '@/config.js';
export const meta = {
requireCredential: true,
@@ -20,39 +22,50 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.config)
+ private config: Config,
- // Compare password
- const same = await bcrypt.compare(ps.password, profile.password!);
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
- if (!same) {
- throw new Error('incorrect password');
- }
+ // Compare password
+ const same = await bcrypt.compare(ps.password, profile.password!);
+
+ if (!same) {
+ throw new Error('incorrect password');
+ }
- // Generate user's secret key
- const secret = speakeasy.generateSecret({
- length: 32,
- });
+ // Generate user's secret key
+ const secret = speakeasy.generateSecret({
+ length: 32,
+ });
- await UserProfiles.update(user.id, {
- twoFactorTempSecret: secret.base32,
- });
+ await this.userProfilesRepository.update(me.id, {
+ twoFactorTempSecret: secret.base32,
+ });
- // Get the data URL of the authenticator URL
- const url = speakeasy.otpauthURL({
- secret: secret.base32,
- encoding: 'base32',
- label: user.username,
- issuer: config.host,
- });
- const dataUrl = await QRCode.toDataURL(url);
+ // Get the data URL of the authenticator URL
+ const url = speakeasy.otpauthURL({
+ secret: secret.base32,
+ encoding: 'base32',
+ label: me.username,
+ issuer: this.config.host,
+ });
+ const dataUrl = await QRCode.toDataURL(url);
- return {
- qr: dataUrl,
- url,
- secret: secret.base32,
- label: user.username,
- issuer: config.host,
- };
-});
+ return {
+ qr: dataUrl,
+ url,
+ secret: secret.base32,
+ label: me.username,
+ issuer: this.config.host,
+ };
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts
index eb2f75308d..1889dd7893 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts
@@ -1,7 +1,11 @@
import bcrypt from 'bcryptjs';
-import define from '../../../define.js';
-import { UserProfiles, UserSecurityKeys, Users } from '@/models/index.js';
-import { publishMainStream } from '@/services/stream.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js';
+import type { UsersRepository } from '@/models/index.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -19,27 +23,41 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userSecurityKeysRepository)
+ private userSecurityKeysRepository: UserSecurityKeysRepository,
- // Compare password
- const same = await bcrypt.compare(ps.password, profile.password!);
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- if (!same) {
- throw new Error('incorrect password');
- }
+ private userEntityService: UserEntityService,
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
+
+ // Compare password
+ const same = await bcrypt.compare(ps.password, profile.password!);
- // Make sure we only delete the user's own creds
- await UserSecurityKeys.delete({
- userId: user.id,
- id: ps.credentialId,
- });
+ if (!same) {
+ throw new Error('incorrect password');
+ }
- // Publish meUpdated event
- publishMainStream(user.id, 'meUpdated', await Users.pack(user.id, user, {
- detail: true,
- includeSecrets: true,
- }));
+ // Make sure we only delete the user's own creds
+ await this.userSecurityKeysRepository.delete({
+ userId: me.id,
+ id: ps.credentialId,
+ });
- return {};
-});
+ // Publish meUpdated event
+ this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
+ detail: true,
+ includeSecrets: true,
+ }));
+
+ return {};
+ });
+ }
+}
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 45e7a98639..4607e5d981 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
@@ -1,6 +1,8 @@
import bcrypt from 'bcryptjs';
-import define from '../../../define.js';
-import { UserProfiles } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserProfilesRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -17,18 +19,26 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
- // Compare password
- const same = await bcrypt.compare(ps.password, profile.password!);
+ // Compare password
+ const same = await bcrypt.compare(ps.password, profile.password!);
- if (!same) {
- throw new Error('incorrect password');
- }
+ if (!same) {
+ throw new Error('incorrect password');
+ }
- await UserProfiles.update(user.id, {
- twoFactorSecret: null,
- twoFactorEnabled: false,
- });
-});
+ await this.userProfilesRepository.update(me.id, {
+ twoFactorSecret: null,
+ twoFactorEnabled: false,
+ });
+ });
+ }
+}