본문으로 건너뛰기
버전: 개발 버전 (최신)

사용자 로그인 API

사용자 로그인 API는 등록된 사용자가 시스템에 접근하기 위한 인증 과정을 처리합니다. 성공적인 로그인 시, 액세스 토큰과 리프레시 토큰이 발급됩니다.

사용자 로그인

이메일과 해시된 비밀번호를 사용하여 사용자 로그인을 처리하고, 성공 시 토큰을 발급합니다.

  • HTTP Method: POST
  • Path: /de/v1/auth/login
  • 인증: 없음 (이 API는 토큰을 발급받기 위해 사용됨)

Headers

HeaderTypeDescriptionRequired
Content-Typeapplication/json요청 본문 형식을 지정합니다.Yes

Request Body (LoginDto)

비밀번호는 클라이언트에서 해싱하여 전송해야 합니다 (예: SHA-256).

{
"email": "user@example.com",
"passwordHash": "hashed_password_example",
"deviceUuid": "unique-device-id-12345"
}
필드타입설명필수 (Yes/No)
emailstring사용자 이메일 주소Yes
passwordHashstring클라이언트에서 해싱된 사용자 비밀번호Yes
deviceUuidstring디바이스 고유 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)
tokensarray인증 토큰 정보 배열 (TokenResponseDto[]). 정확히 2개의 토큰(access token과 refresh token)을 포함해야 합니다.Yes
tokens[].tokenstring토큰 값eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Yes
tokens[].typestring토큰 타입 (TokenType 참조)ACCESS_TOKEN 또는 REFRESH_TOKENYes
tokens[].expiresInnumber토큰 만료 시간(초)1800Yes
tokens[].issuedAtnumber토큰 발급 시간 (밀리초 단위 타임스탬프)1714523700000Yes
userobject사용자 정보 (UserDto)Yes
user.idstring사용자 IDYes
user.emailstring사용자 이메일Yes
user.questionnaireBundleIdstring사용자의 활성 설문지 번들 IDbundle-v1.2.0-20240301Yes
user.suspensionEndAtnumber일시정지 종료일 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64, isSuspended가 true일 때만 제공)1678972800000No
user.createdAtnumber사용자 생성일 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64)Yes
userCycleobject사용자 현재 활성 주기 정보 (UserCycleDto)Yes
userCycle.idstring사용자 주기 IDYes
userCycle.statusstring사용자 주기 상태 (UserCycleStatus 참조)ACTIVE (그 외: PENDING, COMPLETED, CANCELLED)Yes
userCycle.startedAtnumber주기 시작일 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64)Yes
userCycle.countnumber해당 사용자의 총 주기 개수Yes
profileobject사용자 프로필 정보 (UserProfileDto)Yes
profile.languagestring사용자 언어 설정 (LanguageCodes 참조) (예: 'de-DE')Yes
profile.timezoneobject사용자 시간대 정보 (TimezoneDto)Yes
profile.timezone.idstring사용자 시간대 ID (예: 'Europe/Berlin')Yes
profile.timezone.offsetInMinutesnumberUTC와의 시간차(분) (예: 60)Yes
profile.userNamestring사용자 이름 (예: 'John Doe') (선택 사항)No
rolesstring[]사용자 역할 이름 목록["healthcare.patientUser", "content.editor"]Yes
permissionsstring[]사용자 권한 문자열 목록["sleep.log.create", "sleep.log.read"]Yes
agreementsobject[]사용자 동의 항목 목록 (UserAgreementItemDto[])Yes
agreements[].versionIdstring동의한 약관 버전의 고유 ID8fa325dc-b97b-4aef-ac91-172aff3e9aceYes
agreements[].typestring약관 타입 (AgreementType 참조)CONSENTYes
agreements[].isRequiredboolean필수 약관 여부trueYes
agreements[].isAgreeableboolean동의 가능 여부. true이면 동의 체크박스를 표시하고, false이면 정보 표시 전용trueYes
agreements[].titlestring약관 이름 번역 (agreements_translations.name)DatenschutzhinweiseYes
agreements[].textstring약관 내용 요약Ich stimme den Nutzungsbedingungen gemäß der DiGAV § 4 Abs. 2 zu.Yes
agreements[].detailsUrlstring약관 상세 내용 URLhttps://sleepq.de/legal/einwilligungYes
agreements[].orderIndexnumber동의 항목 순서 인덱스 (type 별 default 값 존재)0Yes
agreements[].isAgreedboolean동의 여부trueYes
agreements[].agreedAtnumber사용자 동의 시각 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64)1767836575262Yes
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.tslogin 메서드의 로직을 기반으로 합니다.
  • 주요 검증 단계 (서비스 로직 내):
    1. 이메일로 사용자 조회 (authRepository.findUserByEmail).
    2. 사용자의 활성 userCycleId 확인 (authRepository.getUserActiveUserCycleId).
    3. 사용자 계정 상태 확인 (user.canLogin(), user.isLocked()).
    4. 사용자 passwordHash 존재 여부 확인.
    5. 제공된 hashedPassword와 저장된 user.passwordHash 비교 (passwordService.comparePassword).
  • 로그인 실패 시, 실패 횟수가 누적되며 (authRepository.incrementFailedLoginAttempts), 설정된 최대 실패 횟수 도달 시 계정이 잠길 수 있습니다 (authRepository.lockAccount).
  • 성공적인 로그인 시, 실패 횟수가 초기화되고 (authRepository.resetFailedLoginAttempts), 사용자의 마지막 로그인 시간이 업데이트됩니다 (authRepository.updateUserLastLogin).
  • deviceUuid는 토큰 생성 시 (tokenService.generateTokens) 사용될 수 있습니다.
  • 발급되는 토큰에는 사용자의 역할(roles), 권한(permissions), 동의한 약관(agreements) 정보가 포함될 수 있습니다.

