@aglyn/tenant-data-admin 1.0.0-beta.254 → 1.0.0-beta.256

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.
@@ -0,0 +1,229 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { createHash, randomBytes } from "crypto";
18
+ import { getApp } from "firebase-admin/app";
19
+ import { getAppCheck } from "firebase-admin/app-check";
20
+ import firebaseAdmin from "./firebase-admin.js";
21
+ import { consumeOnce } from "./consume-once.js";
22
+ import { safeEqual } from "./safe-equal.js";
23
+ /**
24
+ * The native apps' console hand-off (AGL-3816): a sign-in finished in the
25
+ * console, in the system browser or the platform's auth session, handed back
26
+ * to the app that asked for it.
27
+ *
28
+ * ## Why the apps need it
29
+ *
30
+ * Three doors cannot be done natively. Workspace single sign-on runs the
31
+ * workspace's SAML or OIDC provider through the console's `/sso` page. The
32
+ * Windows build has no App Check attestation, so it cannot call Identity
33
+ * Toolkit itself and signs in and up through the console instead (Zach,
34
+ * 2026-10-15). And in all three the console page is the one place the
35
+ * person's identity provider, consent and verification rules already run.
36
+ *
37
+ * ## The exchange
38
+ *
39
+ * 1. The app makes a PKCE pair (RFC 7636) and opens
40
+ * `/auth/native?challenge=…&redirect_uri=…&state=…`. The verifier never
41
+ * leaves the app.
42
+ * 2. The person signs in there however the console offers. The page shows
43
+ * who they are signed in as and asks before continuing, so a sign-in
44
+ * already in the browser is never handed over silently (AGL-1415).
45
+ * 3. {@link authorizeNativeHandoff}: the signed-in browser's ID token buys
46
+ * a one-time code, stored only as its SHA-256 with the uid, the identity
47
+ * pool, the challenge and the redirect it may be delivered to.
48
+ * 4. The page navigates to the redirect with the code and the app's `state`.
49
+ * 5. {@link redeemNativeHandoff}: the app presents the code, its verifier and
50
+ * the same redirect. The record is consumed in one transaction (single use
51
+ * is a property, {@link consumeOnce}) and a Firebase custom token is
52
+ * minted in the pool the account lives in.
53
+ *
54
+ * ## What refuses what
55
+ *
56
+ * - **A leaked code** (browser history, a shoulder): useless without the
57
+ * verifier, which only the app that started the flow holds.
58
+ * - **A replayed code**: the record is consumed by the first redemption,
59
+ * whatever its outcome; two concurrent redemptions cannot both commit.
60
+ * - **A late code**: 120 seconds, the time one navigation and one POST take.
61
+ * - **A code sent elsewhere**: the redirect is fixed at authorization to the
62
+ * apps' own schemes or the desktop's loopback listener, and redemption must
63
+ * name the same one.
64
+ * - **A support session handed to a device**: refused at authorization, as is
65
+ * an address nobody has confirmed (the console's routes refuse one anyway).
66
+ */ /** Where the records live. Server-only: the rules deny every client. */ export const NATIVE_HANDOFFS_COLLECTION = 'nativeHandoffs';
67
+ /** How long a code stays redeemable. */ export const NATIVE_HANDOFF_TTL_MS = 120000;
68
+ /**
69
+ * The apps' own return addresses: the Aglyn app's and Aglyn POS's URL schemes
70
+ * (the platform's auth session hands the return straight to the app that
71
+ * opened it), and on the desktop a listener on the loopback address (RFC 8252
72
+ * §7.3) on a port the app chose.
73
+ */ export const NATIVE_HANDOFF_APP_REDIRECTS = [
74
+ 'aglyn://auth/handoff',
75
+ 'aglyn-pos://auth/handoff'
76
+ ];
77
+ const LOOPBACK_REDIRECT = /^http:\/\/(?:127\.0\.0\.1|\[::1\]):(\d{4,5})\/handoff$/;
78
+ /** Whether `value` is a return address a hand-off may be delivered to. */ export function isNativeHandoffRedirect(value) {
79
+ if (typeof value !== 'string') return false;
80
+ if (NATIVE_HANDOFF_APP_REDIRECTS.includes(value)) return true;
81
+ const loopback = LOOPBACK_REDIRECT.exec(value);
82
+ if (!loopback) return false;
83
+ const port = Number(loopback[1]);
84
+ return port >= 1024 && port <= 65535;
85
+ }
86
+ /** An S256 PKCE challenge: 32 bytes of SHA-256, base64url without padding. */ export function isPkceChallenge(value) {
87
+ return typeof value === 'string' && /^[A-Za-z0-9_-]{43}$/.test(value);
88
+ }
89
+ /** A PKCE verifier (RFC 7636 §4.1). */ export function isPkceVerifier(value) {
90
+ return typeof value === 'string' && /^[A-Za-z0-9._~-]{43,128}$/.test(value);
91
+ }
92
+ /** The S256 challenge for a verifier. */ export function pkceChallengeFor(verifier) {
93
+ return createHash('sha256').update(verifier, 'utf8').digest('base64url');
94
+ }
95
+ /** The stored key for a code: its SHA-256, so the collection holds nothing redeemable. */ export function nativeHandoffKey(code) {
96
+ return createHash('sha256').update(code, 'utf8').digest('hex');
97
+ }
98
+ /**
99
+ * Mint a one-time code for the signed-in account. The caller has verified
100
+ * the ID token and the redirect and challenge shapes.
101
+ */ export async function authorizeNativeHandoff(input) {
102
+ var _input_firestore, _input_nowMs, _input_tenantId;
103
+ const firestore = (_input_firestore = input.firestore) != null ? _input_firestore : firebaseAdmin.app().firestore();
104
+ const code = randomBytes(32).toString('base64url');
105
+ const expiresAtMs = ((_input_nowMs = input.nowMs) != null ? _input_nowMs : Date.now()) + NATIVE_HANDOFF_TTL_MS;
106
+ const record = {
107
+ uid: input.uid,
108
+ tenantId: (_input_tenantId = input.tenantId) != null ? _input_tenantId : null,
109
+ challenge: input.challenge,
110
+ redirectUri: input.redirectUri,
111
+ expiresAtMs
112
+ };
113
+ await firestore.collection(NATIVE_HANDOFFS_COLLECTION).doc(nativeHandoffKey(code))// `expiresAt` is the TTL policy's field (tools/scripts/lib/ttl-policies.mjs);
114
+ // `expiresAtMs` is what redemption checks, so expiry never waits on TTL.
115
+ .create(_extends({}, record, {
116
+ expiresAt: new Date(expiresAtMs)
117
+ }));
118
+ return {
119
+ code,
120
+ expiresAtMs
121
+ };
122
+ }
123
+ /**
124
+ * Redeem a code, once. The record is deleted by the first redemption whatever
125
+ * it decides, so a code that was presented with the wrong verifier cannot be
126
+ * tried again with another.
127
+ */ export async function redeemNativeHandoff(input) {
128
+ var _input_firestore, _input_nowMs;
129
+ var _outcome_value;
130
+ const firestore = (_input_firestore = input.firestore) != null ? _input_firestore : firebaseAdmin.app().firestore();
131
+ const nowMs = (_input_nowMs = input.nowMs) != null ? _input_nowMs : Date.now();
132
+ const ref = firestore.collection(NATIVE_HANDOFFS_COLLECTION).doc(nativeHandoffKey(input.code));
133
+ const result = await consumeOnce(firestore, ref, (data)=>{
134
+ var _stored_uid, _stored_tenantId;
135
+ const stored = data;
136
+ if (typeof stored.expiresAtMs !== 'number' || nowMs > stored.expiresAtMs) {
137
+ return {
138
+ accept: false,
139
+ reason: 'expired',
140
+ remove: true
141
+ };
142
+ }
143
+ if (!safeEqual(pkceChallengeFor(input.verifier), stored.challenge)) {
144
+ return {
145
+ accept: false,
146
+ reason: 'verifier',
147
+ remove: true
148
+ };
149
+ }
150
+ if (!safeEqual(input.redirectUri, stored.redirectUri)) {
151
+ return {
152
+ accept: false,
153
+ reason: 'redirect',
154
+ remove: true
155
+ };
156
+ }
157
+ return {
158
+ accept: true,
159
+ remove: true,
160
+ value: {
161
+ uid: String((_stored_uid = stored.uid) != null ? _stored_uid : ''),
162
+ tenantId: (_stored_tenantId = stored.tenantId) != null ? _stored_tenantId : null
163
+ }
164
+ };
165
+ });
166
+ // Read structurally: `strictNullChecks` is off repo-wide (see consumeOnce).
167
+ const outcome = result;
168
+ if (!outcome.ok || !((_outcome_value = outcome.value) == null ? void 0 : _outcome_value.uid)) {
169
+ var _outcome_reason;
170
+ return {
171
+ ok: false,
172
+ reason: (_outcome_reason = outcome.reason) != null ? _outcome_reason : 'absent'
173
+ };
174
+ }
175
+ return {
176
+ ok: true,
177
+ uid: outcome.value.uid,
178
+ tenantId: outcome.value.tenantId
179
+ };
180
+ }
181
+ /**
182
+ * The desktop's App Check (AGL-3816, Zach 2026-10-15): Compose Desktop has no
183
+ * attestation provider, and production enforces App Check on Identity
184
+ * Toolkit, so the console mints the desktop's tokens for a session it has
185
+ * just signed in. Measured against aglyn-main on 2026-10-15 with no token:
186
+ * `accounts:signInWithCustomToken` answers 401 "Firebase App Check token is
187
+ * invalid." before it reads the custom token, while the Secure Token refresh
188
+ * and `accounts:lookup` reach their own validation. So the hand-off's custom
189
+ * token cannot be exchanged without one, and the FIRST token rides the
190
+ * redemption; after that the desktop asks `/api/auth/native-app-check` with
191
+ * its ID token, which refreshes without App Check.
192
+ *
193
+ * What the token attests is weaker than App Attest or Play Integrity: not a
194
+ * genuine binary, but a session that a person completed in the console in the
195
+ * last moments, bound by PKCE to the process holding it (redemption), or a
196
+ * live, unrevoked ID token (the refresh route). Nothing outlives sign-out:
197
+ * the desktop drops its token, and a revoked or disabled account is refused
198
+ * a new one.
199
+ */ /** A desktop App Check token's lifetime: an hour, refreshed by the app at half-life. */ export const NATIVE_APP_CHECK_TTL_MS = 60 * 60 * 1000;
200
+ /**
201
+ * The Firebase app the desktop's tokens are minted for: the dedicated desktop
202
+ * registration when configured (`NATIVE_DESKTOP_APP_CHECK_APP_ID`), else the
203
+ * console's own web app, which App Check already accepts. Null when neither
204
+ * is set (a self-host without App Check), and the desktop then sends none.
205
+ */ export function nativeDesktopAppCheckAppId(env = process.env) {
206
+ var _env_NATIVE_DESKTOP_APP_CHECK_APP_ID, _env_NEXT_PUBLIC_FIREBASE_APP_ID;
207
+ const dedicated = (_env_NATIVE_DESKTOP_APP_CHECK_APP_ID = env['NATIVE_DESKTOP_APP_CHECK_APP_ID']) == null ? void 0 : _env_NATIVE_DESKTOP_APP_CHECK_APP_ID.trim();
208
+ if (dedicated) return dedicated;
209
+ return ((_env_NEXT_PUBLIC_FIREBASE_APP_ID = env['NEXT_PUBLIC_FIREBASE_APP_ID']) == null ? void 0 : _env_NEXT_PUBLIC_FIREBASE_APP_ID.trim()) || null;
210
+ }
211
+ /** Whether a return address is the desktop's loopback listener (not an app scheme). */ export function isLoopbackRedirect(value) {
212
+ return LOOPBACK_REDIRECT.test(value);
213
+ }
214
+ /** Mint one App Check token for the desktop; null when App Check is not configured here. */ export async function mintNativeAppCheckToken(input = {}) {
215
+ var _input_nowMs;
216
+ const appId = nativeDesktopAppCheckAppId(input.env);
217
+ if (!appId) return null;
218
+ // Touch the facade so the app is initialized before App Check asks for it.
219
+ void firebaseAdmin;
220
+ const minted = await getAppCheck(getApp()).createToken(appId, {
221
+ ttlMillis: NATIVE_APP_CHECK_TTL_MS
222
+ });
223
+ return {
224
+ token: minted.token,
225
+ expiresAtMs: ((_input_nowMs = input.nowMs) != null ? _input_nowMs : Date.now()) + minted.ttlMillis
226
+ };
227
+ }
228
+
229
+ //# sourceMappingURL=native-handoff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/native-handoff.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\nimport { createHash, randomBytes } from 'crypto'\nimport { getApp } from 'firebase-admin/app'\nimport { getAppCheck } from 'firebase-admin/app-check'\nimport firebaseAdmin from './firebase-admin'\nimport { consumeOnce } from './consume-once'\nimport { safeEqual } from './safe-equal'\n\n/**\n * The native apps' console hand-off (AGL-3816): a sign-in finished in the\n * console, in the system browser or the platform's auth session, handed back\n * to the app that asked for it.\n *\n * ## Why the apps need it\n *\n * Three doors cannot be done natively. Workspace single sign-on runs the\n * workspace's SAML or OIDC provider through the console's `/sso` page. The\n * Windows build has no App Check attestation, so it cannot call Identity\n * Toolkit itself and signs in and up through the console instead (Zach,\n * 2026-10-15). And in all three the console page is the one place the\n * person's identity provider, consent and verification rules already run.\n *\n * ## The exchange\n *\n * 1. The app makes a PKCE pair (RFC 7636) and opens\n * `/auth/native?challenge=…&redirect_uri=…&state=…`. The verifier never\n * leaves the app.\n * 2. The person signs in there however the console offers. The page shows\n * who they are signed in as and asks before continuing, so a sign-in\n * already in the browser is never handed over silently (AGL-1415).\n * 3. {@link authorizeNativeHandoff}: the signed-in browser's ID token buys\n * a one-time code, stored only as its SHA-256 with the uid, the identity\n * pool, the challenge and the redirect it may be delivered to.\n * 4. The page navigates to the redirect with the code and the app's `state`.\n * 5. {@link redeemNativeHandoff}: the app presents the code, its verifier and\n * the same redirect. The record is consumed in one transaction (single use\n * is a property, {@link consumeOnce}) and a Firebase custom token is\n * minted in the pool the account lives in.\n *\n * ## What refuses what\n *\n * - **A leaked code** (browser history, a shoulder): useless without the\n * verifier, which only the app that started the flow holds.\n * - **A replayed code**: the record is consumed by the first redemption,\n * whatever its outcome; two concurrent redemptions cannot both commit.\n * - **A late code**: 120 seconds, the time one navigation and one POST take.\n * - **A code sent elsewhere**: the redirect is fixed at authorization to the\n * apps' own schemes or the desktop's loopback listener, and redemption must\n * name the same one.\n * - **A support session handed to a device**: refused at authorization, as is\n * an address nobody has confirmed (the console's routes refuse one anyway).\n */\n\n/** Where the records live. Server-only: the rules deny every client. */\nexport const NATIVE_HANDOFFS_COLLECTION = 'nativeHandoffs'\n\n/** How long a code stays redeemable. */\nexport const NATIVE_HANDOFF_TTL_MS = 120_000\n\n/**\n * The apps' own return addresses: the Aglyn app's and Aglyn POS's URL schemes\n * (the platform's auth session hands the return straight to the app that\n * opened it), and on the desktop a listener on the loopback address (RFC 8252\n * §7.3) on a port the app chose.\n */\nexport const NATIVE_HANDOFF_APP_REDIRECTS: readonly string[] = [\n 'aglyn://auth/handoff',\n 'aglyn-pos://auth/handoff',\n]\n\nconst LOOPBACK_REDIRECT = /^http:\\/\\/(?:127\\.0\\.0\\.1|\\[::1\\]):(\\d{4,5})\\/handoff$/\n\n/** Whether `value` is a return address a hand-off may be delivered to. */\nexport function isNativeHandoffRedirect(value: unknown): value is string {\n if (typeof value !== 'string') return false\n if (NATIVE_HANDOFF_APP_REDIRECTS.includes(value)) return true\n const loopback = LOOPBACK_REDIRECT.exec(value)\n if (!loopback) return false\n const port = Number(loopback[1])\n return port >= 1024 && port <= 65535\n}\n\n/** An S256 PKCE challenge: 32 bytes of SHA-256, base64url without padding. */\nexport function isPkceChallenge(value: unknown): value is string {\n return typeof value === 'string' && /^[A-Za-z0-9_-]{43}$/.test(value)\n}\n\n/** A PKCE verifier (RFC 7636 §4.1). */\nexport function isPkceVerifier(value: unknown): value is string {\n return typeof value === 'string' && /^[A-Za-z0-9._~-]{43,128}$/.test(value)\n}\n\n/** The S256 challenge for a verifier. */\nexport function pkceChallengeFor(verifier: string): string {\n return createHash('sha256').update(verifier, 'utf8').digest('base64url')\n}\n\n/** The stored key for a code: its SHA-256, so the collection holds nothing redeemable. */\nexport function nativeHandoffKey(code: string): string {\n return createHash('sha256').update(code, 'utf8').digest('hex')\n}\n\ninterface StoredNativeHandoff {\n uid: string\n tenantId: string | null\n challenge: string\n redirectUri: string\n expiresAtMs: number\n}\n\n/**\n * Mint a one-time code for the signed-in account. The caller has verified\n * the ID token and the redirect and challenge shapes.\n */\nexport async function authorizeNativeHandoff(input: {\n uid: string\n tenantId: string | null\n challenge: string\n redirectUri: string\n nowMs?: number\n firestore?: FirebaseFirestore.Firestore\n}): Promise<{ code: string; expiresAtMs: number }> {\n const firestore = input.firestore ?? firebaseAdmin.app().firestore()\n const code = randomBytes(32).toString('base64url')\n const expiresAtMs = (input.nowMs ?? Date.now()) + NATIVE_HANDOFF_TTL_MS\n const record: StoredNativeHandoff = {\n uid: input.uid,\n tenantId: input.tenantId ?? null,\n challenge: input.challenge,\n redirectUri: input.redirectUri,\n expiresAtMs,\n }\n await firestore\n .collection(NATIVE_HANDOFFS_COLLECTION)\n .doc(nativeHandoffKey(code))\n // `expiresAt` is the TTL policy's field (tools/scripts/lib/ttl-policies.mjs);\n // `expiresAtMs` is what redemption checks, so expiry never waits on TTL.\n .create({ ...record, expiresAt: new Date(expiresAtMs) })\n return { code, expiresAtMs }\n}\n\n/** Why a redemption was refused. */\nexport type NativeHandoffRefusal =\n | 'absent'\n | 'expired'\n | 'verifier'\n | 'redirect'\n\n/**\n * Redeem a code, once. The record is deleted by the first redemption whatever\n * it decides, so a code that was presented with the wrong verifier cannot be\n * tried again with another.\n */\nexport async function redeemNativeHandoff(input: {\n code: string\n verifier: string\n redirectUri: string\n nowMs?: number\n firestore?: FirebaseFirestore.Firestore\n}): Promise<\n | { ok: true; uid: string; tenantId: string | null }\n | { ok: false; reason: NativeHandoffRefusal }\n> {\n const firestore = input.firestore ?? firebaseAdmin.app().firestore()\n const nowMs = input.nowMs ?? Date.now()\n const ref = firestore\n .collection(NATIVE_HANDOFFS_COLLECTION)\n .doc(nativeHandoffKey(input.code))\n const result = await consumeOnce(firestore, ref, (data) => {\n const stored = data as Partial<StoredNativeHandoff>\n if (typeof stored.expiresAtMs !== 'number' || nowMs > stored.expiresAtMs) {\n return { accept: false, reason: 'expired', remove: true }\n }\n if (!safeEqual(pkceChallengeFor(input.verifier), stored.challenge)) {\n return { accept: false, reason: 'verifier', remove: true }\n }\n if (!safeEqual(input.redirectUri, stored.redirectUri)) {\n return { accept: false, reason: 'redirect', remove: true }\n }\n return {\n accept: true,\n remove: true,\n value: { uid: String(stored.uid ?? ''), tenantId: stored.tenantId ?? null },\n }\n })\n // Read structurally: `strictNullChecks` is off repo-wide (see consumeOnce).\n const outcome = result as {\n ok: boolean\n value: { uid: string; tenantId: string | null } | null\n reason: string | null\n }\n if (!outcome.ok || !outcome.value?.uid) {\n return { ok: false, reason: (outcome.reason ?? 'absent') as NativeHandoffRefusal }\n }\n return { ok: true, uid: outcome.value.uid, tenantId: outcome.value.tenantId }\n}\n\n/**\n * The desktop's App Check (AGL-3816, Zach 2026-10-15): Compose Desktop has no\n * attestation provider, and production enforces App Check on Identity\n * Toolkit, so the console mints the desktop's tokens for a session it has\n * just signed in. Measured against aglyn-main on 2026-10-15 with no token:\n * `accounts:signInWithCustomToken` answers 401 \"Firebase App Check token is\n * invalid.\" before it reads the custom token, while the Secure Token refresh\n * and `accounts:lookup` reach their own validation. So the hand-off's custom\n * token cannot be exchanged without one, and the FIRST token rides the\n * redemption; after that the desktop asks `/api/auth/native-app-check` with\n * its ID token, which refreshes without App Check.\n *\n * What the token attests is weaker than App Attest or Play Integrity: not a\n * genuine binary, but a session that a person completed in the console in the\n * last moments, bound by PKCE to the process holding it (redemption), or a\n * live, unrevoked ID token (the refresh route). Nothing outlives sign-out:\n * the desktop drops its token, and a revoked or disabled account is refused\n * a new one.\n */\n\n/** A desktop App Check token's lifetime: an hour, refreshed by the app at half-life. */\nexport const NATIVE_APP_CHECK_TTL_MS = 60 * 60 * 1000\n\n/**\n * The Firebase app the desktop's tokens are minted for: the dedicated desktop\n * registration when configured (`NATIVE_DESKTOP_APP_CHECK_APP_ID`), else the\n * console's own web app, which App Check already accepts. Null when neither\n * is set (a self-host without App Check), and the desktop then sends none.\n */\nexport function nativeDesktopAppCheckAppId(\n env: Record<string, string | undefined> = process.env,\n): string | null {\n const dedicated = env['NATIVE_DESKTOP_APP_CHECK_APP_ID']?.trim()\n if (dedicated) return dedicated\n return env['NEXT_PUBLIC_FIREBASE_APP_ID']?.trim() || null\n}\n\n/** Whether a return address is the desktop's loopback listener (not an app scheme). */\nexport function isLoopbackRedirect(value: string): boolean {\n return LOOPBACK_REDIRECT.test(value)\n}\n\n/** Mint one App Check token for the desktop; null when App Check is not configured here. */\nexport async function mintNativeAppCheckToken(\n input: { nowMs?: number; env?: Record<string, string | undefined> } = {},\n): Promise<{ token: string; expiresAtMs: number } | null> {\n const appId = nativeDesktopAppCheckAppId(input.env)\n if (!appId) return null\n // Touch the facade so the app is initialized before App Check asks for it.\n void firebaseAdmin\n const minted = await getAppCheck(getApp()).createToken(appId, { ttlMillis: NATIVE_APP_CHECK_TTL_MS })\n return { token: minted.token, expiresAtMs: (input.nowMs ?? Date.now()) + minted.ttlMillis }\n}\n"],"names":["createHash","randomBytes","getApp","getAppCheck","firebaseAdmin","consumeOnce","safeEqual","NATIVE_HANDOFFS_COLLECTION","NATIVE_HANDOFF_TTL_MS","NATIVE_HANDOFF_APP_REDIRECTS","LOOPBACK_REDIRECT","isNativeHandoffRedirect","value","includes","loopback","exec","port","Number","isPkceChallenge","test","isPkceVerifier","pkceChallengeFor","verifier","update","digest","nativeHandoffKey","code","authorizeNativeHandoff","input","firestore","app","toString","expiresAtMs","nowMs","Date","now","record","uid","tenantId","challenge","redirectUri","collection","doc","create","expiresAt","redeemNativeHandoff","outcome","ref","result","data","stored","accept","reason","remove","String","ok","NATIVE_APP_CHECK_TTL_MS","nativeDesktopAppCheckAppId","env","process","dedicated","trim","isLoopbackRedirect","mintNativeAppCheckToken","appId","minted","createToken","ttlMillis","token"],"mappings":"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"}
@@ -272,7 +272,7 @@ const firestoreApp = ()=>firebaseAdmin.app().firestore();
272
272
  'org'
