본문으로 건너뛰기

Access Code 관리 API 명세

개요

이 문서는 AccessCode 도메인의 Access Code 관리 API에 대한 세부 명세를 제공합니다. Access Code 관리 API는 불면증 진단을 받은 환자의 앱 회원가입을 위한 인증 코드의 생성, 검증, 사용 등 전체 생명주기를 관리하는 기능을 제공합니다.

API 정의

DTO 정의

CreateAccessCodeDto

export class CreateAccessCodeDto {
@IsEnum(AccessCodeType)
@ApiProperty({
description: 'Access Code 유형',
example: 'TREATMENT',
enum: ['TREATMENT', 'TRIAL', 'DIAGNOSIS'],
})
type: AccessCodeType;

@IsString()
@ApiProperty({
description: '코드 생성자 ID',
example: 'user_123',
})
creatorId: string;

@IsString()
@ApiProperty({
description: '계정 ID',
example: 'account_456',
})
accountId: string;

@IsNumber()
@Min(1)
@Max(365)
@ApiProperty({
description: '치료 기간(일)',
example: 90,
minimum: 1,
maximum: 365,
})
treatmentPeriod: number;

@IsNumber()
@Min(1)
@Max(90)
@ApiProperty({
description: '사용 가능 기간(일)',
example: 30,
minimum: 1,
maximum: 90,
})
usagePeriod: number;

@IsOptional()
@IsEmail()
@ApiProperty({
description: '이메일(선택, 마스킹 처리)',
example: 'm***@example.com',
required: false,
})
email?: string;

@IsEnum(RegistrationChannel)
@ApiProperty({
description: '등록 채널',
example: 'WEB',
enum: ['WEB', 'MOBILE', 'CLINIC'],
})
registrationChannel: RegistrationChannel;

@IsOptional()
@IsString()
@ApiProperty({
description: '무작위 배정 코드(선택)',
example: 'RND123',
required: false,
})
randomizationCode?: string;

@IsEnum(DeliveryMethod)
@ApiProperty({
description: '전달 방식',
example: 'EMAIL',
enum: ['EMAIL', 'SMS', 'PRINTED'],
})
deliveryMethod: DeliveryMethod;

@ValidateNested()
@Type(() => PrivacyConsentDto)
@ApiProperty({
description: '개인정보 처리 동의',
type: PrivacyConsentDto,
})
privacyConsent: PrivacyConsentDto;

@IsOptional()
@ValidateNested()
@Type(() => TimeMachineOptionsDto)
@ApiProperty({
description: 'TimeMachine 옵션(선택)',
type: TimeMachineOptionsDto,
required: false,
})
timeMachineOptions?: TimeMachineOptionsDto;
}

PrivacyConsentDto

export class PrivacyConsentDto {
@IsBoolean()
@ApiProperty({
description: '데이터 처리 동의',
example: true,
})
dataProcessing: boolean;

@IsBoolean()
@ApiProperty({
description: '이메일 마케팅 동의',
example: false,
})
emailMarketing: boolean;

@IsBoolean()
@ApiProperty({
description: '제3자 공유 동의',
example: false,
})
thirdPartySharing: boolean;
}

TimeMachineOptionsDto

export class TimeMachineOptionsDto {
@IsBoolean()
@ApiProperty({
description: 'TimeMachine 사용 여부',
example: true,
})
useTimeMachine: boolean;

@IsOptional()
@IsDateString()
@ApiProperty({
description: '가상 시간 시작일(13자리 Unix 타임스탬프, 밀리초)',
example: 1710924000000,
required: false,
})
virtualTimeStartDate?: string;

@IsOptional()
@IsBoolean()
@ApiProperty({
description: '가상 시간 기준 만료 여부',
example: true,
required: false,
default: false,
})
expirationBasedOnVirtualTime?: boolean;

@IsOptional()
@IsBoolean()
@ApiProperty({
description: '사용자 등록과 동기화 여부',
example: true,
required: false,
default: false,
})
synchronizeWithUserRegistration?: boolean;

@IsOptional()
@IsString()
@ApiProperty({
description: 'TimeMachine 사용 이유',
example: '테스트 목적',
required: false,
})
timeMachineReason?: string;
}

