Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
491 commits
Select commit Hold shift + click to select a range
0a51b4b
fix: Rebase
tevincent Mar 24, 2026
ca8988e
chore: Code review
tevincent Mar 24, 2026
20bfa7d
chore: Put classes that can be internal internal
tevincent Mar 24, 2026
97a36c6
chore: Fix build
tevincent Mar 24, 2026
cb2326d
chore: Move extensions package into internal in appleMain sources
LouisCAD Mar 24, 2026
8622f94
chore: Use imports again in KDoc
LouisCAD Mar 24, 2026
92d7782
chore: Remove redundant qualifier
tevincent Mar 24, 2026
e7042a0
chore: Move some extensions to the right folder
tevincent Mar 24, 2026
5f2be90
chore: Reformat code
tevincent Mar 24, 2026
c1a2472
fix: Fix KeyAccessibility mapping
LouisCAD Mar 24, 2026
63b5e7b
fix: Fix key creation in the iOS KeyChain on actual devices
LouisCAD Mar 24, 2026
de29865
feat: Adapt callback for facade after merge of main branch
baillyjamy Mar 24, 2026
a451f3f
feat: Open notification settings natif
baillyjamy Mar 26, 2026
8378435
feat: Use only AppStatus instead of isLoading state
baillyjamy Mar 25, 2026
eda7380
fix: Url param unified
baillyjamy Mar 26, 2026
3e8f4fe
fix: Apply review suggestion
baillyjamy Mar 26, 2026
9d65a93
feat: Add the possibility to refresh the token using the passkey
tevincent Mar 30, 2026
8b94fba
chore: Add all the necessary code for kauth BDD
tevincent Mar 18, 2026
9d94457
feat: Get token from OTP for legacy accounts
tevincent Mar 25, 2026
0f48a07
chore: Rename db to accounsDatabase
tevincent Mar 25, 2026
708bd8d
chore: Add missing parameter to get facade
tevincent Mar 25, 2026
11e56b4
chore: Generate a random device UUID when we start the migration process
tevincent Mar 25, 2026
41b2fc9
chore: Clean code
tevincent Mar 25, 2026
469ac6b
chore: Give a way for apps to know something went wrong during migration
tevincent Mar 25, 2026
2895905
chore: Start migration at startup of application
tevincent Mar 26, 2026
a9c8055
chore: Slight modification to send the userId as a long
tevincent Mar 26, 2026
50e6585
chore: Fix initial
tevincent Mar 26, 2026
f3c4a4d
chore: Clean code
tevincent Mar 27, 2026
df78809
chore: Use a try finally to close the DB
tevincent Mar 27, 2026
7f2d386
chore: Use MigrationManager in facade
tevincent Mar 27, 2026
282efd9
chore: Clean code
tevincent Mar 27, 2026
31d24dc
chore: Remove close DB
tevincent Mar 27, 2026
2552514
chore: Remove override startMigration
tevincent Mar 30, 2026
2fd7ac7
chore: Replace big `?.let { }` with `?: return`
LouisCAD Mar 30, 2026
809315d
chore: Replace toString() with toHexDashString() to be more explicit
LouisCAD Mar 30, 2026
41139f6
chore: Rename lambda parameter
LouisCAD Mar 30, 2026
2af7e86
chore: Use OTPUser instead of LegacyAccount and rename OTPUser to Leg…
tevincent Mar 30, 2026
d1ad3cf
chore: AccountRepository in AuthenticatorManager should not be nullable
tevincent Mar 30, 2026
2d5ee53
chore: Set Status to ToBeMigrated
tevincent Mar 30, 2026
9cf5a64
chore: Set TokenFromOtp to internal
tevincent Mar 30, 2026
e708f53
chore: SetLegacyUser to internal
tevincent Mar 30, 2026
3230fc7
chore: Remove unneeded safe call
tevincent Mar 30, 2026
fcc6629
chore: Rename userID to userId in LegacyUser
tevincent Mar 30, 2026
065e463
refactor: Replace manual JSON parsing with LegacyUser model update
LouisCAD Mar 30, 2026
34ddeb7
refactor: Replace apply block to simplify internal logic
LouisCAD Mar 30, 2026
fe6d706
refactor: Use the Long type instead of the String type for userId
LouisCAD Mar 30, 2026
e984dd7
refactor: Remove no longer needed model mapping
LouisCAD Mar 30, 2026
3fc9281
chore: Code review
tevincent Mar 31, 2026
6d6874d
chore: Fix comments
tevincent Mar 31, 2026
a675c01
docs: Add KDoc to the LegacyUser model
LouisCAD Mar 31, 2026
85273a4
chore: Rename startMigration into tryMigrating in MigrationManager
LouisCAD Mar 31, 2026
17dfe70
chore: Rename parameter for more clarity
LouisCAD Mar 31, 2026
f9ec095
fix: Don't generate the OTP if not needed
LouisCAD Mar 31, 2026
941bca7
chore: Lower the scope of opt-in annotation
LouisCAD Mar 31, 2026
944da81
chore: Treat IllegalStateException as non retriable
LouisCAD Mar 31, 2026
cffb4d2
fix: Remove unneeded runCatching
LouisCAD Mar 31, 2026
341d801
chore: Rename TokenFromOtp to OtpPayload
tevincent Mar 31, 2026
a1fe2a6
chore: Readd DummyAuthenticatorFacade
tevincent Mar 31, 2026
bd28c21
chore: Remove the needsResolution for AppStatus.LoggingIn
LouisCAD Apr 1, 2026
8797e64
feat: Delete a legacy account after a migration and delete completely…
tevincent Mar 31, 2026
8aff511
chore: Fix delete query
tevincent Mar 31, 2026
817522c
chore: Use mapNotNull
tevincent Mar 31, 2026
a9c9226
chore: Clean code
tevincent Apr 1, 2026
70fe735
chore: Use toByteArray() extension for NSData
LouisCAD Apr 1, 2026
8d69c9d
chore: Remove unneeded call to NSUserDefaults.synchornize()
LouisCAD Apr 1, 2026
24d1ccd
chore: Rename OnboardingDone to EverythingReady
LouisCAD Apr 1, 2026
f74bf9a
chore: Introduce AppStatus.AddingAnAccount and add addAnAccount callb…
LouisCAD Apr 1, 2026
2cfa7f9
feat: Add password during migration
tevincent Apr 2, 2026
2f8b7cf
fix: Migration is now complete
tevincent Apr 2, 2026
4123933
fix: Sonar lint
tevincent Mar 27, 2026
f871e01
chore: Code review
tevincent Apr 2, 2026
f687ff4
chore: Introduce convenience appStatusOrNull function
LouisCAD Apr 7, 2026
40dd6d0
chore: Implement CrashReportInterface in the KMP module
LouisCAD Apr 2, 2026
6927d45
fix: Change condition to check wether an account is already connected
tevincent Apr 2, 2026
0bda63e
feat: Add 2fa settings url
baillyjamy Apr 7, 2026
108ba7d
feat: Can now create account
baillyjamy Apr 7, 2026
804c867
fix: Select java version on kmp build
baillyjamy Apr 7, 2026
a1b2c6a
fix: Remove old keys with a specific userId before creating a new one…
tevincent Apr 7, 2026
fd32ea5
chore: Rename method
tevincent Apr 7, 2026
89d72ed
chore: Rename variable
tevincent Apr 7, 2026
01ed56d
chore: Code review
tevincent Apr 7, 2026
0745266
chore: Add createAccountCancelUrl
baillyjamy Apr 7, 2026
bec03ee
chore: Add timeout + report to Cross App Login token retrieval
LouisCAD Apr 8, 2026
b2757ae
chore: Remove unneeded data modifiers on private class
LouisCAD Apr 8, 2026
7217456
docs: Add KDoc to withTimeoutOrNull with report
LouisCAD Apr 8, 2026
c4a7c53
chore: Wip try to have account to relogin
baillyjamy Apr 14, 2026
8c78b11
feat: Login Screen with AccountRow
baillyjamy Apr 15, 2026
ff1e31f
feat: Finish login screen and call sendCredentials from legacy account
baillyjamy Apr 15, 2026
3a1ca0e
chore: Add prod url
baillyjamy Apr 13, 2026
dade54e
fix: Add missing @Throws annotation on removeAccount
LouisCAD Apr 14, 2026
ce602bb
feat: Use url constant for recover password url
baillyjamy Apr 15, 2026
199814c
fix: Delete old keys during login
tevincent Apr 15, 2026
42e3304
fix: Fix API url inconsistency by replacing unneeded ApiEnvironment
LouisCAD Apr 16, 2026
e35aade
chore: Revert DummyFacade
baillyjamy Apr 16, 2026
325179d
refactor: Rework Account.Status
LouisCAD Apr 20, 2026
b5db1ea
fix: Fix sealed interface entry superclass
LouisCAD Apr 20, 2026
6f18076
chore: Rename conversion extension files
LouisCAD Apr 21, 2026
21c6f06
fix: Fix MustReLogin state not staying, and add skip lambda
LouisCAD Apr 21, 2026
0a65c61
feat: Refactor ActionRequiredCard
baillyjamy Apr 22, 2026
2f6e3eb
refactor: Rework Account.Status
LouisCAD Apr 20, 2026
78fa23f
fix: Fix sealed interface entry superclass
LouisCAD Apr 20, 2026
84e5610
fix: Fix MustReLogin state not staying, and add skip lambda
LouisCAD Apr 21, 2026
3ed885a
chore: Add http statusCode to ApiException
LouisCAD Apr 22, 2026
88b9673
chore: Improve error handling
LouisCAD Apr 22, 2026
f3d4ffb
chore: Add an error code to Issue.Reason.Other
LouisCAD Apr 22, 2026
dcdaa07
chore: Extract throwable to Issue.Retriable.Reason function
LouisCAD Apr 22, 2026
97058cc
refactor: Simplify Account.Status.NotConnected.ReLogin
LouisCAD Apr 22, 2026
7b97342
feat: Add user id to sentry captures
LouisCAD Apr 22, 2026
d83f290
chore: Handle wrong password in MigrationManager.tryMigrating
LouisCAD Apr 22, 2026
6add1a1
fix: Account is not inserted in DB when doing a migration
tevincent Apr 22, 2026
f7d4b3b
chore: Set UserProfileBridge as functional interface
tevincent Apr 22, 2026
1733259
chore: Code review
tevincent Apr 22, 2026
ab0fd9f
chore: Merge persistUser and persistToken
tevincent Apr 23, 2026
a41594d
chore: Remove unused class from profile
tevincent Apr 23, 2026
35bb81c
chore: Remove some unused parameters from getUserProfile
tevincent Apr 23, 2026
5451406
chore: Use ApiToken to get tokenType
tevincent Apr 23, 2026
a0e3773
chore: Rename apiToken to temporaryToken
tevincent Apr 23, 2026
12c4b59
chore: Rename apiToken to derivedToken for CrossAppLogin
tevincent Apr 23, 2026
ce700bb
chore: Replace object + var with class
LouisCAD Apr 23, 2026
f492d12
docs: Update KDoc following parameter name change
LouisCAD Apr 23, 2026
d04fc48
chore: Merge TokenBridge and UserProfileBridge into AuthenticatorBridge
tevincent Apr 23, 2026
3093927
chore: Put back proper local property names for access tokens
LouisCAD Apr 23, 2026
ef53ff4
fix: The variable emailValidatedAt is now nullable
tevincent Apr 24, 2026
a1a6b2c
chore: Utils method to manipulate files on each platforms
tevincent Apr 17, 2026
c0e3f54
feat: Implements migration of accounts from backup
tevincent Apr 17, 2026
729f405
fix: Delete the old passkey when we successfully get a new token with…
tevincent Apr 17, 2026
6e6da32
chore: Avoid having tokenBridge as parameter of MigrationManager
tevincent Apr 17, 2026
62629ab
fix: Put methods to internal
tevincent Apr 23, 2026
0c48679
chore: Create a new Account status to migrate accounts and avoid crea…
tevincent Apr 24, 2026
70c7cdf
chore: Remove unused method
tevincent Apr 24, 2026
fc3ad04
chore: Clean code
tevincent Apr 24, 2026
dcfc9b9
fix: Use the right token when persistUser is called
tevincent Apr 27, 2026
1002333
chore: Improve how we update accounts when restoring from backup
tevincent Apr 27, 2026
56eb559
chore: Do not include the file created on iOS in the backup
tevincent Apr 27, 2026
92dd1d6
chore: Move getApplicationSupportDirectory to FileUtils.apple.kt
LouisCAD Apr 27, 2026
e7938dd
fix: Handle NSError in FileUtils.apple.kt
LouisCAD Apr 27, 2026
da357d7
chore: Put arguments on separate lines with names
LouisCAD Apr 27, 2026
079fba7
chore: Rename parameter name for clarity
LouisCAD Apr 27, 2026
9047b4c
chore: Add optional userId to KeyNotFound
LouisCAD Apr 27, 2026
87fa7da
chore: Remove unused returned value warning
LouisCAD Apr 27, 2026
7550538
chore: Replace NPEs with ISEs with error messages
LouisCAD Apr 27, 2026
b687191
chore: Replace unnecessary Xor wrapping with nullable String
LouisCAD Apr 27, 2026
2e50ad7
chore: Make FileUtils contents internal
LouisCAD Apr 27, 2026
7f0e98e
chore: Rename createFile to createBackupExcludedFile
LouisCAD Apr 27, 2026
e9b9ec9
refactor: Introduce RestoreFromBackupDetector
LouisCAD Apr 27, 2026
2383579
chore: Put arguments on separate lines
LouisCAD Apr 27, 2026
a6ee106
fix: Handle exceptions and retries for restoration from backup
LouisCAD Apr 27, 2026
140b474
perf: Use Dispatchers.IO for keychain operations
LouisCAD Apr 27, 2026
94e224e
chore: Add BackupExclusionGotcha opt-in requirement annotation
LouisCAD Apr 28, 2026
ea072a2
chore: Rename BackupExclusionGotcha to BackupExclusionOnlyApplePlatforms
LouisCAD Apr 28, 2026
9c83a9c
chore: Remove unused userId
tevincent Apr 28, 2026
26a4c34
chore: Rename method to avoid an unnecessary val
tevincent Apr 28, 2026
dea4e1c
fix: Add missing AttemptingToConnect state emission
LouisCAD Apr 28, 2026
2f1e2bd
chore: Add pre-made key filters in KeyPairManager
LouisCAD Apr 28, 2026
519ed22
fix: Stop removing all passkeys after restoration from backup
LouisCAD Apr 28, 2026
562ca61
chore: Add more extensions for Apple CoreFoundation types
LouisCAD Apr 28, 2026
45b95a6
fix: Make backup restoration restartable
LouisCAD Apr 28, 2026
f36232f
fix: Fix test compilation
LouisCAD Apr 29, 2026
bbc9823
refactor: Fix key retrieval by passkey id on iOS
LouisCAD Apr 29, 2026
0f8eb1d
fix: Filter out duplicate key ids
LouisCAD Apr 29, 2026
3bd2297
chore: Add missing internal modifier
LouisCAD Apr 29, 2026
221a3fd
chore: Handle potential IOException on retrieving file creation date …
LouisCAD Apr 29, 2026
5d3a59e
fix: Add missing CFRelease calls
LouisCAD Apr 29, 2026
c21c9d2
chore: Make CFArrayRef extensions have a non-null receiver
LouisCAD Apr 29, 2026
4e12d53
chore: Rename local property
LouisCAD Apr 29, 2026
c979244
fix: Remove obsolete function that was failing iOS compilation
LouisCAD Apr 29, 2026
a344937
chore: Simplify getAllPrivateKeysQuery()
LouisCAD Apr 29, 2026
775088b
chore: Allow backup of passkeys on iOS
LouisCAD Apr 29, 2026
a33a334
refactor: Rename Reason to Cause
LouisCAD Apr 30, 2026
ef88dc6
fix: Add missing custom NetworkException handling
LouisCAD Apr 30, 2026
9164ccd
chore: Add missing cause in custom NetworkException
LouisCAD Apr 30, 2026
2b4ee5e
chore: Add parameter names to constructor call
LouisCAD Apr 30, 2026
ee6394f
feat: Allow the dismissal of an issue during ReLogin
LouisCAD Apr 30, 2026
673c388
chore: Extract handleSingleAccountToReLoginOrSkip(…) to simplify appS…
LouisCAD Apr 30, 2026
bc79a46
refactor: Rename cause to issue in LoginFailed
LouisCAD Apr 30, 2026
6f070c9
chore: Put passkeys in a dedicated folder on Android
LouisCAD Apr 29, 2026
fe9ed55
chore: Ensure key pairs are moved for test users
LouisCAD Apr 30, 2026
6a6920b
refactor: Add Shared prefix to models shared with iOS to workaround S…
LouisCAD Apr 30, 2026
12ac37f
perf: Avoid initializing HttpClient on the main thread
LouisCAD Apr 30, 2026
4f29176
fix: Fix crashes
tevincent May 5, 2026
bff16b9
feat: Add securityScore and lastPasswordUpdate in AccountEntity
tevincent Apr 29, 2026
50127dc
feat: Expose accountsWithPasswordUpdate in MainView to display a dial…
tevincent May 1, 2026
62b6eb5
chore: Fix build
tevincent May 1, 2026
f587bea
refactor: Replace String with SharedApiToken in AuthenticatorBridge
LouisCAD May 4, 2026
3679c42
chore: Remove unneeded Unit lambda parameter
LouisCAD May 4, 2026
5a85b10
chore: Import select
LouisCAD May 4, 2026
d0deae3
chore: Remove unneeded qualifier name
LouisCAD May 4, 2026
82f90e7
chore: Remove DummyAuthenticatorFacade
LouisCAD May 5, 2026
2a6d511
chore: Make security score nullable
LouisCAD May 5, 2026
e18dbab
chore: Introduce the buildFlowWithElements extension for DynamicLazyMap
LouisCAD May 5, 2026
c1598f6
refactor: Replace accountsWithUpdatedPassword with LoggedIn properties
LouisCAD May 5, 2026
7343245
chore: Remove unused data class
tevincent May 6, 2026
95e2951
fix: Fix too restrictive precondition that was causing rare failures …
LouisCAD May 6, 2026
fd197f2
refactor: Remove middle class
LouisCAD May 7, 2026
3c7ab74
feat: Add SETTINGS_ACCOUNT_SECURITY_URL
baillyjamy May 11, 2026
abab033
feat: Security score card is now an AccountSecurityConfiguration
baillyjamy May 12, 2026
c75a17c
chore: Add missing DynamicLazyMap helpers
LouisCAD May 7, 2026
dbebfb1
feat: WIP refreshToken support
LouisCAD May 7, 2026
12992ee
chore: Instantiate AuthenticatorRequests at the right place
LouisCAD May 11, 2026
4122472
chore: Update all account properties when refreshing the profile
LouisCAD May 11, 2026
d442566
feat: Add Disconnected status for Account
LouisCAD May 11, 2026
9b913d3
fix: Fix unauthorized (401) handling by ktor auth
LouisCAD May 12, 2026
38613c5
chore: Workaround wrong http status code
LouisCAD May 12, 2026
efe7c80
chore: Properly close the http client and its engine when the user is…
LouisCAD May 12, 2026
67241c3
chore: Fix typo
LouisCAD May 12, 2026
0335e7f
chore: Add precondition
LouisCAD May 12, 2026
6b77244
chore: Skip unauthorized conversion only when needed
LouisCAD May 12, 2026
8c381b0
chore: Remove unused field in User for better implementation on iOS
tevincent May 12, 2026
cd7192a
chore: Remove lastLoginAt
tevincent May 12, 2026
8cfa1ae
chore: Make token nullable in facade to remove an account not present…
tevincent May 13, 2026
af83f55
chore: Remove duplicate call to delete
tevincent May 13, 2026
0ef6af9
chore: Remove temporary passkey move code
LouisCAD Apr 30, 2026
7543681
feat: Add Screen matomo event
baillyjamy May 20, 2026
b938477
feat: Add account and settings matomo event
baillyjamy May 20, 2026
aa1073c
fix: Delete legacy account if we remove the account after a canceled …
tevincent May 19, 2026
1d988f7
feat: Add migration event matomo
baillyjamy May 21, 2026
fb633c5
feat: Add AGENTS.md
Copilot May 20, 2026
c621110
fix: Add closing and prevent popups options for manager url
baillyjamy May 26, 2026
28e6791
fix: Set shouldBeRegistered in ApiToken to false when necessary to av…
tevincent May 26, 2026
e9aaa70
chore: Rename shouldBeRegistered into isTemporary
tevincent May 26, 2026
ef11d95
chore: Avoid refreshing token if loaded one is stale
LouisCAD May 28, 2026
c859e54
fix: Persist the refreshed token
LouisCAD May 28, 2026
bb1d426
fix: Persist user profile when refreshing from home
tevincent Jun 2, 2026
f83f24d
chore: Persist the user when we call addAccounts
tevincent Jun 2, 2026
106c9da
chore: Add more logs to understand what's happening
tevincent Jun 4, 2026
794e2ce
chore: Only call persistUserProfile when account is updated
tevincent Jun 4, 2026
273c634
fix: Bump the DB version to avoid hash mismatch
LouisCAD Jun 11, 2026
203c659
chore: Suppress warning about not-always redundant warning suppression
LouisCAD Jun 11, 2026
877f9cc
fix: Allow refreshing the temporary token
LouisCAD Jun 16, 2026
3cac935
chore: Report an error if expected temporary token is missing
LouisCAD Jun 16, 2026
1da5e21
fix: Ensure when the account is disconnected that we delete accounts …
tevincent Jun 24, 2026
6757d78
chore: Code review
tevincent Jun 24, 2026
0ab3bd2
feat: Introduce BlockLogger
LouisCAD Jun 25, 2026
babf511
chore: Add breadcrumbs with BlockLogger
LouisCAD Jun 25, 2026
f9d674e
chore: Redact tokens from data classes toString()
LouisCAD Jun 25, 2026
7f399a7
chore: Update Core
tevincent Jun 26, 2026
def1056
fix: Fix broken state the user couldn't recover from
LouisCAD Jul 22, 2026
cce0b28
fix: Remove racy "optimization"
LouisCAD Jul 23, 2026
183d9be
docs: Update KDoc to match new behavior
LouisCAD Jul 23, 2026
583a366
docs: Fix misspelling in KDoc
LouisCAD Jul 23, 2026
91c97a5
fix: Fix mistake in 89d3182c (Remove racy "optimization")
LouisCAD Jul 23, 2026
d6f874a
feat: Auto-recover accounts whose completeMigration failed
LouisCAD Jul 23, 2026
93c9e07
fix: Properly disable the header of the webpages
LouisCAD Jul 28, 2026
239005f
fix: Fix wrong intermediate state
LouisCAD Jul 29, 2026
0555ff8
feat: Allow logging status changes
LouisCAD Jul 30, 2026
e3a234e
fix: Fix wrong AppStatus caused by dual source of truth
LouisCAD Jul 30, 2026
b5f304d
chore: Remove unneeded kotlincrypto library with okio
LouisCAD Jul 27, 2026
cdb25e1
fix: Token was not saved during migration
tevincent Jul 30, 2026
8e85817
perf: Ensure `deleteKeysMatching` can't block the calling thread
LouisCAD Jul 30, 2026
0fe8c97
chore: Make XCFramework dynamic/shared instead of static
LouisCAD Jul 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 197 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# AGENTS.md - Authenticator Multiplatform Library

