@aglyn/aglyn 1.0.0-beta.253 → 1.0.0-beta.255

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.
Files changed (29) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/business-profile-values.d.ts +83 -0
  3. package/src/lib/app-utils/business-profile-values.js +160 -0
  4. package/src/lib/app-utils/business-profile-values.js.map +1 -0
  5. package/src/lib/app-utils/business-profile.d.ts +7 -60
  6. package/src/lib/app-utils/business-profile.js +2 -172
  7. package/src/lib/app-utils/business-profile.js.map +1 -1
  8. package/src/lib/app-utils/docs-help-sections.generated.js +8 -5
  9. package/src/lib/app-utils/docs-help-sections.generated.js.map +1 -1
  10. package/src/lib/app-utils/docs-help.generated.d.ts +13 -7
  11. package/src/lib/app-utils/docs-help.generated.js +53 -16
  12. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  13. package/src/lib/app-utils/docs-index.generated.js +237 -6
  14. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  15. package/src/lib/app-utils/lockdown.d.ts +6 -0
  16. package/src/lib/app-utils/lockdown.js +20 -0
  17. package/src/lib/app-utils/lockdown.js.map +1 -1
  18. package/src/lib/app-utils/native-app-association.d.ts +129 -0
  19. package/src/lib/app-utils/native-app-association.js +248 -0
  20. package/src/lib/app-utils/native-app-association.js.map +1 -0
  21. package/src/lib/app-utils/sso-enforcement.d.ts +69 -0
  22. package/src/lib/app-utils/sso-enforcement.js +66 -0
  23. package/src/lib/app-utils/sso-enforcement.js.map +1 -0
  24. package/src/lib/app-utils/time-zone-choices.d.ts +46 -0
  25. package/src/lib/app-utils/time-zone-choices.js +469 -0
  26. package/src/lib/app-utils/time-zone-choices.js.map +1 -0
  27. package/src/lib/data-transfer/site-package-diff.d.ts +108 -0
  28. package/src/lib/data-transfer/site-package-diff.js +268 -0
  29. package/src/lib/data-transfer/site-package-diff.js.map +1 -0