ValidateAccessCodeDto

export class ValidateAccessCodeDto {
@IsString()
@ApiProperty({
description: 'Access Code',
example: 'AB12CD34EF56GH78',
})
code: string;

@IsString()
@ApiProperty({
description: '기기 ID',
example: 'DEVICE_001',
})
deviceId: string;
}

AccessCodeValidationResult

export class AccessCodeValidationResult {
@ApiProperty({
description: '코드 유효성 여부',
example: true,
})
isValid: boolean;

@ApiProperty({
description: '코드 정보',
type: AccessCodeInfo,
required: false,
})
codeInfo?: AccessCodeInfo;
}

AccessCodeInfo

export class AccessCodeInfo {
@ApiProperty({
description: '코드 ID',
example: 'code_789',
})
id: string;

@ApiProperty({
description: '치료 기간(일)',
example: 90,
})
treatmentPeriod: number;

@ApiProperty({
description: '만료일(13자리 Unix 타임스탬프, 밀리초)',
example: 1713602400000,
})
expiresAt: number;
}

UseAccessCodeDto

export class UseAccessCodeDto {
@IsString()
@ApiProperty({
description: '사용자 ID',
example: 'user_123',
})
userId: string;

@IsString()
@ApiProperty({
description: '기기 ID',
example: 'DEVICE_001',
})
deviceId: string;

@IsOptional()
@ValidateNested()
@Type(() => UseTimeMachineOptionsDto)
@ApiProperty({
description: 'TimeMachine 옵션(선택)',
type: UseTimeMachineOptionsDto,
required: false,
})
timeMachineOptions?: UseTimeMachineOptionsDto;
}

UseTimeMachineOptionsDto

export class UseTimeMachineOptionsDto {
@IsBoolean()
@ApiProperty({
description: 'TimeMachine 사용 여부',
example: true,
})
useTimeMachine: boolean;

@IsOptional()
@IsDateString()
@ApiProperty({
description: '가상 시간 시작일(13자리 Unix 타임스탬프, 밀리초)',
example: 1711374600000,
required: false,
})
virtualTimeStartDate?: string;

@IsOptional()
@IsBoolean()
@ApiProperty({
description: '기존 코드 설정 덮어쓰기 여부',
example: false,
required: false,
default: false,
})
overrideCodeSettings?: boolean;

@IsOptional()
@IsString()
@ApiProperty({
description: 'TimeMachine 사용 이유',
example: '사용자 등록 처리',
required: false,
})
reason?: string;
}

UsedAccessCode

export class UsedAccessCode {
@ApiProperty({
description: '코드 ID',
example: 'code_789',
})
id: string;

@ApiProperty({
description: '코드 상태',
example: 'USED',
enum: ['UNUSED', 'USED', 'EXPIRED', 'REVOKED'],
})
status: string;

@ApiProperty({
description: '사용 일시(13자리 Unix 타임스탬프, 밀리초)',
example: 1711374600000,
})
usedAt: number;

@ApiProperty({
description: '사용자 ID',
example: 'user_123',
})
userId: string;

@ApiProperty({
description: 'TimeMachine 사용 여부',
example: true,
})
timeMachineEnabled: boolean;

@ApiProperty({
description: '가상 시간 시작일(13자리 Unix 타임스탬프, 밀리초)',
example: 1711374600000,
required: false,
})
virtualTimeStartDate?: number;
}

BatchCreateAccessCodeDto

