@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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,144 @@
|
|
|
1
1
|
# @chipmobilesdk/rn-auth
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Every peer range has an upper bound, and no package declares a peer it does not use. ([#21](https://github.com/operationd8979/chip-mobile-sdk/pull/21))
|
|
8
|
+
|
|
9
|
+
A peer range is now a caret, a tilde or an explicit `<`, except for `react` and
|
|
10
|
+
`react-native`: an app brings exactly one of each, so a floor is the only useful thing
|
|
11
|
+
to say about them. Every other peer is a library the app installs beside us, and an
|
|
12
|
+
open `>=` was a promise about versions nobody had run — mmkv 3→4 lost entitlements and
|
|
13
|
+
pending purchases exactly that way.
|
|
14
|
+
|
|
15
|
+
**Four of the new ceilings exclude a version that is already published**, so an app
|
|
16
|
+
that took the open range at its word may now see a peer warning. The new bound is the
|
|
17
|
+
major this release is tested against, which is the thing the old range failed to say:
|
|
18
|
+
|
|
19
|
+
| Peer | Was | Now | Published above the ceiling |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| `react-native-app-auth` (rn-auth) | `>=8` | `^8.0.0` | 9.0.0 |
|
|
22
|
+
| `react-native-keychain` (rn-auth) | `>=10` | `^10.0.0` | 11.0.0 |
|
|
23
|
+
| `@op-engineering/op-sqlite` (rn-local-db) | `>=17.1.0` | `^17.1.0` | 18.x, 19.x |
|
|
24
|
+
| `@react-native-firebase/messaging` (rn-notification) | `>=26.0.0` | `^26.0.0` | 27.x |
|
|
25
|
+
|
|
26
|
+
The others — `react-native-vision-camera` `^5.1.1`, `react-native-svg` `^15.15.0`,
|
|
27
|
+
`react-native-unistyles` `^3.2.0`, `@dr.pogodin/react-native-fs` `^2.39.2`,
|
|
28
|
+
`react-native-quick-crypto` `^1.0.0`, `react-native-notify-kit` `^10.5.0`,
|
|
29
|
+
`react-native-localize` `^3.7.0`, `react-native-mmkv` `^4.0.0` — exclude nothing that
|
|
30
|
+
exists today. rn-payment's mmkv range was already bounded (`>=4.0.0 <5`) and is now
|
|
31
|
+
written as the caret that means the same thing.
|
|
32
|
+
|
|
33
|
+
Peers nothing imports are gone. `rn-theme`, `rn-local-db` and `rn-notification` asked
|
|
34
|
+
for `react` without having a component or a hook between them; `rn-auth`, `rn-ai` and
|
|
35
|
+
`rn-i18n` asked for `react-native` without touching a React Native API (rn-auth reaches
|
|
36
|
+
both native modules through `lazyPeer`, rn-i18n formats on `Intl`); `rn-notification`
|
|
37
|
+
asked for `@react-native-firebase/app`, which messaging declares itself. `rn-ai` no
|
|
38
|
+
longer peers on `@chipmobilesdk/rn-auth` at all — it asks for a `TokenProvider`, a
|
|
39
|
+
two-method interface rn-auth's `auth` satisfies, so an app with its own token source
|
|
40
|
+
installs nothing extra. An app that enables push or signs in through rn-auth installs
|
|
41
|
+
the same packages as before; it is the manifest that stopped overstating.
|
|
42
|
+
|
|
43
|
+
`react-native-localize` is declared optional, which is what the code already did: the
|
|
44
|
+
time-zone reader goes through `lazyPeer` and falls back to `Intl` without it. The
|
|
45
|
+
`peerDependenciesMeta` entries that said `optional: false` are gone — that is npm's
|
|
46
|
+
default, and writing it down only invited the question of which peers were undecided.
|
|
47
|
+
|
|
48
|
+
`npm run check:manifests` now fails on an unbounded peer range, a peer nothing in
|
|
49
|
+
`src/` imports, and a `peerDependenciesMeta` entry for either, so the policy holds
|
|
50
|
+
without anyone remembering it.
|
|
51
|
+
|
|
52
|
+
- Every package now ships built JavaScript with declaration files, instead of asking the
|
|
53
|
+
app to compile its TypeScript. ([#22](https://github.com/operationd8979/chip-mobile-sdk/pull/22))
|
|
54
|
+
|
|
55
|
+
Each tarball carries `lib/commonjs`, `lib/module` and `lib/typescript` (built with
|
|
56
|
+
`react-native-builder-bob`), and `src/` alongside them. What that changes for a
|
|
57
|
+
consumer:
|
|
58
|
+
|
|
59
|
+
- **Nothing to add to `transformIgnorePatterns`.** `require` resolves to the CommonJS
|
|
60
|
+
build, so an app's Jest loads this package without being told to transform it. That
|
|
61
|
+
was the one thing every consuming app had to know about us (DEP-04).
|
|
62
|
+
- **Typechecking reads our `.d.ts`** rather than compiling our source under our
|
|
63
|
+
`tsconfig`. A consumer's stricter — or looser — settings no longer meet our code.
|
|
64
|
+
- **`import` resolves to ESM**, so a bundler can tree-shake what it does not use.
|
|
65
|
+
|
|
66
|
+
No exported name changed, and no subpath moved: `.`, `./testing`, `./push`,
|
|
67
|
+
`./advanced`, `./app-auth`, `./keychain` and rn-core's twelve subpaths resolve exactly
|
|
68
|
+
as before, each now with its own declarations for both module systems. publint and
|
|
69
|
+
`attw --profile node16` pass on all twelve packages, which they could not before.
|
|
70
|
+
|
|
71
|
+
Inside the monorepo nothing changed either, and that is deliberate: every subpath also
|
|
72
|
+
declares a private `chipmobilesdk-source` condition pointing at its TypeScript, and
|
|
73
|
+
Metro, Jest and `tsc` here ask for that condition first. The demo app bundles 371
|
|
74
|
+
modules straight from `packages/*/src`, so editing a package still changes the app with
|
|
75
|
+
no build step, and coverage is still measured on the source. The condition is named for
|
|
76
|
+
this organization rather than using the conventional `source` because third-party
|
|
77
|
+
packages declare that one too — `@dr.pogodin/react-native-fs` does — and asking for it
|
|
78
|
+
pulled their sources into our stricter compile.
|
|
79
|
+
|
|
80
|
+
`npm run build:packages` builds everything, `prepack` rebuilds before any `npm pack`,
|
|
81
|
+
and `lib/` is git-ignored: the published bytes are produced by the release, never
|
|
82
|
+
committed. `npm run check:manifests` now fails a package whose `main`, `module`,
|
|
83
|
+
`types`, `source` condition, `files`, build script or `prepack` do not match this shape.
|
|
84
|
+
|
|
85
|
+
### Patch Changes
|
|
86
|
+
|
|
87
|
+
- Each package's public API and its weight are now recorded in the repository, so a change
|
|
88
|
+
to either is reviewed rather than discovered. ([#23](https://github.com/operationd8979/chip-mobile-sdk/pull/23))
|
|
89
|
+
|
|
90
|
+
`packages/<pkg>/etc/*.api.md` is an api-extractor report per entrypoint — twenty-seven
|
|
91
|
+
of them, generated from the declarations the package publishes and committed. A renamed
|
|
92
|
+
type, a widened union, one more optional field: all of it shows up in a pull request's
|
|
93
|
+
diff. `npm run check:api` fails when a report is out of date and `npm run api:update`
|
|
94
|
+
rewrites them.
|
|
95
|
+
|
|
96
|
+
A report that moves now needs a **minor or major** changeset for that package.
|
|
97
|
+
`check:changesets` enforces it, because the report is generated from what the package
|
|
98
|
+
publishes, and a patch release that moves the public surface is how a consumer's build
|
|
99
|
+
breaks on `npm update`. A report added for the first time is a baseline, not a change.
|
|
100
|
+
`./testing` deliberately has no report: test doubles are not a surface a release is
|
|
101
|
+
gated on.
|
|
102
|
+
|
|
103
|
+
`npm run check:size` holds every entrypoint to a budget in `.size-limit.json`, measured
|
|
104
|
+
on the ESM build with peers and rn-core treated as external — they are what an app
|
|
105
|
+
installs beside us, not our weight. Each budget started at today's measurement plus five
|
|
106
|
+
per cent, so growth becomes a decision someone makes instead of a drift nobody sees. The
|
|
107
|
+
current figures run from 500 B (`rn-core/lazy`) to 19 kB (rn-notification).
|
|
108
|
+
|
|
109
|
+
No behaviour or API changed in this release; the entry is here so the changelog records
|
|
110
|
+
when the reports and the budgets began.
|
|
111
|
+
- Updated dependencies ([#22](https://github.com/operationd8979/chip-mobile-sdk/pull/22), [#23](https://github.com/operationd8979/chip-mobile-sdk/pull/23)):
|
|
112
|
+
- @chipmobilesdk/rn-core@0.2.0
|
|
113
|
+
|
|
114
|
+
## 0.5.0
|
|
115
|
+
|
|
116
|
+
### Minor Changes
|
|
117
|
+
|
|
118
|
+
- A restored session reports the provider the user signed in with. A refresh answers with the tenant's own `idp` (`google.com`), so after a restart the same session used to describe itself differently — `profile.provider` changed from `google` to `google.com`. ([#19](https://github.com/operationd8979/chip-mobile-sdk/pull/19))
|
|
119
|
+
|
|
120
|
+
`auth.canAccessPremium()` answers whether a premium path is open and changes nothing. `requirePremium()`, which also moves the whole controller to `access-denied` — a status every screen reading `state.status` then sees, and one only a sign-in or `restore()` leaves — is deprecated and will be removed in 0.5.0. Both are on `useAuth()`.
|
|
121
|
+
|
|
122
|
+
`ProviderId` is open (`KnownProviderId | (string & {})`), `labelKey` takes an app's own message key, and `ProviderConfig.additionalParameters` passes extra OIDC parameters such as `prompt`, so federating another identity provider is a change to an app's config rather than a release of this package. A provider's own parameters cannot override `domain_hint`.
|
|
123
|
+
|
|
124
|
+
`config.messages` is read at last: `auth.getMessages()` and `useAuthMessages()` return this package's copy with the app's overrides applied. Configuration validation refuses a plaintext `authority` (http is accepted only for localhost), a provider platform other than `ios`/`android`, and a provider with no `labelKey`. `createAppAuthClient({ serviceConfiguration })` takes the endpoints directly, so an authorize, refresh or sign-out no longer costs a `.well-known` round-trip. `mapClaimsToProfile` and `describeProviderIdentity` are exported, which is what the already-public `ProviderIdentity` type describes.
|
|
125
|
+
|
|
126
|
+
`useAccessToken(resourceKey)` drops a result that arrives after the component unmounted or moved to another resource, and starts from `idle` when the resource changes.
|
|
127
|
+
|
|
128
|
+
The stored session artifact holds an identity — tenant user id, display name, email, provider — and no longer the whole id-token claim set, group ids included; a restore that rotated nothing leaves the Keychain untouched. After an `offline` restore, `profile.claims` is empty and the tier fails closed, as before. The never-emitted `entitlement-unverified` error code is gone from `AuthErrorCode`, and `AuthError.messageKey` is typed as `AuthMessageKey` rather than `string`.
|
|
129
|
+
|
|
130
|
+
- A 401 retry now gets a token the provider had to fetch again. `TokenProvider.requestAccessToken` takes `{ forceRefresh }`, which rn-auth implements by dropping the cached token for that resource, so the retry no longer re-sends the token the gateway had just refused — a cached token is still unexpired by the device's clock when the server rejects it. ([#19](https://github.com/operationd8979/chip-mobile-sdk/pull/19))
|
|
131
|
+
|
|
132
|
+
Failures an app can tell apart. `network` is a request that never arrived (no connection, a timeout, a dropped stream) and is retryable; `session-expired` is a session the user must renew. `AiError.cause` carries the code from the layer underneath — the gateway's own code, or rn-auth's — so a screen can explain *why*, and `retryAfterSeconds` is read from the `Retry-After` header when the body omits it. Both new codes have user copy, and the gateway's full code list is mapped rather than guessed from the status.
|
|
133
|
+
|
|
134
|
+
The remote behavior config is checked before it is trusted. `configUrl` and the document's `api.baseUrl` must be https (localhost excepted), the document must name the app it is for, its `schemaVersion` must be one this release understands, and `allowedHosts` pins where the client may send its bearer token. A broken TTL no longer means a revalidation on every request, and a retry count is bounded. A not-modified answer against a cache that is no longer there asks again without the ETag instead of reporting the 304 as a failure.
|
|
135
|
+
|
|
136
|
+
`configCacheFromKeyValue(port)` makes a durable config cache out of rn-core's `KeyValuePort`, so a second launch uses the last-good document instead of waiting for the network.
|
|
137
|
+
|
|
138
|
+
One quota figure: a chat's `done` event and `getQuota()` both record it on the client (`lastQuota()`), so `useAiQuota` and `useAiChat` cannot disagree, and a quota response that does not match the contract is refused rather than displayed. `getQuota()` recovers like a chat: a designated 404/410 reloads the configuration and retries, and a 401 retries once.
|
|
139
|
+
|
|
140
|
+
Streamed text reaches the screen on an interval (50 ms by default, `useAiChat(client, { flushIntervalMs })`) rather than once per token, and whatever arrived is flushed with the terminal state, so a half-answer stays on screen when a stream fails. A stream framed with CRLF is parsed incrementally, as the SSE specification allows. The transport seam is a published `StreamTransport` port instead of the internal reader's signature.
|
|
141
|
+
|
|
3
142
|
## 0.4.0
|
|
4
143
|
|
|
5
144
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -30,9 +30,12 @@ npm install @chipmobilesdk/rn-auth react-native-app-auth react-native-keychain
|
|
|
30
30
|
cd ios && pod install
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
`react`, `react-native
|
|
34
|
-
are **peer dependencies**. The two native libraries are only
|
|
35
|
-
default adapters — an app injecting custom ports may omit them
|
|
33
|
+
`react`, `react-native-app-auth` (`^8.0.0`) and `react-native-keychain`
|
|
34
|
+
(`^10.0.0`) are the **peer dependencies**. The two native libraries are only
|
|
35
|
+
required by the default adapters — an app injecting custom ports may omit them,
|
|
36
|
+
and they are declared optional for that reason. There is no `react-native` peer:
|
|
37
|
+
the controller is plain TypeScript, and both native modules are reached through
|
|
38
|
+
`lazyPeer`.
|
|
36
39
|
|
|
37
40
|
See [native setup](../../specs/003-mobile-entra-auth/contracts/native-setup.md)
|
|
38
41
|
for the iOS `Info.plist` URL scheme and the Android `appAuthRedirectScheme`
|
|
@@ -74,11 +77,45 @@ export const App = () => (
|
|
|
74
77
|
);
|
|
75
78
|
```
|
|
76
79
|
|
|
77
|
-
Hooks: `useAuth()` (state + `signIn`/`signOut`/`signOutGlobal`/`restore`/`
|
|
80
|
+
Hooks: `useAuth()` (state + `signIn`/`signOut`/`signOutGlobal`/`restore`/`canAccessPremium`),
|
|
78
81
|
`useProfile()`, `useAccessTier()`, `useAccessToken(resourceKey)`,
|
|
82
|
+
`useAuthMessages()` (this package's copy, with your `messages` applied),
|
|
79
83
|
`useSignInProviders()` (all platform providers, in config order — map to one
|
|
80
84
|
button each) and `useSignInProvider()` (first available, for a single-button UI).
|
|
81
85
|
|
|
86
|
+
### Gating a premium path
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
const { canAccessPremium } = useAuth();
|
|
90
|
+
const gate = canAccessPremium(); // { allowed, tier } — reads, changes nothing
|
|
91
|
+
|
|
92
|
+
return gate.allowed ? <PremiumReport /> : <UpgradeNotice tier={gate.tier} />;
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`canAccessPremium()` is a question. `requirePremium()`, which answers the same
|
|
96
|
+
question *and* moves the whole controller to `access-denied` — a status every
|
|
97
|
+
screen reading `state.status` then sees, and only a sign-in or `restore()`
|
|
98
|
+
leaves — is deprecated and will go in 0.5.0.
|
|
99
|
+
|
|
100
|
+
### Providers beyond the three with built-in copy
|
|
101
|
+
|
|
102
|
+
A provider is a `domain_hint` and a label, so adding one the tenant federates is
|
|
103
|
+
a change to this config and nothing else:
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
providers: [
|
|
107
|
+
{ id: 'microsoft', platforms: ['ios', 'android'], domainHint: 'live.com',
|
|
108
|
+
labelKey: 'app.action.continueMicrosoft', // one of your own keys
|
|
109
|
+
additionalParameters: { prompt: 'select_account' }, // merged into authorize
|
|
110
|
+
},
|
|
111
|
+
]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`ProviderId` is `KnownProviderId | (string & {})`: `'google' | 'apple' |
|
|
115
|
+
'facebook'` stay in autocomplete, and any other id is accepted. A provider's
|
|
116
|
+
`additionalParameters` cannot override `domain_hint`, so it can never route the
|
|
117
|
+
flow to another IdP.
|
|
118
|
+
|
|
82
119
|
### Session lifecycle guarantees
|
|
83
120
|
|
|
84
121
|
- **Restore deletes the stored session only when the provider rejects the refresh
|
|
@@ -91,6 +128,15 @@ button each) and `useSignInProvider()` (first available, for a single-button UI)
|
|
|
91
128
|
`requestAccessToken` calls for one resource share one network refresh.
|
|
92
129
|
- **`signOutGlobal()` returns `{ global: 'ended' | 'failed', error? }`.** Local state
|
|
93
130
|
is cleared either way; `failed` means the tenant session may still be alive.
|
|
131
|
+
- **A restored profile reports the provider the user signed in with.** The stored
|
|
132
|
+
artifact carries it, because a refresh answers with the tenant's own `idp`
|
|
133
|
+
(`google.com`), which is not the id the app passed to `signIn`.
|
|
134
|
+
- **The stored artifact holds an identity, not a token's claims:** the tenant user
|
|
135
|
+
id, display name, email and provider. After a restore that reached the provider,
|
|
136
|
+
`profile.claims` is the fresh id-token claim set; after an `offline` restore it is
|
|
137
|
+
empty, and the access tier fails closed. Read `state.restore` to tell them apart.
|
|
138
|
+
- **An unchanged session is not rewritten.** A restore that rotated nothing leaves
|
|
139
|
+
the Keychain untouched, off the startup path.
|
|
94
140
|
|
|
95
141
|
## Configuration contract (LIB-010)
|
|
96
142
|
|
|
@@ -106,10 +152,15 @@ client secrets are configured in the External ID tenant, never in the app
|
|
|
106
152
|
| C2 — Facebook (optional) | `providers[facebook]`: `domainHint: 'facebook.com'`, `platforms: ['ios','android']`, `labelKey: 'auth.action.continueFacebook'`, optional `available` |
|
|
107
153
|
| D — Resources | `resources[].key`, `resources[].scopes`, `resources[].displayName?` |
|
|
108
154
|
| E — Access tier | `accessTierMapping.premiumGroupObjectIds` (+ optional role names / claim matchers; display names are diagnostics-only) |
|
|
109
|
-
| F — Ports | `secureStore` (required), `authClient` (optional), `messages` (optional copy override) |
|
|
155
|
+
| F — Ports | `secureStore` (required), `authClient` (optional), `messages` (optional copy override, readable through `useAuthMessages()`) |
|
|
110
156
|
|
|
111
|
-
|
|
112
|
-
|
|
157
|
+
`createAuth` throws `AuthConfigError` before any flow can start when: `authority`
|
|
158
|
+
is missing, relative, or not `https` (plain `http` is accepted only for
|
|
159
|
+
`localhost`); `redirectUrl` has no scheme; `offline_access` is absent from
|
|
160
|
+
`scopes`; no provider is configured, or one repeats an id, or lacks a
|
|
161
|
+
`domainHint` or `labelKey`, or declares a platform other than `ios`/`android`; a
|
|
162
|
+
resource repeats a key or declares no scope; `accessTierMapping` names no premium
|
|
163
|
+
indicator; or no `secureStore` is supplied.
|
|
113
164
|
|
|
114
165
|
## Tenant / provider prerequisite checklist (LIB-018)
|
|
115
166
|
|
|
@@ -160,19 +211,28 @@ are unsupported.
|
|
|
160
211
|
|
|
161
212
|
- Controller and configuration: `createAuth`, `Auth`, `AuthConfig`,
|
|
162
213
|
`CreateAuthOptions`, `AuthState`, `AuthStatus`, `RestoreStatus`
|
|
163
|
-
- Identity and access: `Profile`, `ProviderConfig`, `
|
|
164
|
-
`
|
|
165
|
-
`
|
|
214
|
+
- Identity and access: `Profile`, `ProviderConfig`, `ProviderId`,
|
|
215
|
+
`KnownProviderId`, `ProviderIdentity`, `ResourceConfig`, `AccessTier`,
|
|
216
|
+
`AccessTierMapping`, `AccessTokenResult`, `PremiumGateResult`,
|
|
217
|
+
`evaluateAccessTier`, `failClosedTier`, `mapClaimsToProfile`,
|
|
218
|
+
`describeProviderIdentity` (what an address is: `direct`, Apple's
|
|
219
|
+
`private-relay`, or `none`)
|
|
166
220
|
- Ports and default adapters: `SecureTokenStore`, `createKeychainSecureStore`,
|
|
167
221
|
`AUTH_STORAGE_KEY`, `OidcAuthClient`, `createAppAuthClient` and the public OIDC
|
|
168
|
-
request/token types.
|
|
222
|
+
request/token types. `createAppAuthClient({ serviceConfiguration })` takes the
|
|
223
|
+
authorization, token and end-session endpoints directly; without them the native
|
|
224
|
+
module fetches `.well-known` on every authorize, refresh and sign-out, including
|
|
225
|
+
the refresh on the startup path. The two default adapters also have their own entrypoints,
|
|
169
226
|
`@chipmobilesdk/rn-auth/app-auth` and `@chipmobilesdk/rn-auth/keychain`, so an app
|
|
170
227
|
that injects its own client or store can skip them. Neither loads its native
|
|
171
228
|
module until it is used: react-native-app-auth on the first sign-in, refresh or
|
|
172
229
|
sign-out, react-native-keychain on the first read or write. Creating `auth` at
|
|
173
230
|
startup loads neither. A missing module is an `AuthConfigError` at that moment.
|
|
174
231
|
- React: `AuthProvider`, `useAuth`, `useProfile`, `useAccessTier`,
|
|
175
|
-
`useAccessToken`, `useSignInProvider`, `useSignInProviders`,
|
|
232
|
+
`useAccessToken`, `useAuthMessages`, `useSignInProvider`, `useSignInProviders`,
|
|
233
|
+
`useAuthSelector`. `useAccessToken(resourceKey)` starts from `idle` again when
|
|
234
|
+
the resource changes, and a result that arrives after the component unmounted
|
|
235
|
+
or moved on is dropped rather than applied.
|
|
176
236
|
Each hook reads the state through `useSyncExternalStore` and re-renders only when
|
|
177
237
|
what it returns changes: `useProfile` does not render for a status change. Use
|
|
178
238
|
`useAuthSelector(state => …, isEqual?)` for any other slice. `auth.getState()`
|
|
@@ -202,8 +262,8 @@ adapters; run them against a store or client you inject.
|
|
|
202
262
|
|
|
203
263
|
## Compatibility and validation
|
|
204
264
|
|
|
205
|
-
React ≥19 · React Native ≥0.85 · `react-native-app-auth`
|
|
206
|
-
`react-native-keychain`
|
|
265
|
+
React ≥19 · React Native ≥0.85 · `react-native-app-auth` `^8.0.0` ·
|
|
266
|
+
`react-native-keychain` `^10.0.0` · iOS + Android · Node ≥22.11 for tooling.
|
|
207
267
|
|
|
208
268
|
The demo integration lives in `src/screens/AuthDemoScreen.tsx`. Run from the
|
|
209
269
|
repository root:
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.evaluateAccessTier = evaluateAccessTier;
|
|
7
|
+
exports.failClosedTier = failClosedTier;
|
|
8
|
+
/**
|
|
9
|
+
* Access-tier evaluation (FR-010/FR-011/FR-019/FR-020, R6).
|
|
10
|
+
*
|
|
11
|
+
* Pure function over the authenticated token's claims and the app-specific
|
|
12
|
+
* mapping. Object ids are authoritative; display names never grant access.
|
|
13
|
+
* Missing, stale, ambiguous, or group-overage claims fail closed (premium
|
|
14
|
+
* denied, verification = 'unverified').
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** The fail-closed default: free + unverified. */
|
|
18
|
+
function failClosedTier() {
|
|
19
|
+
return {
|
|
20
|
+
tier: 'free',
|
|
21
|
+
source: 'default',
|
|
22
|
+
verification: 'unverified'
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function toStringArray(value) {
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
return value.filter(entry => typeof entry === 'string');
|
|
28
|
+
}
|
|
29
|
+
if (typeof value === 'string') {
|
|
30
|
+
return [value];
|
|
31
|
+
}
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Entra emits a groups overage indicator (`_claim_names.groups` or `hasgroups`)
|
|
37
|
+
* instead of inline groups when the list is too large. Reading the full list
|
|
38
|
+
* would require Graph (out of scope, FR-012), so overage fails closed.
|
|
39
|
+
*/
|
|
40
|
+
function hasGroupOverage(claims) {
|
|
41
|
+
if (claims.hasgroups === true) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
const claimNames = claims._claim_names;
|
|
45
|
+
if (claimNames && typeof claimNames === 'object' && 'groups' in claimNames) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
function matchesClaim(claims, matcher) {
|
|
51
|
+
const value = claims[matcher.claim];
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
return value.includes(matcher.equals);
|
|
54
|
+
}
|
|
55
|
+
return value === matcher.equals;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Evaluate the access tier, failing closed when entitlement is unverifiable. */
|
|
59
|
+
function evaluateAccessTier(claims, mapping, now = Date.now()) {
|
|
60
|
+
// Object ids first (authoritative). Display names are never consulted.
|
|
61
|
+
const groups = toStringArray(claims.groups);
|
|
62
|
+
if (mapping.premiumGroupObjectIds.length > 0 && groups.some(group => mapping.premiumGroupObjectIds.includes(group))) {
|
|
63
|
+
return {
|
|
64
|
+
tier: 'premium',
|
|
65
|
+
source: 'group',
|
|
66
|
+
verification: 'verified',
|
|
67
|
+
lastRefreshAt: now
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Roles next.
|
|
72
|
+
const roles = toStringArray(claims.roles);
|
|
73
|
+
if (mapping.premiumRoleNames?.length && roles.some(role => mapping.premiumRoleNames.includes(role))) {
|
|
74
|
+
return {
|
|
75
|
+
tier: 'premium',
|
|
76
|
+
source: 'role',
|
|
77
|
+
verification: 'verified',
|
|
78
|
+
lastRefreshAt: now
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Custom claim matchers last.
|
|
83
|
+
if (mapping.premiumClaimMatchers?.some(matcher => matchesClaim(claims, matcher))) {
|
|
84
|
+
return {
|
|
85
|
+
tier: 'premium',
|
|
86
|
+
source: 'claim',
|
|
87
|
+
verification: 'verified',
|
|
88
|
+
lastRefreshAt: now
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// No premium indicator matched. If group claims are unavailable due to
|
|
93
|
+
// overage and premium depends on groups, we cannot verify → fail closed.
|
|
94
|
+
if (mapping.premiumGroupObjectIds.length > 0 && hasGroupOverage(claims)) {
|
|
95
|
+
return {
|
|
96
|
+
tier: 'free',
|
|
97
|
+
source: 'default',
|
|
98
|
+
verification: 'unverified',
|
|
99
|
+
lastRefreshAt: now
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Verified free: claims were present and simply did not grant premium.
|
|
104
|
+
return {
|
|
105
|
+
tier: 'free',
|
|
106
|
+
source: 'default',
|
|
107
|
+
verification: 'verified',
|
|
108
|
+
lastRefreshAt: now
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=accessTier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["failClosedTier","tier","source","verification","toStringArray","value","Array","isArray","filter","entry","hasGroupOverage","claims","hasgroups","claimNames","_claim_names","matchesClaim","matcher","claim","includes","equals","evaluateAccessTier","mapping","now","Date","groups","premiumGroupObjectIds","length","some","group","lastRefreshAt","roles","premiumRoleNames","role","premiumClaimMatchers"],"sourceRoot":"../../../src","sources":["access/accessTier.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "createAppAuthClient", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _authClient.createAppAuthClient;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
var _authClient = require("./oidc/authClient.js");
|
|
13
|
+
//# sourceMappingURL=app-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_authClient","require"],"sourceRoot":"../../src","sources":["app-auth.ts"],"mappings":";;;;;;;;;;;AAKA,IAAAA,WAAA,GAAAC,OAAA","ignoreList":[]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.DEVICE_PLATFORMS = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Public type contracts for @chipmobilesdk/rn-auth. Shapes follow
|
|
9
|
+
* specs/003-mobile-entra-auth/data-model.md.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** The providers this package has copy for out of the box. */
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A provider id. Open on purpose: routing is `domain_hint` and nothing else, so
|
|
16
|
+
* adding an IdP the tenant federates — Microsoft account, an email one-time
|
|
17
|
+
* code — is a change to an app's config, not a release of this package
|
|
18
|
+
* (AUTH-14). `KnownProviderId` keeps the three familiar ones in autocomplete.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const DEVICE_PLATFORMS = exports.DEVICE_PLATFORMS = ['ios', 'android'];
|
|
22
|
+
|
|
23
|
+
/** Provider Setup Configuration — non-secret public values only (FR-016/FR-017). */
|
|
24
|
+
|
|
25
|
+
/** One configured protected resource for delegated token requests (FR-007). */
|
|
26
|
+
|
|
27
|
+
/** App-specific mapping from tenant indicators to access tiers (FR-019/FR-020). */
|
|
28
|
+
|
|
29
|
+
/** App-supplied, public-only setup input (FR-015/FR-016/FR-017). */
|
|
30
|
+
|
|
31
|
+
/** Advanced/testing options. `platform` overrides the detected device platform. */
|
|
32
|
+
|
|
33
|
+
/** Customer User / app-visible profile (FR-006). */
|
|
34
|
+
|
|
35
|
+
/** Access Tier — local interpretation of tenant indicators (FR-010). */
|
|
36
|
+
|
|
37
|
+
/** Access Token Request result (FR-007). */
|
|
38
|
+
|
|
39
|
+
/** Single active app-visible authenticated state (FR-005/FR-022). */
|
|
40
|
+
|
|
41
|
+
/** Result of a premium-only gate check (US3). */
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* What `signOutGlobal` did beyond the device (added in 0.2.2). Local state is
|
|
45
|
+
* cleared either way; `failed` means the tenant session may still be alive, so
|
|
46
|
+
* the next interactive sign-in can complete without a password prompt.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/** The auth controller returned by `createAuth` (single active account). */
|
|
50
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["DEVICE_PLATFORMS","exports"],"sourceRoot":"../../../src","sources":["config/types.ts"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;;AAQA;;AAGA;AACA;AACA;AACA;AACA;AACA;;AAIO,MAAMA,gBAA2C,GAAAC,OAAA,CAAAD,gBAAA,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC;;AAE7E;;AAoBA;;AAYA;;AAUA;;AAmBA;;AAiBA;;AAoBA;;AAUA;;AASA;;AASA;;AAMA;AACA;AACA;AACA;AACA;;AAOA","ignoreList":[]}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.validateConfig = validateConfig;
|
|
7
|
+
var _types = require("./types.js");
|
|
8
|
+
var _errors = require("../diagnostics/errors.js");
|
|
9
|
+
/**
|
|
10
|
+
* Deterministic configuration validation (FR-005 → configuration-error).
|
|
11
|
+
* Throws AuthConfigError before any provider flow can start (US4 scenario 2).
|
|
12
|
+
* Covers the auth-config.md Section A–F rules.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
function hasScheme(url) {
|
|
16
|
+
return /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** https, or http on a development machine. */
|
|
20
|
+
function isSecureAuthority(url) {
|
|
21
|
+
if (/^https:/i.test(url)) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
return /^http:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?(\/|$)/i.test(url);
|
|
25
|
+
}
|
|
26
|
+
function validateConfig(config) {
|
|
27
|
+
// Section A — tenant/client settings
|
|
28
|
+
if (!config.authority || !config.authority.trim()) {
|
|
29
|
+
throw new _errors.AuthConfigError('auth.error.configuration', 'Missing "authority"');
|
|
30
|
+
}
|
|
31
|
+
if (!hasScheme(config.authority)) {
|
|
32
|
+
throw new _errors.AuthConfigError('auth.error.configuration', '"authority" must be an absolute URL');
|
|
33
|
+
}
|
|
34
|
+
// Tokens travel over this. A tenant reachable over plaintext http is a
|
|
35
|
+
// configuration mistake, not a deployment choice — except on a dev machine.
|
|
36
|
+
if (!isSecureAuthority(config.authority)) {
|
|
37
|
+
throw new _errors.AuthConfigError('auth.error.configuration', '"authority" must use https (http is accepted only for localhost)');
|
|
38
|
+
}
|
|
39
|
+
if (!config.clientId || !config.clientId.trim()) {
|
|
40
|
+
throw new _errors.AuthConfigError('auth.error.configuration', 'Missing "clientId"');
|
|
41
|
+
}
|
|
42
|
+
if (!config.redirectUrl || !config.redirectUrl.trim()) {
|
|
43
|
+
throw new _errors.AuthConfigError('auth.error.configuration', 'Missing "redirectUrl"');
|
|
44
|
+
}
|
|
45
|
+
if (!hasScheme(config.redirectUrl)) {
|
|
46
|
+
throw new _errors.AuthConfigError('auth.error.redirectMismatch', '"redirectUrl" must include a scheme matching the native redirect configuration');
|
|
47
|
+
}
|
|
48
|
+
if (!Array.isArray(config.scopes) || config.scopes.length === 0) {
|
|
49
|
+
throw new _errors.AuthConfigError('auth.error.configuration', 'At least one OIDC scope is required');
|
|
50
|
+
}
|
|
51
|
+
if (!config.scopes.includes('offline_access')) {
|
|
52
|
+
throw new _errors.AuthConfigError('auth.error.configuration', '"offline_access" scope is required for session restore and silent refresh');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Sections B/C — providers
|
|
56
|
+
if (!Array.isArray(config.providers) || config.providers.length === 0) {
|
|
57
|
+
throw new _errors.AuthConfigError('auth.error.configuration', 'At least one provider must be configured');
|
|
58
|
+
}
|
|
59
|
+
const providerIds = new Set();
|
|
60
|
+
for (const provider of config.providers) {
|
|
61
|
+
if (providerIds.has(provider.id)) {
|
|
62
|
+
throw new _errors.AuthConfigError('auth.error.configuration', `Duplicate provider "${provider.id}"`);
|
|
63
|
+
}
|
|
64
|
+
providerIds.add(provider.id);
|
|
65
|
+
if (!provider.domainHint || !provider.domainHint.trim()) {
|
|
66
|
+
throw new _errors.AuthConfigError('auth.error.configuration', `Provider "${provider.id}" is missing a domainHint`);
|
|
67
|
+
}
|
|
68
|
+
if (!Array.isArray(provider.platforms) || provider.platforms.length === 0) {
|
|
69
|
+
throw new _errors.AuthConfigError('auth.error.configuration', `Provider "${provider.id}" must declare at least one platform`);
|
|
70
|
+
}
|
|
71
|
+
for (const platform of provider.platforms) {
|
|
72
|
+
if (!_types.DEVICE_PLATFORMS.includes(platform)) {
|
|
73
|
+
// A typo here hides the provider on every device rather than failing.
|
|
74
|
+
throw new _errors.AuthConfigError('auth.error.configuration', `Provider "${provider.id}" declares unknown platform "${String(platform)}"`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (typeof provider.labelKey !== 'string' || !provider.labelKey.trim()) {
|
|
78
|
+
throw new _errors.AuthConfigError('auth.error.configuration', `Provider "${provider.id}" is missing a labelKey`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Section D — resources
|
|
83
|
+
if (config.resources) {
|
|
84
|
+
const resourceKeys = new Set();
|
|
85
|
+
for (const resource of config.resources) {
|
|
86
|
+
if (!resource.key || !resource.key.trim()) {
|
|
87
|
+
throw new _errors.AuthConfigError('auth.error.configuration', 'A resource is missing "key"');
|
|
88
|
+
}
|
|
89
|
+
if (resourceKeys.has(resource.key)) {
|
|
90
|
+
throw new _errors.AuthConfigError('auth.error.configuration', `Duplicate resource "${resource.key}"`);
|
|
91
|
+
}
|
|
92
|
+
resourceKeys.add(resource.key);
|
|
93
|
+
if (!Array.isArray(resource.scopes) || resource.scopes.length === 0) {
|
|
94
|
+
throw new _errors.AuthConfigError('auth.error.configuration', `Resource "${resource.key}" must declare at least one scope`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Section E — access-tier mapping
|
|
100
|
+
const mapping = config.accessTierMapping;
|
|
101
|
+
if (!mapping) {
|
|
102
|
+
throw new _errors.AuthConfigError('auth.error.configuration', 'Missing "accessTierMapping"');
|
|
103
|
+
}
|
|
104
|
+
const hasPremiumIndicator = (mapping.premiumGroupObjectIds?.length ?? 0) > 0 || (mapping.premiumRoleNames?.length ?? 0) > 0 || (mapping.premiumClaimMatchers?.length ?? 0) > 0;
|
|
105
|
+
if (!hasPremiumIndicator) {
|
|
106
|
+
throw new _errors.AuthConfigError('auth.error.configuration', 'accessTierMapping must define at least one premium group object id, role name, or claim matcher');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Section F — injected ports
|
|
110
|
+
if (!config.secureStore) {
|
|
111
|
+
throw new _errors.AuthConfigError('auth.error.configuration', 'A secureStore is required (FR-021)');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=validateConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_types","require","_errors","hasScheme","url","test","isSecureAuthority","validateConfig","config","authority","trim","AuthConfigError","clientId","redirectUrl","Array","isArray","scopes","length","includes","providers","providerIds","Set","provider","has","id","add","domainHint","platforms","platform","DEVICE_PLATFORMS","String","labelKey","resources","resourceKeys","resource","key","mapping","accessTierMapping","hasPremiumIndicator","premiumGroupObjectIds","premiumRoleNames","premiumClaimMatchers","secureStore"],"sourceRoot":"../../../src","sources":["config/validateConfig.ts"],"mappings":"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","ignoreList":[]}
|