Skip to content

Commit 0b16b7f

Browse files
authored
Merge pull request #160 from gsainfoteam/159-kdh-non-email-passkey
159 kdh non email passkey
2 parents 1d0cfcb + fecaeda commit 0b16b7f

File tree

10 files changed

+49
-90
lines changed

10 files changed

+49
-90
lines changed

docs/erd.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ erDiagram
4949
"authenticator" {
5050
String id "🗝️"
5151
String name
52-
String credential_id
5352
Bytes public_key
5453
Int counter
5554
String user_uuid
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
Warnings:
3+
4+
- You are about to drop the column `credential_id` on the `authenticator` table. All the data in the column will be lost.
5+
6+
*/
7+
-- DropIndex
8+
DROP INDEX "authenticator_credential_id_key";
9+
10+
-- AlterTable
11+
ALTER TABLE "authenticator" DROP COLUMN "credential_id";

prisma/schema.prisma

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,8 @@ model Consent {
8787
}
8888

8989
model Authenticator {
90-
id String @id @default(uuid())
90+
id String @id
9191
name String
92-
credentialId String @unique @map("credential_id")
9392
publicKey Bytes @map("public_key")
9493
counter Int
9594
userUuid String @db.Uuid @map("user_uuid")

src/auth/auth.controller.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,7 @@ import {
2424
import { FastifyReply, FastifyRequest } from 'fastify';
2525

2626
import { AuthService } from './auth.service';
27-
import {
28-
LoginDto,
29-
PasskeyDto,
30-
VerifyPasskeyAuthenticationDto,
31-
} from './dto/req.dto';
27+
import { LoginDto, VerifyPasskeyAuthenticationDto } from './dto/req.dto';
3228
import { LoginResDto, PasskeyAuthOptionResDto } from './dto/res.dto';
3329

3430
@ApiTags('auth')
@@ -149,10 +145,8 @@ export class AuthController {
149145
@ApiNotFoundResponse({ description: 'Email is not found' })
150146
@ApiInternalServerErrorResponse({ description: 'server error' })
151147
@Post('passkey')
152-
async authenticateOptions(
153-
@Body() { email }: PasskeyDto,
154-
): Promise<PasskeyAuthOptionResDto> {
155-
return this.authService.authenticateOptions(email);
148+
async authenticateOptions(): Promise<PasskeyAuthOptionResDto> {
149+
return this.authService.authenticateOptions();
156150
}
157151

158152
@ApiOperation({
@@ -165,7 +159,7 @@ export class AuthController {
165159
@ApiInternalServerErrorResponse({ description: 'server error' })
166160
@Post('passkey/verify')
167161
async verifyAuthentication(
168-
@Body() { email, authenticationResponse }: VerifyPasskeyAuthenticationDto,
162+
@Body() { key, authenticationResponse }: VerifyPasskeyAuthenticationDto,
169163
@Res({ passthrough: true }) response: FastifyReply,
170164
): Promise<LoginResDto> {
171165
const {
@@ -174,7 +168,7 @@ export class AuthController {
174168
refreshTokenExpireTime,
175169
accessTokenExpireTime,
176170
} = await this.authService.verifyAuthentication(
177-
email,
171+
key,
178172
authenticationResponse,
179173
);
180174
response.cookie('accessToken', accessToken, {

src/auth/auth.repository.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,11 @@ export class AuthRepository {
6565
});
6666
}
6767

68-
async findAuthenticator(credentialId: string): Promise<Authenticator> {
68+
async findAuthenticator(id: string): Promise<Authenticator> {
6969
return this.prismaService.authenticator
7070
.findUniqueOrThrow({
7171
where: {
72-
credentialId,
72+
id,
7373
},
7474
})
7575
.catch((error) => {
@@ -87,13 +87,13 @@ export class AuthRepository {
8787
}
8888

8989
async updatePasskeyCounter(
90-
credentialId: string,
90+
id: string,
9191
counter: number,
9292
): Promise<Authenticator> {
9393
return this.prismaService.authenticator
9494
.update({
9595
where: {
96-
credentialId,
96+
id,
9797
},
9898
data: {
9999
counter,

src/auth/auth.service.ts

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,21 @@
11
import { Loggable } from '@lib/logger/decorator/loggable';
22
import { RedisService } from '@lib/redis';
3-
import {
4-
Injectable,
5-
Logger,
6-
NotFoundException,
7-
UnauthorizedException,
8-
} from '@nestjs/common';
3+
import { Injectable, Logger, UnauthorizedException } from '@nestjs/common';
94
import { ConfigService } from '@nestjs/config';
105
import { JwtService } from '@nestjs/jwt';
116
import { User } from '@prisma/client';
127
import {
138
generateAuthenticationOptions,
149
verifyAuthenticationResponse,
1510
} from '@simplewebauthn/server';
16-
import {
17-
AuthenticationResponseJSON,
18-
PublicKeyCredentialRequestOptionsJSON,
19-
} from '@simplewebauthn/types';
11+
import { AuthenticationResponseJSON } from '@simplewebauthn/types';
2012
import * as bcrypt from 'bcryptjs';
2113
import * as crypto from 'crypto';
2214
import ms, { StringValue } from 'ms';
2315

2416
import { AuthRepository } from './auth.repository';
2517
import { LoginDto } from './dto/req.dto';
18+
import { PasskeyAuthOptionResDto } from './dto/res.dto';
2619
import { LoginResultType } from './types/loginResult.type';
2720

2821
@Injectable()
@@ -107,47 +100,30 @@ export class AuthService {
107100
return this.authRepository.findUserByUuid(uuid);
108101
}
109102

110-
async authenticateOptions(
111-
email: string,
112-
): Promise<PublicKeyCredentialRequestOptionsJSON> {
113-
const user = await this.authRepository.findUserByEmail(email);
114-
115-
if (!user || user.authenticators.length === 0) {
116-
throw new NotFoundException();
117-
}
118-
103+
async authenticateOptions(): Promise<PasskeyAuthOptionResDto> {
119104
const options = await generateAuthenticationOptions({
120105
rpID: this.passkeyRpId,
121-
allowCredentials: user.authenticators.map((auth) => ({
122-
id: auth.credentialId,
123-
type: 'public-key',
124-
})),
125106
});
107+
const key = crypto.randomUUID();
126108

127-
await this.redisService.set<string>(user.uuid, options.challenge, {
109+
await this.redisService.set<string>(key, options.challenge, {
128110
prefix: this.passkeyPrefix,
129111
ttl: 10 * 60,
130112
});
131113

132-
return options;
114+
return { key, ...options };
133115
}
134116

135117
async verifyAuthentication(
136-
email: string,
118+
key: string,
137119
response: AuthenticationResponseJSON,
138120
): Promise<LoginResultType> {
139-
const user = await this.authRepository.findUserByEmail(email);
140-
if (!user) throw new NotFoundException();
141-
142-
const expectedChallenge = await this.redisService.getOrThrow<string>(
143-
user.uuid,
144-
{
145-
prefix: this.passkeyPrefix,
146-
},
147-
);
121+
const expectedChallenge = await this.redisService.getOrThrow<string>(key, {
122+
prefix: this.passkeyPrefix,
123+
});
148124
if (!expectedChallenge) throw new UnauthorizedException();
149125

150-
await this.redisService.del(user.uuid, { prefix: this.passkeyPrefix });
126+
await this.redisService.del(key, { prefix: this.passkeyPrefix });
151127

152128
const authenticator = await this.authRepository.findAuthenticator(
153129
response.id,
@@ -161,7 +137,6 @@ export class AuthService {
161137
expectedRPID: this.passkeyRpId,
162138
credential: {
163139
...authenticator,
164-
id: authenticator.credentialId,
165140
},
166141
requireUserVerification: true,
167142
},
@@ -172,11 +147,11 @@ export class AuthService {
172147
}
173148

174149
await this.authRepository.updatePasskeyCounter(
175-
authenticator.credentialId,
150+
authenticator.id,
176151
authenticationInfo.newCounter,
177152
);
178153

179-
return this.issueTokens(user.uuid);
154+
return this.issueTokens(authenticator.userUuid);
180155
}
181156

182157
/**

src/auth/dto/req.dto.ts

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,6 @@ export class LoginDto {
3535
password: string;
3636
}
3737

38-
export class PasskeyDto {
39-
@ApiProperty({
40-
example: '[email protected]',
41-
description: '유저의 이메일 주소',
42-
})
43-
@IsEmail()
44-
@IsGistEmail()
45-
@Transform(({ value }) => {
46-
if (typeof value === 'string') {
47-
return value.toLowerCase();
48-
}
49-
throw new BadRequestException('이메일 형식이 올바르지 않습니다.');
50-
})
51-
email: string;
52-
}
53-
5438
class AuthenticationResponseObjectDto {
5539
@ApiProperty({ example: 'eyJ0eXBlIjoid2ViYXV0aG...' })
5640
@IsString()
@@ -125,18 +109,11 @@ class AuthenticationResponseDto {
125109

126110
export class VerifyPasskeyAuthenticationDto {
127111
@ApiProperty({
128-
example: '[email protected]',
129-
description: '유저의 이메일 주소',
112+
example: 'uuid',
113+
description: 'uuid for challenge',
130114
})
131-
@IsEmail()
132-
@IsGistEmail()
133-
@Transform(({ value }) => {
134-
if (typeof value === 'string') {
135-
return value.toLowerCase();
136-
}
137-
throw new BadRequestException('이메일 형식이 올바르지 않습니다.');
138-
})
139-
email: string;
115+
@IsString()
116+
key: string;
140117

141118
@ApiProperty({
142119
description: '유저의 패스키 인증 응답',

src/auth/dto/res.dto.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ class AuthenticationExtensionsDto {
4848
}
4949

5050
export class PasskeyAuthOptionResDto {
51+
@ApiProperty({
52+
description: 'uuid for challenge',
53+
example: 'uuid',
54+
})
55+
key: string;
56+
5157
@ApiProperty({
5258
description: 'challenge (Base64URL)',
5359
example: 'HPv7vydo...',

src/user/user.repository.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ export class UserRepository {
265265
async saveAuthenticator(
266266
name: string,
267267
authenticator: {
268-
credentialId: string;
268+
id: string;
269269
publicKey: Uint8Array;
270270
counter: number;
271271
userUuid: string;
@@ -278,9 +278,7 @@ export class UserRepository {
278278
.catch((error) => {
279279
if (error instanceof PrismaClientKnownRequestError) {
280280
if (error.code === 'P2002') {
281-
this.logger.debug(
282-
`conflict credentialId: ${authenticator.credentialId}`,
283-
);
281+
this.logger.debug(`conflict credentialId: ${authenticator.id}`);
284282
throw new ConflictException('conflict credentialId');
285283
}
286284
this.logger.debug(`prisma error occurred: ${error.code}`);

src/user/user.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ export class UserService {
238238
userID: Buffer.from(user.uuid),
239239
userName: user.name,
240240
excludeCredentials: user.authenticators.map((auth) => ({
241-
id: auth.credentialId,
241+
id: auth.id,
242242
type: 'public-key',
243243
})),
244244
});
@@ -280,7 +280,7 @@ export class UserService {
280280
const { id, publicKey, counter } = registrationInfo.credential;
281281

282282
await this.userRepository.saveAuthenticator(name, {
283-
credentialId: id,
283+
id,
284284
publicKey,
285285
counter,
286286
userUuid: user.uuid,

0 commit comments

Comments
 (0)