export class BatchCreateAccessCodeDto {
@IsNumber()
@Min(1)
@Max(1000)
@ApiProperty({
description: '생성할 코드 수',
example: 10,
minimum: 1,
maximum: 1000,
})
count: number;

@IsEnum(AccessCodeType)
@ApiProperty({
description: 'Access Code 유형',
example: 'TREATMENT',
enum: ['TREATMENT', 'TRIAL', 'DIAGNOSIS'],
})
type: AccessCodeType;

@IsString()
@ApiProperty({
description: '코드 생성자 ID',
example: 'user_123',
})
creatorId: string;

@IsString()
@ApiProperty({
description: '계정 ID',
example: 'account_456',
})
accountId: string;

@IsNumber()
@Min(1)
@Max(365)
@ApiProperty({
description: '치료 기간(일)',
example: 90,
minimum: 1,
maximum: 365,
})
treatmentPeriod: number;

@IsNumber()
@Min(1)
@Max(90)
@ApiProperty({
description: '사용 가능 기간(일)',
example: 30,
minimum: 1,
maximum: 90,
})
usagePeriod: number;

@IsEnum(RegistrationChannel)
@ApiProperty({
description: '등록 채널',
example: 'WEB',
enum: ['WEB', 'MOBILE', 'CLINIC'],
})
registrationChannel: RegistrationChannel;

@IsOptional()
@ValidateNested()
@Type(() => BatchTimeMachineOptionsDto)
@ApiProperty({
description: 'TimeMachine 옵션(선택)',
type: BatchTimeMachineOptionsDto,
required: false,
})
timeMachineOptions?: BatchTimeMachineOptionsDto;
}

BatchTimeMachineOptionsDto

export class BatchTimeMachineOptionsDto {
@IsBoolean()
@ApiProperty({
description: '모든 코드에 TimeMachine 사용 여부',
example: true,
})
useTimeMachineForAll: boolean;

@IsOptional()
@IsDateString()
@ApiProperty({
description: '공통 가상 시간 시작일(13자리 Unix 타임스탬프, 밀리초)',
example: 1710924000000,
required: false,
})
commonVirtualTimeStartDate?: string;

@IsOptional()
@IsBoolean()
@ApiProperty({
description: '가상 시간 기준 만료 여부',
example: true,
required: false,
default: false,
})
expirationBasedOnVirtualTime?: boolean;

@IsOptional()
@IsString()
@ApiProperty({
description: 'TimeMachine 사용 이유',
example: '일괄 테스트 코드 생성',
required: false,
})
reason?: string;
}

BatchResult

export class BatchResult {
@ApiProperty({
description: '생성된 코드 목록',
type: [AccessCode],
})
items: AccessCode[];

@ApiProperty({
description: '페이지네이션 정보',
type: MetaPagination,
})
metadata: MetaPagination;

@ApiProperty({
description: '배치 ID',
example: 'batch_001',
})
batchId: string;

@ApiProperty({
description: 'TimeMachine 사용 여부',
example: true,
})
timeMachineEnabled: boolean;
}

AccessCode

export class AccessCode {
@ApiProperty({
description: '코드 ID',
example: 'code_789',
})
id: string;

@ApiProperty({
description: 'Access Code',
example: 'AB12CD34EF56GH78',
})
code: string;

@ApiProperty({
description: '만료일(13자리 Unix 타임스탬프, 밀리초)',
example: 1713602400000,
})
expiresAt: number;

@ApiProperty({
description: '코드 상태',
example: 'UNUSED',
enum: ['UNUSED', 'USED', 'EXPIRED', 'REVOKED'],
})
status: string;

@ApiProperty({
description: '생성일(13자리 Unix 타임스탬프, 밀리초)',
example: 1710924000000,
})
createdAt: string;

@ApiProperty({
description: 'TimeMachine 사용 여부',
example: true,
})
timeMachineEnabled: boolean;

@ApiProperty({
description: '가상 시간 시작일(13자리 Unix 타임스탬프, 밀리초)',
example: 1710924000000,
required: false,
})
virtualTimeStartDate?: number;
}

코드 생성 API

요청

  • 메서드: POST
  • 경로: /v1/access-codes
  • 헤더:
    • Content-Type: application/json
    • Authorization: Bearer {token}
    • X-Admin-Token: {admin_token}
    • Privacy-Policy-Version: 2024.1
    • Data-Processing-Purpose: USER_AUTHENTICATION
  • 본문: CreateAccessCodeDto

응답

  • 성공 응답 (201 Created): AccessCode
  • 오류 응답:
    • 401 Unauthorized: 인증 실패
    • 403 Forbidden: 권한 없음
    • 400 Bad Request: 잘못된 요청 형식

예시

요청:

POST /v1/access-codes
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Admin-Token: admin-token-xyz
Privacy-Policy-Version: 2024.1
Data-Processing-Purpose: USER_AUTHENTICATION
Content-Type: application/json

{
"type": "TREATMENT",
"creatorId": "user_123",
"accountId": "account_456",
"treatmentPeriod": 90,
"usagePeriod": 30,
"email": "m***@example.com",
"registrationChannel": "WEB",
"randomizationCode": "RND123",
"deliveryMethod": "EMAIL",
"privacyConsent": {
"dataProcessing": true,
"emailMarketing": false,
"thirdPartySharing": false
},
"timeMachineOptions": {
"useTimeMachine": true,
"virtualTimeStartDate": 1710924000000,
"expirationBasedOnVirtualTime": true,
"synchronizeWithUserRegistration": true,
"timeMachineReason": "테스트 목적"
}
}

응답:

{
"id": "code_789",
"code": "AB12CD34EF56GH78",
"expiresAt": 1713602400000,
"status": "UNUSED",
"createdAt": 1710924000000,
"timeMachineEnabled": true,
"virtualTimeStartDate": 1710924000000
}

코드 검증 API

요청

  • 메서드: POST
  • 경로: /v1/access-codes/validate
  • 헤더:
    • Content-Type: application/json
  • 본문: ValidateAccessCodeDto

응답

  • 성공 응답 (200 OK): AccessCodeValidationResult
  • 오류 응답:
    • 400 Bad Request: 잘못된 요청 형식

예시

요청:

POST /v1/access-codes/validate
Content-Type: application/json

{
"code": "AB12CD34EF56GH78",
"deviceId": "DEVICE_001"
}

응답:

{
"isValid": true,
"codeInfo": {
"id": "code_789",
"treatmentPeriod": 90,
"expiresAt": 1713602400000
}
}

코드 사용 API

요청

  • 메서드: POST
  • 경로: /v1/access-codes/{codeId}/use
  • 헤더:
    • Content-Type: application/json
    • Authorization: Bearer {service_token}
  • 파라미터:
    • codeId: 사용할 코드 ID
  • 본문: UseAccessCodeDto

응답

  • 성공 응답 (200 OK): UsedAccessCode
  • 오류 응답:
    • 401 Unauthorized: 인증 실패
    • 403 Forbidden: 권한 없음
    • 400 Bad Request: 잘못된 요청 형식
    • 404 Not Found: 코드 없음
    • 409 Conflict: 이미 사용된 코드

예시

요청:

POST /v1/access-codes/code_789/use
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

{
"userId": "user_123",
"deviceId": "DEVICE_001",
"timeMachineOptions": {
"useTimeMachine": true,
"virtualTimeStartDate": 1711374600000,
"overrideCodeSettings": false,
"reason": "사용자 등록 처리"
}
}

응답:

{
"id": "code_789",
"status": "USED",
"usedAt": 1711374600000,
"userId": "user_123",
"timeMachineEnabled": true,
"virtualTimeStartDate": 1711374600000
}

일괄 코드 생성 API

요청

  • 메서드: POST
  • 경로: /v1/access-codes/batch
  • 헤더:
    • Content-Type: application/json
    • Authorization: Bearer {token}
    • X-Admin-Token: {admin_token}
  • 본문: BatchCreateAccessCodeDto

응답

  • 성공 응답 (201 Created): BatchResult
  • 오류 응답:
    • 401 Unauthorized: 인증 실패
    • 403 Forbidden: 권한 없음
    • 400 Bad Request: 잘못된 요청 형식

예시

요청:

POST /v1/access-codes/batch
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Admin-Token: admin-token-xyz

{
"count": 10,
"type": "TREATMENT",
"creatorId": "user_123",
"accountId": "account_456",
"treatmentPeriod": 90,
"usagePeriod": 30,
"registrationChannel": "WEB",
"timeMachineOptions": {
"useTimeMachineForAll": true,
"commonVirtualTimeStartDate": 1710924000000,
"expirationBasedOnVirtualTime": true,
"reason": "일괄 테스트 코드 생성"
}
}

응답:

{
"items": [
{
"id": "code_789",
"code": "AB12CD34EF56GH78",
"expiresAt": 1713602400000,
"status": "UNUSED",
"createdAt": 1710924000000,
"timeMachineEnabled": true,
"virtualTimeStartDate": 1710924000000
},
// 추가 코들...
],
"metadata": {
"totalCount": 10,
"currentPage": 1,
"pageSize": 10,
"totalPages": 1
},
"batchId": "batch_001",
"timeMachineEnabled": true
}

코드 생성 규칙

코드 형식

  • 기본 형식: 16자리 영숫자 조합 (대문자 영문 + 숫자)
  • 예: AB12CD34EF56GH78

보안 요구사항

  • 충분한 엔트로피 (최소 90비트)
  • 순차적이거나 패턴화된 코드 생성 금지
  • 안전한 난수 생성기 사용

만료 규칙

  • 생성 후 usagePeriod 일이 지나면 만료
  • TimeMachine 사용 시, 가상 시간 기준으로 만료일 계산 가능
  • 만료된 코드는 재사용 불가

사용 제한

  • 각 코드는 1회만 사용 가능
  • 동일 기기에서의 반복 검증 시도는 제한 (최대 5회/분)

오류 코드

Access Code 관련 오류 코드

HTTP 상태 코드오류 코드메시지설명대응 방법
4003001INVALID_CODE입력한 코드가 유효하지 않음코드를 확인하고 다시 시도
4003002CODE_ALREADY_USED입력한 코드가 이미 사용됨다른 코드를 사용하거나 관리자에게 문의
4003003CODE_EXPIRED입력한 코드가 만료됨새로운 코드를 발급받거나 관리자에게 문의
4093004DUPLICATE_CODE입력한 코드가 이미 존재함고유한 코드를 생성하거나 관리자에게 문의
4043005CODE_NOT_FOUND요청한 코드를 찾을 수 없음코드 ID를 확인하고 다시 시도
4003006INVALID_PARAMETERS코드 생성 매개변수가 유효하지 않음매개변수를 확인하고 다시 시도
4293007TOO_MANY_ATTEMPTS코드 검증 시도 횟수 초과잠시 후 다시 시도

TimeMachine 관련 오류 코드

HTTP 상태 코드오류 코드메시지설명대응 방법
4004001INVALID_VIRTUAL_TIME가상 시간 설정이 유효하지 않음유효한 시간을 설정하고 다시 시도
4094002TIME_MACHINE_DISABLEDTimeMachine 기능이 비활성화됨TimeMachine 기능을 활성화하거나 관리자에게 문의
4004003FUTURE_VIRTUAL_TIME현재보다 미래의 가상 시간으로 설정 불가현재 시간보다 이전 시간으로 설정
4004004VIRTUAL_TIME_TOO_OLD설정 가능한 범위보다 오래된 가상 시간허용된 범위 내의 시간으로 설정

보안 고려사항

개인정보 보호

  • 이메일 등 개인 식별 정보는 마스킹 처리
  • 저장 시 암호화 필수
  • 개인정보 처리는 명시적 동의 필요

접근 제어

  • 코드 생성은 관리자 권한 필요
  • 코드 검증은 인증 없이 가능
  • 코드 사용은 서비스 계정 인증 필요

감사 추적

  • 모든 코드 생성, 검증, 사용 이벤트 기록
  • 감사 로그는 최소 1년간 보관
  • 로그 항목에는 IP, 기기 ID, 타임스탬프 포함

성능 고려사항

  • 코드 검증 API는 최대 50ms 이내 응답
  • 배치 코드 생성은 비동기 처리 (대량 생성 시)
  • 캐싱을 통한 검증 속도 최적화

변경 이력

버전날짜작성자변경 내용
0.1.02025-04-02bok@weltcorp.com최초 작성