@@ -0,0 +1,248 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /*==========================================
17
+ * THE DOCUMENTS THAT TIE THE NATIVE APPS TO A DOMAIN (AGL-3817).
18
+ *
19
+ * Apple's `apple-app-site-association` and Google's `assetlinks.json` are how
20
+ * a domain says which apps may use its passkeys (`webcredentials`,
21
+ * `get_login_creds`) and open its links (`applinks`, `handle_all_urls`). Both
22
+ * are fetched by the platform's verifier, not by a person, so each has to be
23
+ * exactly `application/json`, a 200, no redirect and no cookie.
24
+ *
25
+ * Everything that names an app comes from the environment, never from here:
26
+ *
27
+ * - `NATIVE_APPLE_APP_IDS`: comma-separated `<team id>.<bundle id>` values,
28
+ * in the order the system should prefer them;
29
+ * - `NATIVE_ANDROID_APPS`: `;`-separated `<package>=<sha256>,<sha256>`
30
+ * entries, each fingerprint the colon-separated hex Play Console prints.
31
+ *
32
+ * Unset (or nothing valid in it), the document answers 404: a deployment
33
+ * with no apps of its own vouches for none.
34
+ *
35
+ * A leaf with no imports, because both middlewares read the paths below.
36
+ *=========================================*/ /** Where Apple's verifier reads the association file: no `.json` suffix. */ export const APPLE_APP_SITE_ASSOCIATION_PATH = '/.well-known/apple-app-site-association';
37
+ /** Where Google's verifier reads the statement list. */ export const ASSET_LINKS_PATH = '/.well-known/assetlinks.json';
38
+ /**
39
+ * The route handlers each middleware rewrites the two public paths to. They
40
+ * live under `/api` because both apps keep their route handlers there, and
41
+ * neither middleware runs again on `/api`.
42
+ */ export const NATIVE_APP_ASSOCIATION_ROUTES = {
43
+ [APPLE_APP_SITE_ASSOCIATION_PATH]: '/api/native-app-association/apple-app-site-association',
44
+ [ASSET_LINKS_PATH]: '/api/native-app-association/assetlinks'
45
+ };
46
+ /** The route handler a public association path is served by, or null. */ export function nativeAppAssociationRoute(pathname) {
47
+ return Object.prototype.hasOwnProperty.call(NATIVE_APP_ASSOCIATION_ROUTES, pathname) ? NATIVE_APP_ASSOCIATION_ROUTES[pathname] : null;
48
+ }
49
+ /**
50
+ * The console's top-level sections the native apps have no screen for, so a
51
+ * link to one stays in the browser. They are the sign-in and verification
52
+ * flows (an emailed verify or reset link opened by an app that cannot finish
53
+ * it would strand the person), the sign-out page, the site admin bar's
54
+ * connect page and a plugin's public device pages.
55
+ *
56
+ * The rest of the console's top-level sections (`admin`, `billing`,
57
+ * `manage`, `support`) and every workspace path are what the native
58
+ * deep-link grammar resolves (`DeepLinks.swift`, `DeepLinks.kt`): a
59
+ * registered screen, the Besigner in the app's own web view, or the nearest
60
+ * native screen. `apps/console/specs/native-app-association.spec.ts` holds
61
+ * this list to the console's route set and the native link specs.
62
+ */ export const NATIVE_WEB_ONLY_TOP_LEVEL_SEGMENTS = [
63
+ 'account-recovery',
64
+ 'auth',
65
+ 'edit-access',
66
+ 'kiosk',
67
+ 'reset-password',
68
+ 'signin',
69
+ 'signout',
70
+ 'signup',
71
+ 'sso',
72
+ 'verify-email'
73
+ ];
74
+ /**
75
+ * The console paths the apps open, as `applinks` components. Apple reads
76
+ * them in order and the first match wins, so every exclusion comes ahead of
77
+ * the closing catch-all.
78
+ */ export function consoleAppLinksComponents() {
79
+ const excluded = [
80
+ {
81
+ '/': '/_*',
82
+ exclude: true,
83
+ comment: 'framework and internal paths'
84
+ },
85
+ {
86
+ '/': '/api',
87
+ exclude: true
88
+ },
89
+ {
90
+ '/': '/api/*',
91
+ exclude: true,
92
+ comment: 'route handlers'
93
+ },
94
+ {
95
+ '/': '/.well-known/*',
96
+ exclude: true
97
+ }
98
+ ];
99
+ for (const segment of NATIVE_WEB_ONLY_TOP_LEVEL_SEGMENTS){
100
+ excluded.push({
101
+ '/': `/${segment}`,
102
+ exclude: true
103
+ }, {
104
+ '/': `/${segment}/*`,
105
+ exclude: true
106
+ });
107
+ }
108
+ return [
109
+ ...excluded,
110
+ {
111
+ '/': '/*',
112
+ comment: 'workspace, site, account, billing, support and staff pages'
113
+ }
114
+ ];
115
+ }
116
+ /** `TEAMID1234.com.example.app`: a 10-character team id, then a bundle id. */ const APPLE_APP_ID = /^[A-Z0-9]{10}\.[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)+$/;
117
+ /** A Java package name: dot-separated identifiers, at least two. */ const ANDROID_PACKAGE = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/;
118
+ /** A SHA-256 certificate fingerprint as 32 colon-separated hex pairs. */ const SHA256_FINGERPRINT = /^([0-9A-F]{2}:){31}[0-9A-F]{2}$/;
119
+ /** The Apple app ids in `NATIVE_APPLE_APP_IDS`, in order; invalid ones dropped. */ export function parseAppleAppIds(raw) {
120
+ const ids = (raw != null ? raw : '').split(',').map((value)=>value.trim()).filter((value)=>APPLE_APP_ID.test(value));
121
+ return [
122
+ ...new Set(ids)
123
+ ];
124
+ }
125
+ /**
126
+ * The Android apps in `NATIVE_ANDROID_APPS`. An entry without a valid package
127
+ * or without one valid fingerprint is dropped whole: a statement with no
128
+ * certificate verifies nothing.
129
+ */ export function parseAndroidApps(raw) {
130
+ const apps = [];
131
+ for (const entry of (raw != null ? raw : '').split(';')){
132
+ const at = entry.indexOf('=');
133
+ if (at < 0) continue;
134
+ const packageName = entry.slice(0, at).trim();
135
+ if (!ANDROID_PACKAGE.test(packageName)) continue;
136
+ if (apps.some((app)=>app.packageName === packageName)) continue;
137
+ const fingerprints = entry.slice(at + 1).split(',').map((value)=>value.trim().toUpperCase()).filter((value)=>SHA256_FINGERPRINT.test(value));
138
+ if (fingerprints.length === 0) continue;
139
+ apps.push({
140
+ packageName,
141
+ sha256Fingerprints: [
142
+ ...new Set(fingerprints)
143
+ ]
144
+ });
145
+ }
146
+ return apps;
147
+ }
148
+ /**
149
+ * The association file for these app ids. `applinks` is only for a host
150
+ * whose paths the native deep-link grammar resolves: the console. A host
151
+ * that serves the passkey relying party alone gets `webcredentials` only.
152
+ *
153
+ * One `details` entry per app, in the configured order, because when two
154
+ * installed apps claim the same path the system takes the first.
155
+ */ export function appleAppSiteAssociation(appIds, options) {
156
+ const document = {
157
+ webcredentials: {
158
+ apps: [
159
+ ...appIds
160
+ ]
161
+ }
162
+ };
163
+ if (options.applinks) {
164
+ const components = consoleAppLinksComponents();
165
+ document.applinks = {
166
+ apps: [],
167
+ details: appIds.map((appId)=>({
168
+ appIDs: [
169
+ appId
170
+ ],
171
+ components
172
+ }))
173
+ };
174
+ }
175
+ return document;
176
+ }
177
+ /** Passkeys and app links, for every configured app. */ export const ASSET_LINK_RELATIONS = [
178
+ 'delegate_permission/common.handle_all_urls',
179
+ 'delegate_permission/common.get_login_creds'
180
+ ];
181
+ /** The `assetlinks.json` statement list Google's verifier reads. */ export function assetLinks(apps) {
182
+ return apps.map((app)=>({
183
+ relation: [
184
+ ...ASSET_LINK_RELATIONS
185
+ ],
186
+ target: {
187
+ namespace: 'android_app',
188
+ package_name: app.packageName,
189
+ sha256_cert_fingerprints: [
190
+ ...app.sha256Fingerprints
191
+ ]
192
+ }
193
+ }));
194
+ }
195
+ /** The request's hostname: lowercase, no port. */ export function requestHostname(host) {
196
+ return (host != null ? host : '').trim().toLowerCase().replace(/:\d+$/, '');
197
+ }
198
+ /** A URL's hostname, or null when it is not one. */ export function hostnameOfUrl(url) {
199
+ if (!url) return null;
200
+ try {
201
+ return new URL(url).hostname.toLowerCase() || null;
202
+ } catch (unused) {
203
+ return null;
204
+ }
205
+ }
206
+ /** Short on purpose: Apple's CDN caches on top, and a fix must land within minutes. */ const CACHE_CONTROL = 'public, max-age=300, s-maxage=300';
207
+ /**
208
+ * Whether this host may answer. A Vercel preview deployment is reachable only
209
+ * at the platform's own generated hostnames, so it answers on any host, which
210
+ * is what lets a preview be checked before it is promoted.
211
+ */ function servesHost(request) {
212
+ if (request.env['VERCEL_ENV'] === 'preview') return true;
213
+ const hostname = requestHostname(request.host);
214
+ if (!hostname) return false;
215
+ return request.servedHosts.some((served)=>requestHostname(served) === hostname);
216
+ }
217
+ function notFound() {
218
+ return new Response('Not Found', {
219
+ status: 404,
220
+ headers: {
221
+ 'content-type': 'text/plain; charset=utf-8',
222
+ 'cache-control': CACHE_CONTROL
223
+ }
224
+ });
225
+ }
226
+ function json(body) {
227
+ return new Response(JSON.stringify(body), {
228
+ status: 200,
229
+ headers: {
230
+ 'content-type': 'application/json',
231
+ 'cache-control': CACHE_CONTROL
232
+ }
233
+ });
234
+ }
235
+ /** `GET /.well-known/apple-app-site-association`. */ export function appleAppSiteAssociationResponse(request) {
236
+ const appIds = parseAppleAppIds(request.env['NATIVE_APPLE_APP_IDS']);
237
+ if (appIds.length === 0 || !servesHost(request)) return notFound();
238
+ return json(appleAppSiteAssociation(appIds, {
239
+ applinks: request.applinks
240
+ }));
241
+ }
242
+ /** `GET /.well-known/assetlinks.json`. */ export function assetLinksResponse(request) {
243
+ const apps = parseAndroidApps(request.env['NATIVE_ANDROID_APPS']);
244
+ if (apps.length === 0 || !servesHost(request)) return notFound();
245
+ return json(assetLinks(apps));
246
+ }
247
+
248
+ //# sourceMappingURL=native-app-association.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/native-app-association.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * THE DOCUMENTS THAT TIE THE NATIVE APPS TO A DOMAIN (AGL-3817).\n *\n * Apple's `apple-app-site-association` and Google's `assetlinks.json` are how\n * a domain says which apps may use its passkeys (`webcredentials`,\n * `get_login_creds`) and open its links (`applinks`, `handle_all_urls`). Both\n * are fetched by the platform's verifier, not by a person, so each has to be\n * exactly `application/json`, a 200, no redirect and no cookie.\n *\n * Everything that names an app comes from the environment, never from here:\n *\n * - `NATIVE_APPLE_APP_IDS`: comma-separated `<team id>.<bundle id>` values,\n * in the order the system should prefer them;\n * - `NATIVE_ANDROID_APPS`: `;`-separated `<package>=<sha256>,<sha256>`\n * entries, each fingerprint the colon-separated hex Play Console prints.\n *\n * Unset (or nothing valid in it), the document answers 404: a deployment\n * with no apps of its own vouches for none.\n *\n * A leaf with no imports, because both middlewares read the paths below.\n *=========================================*/\n\n/** Where Apple's verifier reads the association file: no `.json` suffix. */\nexport const APPLE_APP_SITE_ASSOCIATION_PATH = '/.well-known/apple-app-site-association'\n\n/** Where Google's verifier reads the statement list. */\nexport const ASSET_LINKS_PATH = '/.well-known/assetlinks.json'\n\n/**\n * The route handlers each middleware rewrites the two public paths to. They\n * live under `/api` because both apps keep their route handlers there, and\n * neither middleware runs again on `/api`.\n */\nexport const NATIVE_APP_ASSOCIATION_ROUTES: Readonly<Record<string, string>> = {\n [APPLE_APP_SITE_ASSOCIATION_PATH]: '/api/native-app-association/apple-app-site-association',\n [ASSET_LINKS_PATH]: '/api/native-app-association/assetlinks',\n}\n\n/** The route handler a public association path is served by, or null. */\nexport function nativeAppAssociationRoute(pathname: string): string | null {\n return Object.prototype.hasOwnProperty.call(NATIVE_APP_ASSOCIATION_ROUTES, pathname)\n ? NATIVE_APP_ASSOCIATION_ROUTES[pathname]\n : null\n}\n\n/**\n * The console's top-level sections the native apps have no screen for, so a\n * link to one stays in the browser. They are the sign-in and verification\n * flows (an emailed verify or reset link opened by an app that cannot finish\n * it would strand the person), the sign-out page, the site admin bar's\n * connect page and a plugin's public device pages.\n *\n * The rest of the console's top-level sections (`admin`, `billing`,\n * `manage`, `support`) and every workspace path are what the native\n * deep-link grammar resolves (`DeepLinks.swift`, `DeepLinks.kt`): a\n * registered screen, the Besigner in the app's own web view, or the nearest\n * native screen. `apps/console/specs/native-app-association.spec.ts` holds\n * this list to the console's route set and the native link specs.\n */\nexport const NATIVE_WEB_ONLY_TOP_LEVEL_SEGMENTS: readonly string[] = [\n 'account-recovery',\n 'auth',\n 'edit-access',\n 'kiosk',\n 'reset-password',\n 'signin',\n 'signout',\n 'signup',\n 'sso',\n 'verify-email',\n]\n\n/** One `applinks` component: a URL path pattern, optionally excluded. */\nexport interface AppLinksComponent {\n '/': string\n exclude?: true\n comment?: string\n}\n\n/**\n * The console paths the apps open, as `applinks` components. Apple reads\n * them in order and the first match wins, so every exclusion comes ahead of\n * the closing catch-all.\n */\nexport function consoleAppLinksComponents(): AppLinksComponent[] {\n const excluded: AppLinksComponent[] = [\n { '/': '/_*', exclude: true, comment: 'framework and internal paths' },\n { '/': '/api', exclude: true },\n { '/': '/api/*', exclude: true, comment: 'route handlers' },\n { '/': '/.well-known/*', exclude: true },\n ]\n for (const segment of NATIVE_WEB_ONLY_TOP_LEVEL_SEGMENTS) {\n excluded.push({ '/': `/${segment}`, exclude: true }, { '/': `/${segment}/*`, exclude: true })\n }\n return [\n ...excluded,\n { '/': '/*', comment: 'workspace, site, account, billing, support and staff pages' },\n ]\n}\n\n/** `TEAMID1234.com.example.app`: a 10-character team id, then a bundle id. */\nconst APPLE_APP_ID = /^[A-Z0-9]{10}\\.[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)+$/\n\n/** A Java package name: dot-separated identifiers, at least two. */\nconst ANDROID_PACKAGE = /^[A-Za-z][A-Za-z0-9_]*(\\.[A-Za-z][A-Za-z0-9_]*)+$/\n\n/** A SHA-256 certificate fingerprint as 32 colon-separated hex pairs. */\nconst SHA256_FINGERPRINT = /^([0-9A-F]{2}:){31}[0-9A-F]{2}$/\n\n/** The Apple app ids in `NATIVE_APPLE_APP_IDS`, in order; invalid ones dropped. */\nexport function parseAppleAppIds(raw: string | undefined): string[] {\n const ids = (raw ?? '')\n .split(',')\n .map((value) => value.trim())\n .filter((value) => APPLE_APP_ID.test(value))\n return [...new Set(ids)]\n}\n\n/** One Android app and the signing certificates it may be verified by. */\nexport interface AndroidAppAssociation {\n packageName: string\n sha256Fingerprints: string[]\n}\n\n/**\n * The Android apps in `NATIVE_ANDROID_APPS`. An entry without a valid package\n * or without one valid fingerprint is dropped whole: a statement with no\n * certificate verifies nothing.\n */\nexport function parseAndroidApps(raw: string | undefined): AndroidAppAssociation[] {\n const apps: AndroidAppAssociation[] = []\n for (const entry of (raw ?? '').split(';')) {\n const at = entry.indexOf('=')\n if (at < 0) continue\n const packageName = entry.slice(0, at).trim()\n if (!ANDROID_PACKAGE.test(packageName)) continue\n if (apps.some((app) => app.packageName === packageName)) continue\n const fingerprints = entry\n .slice(at + 1)\n .split(',')\n .map((value) => value.trim().toUpperCase())\n .filter((value) => SHA256_FINGERPRINT.test(value))\n if (fingerprints.length === 0) continue\n apps.push({ packageName, sha256Fingerprints: [...new Set(fingerprints)] })\n }\n return apps\n}\n\n/** The `apple-app-site-association` document Apple's CDN reads. */\nexport interface AppleAppSiteAssociation {\n applinks?: {\n apps: []\n details: Array<{ appIDs: string[]; components: AppLinksComponent[] }>\n }\n webcredentials: { apps: string[] }\n}\n\n/**\n * The association file for these app ids. `applinks` is only for a host\n * whose paths the native deep-link grammar resolves: the console. A host\n * that serves the passkey relying party alone gets `webcredentials` only.\n *\n * One `details` entry per app, in the configured order, because when two\n * installed apps claim the same path the system takes the first.\n */\nexport function appleAppSiteAssociation(\n appIds: string[],\n options: { applinks: boolean },\n): AppleAppSiteAssociation {\n const document: AppleAppSiteAssociation = { webcredentials: { apps: [...appIds] } }\n if (options.applinks) {\n const components = consoleAppLinksComponents()\n document.applinks = {\n apps: [],\n details: appIds.map((appId) => ({ appIDs: [appId], components })),\n }\n }\n return document\n}\n\n/** One Digital Asset Links statement. */\nexport interface AssetLinkStatement {\n relation: string[]\n target: { namespace: 'android_app'; package_name: string; sha256_cert_fingerprints: string[] }\n}\n\n/** Passkeys and app links, for every configured app. */\nexport const ASSET_LINK_RELATIONS = [\n 'delegate_permission/common.handle_all_urls',\n 'delegate_permission/common.get_login_creds',\n] as const\n\n/** The `assetlinks.json` statement list Google's verifier reads. */\nexport function assetLinks(apps: AndroidAppAssociation[]): AssetLinkStatement[] {\n return apps.map((app) => ({\n relation: [...ASSET_LINK_RELATIONS],\n target: {\n namespace: 'android_app',\n package_name: app.packageName,\n sha256_cert_fingerprints: [...app.sha256Fingerprints],\n },\n }))\n}\n\n/** The request's hostname: lowercase, no port. */\nexport function requestHostname(host: string | null | undefined): string {\n return (host ?? '').trim().toLowerCase().replace(/:\\d+$/, '')\n}\n\n/** A URL's hostname, or null when it is not one. */\nexport function hostnameOfUrl(url: string | null | undefined): string | null {\n if (!url) return null\n try {\n return new URL(url).hostname.toLowerCase() || null\n } catch {\n return null\n }\n}\n\n/** What a route handler needs to answer one of the two documents. */\nexport interface NativeAppAssociationRequest {\n /** The request's `Host` header. */\n host: string | null\n /**\n * The hostnames this deployment vouches for. Every other host an app\n * serves (a customer's custom domain, a workspace subdomain, a white-label\n * console) answers 404, because the apps are this deployment's and not\n * theirs.\n */\n servedHosts: ReadonlyArray<string | null | undefined>\n /** Whether this host's paths are console links the apps resolve. */\n applinks: boolean\n /** The environment; `process.env` in the routes, a fixture in specs. */\n env: Readonly<Record<string, string | undefined>>\n}\n\n/** Short on purpose: Apple's CDN caches on top, and a fix must land within minutes. */\nconst CACHE_CONTROL = 'public, max-age=300, s-maxage=300'\n\n/**\n * Whether this host may answer. A Vercel preview deployment is reachable only\n * at the platform's own generated hostnames, so it answers on any host, which\n * is what lets a preview be checked before it is promoted.\n */\nfunction servesHost(request: NativeAppAssociationRequest): boolean {\n if (request.env['VERCEL_ENV'] === 'preview') return true\n const hostname = requestHostname(request.host)\n if (!hostname) return false\n return request.servedHosts.some((served) => requestHostname(served) === hostname)\n}\n\nfunction notFound(): Response {\n return new Response('Not Found', {\n status: 404,\n headers: { 'content-type': 'text/plain; charset=utf-8', 'cache-control': CACHE_CONTROL },\n })\n}\n\nfunction json(body: unknown): Response {\n return new Response(JSON.stringify(body), {\n status: 200,\n headers: { 'content-type': 'application/json', 'cache-control': CACHE_CONTROL },\n })\n}\n\n/** `GET /.well-known/apple-app-site-association`. */\nexport function appleAppSiteAssociationResponse(request: NativeAppAssociationRequest): Response {\n const appIds = parseAppleAppIds(request.env['NATIVE_APPLE_APP_IDS'])\n if (appIds.length === 0 || !servesHost(request)) return notFound()\n return json(appleAppSiteAssociation(appIds, { applinks: request.applinks }))\n}\n\n/** `GET /.well-known/assetlinks.json`. */\nexport function assetLinksResponse(request: NativeAppAssociationRequest): Response {\n const apps = parseAndroidApps(request.env['NATIVE_ANDROID_APPS'])\n if (apps.length === 0 || !servesHost(request)) return notFound()\n return json(assetLinks(apps))\n}\n"],"names":["APPLE_APP_SITE_ASSOCIATION_PATH","ASSET_LINKS_PATH","NATIVE_APP_ASSOCIATION_ROUTES","nativeAppAssociationRoute","pathname","Object","prototype","hasOwnProperty","call","NATIVE_WEB_ONLY_TOP_LEVEL_SEGMENTS","consoleAppLinksComponents","excluded","exclude","comment","segment","push","APPLE_APP_ID","ANDROID_PACKAGE","SHA256_FINGERPRINT","parseAppleAppIds","raw","ids","split","map","value","trim","filter","test","Set","parseAndroidApps","apps","entry","at","indexOf","packageName","slice","some","app","fingerprints","toUpperCase","length","sha256Fingerprints","appleAppSiteAssociation","appIds","options","document","webcredentials","applinks","components","details","appId","appIDs","ASSET_LINK_RELATIONS","assetLinks","relation","target","namespace","package_name","sha256_cert_fingerprints","requestHostname","host","toLowerCase","replace","hostnameOfUrl","url","URL","hostname","CACHE_CONTROL","servesHost","request","env","servedHosts","served","notFound","Response","status","headers","json","body","JSON","stringify","appleAppSiteAssociationResponse","assetLinksResponse"],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * WHAT ENFORCING SINGLE SIGN-ON WOULD DO TO EACH ACCOUNT (AGL-1888, AGL-3671) —
19
+ * the pure half of the Single sign-on card's rehearsal table, with no import at
20
+ * all, so the native apps replay it (`ssoEnforcementAccounts`) beside the
21
+ * console's card, which draws the very same rows.
22
+ *
23
+ * The rehearsal answers one entry per account of the identity pool: the sign-in
24
+ * methods enforcement would remove (`unlinked`), the ones it leaves (`kept`),
25
+ * and why it would leave the account alone (`skipped`). An account can be
26
+ * designated break-glass only if it holds a way in that is not the identity
27
+ * provider itself: anything else would look like protection and provide none.
28
+ */
29
+ /** One account of the rehearsal, as the single sign-on route answers it. */
30
+ export interface SsoEnforcementAccount {
31
+ uid: string;
32
+ email: string | null;
33
+ unlinked: readonly string[];
34
+ kept: readonly string[];
35
+ skipped?: string;
36
+ }
37
+ /** One row of the rehearsal table, in the words the console shows. */
38
+ export interface SsoEnforcementRow {
39
+ uid: string;
40
+ /** The address, else the account's uid. */
41
+ label: string;
42
+ /** What the account would lose, or why it would lose nothing. */
43
+ wouldLose: string;
44
+ /** What it keeps. */
45
+ keeps: string;
46
+ /** Whether it can be designated break-glass (it holds a way in other than the provider). */
47
+ eligible: boolean;
48
+ /** Whether it is designated break-glass now. */
49
+ designated: boolean;
50
+ }
51
+ /**
52
+ * Would designating this account actually keep a way in?
53
+ *
54
+ * Mirrors `assessSsoLockoutRisk` deliberately, and over the same fact it
55
+ * uses: an account protects the org only when it holds a provider that is
56
+ * NOT the org's IdP. Designating an account whose sole credential is the SAML
57
+ * link is the most natural way to get this wrong, because it looks exactly
58
+ * like protection and fails in precisely the situation it exists for.
59
+ *
60
+ * The plan is read BEFORE designation, so both halves count: `unlinked` is
61
+ * what enforcement would strip (all non-IdP by construction), and `kept` is
62
+ * what it would leave — which for an already-designated or would-orphan
63
+ * account is the whole set.
64
+ */
65
+ export declare function protectsIfDesignated(account: Pick<SsoEnforcementAccount, 'unlinked' | 'kept'>, providerId: string | undefined): boolean;
66
+ /** What the account would lose, as the rehearsal table words it. */
67
+ export declare function ssoEnforcementLoses(account: Pick<SsoEnforcementAccount, 'unlinked' | 'skipped'>): string;
68
+ /** The rehearsal table's rows, one per account, for the break-glass choice. */
69
+ export declare function ssoEnforcementAccounts(accounts: readonly SsoEnforcementAccount[] | null | undefined, providerId: string | undefined, designated: readonly string[] | null | undefined): SsoEnforcementRow[];
@@ -0,0 +1,66 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * WHAT ENFORCING SINGLE SIGN-ON WOULD DO TO EACH ACCOUNT (AGL-1888, AGL-3671) —
18
+ * the pure half of the Single sign-on card's rehearsal table, with no import at
19
+ * all, so the native apps replay it (`ssoEnforcementAccounts`) beside the
20
+ * console's card, which draws the very same rows.
21
+ *
22
+ * The rehearsal answers one entry per account of the identity pool: the sign-in
23
+ * methods enforcement would remove (`unlinked`), the ones it leaves (`kept`),
24
+ * and why it would leave the account alone (`skipped`). An account can be
25
+ * designated break-glass only if it holds a way in that is not the identity
26
+ * provider itself: anything else would look like protection and provide none.
27
+ */ /** One account of the rehearsal, as the single sign-on route answers it. */ /**
28
+ * Would designating this account actually keep a way in?
29
+ *
30
+ * Mirrors `assessSsoLockoutRisk` deliberately, and over the same fact it
31
+ * uses: an account protects the org only when it holds a provider that is
32
+ * NOT the org's IdP. Designating an account whose sole credential is the SAML
33
+ * link is the most natural way to get this wrong, because it looks exactly
34
+ * like protection and fails in precisely the situation it exists for.
35
+ *
36
+ * The plan is read BEFORE designation, so both halves count: `unlinked` is
37
+ * what enforcement would strip (all non-IdP by construction), and `kept` is
38
+ * what it would leave — which for an already-designated or would-orphan
39
+ * account is the whole set.
40
+ */ export function protectsIfDesignated(account, providerId) {
41
+ return [
42
+ ...account.unlinked,
43
+ ...account.kept
44
+ ].some((id)=>id !== providerId);
45
+ }
46
+ /** What the account would lose, as the rehearsal table words it. */ export function ssoEnforcementLoses(account) {
47
+ if (account.skipped === 'break-glass') return 'Nothing — designated break-glass';
48
+ if (account.skipped) return 'Nothing — it would be left with no way in';
49
+ return account.unlinked.join(', ') || '—';
50
+ }
51
+ /** The rehearsal table's rows, one per account, for the break-glass choice. */ export function ssoEnforcementAccounts(accounts, providerId, designated) {
52
+ const chosen = new Set(designated != null ? designated : []);
53
+ return (accounts != null ? accounts : []).map((account)=>{
54
+ var _account_email;
55
+ return {
56
+ uid: account.uid,
57
+ label: (_account_email = account.email) != null ? _account_email : account.uid,
58
+ wouldLose: ssoEnforcementLoses(account),
59
+ keeps: account.kept.join(', ') || '—',
60
+ eligible: protectsIfDesignated(account, providerId),
61
+ designated: chosen.has(account.uid)
62
+ };
63
+ });
64
+ }
65
+
66
+ //# sourceMappingURL=sso-enforcement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/sso-enforcement.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * WHAT ENFORCING SINGLE SIGN-ON WOULD DO TO EACH ACCOUNT (AGL-1888, AGL-3671) —\n * the pure half of the Single sign-on card's rehearsal table, with no import at\n * all, so the native apps replay it (`ssoEnforcementAccounts`) beside the\n * console's card, which draws the very same rows.\n *\n * The rehearsal answers one entry per account of the identity pool: the sign-in\n * methods enforcement would remove (`unlinked`), the ones it leaves (`kept`),\n * and why it would leave the account alone (`skipped`). An account can be\n * designated break-glass only if it holds a way in that is not the identity\n * provider itself: anything else would look like protection and provide none.\n */\n\n/** One account of the rehearsal, as the single sign-on route answers it. */\nexport interface SsoEnforcementAccount {\n uid: string\n email: string | null\n unlinked: readonly string[]\n kept: readonly string[]\n skipped?: string\n}\n\n/** One row of the rehearsal table, in the words the console shows. */\nexport interface SsoEnforcementRow {\n uid: string\n /** The address, else the account's uid. */\n label: string\n /** What the account would lose, or why it would lose nothing. */\n wouldLose: string\n /** What it keeps. */\n keeps: string\n /** Whether it can be designated break-glass (it holds a way in other than the provider). */\n eligible: boolean\n /** Whether it is designated break-glass now. */\n designated: boolean\n}\n\n/**\n * Would designating this account actually keep a way in?\n *\n * Mirrors `assessSsoLockoutRisk` deliberately, and over the same fact it\n * uses: an account protects the org only when it holds a provider that is\n * NOT the org's IdP. Designating an account whose sole credential is the SAML\n * link is the most natural way to get this wrong, because it looks exactly\n * like protection and fails in precisely the situation it exists for.\n *\n * The plan is read BEFORE designation, so both halves count: `unlinked` is\n * what enforcement would strip (all non-IdP by construction), and `kept` is\n * what it would leave — which for an already-designated or would-orphan\n * account is the whole set.\n */\nexport function protectsIfDesignated(account: Pick<SsoEnforcementAccount, 'unlinked' | 'kept'>, providerId: string | undefined): boolean {\n return [...account.unlinked, ...account.kept].some((id) => id !== providerId)\n}\n\n/** What the account would lose, as the rehearsal table words it. */\nexport function ssoEnforcementLoses(account: Pick<SsoEnforcementAccount, 'unlinked' | 'skipped'>): string {\n if (account.skipped === 'break-glass') return 'Nothing — designated break-glass'\n if (account.skipped) return 'Nothing — it would be left with no way in'\n return account.unlinked.join(', ') || '—'\n}\n\n/** The rehearsal table's rows, one per account, for the break-glass choice. */\nexport function ssoEnforcementAccounts(\n accounts: readonly SsoEnforcementAccount[] | null | undefined,\n providerId: string | undefined,\n designated: readonly string[] | null | undefined,\n): SsoEnforcementRow[] {\n const chosen = new Set(designated ?? [])\n return (accounts ?? []).map((account) => ({\n uid: account.uid,\n label: account.email ?? account.uid,\n wouldLose: ssoEnforcementLoses(account),\n keeps: account.kept.join(', ') || '—',\n eligible: protectsIfDesignated(account, providerId),\n designated: chosen.has(account.uid),\n }))\n}\n"],"names":["protectsIfDesignated","account","providerId","unlinked","kept","some","id","ssoEnforcementLoses","skipped","join","ssoEnforcementAccounts","accounts","designated","chosen","Set","map","uid","label","email","wouldLose","keeps","eligible","has"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;CAWC,GAED,0EAA0E,GAwB1E;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASA,qBAAqBC,OAAyD,EAAEC,UAA8B;IAC5H,OAAO;WAAID,QAAQE,QAAQ;WAAKF,QAAQG,IAAI;KAAC,CAACC,IAAI,CAAC,CAACC,KAAOA,OAAOJ;AACpE;AAEA,kEAAkE,GAClE,OAAO,SAASK,oBAAoBN,OAA4D;IAC9F,IAAIA,QAAQO,OAAO,KAAK,eAAe,OAAO;IAC9C,IAAIP,QAAQO,OAAO,EAAE,OAAO;IAC5B,OAAOP,QAAQE,QAAQ,CAACM,IAAI,CAAC,SAAS;AACxC;AAEA,6EAA6E,GAC7E,OAAO,SAASC,uBACdC,QAA6D,EAC7DT,UAA8B,EAC9BU,UAAgD;IAEhD,MAAMC,SAAS,IAAIC,IAAIF,qBAAAA,aAAc,EAAE;IACvC,OAAO,CAACD,mBAAAA,WAAY,EAAE,EAAEI,GAAG,CAAC,CAACd;YAEpBA;eAFiC;YACxCe,KAAKf,QAAQe,GAAG;YAChBC,KAAK,GAAEhB,iBAAAA,QAAQiB,KAAK,YAAbjB,iBAAiBA,QAAQe,GAAG;YACnCG,WAAWZ,oBAAoBN;YAC/BmB,OAAOnB,QAAQG,IAAI,CAACK,IAAI,CAAC,SAAS;YAClCY,UAAUrB,qBAAqBC,SAASC;YACxCU,YAAYC,OAAOS,GAAG,CAACrB,QAAQe,GAAG;QACpC;;AACF"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * THE TIME ZONES THE PICKER OFFERS (AGL-3252, AGL-3671) — the list the console's
19
+ * time zone controls show, kept as data with no import at all, so the native
20
+ * apps replay it (`timeZoneChoices`) beside the console's pickers.
21
+ *
22
+ * The console reads its list off `Intl.supportedValuesOf('timeZone')`
23
+ * (`supportedTimeZones()`), because the IANA database renames zones and a
24
+ * picker built from the runtime cannot offer a name the runtime refuses. An app
25
+ * has no `Intl` to ask, and its platform's own list is a different one (it
26
+ * carries every legacy alias: `US/Eastern`, `GB-Eire`, `Etc/GMT+5`), so the
27
+ * apps offer THIS snapshot of what that call answers: canonical names only,
28
+ * each of which `isSupportedTimeZone` accepts, which is what the workspace
29
+ * route checks. It is a snapshot: when the IANA database moves, regenerate it
30
+ * from `Intl.supportedValuesOf('timeZone')` (the spec beside this file fails
31
+ * for a zone the runtime no longer accepts, which the route would refuse).
32
+ */
33
+ export declare const TIME_ZONE_CHOICES: readonly string[];
34
+ /** One entry of a picker: the stored zone name, and what the person reads. */
35
+ export interface TimeZoneChoice {
36
+ value: string;
37
+ label: string;
38
+ }
39
+ /** The label of the first choice: an organization that never set a zone dates everything in UTC, and says so. */
40
+ export declare const TIME_ZONE_DEFAULT_LABEL = "UTC (default)";
41
+ /**
42
+ * The picker's choices, in the order the console shows them: the default (an empty value, which the route stores as
43
+ * no zone at all) first, then every zone by name, with its underscores read as spaces (`America/New_York` is
44
+ * `America/New York`).
45
+ */
46
+ export declare function timeZoneChoices(): TimeZoneChoice[];