> For the Android app norms, see `app/AGENTS.md`. For Core library norms, see `Core/AGENTS.md`. For the composite build overview,
> see the root `AGENTS.md`.

## Module Summary

`:multiplatform-lib` is the Kotlin Multiplatform module that holds the shared business logic of the Infomaniak Authenticator. It
is consumed:

- by the Android app via `implementation(project(":multiplatform-lib"))` (see `app/build.gradle.kts`);
- by the iOS / macOS Authenticator as a static XCFramework named `CoreAuthenticator`, distributed through the root
`Package.swift`.

It owns the OTP engine (TOTP/HOTP), the API client, the local Room database, the account/2FA repositories, the WebAuthn / passkey
logic and the migration models exchanged between platforms.

## High-Level Tech Stack

- **Kotlin Multiplatform** with targets: `androidLibrary` (namespace `com.infomaniak.auth.multiplatform`), `iosArm64`,
`iosSimulatorArm64`, `macosArm64`.
- **SKIE** for idiomatic Swift interop (default arguments, sealed classes, suspend functions).
- **Ktor client** for HTTP (engines: OkHttp on Android, Darwin on Apple).
- **kotlinx.serialization** (`json`, `cbor`) - JSON is configured in `internal/network/ApiClientProvider.kt` with
`coerceInputValues = true`, `ignoreUnknownKeys = true`, and `decodeEnumsCaseInsensitive`.
- **AndroidX Room** (multiplatform Room with `androidx.sqlite.bundled`) - schemas are exported to `multiplatform-lib/schemas`.
- **okio** for hashing.
- **kotlin-base32** (`osmerion-kotlin-base32`) for OTP computation.
- **Coroutines** (`kotlinx.coroutines.core`, `kotlinx.coroutines.test`).
- **Parcelize** plugin enabled for Android-only `@Parcelize` models.

