@chipmobilesdk/rn-auth 0.4.0 → 0.6.0
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/CHANGELOG.md +139 -0
- package/README.md +74 -14
- package/lib/commonjs/access/accessTier.js +111 -0
- package/lib/commonjs/access/accessTier.js.map +1 -0
- package/lib/commonjs/app-auth.js +13 -0
- package/lib/commonjs/app-auth.js.map +1 -0
- package/lib/commonjs/config/types.js +50 -0
- package/lib/commonjs/config/types.js.map +1 -0
- package/lib/commonjs/config/validateConfig.js +114 -0
- package/lib/commonjs/config/validateConfig.js.map +1 -0
- package/lib/commonjs/core/authMachine.js +406 -0
- package/lib/commonjs/core/authMachine.js.map +1 -0
- package/lib/commonjs/core/profile.js +59 -0
- package/lib/commonjs/core/profile.js.map +1 -0
- package/lib/commonjs/core/session.js +125 -0
- package/lib/commonjs/core/session.js.map +1 -0
- package/lib/commonjs/diagnostics/errors.js +83 -0
- package/lib/commonjs/diagnostics/errors.js.map +1 -0
- package/lib/commonjs/diagnostics/messageKeys.js +51 -0
- package/lib/commonjs/diagnostics/messageKeys.js.map +1 -0
- package/lib/commonjs/index.js +175 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/keychain.js +13 -0
- package/lib/commonjs/keychain.js.map +1 -0
- package/lib/commonjs/oidc/authClient.js +157 -0
- package/lib/commonjs/oidc/authClient.js.map +1 -0
- package/lib/commonjs/oidc/classifyError.js +65 -0
- package/lib/commonjs/oidc/classifyError.js.map +1 -0
- package/lib/commonjs/oidc/socialRouting.js +58 -0
- package/lib/commonjs/oidc/socialRouting.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/react/AuthProvider.js +37 -0
- package/lib/commonjs/react/AuthProvider.js.map +1 -0
- package/lib/commonjs/react/hooks.js +122 -0
- package/lib/commonjs/react/hooks.js.map +1 -0
- package/lib/commonjs/testing/conformance.js +148 -0
- package/lib/commonjs/testing/conformance.js.map +1 -0
- package/lib/commonjs/testing/index.js +53 -0
- package/lib/commonjs/testing/index.js.map +1 -0
- package/lib/commonjs/tokens/accessToken.js +165 -0
- package/lib/commonjs/tokens/accessToken.js.map +1 -0
- package/lib/commonjs/tokens/secureStore.js +60 -0
- package/lib/commonjs/tokens/secureStore.js.map +1 -0
- package/lib/module/access/accessTier.js +106 -0
- package/lib/module/access/accessTier.js.map +1 -0
- package/lib/module/app-auth.js +9 -0
- package/lib/module/app-auth.js.map +1 -0
- package/lib/module/config/types.js +46 -0
- package/lib/module/config/types.js.map +1 -0
- package/lib/module/config/validateConfig.js +110 -0
- package/lib/module/config/validateConfig.js.map +1 -0
- package/lib/module/core/authMachine.js +402 -0
- package/lib/module/core/authMachine.js.map +1 -0
- package/lib/module/core/profile.js +54 -0
- package/lib/module/core/profile.js.map +1 -0
- package/lib/module/core/session.js +118 -0
- package/lib/module/core/session.js.map +1 -0
- package/lib/module/diagnostics/errors.js +74 -0
- package/lib/module/diagnostics/errors.js.map +1 -0
- package/lib/module/diagnostics/messageKeys.js +46 -0
- package/lib/module/diagnostics/messageKeys.js.map +1 -0
- package/lib/module/index.js +33 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/keychain.js +9 -0
- package/lib/module/keychain.js.map +1 -0
- package/lib/module/oidc/authClient.js +152 -0
- package/lib/module/oidc/authClient.js.map +1 -0
- package/lib/module/oidc/classifyError.js +60 -0
- package/lib/module/oidc/classifyError.js.map +1 -0
- package/lib/module/oidc/socialRouting.js +52 -0
- package/lib/module/oidc/socialRouting.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/react/AuthProvider.js +31 -0
- package/lib/module/react/AuthProvider.js.map +1 -0
- package/lib/module/react/hooks.js +111 -0
- package/lib/module/react/hooks.js.map +1 -0
- package/lib/module/testing/conformance.js +140 -0
- package/lib/module/testing/conformance.js.map +1 -0
- package/lib/module/testing/index.js +25 -0
- package/lib/module/testing/index.js.map +1 -0
- package/lib/module/tokens/accessToken.js +161 -0
- package/lib/module/tokens/accessToken.js.map +1 -0
- package/lib/module/tokens/secureStore.js +56 -0
- package/lib/module/tokens/secureStore.js.map +1 -0
- package/lib/typescript/commonjs/access/accessTier.d.ts +14 -0
- package/lib/typescript/commonjs/access/accessTier.d.ts.map +1 -0
- package/lib/typescript/commonjs/app-auth.d.ts +7 -0
- package/lib/typescript/commonjs/app-auth.d.ts.map +1 -0
- package/lib/typescript/commonjs/config/types.d.ts +171 -0
- package/lib/typescript/commonjs/config/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/config/validateConfig.d.ts +8 -0
- package/lib/typescript/commonjs/config/validateConfig.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/authMachine.d.ts +14 -0
- package/lib/typescript/commonjs/core/authMachine.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/profile.d.ts +15 -0
- package/lib/typescript/commonjs/core/profile.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/session.d.ts +60 -0
- package/lib/typescript/commonjs/core/session.d.ts.map +1 -0
- package/lib/typescript/commonjs/diagnostics/errors.d.ts +37 -0
- package/lib/typescript/commonjs/diagnostics/errors.d.ts.map +1 -0
- package/lib/typescript/commonjs/diagnostics/messageKeys.d.ts +57 -0
- package/lib/typescript/commonjs/diagnostics/messageKeys.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +27 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/keychain.d.ts +7 -0
- package/lib/typescript/commonjs/keychain.d.ts.map +1 -0
- package/lib/typescript/commonjs/oidc/authClient.d.ts +75 -0
- package/lib/typescript/commonjs/oidc/authClient.d.ts.map +1 -0
- package/lib/typescript/commonjs/oidc/classifyError.d.ts +28 -0
- package/lib/typescript/commonjs/oidc/classifyError.d.ts.map +1 -0
- package/lib/typescript/commonjs/oidc/socialRouting.d.ts +22 -0
- package/lib/typescript/commonjs/oidc/socialRouting.d.ts.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/react/AuthProvider.d.ts +17 -0
- package/lib/typescript/commonjs/react/AuthProvider.d.ts.map +1 -0
- package/lib/typescript/commonjs/react/hooks.d.ts +42 -0
- package/lib/typescript/commonjs/react/hooks.d.ts.map +1 -0
- package/lib/typescript/commonjs/testing/conformance.d.ts +32 -0
- package/lib/typescript/commonjs/testing/conformance.d.ts.map +1 -0
- package/lib/typescript/commonjs/testing/index.d.ts +11 -0
- package/lib/typescript/commonjs/testing/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/tokens/accessToken.d.ts +45 -0
- package/lib/typescript/commonjs/tokens/accessToken.d.ts.map +1 -0
- package/lib/typescript/commonjs/tokens/secureStore.d.ts +26 -0
- package/lib/typescript/commonjs/tokens/secureStore.d.ts.map +1 -0
- package/lib/typescript/module/access/accessTier.d.ts +14 -0
- package/lib/typescript/module/access/accessTier.d.ts.map +1 -0
- package/lib/typescript/module/app-auth.d.ts +7 -0
- package/lib/typescript/module/app-auth.d.ts.map +1 -0
- package/lib/typescript/module/config/types.d.ts +171 -0
- package/lib/typescript/module/config/types.d.ts.map +1 -0
- package/lib/typescript/module/config/validateConfig.d.ts +8 -0
- package/lib/typescript/module/config/validateConfig.d.ts.map +1 -0
- package/lib/typescript/module/core/authMachine.d.ts +14 -0
- package/lib/typescript/module/core/authMachine.d.ts.map +1 -0
- package/lib/typescript/module/core/profile.d.ts +15 -0
- package/lib/typescript/module/core/profile.d.ts.map +1 -0
- package/lib/typescript/module/core/session.d.ts +60 -0
- package/lib/typescript/module/core/session.d.ts.map +1 -0
- package/lib/typescript/module/diagnostics/errors.d.ts +37 -0
- package/lib/typescript/module/diagnostics/errors.d.ts.map +1 -0
- package/lib/typescript/module/diagnostics/messageKeys.d.ts +57 -0
- package/lib/typescript/module/diagnostics/messageKeys.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +27 -0
- package/lib/typescript/module/index.d.ts.map +1 -0
- package/lib/typescript/module/keychain.d.ts +7 -0
- package/lib/typescript/module/keychain.d.ts.map +1 -0
- package/lib/typescript/module/oidc/authClient.d.ts +75 -0
- package/lib/typescript/module/oidc/authClient.d.ts.map +1 -0
- package/lib/typescript/module/oidc/classifyError.d.ts +28 -0
- package/lib/typescript/module/oidc/classifyError.d.ts.map +1 -0
- package/lib/typescript/module/oidc/socialRouting.d.ts +22 -0
- package/lib/typescript/module/oidc/socialRouting.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/react/AuthProvider.d.ts +17 -0
- package/lib/typescript/module/react/AuthProvider.d.ts.map +1 -0
- package/lib/typescript/module/react/hooks.d.ts +42 -0
- package/lib/typescript/module/react/hooks.d.ts.map +1 -0
- package/lib/typescript/module/testing/conformance.d.ts +32 -0
- package/lib/typescript/module/testing/conformance.d.ts.map +1 -0
- package/lib/typescript/module/testing/index.d.ts +11 -0
- package/lib/typescript/module/testing/index.d.ts.map +1 -0
- package/lib/typescript/module/tokens/accessToken.d.ts +45 -0
- package/lib/typescript/module/tokens/accessToken.d.ts.map +1 -0
- package/lib/typescript/module/tokens/secureStore.d.ts +26 -0
- package/lib/typescript/module/tokens/secureStore.d.ts.map +1 -0
- package/package.json +68 -21
- package/skills/sdk-rn-auth/SKILL.md +35 -2
- package/src/config/types.ts +53 -5
- package/src/config/validateConfig.ts +32 -1
- package/src/core/authMachine.ts +40 -6
- package/src/core/profile.ts +6 -1
- package/src/core/session.ts +49 -3
- package/src/diagnostics/errors.ts +7 -5
- package/src/index.ts +7 -0
- package/src/oidc/authClient.ts +19 -1
- package/src/oidc/classifyError.ts +2 -1
- package/src/oidc/socialRouting.ts +3 -1
- package/src/react/hooks.ts +53 -14
- package/src/tokens/accessToken.ts +20 -9
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public hooks (LIB-002). Each reads the controller's state through
|
|
3
|
+
* `useSyncExternalStore` (rn-core's `useExternalStore`), selecting only what it
|
|
4
|
+
* returns, so `useProfile` does not re-render when only the status changes.
|
|
5
|
+
*/
|
|
6
|
+
import type { AccessTier, AccessTokenResult, AuthState, GlobalSignOutResult, PremiumGateResult, Profile, ProviderConfig, ProviderId } from '../config/types.js';
|
|
7
|
+
import type { AccessTokenRequestOptions } from '../tokens/accessToken.js';
|
|
8
|
+
import type { AuthMessageMap } from '../diagnostics/messageKeys.js';
|
|
9
|
+
export interface UseAuthResult {
|
|
10
|
+
state: AuthState;
|
|
11
|
+
signIn(providerId: ProviderId): Promise<AuthState>;
|
|
12
|
+
signOut(): Promise<void>;
|
|
13
|
+
signOutGlobal(): Promise<GlobalSignOutResult>;
|
|
14
|
+
restore(): Promise<AuthState>;
|
|
15
|
+
/** Whether a premium-only path is open. Changes nothing: show the denial where it happened. */
|
|
16
|
+
canAccessPremium(): PremiumGateResult;
|
|
17
|
+
/** @deprecated Use `canAccessPremium`; this moves the whole controller to `access-denied` (AUTH-12). */
|
|
18
|
+
requirePremium(): PremiumGateResult;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Any slice of the auth state; the component re-renders only when the slice
|
|
22
|
+
* changes by `isEqual` (default `Object.is`).
|
|
23
|
+
*/
|
|
24
|
+
export declare function useAuthSelector<Selected>(select: (state: AuthState) => Selected, isEqual?: (a: Selected, b: Selected) => boolean): Selected;
|
|
25
|
+
export declare function useAuth(): UseAuthResult;
|
|
26
|
+
/** This package's copy, with the app's `messages` overrides applied. */
|
|
27
|
+
export declare function useAuthMessages(): AuthMessageMap;
|
|
28
|
+
export declare function useProfile(): Profile | undefined;
|
|
29
|
+
export declare function useAccessTier(): AccessTier;
|
|
30
|
+
/** The first provider to present on the current platform (UX-004), or undefined. */
|
|
31
|
+
export declare function useSignInProvider(): ProviderConfig | undefined;
|
|
32
|
+
/** All available providers for the current platform, in configuration order (FR-003/FR-004). */
|
|
33
|
+
export declare function useSignInProviders(): ProviderConfig[];
|
|
34
|
+
export type UseAccessTokenStatus = 'idle' | 'loading' | 'success' | 'denied' | 'unavailable' | 'error';
|
|
35
|
+
export interface UseAccessTokenResult {
|
|
36
|
+
status: UseAccessTokenStatus;
|
|
37
|
+
token?: string | undefined;
|
|
38
|
+
/** `{ forceRefresh: true }` after a server refused the token this returned. */
|
|
39
|
+
request(options?: AccessTokenRequestOptions): Promise<AccessTokenResult>;
|
|
40
|
+
}
|
|
41
|
+
export declare function useAccessToken(resourceKey: string): UseAccessTokenResult;
|
|
42
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../../src/react/hooks.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EACX,UAAU,EACV,iBAAiB,EACjB,SAAS,EACT,mBAAmB,EACnB,iBAAiB,EACjB,OAAO,EACP,cAAc,EACd,UAAU,EACV,MAAM,oBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA4B,CAAC;AAEjE,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACnD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,aAAa,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC9C,OAAO,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC;IAC9B,+FAA+F;IAC/F,gBAAgB,IAAI,iBAAiB,CAAC;IACtC,wGAAwG;IACxG,cAAc,IAAI,iBAAiB,CAAC;CACpC;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EACvC,MAAM,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,QAAQ,EACtC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,KAAK,OAAO,GAC7C,QAAQ,CAGV;AAMD,wBAAgB,OAAO,IAAI,aAAa,CAYvC;AAED,wEAAwE;AACxE,wBAAgB,eAAe,IAAI,cAAc,CAEhD;AAED,wBAAgB,UAAU,IAAI,OAAO,GAAG,SAAS,CAEhD;AAED,wBAAgB,aAAa,IAAI,UAAU,CAE1C;AAED,oFAAoF;AACpF,wBAAgB,iBAAiB,IAAI,cAAc,GAAG,SAAS,CAG9D;AAED,gGAAgG;AAChG,wBAAgB,kBAAkB,IAAI,cAAc,EAAE,CAGrD;AAED,MAAM,MAAM,oBAAoB,GAC/B,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,aAAa,GAAG,OAAO,CAAC;AAErE,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,+EAA+E;IAC/E,OAAO,CAAC,OAAO,CAAC,EAAE,yBAAyB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;CACzE;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,oBAAoB,CAyCxE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What any `SecureTokenStore` and `OidcAuthClient` must do (IOS-12, AUTH-13).
|
|
3
|
+
*
|
|
4
|
+
* The same checks run against the in-memory doubles and against the keychain and
|
|
5
|
+
* app-auth adapters (native modules mocked in Jest, or for real from a demo
|
|
6
|
+
* screen on a device), so a test that drives a double proves something about the
|
|
7
|
+
* device path. Each check builds its own subject through a harness.
|
|
8
|
+
*/
|
|
9
|
+
import { type Check, type ConformanceReport } from '@chipmobilesdk/rn-core/testing';
|
|
10
|
+
import type { OidcAuthClient } from '../oidc/authClient.js';
|
|
11
|
+
import type { SecureTokenStore } from '../tokens/secureStore.js';
|
|
12
|
+
export interface SecureStoreHarness {
|
|
13
|
+
/** An empty store. */
|
|
14
|
+
create(): SecureTokenStore;
|
|
15
|
+
}
|
|
16
|
+
export interface OidcSubject {
|
|
17
|
+
readonly client: OidcAuthClient;
|
|
18
|
+
/** The identity provider answers sign-in and refresh with tokens for these claims. */
|
|
19
|
+
issues(claims: Record<string, unknown>): void;
|
|
20
|
+
/** The user closes the sign-in sheet. */
|
|
21
|
+
userCancels(): void;
|
|
22
|
+
/** The provider no longer accepts the refresh token. */
|
|
23
|
+
rejectsRefresh(): void;
|
|
24
|
+
}
|
|
25
|
+
export interface OidcHarness {
|
|
26
|
+
create(): OidcSubject;
|
|
27
|
+
}
|
|
28
|
+
export declare const secureTokenStoreChecks: readonly Check<SecureStoreHarness>[];
|
|
29
|
+
export declare const oidcAuthClientChecks: readonly Check<OidcHarness>[];
|
|
30
|
+
export declare function runSecureTokenStoreConformance(harness: SecureStoreHarness): Promise<ConformanceReport>;
|
|
31
|
+
export declare function runOidcAuthClientConformance(harness: OidcHarness): Promise<ConformanceReport>;
|
|
32
|
+
//# sourceMappingURL=conformance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance.d.ts","sourceRoot":"","sources":["../../../../src/testing/conformance.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAGN,KAAK,KAAK,EACV,KAAK,iBAAiB,EACtB,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAAuB,CAAC;AAE9D,MAAM,WAAW,kBAAkB;IAClC,sBAAsB;IACtB,MAAM,IAAI,gBAAgB,CAAC;CAC3B;AAED,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,sFAAsF;IACtF,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC9C,yCAAyC;IACzC,WAAW,IAAI,IAAI,CAAC;IACpB,wDAAwD;IACxD,cAAc,IAAI,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC3B,MAAM,IAAI,WAAW,CAAC;CACtB;AAKD,eAAO,MAAM,sBAAsB,EAAE,SAAS,KAAK,CAAC,kBAAkB,CAAC,EA+CtE,CAAC;AAaF,eAAO,MAAM,oBAAoB,EAAE,SAAS,KAAK,CAAC,WAAW,CAAC,EAkE7D,CAAC;AAEF,wBAAgB,8BAA8B,CAC7C,OAAO,EAAE,kBAAkB,GACzB,OAAO,CAAC,iBAAiB,CAAC,CAE5B;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAE7F"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@chipmobilesdk/rn-auth/testing`: test doubles and the port conformance suites.
|
|
3
|
+
*
|
|
4
|
+
* A separate entrypoint so neither can reach a production bundle.
|
|
5
|
+
*/
|
|
6
|
+
import type { SecureTokenStore } from '../tokens/secureStore.js';
|
|
7
|
+
/** A `SecureTokenStore` held in memory, for tests and for the conformance suite. */
|
|
8
|
+
export declare function createMemorySecureStore(seed?: Record<string, string>): SecureTokenStore;
|
|
9
|
+
export { oidcAuthClientChecks, runOidcAuthClientConformance, runSecureTokenStoreConformance, secureTokenStoreChecks, } from './conformance.js';
|
|
10
|
+
export type { OidcHarness, OidcSubject, SecureStoreHarness } from './conformance.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAAuB,CAAC;AAE9D,oFAAoF;AACpF,wBAAgB,uBAAuB,CAAC,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,gBAAgB,CAa3F;AAED,OAAO,EACN,oBAAoB,EACpB,4BAA4B,EAC5B,8BAA8B,EAC9B,sBAAsB,GACtB,MAAM,kBAAe,CAAC;AACvB,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,kBAAe,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-resource delegated access-token acquisition with an in-memory cache and
|
|
3
|
+
* silent refresh (FR-007, PERF-003, R4). Access tokens are never persisted to
|
|
4
|
+
* disk (FR-021) — only kept in memory keyed by resource until expiry.
|
|
5
|
+
*/
|
|
6
|
+
import type { AccessTokenResult, ResourceConfig } from '../config/types.js';
|
|
7
|
+
import type { OidcAuthClient } from '../oidc/authClient.js';
|
|
8
|
+
export interface AccessTokenManagerDeps {
|
|
9
|
+
authority: string;
|
|
10
|
+
clientId: string;
|
|
11
|
+
redirectUrl: string;
|
|
12
|
+
resources: ResourceConfig[];
|
|
13
|
+
authClient: OidcAuthClient;
|
|
14
|
+
getRefreshToken: () => string | null;
|
|
15
|
+
/**
|
|
16
|
+
* The session generation, bumped by every sign-in, sign-out and restore. A
|
|
17
|
+
* refresh that started under one generation and finished under another
|
|
18
|
+
* belongs to a session that no longer exists, so its result is discarded.
|
|
19
|
+
*/
|
|
20
|
+
sessionEpoch: () => number;
|
|
21
|
+
/**
|
|
22
|
+
* The provider rotated the refresh token. Resolves once the new token is
|
|
23
|
+
* live, and persisted where it can be: the old one may already be dead, so a
|
|
24
|
+
* restart must not come back with it.
|
|
25
|
+
*/
|
|
26
|
+
onRefreshTokenRotated: (token: string, epoch: number) => Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export interface AccessTokenRequestOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Ignore the cached token and ask the provider for a new one.
|
|
31
|
+
*
|
|
32
|
+
* For the caller that just had one rejected: a token the server refused is
|
|
33
|
+
* still unexpired by this device's clock, so asking again without this
|
|
34
|
+
* returns the same token and the retry is the same request (AI-01).
|
|
35
|
+
*/
|
|
36
|
+
forceRefresh?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface AccessTokenManager {
|
|
39
|
+
request(resourceKey: string, options?: AccessTokenRequestOptions): Promise<AccessTokenResult>;
|
|
40
|
+
/** Whether `request` would answer from the cache, without touching the network. */
|
|
41
|
+
hasFreshToken(resourceKey: string): boolean;
|
|
42
|
+
clear(): void;
|
|
43
|
+
}
|
|
44
|
+
export declare function createAccessTokenManager(deps: AccessTokenManagerDeps): AccessTokenManager;
|
|
45
|
+
//# sourceMappingURL=accessToken.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accessToken.d.ts","sourceRoot":"","sources":["../../../../src/tokens/accessToken.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,oBAAiB,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAoB,CAAC;AAUzD,MAAM,WAAW,sBAAsB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,UAAU,EAAE,cAAc,CAAC;IAC3B,eAAe,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACrC;;;;OAIG;IACH,YAAY,EAAE,MAAM,MAAM,CAAC;IAC3B;;;;OAIG;IACH,qBAAqB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,yBAAyB;IACzC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IAClC,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,yBAAyB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC9F,mFAAmF;IACnF,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5C,KAAK,IAAI,IAAI,CAAC;CACd;AAOD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,sBAAsB,GAAG,kBAAkB,CA4GzF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SecureTokenStore port + default react-native-keychain adapter (FR-021, R2).
|
|
3
|
+
*
|
|
4
|
+
* Refresh tokens and the session-restore artifact persist ONLY through this
|
|
5
|
+
* port, backed by platform secure storage (iOS Keychain / Android Keystore).
|
|
6
|
+
* Access tokens are never written here — they stay in memory (see accessToken).
|
|
7
|
+
* The default adapter loads the native module on its first read or write, so
|
|
8
|
+
* creating it, and unit tests that inject a fake, load no native code.
|
|
9
|
+
*/
|
|
10
|
+
/** Single key under which the persisted session artifact is stored. */
|
|
11
|
+
export declare const AUTH_STORAGE_KEY = "@chipmobilesdk/rn-auth/session";
|
|
12
|
+
export interface SecureTokenStore {
|
|
13
|
+
getItem(key: string): Promise<string | null>;
|
|
14
|
+
setItem(key: string, value: string): Promise<void>;
|
|
15
|
+
removeItem(key: string): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export interface KeychainSecureStoreOptions {
|
|
18
|
+
/** Optional Keychain access-control accessibility constant. */
|
|
19
|
+
accessible?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Default secure store backed by react-native-keychain. Each logical key maps to
|
|
23
|
+
* a Keychain "service" so multiple keys can coexist. Validated on device.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createKeychainSecureStore(options?: KeychainSecureStoreOptions): SecureTokenStore;
|
|
26
|
+
//# sourceMappingURL=secureStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secureStore.d.ts","sourceRoot":"","sources":["../../../../src/tokens/secureStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,uEAAuE;AACvE,eAAO,MAAM,gBAAgB,mCAAmC,CAAC;AAEjE,MAAM,WAAW,gBAAgB;IAChC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,0BAA0B;IAC1C,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACxC,OAAO,GAAE,0BAA+B,GACtC,gBAAgB,CAoBlB"}
|
package/package.json
CHANGED
|
@@ -1,30 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chipmobilesdk/rn-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
6
|
+
"source": "./src/index.ts",
|
|
7
|
+
"main": "./lib/commonjs/index.js",
|
|
8
|
+
"module": "./lib/module/index.js",
|
|
9
|
+
"types": "./lib/typescript/commonjs/index.d.ts",
|
|
8
10
|
"exports": {
|
|
9
11
|
".": {
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
|
|
12
|
+
"chipmobilesdk-source": "./src/index.ts",
|
|
13
|
+
"import": {
|
|
14
|
+
"types": "./lib/typescript/module/index.d.ts",
|
|
15
|
+
"default": "./lib/module/index.js"
|
|
16
|
+
},
|
|
17
|
+
"require": {
|
|
18
|
+
"types": "./lib/typescript/commonjs/index.d.ts",
|
|
19
|
+
"default": "./lib/commonjs/index.js"
|
|
20
|
+
}
|
|
13
21
|
},
|
|
14
22
|
"./app-auth": {
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
23
|
+
"chipmobilesdk-source": "./src/app-auth.ts",
|
|
24
|
+
"import": {
|
|
25
|
+
"types": "./lib/typescript/module/app-auth.d.ts",
|
|
26
|
+
"default": "./lib/module/app-auth.js"
|
|
27
|
+
},
|
|
28
|
+
"require": {
|
|
29
|
+
"types": "./lib/typescript/commonjs/app-auth.d.ts",
|
|
30
|
+
"default": "./lib/commonjs/app-auth.js"
|
|
31
|
+
}
|
|
18
32
|
},
|
|
19
33
|
"./keychain": {
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
34
|
+
"chipmobilesdk-source": "./src/keychain.ts",
|
|
35
|
+
"import": {
|
|
36
|
+
"types": "./lib/typescript/module/keychain.d.ts",
|
|
37
|
+
"default": "./lib/module/keychain.js"
|
|
38
|
+
},
|
|
39
|
+
"require": {
|
|
40
|
+
"types": "./lib/typescript/commonjs/keychain.d.ts",
|
|
41
|
+
"default": "./lib/commonjs/keychain.js"
|
|
42
|
+
}
|
|
23
43
|
},
|
|
24
44
|
"./testing": {
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
|
|
45
|
+
"chipmobilesdk-source": "./src/testing/index.ts",
|
|
46
|
+
"import": {
|
|
47
|
+
"types": "./lib/typescript/module/testing/index.d.ts",
|
|
48
|
+
"default": "./lib/module/testing/index.js"
|
|
49
|
+
},
|
|
50
|
+
"require": {
|
|
51
|
+
"types": "./lib/typescript/commonjs/testing/index.d.ts",
|
|
52
|
+
"default": "./lib/commonjs/testing/index.js"
|
|
53
|
+
}
|
|
28
54
|
}
|
|
29
55
|
},
|
|
30
56
|
"files": [
|
|
@@ -41,21 +67,23 @@
|
|
|
41
67
|
"src/testing",
|
|
42
68
|
"skills",
|
|
43
69
|
"README.md",
|
|
44
|
-
"CHANGELOG.md"
|
|
70
|
+
"CHANGELOG.md",
|
|
71
|
+
"lib"
|
|
45
72
|
],
|
|
46
73
|
"sideEffects": false,
|
|
47
74
|
"scripts": {
|
|
75
|
+
"build": "bob build",
|
|
48
76
|
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.test.json",
|
|
49
|
-
"pack:check": "npm pack --dry-run"
|
|
77
|
+
"pack:check": "npm pack --dry-run",
|
|
78
|
+
"prepack": "npm run build"
|
|
50
79
|
},
|
|
51
80
|
"dependencies": {
|
|
52
|
-
"@chipmobilesdk/rn-core": "^0.
|
|
81
|
+
"@chipmobilesdk/rn-core": "^0.2.0"
|
|
53
82
|
},
|
|
54
83
|
"peerDependencies": {
|
|
55
84
|
"react": ">=19",
|
|
56
|
-
"react-native": "
|
|
57
|
-
"react-native-
|
|
58
|
-
"react-native-keychain": ">=10"
|
|
85
|
+
"react-native-app-auth": "^8.0.0",
|
|
86
|
+
"react-native-keychain": "^10.0.0"
|
|
59
87
|
},
|
|
60
88
|
"peerDependenciesMeta": {
|
|
61
89
|
"react-native-app-auth": {
|
|
@@ -68,5 +96,24 @@
|
|
|
68
96
|
"publishConfig": {
|
|
69
97
|
"access": "public",
|
|
70
98
|
"registry": "https://registry.npmjs.org/"
|
|
99
|
+
},
|
|
100
|
+
"react-native-builder-bob": {
|
|
101
|
+
"source": "src",
|
|
102
|
+
"output": "lib",
|
|
103
|
+
"targets": [
|
|
104
|
+
[
|
|
105
|
+
"commonjs",
|
|
106
|
+
{
|
|
107
|
+
"esm": true
|
|
108
|
+
}
|
|
109
|
+
],
|
|
110
|
+
[
|
|
111
|
+
"module",
|
|
112
|
+
{
|
|
113
|
+
"esm": true
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
"typescript"
|
|
117
|
+
]
|
|
71
118
|
}
|
|
72
119
|
}
|
|
@@ -35,12 +35,27 @@ value.
|
|
|
35
35
|
|
|
36
36
|
## Public API
|
|
37
37
|
|
|
38
|
+
`createAuth(config)` is the entry point: one controller, one active account, created at
|
|
39
|
+
startup without loading any native module. Wrap the app in `<AuthProvider auth={auth}>`
|
|
40
|
+
and read state through the hooks — never re-implement the orchestration.
|
|
41
|
+
|
|
38
42
|
Runtime:
|
|
39
43
|
|
|
40
|
-
- `
|
|
44
|
+
- `createAuth` — the controller: `getState`, `subscribe`, `restore`, `signIn(providerId)`,
|
|
45
|
+
`signOut`, `signOutGlobal`, `requestAccessToken(resourceKey)`, `getAccessTier`,
|
|
46
|
+
`canAccessPremium`, `getPlatformProvider(s)`, `getMessages`
|
|
47
|
+
- Hooks: `useAuth`, `useProfile`, `useAccessTier`, `useAccessToken`, `useAuthMessages`,
|
|
48
|
+
`useSignInProvider`, `useSignInProviders`, `useAuthSelector`
|
|
49
|
+
- `AuthProvider`
|
|
50
|
+
- `createAppAuthClient(...)` — the OIDC client; pass `serviceConfiguration` to skip a
|
|
51
|
+
`.well-known` fetch on every call
|
|
41
52
|
- `createKeychainSecureStore(...)` — token persistence; `AUTH_STORAGE_KEY`
|
|
42
|
-
- `evaluateAccessTier(...)`, `failClosedTier` — feature gating
|
|
53
|
+
- `evaluateAccessTier(...)`, `failClosedTier` — feature gating, denying when unknown
|
|
54
|
+
- `mapClaimsToProfile`, `describeProviderIdentity` — claims to a profile, and whether an
|
|
55
|
+
address is direct, Apple's private relay, or absent
|
|
43
56
|
- `AuthProvider` — React context provider
|
|
57
|
+
- `AuthError`, `AuthConfigError`, `isAuthError`, `redact`, `authMessages`,
|
|
58
|
+
`resolveMessages`, `DEFAULT_MESSAGES`
|
|
44
59
|
|
|
45
60
|
Testing (`@chipmobilesdk/rn-auth/testing`): `createMemorySecureStore`,
|
|
46
61
|
`runSecureTokenStoreConformance`, `runOidcAuthClientConformance` — README `## Testing`.
|
|
@@ -51,6 +66,24 @@ Types: `SecureTokenStore`, `AuthProviderProps`, `AuthErrorCode`, `AuthErrorAudie
|
|
|
51
66
|
`AuthErrorAudience` distinguishes what may be shown to a user from what is diagnostic
|
|
52
67
|
only — never render a diagnostic-audience message in the UI.
|
|
53
68
|
|
|
69
|
+
## Three things that bite
|
|
70
|
+
|
|
71
|
+
- **`signOutGlobal()` can report `{ global: 'failed' }`.** Local state is cleared either
|
|
72
|
+
way, but the tenant session may still be alive, so the next sign-in can complete with
|
|
73
|
+
no password prompt. Say so in the UI rather than claiming the user is fully signed out.
|
|
74
|
+
- **`requestAccessToken(resourceKey)` answers with a status, not a throw.** Handle
|
|
75
|
+
`denied` and `unavailable` as states; only `success` carries a token. `useAccessToken`
|
|
76
|
+
starts from `idle` again when the resource key changes.
|
|
77
|
+
- **iOS needs the native redirect set up** (URL scheme matching `redirectUrl`, and the
|
|
78
|
+
Apple provider configured in the tenant). A `redirect-mismatch` error is that, not a
|
|
79
|
+
code fault — see the prerequisite checklist.
|
|
80
|
+
|
|
81
|
+
## A premium gate is a question
|
|
82
|
+
|
|
83
|
+
Use `canAccessPremium()` and render the denial where it happened.
|
|
84
|
+
`requirePremium()` is deprecated: it also moves the whole controller to
|
|
85
|
+
`access-denied`, which every screen reading `state.status` then sees.
|
|
86
|
+
|
|
54
87
|
## Where the detail lives
|
|
55
88
|
|
|
56
89
|
Read one section of `node_modules/@chipmobilesdk/rn-auth/README.md`:
|
package/src/config/types.ts
CHANGED
|
@@ -3,23 +3,44 @@
|
|
|
3
3
|
* specs/003-mobile-entra-auth/data-model.md.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type { AccessTokenRequestOptions } from '../tokens/accessToken';
|
|
6
7
|
import type { SecureTokenStore } from '../tokens/secureStore';
|
|
7
8
|
import type { OidcAuthClient } from '../oidc/authClient';
|
|
8
9
|
import type { AuthError, AuthErrorCode } from '../diagnostics/errors';
|
|
9
10
|
import type { AuthMessageKey, AuthMessageMap } from '../diagnostics/messageKeys';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
/** The providers this package has copy for out of the box. */
|
|
13
|
+
export type KnownProviderId = 'google' | 'apple' | 'facebook';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A provider id. Open on purpose: routing is `domain_hint` and nothing else, so
|
|
17
|
+
* adding an IdP the tenant federates — Microsoft account, an email one-time
|
|
18
|
+
* code — is a change to an app's config, not a release of this package
|
|
19
|
+
* (AUTH-14). `KnownProviderId` keeps the three familiar ones in autocomplete.
|
|
20
|
+
*/
|
|
21
|
+
export type ProviderId = KnownProviderId | (string & {});
|
|
12
22
|
export type DevicePlatform = 'ios' | 'android';
|
|
13
23
|
|
|
24
|
+
export const DEVICE_PLATFORMS: readonly DevicePlatform[] = ['ios', 'android'];
|
|
25
|
+
|
|
14
26
|
/** Provider Setup Configuration — non-secret public values only (FR-016/FR-017). */
|
|
15
27
|
export interface ProviderConfig {
|
|
16
28
|
id: ProviderId;
|
|
17
29
|
platforms: DevicePlatform[];
|
|
18
30
|
/** External ID `domain_hint` routing the user flow to this IdP (R3). */
|
|
19
31
|
domainHint: string;
|
|
20
|
-
/**
|
|
21
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Message key for the button label (Continue with Google / Apple). A key of
|
|
34
|
+
* this package's catalogue, or one of the app's own for a provider it added.
|
|
35
|
+
*/
|
|
36
|
+
labelKey: AuthMessageKey | (string & {});
|
|
22
37
|
available?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Extra OIDC authorize parameters for this provider, merged after
|
|
40
|
+
* `domain_hint` — `prompt`, `login_hint`, whatever the tenant's user flow
|
|
41
|
+
* expects. `domain_hint` itself cannot be overridden here.
|
|
42
|
+
*/
|
|
43
|
+
additionalParameters?: Record<string, string>;
|
|
23
44
|
}
|
|
24
45
|
|
|
25
46
|
/** One configured protected resource for delegated token requests (FR-007). */
|
|
@@ -55,6 +76,11 @@ export interface AuthConfig {
|
|
|
55
76
|
accessTierMapping: AccessTierMapping;
|
|
56
77
|
secureStore: SecureTokenStore;
|
|
57
78
|
authClient?: OidcAuthClient;
|
|
79
|
+
/**
|
|
80
|
+
* Copy overrides for this package's own message keys, resolved over the
|
|
81
|
+
* English defaults and readable through `auth.getMessages()` and
|
|
82
|
+
* `useAuthMessages()`.
|
|
83
|
+
*/
|
|
58
84
|
messages?: Partial<AuthMessageMap>;
|
|
59
85
|
}
|
|
60
86
|
|
|
@@ -81,6 +107,11 @@ export interface Profile {
|
|
|
81
107
|
displayName?: string | undefined;
|
|
82
108
|
email?: string | undefined;
|
|
83
109
|
provider: string;
|
|
110
|
+
/**
|
|
111
|
+
* The id-token claims this profile was mapped from. Empty after a restore
|
|
112
|
+
* that could not reach the provider: the stored artifact holds the identity,
|
|
113
|
+
* not the token (AUTH-23). Read `restore` on the state to tell the two apart.
|
|
114
|
+
*/
|
|
84
115
|
claims: Record<string, unknown>;
|
|
85
116
|
}
|
|
86
117
|
|
|
@@ -143,10 +174,27 @@ export interface Auth {
|
|
|
143
174
|
signIn(providerId: ProviderId): Promise<AuthState>;
|
|
144
175
|
signOut(): Promise<void>;
|
|
145
176
|
signOutGlobal(): Promise<GlobalSignOutResult>;
|
|
146
|
-
|
|
177
|
+
/**
|
|
178
|
+
* A delegated token for one configured resource. Answers from the in-memory
|
|
179
|
+
* cache while the token is unexpired; `forceRefresh` skips and drops that
|
|
180
|
+
* cache, which is what a caller whose token was just rejected needs.
|
|
181
|
+
*/
|
|
182
|
+
requestAccessToken(
|
|
183
|
+
resourceKey: string,
|
|
184
|
+
options?: AccessTokenRequestOptions,
|
|
185
|
+
): Promise<AccessTokenResult>;
|
|
147
186
|
getAccessTier(): AccessTier;
|
|
148
|
-
/**
|
|
187
|
+
/** Whether a premium-only path is open, and the tier behind the answer. Reads nothing else and changes nothing (FR-011). */
|
|
188
|
+
canAccessPremium(): PremiumGateResult;
|
|
189
|
+
/**
|
|
190
|
+
* @deprecated Use `canAccessPremium` and show the denial where it happened.
|
|
191
|
+
* This also moves the whole controller to `access-denied`, which every screen
|
|
192
|
+
* reading `status` then sees, and only a `restore` or a sign-in leaves
|
|
193
|
+
* (AUTH-12). Kept for 0.4.x.
|
|
194
|
+
*/
|
|
149
195
|
requirePremium(): PremiumGateResult;
|
|
196
|
+
/** This package's copy: the English defaults with the app's `messages` over them. */
|
|
197
|
+
getMessages(): AuthMessageMap;
|
|
150
198
|
/** The first available provider to present on the current platform (UX-004), or undefined. */
|
|
151
199
|
getPlatformProvider(): ProviderConfig | undefined;
|
|
152
200
|
/** All available providers for the current platform, in configuration order (FR-003/FR-004). */
|
|
@@ -4,13 +4,21 @@
|
|
|
4
4
|
* Covers the auth-config.md Section A–F rules.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type
|
|
7
|
+
import { DEVICE_PLATFORMS, type AuthConfig } from './types';
|
|
8
8
|
import { AuthConfigError } from '../diagnostics/errors';
|
|
9
9
|
|
|
10
10
|
function hasScheme(url: string): boolean {
|
|
11
11
|
return /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/** https, or http on a development machine. */
|
|
15
|
+
function isSecureAuthority(url: string): boolean {
|
|
16
|
+
if (/^https:/i.test(url)) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
return /^http:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?(\/|$)/i.test(url);
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
export function validateConfig(config: AuthConfig): void {
|
|
15
23
|
// Section A — tenant/client settings
|
|
16
24
|
if (!config.authority || !config.authority.trim()) {
|
|
@@ -19,6 +27,14 @@ export function validateConfig(config: AuthConfig): void {
|
|
|
19
27
|
if (!hasScheme(config.authority)) {
|
|
20
28
|
throw new AuthConfigError('auth.error.configuration', '"authority" must be an absolute URL');
|
|
21
29
|
}
|
|
30
|
+
// Tokens travel over this. A tenant reachable over plaintext http is a
|
|
31
|
+
// configuration mistake, not a deployment choice — except on a dev machine.
|
|
32
|
+
if (!isSecureAuthority(config.authority)) {
|
|
33
|
+
throw new AuthConfigError(
|
|
34
|
+
'auth.error.configuration',
|
|
35
|
+
'"authority" must use https (http is accepted only for localhost)',
|
|
36
|
+
);
|
|
37
|
+
}
|
|
22
38
|
if (!config.clientId || !config.clientId.trim()) {
|
|
23
39
|
throw new AuthConfigError('auth.error.configuration', 'Missing "clientId"');
|
|
24
40
|
}
|
|
@@ -66,6 +82,21 @@ export function validateConfig(config: AuthConfig): void {
|
|
|
66
82
|
`Provider "${provider.id}" must declare at least one platform`,
|
|
67
83
|
);
|
|
68
84
|
}
|
|
85
|
+
for (const platform of provider.platforms) {
|
|
86
|
+
if (!DEVICE_PLATFORMS.includes(platform)) {
|
|
87
|
+
// A typo here hides the provider on every device rather than failing.
|
|
88
|
+
throw new AuthConfigError(
|
|
89
|
+
'auth.error.configuration',
|
|
90
|
+
`Provider "${provider.id}" declares unknown platform "${String(platform)}"`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (typeof provider.labelKey !== 'string' || !provider.labelKey.trim()) {
|
|
95
|
+
throw new AuthConfigError(
|
|
96
|
+
'auth.error.configuration',
|
|
97
|
+
`Provider "${provider.id}" is missing a labelKey`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
69
100
|
}
|
|
70
101
|
|
|
71
102
|
// Section D — resources
|
package/src/core/authMachine.ts
CHANGED
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
shallowEqual,
|
|
17
17
|
} from '@chipmobilesdk/rn-core';
|
|
18
18
|
|
|
19
|
+
import type { AuthMessageMap } from '../diagnostics/messageKeys';
|
|
20
|
+
import { resolveMessages } from '../diagnostics/messageKeys';
|
|
19
21
|
import type {
|
|
20
22
|
Auth,
|
|
21
23
|
AuthConfig,
|
|
@@ -37,7 +39,7 @@ import {
|
|
|
37
39
|
} from '../oidc/socialRouting';
|
|
38
40
|
import { mapClaimsToProfile } from './profile';
|
|
39
41
|
import { evaluateAccessTier, failClosedTier } from '../access/accessTier';
|
|
40
|
-
import { restoreSession, type PersistedSession } from './session';
|
|
42
|
+
import { restoreSession, sameSession, storedProfileFrom, type PersistedSession } from './session';
|
|
41
43
|
import { AUTH_STORAGE_KEY } from '../tokens/secureStore';
|
|
42
44
|
import { createAccessTokenManager } from '../tokens/accessToken';
|
|
43
45
|
import { AuthError, toAuthError, type AuthErrorCode } from '../diagnostics/errors';
|
|
@@ -62,6 +64,8 @@ export function createAuthMachine(config: AuthConfig, options: CreateAuthOptions
|
|
|
62
64
|
const secureStore = config.secureStore;
|
|
63
65
|
const authClient = config.authClient ?? createAppAuthClient();
|
|
64
66
|
const resources = config.resources ?? [];
|
|
67
|
+
// Declared since 0.1.0 and read by nothing until now (AUTH-15).
|
|
68
|
+
const messages = resolveMessages(config.messages);
|
|
65
69
|
|
|
66
70
|
let state: AuthState = Object.freeze({ status: 'loading', restore: 'pending' });
|
|
67
71
|
let refreshToken: string | null = null;
|
|
@@ -84,12 +88,29 @@ export function createAuthMachine(config: AuthConfig, options: CreateAuthOptions
|
|
|
84
88
|
return next;
|
|
85
89
|
};
|
|
86
90
|
|
|
87
|
-
|
|
91
|
+
/** The artifact as last written or read, so an identical write is skipped. */
|
|
92
|
+
let storedSession: PersistedSession | null = null;
|
|
93
|
+
|
|
94
|
+
const writeSession = (
|
|
95
|
+
session: { refreshToken: string; profile: Profile; idToken?: string | undefined },
|
|
96
|
+
epoch: number,
|
|
97
|
+
): Promise<void> =>
|
|
88
98
|
enqueueStoreOp(async () => {
|
|
89
99
|
if (epoch !== sessionEpoch) {
|
|
90
100
|
return; // The session this belonged to has already ended.
|
|
91
101
|
}
|
|
92
|
-
|
|
102
|
+
const artifact: PersistedSession = {
|
|
103
|
+
refreshToken: session.refreshToken,
|
|
104
|
+
profile: storedProfileFrom(session.profile),
|
|
105
|
+
idToken: session.idToken,
|
|
106
|
+
};
|
|
107
|
+
// A restore that rotated nothing used to write the Keychain again on
|
|
108
|
+
// every launch, on the startup path (AUTH-23).
|
|
109
|
+
if (storedSession && sameSession(storedSession, artifact)) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
await secureStore.setItem(AUTH_STORAGE_KEY, JSON.stringify(artifact));
|
|
113
|
+
storedSession = artifact;
|
|
93
114
|
});
|
|
94
115
|
|
|
95
116
|
const tokenManager = createAccessTokenManager({
|
|
@@ -155,6 +176,7 @@ export function createAuthMachine(config: AuthConfig, options: CreateAuthOptions
|
|
|
155
176
|
sessionEpoch += 1;
|
|
156
177
|
refreshToken = null;
|
|
157
178
|
idToken = null;
|
|
179
|
+
storedSession = null;
|
|
158
180
|
tokenManager.clear();
|
|
159
181
|
await enqueueStoreOp(() => secureStore.removeItem(AUTH_STORAGE_KEY));
|
|
160
182
|
}
|
|
@@ -193,6 +215,8 @@ export function createAuthMachine(config: AuthConfig, options: CreateAuthOptions
|
|
|
193
215
|
return getState();
|
|
194
216
|
}
|
|
195
217
|
|
|
218
|
+
storedSession = result.persisted ?? null;
|
|
219
|
+
|
|
196
220
|
if (result.outcome === 'restored' && result.profile && result.refreshToken) {
|
|
197
221
|
refreshToken = result.refreshToken;
|
|
198
222
|
idToken = result.idToken ?? null;
|
|
@@ -334,16 +358,17 @@ export function createAuthMachine(config: AuthConfig, options: CreateAuthOptions
|
|
|
334
358
|
return result;
|
|
335
359
|
},
|
|
336
360
|
|
|
337
|
-
async requestAccessToken(resourceKey) {
|
|
361
|
+
async requestAccessToken(resourceKey, tokenOptions) {
|
|
338
362
|
const epoch = sessionEpoch;
|
|
339
363
|
// Only a real network refresh is a `token-refreshing` phase; a cache hit
|
|
340
364
|
// would otherwise flash every subscriber twice for nothing.
|
|
341
365
|
const refreshing =
|
|
342
|
-
state.status === 'authenticated' &&
|
|
366
|
+
state.status === 'authenticated' &&
|
|
367
|
+
(tokenOptions?.forceRefresh === true || !tokenManager.hasFreshToken(resourceKey));
|
|
343
368
|
if (refreshing) {
|
|
344
369
|
setState({ status: 'token-refreshing' });
|
|
345
370
|
}
|
|
346
|
-
const result = await tokenManager.request(resourceKey);
|
|
371
|
+
const result = await tokenManager.request(resourceKey, tokenOptions);
|
|
347
372
|
// Restored only if nothing else moved the session meanwhile: a sign-out
|
|
348
373
|
// during the refresh used to be overwritten back to `authenticated`.
|
|
349
374
|
if (refreshing && epoch === sessionEpoch && state.status === 'token-refreshing') {
|
|
@@ -356,6 +381,11 @@ export function createAuthMachine(config: AuthConfig, options: CreateAuthOptions
|
|
|
356
381
|
return state.accessTier ?? failClosedTier();
|
|
357
382
|
},
|
|
358
383
|
|
|
384
|
+
canAccessPremium(): PremiumGateResult {
|
|
385
|
+
const tier = state.accessTier ?? failClosedTier();
|
|
386
|
+
return { allowed: tier.tier === 'premium' && tier.verification === 'verified', tier };
|
|
387
|
+
},
|
|
388
|
+
|
|
359
389
|
requirePremium(): PremiumGateResult {
|
|
360
390
|
const tier = state.accessTier ?? failClosedTier();
|
|
361
391
|
const allowed = tier.tier === 'premium' && tier.verification === 'verified';
|
|
@@ -365,6 +395,10 @@ export function createAuthMachine(config: AuthConfig, options: CreateAuthOptions
|
|
|
365
395
|
return { allowed, tier };
|
|
366
396
|
},
|
|
367
397
|
|
|
398
|
+
getMessages(): AuthMessageMap {
|
|
399
|
+
return messages;
|
|
400
|
+
},
|
|
401
|
+
|
|
368
402
|
getPlatformProvider() {
|
|
369
403
|
return defaultProviderForPlatform(config, platform);
|
|
370
404
|
},
|