사용자 로그인 API
사용자 로그인 API는 등록된 사용자가 시스템에 접근하기 위한 인증 과정을 처리합니다. 성공적인 로그인 시, 액세스 토큰과 리프레시 토큰이 발급됩니다.
사용자 로그인
이메일과 해시된 비밀번호를 사용하여 사용자 로그인을 처리하고, 성공 시 토큰을 발급합니다.
- HTTP Method:
POST - Path:
/de/v1/auth/login - 인증: 없음 (이 API는 토큰을 발급받기 위해 사용됨)
Headers
| Header | Type | Description | Required |
|---|---|---|---|
Content-Type | application/json | 요청 본문 형식을 지정합니다. | Yes |
Request Body (LoginDto)
비밀번호는 클라이언트에서 해싱하여 전송해야 합니다 (예: SHA-256).
{
"email": "user@example.com",
"passwordHash": "hashed_password_example",
"deviceUuid": "unique-device-id-12345"
}
| 필드 | 타입 | 설명 | 필수 (Yes/No) |
|---|---|---|---|
email | string | 사용자 이메일 주소 | Yes |
passwordHash | string | 클라이언트에서 해싱된 사용자 비밀번호 | Yes |
deviceUuid | string | 디바이스 고유 UUID (선택 사항) | No |
Responses
| HTTP Status Code | 설명 | Error Code(s) |
|---|---|---|
200 OK | 로그인 성공 | - |
400 Bad Request | 잘못된 요청 (예: 잘못된 자격 증명, 계정 잠김) | 2010, 2004 |
401 Unauthorized | 앱 토큰 인증 실패 | 2051 |
404 Not Found | 리소스 없음 (예: 사용자 주기 없음) | 7003 |
500 Internal Server Error | 서버 내부 오류 | 2000 |
200 OK - 로그인 성공
성공적으로 로그인하면 LoginResponseDto에 정의된 전체 사용자 세션 정보가 반환됩니다. 여기에는 인증 토큰, 사용자 정보, 현재 활성 주기, 프로필 정보, 역할, 권한 및 동의한 약관 목록이 포함됩니다.
{
"tokens": [
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"type": "ACCESS_TOKEN",
"expiresIn": 1800,
"issuedAt": 1714523700000
},
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"type": "REFRESH_TOKEN",
"expiresIn": 604800,
"issuedAt": 1714523700000
}
],
"user": {
"id": "c1e7c5cf-6fc2-4f4f-8e2f-9b8a3c5e2d1b",
"email": "user@example.com",
"questionnaireBundleId": "bundle-v1.2.0-20240301",
"suspensionEndAt": 1678972800000,
"createdAt": 1678886400000
},
"userCycle": {
"id": "d2f8e5c1-9b3a-4f8c-8e2f-1b9a3c5e2d7c",
"status": "ACTIVE",
"startedAt": 1678886400000,
"count": 3,
"treatmentDurationDays": 90
},
"profile": {
"language": "de-DE",
"timezone": {
"id": "Europe/Berlin",
"offsetInMinutes": 60
},
"userName": "John Doe"
},
"roles": ["healthcare.patientUser", "content.editor"],
"permissions": ["sleep.log.create", "sleep.log.read", "sleep.goal.update"],
"agreements": [
{
"versionId": "some-version-id",
"type": "CONSENT",
"isRequired": false,
"isAgreeable": true,
"title": "Datenverarbeitung zur Verbesserung",
"text": "Ich erlaube die Verarbeitung der zu der dauerhaften Gewährleistung der technischen Funktionsfähigkeit...",
"detailsUrl": "https://www.weltcorp.com",
"orderIndex": 6,
"isAgreed": false,
"agreedAt": 1699981800000
}
]
}
| 필드 | 타입 | 설명 | 예시 | 필수 (Yes/No) |
|---|---|---|---|---|
tokens | array | 인증 토큰 정보 배열 (TokenResponseDto[]). 정확히 2개의 토큰(access token과 refresh token)을 포함해야 합니다. | Yes | |
tokens[].token | string | 토큰 값 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... | Yes |
tokens[].type | string | 토큰 타입 (TokenType 참조) | ACCESS_TOKEN 또는 REFRESH_TOKEN | Yes |
tokens[].expiresIn | number | 토큰 만료 시간(초) | 1800 | Yes |
tokens[].issuedAt | number | 토큰 발급 시간 (밀리초 단위 타임스탬프) | 1714523700000 | Yes |
user | object | 사용자 정보 (UserDto) | Yes | |
user.id | string | 사용자 ID | Yes | |
user.email | string | 사용자 이메일 | Yes | |
user.questionnaireBundleId | string | 사용자의 활성 설문지 번들 ID | bundle-v1.2.0-20240301 | Yes |
user.suspensionEndAt | number | 일시정지 종료일 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64, isSuspended가 true일 때만 제공) | 1678972800000 | No |
user.createdAt | number | 사용자 생성일 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64) | Yes | |
userCycle | object | 사용자 현재 활성 주기 정보 (UserCycleDto) | Yes | |
userCycle.id | string | 사용자 주기 ID | Yes | |
userCycle.status | string | 사용자 주기 상태 (UserCycleStatus 참조) | ACTIVE (그 외: PENDING, COMPLETED, CANCELLED) | Yes |
userCycle.startedAt | number | 주기 시작일 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64) | Yes | |
userCycle.count | number | 해당 사용자의 총 주기 개수 | Yes | |
profile | object | 사용자 프로필 정보 (UserProfileDto) | Yes | |
profile.language | string | 사용자 언어 설정 (LanguageCodes 참조) (예: 'de-DE') | Yes | |
profile.timezone | object | 사용자 시간대 정보 (TimezoneDto) | Yes | |
profile.timezone.id | string | 사용자 시간대 ID (예: 'Europe/Berlin') | Yes | |
profile.timezone.offsetInMinutes | number | UTC와의 시간차(분) (예: 60) | Yes | |
profile.userName | string | 사용자 이름 (예: 'John Doe') (선택 사항) | No | |
roles | string[] | 사용자 역할 이름 목록 | ["healthcare.patientUser", "content.editor"] | Yes |
permissions | string[] | 사용자 권한 문자열 목록 | ["sleep.log.create", "sleep.log.read"] | Yes |
agreements | object[] | 사용자 동의 항목 목록 (UserAgreementItemDto[]) | Yes | |
agreements[].versionId | string | 동의한 약관 버전의 고유 ID | 8fa325dc-b97b-4aef-ac91-172aff3e9ace | Yes |
agreements[].type | string | 약관 타입 (AgreementType 참조) | CONSENT | Yes |
agreements[].isRequired | boolean | 필수 약관 여부 | true | Yes |
agreements[].isAgreeable | boolean | 동의 가능 여부. true이면 동의 체크박스를 표시하고, false이면 정보 표시 전용 | true | Yes |
agreements[].title | string | 약관 이름 번역 (agreements_translations.name) | Datenschutzhinweise | Yes |
agreements[].text | string | 약관 내용 요약 | Ich stimme den Nutzungsbedingungen gemäß der DiGAV § 4 Abs. 2 zu. | Yes |
agreements[].detailsUrl | string | 약관 상세 내용 URL | https://sleepq.de/legal/einwilligung | Yes |
agreements[].orderIndex | number | 동의 항목 순서 인덱스 (type 별 default 값 존재) | 0 | Yes |
agreements[].isAgreed | boolean | 동의 여부 | true | Yes |
agreements[].agreedAt | number | 사용자 동의 시각 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64) | 1767836575262 | Yes |
400 Bad Request - 잘못된 요청
예시: 잘못된 자격 증명 (이메일 또는 비밀번호 오류)
{
"code": 2010, // AuthErrorCode.INVALID_CREDENTIALS
"message": "INVALID_CREDENTIALS",
"detail": "이메일 또는 비밀번호가 올바르지 않습니다",
"metadata": {
"remainingAttempts": 4
}
}
이 오류는 InvalidCredentialsError에 해당하며, authentication.service.ts에서 사용자를 찾지 못하거나 비밀번호가 일치하지 않을 때 발생합니다.
이메일 또는 비밀번호 형식이 잘못된 경우에도 이 오류가 발생할 수 있습니다.
예시: 계정 잠김
{
"code": 2004, // AuthErrorCode.ACCOUNT_LOCKED
"message": "ACCOUNT_LOCKED",
"detail": "계정이 잠겨 있습니다",
"metadata": {
"remainingLockoutSeconds": 58
}
}
이 오류는 AccountLockedError에 해당하며, 로그인 시도 실패 횟수 초과 등으로 계정이 잠겼을 때 발생합니다.
401 Unauthorized - 앱 토큰 인증 실패
{
"code": 2051,
"message": "INVALID_TOKEN",
"detail": "토큰이 유효하지 않습니다"
}
404 Not Found - 리소스 없음
예시: 사용자 활성 주기(UserCycle)를 찾을 수 없음
{
"code": 7003, // UserErrorCode.USER_CYCLE_NOT_FOUND
"message": "USER_CYCLE_NOT_FOUND",
"detail": "사용자 주기를 찾을 수 없습니다.",
"metadata": {
"userId": "user-id-example"
}
}
이 오류는 UserCycleNotFoundError에 해당하며, authentication.service.ts에서 사용자의 활성 UserCycle ID를 조회할 수 없을 때 발생합니다.
500 Internal Server Error - 서버 내부 오류
{
"code": 2000, // AuthErrorCode.SERVER_ERROR
"message": "SERVER_ERROR",
"detail": "서버 내부 오류"
}
이 오류는 ServerError에 해당하며, 처리 중 예기치 않은 서버 내부 문제가 발생했을 때 반환될 수 있습니다.
설명
- 이 API는
libs/feature/auth/src/lib/infrastructure/services/authentication.service.ts의login메서드의 로직을 기반으로 합니다. - 주요 검증 단계 (서비스 로직 내):
- 이메일로 사용자 조회 (
authRepository.findUserByEmail). - 사용자의 활성
userCycleId확인 (authRepository.getUserActiveUserCycleId). - 사용자 계정 상태 확인 (
user.canLogin(),user.isLocked()). - 사용자
passwordHash존재 여부 확인. - 제공된
hashedPassword와 저장된user.passwordHash비교 (passwordService.comparePassword).
- 이메일로 사용자 조회 (
- 로그인 실패 시, 실패 횟수가 누적되며 (
authRepository.incrementFailedLoginAttempts), 설정된 최대 실패 횟수 도달 시 계정이 잠길 수 있습니다 (authRepository.lockAccount). - 성공적인 로그인 시, 실패 횟수가 초기화되고 (
authRepository.resetFailedLoginAttempts), 사용자의 마지막 로그인 시간이 업데이트됩니다 (authRepository.updateUserLastLogin). deviceUuid는 토큰 생성 시 (tokenService.generateTokens) 사용될 수 있습니다.- 발급되는 토큰에는 사용자의 역할(roles), 권한(permissions), 동의한 약관(agreements) 정보가 포함될 수 있습니다.
LoginResponseDto 상세
최상위 응답 객체인 LoginResponseDto에 대한 상세 설명입니다.
| 필드 | 타입 | 설명 | 예시 (일부) | 필수 |
|---|---|---|---|---|
tokens | TokenResponseDto[] | 인증 토큰 정보 배열 | (TokenResponseDto 참조) | Yes |
user | UserDto | 사용자 정보 | (UserDto 참조) | Yes |
userCycle | UserCycleDto | 사용자 현재 활성 주기 정보 | (UserCycleDto 참조) | Yes |
profile | UserProfileDto | 사용자 프로필 정보 | (UserProfileDto 참조) | Yes |
roles | string[] | 사용자 역할 이름 목록 | ["healthcare.patientUser", "content.editor"] | Yes |
permissions | string[] | 사용자 권한 문자열 목록 | ["sleep.log.create", "sleep.log.read"] | Yes |
agreements | UserAgreementItemDto[] | 사용자 동의 항목 목록 | (UserAgreementItemDto 배열 참조) | Yes |
TokenResponseDto 상세
LoginResponseDto의 tokens 배열에 있는 각 객체에 대한 상세 설명입니다. 이 배열은 반드시 정확히 2개의 토큰(access token과 refresh token)을 포함해야 합니다.
| 필드 | 타입 | 설명 | 예시 | 필수 |
|---|---|---|---|---|
token | string | 토큰 값 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... | Yes |
type | string | 토큰 타입 (TokenType 참조) | ACCESS_TOKEN 또는 REFRESH_TOKEN | Yes |
expiresIn | number | 토큰 만료 시간 (초, Kotlin: Int, Swift: Int) | 1800 | Yes |
issuedAt | number | 토큰 발급 시간 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64) | 1714523700000 | Yes |
UserDto 상세
LoginResponseDto의 user 객체에 대한 상세 설명입니다.
| 필드 | 타입 | 설명 | 예시 | 필수 (Yes/No) |
|---|---|---|---|---|
id | string | 사용자 ID | c1e7c5cf-6fc2-4f4f-8e2f-9b8a3c5e2d1b | Yes |
email | string | 사용자 이메일 | user@example.com | Yes |
questionnaireBundleId | string | 사용자의 활성 설문지 번들 ID | bundle-v1.2.0-20240301 | Yes |
suspensionEndAt | number | 일시정지 종료일 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64, isSuspended가 true일 때만 제공) | 1678972800000 | No |
createdAt | number | 사용자 생성일 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64) | 1678886400000 | Yes |
UserCycleDto 상세
LoginResponseDto의 userCycle 객체에 대한 상세 설명입니다.
| 필드 | 타입 | 설명 | 예시 | 필수 (Yes/No) |
|---|---|---|---|---|
id | string | 사용자 주기 ID | d2f8e5c1-9b3a-4f8c-8e2f-1b9a3c5e2d7c | Yes |
status | string | 사용자 주기 상태 | ACTIVE (그 외: PENDING, COMPLETED, CANCELLED) | Yes |
startedAt | number | 주기 시작일 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64) | 1678886400000 | Yes |
count | number | 해당 사용자의 총 주기 개수 (Kotlin: Int, Swift: Int) | 3 | Yes |
treatmentDurationDays | number | 치료 주기의 총 기간 (일수, Kotlin: Int, Swift: Int) | 90 | Yes |
UserProfileDto 상세
LoginResponseDto의 profile 객체에 대한 상세 설명입니다.
| 필드 | 타입 | 설명 | 예시 | 필수 (Yes/No) |
|---|---|---|---|---|
language | string | 사용자 언어 설정 (LanguageCodes 참조) | de | Yes |
timezone | TimezoneDto | 사용자 시간대 정보 (TimezoneDto 참조) | (TimezoneDto 상세 섹션 참조) | Yes |
userName | string | 사용자 이름 (예: 'John Doe') (선택 사항) | John Doe | No |
TimezoneDto 상세
UserProfileDto 내의 timezone 객체에 대한 상세 설명입니다.
| 필드 | 타입 | 설명 | 예시 | 필수 (Yes/No) |
|---|---|---|---|---|
id | string | 사용자 시간대 ID (예: 'Europe/Berlin') | Europe/Berlin | Yes |
offsetInMinutes | number | UTC와의 시간차 (분, Kotlin: Int, Swift: Int) (예: 60) | 60 | Yes |
변경 이력
| 버전 | 날짜 | 작성자 | 변경 내용 |
|---|---|---|---|
| 0.1.0 | 2025-05-19 | elizabeth@weltcorp.com | 최초 문서 작성 |
| 0.2.0 | 2025-05-27 | elizabeth@weltcorp.com | 401 Unauthorized 오류 추가 (2051 INVALID_TOKEN) |
| 0.3.0 | 2025-06-24 | elizabeth@weltcorp.com | UserDto에 questionnaireBundleId 필드 추가 |
| 0.4.0 | 2025-06-25 | elizabeth@weltcorp.com | UserDto에 suspensionEndAt 필드 추가 |
| 0.5.0 | 2025-07-15 | bok@weltcorp.com | UserCycleDto에 treatmentDurationDays 필드 추가 (치료 주기 총 기간) |
| 0.65.0 | 2026-01-14 | pibi@weltcorp.com | response 에 orderIndex 필드 추가 |
| 0.67.0 | 2026-02-23 | jeff@weltcorp.com | agreements[].isAgreeable 필드 추가 |