## Context Map

```
multiplatform-lib/
├── build.gradle.kts # KMP setup: targets, XCFramework, SKIE, Room
├── schemas/ # Exported Room schemas (commit when DB schema changes)
└── src/
├── commonMain/kotlin/
│ ├── Account.kt # Public KMP model for an Authenticator account
│ ├── AppStatus.kt
│ ├── AuthenticatorFacade.kt # Main public entry point for both Android and iOS
│ ├── CredentialsForMigration.kt
│ ├── Issue.kt
│ ├── matomo/ # Shared Matomo tracking helpers
│ ├── models/ # Public DTOs / domain models
│ │ └── migration/ # Shared* models exposed to iOS (e.g. SharedApiToken, SharedUserProfile)
│ ├── network/ # Public network types
│ │ ├── exceptions/
│ │ └── interfaces/
│ ├── repository/ # Public repository interfaces
│ ├── room/ # Public Room entities / DAOs (DBs are constructed per platform)
│ │ └── appsettings/
│ └── internal/ # Everything `internal` (not exported)
│ ├── KeyManager.kt # `KeyPairManager()` companion delegates to expect/actual createKeyPairManager()
│ ├── db/
│ ├── extensions/
│ ├── managers/ # e.g. MigrationManager (deletes legacy account + DB after migration)
│ ├── models/
│ ├── network/ # ApiClientProvider, request builders
│ ├── otp/ # TOTP/HOTP code generation
│ ├── repositories/
│ ├── requests/
│ ├── utils/
│ └── webauthn/
├── androidMain/kotlin/
│ ├── db/ # Android Room DB builder
│ └── internal/
│ ├── KeyPairManagerImpl.android.kt # Stores keys as files under appCtx.filesDir/passkeys
│ ├── db/
│ ├── network/utils/
│ ├── otp/
│ ├── room/
│ │ └── legacy/ # Legacy DB used by MigrationManager
│ └── utils/
├── appleMain/kotlin/
│ ├── db/ # Apple Room DB builder
│ └── internal/
│ ├── KeyGen.apple.kt # Keychain-backed key generation (tags: "$userId-$keyId" / "$userId-$keyId.pub")
│ ├── KeyPairManagerImpl.apple.kt
│ ├── db/
│ ├── extensions/
│ ├── network/utils/
│ ├── otp/
│ └── utils/
├── iosMain/kotlin/internal/utils/
├── macosMain/kotlin/internal/utils/
├── commonTest/kotlin/internal/ # Shared tests (kotlin.test + ktor-client-mock)
├── androidHostTest/kotlin/internal/ # JVM unit tests for android-specific code
├── androidDeviceTest/kotlin/internal/ # Espresso / AndroidJUnit tests (require a device)
└── appleTest/kotlin/internal/ # Tests for Apple targets
```