LoginResponseDto 상세

최상위 응답 객체인 LoginResponseDto에 대한 상세 설명입니다.

필드타입설명예시 (일부)필수
tokensTokenResponseDto[]인증 토큰 정보 배열(TokenResponseDto 참조)Yes
userUserDto사용자 정보(UserDto 참조)Yes
userCycleUserCycleDto사용자 현재 활성 주기 정보(UserCycleDto 참조)Yes
profileUserProfileDto사용자 프로필 정보(UserProfileDto 참조)Yes
rolesstring[]사용자 역할 이름 목록["healthcare.patientUser", "content.editor"]Yes
permissionsstring[]사용자 권한 문자열 목록["sleep.log.create", "sleep.log.read"]Yes
agreementsUserAgreementItemDto[]사용자 동의 항목 목록(UserAgreementItemDto 배열 참조)Yes

TokenResponseDto 상세

LoginResponseDtotokens 배열에 있는 각 객체에 대한 상세 설명입니다. 이 배열은 반드시 정확히 2개의 토큰(access token과 refresh token)을 포함해야 합니다.

필드타입설명예시필수
tokenstring토큰 값eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Yes
typestring토큰 타입 (TokenType 참조)ACCESS_TOKEN 또는 REFRESH_TOKENYes
expiresInnumber토큰 만료 시간 (초, Kotlin: Int, Swift: Int)1800Yes
issuedAtnumber토큰 발급 시간 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64)1714523700000Yes

UserDto 상세

LoginResponseDtouser 객체에 대한 상세 설명입니다.

필드타입설명예시필수 (Yes/No)
idstring사용자 IDc1e7c5cf-6fc2-4f4f-8e2f-9b8a3c5e2d1bYes
emailstring사용자 이메일user@example.comYes
questionnaireBundleIdstring사용자의 활성 설문지 번들 IDbundle-v1.2.0-20240301Yes
suspensionEndAtnumber일시정지 종료일 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64, isSuspended가 true일 때만 제공)1678972800000No
createdAtnumber사용자 생성일 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64)1678886400000Yes

UserCycleDto 상세

LoginResponseDtouserCycle 객체에 대한 상세 설명입니다.

필드타입설명예시필수 (Yes/No)
idstring사용자 주기 IDd2f8e5c1-9b3a-4f8c-8e2f-1b9a3c5e2d7cYes
statusstring사용자 주기 상태ACTIVE (그 외: PENDING, COMPLETED, CANCELLED)Yes
startedAtnumber주기 시작일 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64)1678886400000Yes
countnumber해당 사용자의 총 주기 개수 (Kotlin: Int, Swift: Int)3Yes
treatmentDurationDaysnumber치료 주기의 총 기간 (일수, Kotlin: Int, Swift: Int)90Yes

UserProfileDto 상세

LoginResponseDtoprofile 객체에 대한 상세 설명입니다.

필드타입설명예시필수 (Yes/No)
languagestring사용자 언어 설정 (LanguageCodes 참조)deYes
timezoneTimezoneDto사용자 시간대 정보 (TimezoneDto 참조)(TimezoneDto 상세 섹션 참조)Yes
userNamestring사용자 이름 (예: 'John Doe') (선택 사항)John DoeNo

TimezoneDto 상세

UserProfileDto 내의 timezone 객체에 대한 상세 설명입니다.

필드타입설명예시필수 (Yes/No)
idstring사용자 시간대 ID (예: 'Europe/Berlin')Europe/BerlinYes
offsetInMinutesnumberUTC와의 시간차 (분, Kotlin: Int, Swift: Int) (예: 60)60Yes

변경 이력

버전날짜작성자변경 내용
0.1.02025-05-19elizabeth@weltcorp.com최초 문서 작성
0.2.02025-05-27elizabeth@weltcorp.com401 Unauthorized 오류 추가 (2051 INVALID_TOKEN)
0.3.02025-06-24elizabeth@weltcorp.comUserDto에 questionnaireBundleId 필드 추가
0.4.02025-06-25elizabeth@weltcorp.comUserDto에 suspensionEndAt 필드 추가
0.5.02025-07-15bok@weltcorp.comUserCycleDto에 treatmentDurationDays 필드 추가 (치료 주기 총 기간)
0.65.02026-01-14pibi@weltcorp.comresponse 에 orderIndex 필드 추가
0.67.02026-02-23jeff@weltcorp.comagreements[].isAgreeable 필드 추가