@chipmobilesdk/rn-auth 0.2.1 → 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/CHANGELOG.md +126 -71
- package/README.md +143 -130
- package/package.json +51 -54
- package/skills/sdk-rn-auth/SKILL.md +1 -2
- package/src/access/accessTier.ts +88 -88
- package/src/config/types.ts +154 -143
- package/src/config/validateConfig.ts +114 -108
- package/src/core/authMachine.ts +364 -249
- package/src/core/profile.ts +57 -60
- package/src/core/session.ts +105 -96
- package/src/diagnostics/errors.ts +87 -85
- package/src/diagnostics/messageKeys.ts +59 -61
- package/src/index.ts +71 -82
- package/src/oidc/authClient.ts +195 -224
- package/src/oidc/classifyError.ts +76 -0
- package/src/oidc/socialRouting.ts +66 -73
- package/src/react/AuthProvider.tsx +41 -47
- package/src/react/hooks.ts +86 -90
- package/src/tokens/accessToken.ts +159 -105
- package/src/tokens/secureStore.ts +51 -52
|
@@ -1,108 +1,114 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deterministic configuration validation (FR-005 → configuration-error).
|
|
3
|
-
* Throws AuthConfigError before any provider flow can start (US4 scenario 2).
|
|
4
|
-
* Covers the auth-config.md Section A–F rules.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { AuthConfig } from './types';
|
|
8
|
-
import { AuthConfigError } from '../diagnostics/errors';
|
|
9
|
-
|
|
10
|
-
function hasScheme(url: string): boolean {
|
|
11
|
-
return /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function validateConfig(config: AuthConfig): void {
|
|
15
|
-
// Section A — tenant/client settings
|
|
16
|
-
if (!config.authority || !config.authority.trim()) {
|
|
17
|
-
throw new AuthConfigError('auth.error.configuration', 'Missing "authority"');
|
|
18
|
-
}
|
|
19
|
-
if (!hasScheme(config.authority)) {
|
|
20
|
-
throw new AuthConfigError('auth.error.configuration', '"authority" must be an absolute URL');
|
|
21
|
-
}
|
|
22
|
-
if (!config.clientId || !config.clientId.trim()) {
|
|
23
|
-
throw new AuthConfigError('auth.error.configuration', 'Missing "clientId"');
|
|
24
|
-
}
|
|
25
|
-
if (!config.redirectUrl || !config.redirectUrl.trim()) {
|
|
26
|
-
throw new AuthConfigError('auth.error.configuration', 'Missing "redirectUrl"');
|
|
27
|
-
}
|
|
28
|
-
if (!hasScheme(config.redirectUrl)) {
|
|
29
|
-
throw new AuthConfigError(
|
|
30
|
-
'auth.error.redirectMismatch',
|
|
31
|
-
'"redirectUrl" must include a scheme matching the native redirect configuration',
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
if (!Array.isArray(config.scopes) || config.scopes.length === 0) {
|
|
35
|
-
throw new AuthConfigError('auth.error.configuration', 'At least one OIDC scope is required');
|
|
36
|
-
}
|
|
37
|
-
if (!config.scopes.includes('offline_access')) {
|
|
38
|
-
throw new AuthConfigError(
|
|
39
|
-
'auth.error.configuration',
|
|
40
|
-
'"offline_access" scope is required for session restore and silent refresh',
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Sections B/C — providers
|
|
45
|
-
if (!Array.isArray(config.providers) || config.providers.length === 0) {
|
|
46
|
-
throw new AuthConfigError(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
providerIds.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
throw new AuthConfigError('auth.error.configuration',
|
|
77
|
-
}
|
|
78
|
-
resourceKeys.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic configuration validation (FR-005 → configuration-error).
|
|
3
|
+
* Throws AuthConfigError before any provider flow can start (US4 scenario 2).
|
|
4
|
+
* Covers the auth-config.md Section A–F rules.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { AuthConfig } from './types';
|
|
8
|
+
import { AuthConfigError } from '../diagnostics/errors';
|
|
9
|
+
|
|
10
|
+
function hasScheme(url: string): boolean {
|
|
11
|
+
return /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function validateConfig(config: AuthConfig): void {
|
|
15
|
+
// Section A — tenant/client settings
|
|
16
|
+
if (!config.authority || !config.authority.trim()) {
|
|
17
|
+
throw new AuthConfigError('auth.error.configuration', 'Missing "authority"');
|
|
18
|
+
}
|
|
19
|
+
if (!hasScheme(config.authority)) {
|
|
20
|
+
throw new AuthConfigError('auth.error.configuration', '"authority" must be an absolute URL');
|
|
21
|
+
}
|
|
22
|
+
if (!config.clientId || !config.clientId.trim()) {
|
|
23
|
+
throw new AuthConfigError('auth.error.configuration', 'Missing "clientId"');
|
|
24
|
+
}
|
|
25
|
+
if (!config.redirectUrl || !config.redirectUrl.trim()) {
|
|
26
|
+
throw new AuthConfigError('auth.error.configuration', 'Missing "redirectUrl"');
|
|
27
|
+
}
|
|
28
|
+
if (!hasScheme(config.redirectUrl)) {
|
|
29
|
+
throw new AuthConfigError(
|
|
30
|
+
'auth.error.redirectMismatch',
|
|
31
|
+
'"redirectUrl" must include a scheme matching the native redirect configuration',
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
if (!Array.isArray(config.scopes) || config.scopes.length === 0) {
|
|
35
|
+
throw new AuthConfigError('auth.error.configuration', 'At least one OIDC scope is required');
|
|
36
|
+
}
|
|
37
|
+
if (!config.scopes.includes('offline_access')) {
|
|
38
|
+
throw new AuthConfigError(
|
|
39
|
+
'auth.error.configuration',
|
|
40
|
+
'"offline_access" scope is required for session restore and silent refresh',
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Sections B/C — providers
|
|
45
|
+
if (!Array.isArray(config.providers) || config.providers.length === 0) {
|
|
46
|
+
throw new AuthConfigError(
|
|
47
|
+
'auth.error.configuration',
|
|
48
|
+
'At least one provider must be configured',
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const providerIds = new Set<string>();
|
|
52
|
+
for (const provider of config.providers) {
|
|
53
|
+
if (providerIds.has(provider.id)) {
|
|
54
|
+
throw new AuthConfigError('auth.error.configuration', `Duplicate provider "${provider.id}"`);
|
|
55
|
+
}
|
|
56
|
+
providerIds.add(provider.id);
|
|
57
|
+
if (!provider.domainHint || !provider.domainHint.trim()) {
|
|
58
|
+
throw new AuthConfigError(
|
|
59
|
+
'auth.error.configuration',
|
|
60
|
+
`Provider "${provider.id}" is missing a domainHint`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
if (!Array.isArray(provider.platforms) || provider.platforms.length === 0) {
|
|
64
|
+
throw new AuthConfigError(
|
|
65
|
+
'auth.error.configuration',
|
|
66
|
+
`Provider "${provider.id}" must declare at least one platform`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Section D — resources
|
|
72
|
+
if (config.resources) {
|
|
73
|
+
const resourceKeys = new Set<string>();
|
|
74
|
+
for (const resource of config.resources) {
|
|
75
|
+
if (!resource.key || !resource.key.trim()) {
|
|
76
|
+
throw new AuthConfigError('auth.error.configuration', 'A resource is missing "key"');
|
|
77
|
+
}
|
|
78
|
+
if (resourceKeys.has(resource.key)) {
|
|
79
|
+
throw new AuthConfigError(
|
|
80
|
+
'auth.error.configuration',
|
|
81
|
+
`Duplicate resource "${resource.key}"`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
resourceKeys.add(resource.key);
|
|
85
|
+
if (!Array.isArray(resource.scopes) || resource.scopes.length === 0) {
|
|
86
|
+
throw new AuthConfigError(
|
|
87
|
+
'auth.error.configuration',
|
|
88
|
+
`Resource "${resource.key}" must declare at least one scope`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Section E — access-tier mapping
|
|
95
|
+
const mapping = config.accessTierMapping;
|
|
96
|
+
if (!mapping) {
|
|
97
|
+
throw new AuthConfigError('auth.error.configuration', 'Missing "accessTierMapping"');
|
|
98
|
+
}
|
|
99
|
+
const hasPremiumIndicator =
|
|
100
|
+
(mapping.premiumGroupObjectIds?.length ?? 0) > 0 ||
|
|
101
|
+
(mapping.premiumRoleNames?.length ?? 0) > 0 ||
|
|
102
|
+
(mapping.premiumClaimMatchers?.length ?? 0) > 0;
|
|
103
|
+
if (!hasPremiumIndicator) {
|
|
104
|
+
throw new AuthConfigError(
|
|
105
|
+
'auth.error.configuration',
|
|
106
|
+
'accessTierMapping must define at least one premium group object id, role name, or claim matcher',
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Section F — injected ports
|
|
111
|
+
if (!config.secureStore) {
|
|
112
|
+
throw new AuthConfigError('auth.error.configuration', 'A secureStore is required (FR-021)');
|
|
113
|
+
}
|
|
114
|
+
}
|