273
273
  ],
274
274
  exported: true,
275
- note: 'EXISTENCE ONLY for the grant — each site’s connection to the merchant’s own ShipBob, ShipMonk or Amazon Multi-Channel Fulfillment account (AGL-3634, AGL-3697): network, status, account name, ShipBob channel, ShipMonk store id, Amazon marketplaces, routing and stock settings, the last stock count by SKU and totals. The sealed access and refresh tokens (for ShipMonk, the merchant’s own API key, sealed as the access token), the webhook token’s hash and the id of the key that sealed them carry `token` in their names, and ShipMonk’s sealed webhook signing secret carries `secret` in its name; all are redacted (see redactSecrets). The document id is the site id and the network.'
275
+ note: 'EXISTENCE ONLY for the grant — each site’s connection to the merchant’s own ShipBob, ShipMonk, Flexport or Amazon Multi-Channel Fulfillment account (AGL-3634, AGL-3697, AGL-3822): network, status, account name, ShipBob channel, ShipMonk store id, Amazon marketplaces, routing and stock settings, the last stock count by SKU and totals. The sealed access and refresh tokens (for ShipMonk and Flexport, the merchant’s own API key or token, sealed as the access token), the webhook token’s hash and the id of the key that sealed them carry `token` in their names, and ShipMonk’s sealed webhook signing secret carries `secret` in its name; all are redacted (see redactSecrets). The document id is the site id and the network.'
276
276
  },
