@chipmobilesdk/rn-auth 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,54 +1,51 @@
1
- {
2
- "name": "@chipmobilesdk/rn-auth",
3
- "version": "0.3.0",
4
- "private": false,
5
- "license": "UNLICENSED",
6
- "main": "src/index.ts",
7
- "types": "src/index.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./src/index.ts",
11
- "react-native": "./src/index.ts",
12
- "default": "./src/index.ts"
13
- }
14
- },
15
- "files": [
16
- "src/index.ts",
17
- "src/config",
18
- "src/core",
19
- "src/tokens",
20
- "src/oidc",
21
- "src/access",
22
- "src/diagnostics",
23
- "src/react",
24
- "skills",
25
- "README.md",
26
- "CHANGELOG.md"
27
- ],
28
- "sideEffects": false,
29
- "scripts": {
30
- "typecheck": "tsc -p tsconfig.json --noEmit",
31
- "pack:dry-run": "npm pack --dry-run"
32
- },
33
- "peerDependencies": {
34
- "react": ">=19",
35
- "react-native": ">=0.85",
36
- "react-native-app-auth": ">=8",
37
- "react-native-keychain": ">=10"
38
- },
39
- "peerDependenciesMeta": {
40
- "react-native-app-auth": {
41
- "optional": true
42
- },
43
- "react-native-keychain": {
44
- "optional": true
45
- }
46
- },
47
- "engines": {
48
- "node": ">=22.11.0"
49
- },
50
- "publishConfig": {
51
- "access": "public",
52
- "registry": "https://registry.npmjs.org/"
53
- }
54
- }
1
+ {
2
+ "name": "@chipmobilesdk/rn-auth",
3
+ "version": "0.3.1",
4
+ "private": false,
5
+ "license": "UNLICENSED",
6
+ "main": "src/index.ts",
7
+ "types": "src/index.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./src/index.ts",
11
+ "react-native": "./src/index.ts",
12
+ "default": "./src/index.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "src/index.ts",
17
+ "src/config",
18
+ "src/core",
19
+ "src/tokens",
20
+ "src/oidc",
21
+ "src/access",
22
+ "src/diagnostics",
23
+ "src/react",
24
+ "skills",
25
+ "README.md",
26
+ "CHANGELOG.md"
27
+ ],
28
+ "sideEffects": false,
29
+ "scripts": {
30
+ "typecheck": "tsc -p tsconfig.json --noEmit",
31
+ "pack:check": "npm pack --dry-run"
32
+ },
33
+ "peerDependencies": {
34
+ "react": ">=19",
35
+ "react-native": ">=0.85",
36
+ "react-native-app-auth": ">=8",
37
+ "react-native-keychain": ">=10"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "react-native-app-auth": {
41
+ "optional": true
42
+ },
43
+ "react-native-keychain": {
44
+ "optional": true
45
+ }
46
+ },
47
+ "publishConfig": {
48
+ "access": "public",
49
+ "registry": "https://registry.npmjs.org/"
50
+ }
51
+ }
@@ -1,63 +1,62 @@
1
- ---
2
- name: sdk-rn-auth
3
- description: "Authentication via @chipmobilesdk/rn-auth: OIDC/AppAuth login, keychain token storage, access tiers, auth error handling. Use when wiring sign-in, storing tokens securely, gating features by tier, or configuring a tenant/provider."
4
- metadata:
5
- package: "@chipmobilesdk/rn-auth"
6
- packageVersion: "0.2.0"
7
- user-invocable: true
8
- disable-model-invocation: false
9
- ---
10
-
11
- # @chipmobilesdk/rn-auth
12
-
13
- OIDC authentication with secure token storage and an access-tier model for
14
- feature gating.
15
-
16
- ## Use this when
17
-
18
- - Wiring sign-in / sign-out
19
- - Persisting tokens (must be keychain, never plain storage)
20
- - Gating a feature on the user's access tier
21
- - Mapping an auth failure to a user-facing message
22
- - Onboarding a new tenant or identity provider
23
-
24
- ## Configuration is a contract, not options
25
-
26
- Tenant and provider setup has prerequisites that fail at runtime, not at build time.
27
- Before configuring anything, walk `## Configuration contract (LIB-010)` and
28
- `## Tenant / provider prerequisite checklist (LIB-018)` in the README. Skipping the
29
- checklist is the usual cause of "login works locally, fails in the new environment".
30
-
31
- ## Fail closed
32
-
33
- `failClosedTier` exists because an unknown tier must deny access, not grant it. When
34
- tier evaluation is unavailable, deny. Never default an unresolved tier to a permissive
35
- value.
36
-
37
- ## Public API (v0.3.0)
38
-
39
- Runtime:
40
-
41
- - `createAppAuthClient(...)` — the OIDC client
42
- - `createKeychainSecureStore(...)` — token persistence; `AUTH_STORAGE_KEY`
43
- - `evaluateAccessTier(...)`, `failClosedTier` — feature gating
44
- - `AuthProvider` — React context provider
45
-
46
- Types: `SecureTokenStore`, `AuthProviderProps`, `AuthErrorCode`, `AuthErrorAudience`,
47
- `AuthMessageMap`, `AuthMessageKey`
48
-
49
- `AuthErrorAudience` distinguishes what may be shown to a user from what is diagnostic
50
- only — never render a diagnostic-audience message in the UI.
51
-
52
- ## Where the detail lives
53
-
54
- Read one section of `node_modules/@chipmobilesdk/rn-auth/README.md`:
55
-
56
- | Need | Section |
57
- |---|---|
58
- | First integration | `## Install (consuming app)`, `## Quick start` |
59
- | Required config fields | `## Configuration contract (LIB-010)` |
60
- | New tenant or IdP | `## Tenant / provider prerequisite checklist (LIB-018)` |
61
- | Token handling, logging rules | `## Security & privacy` |
62
- | Full export list | `## Public API` |
63
- | Version/peer constraints | `## Compatibility and validation` |
1
+ ---
2
+ name: sdk-rn-auth
3
+ description: "Authentication via @chipmobilesdk/rn-auth: OIDC/AppAuth login, keychain token storage, access tiers, auth error handling. Use when wiring sign-in, storing tokens securely, gating features by tier, or configuring a tenant/provider."
4
+ metadata:
5
+ package: "@chipmobilesdk/rn-auth"
6
+ user-invocable: true
7
+ disable-model-invocation: false
8
+ ---
9
+
10
+ # @chipmobilesdk/rn-auth
11
+
12
+ OIDC authentication with secure token storage and an access-tier model for
13
+ feature gating.
14
+
15
+ ## Use this when
16
+
17
+ - Wiring sign-in / sign-out
18
+ - Persisting tokens (must be keychain, never plain storage)
19
+ - Gating a feature on the user's access tier
20
+ - Mapping an auth failure to a user-facing message
21
+ - Onboarding a new tenant or identity provider
22
+
23
+ ## Configuration is a contract, not options
24
+
25
+ Tenant and provider setup has prerequisites that fail at runtime, not at build time.
26
+ Before configuring anything, walk `## Configuration contract (LIB-010)` and
27
+ `## Tenant / provider prerequisite checklist (LIB-018)` in the README. Skipping the
28
+ checklist is the usual cause of "login works locally, fails in the new environment".
29
+
30
+ ## Fail closed
31
+
32
+ `failClosedTier` exists because an unknown tier must deny access, not grant it. When
33
+ tier evaluation is unavailable, deny. Never default an unresolved tier to a permissive
34
+ value.
35
+
36
+ ## Public API
37
+
38
+ Runtime:
39
+
40
+ - `createAppAuthClient(...)` — the OIDC client
41
+ - `createKeychainSecureStore(...)` — token persistence; `AUTH_STORAGE_KEY`
42
+ - `evaluateAccessTier(...)`, `failClosedTier` — feature gating
43
+ - `AuthProvider` — React context provider
44
+
45
+ Types: `SecureTokenStore`, `AuthProviderProps`, `AuthErrorCode`, `AuthErrorAudience`,
46
+ `AuthMessageMap`, `AuthMessageKey`
47
+
48
+ `AuthErrorAudience` distinguishes what may be shown to a user from what is diagnostic
49
+ only — never render a diagnostic-audience message in the UI.
50
+
51
+ ## Where the detail lives
52
+
53
+ Read one section of `node_modules/@chipmobilesdk/rn-auth/README.md`:
54
+
55
+ | Need | Section |
56
+ |---|---|
57
+ | First integration | `## Install (consuming app)`, `## Quick start` |
58
+ | Required config fields | `## Configuration contract (LIB-010)` |
59
+ | New tenant or IdP | `## Tenant / provider prerequisite checklist (LIB-018)` |
60
+ | Token handling, logging rules | `## Security & privacy` |
61
+ | Full export list | `## Public API` |
62
+ | Version/peer constraints | `## Compatibility and validation` |
@@ -1,88 +1,88 @@
1
- /**
2
- * Access-tier evaluation (FR-010/FR-011/FR-019/FR-020, R6).
3
- *
4
- * Pure function over the authenticated token's claims and the app-specific
5
- * mapping. Object ids are authoritative; display names never grant access.
6
- * Missing, stale, ambiguous, or group-overage claims fail closed (premium
7
- * denied, verification = 'unverified').
8
- */
9
-
10
- import type { AccessTier, AccessTierMapping, ClaimMatcher } from '../config/types';
11
-
12
- /** The fail-closed default: free + unverified. */
13
- export function failClosedTier(): AccessTier {
14
- return { tier: 'free', source: 'default', verification: 'unverified' };
15
- }
16
-
17
- function toStringArray(value: unknown): string[] {
18
- if (Array.isArray(value)) {
19
- return value.filter((entry): entry is string => typeof entry === 'string');
20
- }
21
- if (typeof value === 'string') {
22
- return [value];
23
- }
24
- return [];
25
- }
26
-
27
- /**
28
- * Entra emits a groups overage indicator (`_claim_names.groups` or `hasgroups`)
29
- * instead of inline groups when the list is too large. Reading the full list
30
- * would require Graph (out of scope, FR-012), so overage fails closed.
31
- */
32
- function hasGroupOverage(claims: Record<string, unknown>): boolean {
33
- if (claims.hasgroups === true) {
34
- return true;
35
- }
36
- const claimNames = claims._claim_names as Record<string, unknown> | undefined;
37
- if (claimNames && typeof claimNames === 'object' && 'groups' in claimNames) {
38
- return true;
39
- }
40
- return false;
41
- }
42
-
43
- function matchesClaim(claims: Record<string, unknown>, matcher: ClaimMatcher): boolean {
44
- const value = claims[matcher.claim];
45
- if (Array.isArray(value)) {
46
- return value.includes(matcher.equals);
47
- }
48
- return value === matcher.equals;
49
- }
50
-
51
- /** Evaluate the access tier, failing closed when entitlement is unverifiable. */
52
- export function evaluateAccessTier(
53
- claims: Record<string, unknown>,
54
- mapping: AccessTierMapping,
55
- now: number = Date.now(),
56
- ): AccessTier {
57
- // Object ids first (authoritative). Display names are never consulted.
58
- const groups = toStringArray(claims.groups);
59
- if (
60
- mapping.premiumGroupObjectIds.length > 0 &&
61
- groups.some(group => mapping.premiumGroupObjectIds.includes(group))
62
- ) {
63
- return { tier: 'premium', source: 'group', verification: 'verified', lastRefreshAt: now };
64
- }
65
-
66
- // Roles next.
67
- const roles = toStringArray(claims.roles);
68
- if (
69
- mapping.premiumRoleNames?.length &&
70
- roles.some(role => mapping.premiumRoleNames!.includes(role))
71
- ) {
72
- return { tier: 'premium', source: 'role', verification: 'verified', lastRefreshAt: now };
73
- }
74
-
75
- // Custom claim matchers last.
76
- if (mapping.premiumClaimMatchers?.some(matcher => matchesClaim(claims, matcher))) {
77
- return { tier: 'premium', source: 'claim', verification: 'verified', lastRefreshAt: now };
78
- }
79
-
80
- // No premium indicator matched. If group claims are unavailable due to
81
- // overage and premium depends on groups, we cannot verify → fail closed.
82
- if (mapping.premiumGroupObjectIds.length > 0 && hasGroupOverage(claims)) {
83
- return { tier: 'free', source: 'default', verification: 'unverified', lastRefreshAt: now };
84
- }
85
-
86
- // Verified free: claims were present and simply did not grant premium.
87
- return { tier: 'free', source: 'default', verification: 'verified', lastRefreshAt: now };
88
- }
1
+ /**
2
+ * Access-tier evaluation (FR-010/FR-011/FR-019/FR-020, R6).
3
+ *
4
+ * Pure function over the authenticated token's claims and the app-specific
5
+ * mapping. Object ids are authoritative; display names never grant access.
6
+ * Missing, stale, ambiguous, or group-overage claims fail closed (premium
7
+ * denied, verification = 'unverified').
8
+ */
9
+
10
+ import type { AccessTier, AccessTierMapping, ClaimMatcher } from '../config/types';
11
+
12
+ /** The fail-closed default: free + unverified. */
13
+ export function failClosedTier(): AccessTier {
14
+ return { tier: 'free', source: 'default', verification: 'unverified' };
15
+ }
16
+
17
+ function toStringArray(value: unknown): string[] {
18
+ if (Array.isArray(value)) {
19
+ return value.filter((entry): entry is string => typeof entry === 'string');
20
+ }
21
+ if (typeof value === 'string') {
22
+ return [value];
23
+ }
24
+ return [];
25
+ }
26
+
27
+ /**
28
+ * Entra emits a groups overage indicator (`_claim_names.groups` or `hasgroups`)
29
+ * instead of inline groups when the list is too large. Reading the full list
30
+ * would require Graph (out of scope, FR-012), so overage fails closed.
31
+ */
32
+ function hasGroupOverage(claims: Record<string, unknown>): boolean {
33
+ if (claims.hasgroups === true) {
34
+ return true;
35
+ }
36
+ const claimNames = claims._claim_names as Record<string, unknown> | undefined;
37
+ if (claimNames && typeof claimNames === 'object' && 'groups' in claimNames) {
38
+ return true;
39
+ }
40
+ return false;
41
+ }
42
+
43
+ function matchesClaim(claims: Record<string, unknown>, matcher: ClaimMatcher): boolean {
44
+ const value = claims[matcher.claim];
45
+ if (Array.isArray(value)) {
46
+ return value.includes(matcher.equals);
47
+ }
48
+ return value === matcher.equals;
49
+ }
50
+
51
+ /** Evaluate the access tier, failing closed when entitlement is unverifiable. */
52
+ export function evaluateAccessTier(
53
+ claims: Record<string, unknown>,
54
+ mapping: AccessTierMapping,
55
+ now: number = Date.now(),
56
+ ): AccessTier {
57
+ // Object ids first (authoritative). Display names are never consulted.
58
+ const groups = toStringArray(claims.groups);
59
+ if (
60
+ mapping.premiumGroupObjectIds.length > 0 &&
61
+ groups.some(group => mapping.premiumGroupObjectIds.includes(group))
62
+ ) {
63
+ return { tier: 'premium', source: 'group', verification: 'verified', lastRefreshAt: now };
64
+ }
65
+
66
+ // Roles next.
67
+ const roles = toStringArray(claims.roles);
68
+ if (
69
+ mapping.premiumRoleNames?.length &&
70
+ roles.some(role => mapping.premiumRoleNames!.includes(role))
71
+ ) {
72
+ return { tier: 'premium', source: 'role', verification: 'verified', lastRefreshAt: now };
73
+ }
74
+
75
+ // Custom claim matchers last.
76
+ if (mapping.premiumClaimMatchers?.some(matcher => matchesClaim(claims, matcher))) {
77
+ return { tier: 'premium', source: 'claim', verification: 'verified', lastRefreshAt: now };
78
+ }
79
+
80
+ // No premium indicator matched. If group claims are unavailable due to
81
+ // overage and premium depends on groups, we cannot verify → fail closed.
82
+ if (mapping.premiumGroupObjectIds.length > 0 && hasGroupOverage(claims)) {
83
+ return { tier: 'free', source: 'default', verification: 'unverified', lastRefreshAt: now };
84
+ }
85
+
86
+ // Verified free: claims were present and simply did not grant premium.
87
+ return { tier: 'free', source: 'default', verification: 'verified', lastRefreshAt: now };
88
+ }