feat: passkey support (#11804)
https://github.com/MisskeyIO/misskey/pull/149
This commit is contained in:
parent
bc52d7a4fb
commit
ff9a65e8fa
30 changed files with 800 additions and 1028 deletions
|
|
@ -3,22 +3,38 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { promisify } from 'node:util';
|
||||
import * as crypto from 'node:crypto';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UserProfilesRepository, AttestationChallengesRepository } from '@/models/index.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js';
|
||||
import type { UserProfilesRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
const randomBytes = promisify(crypto.randomBytes);
|
||||
import { WebAuthnService } from '@/core/WebAuthnService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
||||
secure: true,
|
||||
|
||||
errors: {
|
||||
userNotFound: {
|
||||
message: 'User not found.',
|
||||
code: 'USER_NOT_FOUND',
|
||||
id: '652f899f-66d4-490e-993e-6606c8ec04c3',
|
||||
},
|
||||
|
||||
incorrectPassword: {
|
||||
message: 'Incorrect password.',
|
||||
code: 'INCORRECT_PASSWORD',
|
||||
id: '38769596-efe2-4faf-9bec-abbb3f2cd9ba',
|
||||
},
|
||||
|
||||
twoFactorNotEnabled: {
|
||||
message: '2fa not enabled.',
|
||||
code: 'TWO_FACTOR_NOT_ENABLED',
|
||||
id: 'bf32b864-449b-47b8-974e-f9a5468546f1',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
@ -29,53 +45,43 @@ export const paramDef = {
|
|||
required: ['password'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userProfilesRepository)
|
||||
private userProfilesRepository: UserProfilesRepository,
|
||||
|
||||
@Inject(DI.attestationChallengesRepository)
|
||||
private attestationChallengesRepository: AttestationChallengesRepository,
|
||||
|
||||
private idService: IdService,
|
||||
private twoFactorAuthenticationService: TwoFactorAuthenticationService,
|
||||
private webAuthnService: WebAuthnService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
|
||||
const profile = await this.userProfilesRepository.findOne({
|
||||
where: {
|
||||
userId: me.id,
|
||||
},
|
||||
relations: ['user'],
|
||||
});
|
||||
|
||||
if (profile == null) {
|
||||
throw new ApiError(meta.errors.userNotFound);
|
||||
}
|
||||
|
||||
// Compare password
|
||||
const same = await bcrypt.compare(ps.password, profile.password!);
|
||||
const same = await bcrypt.compare(ps.password, profile.password ?? '');
|
||||
|
||||
if (!same) {
|
||||
throw new Error('incorrect password');
|
||||
throw new ApiError(meta.errors.incorrectPassword);
|
||||
}
|
||||
|
||||
if (!profile.twoFactorEnabled) {
|
||||
throw new Error('2fa not enabled');
|
||||
throw new ApiError(meta.errors.twoFactorNotEnabled);
|
||||
}
|
||||
|
||||
// 32 byte challenge
|
||||
const entropy = await randomBytes(32);
|
||||
const challenge = entropy.toString('base64')
|
||||
.replace(/=/g, '')
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_');
|
||||
|
||||
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,
|
||||
};
|
||||
return await this.webAuthnService.initiateRegistration(
|
||||
me.id,
|
||||
profile.user?.username ?? me.id,
|
||||
profile.user?.name ?? undefined,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue