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 상태 코드 | 오류 코드 | 메시지 | 설명 | 대응 방법 |
|---|---|---|---|---|
| 400 | 3001 | INVALID_CODE | 입력한 코드가 유효하지 않음 | 코드를 확인하고 다시 시도 |
| 400 | 3002 | CODE_ALREADY_USED | 입력한 코드가 이미 사용됨 | 다른 코드를 사용하거나 관리자에게 문의 |
| 400 | 3003 | CODE_EXPIRED | 입력한 코드가 만료됨 | 새로운 코드를 발급받거나 관리자에게 문의 |
| 409 | 3004 | DUPLICATE_CODE | 입력한 코드가 이미 존재함 | 고유한 코드를 생성하거나 관리자에게 문의 |
| 404 | 3005 | CODE_NOT_FOUND | 요청한 코드를 찾을 수 없음 | 코드 ID를 확인하고 다시 시도 |
| 400 | 3006 | INVALID_PARAMETERS | 코드 생성 매개변수가 유효하지 않음 | 매개변수를 확인하고 다시 시도 |
| 429 | 3007 | TOO_MANY_ATTEMPTS | 코드 검증 시도 횟수 초과 | 잠시 후 다시 시도 |
TimeMachine 관련 오류 코드
| HTTP 상태 코드 | 오류 코드 | 메시지 | 설명 | 대응 방법 |
|---|---|---|---|---|
| 400 | 4001 | INVALID_VIRTUAL_TIME | 가상 시간 설정이 유효하지 않음 | 유효한 시간을 설정하고 다시 시도 |
| 409 | 4002 | TIME_MACHINE_DISABLED | TimeMachine 기능이 비활성화됨 | TimeMachine 기능을 활성화하거나 관리자에게 문의 |
| 400 | 4003 | FUTURE_VIRTUAL_TIME | 현재보다 미래의 가상 시간으로 설정 불가 | 현재 시간보다 이전 시간으로 설정 |
| 400 | 4004 | VIRTUAL_TIME_TOO_OLD | 설정 가능한 범위보다 오래된 가상 시간 | 허용된 범위 내의 시간으로 설정 |
보안 고려사항
개인정보 보호
- 이메일 등 개인 식별 정보는 마스킹 처리
- 저장 시 암호화 필수
- 개인정보 처리는 명시적 동의 필요
접근 제어
- 코드 생성은 관리자 권한 필요
- 코드 검증은 인증 없이 가능
- 코드 사용은 서비스 계정 인증 필요
감사 추적
- 모든 코드 생성, 검증, 사용 이벤트 기록
- 감사 로그는 최소 1년간 보관
- 로그 항목에는 IP, 기기 ID, 타임스탬프 포함
성능 고려사항
- 코드 검증 API는 최대 50ms 이내 응답
- 배치 코드 생성은 비동기 처리 (대량 생성 시)
- 캐싱을 통한 검증 속도 최적화
변경 이력
| 버전 | 날짜 | 작성자 | 변경 내용 |
|---|---|---|---|
| 0.1.0 | 2025-04-02 | bok@weltcorp.com | 최초 작성 |