## Local Norms

### Architecture & Design

- **Public surface**: anything not marked `internal` is exported to the Android app and to Swift (via the `CoreAuthenticator`
XCFramework). Be deliberate about visibility - default to `internal`.
- **Entry point**: `AuthenticatorFacade.kt` is the main public facade. Prefer adding/extending facade methods over exposing
internal classes.
- **expect/actual**: Cross-platform abstractions use either `expect`/`actual` declarations or a common abstract class with a
per-platform `createX()` factory (see `internal/KeyManager.kt` - `KeyPairManager()` companion `invoke` delegates to an
expect/actual `createKeyPairManager()`).
- **Internal package**: All non-API code lives under `internal/` packages and is marked `internal`. Do not move internal types
into the public packages just to share them across modules - use a typealias or a thin public wrapper instead.
- **iOS naming**: Models exported to iOS that may collide with Swift built-ins use the `Shared*` prefix (e.g. `SharedApiToken`,
`SharedUserProfile`, `SharedSecurity`). Follow this convention when adding new migration / interop models.
- **Migration**: `MigrationManager` is responsible for moving data from legacy storage. After a successful migration it deletes
the legacy account and deletes the legacy DB when no legacy accounts remain - preserve this invariant when modifying it.
- **API client**: build all HTTP clients through `internal/network/ApiClientProvider.kt`. Keep its
`Json { coerceInputValues = true; ignoreUnknownKeys = true; decodeEnumsCaseInsensitive }` configuration intact - the API
tolerates unknown fields and case differences by design.
- **Key storage**:
- Android: files under `appCtx.filesDir/passkeys` named `"$userId-$keyId-(public|private).key"`.
- Apple Keychain tags: `"$userId-$keyId"` (private) and `"$userId-$keyId.pub"` (public).
Maintain these conventions when adding key-management code; do not rename existing entries (existing users would lose their
keys).
- **Room schemas**: When you change a Room entity, the generated schema under `multiplatform-lib/schemas/` must be committed
alongside a migration.

