Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 35 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
language: en-US

reviews:
request_changes_workflow: false
high_level_summary: true
poem: false

path_instructions:
- path: "src/**/*.kt"
instructions: |
Flag any hardcoded occurrence of the literal `thunderid`/`ThunderID`/`THUNDERID` (any casing) used to
build a runtime key or name — `EncryptedSharedPreferences` keys, log tags, and similar.

Do not flag the package namespace or a type whose purpose IS to represent the SDK itself (e.g.
`ThunderIDClient`, `ThunderIDProvider`). That's a fixed identity, not a per-tenant value.

For everything else, ask: **"Should this read from `ThunderIDConfig.vendor` instead of hardcoding the
vendor name?"** The best fix is avoiding the vendor name entirely when the brand prefix isn't
load-bearing. When a brand-scoped namespace is genuinely required, it should resolve through
`config.vendor` (which already defaults to `"thunderid"`) rather than a literal string. If the same
default-resolution logic starts appearing in more than one place, ask for it to be extracted into a
shared helper instead of repeated inline.

auto_review:
enabled: true
ignore_title_keywords:
- "WIP"
- "DO NOT MERGE"
base_branches:
- main

path_filters:
- "!**/build/**"
- "!**/.gradle/**"
9 changes: 9 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@

Kotlin Android library providing the ThunderID authentication SDK (`dev.thunderid.android`) and a Jetpack Compose component library (`dev.thunderid.compose`). The `samples/quickstart` directory contains a standalone demo app.

## Vendor naming rules

The SDK is white-labelable: a consuming app can override the brand/vendor namespace via `ThunderIDConfig.vendor`, so storage keys, log tags, and similar runtime names shouldn't be pinned to one brand.

- Do not hardcode the literal `thunderid`/`ThunderID`/`THUNDERID` (any casing) when building a runtime key/name that a consumer's `vendor` override should control — `EncryptedSharedPreferences` keys, log tags, and the like.
- It's fine for the **entry point** — the package namespace or a class whose purpose IS to represent the SDK itself (e.g. `ThunderIDClient`, `ThunderIDProvider`) — to carry the name. That's a fixed identity, not a per-tenant value. Don't flag those.
- Avoiding the vendor name entirely is the best outcome, when the brand prefix isn't actually load-bearing.
- When a brand-scoped namespace is genuinely required, resolve it from `config.vendor` (which already defaults to `"thunderid"`) instead of hardcoding a literal. If the same default-resolution logic starts appearing in more than one place, extract a small shared helper rather than repeating the literal default.

## Build & test

```bash
Expand Down
7 changes: 5 additions & 2 deletions src/main/kotlin/dev/thunderid/android/StorageAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ interface StorageAdapter {
/**
* Default storage using Android EncryptedSharedPreferences backed by the Android Keystore (spec §11.1).
*/
class EncryptedStorageAdapter(context: Context) : StorageAdapter {
class EncryptedStorageAdapter(
context: Context,
prefsName: String = "dev.thunderid.sdk.prefs",
) : StorageAdapter {
private val prefs =
run {
val masterKey =
Expand All @@ -50,7 +53,7 @@ class EncryptedStorageAdapter(context: Context) : StorageAdapter {
.build()
EncryptedSharedPreferences.create(
context,
"dev.thunderid.sdk.prefs",
prefsName,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
Expand Down
12 changes: 11 additions & 1 deletion src/main/kotlin/dev/thunderid/android/ThunderIDConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,17 @@ data class ThunderIDConfig(
val instanceId: Int? = null,
// Development only — bypasses TLS certificate verification. Never use in production.
val allowInsecureConnections: Boolean = false,
)
/**
* Vendor/brand namespace used to derive default storage identifiers (e.g. EncryptedSharedPreferences file
* name). Override this when white-labeling the SDK under a different brand. Defaults to
* [DEFAULT_VENDOR].
*/
val vendor: String = DEFAULT_VENDOR,
) {
companion object {
const val DEFAULT_VENDOR: String = "thunderid"
}
}

data class TokenValidationConfig(
val validate: Boolean = true,
Expand Down
6 changes: 4 additions & 2 deletions src/main/kotlin/dev/thunderid/compose/ThunderIDProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,12 @@ import dev.thunderid.compose.i18n.ThunderIDI18n
fun ThunderIDProvider(
config: ThunderIDConfig,
client: ThunderIDClient = remember { ThunderIDClient() },
i18n: ThunderIDI18n = remember { ThunderIDI18n() },
i18n: ThunderIDI18n? = null,
content: @Composable () -> Unit,
) {
val state = remember(client, i18n) { ThunderIDState(client, i18n) }
val resolvedI18n =
i18n ?: remember(config.vendor) { ThunderIDI18n(storageKey = "${config.vendor}_locale") }
val state = remember(client, resolvedI18n) { ThunderIDState(client, resolvedI18n) }
val scope = rememberCoroutineScope()

LaunchedEffect(config) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ fun BaseSignIn(
val response = thunderState.client.signIn(payload = payload, request = request)
handleSignInResponse(response, signInState, thunderState, onComplete, onError)
} catch (e: Exception) {
android.util.Log.e("ThunderID:SignIn", "Flow submit failed", e)
android.util.Log.e("SignInFlow", "Flow submit failed", e)
signInState.error = e.message
onError?.invoke(e.message ?: "Sign-in failed")
} finally {
Expand All @@ -180,13 +180,13 @@ fun BaseSignIn(
val payload = EmbeddedSignInPayload(actionId = "__initiate__")
val response = thunderState.client.signIn(payload = payload, request = request)
android.util.Log.d(
"ThunderID:SignIn",
"SignInFlow",
"status=${response.flowStatus} inputs=${response.data?.inputs?.size} " +
"actions=${response.data?.actions?.size} data=${response.data}",
)
handleSignInResponse(response, signInState, thunderState, onComplete, onError)
} catch (e: Exception) {
android.util.Log.e("ThunderID:SignIn", "Flow initiation failed", e)
android.util.Log.e("SignInFlow", "Flow initiation failed", e)
signInState.error = e.message
onError?.invoke(e.message ?: "Sign-in failed")
} finally {
Expand Down
Loading