From fbac569e785c8c151a3ff78ca0f1c41ca2682540 Mon Sep 17 00:00:00 2001 From: chanwoo7 Date: Thu, 8 Oct 2026 10:30:27 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=8B=9C=EB=93=9C=20username=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=EC=9D=84=20=EC=83=9D=EC=84=B1=20=EC=A0=95?= =?UTF-8?q?=EC=B1=85=EA=B3=BC=20=EB=A7=9E=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 릴리즈 #527 Codex 리뷰 반영. #526에서 생성 정책에 대문자를 허용했는데 prisma/seed/credential-policy.ts는 소문자 전용 정규식을 따로 들고 있어 ADMIN_SEED_USERNAME=Ops.Admin 같은 값이 시드에서 거절됨. - 시드 검증이 USERNAME_PATTERN·MIN/MAX_USERNAME_LENGTH를 직접 쓰도록 변경(정책 사본 제거) - 위반 메시지: 소문자 → 영문 대소문자, 값은 계속 싣지 않음 테스트 - src/test/seed-credential-policy.spec.ts: username 10건을 시드 검증과 AdminCreateAdminInput이 같은 판정을 내리는지 표로 확인, 메시지에 값 미포함·password 위반 메시지 각 1건 - 반증: 수정 전 시드 정책으로 되돌리면 대문자 케이스 2건 실패 --- prisma/seed/credential-policy.ts | 18 ++++++-- src/test/seed-credential-policy.spec.ts | 61 +++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 src/test/seed-credential-policy.spec.ts diff --git a/prisma/seed/credential-policy.ts b/prisma/seed/credential-policy.ts index b5f14679..1597bd6b 100644 --- a/prisma/seed/credential-policy.ts +++ b/prisma/seed/credential-policy.ts @@ -3,16 +3,26 @@ * 통과 못 하는 값으로 만들어 두면 시드는 성공하는데 로그인은 ValidationPipe에서 전부 거절된다. */ import { IsStrongPasswordConstraint } from '@/common/validators/strong-password.validator'; - -const USERNAME_PATTERN = /^[a-z0-9._-]{4,80}$/; +import { + MAX_USERNAME_LENGTH, + MIN_USERNAME_LENGTH, + USERNAME_PATTERN, +} from '@/features/auth/constants/auth-admin.constants'; export function assertSeedCredential(args: { username: string; password: string; }): void { - if (!USERNAME_PATTERN.test(args.username)) { + const { username } = args; + if ( + username.length < MIN_USERNAME_LENGTH || + username.length > MAX_USERNAME_LENGTH || + !USERNAME_PATTERN.test(username) + ) { // 값은 메시지에 싣지 않는다 — env에서 온 자격증명이 오류 로그로 새는 경로(CodeQL clear-text-logging) - throw new Error('시드 username 정책 위반: 4~80자, 소문자·숫자·._- 만 허용'); + throw new Error( + `시드 username 정책 위반: ${MIN_USERNAME_LENGTH}~${MAX_USERNAME_LENGTH}자, 영문 대소문자·숫자·._- 만 허용`, + ); } if (!new IsStrongPasswordConstraint().validate(args.password)) { throw new Error( diff --git a/src/test/seed-credential-policy.spec.ts b/src/test/seed-credential-policy.spec.ts new file mode 100644 index 00000000..e6428f12 --- /dev/null +++ b/src/test/seed-credential-policy.spec.ts @@ -0,0 +1,61 @@ +import 'reflect-metadata'; + +import { plainToInstance } from 'class-transformer'; +import { validate } from 'class-validator'; + +import { assertSeedCredential } from '../../prisma/seed/credential-policy'; + +import { AdminCreateAdminInput } from '@/features/auth/dto/inputs/admin-create-admin.input'; + +const PASSWORD = 'Strong!Pass1'; + +function seedAccepts(username: string): boolean { + try { + assertSeedCredential({ username, password: PASSWORD }); + return true; + } catch { + return false; + } +} + +async function dtoAccepts(username: string): Promise { + const input = plainToInstance(AdminCreateAdminInput, { + username, + password: PASSWORD, + }); + return (await validate(input)).length === 0; +} + +// 시드가 만든 계정이 관리자 생성 경로와 같은 username 정책을 따르는지 — 두 판정이 어긋나면 안 된다 +describe('시드 자격증명 username 정책', () => { + it.each([ + ['소문자', 'ops.admin', true], + ['대문자 혼용', 'Ops.Admin_1', true], + ['전부 대문자', 'OPS-ADMIN', true], + ['4자', 'abcd', true], + ['80자', 'a'.repeat(80), true], + ['3자', 'abc', false], + ['81자', 'a'.repeat(81), false], + ['공백', 'ops admin', false], + ['허용 외 문자(@)', 'ops@admin', false], + ['한글', '관리자계정', false], + ])('%s: 생성 DTO와 같은 판정(%s → %s)', async (_label, username, ok) => { + expect(seedAccepts(username)).toBe(ok); + expect(await dtoAccepts(username)).toBe(ok); + }); + + it('위반 메시지에 username 값을 싣지 않는다', () => { + expect(() => + assertSeedCredential({ username: 'bad@name', password: PASSWORD }), + ).toThrow(/^시드 username 정책 위반: 4~80자/); + expect(() => + assertSeedCredential({ username: 'bad@name', password: PASSWORD }), + ).not.toThrow(/bad@name/); + }); + + it('password 정책 위반은 별도 메시지로 거절한다', () => { + expect(() => + assertSeedCredential({ username: 'ops.admin', password: 'weak' }), + ).toThrow(/^시드 password 정책 위반/); + }); +});