### Commands (run from repo root)

```bash
# Initialize Core submodule (required for any Gradle command)
git submodule update --init --recursive

# Assemble the Android variant of the library
./gradlew :multiplatform-lib:assemble

# Compile all KMP targets
./gradlew :multiplatform-lib:build

# Common (JVM) tests
./gradlew :multiplatform-lib:commonTest
./gradlew :multiplatform-lib:androidHostTest

# All tests (host + simulator)
./gradlew :multiplatform-lib:allTests

# Apple tests (require macOS host)
./gradlew :multiplatform-lib:iosSimulatorArm64Test
./gradlew :multiplatform-lib:macosArm64Test

# Build the iOS / macOS XCFramework (CoreAuthenticator.xcframework)
./buildXCFramework
```

### Code Style

Same general Kotlin rules as the app (see `app/AGENTS.md`):

- 130-char line limit (exceptions: single-line comments, imports, hardcoded strings).
- Max 1 consecutive blank line; 1 blank line after early-return blocks.
- GPLv3 copyright header in every file. No blank line between the closing `*/` and `package`.
- Official Kotlin code style (`kotlin.code.style=official`).
- Trivial control flow: one-liner. Non-trivial: braces + newlines.

KMP-specific rules:

- Use `expect`/`actual` for platform APIs; keep the `expect` signature minimal and document any platform constraints.
- Prefer `kotlinx.coroutines.flow.Flow` / `suspend` functions in the public API - SKIE turns them into idiomatic Swift
`AsyncSequence` / `async` functions.
- Avoid Java-only APIs in `commonMain` / `appleMain`. Use `kotlinx.io`, `okio`, or `kotlinx.datetime` instead.
- Keep `androidMain` dependencies behind `expect`/`actual` - do not add `androidx.*` imports to `commonMain`.
- For Apple-only types in public APIs, mark them with `@Throws(...)` where Swift needs to handle errors.