277
277
  {
278
278
  collection: 'fulfillmentNetworkOrders',
@@ -326,7 +326,7 @@ const firestoreApp = ()=>firebaseAdmin.app().firestore();
326
326
  'org'
327
327
  ],
328
328
  exported: true,
329
- note: 'EXISTENCE ONLY for the keys — each site’s connection to the merchant’s own Cin7 Core, inFlow or Brightpearl account (AGL-3642): system, status, account name, the stock, product and order settings (location, the customer orders are recorded under, the tax rule), the last count by SKU, the last syncs’ counts, cursors and totals. The sealed API keys or grant and the id of the key that sealed them carry `credential` in their names and are redacted (see redactSecrets); Brightpearl’s account code and datacenter are disclosed as stored. The document id is the site id. Its `log` subcollection is not read by this export.'
329
+ note: 'EXISTENCE ONLY for the keys — each site’s connection to the merchant’s own Cin7 Core, inFlow, Brightpearl or Ordoro account (AGL-3642): system, status, account name, the stock, product and order settings (location, the customer or Ordoro sales channel orders are recorded under, the tax rule), the last count by SKU, the last syncs’ counts, cursors and totals. The sealed API keys or grant and the id of the key that sealed them carry `credential` in their names and are redacted (see redactSecrets); Brightpearl’s account code and datacenter are disclosed as stored. The document id is the site id. Its `log` subcollection is not read by this export.'
330
330
  },
331
331
  {
332
332
  collection: 'inventorySyncOrders',