From 2c5e0c6f8ce1df89aefa062e58f1e09ba5bd2c98 Mon Sep 17 00:00:00 2001 From: Brion Date: Fri, 10 Jul 2026 22:05:12 +0530 Subject: [PATCH] Introduce vendor namespace support Adds a `vendor` option to ThunderIDConfig so adopters can white-label the SDK's runtime identifiers (Keychain service name, i18n locale storage key) instead of being pinned to the `thunderid` brand prefix. Related: thunder-id/thunderid#3896 --- .coderabbit.yaml | 34 +++++++++++++++++++ AGENTS.md | 9 +++++ Sources/ThunderID/ThunderIDClient.swift | 2 +- Sources/ThunderID/ThunderIDConfig.swift | 9 ++++- Sources/ThunderID/VendorConstants.swift | 26 ++++++++++++++ .../environment/ThunderIDViewModifier.swift | 5 +-- 6 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 .coderabbit.yaml create mode 100644 Sources/ThunderID/VendorConstants.swift diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000..e749503 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,34 @@ +# 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: "Sources/**/*.swift" + instructions: | + Flag any hardcoded occurrence of the literal `thunderid`/`ThunderID`/`THUNDERID` (any casing) used to + build a runtime key or name — Keychain service names, log tags, and similar. + + Do not flag the module name 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/**" diff --git a/AGENTS.md b/AGENTS.md index edcef61..bcd4825 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,6 +4,15 @@ Swift package providing the ThunderID authentication SDK (`ThunderID`) and a SwiftUI component library (`ThunderIDSwiftUI`). 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 — Keychain service names, log tags, and the like. +- It's fine for the **entry point** — the module name or a type 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 diff --git a/Sources/ThunderID/ThunderIDClient.swift b/Sources/ThunderID/ThunderIDClient.swift index dd9c36f..5891516 100644 --- a/Sources/ThunderID/ThunderIDClient.swift +++ b/Sources/ThunderID/ThunderIDClient.swift @@ -41,7 +41,7 @@ public final class ThunderIDClient { } try validateConfig(config) self.config = config - let adapter = storage ?? config.storage ?? KeychainStorageAdapter() + let adapter = storage ?? config.storage ?? KeychainStorageAdapter(service: "dev.\(config.vendor).sdk") let http = HTTPClient(baseUrl: config.baseUrl) tokenStore = TokenStore(storage: adapter) jwksCache = JWKSCache(httpClient: http) diff --git a/Sources/ThunderID/ThunderIDConfig.swift b/Sources/ThunderID/ThunderIDConfig.swift index efc7159..6a8eb51 100644 --- a/Sources/ThunderID/ThunderIDConfig.swift +++ b/Sources/ThunderID/ThunderIDConfig.swift @@ -48,6 +48,11 @@ public struct ThunderIDConfig { public var storage: StorageAdapter? public var instanceId: Int? + /// Vendor/brand namespace used to derive default storage identifiers (e.g. Keychain service name). + /// Override this when white-labeling the SDK under a different brand. Defaults to + /// `VendorConstants.vendorPrefix` ("thunderid"). + public var vendor: String + public init( baseUrl: String, clientId: String? = nil, @@ -64,7 +69,8 @@ public struct ThunderIDConfig { organizationHandle: String? = nil, tokenValidation: TokenValidationConfig = .init(), storage: StorageAdapter? = nil, - instanceId: Int? = nil + instanceId: Int? = nil, + vendor: String = VendorConstants.vendorPrefix ) { self.baseUrl = baseUrl self.clientId = clientId @@ -82,6 +88,7 @@ public struct ThunderIDConfig { self.tokenValidation = tokenValidation self.storage = storage self.instanceId = instanceId + self.vendor = vendor } } diff --git a/Sources/ThunderID/VendorConstants.swift b/Sources/ThunderID/VendorConstants.swift new file mode 100644 index 0000000..3cdbc0f --- /dev/null +++ b/Sources/ThunderID/VendorConstants.swift @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/// Constants for vendor-specific configuration. +/// +/// By default, the vendor is inferred as ThunderID. +public enum VendorConstants { + /// The prefix used for vendor-specific storage identifiers (e.g. Keychain service name), or other + /// runtime keys/names. + public static let vendorPrefix: String = "thunderid" +} diff --git a/Sources/ThunderIDSwiftUI/environment/ThunderIDViewModifier.swift b/Sources/ThunderIDSwiftUI/environment/ThunderIDViewModifier.swift index c0bad08..522324d 100644 --- a/Sources/ThunderIDSwiftUI/environment/ThunderIDViewModifier.swift +++ b/Sources/ThunderIDSwiftUI/environment/ThunderIDViewModifier.swift @@ -45,7 +45,8 @@ public extension View { /// ContentView() /// .thunderIDProvider(config: ThunderIDConfig(baseUrl: "...", clientId: "...")) /// ``` - func thunderIDProvider(config: ThunderIDConfig, i18n: ThunderIDI18n = ThunderIDI18n()) -> some View { - modifier(ThunderIDProviderModifier(config: config, i18n: i18n)) + func thunderIDProvider(config: ThunderIDConfig, i18n: ThunderIDI18n? = nil) -> some View { + let resolvedI18n = i18n ?? ThunderIDI18n(storageKey: "\(config.vendor)_locale") + return modifier(ThunderIDProviderModifier(config: config, i18n: resolvedI18n)) } }