### Testing

- **commonTest**: pure-Kotlin tests using `kotlin.test` and `ktor-client-mock` for HTTP. Add new business-logic tests here
whenever possible so they run on every target.
- **androidHostTest**: JVM unit tests for code that needs Android-host-side resources (no device required).
- **androidDeviceTest**: instrumented tests (`androidx.test.junit`, `espresso-core`) - require a device/emulator.
- **appleTest**: tests for the Apple actuals.

### Public API stability

- The Android app and the iOS / macOS app consume this module as a binary contract. Treat any change to non-`internal`
declarations as a breaking change candidate: prefer adding to the API over modifying it.
- When renaming a public class/function that is consumed by iOS, also check `Package.swift` and the Swift side for impact (the
framework name `CoreAuthenticator` and `bundleId` `com.infomaniak.multiplatform-authenticator.CoreAuthenticator` must stay in
sync with the iOS project).

## Learned Preferences

Add KMP-specific corrections here as they occur.

- Prefer `internal` visibility by default; only widen to `public` when the symbol is part of the cross-platform API surface.
- Use the `Shared*` prefix for migration / interop models exposed to Swift to avoid name collisions.

## Self-correction

- **Stale Map**: Update when you encounter new source sets, packages, or expect/actual splits not listed.
- **Schema drift**: If `./gradlew :multiplatform-lib:build` regenerates files under `schemas/`, commit them as part of the same
change.
- **Reference Core / app**: When editing code that is bridged into the Android app, cross-check `app/AGENTS.md`; when editing Core
imports, check `Core/AGENTS.md`.
140 changes: 140 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import co.touchlab.skie.configuration.DefaultArgumentInterop
import com.android.build.api.dsl.androidLibrary
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework

plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(core.plugins.android.kmp.library)
alias(core.plugins.kotlin.serialization)
alias(libs.plugins.skie)
alias(libs.plugins.androidx.room)
alias(libs.plugins.ksp)
kotlin("plugin.parcelize")
}

val androidCompileSdk: Int by rootProject.extra
val androidMinSdk: Int by rootProject.extra
val javaVersion: JavaVersion by rootProject.extra

kotlin {
@Suppress("UnstableApiUsage")
androidLibrary {
namespace = "com.infomaniak.auth.multiplatform"
compileSdk = androidCompileSdk
minSdk = androidMinSdk

withDeviceTestBuilder {
sourceSetTreeName = "test"
}
withHostTest {}
compilerOptions {
jvmTarget.set(JvmTarget.fromTarget(javaVersion.toString()))
}
}

val xcframeworkName = "CoreAuthenticator"
val xcf = project.XCFramework(xcframeworkName)
listOf(
iosArm64(),
iosSimulatorArm64(),
macosArm64(),
).forEach {
it.binaries.framework {
baseName = xcframeworkName
binaryOption("bundleId", "com.infomaniak.multiplatform-authenticator.${xcframeworkName}")
xcf.add(this)
linkerOpts.add("-lsqlite3")
}
}

sourceSets {
commonMain.dependencies {
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.sqlite.bundled)
}
}

