forked from OurHike/OurHike
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexpected-origins.yml
More file actions
257 lines (245 loc) · 13.3 KB
/
Copy pathexpected-origins.yml
File metadata and controls
257 lines (245 loc) · 13.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# Every origin the app is served from, and the CORS contract the data bucket
# has to honour for each of them.
#
# THE FAILURE THIS EXISTS TO PREVENT (#427, #431). The R2 bucket's CORS
# allow-list held the preview origins and `localhost` but not
# `https://ourhike.github.io`. Every data fetch from production was refused by
# the browser, so the deployed app drew a topo sheet with no Appalachian Trail
# on it - for eight days, while the published data was correct the whole time
# and every check in this repository stayed green.
#
# They stayed green because none of them is a browser. `Origin` is the single
# header that decides whether a browser may read a response, and no check
# anywhere sent one: a ranged GET with no `Origin` returned 206 with
# `Content-Range`, `ETag` and `Accept-Ranges` intact throughout the outage.
# pipeline/DATA_RELEASES.md had already written down that "a CORS regression
# silently disarms the `If-Range` comparison on real devices while CI, which is
# not a browser, would never notice." It named this blind spot in advance and we
# shipped past it.
#
# WHY A FILE HERE AND NOT PROSE IN LAUNCH_CHECKLIST.md. The rule that the R2
# and Supabase allow-lists have to move together was written down, in prose,
# before the outage - and prose is what got us here: previews moved to
# `pages.dev`, both lists gained the wildcard, one of them lost production, and
# nothing disagreed. `.github/expected-settings.yml` is the precedent for the
# fix. It turned "here is what should be on the settings page" into something a
# test reads, and this is the same move for configuration that lives in
# Cloudflare and Supabase rather than on GitHub's settings page.
#
# ONE HOME PER ITEM (CONTRIBUTING.md). This file is the declaration;
# LAUNCH_CHECKLIST.md §1.4 and §4.3b stay the prose instructions for pasting it
# into each dashboard. The policy JSON that used to be embedded in
# LAUNCH_CHECKLIST.md is now generated from this file - run
# `python pipeline/check_deployment.py --print-cors-policy` and paste what it
# prints, rather than hand-editing a copy that can drift from the copy the
# check reads.
#
# WHO READS THIS: `pipeline/check_deployment.py` (the daily origin-aware
# reachability check) and `pipeline/check_auth_redirects.py` (the same question
# asked of Supabase's redirect allow-list). Two readers is the point of the
# declaration - #431 tier 3 - and the second one earned it immediately: on its
# first run it found production's allow-list and Site URL still naming
# `jaimito-asuntos-gringuenos.github.io`, the pre-org-migration Pages host,
# which by then answered 404. Every sign-in from production was redirecting to
# a dead host carrying the auth code. Exactly #427's shape - an allow-list that
# did not move when the origin did - on the auth surface rather than the data
# one.
# The request headers the client actually sends to the data bucket, which is a
# different list from the one a reader would guess, and getting it wrong breaks
# something that only breaks on a phone.
#
# `range` is CORS-safelisted for simple byte ranges, so a FIRST download needs
# no preflight and works even if this list is wrong. `if-range` is NOT
# safelisted: a browser sending it must pass an `OPTIONS` preflight whose
# `Access-Control-Allow-Headers` names it. The client sends `If-Range` on every
# RESUME (client/src/lib/archiveDownload.ts) - it is what makes the server
# itself arbitrate a stale partial rather than splicing old bytes onto new.
#
# So a missing `if-range` here is invisible until a hiker resumes an
# interrupted 1.18 GB download, which is precisely the case the resume exists
# for and precisely where it cannot be debugged. LAUNCH_CHECKLIST.md's embedded
# copy carried `if-match` - a header nothing in this repository has ever sent -
# and not `if-range`. That is the drift this file exists to end.
#
# Measured against the live bucket on 2026-08-09, rather than inferred: a
# preflight for `if-match` was answered 204, and one for `if-range` **403
# Forbidden**. The bucket has since been re-pasted from this file - re-measured
# 2026-08-12 on all four origins below, `if-range` is answered 204 and
# `if-match` is the one refused, which is this file's list exactly.
#
# Being ALLOWED to send `If-Range` is not the same as the server obeying it,
# and this bucket does the first and not the second (#506): a stale validator
# is answered 206 with the range served, where RFC 9110 requires 200. That is
# not this file's subject - nothing in a CORS policy can fix it - and
# `archiveDownload.ts` makes the comparison client-side because of it.
#
# Since #566 the two live one file apart rather than one repository apart:
# `check_deployment.py`, which reads THIS file for its CORS checks, also asks
# the `If-Range` question in the same daily run. It was `verify_release.py`
# check 7 until then, where it failed on every release for a reason no release
# could affect. The `if-range` entry above is what lets a browser send the
# header at all; whether the server then obeys it is the other check's, and
# both being green is what the resume actually needs.
#
# `content-type` was in the old hand-kept copy and is deliberately not here:
# every request to this bucket is a GET or a HEAD with no body, and a request
# with no body sends no `Content-Type`. Allowing a header nothing sends is not
# harmful, but it is one more line that reads as load-bearing to the next
# person deciding what is safe to remove.
request_headers:
- range
- if-range
# The response headers a browser must be allowed to READ. Not the same thing as
# the headers being present: R2 sent all four throughout the outage, and a
# browser still could not see them.
#
# `content-range` and `etag` are load-bearing rather than informational. The
# resumable download reads `content-range` to know whether the server honoured
# a range request at all, and treats a missing one as "start over" rather than
# corrupting the file; `etag` is what `if-range` is compared against.
expose_headers:
- accept-ranges
- content-length
- content-range
- etag
# The methods the app uses. It never writes to this bucket - publishing is a
# workflow holding credentials no browser has - so anything beyond these two
# would be a widening nobody asked for.
methods:
- GET
- HEAD
# How long a browser may cache a preflight. Low enough that fixing a bad policy
# is not followed by an hour of stale refusals.
max_age_seconds: 3600
# The Supabase projects whose redirect allow-lists have to agree with the
# origins below, and how each one is reached.
#
# TWO PROJECTS, TWO GENUINELY DIFFERENT LISTS - which is why this is a mapping
# rather than one list reused twice. Production allows the Pages origin, the
# preview wildcard and both localhost ports. UA allows only its own exact
# hostname and the localhosts: pull-request previews build against
# PRODUCTION's Supabase project, so putting the preview wildcard on UA's list
# would let any preview hostname complete a sign-in against the UA user pool
# and buy nothing in exchange.
#
# `site_url_origin` names which origin is that project's Site URL - the single
# exact URL Supabase falls back to when a redirect is refused. It is worth
# checking precisely because it is the *fallback*: a wrong one turns "this
# redirect is not allowed" into a silent trip to somewhere else, which is how
# the pre-migration host went unnoticed. `check_auth_redirects.py` reads it
# back for free, since a deliberately-refused probe lands there.
supabase_projects:
production:
url_var: SUPABASE_URL
key_var: SUPABASE_ANON_KEY
# Moved to the custom domain with the app (#733). The Site URL is the
# FALLBACK - where Supabase sends a redirect it refused - so leaving it on
# a host that now answers 301 would put an auth round trip through a
# redirect on the one path that is hardest to debug and easiest to believe
# is working. Whether a 301 preserves the code is not something this
# repository has measured, and the Site URL is the wrong place to find out.
#
# This is the entry that makes the Supabase dashboard change ORDERING
# rather than cleanup: `check_auth_redirects.py` reads the live Site URL
# back on every run, so this file and the dashboard disagree - loudly, in
# the daily check - from the moment this lands until someone changes it
# there. Change it there first.
site_url_origin: https://ourhike.org
serves: The project hikers sign in to.
ua:
url_var: UA_SUPABASE_URL
key_var: UA_SUPABASE_ANON_KEY
site_url_origin: https://ua.ourhike-preview.pages.dev
# `ua.yml` uses the UA_ pair or neither: with them unset UA signs in
# against production's project instead, which is a supported fallback
# (RELEASING.md 3d) and the reason this project being unconfigured is a
# skip rather than a failure.
serves: The release candidate's project, so a UA tester is not a hiker.
origins:
- pattern: https://ourhike.org
probe: https://ourhike.org
# Where the app itself is served on this origin - `VITE_BASE_PATH` in the
# workflow that builds for it. Not decoration: an auth redirect targets the
# app rather than the origin root, and a redirect to the bare origin lands
# on the landing page with the code in its URL and nothing there to read
# it. It is also what makes the probe concrete.
app_path: /app/
supabase: [production]
serves: The deployed app and landing page, on GitHub Pages via ourhike.org.
# The one whose absence is an outage rather than an inconvenience: it is
# the origin every hiker is on.
hiker_facing: true
- pattern: https://ourhike.github.io
probe: https://ourhike.github.io
# Still `/OurHike/app/`, even though nothing is BUILT for this path any
# more. GitHub Pages answers the project-site path with a 301 to the custom
# domain once `site/CNAME` is live, and a bookmark has to be able to
# complete that hop rather than be refused at it.
app_path: /OurHike/app/
supabase: [production]
serves: The pre-#733 origin - redirects for a browser, still home to older installs.
# STILL hiker_facing, which is the opposite of what "we moved" suggests and
# is the more careful answer.
#
# @unvalidated - reasoned from how service workers and per-origin storage
# work, not measured against a real install, and worth measuring before
# this entry is retired. A PWA installed before the move has its service
# worker registered on THIS origin and its downloaded archive in THIS
# origin's IndexedDB. Opening it should serve the cached shell and read the
# map a hiker already has - none of which the 301 touches, because a
# service worker update request that redirects cross-origin fails rather
# than following. If that is right, these installs keep working here and
# keep fetching from R2 with this Origin, and a CORS regression on it is a
# hiker who cannot resume a download rather than a broken redirect.
#
# Being wrong in this direction costs a check reported as blocking when it
# was merely inconvenient. Being wrong in the other direction is #427,
# narrowed to whoever installed early. What would settle it: open an
# install made before the cutover, offline, after the domain moves.
#
# Retiring this entry is a separate change and needs that measurement plus
# evidence the installs are gone - not a guess about how long is long
# enough.
hiker_facing: true
- pattern: https://*.ourhike-preview.pages.dev
# A wildcard cannot be probed as itself - `*` is not a hostname a browser
# would ever send. So the check sends a CONCRETE origin the wildcard is
# supposed to match, which is the only way to learn whether the rule
# actually covers a pull request that does not exist yet. `pr-1` is chosen
# for being permanent in shape and meaningless in particular.
probe: https://pr-1.ourhike-preview.pages.dev
app_path: /
supabase: [production]
serves: Pull-request previews, on Cloudflare Pages.
# Losing this costs reviewers a preview, not hikers a map. It is still
# checked, because a preview that cannot download is how a change gets
# reviewed as working when it is not.
hiker_facing: false
- pattern: https://ua.ourhike-preview.pages.dev
probe: https://ua.ourhike-preview.pages.dev
app_path: /
supabase: [ua]
# Deliberately absent from the bucket policy: the preview wildcard above
# already covers this hostname, and `ua.yml` says so as the reason UA needs
# no new CORS entry. Listing it again would be a second entry for one fact.
# Supabase is the opposite case - UA's project must name this host exactly,
# because it must NOT carry the wildcard.
cors: false
serves: The release candidate, on the previews' Pages project.
hiker_facing: false
- pattern: http://localhost:5173
probe: http://localhost:5173
app_path: /
supabase: [production, ua]
serves: "`npm run dev` - Vite's dev server."
hiker_facing: false
- pattern: http://localhost:4173
probe: http://localhost:4173
app_path: /
supabase: [production, ua]
# A different server from 5173, not a second guess at the same one:
# `npm run preview` serves the BUILT bundle. It is the one you reach for to
# check something behaves the same after a production build, which makes it
# exactly the wrong one to have working differently from the others.
serves: "`npm run preview` - the built bundle."
hiker_facing: false