diff --git a/Core b/Core
index 308bf87..cc66df0 160000
--- a/Core
+++ b/Core
@@ -1 +1 @@
-Subproject commit 308bf878381e5c1ce989f7d4ad1d0aead844ca44
+Subproject commit cc66df014eee602c935a48719ba14c328e421810
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index e807906..e0f7b8e 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,6 +2,7 @@
kotlin = "2.3.0"
kotlinxCollectionsImmutable = "0.4.0"
ksp = "2.3.4"
+ktor = "3.3.0"
lifecycleViewmodelNav3 = "2.10.0"
nav3Core = "1.0.0"
room = "2.8.4"
@@ -16,6 +17,13 @@ androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref =
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinxCollectionsImmutable" }
+ktor-client-content-negociation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
+ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
+ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
+ktor-client-encoding = { module = "io.ktor:ktor-client-encoding", version.ref = "ktor" }
+ktor-client-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
+ktor-client-mock = { module = "io.ktor:ktor-client-mock", version.ref = "ktor" }
+ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
infomaniak-core-auth = { module = "com.infomaniak.core:Auth" }
infomaniak-core-common = { module = "com.infomaniak.core:Common" }
diff --git a/multiplatform-lib/build.gradle.kts b/multiplatform-lib/build.gradle.kts
index f32c2e9..179e531 100644
--- a/multiplatform-lib/build.gradle.kts
+++ b/multiplatform-lib/build.gradle.kts
@@ -52,20 +52,32 @@ kotlin {
commonMain {
dependencies {
implementation(core.kotlinx.coroutines.core)
+ implementation(core.kotlinx.serialization.cbor)
+ implementation(libs.ktor.client.core)
+ implementation(libs.ktor.client.content.negociation)
+ implementation(libs.ktor.client.json)
+ implementation(libs.ktor.client.encoding)
}
}
commonTest {
dependencies {
implementation(kotlin("test"))
implementation(core.kotlinx.coroutines.test)
+ implementation(libs.ktor.client.mock)
}
}
androidMain {
dependencies {
+ implementation(libs.ktor.client.okhttp)
implementation(core.splitties.appctx)
implementation(core.splitties.bitflags)
}
}
+ iosMain {
+ dependencies {
+ implementation(libs.ktor.client.darwin)
+ }
+ }
val androidDeviceTest by getting {
dependencies {
implementation(core.androidx.junit)
diff --git a/multiplatform-lib/src/commonMain/kotlin/AuthenticatorInjection.kt b/multiplatform-lib/src/commonMain/kotlin/AuthenticatorInjection.kt
new file mode 100644
index 0000000..0e7ebb4
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/AuthenticatorInjection.kt
@@ -0,0 +1,50 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+import com.infomaniak.auth.lib.network.ApiClientProvider
+import com.infomaniak.auth.lib.network.interfaces.CrashReportInterface
+import network.repositories.WebAuthnRepository
+import network.utils.ApiEnvironment
+
+/*
+ Infomaniak Authenticator - Android
+ Copyright (C) 2026 Infomaniak Network SA
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+ */
+
+class AuthenticatorInjection(
+ private val environment: ApiEnvironment,
+ private val userAgent: String,
+ private val databaseRootDirectory: String? = null,
+ private val crashReport: CrashReportInterface,
+) {
+ private val apiClientProvider by lazy { ApiClientProvider(userAgent, crashReport) }
+
+ private val webAuthnRepository by lazy { WebAuthnRepository(apiClientProvider, environment) }
+}
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/ApiClientProvider.kt b/multiplatform-lib/src/commonMain/kotlin/network/ApiClientProvider.kt
new file mode 100644
index 0000000..d1be0a8
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/ApiClientProvider.kt
@@ -0,0 +1,164 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.auth.lib.network
+
+import com.infomaniak.auth.lib.network.interfaces.BreadcrumbType
+import com.infomaniak.auth.lib.network.interfaces.CrashReportInterface
+import com.infomaniak.auth.lib.network.interfaces.CrashReportLevel
+import com.infomaniak.auth.lib.network.models.ApiError
+import com.infomaniak.auth.lib.network.utils.getRequestContextId
+import io.ktor.client.HttpClient
+import io.ktor.client.HttpClientConfig
+import io.ktor.client.engine.HttpClientEngine
+import io.ktor.client.plugins.HttpRequestRetry
+import io.ktor.client.plugins.HttpResponseValidator
+import io.ktor.client.plugins.HttpTimeout
+import io.ktor.client.plugins.UserAgent
+import io.ktor.client.plugins.compression.ContentEncoding
+import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
+import io.ktor.client.statement.HttpResponse
+import io.ktor.client.statement.bodyAsText
+import io.ktor.client.statement.request
+import io.ktor.http.contentLength
+import io.ktor.serialization.kotlinx.json.json
+import io.ktor.utils.io.CancellationException
+import kotlinx.io.IOException
+import kotlinx.serialization.json.Json
+import network.exceptions.ApiException
+import network.exceptions.ApiException.ApiErrorException
+import network.exceptions.ApiException.UnexpectedApiErrorFormatException
+import network.exceptions.NetworkException
+import kotlin.time.Duration.Companion.seconds
+
+class ApiClientProvider internal constructor(
+ engine: HttpClientEngine? = null,
+ // When you don't use AuthenticatorInjection, you don't have an userAgent, so we're currently setting a default value.
+ // See later how to improve it.
+ private val userAgent: String = "Ktor client",
+ private val crashReport: CrashReportInterface? = null,
+) {
+
+ constructor() : this(null)
+ constructor(userAgent: String, crashReport: CrashReportInterface) : this(
+ engine = null,
+ userAgent = userAgent,
+ crashReport = crashReport,
+ )
+
+ val json = Json {
+ ignoreUnknownKeys = true
+ coerceInputValues = true
+ isLenient = true
+ useAlternativeNames = false
+ }
+
+ val httpClient = createHttpClient(engine)
+
+ fun createHttpClient(engine: HttpClientEngine?): HttpClient {
+ val block: HttpClientConfig<*>.() -> Unit = {
+ install(UserAgent) {
+ agent = userAgent
+ }
+ install(ContentNegotiation) {
+ json(this@ApiClientProvider.json)
+ }
+ install(ContentEncoding) {
+ gzip()
+ }
+ install(HttpTimeout) {
+ // Each value can be fine-tuned independently, hence the value not being shared.
+ requestTimeoutMillis = 10.seconds.inWholeMilliseconds
+ connectTimeoutMillis = 10.seconds.inWholeMilliseconds
+ socketTimeoutMillis = 10.seconds.inWholeMilliseconds
+ }
+ install(HttpRequestRetry) {
+ retryOnExceptionIf(maxRetries = MAX_RETRY) { _, cause ->
+ cause.isNetworkException()
+ }
+ delayMillis { retry ->
+ retry * 500L
+ }
+ }
+ HttpResponseValidator {
+ validateResponse { response: HttpResponse ->
+ val requestContextId = response.getRequestContextId()
+ val statusCode = response.status.value
+
+ addSentryUrlBreadcrumb(response, statusCode, requestContextId)
+
+ if (statusCode >= 300) {
+ val bodyResponse = response.bodyAsText()
+ val apiError = runCatching {
+ json.decodeFromString(bodyResponse)
+ }.getOrElse {
+ throw UnexpectedApiErrorFormatException(statusCode, bodyResponse, null, requestContextId)
+ }
+ throw ApiErrorException(apiError.errorCode, apiError.message, requestContextId)
+ }
+ }
+ handleResponseExceptionWithRequest { cause, request ->
+ when (cause) {
+ is IOException -> throw NetworkException("Network error: ${cause.message}")
+ is ApiException, is CancellationException -> throw cause
+ else -> {
+ val response = runCatching { request.call.response }.getOrNull()
+ val requestContextId = response?.getRequestContextId() ?: ""
+ val bodyResponse = response?.bodyAsText() ?: cause.message ?: ""
+ val statusCode = response?.status?.value ?: -1
+ throw UnexpectedApiErrorFormatException(
+ statusCode,
+ bodyResponse,
+ cause,
+ requestContextId
+ )
+ }
+ }
+ }
+ }
+ }
+
+ return if (engine != null) HttpClient(engine, block) else HttpClient(block)
+ }
+
+ private fun addSentryUrlBreadcrumb(response: HttpResponse, statusCode: Int, requestContextId: String) {
+ val requestUrl = response.request.url
+ val data = buildMap {
+ put("url", "${requestUrl.protocol.name}://${requestUrl.host}${requestUrl.encodedPath}")
+ put("method", response.request.method.value)
+ put("status_code", "$statusCode")
+ if (requestUrl.encodedQuery.isNotEmpty()) put("http.query", requestUrl.encodedQuery)
+ put("request_id", requestContextId)
+ put("http.start_timestamp", "${response.requestTime.timestamp}")
+ put("http.end_timestamp", "${response.responseTime.timestamp}")
+ response.contentLength()?.let { put("response_content_length", "$it") }
+ }
+ crashReport?.addBreadcrumb(
+ message = "",
+ category = "http",
+ level = CrashReportLevel.INFO,
+ type = BreadcrumbType.HTTP,
+ data = data
+ )
+ }
+
+ private fun Throwable.isNetworkException() = this is IOException
+
+ companion object {
+ private const val MAX_RETRY = 3
+ }
+}
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/exceptions/ApiException.kt b/multiplatform-lib/src/commonMain/kotlin/network/exceptions/ApiException.kt
new file mode 100644
index 0000000..c00ec4e
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/exceptions/ApiException.kt
@@ -0,0 +1,69 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.exceptions
+
+/**
+ * Parent class of API calls exception.
+ *
+ * This exception is used to represent errors returned by an API, with an associated [requestContextId]
+ * and message describing the problem.
+ *
+ * @param errorMessage The detailed error message explaining the cause of the failure.
+ * @param cause The cause of the exception if exists otherwise null
+ * @property requestContextId The request context id used to track what happened during calls session by the backend
+ */
+sealed class ApiException(
+ errorMessage: String,
+ cause: Throwable?,
+ val requestContextId: String,
+) : Exception(errorMessage, cause) {
+
+ /**
+ * Thrown when an API call fails due to an error identified by a specific error code.
+ *
+ * This exception is used to represent errors returned by an API, with an associated error code
+ * and message describing the problem.
+ *
+ * @property errorCode The specific error code returned by the API.
+ * @property errorMessage The detailed error message explaining the cause of the failure.
+ * @param requestContextId The request context id send by the backend to track the call
+ */
+ open class ApiErrorException(
+ val errorCode: Int,
+ val errorMessage: String,
+ requestContextId: String,
+ ) : ApiException(errorMessage, null, requestContextId)
+
+ /**
+ * Thrown when an API call returns an error in an unexpected format that cannot be parsed.
+ *
+ * This exception indicates that the API response format is different from what was expected,
+ * preventing proper parsing of the error details.
+ *
+ * @property statusCode The HTTP status code returned by the API.
+ * @property bodyResponse The raw response body from the API that could not be parsed.
+ * @param cause The cause of the exception if exists otherwise null
+ * @param requestContextId The request context id send by the backend to track the call
+ */
+ class UnexpectedApiErrorFormatException(
+ val statusCode: Int,
+ val bodyResponse: String,
+ cause: Throwable?,
+ requestContextId: String,
+ ) : ApiException(bodyResponse, cause, requestContextId)
+}
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/exceptions/NetworkException.kt b/multiplatform-lib/src/commonMain/kotlin/network/exceptions/NetworkException.kt
new file mode 100644
index 0000000..b78ac85
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/exceptions/NetworkException.kt
@@ -0,0 +1,25 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.exceptions
+
+/**
+ * Thrown when a network-related error occurs, such as connectivity issues or timeouts.
+ *
+ * @param message A detailed message describing the network error.
+ */
+class NetworkException(message: String) : Exception(message)
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/exceptions/UnknownException.kt b/multiplatform-lib/src/commonMain/kotlin/network/exceptions/UnknownException.kt
new file mode 100644
index 0000000..8d4e81a
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/exceptions/UnknownException.kt
@@ -0,0 +1,34 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.auth.lib.network.exceptions
+
+/**
+ * Represents an unknown exception that can occur during the execution of the application.
+ *
+ * This exception is used to encapsulate unexpected or unknown errors that are not covered
+ * by other specific exception types.
+ *
+ * @constructor Creates an instance of `UnknownException` with a detailed error message and an optional cause.
+ *
+ * @param cause The underlying exception that caused this exception.
+ *
+ * @property message The detailed message describing the error.
+ */
+class UnknownException(cause: Throwable) : Exception(cause) {
+ override val message: String = cause.message ?: cause.toString()
+}
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/interfaces/CrashReportInterface.kt b/multiplatform-lib/src/commonMain/kotlin/network/interfaces/CrashReportInterface.kt
new file mode 100644
index 0000000..d8c1803
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/interfaces/CrashReportInterface.kt
@@ -0,0 +1,86 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.auth.lib.network.interfaces
+
+enum class CrashReportLevel {
+ DEBUG,
+ INFO,
+ WARNING,
+ ERROR,
+ FATAL
+}
+
+/**
+ * Breadcrumb types for Sentry. Controls how the icon/color is displayed in the UI on Sentry's webpage.
+ *
+ * @param value The value used in the JSON payload.
+ * @see Sentry Documentation
+ */
+enum class BreadcrumbType(val value: String) {
+ Default("default"),
+ HTTP("http"),
+}
+
+interface CrashReportInterface {
+ /**
+ * Adds a breadcrumb to the crash reporting system to provide contextual information
+ * leading up to a potential crash.
+ *
+ * @param message A descriptive message for the breadcrumb, explaining the event or action.
+ * @param category A category string to group related breadcrumbs (e.g., "UI", "Network").
+ * @param level The severity level of the breadcrumb (e.g., info, warning, error).
+ * @param type Sentry internal attribute that controls how breadcrumbs are categorized.
+ * @param data Optional additional data providing more context about the event.
+ */
+ fun addBreadcrumb(
+ message: String,
+ category: String,
+ level: CrashReportLevel,
+ type: BreadcrumbType = BreadcrumbType.Default,
+ data: Map? = null,
+ )
+
+ /**
+ * Captures and reports an error to the crash reporting system with optional context
+ * and additional metadata.
+ *
+ * @param message A custom message to be reported (e.g., an error message or event description).
+ * @param error The [Throwable] to be reported.
+ * @param data Optional contextual data to provide more insight into the environment or state when the error occurred.
+ */
+ fun capture(
+ message: String,
+ error: Throwable,
+ data: Map? = null,
+ )
+
+ /**
+ * Captures a custom message and reports it to the crash reporting system with optional context,
+ * severity level, and additional metadata.
+ *
+ * @param message The custom message to be reported (e.g., an error message or event description).
+ * @param data Optional contextual data that provides additional information about the environment
+ * or state when the message was logged.
+ * @param level The severity level of the message (e.g., `info`, `warning`, `error`).
+ */
+ fun capture(
+ message: String,
+ data: Map? = null,
+ level: CrashReportLevel? = null
+ )
+}
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/AllowCredential.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/AllowCredential.kt
new file mode 100644
index 0000000..1f8fa26
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/AllowCredential.kt
@@ -0,0 +1,27 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class AllowCredential(
+ val type: String,
+ val id: String,
+ val transports: List,
+)
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/ApiError.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/ApiError.kt
new file mode 100644
index 0000000..076dd41
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/ApiError.kt
@@ -0,0 +1,26 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.auth.lib.network.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ApiError(
+ val errorCode: Int,
+ val message: String,
+)
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/AuthResult.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/AuthResult.kt
new file mode 100644
index 0000000..55622c6
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/AuthResult.kt
@@ -0,0 +1,32 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.models
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class AuthResult(
+ @SerialName("token_type")
+ val tokenType: String,
+ val scope: String,
+ @SerialName("user_id")
+ val userId: Long,
+ @SerialName("access_token")
+ val accessToken: String,
+)
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/AuthentificationOptions.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/AuthentificationOptions.kt
new file mode 100644
index 0000000..ca06ef0
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/AuthentificationOptions.kt
@@ -0,0 +1,29 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.models
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class AuthenticationOptions(
+ val challenge: String,
+ @SerialName("rpId")
+ val relyingPartyId: String,
+ val allowCredentials: List,
+)
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/ClientExtensionResults.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/ClientExtensionResults.kt
new file mode 100644
index 0000000..4da21af
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/ClientExtensionResults.kt
@@ -0,0 +1,23 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data object ClientExtensionResults // TODO Don't know what's inside this
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/ExcludeCredential.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/ExcludeCredential.kt
new file mode 100644
index 0000000..cd34ec8
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/ExcludeCredential.kt
@@ -0,0 +1,26 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ExcludeCredential(
+ val id: String,
+ val type: String,
+)
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/PasskeysOptions.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/PasskeysOptions.kt
new file mode 100644
index 0000000..53e1807
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/PasskeysOptions.kt
@@ -0,0 +1,31 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.models
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class PasskeysOptions(
+ val challenge: String,
+ @SerialName("rp")
+ val relyingParty: RelyingParty,
+ val user: User,
+ val pubKeyCredParams: List,
+ val excludeCredentials: List,
+)
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/PubKeyCredParam.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/PubKeyCredParam.kt
new file mode 100644
index 0000000..bcdb5ca
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/PubKeyCredParam.kt
@@ -0,0 +1,28 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.models
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class PubKeyCredParam(
+ val type: String,
+ @SerialName("alg")
+ val algorithm: Int,
+)
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/RegisterPasskey.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/RegisterPasskey.kt
new file mode 100644
index 0000000..8fadffa
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/RegisterPasskey.kt
@@ -0,0 +1,33 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.models
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class RegisterPasskey(
+ val device: String,
+ val id: String,
+ val rawId: String,
+ @SerialName("response")
+ val registerPasskeyResponse: RegisterPasskeyResponse,
+ val type: String,
+ val clientExtensionResults: ClientExtensionResults,
+ val authenticatorAttachment: String,
+)
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/RegisterPasskeyResponse.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/RegisterPasskeyResponse.kt
new file mode 100644
index 0000000..e7d1e38
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/RegisterPasskeyResponse.kt
@@ -0,0 +1,30 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class RegisterPasskeyResponse(
+ val attestationObject: String,
+ val clientDataJSON: String,
+ val transports: List,
+ val publicKeyAlgorithm: Int,
+ val publicKey: String,
+ val authenticatorData: String,
+)
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/RelyingParty.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/RelyingParty.kt
new file mode 100644
index 0000000..3b05599
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/RelyingParty.kt
@@ -0,0 +1,27 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class RelyingParty(
+ val id: String,
+ val name: String,
+ val icon: String?,
+)
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/User.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/User.kt
new file mode 100644
index 0000000..77e7b3c
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/User.kt
@@ -0,0 +1,27 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class User(
+ val id: String,
+ val name: String,
+ val displayName: String?,
+)
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/VerifyAuthenticationData.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/VerifyAuthenticationData.kt
new file mode 100644
index 0000000..f8d8810
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/VerifyAuthenticationData.kt
@@ -0,0 +1,31 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class VerifyAuthenticationData(
+ val identity: Long,
+ val id: String,
+ val rawId: String,
+ val response: VerifyResponse,
+ val type: String,
+ val clientExtensionResults: ClientExtensionResults,
+ val authenticatorAttachment: String,
+)
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/VerifyResponse.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/VerifyResponse.kt
new file mode 100644
index 0000000..e69d8b3
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/VerifyResponse.kt
@@ -0,0 +1,28 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class VerifyResponse(
+ val authenticatorData: String,
+ val clientDataJSON: String,
+ val signature: String,
+ val userHandle: String,
+)
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/repositories/WebAuthnRepository.kt b/multiplatform-lib/src/commonMain/kotlin/network/repositories/WebAuthnRepository.kt
new file mode 100644
index 0000000..0a5977b
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/repositories/WebAuthnRepository.kt
@@ -0,0 +1,84 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.repositories
+
+import com.infomaniak.auth.lib.network.ApiClientProvider
+import io.ktor.client.HttpClient
+import kotlinx.serialization.json.Json
+import network.models.AuthResult
+import network.models.AuthenticationOptions
+import network.models.ClientExtensionResults
+import network.models.PasskeysOptions
+import network.models.RegisterPasskey
+import network.models.VerifyAuthenticationData
+import network.models.VerifyResponse
+import network.requests.AuthenticatorRequest
+import network.utils.ApiEnvironment
+
+class WebAuthnRepository internal constructor(private val authenticatorRequest: AuthenticatorRequest) {
+
+ constructor(environment: ApiEnvironment) : this(ApiClientProvider(), environment)
+ constructor(apiClientProvider: ApiClientProvider = ApiClientProvider(), environment: ApiEnvironment) : this(
+ environment = environment,
+ json = apiClientProvider.json,
+ httpClient = apiClientProvider.httpClient,
+ )
+
+ internal constructor(environment: ApiEnvironment, json: Json, httpClient: HttpClient) :
+ this(AuthenticatorRequest(environment, json, httpClient))
+
+ // Authentification challenge (not authentified)
+ suspend fun getAuthenticationOptions(identity: Long): AuthenticationOptions {
+ return authenticatorRequest.challenge(identity)
+ }
+
+ // Authentification verification (not authentified)
+ suspend fun verifyAuthentication(
+ identity: Long,
+ id: String,
+ rawId: String,
+ verifyResponse: VerifyResponse,
+ type: String,
+ clientExtensionResult: ClientExtensionResults,
+ authenticatorAttachment: String,
+ ): AuthResult {
+ return authenticatorRequest.verify(
+ VerifyAuthenticationData(
+ identity,
+ id,
+ rawId,
+ verifyResponse,
+ type,
+ clientExtensionResult,
+ authenticatorAttachment,
+ )
+ )
+ }
+
+ // Generate WebAuthn registration options (authentified)
+ suspend fun getRegistrationOptions(): PasskeysOptions {
+ //TODO where to get the bearer ?
+ return authenticatorRequest.getPasskeysOptions()
+ }
+
+ // Validate WebAuthn registration and save public key (authentified)
+ suspend fun registerCredential(registerPasskey: RegisterPasskey): Result {
+ //TODO where to get the bearer ?
+ return authenticatorRequest.registerPasskey(registerPasskey)
+ }
+}
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/requests/AuthenticatorRequest.kt b/multiplatform-lib/src/commonMain/kotlin/network/requests/AuthenticatorRequest.kt
new file mode 100644
index 0000000..dc49f90
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/requests/AuthenticatorRequest.kt
@@ -0,0 +1,51 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.requests
+
+import io.ktor.client.HttpClient
+import kotlinx.serialization.json.Json
+import network.models.AuthResult
+import network.models.AuthenticationOptions
+import network.models.PasskeysOptions
+import network.models.RegisterPasskey
+import network.models.VerifyAuthenticationData
+import network.utils.ApiEnvironment
+import network.utils.ApiRoutes
+
+internal class AuthenticatorRequest(
+ environment: ApiEnvironment,
+ json: Json,
+ httpClient: HttpClient,
+) : BaseRequest(environment, json, httpClient) {
+
+ suspend fun getPasskeysOptions(): PasskeysOptions {
+ return get(createUrl(ApiRoutes.getPasskeysOptions))
+ }
+
+ suspend fun registerPasskey(registerPasskey: RegisterPasskey): Result {
+ return post(createUrl(ApiRoutes.registerPasskey), registerPasskey)
+ }
+
+ suspend fun challenge(identity: Long): AuthenticationOptions {
+ return post(createUrl(ApiRoutes.challenge), mapOf("identity" to identity))
+ }
+
+ suspend fun verify(verifyAuthenticationData: VerifyAuthenticationData): AuthResult {
+ return post(createUrl(ApiRoutes.verify), verifyAuthenticationData)
+ }
+}
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/requests/BaseRequest.kt b/multiplatform-lib/src/commonMain/kotlin/network/requests/BaseRequest.kt
new file mode 100644
index 0000000..2cc4c9b
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/requests/BaseRequest.kt
@@ -0,0 +1,82 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.requests
+
+import com.infomaniak.auth.lib.network.utils.decode
+import io.ktor.client.HttpClient
+import io.ktor.client.request.delete
+import io.ktor.client.request.get
+import io.ktor.client.request.headers
+import io.ktor.client.request.post
+import io.ktor.client.request.put
+import io.ktor.client.request.setBody
+import io.ktor.http.ContentType
+import io.ktor.http.HeadersBuilder
+import io.ktor.http.URLBuilder
+import io.ktor.http.Url
+import io.ktor.http.contentType
+import kotlinx.serialization.json.Json
+import network.utils.ApiEnvironment
+import network.utils.ApiRoutes
+
+internal open class BaseRequest(
+ protected val environment: ApiEnvironment,
+ protected val json: Json,
+ protected val httpClient: HttpClient,
+) {
+
+ protected fun createUrl(path: String, vararg queries: Pair): Url {
+ val baseUrl = Url(ApiRoutes.apiBaseUrl(environment) + path)
+ return URLBuilder(baseUrl).apply {
+ queries.forEach { parameters.append(it.first, it.second) }
+ }.build()
+ }
+
+ protected suspend inline fun get(
+ url: Url,
+ crossinline appendHeaders: HeadersBuilder.() -> Unit = {},
+ httpClient: HttpClient = this.httpClient,
+ ): R {
+ return httpClient.get(url) {
+ headers { appendHeaders() }
+ }.decode()
+ }
+
+ protected suspend inline fun post(
+ url: Url, data: Any?,
+ crossinline appendHeaders: HeadersBuilder.() -> Unit = {},
+ httpClient: HttpClient = this.httpClient,
+ ): R {
+ return httpClient.post(url) {
+ contentType(ContentType.Application.Json)
+ headers { appendHeaders() }
+ setBody(data)
+ }.decode()
+ }
+
+ protected suspend inline fun put(url: Url, data: Any?, httpClient: HttpClient = this.httpClient): R {
+ return httpClient.put(url) {
+ contentType(ContentType.Application.Json)
+ setBody(data)
+ }.decode()
+ }
+
+ protected suspend inline fun delete(url: Url, httpClient: HttpClient = this.httpClient): R {
+ return httpClient.delete(url) {}.decode()
+ }
+}
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/utils/ApiEnvironment.kt b/multiplatform-lib/src/commonMain/kotlin/network/utils/ApiEnvironment.kt
new file mode 100644
index 0000000..ec6c709
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/utils/ApiEnvironment.kt
@@ -0,0 +1,27 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.utils
+
+sealed class ApiEnvironment(val baseUrl: String) {
+ // Those urls are duplicated with the ones we have in Android so don't forget to change them also in Android
+ data object Preprod :
+ ApiEnvironment("https://authenticator.preprod.dev.infomaniak.ch") //TODO Change this to the final baseUrl
+
+ data object Prod : ApiEnvironment("https://www.authenticator.com") //TODO Change this to the final baseUrl
+ data class Custom(private val url: String) : ApiEnvironment(url)
+}
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/utils/ApiExt.kt b/multiplatform-lib/src/commonMain/kotlin/network/utils/ApiExt.kt
new file mode 100644
index 0000000..e1276bb
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/utils/ApiExt.kt
@@ -0,0 +1,51 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.auth.lib.network.utils
+
+import com.infomaniak.auth.lib.network.exceptions.UnknownException
+import io.ktor.client.call.body
+import io.ktor.client.plugins.timeout
+import io.ktor.client.request.HttpRequestBuilder
+import io.ktor.client.statement.HttpResponse
+import io.ktor.utils.io.CancellationException
+import network.exceptions.ApiException
+import network.exceptions.NetworkException
+import kotlin.time.Duration.Companion.hours
+import kotlin.time.Duration.Companion.minutes
+import kotlin.time.Duration.Companion.seconds
+
+const val CONTENT_REQUEST_ID_HEADER = "x-request-id"
+
+internal fun HttpRequestBuilder.longTimeout() {
+ timeout {
+ requestTimeoutMillis = 1.hours.inWholeMilliseconds // Be permissive for DSL or slow mobile connections.
+ connectTimeoutMillis = 10.seconds.inWholeMilliseconds // Don't let the user wait too long for connection.
+ socketTimeoutMillis = 1.minutes.inWholeMilliseconds // Be permissive if packets are slow to be delivered.
+ }
+}
+
+internal fun HttpResponse.getRequestContextId() = headers[CONTENT_REQUEST_ID_HEADER] ?: ""
+
+internal suspend inline fun HttpResponse.decode(): R = runCatching {
+ body()
+}.getOrElse { exception ->
+ when (exception) {
+ is CancellationException, is NetworkException, is ApiException -> throw exception
+ else -> throw UnknownException(exception)
+ }
+}
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/utils/ApiRoutes.kt b/multiplatform-lib/src/commonMain/kotlin/network/utils/ApiRoutes.kt
new file mode 100644
index 0000000..2b81539
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/utils/ApiRoutes.kt
@@ -0,0 +1,28 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package network.utils
+
+internal object ApiRoutes {
+
+ fun apiBaseUrl(environment: ApiEnvironment) = "${environment.baseUrl}/api/authenticator/"
+
+ const val getPasskeysOptions = "passkeys/options"
+ const val registerPasskey = "passkey"
+ const val challenge = "challenge"
+ const val verify = "verify"
+}