05cc4311a7
honeyDue identity is now owned by Ory Kratos (auth.myhoneydue.com). The
honeyDue Go API no longer does auth — authenticated API requests carry the
Kratos session token on the X-Session-Token header (the old
`Authorization: Token <token>` scheme is gone).
What changed:
- models/Kratos.kt (new): models for Kratos native (`api`) self-service
flows — flow envelope (id + ui.action + ui.nodes/messages), login/
registration success bodies, OIDC/password/recovery/verification submit
payloads, session + identity + traits.
- ApiConfig.kt / ApiClient.kt: add getKratosBaseUrl() — LOCAL points at a
localhost Kratos (:4433), DEV/PROD at auth.myhoneydue.com. Add the
SESSION_TOKEN_HEADER ("X-Session-Token") constant and an authHeader()
request extension.
- AuthApi.kt: rewritten to drive Kratos native flows —
login (GET .../self-service/login/api -> POST ui.action with
method:password), registration (traits:{email,name{first,last}}),
recovery + verification (method:code), Apple/Google via OIDC
(method:oidc, provider, id_token). Kratos validation errors are pulled
from ui.nodes[].messages / ui.messages. On success the Kratos
session_token is resolved against honeyDue /auth/me (still session-token
gated) to assemble AuthResponse. Public method signatures + return types
are unchanged, so APILayer / AuthViewModel / UI / iOS Swift compile
against the same ApiResult<...> shapes with no rework.
- ApiClient.kt: the 401 handler now re-validates the Kratos session via
/sessions/whoami instead of calling a (now-gone) refresh endpoint.
TokenExpiredException is kept (messages updated).
- All 10 honeyDue API clients + AuthenticatedImage + CoilAuthInterceptor:
send X-Session-Token instead of Authorization: Token. CoilAuthInterceptor
drops the authScheme prefix in favour of a configurable headerName.
- iOS Swift: AuthenticatedImage / DocumentDetailView / PresignedUploader
switched to the X-Session-Token header. iOS auth ViewModels keep native
login/registration/recovery forms and need no other change because the
Kotlin APILayer surface is identical — no browser redirect.
- Tests: CoilAuthInterceptorTest rewritten for the X-Session-Token scheme;
HttpClientPluginsTest TokenExpiredException assertions updated.
Verified: :composeApp:compileDebugKotlinAndroid, :assembleDebug and
:compileKotlinIosSimulatorArm64 all build; network/auth unit tests pass.
iOS Swift not built here (no Xcode toolchain) but is correct by
construction against the unchanged Kotlin API.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
233 lines
5.4 KiB
Kotlin
233 lines
5.4 KiB
Kotlin
package com.tt.honeyDue.models
|
|
|
|
import kotlinx.serialization.SerialName
|
|
import kotlinx.serialization.Serializable
|
|
|
|
/**
|
|
* User model matching Go API UserResponse/CurrentUserResponse
|
|
*/
|
|
@Serializable
|
|
data class User(
|
|
val id: Int,
|
|
val username: String,
|
|
val email: String,
|
|
@SerialName("first_name") val firstName: String = "",
|
|
@SerialName("last_name") val lastName: String = "",
|
|
@SerialName("is_active") val isActive: Boolean = true,
|
|
@SerialName("date_joined") val dateJoined: String,
|
|
@SerialName("last_login") val lastLogin: String? = null,
|
|
@SerialName("auth_provider") val authProvider: String? = null,
|
|
// Profile is included in CurrentUserResponse (/auth/me)
|
|
val profile: UserProfile? = null,
|
|
// Verified is returned directly in LoginResponse, and also in profile for CurrentUserResponse
|
|
@SerialName("verified") private val _verified: Boolean = false
|
|
) {
|
|
// Computed property for display name
|
|
val displayName: String
|
|
get() = when {
|
|
firstName.isNotBlank() && lastName.isNotBlank() -> "$firstName $lastName"
|
|
firstName.isNotBlank() -> firstName
|
|
lastName.isNotBlank() -> lastName
|
|
else -> username
|
|
}
|
|
|
|
// Check if user is verified - from direct field (login) OR from profile (currentUser)
|
|
val isVerified: Boolean
|
|
get() = _verified || (profile?.verified ?: false)
|
|
|
|
// Alias for backwards compatibility
|
|
val verified: Boolean
|
|
get() = isVerified
|
|
}
|
|
|
|
/**
|
|
* User profile model matching Go API UserProfileResponse
|
|
*/
|
|
@Serializable
|
|
data class UserProfile(
|
|
val id: Int,
|
|
@SerialName("user_id") val userId: Int,
|
|
val verified: Boolean = false,
|
|
val bio: String = "",
|
|
@SerialName("phone_number") val phoneNumber: String = "",
|
|
@SerialName("date_of_birth") val dateOfBirth: String? = null,
|
|
@SerialName("profile_picture") val profilePicture: String = ""
|
|
)
|
|
|
|
/**
|
|
* Register request matching Go API
|
|
*/
|
|
@Serializable
|
|
data class RegisterRequest(
|
|
val username: String,
|
|
val email: String,
|
|
val password: String,
|
|
@SerialName("first_name") val firstName: String? = null,
|
|
@SerialName("last_name") val lastName: String? = null
|
|
)
|
|
|
|
/**
|
|
* Login request matching Go API
|
|
*/
|
|
@Serializable
|
|
data class LoginRequest(
|
|
val username: String,
|
|
val password: String
|
|
)
|
|
|
|
/**
|
|
* Auth response for login - matching Go API LoginResponse
|
|
*/
|
|
@Serializable
|
|
data class AuthResponse(
|
|
val token: String,
|
|
val user: User
|
|
)
|
|
|
|
/**
|
|
* Token refresh response.
|
|
*
|
|
* Identity is owned by Ory Kratos. Native Kratos session tokens are
|
|
* long-lived and not rotated — there is no refresh endpoint. This type is
|
|
* retained as the return shape of [com.tt.honeyDue.network.AuthApi.refreshToken],
|
|
* which now re-validates the session via Kratos `/sessions/whoami` and echoes
|
|
* the same (unchanged) token back when the session is still active.
|
|
*/
|
|
@Serializable
|
|
data class TokenRefreshResponse(
|
|
val token: String
|
|
)
|
|
|
|
/**
|
|
* Auth response for registration - matching Go API RegisterResponse
|
|
*/
|
|
@Serializable
|
|
data class RegisterResponse(
|
|
val token: String,
|
|
val user: User,
|
|
val message: String = ""
|
|
)
|
|
|
|
/**
|
|
* Verify email request
|
|
*/
|
|
@Serializable
|
|
data class VerifyEmailRequest(
|
|
val code: String
|
|
)
|
|
|
|
/**
|
|
* Verify email response
|
|
*/
|
|
@Serializable
|
|
data class VerifyEmailResponse(
|
|
val message: String,
|
|
val verified: Boolean
|
|
)
|
|
|
|
/**
|
|
* Update profile request
|
|
*/
|
|
@Serializable
|
|
data class UpdateProfileRequest(
|
|
@SerialName("first_name") val firstName: String? = null,
|
|
@SerialName("last_name") val lastName: String? = null,
|
|
val email: String? = null
|
|
)
|
|
|
|
// Password Reset Models
|
|
|
|
@Serializable
|
|
data class ForgotPasswordRequest(
|
|
val email: String
|
|
)
|
|
|
|
@Serializable
|
|
data class ForgotPasswordResponse(
|
|
val message: String
|
|
)
|
|
|
|
@Serializable
|
|
data class VerifyResetCodeRequest(
|
|
val email: String,
|
|
val code: String
|
|
)
|
|
|
|
@Serializable
|
|
data class VerifyResetCodeResponse(
|
|
val message: String,
|
|
@SerialName("reset_token") val resetToken: String
|
|
)
|
|
|
|
@Serializable
|
|
data class ResetPasswordRequest(
|
|
@SerialName("reset_token") val resetToken: String,
|
|
@SerialName("new_password") val newPassword: String
|
|
)
|
|
|
|
@Serializable
|
|
data class ResetPasswordResponse(
|
|
val message: String
|
|
)
|
|
|
|
/**
|
|
* Generic message response used by many endpoints
|
|
*/
|
|
@Serializable
|
|
data class MessageResponse(
|
|
val message: String
|
|
)
|
|
|
|
// Apple Sign In Models
|
|
|
|
/**
|
|
* Apple Sign In request matching Go API
|
|
*/
|
|
@Serializable
|
|
data class AppleSignInRequest(
|
|
@SerialName("id_token") val idToken: String,
|
|
@SerialName("user_id") val userId: String,
|
|
val email: String? = null,
|
|
@SerialName("first_name") val firstName: String? = null,
|
|
@SerialName("last_name") val lastName: String? = null
|
|
)
|
|
|
|
/**
|
|
* Apple Sign In response matching Go API
|
|
*/
|
|
@Serializable
|
|
data class AppleSignInResponse(
|
|
val token: String,
|
|
val user: User,
|
|
@SerialName("is_new_user") val isNewUser: Boolean
|
|
)
|
|
|
|
// Google Sign In Models
|
|
|
|
/**
|
|
* Google Sign In request matching Go API
|
|
*/
|
|
@Serializable
|
|
data class GoogleSignInRequest(
|
|
@SerialName("id_token") val idToken: String
|
|
)
|
|
|
|
/**
|
|
* Google Sign In response matching Go API
|
|
*/
|
|
@Serializable
|
|
data class GoogleSignInResponse(
|
|
val token: String,
|
|
val user: User,
|
|
@SerialName("is_new_user") val isNewUser: Boolean
|
|
)
|
|
|
|
/**
|
|
* Delete account request matching Go API
|
|
*/
|
|
@Serializable
|
|
data class DeleteAccountRequest(
|
|
val password: String? = null,
|
|
val confirmation: String? = null
|
|
)
|