sourceSets {
commonMain {
dependencies {
implementation(core.kotlinx.coroutines.core)
implementation(core.kotlinx.serialization.json)
implementation(core.kotlinx.serialization.cbor)
implementation(core.ktor.client.core)
implementation(core.ktor.client.auth)
implementation(core.ktor.client.content.negociation)
implementation(core.ktor.client.json)
implementation(core.ktor.client.encoding)
implementation(core.okio)
implementation(libs.osmerion.kotlin.base32)
}
}
commonTest {
dependencies {
implementation(kotlin("test"))
implementation(core.kotlinx.coroutines.test)
implementation(core.ktor.client.mock)
}
}
androidMain {
dependencies {
implementation(core.ktor.client.okhttp)
implementation(core.splitties.appctx)
implementation(core.splitties.bitflags)
}
}
appleMain {
dependencies {
implementation(core.ktor.client.darwin)
}
}

listOf("iosArm64", "iosSimulatorArm64", "macosArm64").forEach { target ->
getByName("${target}Main") {
kotlin.srcDir(layout.buildDirectory.dir("generated/ksp/$target/${target}Main/kotlin"))
}
}

val androidDeviceTest by getting {
dependencies {
implementation(core.androidx.junit)
implementation(core.androidx.espresso.core)
}
}
}

compilerOptions {
freeCompilerArgs.add("-Xexpect-actual-classes")
freeCompilerArgs.add("-Xreturn-value-checker=full")
}
}

skie {
features {
group {
DefaultArgumentInterop.Enabled(true)
DefaultArgumentInterop.MaximumDefaultArgumentCount(7)
}
}
build {
produceDistributableFramework()
}
}

room {
schemaDirectory("$projectDir/schemas")
}

dependencies {
add("kspAndroid", libs.androidx.room.compiler)
add("kspIosSimulatorArm64", libs.androidx.room.compiler)
add("kspIosArm64", libs.androidx.room.compiler)
add("kspMacosArm64", libs.androidx.room.compiler)
}

listOf("IosArm64", "IosSimulatorArm64", "MacosArm64").forEach { target ->
tasks.named("compileKotlin$target") {
dependsOn("kspKotlin$target")
}
}
Loading