@biffo/cli 0.79.0 → 0.80.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/_skeletons/sibling-template/.github/workflows/ci.yml +10 -9
- package/_skeletons/sibling-template/.github/workflows/deploy.yml +6 -2
- package/_skeletons/sibling-template/README.md +24 -10
- package/_skeletons/sibling-template/apps/frontend/.env.example +5 -2
- package/_skeletons/sibling-template/apps/frontend/src/lib/auth.test.ts +39 -55
- package/_skeletons/sibling-template/apps/frontend/src/lib/auth.ts +6 -5
- package/_skeletons/sibling-template/apps/frontend/src/lib/identity.ts +23 -39
- package/package.json +1 -1
|
@@ -67,15 +67,16 @@ jobs:
|
|
|
67
67
|
- name: Test
|
|
68
68
|
if: ${{ !cancelled() }}
|
|
69
69
|
run: pnpm run test
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
70
|
+
# Run with NO NEXT_PUBLIC_CORE_COGNITO_* in scope — this is now the
|
|
71
|
+
# PERMANENT state, not a build-guard quirk. The frontend NEVER reads those
|
|
72
|
+
# vars: it resolves the core's Cognito identity at runtime from
|
|
73
|
+
# /.well-known/biffo-identity.json (#403 Stage 3), so nothing is baked into
|
|
74
|
+
# the bundle and deploy.yml no longer sets them either. `next build`
|
|
75
|
+
# prerenders `/`, which imports src/lib/auth.ts; that module builds its
|
|
76
|
+
# Cognito pool lazily (never at module scope) so the build succeeds with no
|
|
77
|
+
# identity in scope. Originally added as a guard for the eager-construction
|
|
78
|
+
# crash (issue #286); it now simply asserts a sibling's build needs no auth
|
|
79
|
+
# credentials at all.
|
|
79
80
|
- name: Build
|
|
80
81
|
if: ${{ !cancelled() }}
|
|
81
82
|
run: pnpm run build
|
|
@@ -153,8 +153,12 @@ jobs:
|
|
|
153
153
|
NEXT_PUBLIC_SIBLING_PATH_PREFIX: ${{ vars.PATH_PREFIX && format('/{0}', vars.PATH_PREFIX) || '' }}
|
|
154
154
|
NEXT_PUBLIC_BASE_PATH: ${{ vars.PATH_PREFIX && format('/{0}', vars.PATH_PREFIX) || '' }}
|
|
155
155
|
NEXT_PUBLIC_CORE_PORTAL_URL: ${{ vars.CORE_PORTAL_URL }}
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
# The frontend no longer bakes NEXT_PUBLIC_CORE_COGNITO_* into the
|
|
157
|
+
# bundle: it resolves the core's Cognito identity at RUNTIME from the
|
|
158
|
+
# core-published /.well-known/biffo-identity.json document (#403), so
|
|
159
|
+
# the build no longer needs the pool/client ids. (The BACKEND still
|
|
160
|
+
# consumes CORE_COGNITO_* via TF_VAR for JWT validation — see the
|
|
161
|
+
# deploy-infra job above; that is deliberately unchanged.)
|
|
158
162
|
NEXT_PUBLIC_API_URL: ${{ vars.API_URL }}
|
|
159
163
|
run: pnpm run build
|
|
160
164
|
- uses: aws-actions/configure-aws-credentials@v6
|
|
@@ -198,14 +198,21 @@ part of the install, so its lifecycle scripts never run.)
|
|
|
198
198
|
|
|
199
199
|
## The build must not need Cognito credentials
|
|
200
200
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
201
|
+
The frontend resolves the core's Cognito identity at **runtime** from the
|
|
202
|
+
core-published `/.well-known/biffo-identity.json` document (#403) — it never
|
|
203
|
+
bakes `NEXT_PUBLIC_CORE_COGNITO_*` into the bundle. So `pnpm run build` succeeds
|
|
204
|
+
with **no** `NEXT_PUBLIC_CORE_COGNITO_*` set, and `.github/workflows/ci.yml`
|
|
205
|
+
runs it that way on every PR to keep it that way. `next build` prerenders `/` in
|
|
206
|
+
Node, which imports `src/lib/auth.ts`; that module constructs its Cognito user
|
|
207
|
+
pool lazily, on first session read, never at module scope (the `CognitoUserPool`
|
|
208
|
+
constructor throws outright when either id is missing). If you add module-scope
|
|
209
|
+
code that requires real core config, the build breaks for everyone — starting
|
|
210
|
+
with CI. Read config inside the function that needs it.
|
|
211
|
+
|
|
212
|
+
(The sibling's **backend** still receives the core pool/client ids at deploy
|
|
213
|
+
time via `TF_VAR_core_cognito_*` — its Terraform builds a JWKS URL from the pool
|
|
214
|
+
id to validate JWTs. That is a separate concern from the frontend bundle and is
|
|
215
|
+
deliberately unchanged.)
|
|
209
216
|
|
|
210
217
|
## The two-phase CDN registration
|
|
211
218
|
|
|
@@ -267,12 +274,19 @@ this repo's own `statusChecks` list — see that method's third parameter):
|
|
|
267
274
|
|
|
268
275
|
1. Rename `biffo.sibling.json`'s `name`/`core_project`/`path_prefix`.
|
|
269
276
|
2. Copy `apps/frontend/.env.example` to `.env.local` and fill in your core
|
|
270
|
-
project's real
|
|
277
|
+
project's real API/portal values for local dev. No Cognito values are
|
|
278
|
+
needed: the frontend resolves the core's Cognito identity at runtime from
|
|
279
|
+
`/.well-known/biffo-identity.json` (#403). A locally-run frontend with no
|
|
280
|
+
reachable document just treats the visitor as signed-out.
|
|
271
281
|
3. `cd infra && terraform init -backend-config=backend.hcl && terraform apply`
|
|
272
282
|
(generate `backend.hcl` yourself for local use — CI generates it inline,
|
|
273
283
|
see `deploy.yml`).
|
|
274
284
|
4. Wire the GitHub Environment variables/secrets `deploy.yml` reads
|
|
275
285
|
(`CORE_COGNITO_USER_POOL_ID`, `CORE_COGNITO_CLIENT_ID`, `CORE_API_URL`,
|
|
276
|
-
`CORE_PORTAL_URL`, `SIBLING_OIDC_ROLE_ARN`, `TF_STATE_BUCKET`, etc.).
|
|
286
|
+
`CORE_PORTAL_URL`, `SIBLING_OIDC_ROLE_ARN`, `TF_STATE_BUCKET`, etc.). The
|
|
287
|
+
`CORE_COGNITO_*` variables are still required: the **backend** consumes them
|
|
288
|
+
via `TF_VAR_core_cognito_*` for JWT validation (its Terraform derives a JWKS
|
|
289
|
+
URL from the pool id). They are no longer inlined into the frontend bundle —
|
|
290
|
+
the frontend resolves the core's Cognito identity at runtime (#403).
|
|
277
291
|
5. Open the registration PR against your core project (`infra/siblings.auto.tfvars.json`)
|
|
278
292
|
yourself, or let `biffo sibling create` do it for you.
|
|
@@ -6,8 +6,11 @@ NEXT_PUBLIC_SIBLING_NAME=example-sibling
|
|
|
6
6
|
NEXT_PUBLIC_SIBLING_PATH_PREFIX=/example-sibling
|
|
7
7
|
NEXT_PUBLIC_BASE_PATH=/example-sibling
|
|
8
8
|
NEXT_PUBLIC_CORE_PORTAL_URL=https://baseurl.com
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
# No Cognito env is needed: the sibling resolves the core's Cognito identity at
|
|
10
|
+
# RUNTIME from /.well-known/biffo-identity.json (served by the core), so nothing
|
|
11
|
+
# is baked into the bundle (#403). A locally-run frontend with no reachable
|
|
12
|
+
# document simply treats the visitor as signed-out — a sibling never signs
|
|
13
|
+
# anyone in (ADR-0007).
|
|
11
14
|
# This sibling's OWN API Gateway endpoint (from `terraform output api_url`),
|
|
12
15
|
# NOT the core project's API — the frontend never calls the core API
|
|
13
16
|
# directly (ADR-0002/ADR-0007).
|
|
@@ -22,16 +22,16 @@ function userWithSession(session: unknown, err: Error | null = null) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
// The
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
function
|
|
29
|
-
vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_USER_POOL_ID', 'us-east-
|
|
30
|
-
vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_CLIENT_ID', '
|
|
25
|
+
// The sibling is DOC-ONLY (#403 Stage 3): it NEVER reads NEXT_PUBLIC_CORE_COGNITO_*.
|
|
26
|
+
// This helper only exists to set BOGUS env values in the "env is ignored" tests,
|
|
27
|
+
// proving the pool is never built from env even when it is present.
|
|
28
|
+
function stubBogusCognitoEnv() {
|
|
29
|
+
vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_USER_POOL_ID', 'us-east-1_BOGUSENV')
|
|
30
|
+
vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_CLIENT_ID', 'bogusenvclientid')
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
// A `fetch` returning a valid identity document. The ids
|
|
34
|
-
//
|
|
33
|
+
// A `fetch` returning a valid identity document. The ids are what the pool must
|
|
34
|
+
// be built from — the ONLY source of Cognito coordinates now.
|
|
35
35
|
function mockFetchDocument(overrides: Record<string, unknown> = {}) {
|
|
36
36
|
const body = {
|
|
37
37
|
userPoolId: 'us-east-1_DOCPOOL',
|
|
@@ -45,7 +45,8 @@ function mockFetchDocument(overrides: Record<string, unknown> = {}) {
|
|
|
45
45
|
}) as unknown as typeof fetch
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
// A `fetch` that rejects (network error) —
|
|
48
|
+
// A `fetch` that rejects (network error) — the document is unreachable, so the
|
|
49
|
+
// sibling resolves to null (no env fallback).
|
|
49
50
|
function mockFetchUnreachable() {
|
|
50
51
|
global.fetch = vi.fn().mockRejectedValue(new Error('network down')) as unknown as typeof fetch
|
|
51
52
|
}
|
|
@@ -53,7 +54,7 @@ function mockFetchUnreachable() {
|
|
|
53
54
|
// Each test re-imports the module fresh: both the pool (auth.ts) and the
|
|
54
55
|
// resolved-identity Promise (identity.ts) are memoised at module scope, so a
|
|
55
56
|
// module instance left over from a previous test would carry that test's
|
|
56
|
-
// document
|
|
57
|
+
// document into the next one.
|
|
57
58
|
async function loadAuth() {
|
|
58
59
|
vi.resetModules()
|
|
59
60
|
return await import('@/lib/auth')
|
|
@@ -62,11 +63,9 @@ async function loadAuth() {
|
|
|
62
63
|
describe('getCurrentSession', () => {
|
|
63
64
|
beforeEach(() => {
|
|
64
65
|
vi.clearAllMocks()
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
// pre-runtime version did.
|
|
69
|
-
mockFetchUnreachable()
|
|
66
|
+
// The document is reachable, so these behaviour tests build the pool from
|
|
67
|
+
// the DOCUMENT (us-east-1_DOCPOOL / docclientid) — never from env.
|
|
68
|
+
mockFetchDocument()
|
|
70
69
|
})
|
|
71
70
|
|
|
72
71
|
afterEach(() => {
|
|
@@ -100,9 +99,9 @@ describe('getCurrentSession', () => {
|
|
|
100
99
|
})
|
|
101
100
|
})
|
|
102
101
|
|
|
103
|
-
// The core
|
|
104
|
-
//
|
|
105
|
-
//
|
|
102
|
+
// The core publishes its Cognito coordinates at runtime; the sibling is
|
|
103
|
+
// DOC-ONLY (#403 Stage 3) and resolves them from that document alone. These
|
|
104
|
+
// tests pin that the document is the sole source — env is never consulted.
|
|
106
105
|
describe('runtime core identity resolution', () => {
|
|
107
106
|
beforeEach(() => {
|
|
108
107
|
vi.clearAllMocks()
|
|
@@ -113,9 +112,9 @@ describe('runtime core identity resolution', () => {
|
|
|
113
112
|
vi.restoreAllMocks()
|
|
114
113
|
})
|
|
115
114
|
|
|
116
|
-
it('builds the pool from the published document
|
|
117
|
-
//
|
|
118
|
-
|
|
115
|
+
it('builds the pool from the published document', async () => {
|
|
116
|
+
// Bogus env is set to prove the document — not env — is the source.
|
|
117
|
+
stubBogusCognitoEnv()
|
|
119
118
|
mockFetchDocument()
|
|
120
119
|
const { getCurrentSession } = await loadAuth()
|
|
121
120
|
getCurrentUser.mockReturnValue(null)
|
|
@@ -131,55 +130,44 @@ describe('runtime core identity resolution', () => {
|
|
|
131
130
|
})
|
|
132
131
|
})
|
|
133
132
|
|
|
134
|
-
it('
|
|
135
|
-
|
|
133
|
+
it('resolves null and never builds the pool from env when the document is unreachable', async () => {
|
|
134
|
+
// Env is present but BOGUS: a DOC-ONLY sibling must ignore it entirely.
|
|
135
|
+
stubBogusCognitoEnv()
|
|
136
136
|
mockFetchUnreachable()
|
|
137
137
|
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
138
138
|
const { getCurrentSession } = await loadAuth()
|
|
139
139
|
getCurrentUser.mockReturnValue(null)
|
|
140
140
|
|
|
141
|
-
await getCurrentSession()
|
|
142
|
-
|
|
143
|
-
expect(poolConstructor).
|
|
144
|
-
UserPoolId: 'us-east-1_TESTPOOL',
|
|
145
|
-
ClientId: 'testclientid',
|
|
146
|
-
})
|
|
141
|
+
await expect(getCurrentSession()).resolves.toBeNull()
|
|
142
|
+
// Proves env is ignored: the pool was never constructed from the bogus env.
|
|
143
|
+
expect(poolConstructor).not.toHaveBeenCalled()
|
|
147
144
|
expect(warn).toHaveBeenCalledOnce()
|
|
148
|
-
expect(warn.mock.calls[0]?.[0]).toContain('DEGRADED')
|
|
149
145
|
})
|
|
150
146
|
|
|
151
|
-
it('
|
|
152
|
-
|
|
147
|
+
it('resolves null and never builds the pool when the document is served but missing ids', async () => {
|
|
148
|
+
stubBogusCognitoEnv()
|
|
153
149
|
// ok:true but the body lacks a clientId — treated as unusable.
|
|
154
150
|
mockFetchDocument({ clientId: '' })
|
|
155
151
|
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
156
152
|
const { getCurrentSession } = await loadAuth()
|
|
157
153
|
getCurrentUser.mockReturnValue(null)
|
|
158
154
|
|
|
159
|
-
await getCurrentSession()
|
|
160
|
-
|
|
161
|
-
expect(poolConstructor).toHaveBeenCalledWith({
|
|
162
|
-
UserPoolId: 'us-east-1_TESTPOOL',
|
|
163
|
-
ClientId: 'testclientid',
|
|
164
|
-
})
|
|
155
|
+
await expect(getCurrentSession()).resolves.toBeNull()
|
|
156
|
+
expect(poolConstructor).not.toHaveBeenCalled()
|
|
165
157
|
expect(warn).toHaveBeenCalledOnce()
|
|
166
158
|
})
|
|
167
159
|
|
|
168
|
-
it('resolves null
|
|
169
|
-
vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_USER_POOL_ID', '')
|
|
170
|
-
vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_CLIENT_ID', '')
|
|
160
|
+
it('resolves null when the document is unreachable', async () => {
|
|
171
161
|
mockFetchUnreachable()
|
|
172
162
|
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
173
163
|
const { getCurrentSession } = await loadAuth()
|
|
174
164
|
|
|
175
165
|
await expect(getCurrentSession()).resolves.toBeNull()
|
|
176
166
|
expect(poolConstructor).not.toHaveBeenCalled()
|
|
177
|
-
|
|
178
|
-
expect(warn).not.toHaveBeenCalled()
|
|
167
|
+
expect(warn).toHaveBeenCalledOnce()
|
|
179
168
|
})
|
|
180
169
|
|
|
181
170
|
it('fetches the document at most once across multiple session reads (memoised)', async () => {
|
|
182
|
-
configureCognitoEnv()
|
|
183
171
|
mockFetchDocument()
|
|
184
172
|
const { getCurrentSession } = await loadAuth()
|
|
185
173
|
getCurrentUser.mockReturnValue(null)
|
|
@@ -210,14 +198,12 @@ describe('lazy pool construction', () => {
|
|
|
210
198
|
})
|
|
211
199
|
|
|
212
200
|
it('does not construct the pool merely by importing the module', async () => {
|
|
213
|
-
|
|
201
|
+
mockFetchDocument()
|
|
214
202
|
await loadAuth()
|
|
215
203
|
expect(poolConstructor).not.toHaveBeenCalled()
|
|
216
204
|
})
|
|
217
205
|
|
|
218
|
-
it('imports cleanly with no
|
|
219
|
-
vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_USER_POOL_ID', '')
|
|
220
|
-
vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_CLIENT_ID', '')
|
|
206
|
+
it('imports cleanly with no fetch available (next build prerender)', async () => {
|
|
221
207
|
// Simulate `next build` prerendering `/` in Node with no fetch in scope.
|
|
222
208
|
const originalFetch = global.fetch
|
|
223
209
|
// @ts-expect-error deliberately remove fetch to mimic the Node prerender.
|
|
@@ -230,16 +216,14 @@ describe('lazy pool construction', () => {
|
|
|
230
216
|
}
|
|
231
217
|
})
|
|
232
218
|
|
|
233
|
-
it('resolves null instead of throwing when
|
|
234
|
-
vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_USER_POOL_ID', '')
|
|
235
|
-
vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_CLIENT_ID', '')
|
|
219
|
+
it('resolves null instead of throwing when the document is unreachable', async () => {
|
|
236
220
|
const { getCurrentSession } = await loadAuth()
|
|
237
221
|
await expect(getCurrentSession()).resolves.toBeNull()
|
|
238
222
|
expect(poolConstructor).not.toHaveBeenCalled()
|
|
239
223
|
})
|
|
240
224
|
|
|
241
|
-
it('constructs the pool once, on first session read, from the
|
|
242
|
-
|
|
225
|
+
it('constructs the pool once, on first session read, from the document', async () => {
|
|
226
|
+
mockFetchDocument()
|
|
243
227
|
const { getCurrentSession } = await loadAuth()
|
|
244
228
|
getCurrentUser.mockReturnValue(null)
|
|
245
229
|
|
|
@@ -248,8 +232,8 @@ describe('lazy pool construction', () => {
|
|
|
248
232
|
|
|
249
233
|
expect(poolConstructor).toHaveBeenCalledTimes(1)
|
|
250
234
|
expect(poolConstructor).toHaveBeenCalledWith({
|
|
251
|
-
UserPoolId: 'us-east-
|
|
252
|
-
ClientId: '
|
|
235
|
+
UserPoolId: 'us-east-1_DOCPOOL',
|
|
236
|
+
ClientId: 'docclientid',
|
|
253
237
|
})
|
|
254
238
|
})
|
|
255
239
|
})
|
|
@@ -32,11 +32,12 @@ import { resolveCoreIdentity } from './identity'
|
|
|
32
32
|
// ---------------------------------------------------------------------------
|
|
33
33
|
// Module-private on purpose: the pool is an implementation detail of
|
|
34
34
|
// getCurrentSession(), not part of this sibling's auth surface. Its Cognito
|
|
35
|
-
// coordinates
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
35
|
+
// coordinates come from resolveCoreIdentity() (identity.ts), which resolves the
|
|
36
|
+
// core's runtime-published /.well-known/biffo-identity.json document. The
|
|
37
|
+
// sibling is now DOC-ONLY (#403 Stage 3): there is NO NEXT_PUBLIC_CORE_COGNITO_*
|
|
38
|
+
// env fallback. An unreachable document resolves to null → the caller treats it
|
|
39
|
+
// as "signed out" — see identity.ts for why runtime resolution kills the
|
|
40
|
+
// stale-baked-pool bug class (#403/#400).
|
|
40
41
|
//
|
|
41
42
|
// Constructed LAZILY, on first session read — never at module scope. The
|
|
42
43
|
// CognitoUserPool constructor throws ("Both UserPoolId and ClientId are
|
|
@@ -29,12 +29,13 @@
|
|
|
29
29
|
// this exactly ONE network request per page load, shared by every caller,
|
|
30
30
|
// rather than one per session read.
|
|
31
31
|
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
32
|
+
// A sibling is DOC-ONLY (#403 Stage 3): it carries NO CORE_COGNITO_* env vars —
|
|
33
|
+
// `biffo sibling create` no longer sets them and this module no longer reads
|
|
34
|
+
// them. When the document is unreachable, resolution returns null, and the
|
|
35
|
+
// caller (getCurrentSession) treats that as "no session" — the sibling redirects
|
|
36
|
+
// to the portal's login rather than trusting a stale baked value, because a
|
|
37
|
+
// stale value is exactly the bug (#400) this removes. A clean redirect beats a
|
|
38
|
+
// silent point at a dead pool.
|
|
38
39
|
// ---------------------------------------------------------------------------
|
|
39
40
|
|
|
40
41
|
export interface CoreIdentity {
|
|
@@ -54,16 +55,6 @@ const IDENTITY_DOCUMENT_PATH = '/.well-known/biffo-identity.json'
|
|
|
54
55
|
// callers before the fetch settles also coalesce onto the one request.
|
|
55
56
|
let cached: Promise<CoreIdentity | null> | null = null
|
|
56
57
|
|
|
57
|
-
// Read the transitional env fallback. Returns a valid CoreIdentity only when
|
|
58
|
-
// BOTH ids are present; otherwise null (an unconfigured build has neither a
|
|
59
|
-
// document nor env, and that is not an error to shout about).
|
|
60
|
-
function identityFromEnv(): CoreIdentity | null {
|
|
61
|
-
const userPoolId = process.env['NEXT_PUBLIC_CORE_COGNITO_USER_POOL_ID'] ?? ''
|
|
62
|
-
const clientId = process.env['NEXT_PUBLIC_CORE_COGNITO_CLIENT_ID'] ?? ''
|
|
63
|
-
if (!userPoolId || !clientId) return null
|
|
64
|
-
return { userPoolId, clientId }
|
|
65
|
-
}
|
|
66
|
-
|
|
67
58
|
// A parsed document counts only when it carries both ids non-empty; a document
|
|
68
59
|
// missing either is treated as unusable and triggers the fallback.
|
|
69
60
|
function identityFromDocument(data: unknown): CoreIdentity | null {
|
|
@@ -90,38 +81,31 @@ async function fetchCoreIdentity(): Promise<CoreIdentity | null> {
|
|
|
90
81
|
const identity = identityFromDocument(await res.json())
|
|
91
82
|
if (identity) return identity
|
|
92
83
|
}
|
|
93
|
-
//
|
|
94
|
-
// missing ids). Fall through to the degraded env fallback below.
|
|
84
|
+
// Served but unusable (non-ok status or missing ids). Fall through to null.
|
|
95
85
|
} catch {
|
|
96
86
|
// Network error, or `fetch` not available (e.g. `next build` prerendering
|
|
97
|
-
//
|
|
87
|
+
// in Node with no fetch — though resolution is lazy and never runs there).
|
|
88
|
+
// Fall through to null.
|
|
98
89
|
}
|
|
99
90
|
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
`${IDENTITY_DOCUMENT_PATH}; falling back to baked ` +
|
|
110
|
-
'NEXT_PUBLIC_CORE_COGNITO_* env vars. This is transitional (#403) and ' +
|
|
111
|
-
'risks pointing at a stale/dead Cognito pool — ensure the core is ' +
|
|
112
|
-
'publishing the runtime identity document.',
|
|
113
|
-
)
|
|
114
|
-
}
|
|
115
|
-
return fallback
|
|
91
|
+
// The document was unreachable or unusable. A sibling has NO baked fallback
|
|
92
|
+
// (#403 Stage 3), so this resolves to null and the caller treats it as
|
|
93
|
+
// "signed out" — a clean redirect to the portal login beats trusting a stale
|
|
94
|
+
// local pool id, which is the exact bug (#400) this removes.
|
|
95
|
+
console.warn(
|
|
96
|
+
`[biffo] could not resolve the core identity document at ${IDENTITY_DOCUMENT_PATH}; ` +
|
|
97
|
+
'treating the visitor as signed out. If this persists, confirm the core is publishing it.',
|
|
98
|
+
)
|
|
99
|
+
return null
|
|
116
100
|
}
|
|
117
101
|
|
|
118
102
|
/**
|
|
119
|
-
* Resolve the core's Cognito identity at runtime
|
|
120
|
-
* `/.well-known/biffo-identity.json` document
|
|
121
|
-
*
|
|
103
|
+
* Resolve the core's Cognito identity at runtime from the published
|
|
104
|
+
* `/.well-known/biffo-identity.json` document. A sibling has no env fallback
|
|
105
|
+
* (#403 Stage 3).
|
|
122
106
|
*
|
|
123
107
|
* Memoised: at most one fetch per page load, shared by every caller. Returns
|
|
124
|
-
* null when
|
|
108
|
+
* null when the document is unreachable or does not supply both ids.
|
|
125
109
|
*/
|
|
126
110
|
export function resolveCoreIdentity(): Promise<CoreIdentity | null> {
|
|
127
111
|
cached ??= fetchCoreIdentity()
|