@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
package/src/core/profile.ts
CHANGED
|
@@ -41,7 +41,12 @@ export function mapClaimsToProfile(
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Describe the external provider identity behind the profile (FR-004 edge
|
|
46
|
+
* cases). `emailBehavior` is what a screen needs to explain an address the user
|
|
47
|
+
* did not choose: Apple's private relay forwards mail and is not the address
|
|
48
|
+
* they type anywhere else.
|
|
49
|
+
*/
|
|
45
50
|
export function describeProviderIdentity(
|
|
46
51
|
claims: Record<string, unknown>,
|
|
47
52
|
providerId?: ProviderId,
|
package/src/core/session.ts
CHANGED
|
@@ -22,13 +22,50 @@ import { mapClaimsToProfile } from './profile';
|
|
|
22
22
|
import { evaluateAccessTier, failClosedTier } from '../access/accessTier';
|
|
23
23
|
import { isAuthError } from '../diagnostics/errors';
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* The identity in the stored artifact: who, not what the token said.
|
|
27
|
+
*
|
|
28
|
+
* The whole claim set used to be written to the Keychain on every launch — all
|
|
29
|
+
* of it, groups included — for a value nothing reads back: the access tier is
|
|
30
|
+
* recomputed from fresh claims, and a restore that cannot reach the provider
|
|
31
|
+
* fails closed anyway (AUTH-23).
|
|
32
|
+
*/
|
|
33
|
+
export type StoredProfile = Omit<Profile, 'claims'>;
|
|
34
|
+
|
|
25
35
|
export interface PersistedSession {
|
|
26
36
|
refreshToken: string;
|
|
27
|
-
profile:
|
|
37
|
+
profile: StoredProfile;
|
|
28
38
|
/** The id token hint for end-session (AUTH-04). Absent in artifacts written before 0.2.2. */
|
|
29
39
|
idToken?: string | undefined;
|
|
30
40
|
}
|
|
31
41
|
|
|
42
|
+
/** The artifact's identity as a profile. Artifacts written before 0.4.0 carry claims; they are kept. */
|
|
43
|
+
export function profileFromStored(stored: StoredProfile | Profile): Profile {
|
|
44
|
+
return { ...stored, claims: 'claims' in stored ? (stored.claims ?? {}) : {} };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** What goes to the store: everything but the claims. */
|
|
48
|
+
export function storedProfileFrom(profile: Profile): StoredProfile {
|
|
49
|
+
return {
|
|
50
|
+
tenantUserId: profile.tenantUserId,
|
|
51
|
+
displayName: profile.displayName,
|
|
52
|
+
email: profile.email,
|
|
53
|
+
provider: profile.provider,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** True when the artifact would be byte-identical, so the write can be skipped. */
|
|
58
|
+
export function sameSession(a: PersistedSession, b: PersistedSession): boolean {
|
|
59
|
+
return (
|
|
60
|
+
a.refreshToken === b.refreshToken &&
|
|
61
|
+
a.idToken === b.idToken &&
|
|
62
|
+
a.profile.tenantUserId === b.profile.tenantUserId &&
|
|
63
|
+
a.profile.displayName === b.profile.displayName &&
|
|
64
|
+
a.profile.email === b.profile.email &&
|
|
65
|
+
a.profile.provider === b.profile.provider
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
32
69
|
export interface RestoreDeps {
|
|
33
70
|
secureStore: SecureTokenStore;
|
|
34
71
|
authority: string;
|
|
@@ -42,6 +79,8 @@ export interface RestoreDeps {
|
|
|
42
79
|
export interface RestoreResult {
|
|
43
80
|
outcome: RestoreStatus;
|
|
44
81
|
profile?: Profile;
|
|
82
|
+
/** The artifact this restore read, so a rewrite that changes nothing can be skipped. */
|
|
83
|
+
persisted?: PersistedSession;
|
|
45
84
|
accessTier?: AccessTier;
|
|
46
85
|
refreshToken?: string;
|
|
47
86
|
idToken?: string | undefined;
|
|
@@ -82,7 +121,13 @@ export async function restoreSession(deps: RestoreDeps): Promise<RestoreResult>
|
|
|
82
121
|
const hasClaims = Object.keys(tokens.claims).length > 0;
|
|
83
122
|
return {
|
|
84
123
|
outcome: 'restored',
|
|
85
|
-
|
|
124
|
+
persisted,
|
|
125
|
+
// The provider id came from the sign-in and is not in the claims: a
|
|
126
|
+
// refresh answered `idp: 'google.com'`, so a restored profile used to
|
|
127
|
+
// report a different provider than the same session did before (AUTH-10).
|
|
128
|
+
profile: hasClaims
|
|
129
|
+
? mapClaimsToProfile(tokens.claims, persisted.profile.provider)
|
|
130
|
+
: profileFromStored(persisted.profile),
|
|
86
131
|
accessTier: hasClaims ? evaluateAccessTier(tokens.claims, deps.mapping) : failClosedTier(),
|
|
87
132
|
refreshToken: tokens.refreshToken || persisted.refreshToken,
|
|
88
133
|
idToken: tokens.idToken || persisted.idToken,
|
|
@@ -96,7 +141,8 @@ export async function restoreSession(deps: RestoreDeps): Promise<RestoreResult>
|
|
|
96
141
|
// read-only and fail closed on entitlement; the next restore tries again.
|
|
97
142
|
return {
|
|
98
143
|
outcome: 'offline',
|
|
99
|
-
|
|
144
|
+
persisted,
|
|
145
|
+
profile: profileFromStored(persisted.profile),
|
|
100
146
|
accessTier: failClosedTier(),
|
|
101
147
|
refreshToken: persisted.refreshToken,
|
|
102
148
|
idToken: persisted.idToken,
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
import { COMMON_PATTERNS, SdkError, redactPatterns } from '@chipmobilesdk/rn-core';
|
|
9
9
|
|
|
10
|
+
import type { AuthMessageKey } from './messageKeys';
|
|
11
|
+
|
|
10
12
|
export type AuthErrorCode =
|
|
11
13
|
| 'configuration-error'
|
|
12
14
|
| 'provider-cancelled'
|
|
@@ -14,14 +16,14 @@ export type AuthErrorCode =
|
|
|
14
16
|
| 'token-acquisition-failed'
|
|
15
17
|
| 'session-expired'
|
|
16
18
|
| 'consent-denied'
|
|
17
|
-
| 'entitlement-unverified'
|
|
18
19
|
| 'network-unavailable';
|
|
19
20
|
|
|
20
21
|
export type AuthErrorAudience = 'user' | 'developer';
|
|
21
22
|
|
|
22
23
|
export interface AuthErrorArgs {
|
|
23
24
|
code: AuthErrorCode;
|
|
24
|
-
|
|
25
|
+
/** A key of this package's catalogue: the copy an app shows for this error. */
|
|
26
|
+
messageKey: AuthMessageKey;
|
|
25
27
|
audience?: AuthErrorAudience;
|
|
26
28
|
detail?: string | undefined;
|
|
27
29
|
}
|
|
@@ -52,7 +54,7 @@ const RETRYABLE: ReadonlySet<AuthErrorCode> = new Set<AuthErrorCode>(['network-u
|
|
|
52
54
|
/** The package's error; an `SdkError` (`@chipmobilesdk/rn-core`) with `pkg: 'rn-auth'`. */
|
|
53
55
|
export class AuthError extends SdkError<AuthErrorCode> {
|
|
54
56
|
readonly audience: AuthErrorAudience;
|
|
55
|
-
readonly messageKey:
|
|
57
|
+
readonly messageKey: AuthMessageKey;
|
|
56
58
|
readonly detail?: string | undefined;
|
|
57
59
|
|
|
58
60
|
constructor(args: AuthErrorArgs) {
|
|
@@ -70,7 +72,7 @@ export class AuthError extends SdkError<AuthErrorCode> {
|
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
export class AuthConfigError extends AuthError {
|
|
73
|
-
constructor(messageKey:
|
|
75
|
+
constructor(messageKey: AuthMessageKey, detail?: string) {
|
|
74
76
|
super({ code: 'configuration-error', messageKey, audience: 'developer', detail });
|
|
75
77
|
this.name = 'AuthConfigError';
|
|
76
78
|
}
|
|
@@ -84,7 +86,7 @@ export function isAuthError(value: unknown): value is AuthError {
|
|
|
84
86
|
export function toAuthError(
|
|
85
87
|
value: unknown,
|
|
86
88
|
fallbackCode: AuthErrorCode = 'token-acquisition-failed',
|
|
87
|
-
fallbackMessageKey = 'auth.error.token',
|
|
89
|
+
fallbackMessageKey: AuthMessageKey = 'auth.error.token',
|
|
88
90
|
): AuthError {
|
|
89
91
|
if (isAuthError(value)) {
|
|
90
92
|
return value;
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,7 @@ export type {
|
|
|
25
25
|
ProviderIdentity,
|
|
26
26
|
ProviderConfig,
|
|
27
27
|
ProviderId,
|
|
28
|
+
KnownProviderId,
|
|
28
29
|
DevicePlatform,
|
|
29
30
|
ResourceConfig,
|
|
30
31
|
ClaimMatcher,
|
|
@@ -37,11 +38,16 @@ export type {
|
|
|
37
38
|
} from './config/types';
|
|
38
39
|
|
|
39
40
|
// Ports + default adapters
|
|
41
|
+
export { describeProviderIdentity, mapClaimsToProfile } from './core/profile';
|
|
42
|
+
|
|
43
|
+
export type { AccessTokenRequestOptions } from './tokens/accessToken';
|
|
40
44
|
export type { SecureTokenStore } from './tokens/secureStore';
|
|
41
45
|
export { createKeychainSecureStore, AUTH_STORAGE_KEY } from './tokens/secureStore';
|
|
42
46
|
export type {
|
|
43
47
|
OidcAuthClient,
|
|
44
48
|
OidcTokens,
|
|
49
|
+
OidcServiceConfiguration,
|
|
50
|
+
AppAuthClientOptions,
|
|
45
51
|
AuthorizeParams,
|
|
46
52
|
RefreshParams,
|
|
47
53
|
EndSessionParams,
|
|
@@ -63,6 +69,7 @@ export { useAuthSelector } from './react/hooks';
|
|
|
63
69
|
export type { AuthProviderProps } from './react/AuthProvider';
|
|
64
70
|
export {
|
|
65
71
|
useAuth,
|
|
72
|
+
useAuthMessages,
|
|
66
73
|
useProfile,
|
|
67
74
|
useAccessTier,
|
|
68
75
|
useAccessToken,
|
package/src/oidc/authClient.ts
CHANGED
|
@@ -116,9 +116,24 @@ function base64UrlDecode(input: string): string {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/** The endpoints discovery would have returned, when an app already knows them. */
|
|
120
|
+
export interface OidcServiceConfiguration {
|
|
121
|
+
authorizationEndpoint: string;
|
|
122
|
+
tokenEndpoint: string;
|
|
123
|
+
endSessionEndpoint?: string;
|
|
124
|
+
revocationEndpoint?: string;
|
|
125
|
+
registrationEndpoint?: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
119
128
|
export interface AppAuthClientOptions {
|
|
120
129
|
/** When true, the OIDC discovery document is fetched from `${authority}`. */
|
|
121
130
|
useDiscovery?: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Static endpoints, used instead of discovery. Every authorize, refresh and
|
|
133
|
+
* sign-out otherwise costs the native module one `.well-known` round-trip,
|
|
134
|
+
* including the refresh on the startup path (AUTH-08).
|
|
135
|
+
*/
|
|
136
|
+
serviceConfiguration?: OidcServiceConfiguration;
|
|
122
137
|
}
|
|
123
138
|
|
|
124
139
|
interface AppAuthModule {
|
|
@@ -165,7 +180,9 @@ const AppAuth: AppAuthModule = {
|
|
|
165
180
|
* client). Validated on device (R8).
|
|
166
181
|
*/
|
|
167
182
|
export function createAppAuthClient(options: AppAuthClientOptions = {}): OidcAuthClient {
|
|
168
|
-
const
|
|
183
|
+
const serviceConfiguration = options.serviceConfiguration;
|
|
184
|
+
// Discovery is pointless once the endpoints are given, whatever was asked for.
|
|
185
|
+
const useDiscovery = serviceConfiguration ? false : (options.useDiscovery ?? true);
|
|
169
186
|
|
|
170
187
|
const baseConfig = (
|
|
171
188
|
authority: string,
|
|
@@ -174,6 +191,7 @@ export function createAppAuthClient(options: AppAuthClientOptions = {}): OidcAut
|
|
|
174
191
|
scopes: string[],
|
|
175
192
|
) => ({
|
|
176
193
|
issuer: useDiscovery ? authority : undefined,
|
|
194
|
+
...(serviceConfiguration ? { serviceConfiguration } : {}),
|
|
177
195
|
clientId,
|
|
178
196
|
redirectUrl,
|
|
179
197
|
scopes,
|
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { AuthError, type AuthErrorCode } from '../diagnostics/errors';
|
|
15
|
+
import type { AuthMessageKey } from '../diagnostics/messageKeys';
|
|
15
16
|
|
|
16
17
|
export interface ClassificationRule {
|
|
17
18
|
readonly code: AuthErrorCode;
|
|
18
|
-
readonly messageKey:
|
|
19
|
+
readonly messageKey: AuthMessageKey;
|
|
19
20
|
/** Matched against the native error's `code` and `message`, case-insensitively. */
|
|
20
21
|
readonly pattern: RegExp;
|
|
21
22
|
}
|
|
@@ -37,7 +37,9 @@ export function resolveProvider(
|
|
|
37
37
|
|
|
38
38
|
/** Build the OIDC additional parameters that route to the chosen IdP. */
|
|
39
39
|
export function buildAdditionalParameters(provider: ProviderConfig): Record<string, string> {
|
|
40
|
-
|
|
40
|
+
// The routing hint last: a provider's own parameters cannot redirect the flow
|
|
41
|
+
// to a different IdP than the one the user tapped.
|
|
42
|
+
return { ...provider.additionalParameters, domain_hint: provider.domainHint };
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
/**
|
package/src/react/hooks.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { useExternalStore } from '@chipmobilesdk/rn-core/react';
|
|
8
|
-
import { useCallback, useState } from 'react';
|
|
8
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
9
9
|
import { useAuthInstance } from './AuthProvider';
|
|
10
10
|
import type {
|
|
11
11
|
AccessTier,
|
|
@@ -18,6 +18,8 @@ import type {
|
|
|
18
18
|
ProviderId,
|
|
19
19
|
} from '../config/types';
|
|
20
20
|
import { failClosedTier } from '../access/accessTier';
|
|
21
|
+
import type { AccessTokenRequestOptions } from '../tokens/accessToken';
|
|
22
|
+
import type { AuthMessageMap } from '../diagnostics/messageKeys';
|
|
21
23
|
|
|
22
24
|
export interface UseAuthResult {
|
|
23
25
|
state: AuthState;
|
|
@@ -25,6 +27,9 @@ export interface UseAuthResult {
|
|
|
25
27
|
signOut(): Promise<void>;
|
|
26
28
|
signOutGlobal(): Promise<GlobalSignOutResult>;
|
|
27
29
|
restore(): Promise<AuthState>;
|
|
30
|
+
/** Whether a premium-only path is open. Changes nothing: show the denial where it happened. */
|
|
31
|
+
canAccessPremium(): PremiumGateResult;
|
|
32
|
+
/** @deprecated Use `canAccessPremium`; this moves the whole controller to `access-denied` (AUTH-12). */
|
|
28
33
|
requirePremium(): PremiumGateResult;
|
|
29
34
|
}
|
|
30
35
|
|
|
@@ -53,10 +58,16 @@ export function useAuth(): UseAuthResult {
|
|
|
53
58
|
signOut: useCallback(() => auth.signOut(), [auth]),
|
|
54
59
|
signOutGlobal: useCallback(() => auth.signOutGlobal(), [auth]),
|
|
55
60
|
restore: useCallback(() => auth.restore(), [auth]),
|
|
61
|
+
canAccessPremium: useCallback(() => auth.canAccessPremium(), [auth]),
|
|
56
62
|
requirePremium: useCallback(() => auth.requirePremium(), [auth]),
|
|
57
63
|
};
|
|
58
64
|
}
|
|
59
65
|
|
|
66
|
+
/** This package's copy, with the app's `messages` overrides applied. */
|
|
67
|
+
export function useAuthMessages(): AuthMessageMap {
|
|
68
|
+
return useAuthInstance().getMessages();
|
|
69
|
+
}
|
|
70
|
+
|
|
60
71
|
export function useProfile(): Profile | undefined {
|
|
61
72
|
return useAuthSelector(selectProfile);
|
|
62
73
|
}
|
|
@@ -83,21 +94,49 @@ export type UseAccessTokenStatus =
|
|
|
83
94
|
export interface UseAccessTokenResult {
|
|
84
95
|
status: UseAccessTokenStatus;
|
|
85
96
|
token?: string | undefined;
|
|
86
|
-
|
|
97
|
+
/** `{ forceRefresh: true }` after a server refused the token this returned. */
|
|
98
|
+
request(options?: AccessTokenRequestOptions): Promise<AccessTokenResult>;
|
|
87
99
|
}
|
|
88
100
|
|
|
89
101
|
export function useAccessToken(resourceKey: string): UseAccessTokenResult {
|
|
90
102
|
const auth = useAuthInstance();
|
|
91
|
-
const [
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
const [state, setState] = useState<{
|
|
104
|
+
status: UseAccessTokenStatus;
|
|
105
|
+
token?: string | undefined;
|
|
106
|
+
resourceKey: string;
|
|
107
|
+
}>({ status: 'idle', resourceKey });
|
|
108
|
+
|
|
109
|
+
// Which request may still write. A result for a resource the component has
|
|
110
|
+
// moved on from, or for an unmounted one, is dropped rather than applied
|
|
111
|
+
// (AUTH-17, standard VII).
|
|
112
|
+
const live = useRef(0);
|
|
113
|
+
useEffect(
|
|
114
|
+
() => () => {
|
|
115
|
+
live.current += 1;
|
|
116
|
+
},
|
|
117
|
+
[],
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const request = useCallback(
|
|
121
|
+
async (options?: AccessTokenRequestOptions) => {
|
|
122
|
+
live.current += 1;
|
|
123
|
+
const id = live.current;
|
|
124
|
+
setState({ status: 'loading', resourceKey });
|
|
125
|
+
const result = await auth.requestAccessToken(resourceKey, options);
|
|
126
|
+
if (id === live.current) {
|
|
127
|
+
setState({ status: result.status, token: result.token, resourceKey });
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
},
|
|
131
|
+
[auth, resourceKey],
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// A new resource starts from `idle`, not from the previous resource's token.
|
|
135
|
+
if (state.resourceKey !== resourceKey) {
|
|
136
|
+
live.current += 1;
|
|
137
|
+
setState({ status: 'idle', resourceKey });
|
|
138
|
+
return { status: 'idle', token: undefined, request };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { status: state.status, token: state.token, request };
|
|
103
142
|
}
|
|
@@ -13,7 +13,6 @@ const EXPIRY_SKEW_MS = 60_000;
|
|
|
13
13
|
interface CachedToken {
|
|
14
14
|
token: string;
|
|
15
15
|
expiresAt: number;
|
|
16
|
-
scopes: string[];
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
export interface AccessTokenManagerDeps {
|
|
@@ -37,8 +36,19 @@ export interface AccessTokenManagerDeps {
|
|
|
37
36
|
onRefreshTokenRotated: (token: string, epoch: number) => Promise<void>;
|
|
38
37
|
}
|
|
39
38
|
|
|
39
|
+
export interface AccessTokenRequestOptions {
|
|
40
|
+
/**
|
|
41
|
+
* Ignore the cached token and ask the provider for a new one.
|
|
42
|
+
*
|
|
43
|
+
* For the caller that just had one rejected: a token the server refused is
|
|
44
|
+
* still unexpired by this device's clock, so asking again without this
|
|
45
|
+
* returns the same token and the retry is the same request (AI-01).
|
|
46
|
+
*/
|
|
47
|
+
forceRefresh?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
40
50
|
export interface AccessTokenManager {
|
|
41
|
-
request(resourceKey: string): Promise<AccessTokenResult>;
|
|
51
|
+
request(resourceKey: string, options?: AccessTokenRequestOptions): Promise<AccessTokenResult>;
|
|
42
52
|
/** Whether `request` would answer from the cache, without touching the network. */
|
|
43
53
|
hasFreshToken(resourceKey: string): boolean;
|
|
44
54
|
clear(): void;
|
|
@@ -94,11 +104,7 @@ export function createAccessTokenManager(deps: AccessTokenManagerDeps): AccessTo
|
|
|
94
104
|
return { resourceKey, status: 'error', errorCode: 'token-acquisition-failed' };
|
|
95
105
|
}
|
|
96
106
|
const expiresAt = tokens.accessTokenExpiresAt ?? Date.now() + 3_600_000;
|
|
97
|
-
cache.set(resourceKey, {
|
|
98
|
-
token: tokens.accessToken,
|
|
99
|
-
expiresAt,
|
|
100
|
-
scopes: resource.scopes,
|
|
101
|
-
});
|
|
107
|
+
cache.set(resourceKey, { token: tokens.accessToken, expiresAt });
|
|
102
108
|
return { resourceKey, status: 'success', token: tokens.accessToken, expiresAt };
|
|
103
109
|
} catch (error) {
|
|
104
110
|
if (isAuthError(error)) {
|
|
@@ -127,13 +133,18 @@ export function createAccessTokenManager(deps: AccessTokenManagerDeps): AccessTo
|
|
|
127
133
|
return fresh(resourceKey) !== undefined;
|
|
128
134
|
},
|
|
129
135
|
|
|
130
|
-
request(resourceKey): Promise<AccessTokenResult> {
|
|
136
|
+
request(resourceKey, options = {}): Promise<AccessTokenResult> {
|
|
131
137
|
const resource = deps.resources.find(entry => entry.key === resourceKey);
|
|
132
138
|
if (!resource) {
|
|
133
139
|
return Promise.resolve({ resourceKey, status: 'error', errorCode: 'configuration-error' });
|
|
134
140
|
}
|
|
135
141
|
|
|
136
|
-
|
|
142
|
+
if (options.forceRefresh === true) {
|
|
143
|
+
// Dropped, not just skipped: the token is known bad, so no later
|
|
144
|
+
// caller should be handed it either.
|
|
145
|
+
cache.delete(resourceKey);
|
|
146
|
+
}
|
|
147
|
+
const cached = options.forceRefresh === true ? undefined : fresh(resourceKey);
|
|
137
148
|
if (cached) {
|
|
138
149
|
return Promise.resolve({
|
|
139
150
|
resourceKey,
|