pyric-admin 0.1.0-alpha.29 → 0.1.0-alpha.30.1

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,27 @@
1
+ /**
2
+ * firebase-admin's error shape (lib/utils/error.js, 13.10.0), shared by the
3
+ * admin mirrors so each service's error class matches upstream in one place.
4
+ *
5
+ * Upstream keeps `{ code, message }` in an own `errorInfo` property and reads
6
+ * `code` through a prototype getter. Its `message` getter is shadowed by the
7
+ * own, non-enumerable `message` that `Error` sets, so only `code` needs one
8
+ * here. `toJSON()` returns both, and
9
+ * `hasCode()` tests the unprefixed code. It never sets `name`, so `name` reads
10
+ * `Error`. The own enumerable keys are `errorInfo` and `codePrefix`.
11
+ */
12
+ export interface ErrorInfo {
13
+ code: string;
14
+ message: string;
15
+ }
16
+ export declare class FirebaseError extends Error {
17
+ readonly errorInfo: ErrorInfo;
18
+ constructor(errorInfo: ErrorInfo);
19
+ get code(): string;
20
+ toJSON(): ErrorInfo;
21
+ }
22
+ export declare class PrefixedFirebaseError extends FirebaseError {
23
+ readonly codePrefix: string;
24
+ constructor(codePrefix: string, code: string, message: string);
25
+ hasCode(code: string): boolean;
26
+ }
27
+ //# sourceMappingURL=firebase-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"firebase-error.d.ts","sourceRoot":"","sources":["../../src/app/firebase-error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;gBAElB,SAAS,EAAE,SAAS;IAMhC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,MAAM,IAAI,SAAS;CAGpB;AAED,qBAAa,qBAAsB,SAAQ,aAAa;IACtD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAEhB,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAK7D,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CAG/B"}
@@ -0,0 +1,25 @@
1
+ export class FirebaseError extends Error {
2
+ errorInfo;
3
+ constructor(errorInfo) {
4
+ super(errorInfo.message);
5
+ this.errorInfo = errorInfo;
6
+ Object.setPrototypeOf(this, new.target.prototype);
7
+ }
8
+ get code() {
9
+ return this.errorInfo?.code;
10
+ }
11
+ toJSON() {
12
+ return { code: this.code, message: this.message };
13
+ }
14
+ }
15
+ export class PrefixedFirebaseError extends FirebaseError {
16
+ codePrefix;
17
+ constructor(codePrefix, code, message) {
18
+ super({ code: `${codePrefix}/${code}`, message });
19
+ this.codePrefix = codePrefix;
20
+ }
21
+ hasCode(code) {
22
+ return `${this.codePrefix}/${code}` === this.code;
23
+ }
24
+ }
25
+ //# sourceMappingURL=firebase-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"firebase-error.js","sourceRoot":"","sources":["../../src/app/firebase-error.ts"],"names":[],"mappings":"AAgBA,MAAM,OAAO,aAAc,SAAQ,KAAK;IAC7B,SAAS,CAAY;IAE9B,YAAY,SAAoB;QAC9B,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACzB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC;IAC9B,CAAC;IAED,MAAM;QACJ,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;IACpD,CAAC;CACF;AAED,MAAM,OAAO,qBAAsB,SAAQ,aAAa;IAC7C,UAAU,CAAS;IAE5B,YAAY,UAAkB,EAAE,IAAY,EAAE,OAAe;QAC3D,KAAK,CAAC,EAAE,IAAI,EAAE,GAAG,UAAU,IAAI,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QAClD,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAED,OAAO,CAAC,IAAY;QAClB,OAAO,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,EAAE,KAAK,IAAI,CAAC,IAAI,CAAC;IACpD,CAAC;CACF"}
@@ -40,6 +40,11 @@ export declare function applicationDefault(): never;
40
40
  * Link-compatible mirror of `firebase-admin/app`'s `cert(serviceAccountPathOrObject)`.
41
41
  * Returns an inert sandbox credential token so unchanged application setup code
42
42
  * can assign `appOptions.credential = cert(...)` under `@pyric/cli/register`.
43
+ *
44
+ * It keeps one field, the service account's project id, because firebase-admin
45
+ * finds an app's project there when the options name none. A path is read as a
46
+ * JSON file; a file that is missing, unreadable, or has no project id gives a
47
+ * credential without one, and never throws, so apps run without a key on disk.
43
48
  */
44
49
  export declare function cert(serviceAccountPathOrObject: unknown): object;
45
50
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/app/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAIL,KAAK,OAAO,EACb,MAAM,eAAe,CAAC;AAMvB,gDAAgD;AAChD,eAAO,MAAM,gBAAgB,eAAuC,CAAC;AACrE,MAAM,MAAM,mBAAmB,GAAG,SAAS,CAAC;AAE5C,yCAAyC;AACzC,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAE5C,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,CAAC,gBAAgB,CAAC,EAAE,SAAS,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8FAA8F;IAC9F,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;CACnC;AAED,sLAAsL;AACtL,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,aAAa,GAAG,eAAe,CAAC;AAC5C,MAAM,MAAM,wBAAwB,GAAG;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,eAAe,CAAC;AAE9E;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,KAAK,CAK1C;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,0BAA0B,EAAE,OAAO,GAAG,MAAM,CAEhE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,wBAAwB,EAAE,OAAO,GAAG,MAAM,CAEtE;AA4CD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,MAAM,CAAC,EAAE,wBAAwB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3D,IAAI,GAAE,MAAyB,GAC9B,aAAa,CAmCf;AAED,4CAA4C;AAC5C,wBAAgB,MAAM,CAAC,IAAI,GAAE,MAAyB,GAAG,aAAa,CAarE;AAED,yCAAyC;AACzC,wBAAgB,OAAO,IAAI,aAAa,EAAE,CAEzC;AAED,8CAA8C;AAC9C,wBAAgB,SAAS,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAS3D;AAwED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,aAAa,GAAG,GAAG,IAAI,eAAe,CAE5E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/app/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAIL,KAAK,OAAO,EACb,MAAM,eAAe,CAAC;AAOvB,gDAAgD;AAChD,eAAO,MAAM,gBAAgB,eAAuC,CAAC;AACrE,MAAM,MAAM,mBAAmB,GAAG,SAAS,CAAC;AAE5C,yCAAyC;AACzC,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAE5C,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,CAAC,gBAAgB,CAAC,EAAE,SAAS,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8FAA8F;IAC9F,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;CACnC;AAED,sLAAsL;AACtL,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,aAAa,GAAG,eAAe,CAAC;AAC5C,MAAM,MAAM,wBAAwB,GAAG;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,eAAe,CAAC;AAE9E;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,KAAK,CAK1C;AAED;;;;;;;;;GASG;AACH,wBAAgB,IAAI,CAAC,0BAA0B,EAAE,OAAO,GAAG,MAAM,CAIhE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,wBAAwB,EAAE,OAAO,GAAG,MAAM,CAEtE;AA4CD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,MAAM,CAAC,EAAE,wBAAwB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3D,IAAI,GAAE,MAAyB,GAC9B,aAAa,CAqCf;AAED,4CAA4C;AAC5C,wBAAgB,MAAM,CAAC,IAAI,GAAE,MAAyB,GAAG,aAAa,CAarE;AAED,yCAAyC;AACzC,wBAAgB,OAAO,IAAI,aAAa,EAAE,CAEzC;AAED,8CAA8C;AAC9C,wBAAgB,SAAS,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAS3D;AAwED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,aAAa,GAAG,GAAG,IAAI,eAAe,CAE5E"}
package/dist/app/index.js CHANGED
@@ -9,6 +9,7 @@ import { REMOTE_SANDBOX_FACTORY, } from 'pyric/sandbox';
9
9
  import { parsePyricSandbox, processActivation } from 'pyric/sandbox/internal';
10
10
  import { readFileSync } from 'node:fs';
11
11
  import { assertAdminAppActive, markAdminAppDeleted } from './lifecycle.js';
12
+ import { keepAppCredentialProjectId, keepCredentialProjectId } from './project-id.js';
12
13
  /** Brand carried by every sandbox admin app. */
13
14
  export const ADMIN_APP_TARGET = Symbol.for('pyric.admin.app.target');
14
15
  /** firebase-admin's default app name. */
@@ -28,9 +29,16 @@ export function applicationDefault() {
28
29
  * Link-compatible mirror of `firebase-admin/app`'s `cert(serviceAccountPathOrObject)`.
29
30
  * Returns an inert sandbox credential token so unchanged application setup code
30
31
  * can assign `appOptions.credential = cert(...)` under `@pyric/cli/register`.
32
+ *
33
+ * It keeps one field, the service account's project id, because firebase-admin
34
+ * finds an app's project there when the options name none. A path is read as a
35
+ * JSON file; a file that is missing, unreadable, or has no project id gives a
36
+ * credential without one, and never throws, so apps run without a key on disk.
31
37
  */
32
38
  export function cert(serviceAccountPathOrObject) {
33
- return { [Symbol.for('pyric.admin.credential')]: 'cert' };
39
+ const credential = { [Symbol.for('pyric.admin.credential')]: 'cert' };
40
+ keepCredentialProjectId(credential, serviceAccountPathOrObject);
41
+ return credential;
34
42
  }
35
43
  /**
36
44
  * Link-compatible mirror of `firebase-admin/app`'s `refreshToken(refreshTokenPathOrObject)`.
@@ -87,6 +95,7 @@ export function initializeApp(config, name = DEFAULT_APP_NAME) {
87
95
  throw alreadyExists(name, 'invalid-app-options');
88
96
  }
89
97
  const app = initializeAmbientApp(name, ambientAppOptions(config));
98
+ keepAppCredentialProjectId(app, config);
90
99
  appRegistry.set(name, app);
91
100
  ambientApps.add(app);
92
101
  return app;
@@ -102,6 +111,7 @@ export function initializeApp(config, name = DEFAULT_APP_NAME) {
102
111
  name,
103
112
  options: appOptionsFrom(config),
104
113
  };
114
+ keepAppCredentialProjectId(app, config);
105
115
  appRegistry.set(name, app);
106
116
  return app;
107
117
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/app/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/app/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /** Keeps the service account's project id on an inert credential, as a non-enumerable property. */
2
+ export declare function keepCredentialProjectId(credential: object, serviceAccountPathOrObject: unknown): void;
3
+ /** Copies a cert credential's project id from initialization options onto the app, beside `options`. */
4
+ export declare function keepAppCredentialProjectId(app: object, config: unknown): void;
5
+ /** firebase-admin's `getExplicitProjectId` for a sandbox admin app. */
6
+ export declare function explicitProjectId(app: {
7
+ readonly options: {
8
+ readonly projectId?: string;
9
+ };
10
+ }): string | undefined;
11
+ //# sourceMappingURL=project-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-id.d.ts","sourceRoot":"","sources":["../../src/app/project-id.ts"],"names":[],"mappings":"AA4CA,mGAAmG;AACnG,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,0BAA0B,EAAE,OAAO,GAAG,IAAI,CAGrG;AAED,wGAAwG;AACxG,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAM7E;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE;IAAE,QAAQ,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG,MAAM,GAAG,SAAS,CAMhH"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The project id an admin app resolves, in firebase-admin's order
3
+ * (`getExplicitProjectId`, firebase-admin 13 `lib/utils/index.js`): the
4
+ * `projectId` app option, then a cert credential's project id, then
5
+ * `GOOGLE_CLOUD_PROJECT`, then `GCLOUD_PROJECT`.
6
+ *
7
+ * Pyric's `cert()` is inert: it keeps the service account's project id and
8
+ * nothing else, so no private key or client email is held in the sandbox.
9
+ * Both keys sit behind `Symbol.for`, as the app registry does, so the require
10
+ * and import copies of this module that the register hook can load agree.
11
+ */
12
+ import { readFileSync } from 'node:fs';
13
+ const CREDENTIAL_PROJECT_ID = Symbol.for('pyric.admin.credential.projectId');
14
+ const APP_CREDENTIAL_PROJECT_ID = Symbol.for('pyric.admin.app.credentialProjectId');
15
+ function nonEmptyString(value) {
16
+ return typeof value === 'string' && value !== '' ? value : undefined;
17
+ }
18
+ /** firebase-admin's `ServiceAccount.fromPath`, except that a file it cannot read or parse yields no object. */
19
+ function readServiceAccountFile(path) {
20
+ try {
21
+ return JSON.parse(readFileSync(path, 'utf8'));
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ }
27
+ /** The project id of a service account object or file path: `projectId`, then `project_id`, as firebase-admin's `copyAttr` reads it. */
28
+ function serviceAccountProjectId(serviceAccountPathOrObject) {
29
+ const isPath = typeof serviceAccountPathOrObject === 'string';
30
+ const account = isPath ? readServiceAccountFile(serviceAccountPathOrObject) : serviceAccountPathOrObject;
31
+ if (typeof account !== 'object' || account === null)
32
+ return undefined;
33
+ const { projectId, project_id: projectIdSnake } = account;
34
+ return nonEmptyString(projectId) ?? nonEmptyString(projectIdSnake);
35
+ }
36
+ function defineHidden(target, key, value) {
37
+ Object.defineProperty(target, key, { value, enumerable: false, writable: false, configurable: false });
38
+ }
39
+ /** Keeps the service account's project id on an inert credential, as a non-enumerable property. */
40
+ export function keepCredentialProjectId(credential, serviceAccountPathOrObject) {
41
+ const projectId = serviceAccountProjectId(serviceAccountPathOrObject);
42
+ if (projectId !== undefined)
43
+ defineHidden(credential, CREDENTIAL_PROJECT_ID, projectId);
44
+ }
45
+ /** Copies a cert credential's project id from initialization options onto the app, beside `options`. */
46
+ export function keepAppCredentialProjectId(app, config) {
47
+ if (typeof config !== 'object' || config === null)
48
+ return;
49
+ const credential = config.credential;
50
+ if (typeof credential !== 'object' || credential === null)
51
+ return;
52
+ const projectId = nonEmptyString(credential[CREDENTIAL_PROJECT_ID]);
53
+ if (projectId !== undefined)
54
+ defineHidden(app, APP_CREDENTIAL_PROJECT_ID, projectId);
55
+ }
56
+ /** firebase-admin's `getExplicitProjectId` for a sandbox admin app. */
57
+ export function explicitProjectId(app) {
58
+ return (nonEmptyString(app.options.projectId) ??
59
+ nonEmptyString(app[APP_CREDENTIAL_PROJECT_ID]) ??
60
+ nonEmptyString(process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT));
61
+ }
62
+ //# sourceMappingURL=project-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-id.js","sourceRoot":"","sources":["../../src/app/project-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,MAAM,qBAAqB,GAAG,MAAM,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;AAC7E,MAAM,yBAAyB,GAAG,MAAM,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;AAIpF,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACvE,CAAC;AAED,+GAA+G;AAC/G,SAAS,sBAAsB,CAAC,IAAY;IAC1C,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,wIAAwI;AACxI,SAAS,uBAAuB,CAAC,0BAAmC;IAClE,MAAM,MAAM,GAAG,OAAO,0BAA0B,KAAK,QAAQ,CAAC;IAC9D,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,sBAAsB,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,0BAA0B,CAAC;IACzG,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACtE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,cAAc,EAAE,GAAG,OAAkC,CAAC;IACrF,OAAO,cAAc,CAAC,SAAS,CAAC,IAAI,cAAc,CAAC,cAAc,CAAC,CAAC;AACrE,CAAC;AAED,SAAS,YAAY,CAAC,MAAc,EAAE,GAAW,EAAE,KAAa;IAC9D,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC;AACzG,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,uBAAuB,CAAC,UAAkB,EAAE,0BAAmC;IAC7F,MAAM,SAAS,GAAG,uBAAuB,CAAC,0BAA0B,CAAC,CAAC;IACtE,IAAI,SAAS,KAAK,SAAS;QAAE,YAAY,CAAC,UAAU,EAAE,qBAAqB,EAAE,SAAS,CAAC,CAAC;AAC1F,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,0BAA0B,CAAC,GAAW,EAAE,MAAe;IACrE,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO;IAC1D,MAAM,UAAU,GAAI,MAAmC,CAAC,UAAU,CAAC;IACnE,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO;IAClE,MAAM,SAAS,GAAG,cAAc,CAAE,UAA4B,CAAC,qBAAqB,CAAC,CAAC,CAAC;IACvF,IAAI,SAAS,KAAK,SAAS;QAAE,YAAY,CAAC,GAAG,EAAE,yBAAyB,EAAE,SAAS,CAAC,CAAC;AACvF,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,iBAAiB,CAAC,GAA0D;IAC1F,OAAO,CACL,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC;QACrC,cAAc,CAAE,GAAqB,CAAC,yBAAyB,CAAC,CAAC;QACjE,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAC/E,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { PrefixedFirebaseError } from '../app/firebase-error.js';
2
+ export type AppCheckErrorCode = 'invalid-argument' | 'invalid-credential' | 'app-check-token-expired' | 'unknown-error';
3
+ /** firebase-admin's FirebaseAppCheckError: a PrefixedFirebaseError with prefix `app-check`. */
4
+ export declare class FirebaseAppCheckError extends PrefixedFirebaseError {
5
+ constructor(code: AppCheckErrorCode, message: string);
6
+ }
7
+ //# sourceMappingURL=error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/app-check/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,GAAG,oBAAoB,GAAG,yBAAyB,GAAG,eAAe,CAAC;AAExH,+FAA+F;AAC/F,qBAAa,qBAAsB,SAAQ,qBAAqB;gBAClD,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM;CAGrD"}
@@ -0,0 +1,8 @@
1
+ import { PrefixedFirebaseError } from '../app/firebase-error.js';
2
+ /** firebase-admin's FirebaseAppCheckError: a PrefixedFirebaseError with prefix `app-check`. */
3
+ export class FirebaseAppCheckError extends PrefixedFirebaseError {
4
+ constructor(code, message) {
5
+ super('app-check', code, message);
6
+ }
7
+ }
8
+ //# sourceMappingURL=error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/app-check/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAIjE,+FAA+F;AAC/F,MAAM,OAAO,qBAAsB,SAAQ,qBAAqB;IAC9D,YAAY,IAAuB,EAAE,OAAe;QAClD,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACpC,CAAC;CACF"}
@@ -0,0 +1,7 @@
1
+ import type { VerifyAppCheckTokenResponse } from './index.js';
2
+ /**
3
+ * Verifies a token from Firebase with firebase-admin, through the verifier the
4
+ * register installs. Its result and its errors come back unchanged.
5
+ */
6
+ export declare function verifyFirebaseToken(token: string, projectId: string): Promise<VerifyAppCheckTokenResponse>;
7
+ //# sourceMappingURL=firebase-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"firebase-token.d.ts","sourceRoot":"","sources":["../../src/app-check/firebase-token.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AA8B9D;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAShH"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * App Check tokens from Firebase: a token whose `kid` names a key that is not
3
+ * the sandbox's (#1552, #1301).
4
+ *
5
+ * In production AI mode the page's `firebase/app-check` is the Firebase SDK,
6
+ * so its tokens come from Firebase, and only firebase-admin's own verifier can
7
+ * check them. `@pyric/cli/register` installs that verifier behind
8
+ * {@link FIREBASE_VERIFIER} in production AI mode only, the same kind of seam
9
+ * as its `pyric.remote.sandboxFactory`: this package never learns what an AI
10
+ * mode is, and never loads firebase-admin itself.
11
+ *
12
+ * With no verifier installed the token is refused with a message that says
13
+ * why. The code stays `invalid-argument`, so a server that branches on it
14
+ * still answers 401.
15
+ */
16
+ import { FirebaseAppCheckError } from './error.js';
17
+ const FIREBASE_VERIFIER = Symbol.for('pyric.admin.appCheck.firebaseVerifier');
18
+ const MESSAGES = {
19
+ notSandbox: "This App Check token was not minted by the Pyric sandbox, so the sandbox's firebase-admin/app-check cannot verify it. " +
20
+ 'A page in production AI mode gets its App Check tokens from Firebase. Start this server in production AI mode too ' +
21
+ '(PYRIC_AI_MODE=production) and Pyric verifies them with firebase-admin. In sandbox AI mode, page tokens come from the sandbox and verify here.',
22
+ install: 'Production AI mode verifies App Check tokens from Firebase with firebase-admin, which is not installed beside this server. ' +
23
+ 'Install it in the project that runs the server (npm install firebase-admin) and restart.',
24
+ };
25
+ function installedVerifier() {
26
+ const verifier = globalThis[FIREBASE_VERIFIER];
27
+ return typeof verifier === 'function' ? verifier : undefined;
28
+ }
29
+ /** Whether a failure is the `firebase-admin` package itself not resolving, from ESM or CJS. */
30
+ function isFirebaseAdminMissing(error) {
31
+ const { code, message } = (error ?? {});
32
+ const isNotFound = code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND';
33
+ const namesFirebaseAdmin = typeof message === 'string' && /'firebase-admin[/']/.test(message);
34
+ return isNotFound && namesFirebaseAdmin;
35
+ }
36
+ /**
37
+ * Verifies a token from Firebase with firebase-admin, through the verifier the
38
+ * register installs. Its result and its errors come back unchanged.
39
+ */
40
+ export async function verifyFirebaseToken(token, projectId) {
41
+ const verifier = installedVerifier();
42
+ if (verifier === undefined)
43
+ throw new FirebaseAppCheckError('invalid-argument', MESSAGES.notSandbox);
44
+ try {
45
+ return await verifier(token, projectId);
46
+ }
47
+ catch (error) {
48
+ if (isFirebaseAdminMissing(error))
49
+ throw new FirebaseAppCheckError('invalid-argument', MESSAGES.install);
50
+ throw error;
51
+ }
52
+ }
53
+ //# sourceMappingURL=firebase-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"firebase-token.js","sourceRoot":"","sources":["../../src/app-check/firebase-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAGnD,MAAM,iBAAiB,GAAG,MAAM,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;AAK9E,MAAM,QAAQ,GAAG;IACf,UAAU,EACR,wHAAwH;QACxH,oHAAoH;QACpH,gJAAgJ;IAClJ,OAAO,EACL,6HAA6H;QAC7H,0FAA0F;CAC7F,CAAC;AAEF,SAAS,iBAAiB;IACxB,MAAM,QAAQ,GAAI,UAAsC,CAAC,iBAAiB,CAAC,CAAC;IAC5E,OAAO,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAE,QAA6B,CAAC,CAAC,CAAC,SAAS,CAAC;AACrF,CAAC;AAED,+FAA+F;AAC/F,SAAS,sBAAsB,CAAC,KAAc;IAC5C,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE,CAA0C,CAAC;IACjF,MAAM,UAAU,GAAG,IAAI,KAAK,sBAAsB,IAAI,IAAI,KAAK,kBAAkB,CAAC;IAClF,MAAM,kBAAkB,GAAG,OAAO,OAAO,KAAK,QAAQ,IAAI,qBAAqB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC9F,OAAO,UAAU,IAAI,kBAAkB,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,KAAa,EAAE,SAAiB;IACxE,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAC;IACrC,IAAI,QAAQ,KAAK,SAAS;QAAE,MAAM,IAAI,qBAAqB,CAAC,kBAAkB,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;IACrG,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,sBAAsB,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,qBAAqB,CAAC,kBAAkB,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;QACzG,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -1,15 +1,54 @@
1
1
  /**
2
- * `pyric-admin/app-check`: deferred service handle for `firebase-admin/app-check`.
2
+ * `pyric-admin/app-check`: the sandbox mirror of `firebase-admin/app-check`.
3
+ *
4
+ * `createToken` mints the same sandbox token the web mirror's `getToken`
5
+ * returns (shared in `pyric/sandbox/internal`), so a token minted on either
6
+ * plane verifies here. Shapes, TTL limits, error codes, and consume semantics
7
+ * follow the `app-check-admin-` captures; the rows are in
8
+ * packages/conformance/registry/app-check.ts (surface `app-check-admin`).
9
+ *
10
+ * One deliberate difference: the sandbox has no list of the project's
11
+ * registered apps, so `createToken` accepts any app id (row
12
+ * `app-check-admin#create-token-unknown-app`, diverged by design).
13
+ *
14
+ * A token whose `kid` is not the sandbox's came from Firebase. It goes to
15
+ * firebase-admin's own verifier when `@pyric/cli/register` installed one
16
+ * (production AI mode), and is refused with a message that says why otherwise
17
+ * (./firebase-token.ts).
3
18
  */
4
- import type { PyricAdminApp } from '../app/index.js';
5
- export { PyricDeferredApiError } from '../deferred.js';
19
+ import { type AppCheckTokenClaims } from 'pyric/sandbox/internal';
20
+ import { type PyricAdminApp } from '../app/index.js';
21
+ export interface AppCheckTokenOptions {
22
+ ttlMillis?: number;
23
+ }
24
+ export interface AppCheckToken {
25
+ token: string;
26
+ ttlMillis: number;
27
+ }
28
+ export interface VerifyAppCheckTokenOptions {
29
+ consume?: boolean;
30
+ }
31
+ export interface DecodedAppCheckToken extends AppCheckTokenClaims {
32
+ app_id: string;
33
+ }
34
+ export interface VerifyAppCheckTokenResponse {
35
+ appId: string;
36
+ token: DecodedAppCheckToken;
37
+ alreadyConsumed?: boolean;
38
+ }
6
39
  export declare class AppCheck {
7
40
  #private;
8
41
  /** @internal */
9
- constructor(...args: unknown[]);
42
+ constructor(app: PyricAdminApp);
10
43
  get app(): PyricAdminApp;
11
- createToken: (...args: unknown[]) => Promise<never>;
12
- verifyToken: (...args: unknown[]) => Promise<never>;
44
+ createToken(appId: string, options?: AppCheckTokenOptions): Promise<AppCheckToken>;
45
+ /**
46
+ * Checks in firebase-admin's order (app-check.js, token-verifier.js,
47
+ * utils/jwt.js): the options and the token's type, synchronously as
48
+ * upstream throws them; then the project, decoding, content, key,
49
+ * signature, and last `nbf` and `exp` as jsonwebtoken reads them.
50
+ */
51
+ verifyToken(appCheckToken: string, options?: VerifyAppCheckTokenOptions): Promise<VerifyAppCheckTokenResponse>;
13
52
  }
14
- export declare const getAppCheck: (app?: PyricAdminApp) => AppCheck;
53
+ export declare function getAppCheck(app?: PyricAdminApp): AppCheck;
15
54
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/app-check/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,qBAAa,QAAQ;;IAGnB,gBAAgB;gBACJ,GAAG,IAAI,EAAE,OAAO,EAAE;IAI9B,IAAI,GAAG,IAAI,aAAa,CAEvB;IAEO,WAAW,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;IACpD,WAAW,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;CAC7D;AAED,eAAO,MAAM,WAAW,EAAE,CAAC,GAAG,CAAC,EAAE,aAAa,KAAK,QAOlD,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/app-check/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAyC,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AACzG,OAAO,EAAU,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAiC7D,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,oBAAqB,SAAQ,mBAAmB;IAC/D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,oBAAoB,CAAC;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAgCD,qBAAa,QAAQ;;IAGnB,gBAAgB;gBACJ,GAAG,EAAE,aAAa;IAI9B,IAAI,GAAG,IAAI,aAAa,CAEvB;IAEK,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC;IAUxF;;;;;OAKG;IACH,WAAW,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,0BAA0B,GAAG,OAAO,CAAC,2BAA2B,CAAC;CAqD/G;AAoCD,wBAAgB,WAAW,CAAC,GAAG,CAAC,EAAE,aAAa,GAAG,QAAQ,CAQzD"}
@@ -1,16 +1,202 @@
1
- import { guardDeferredServiceApp, wireDeferredService } from '../deferred.js';
2
- export { PyricDeferredApiError } from '../deferred.js';
1
+ /**
2
+ * `pyric-admin/app-check`: the sandbox mirror of `firebase-admin/app-check`.
3
+ *
4
+ * `createToken` mints the same sandbox token the web mirror's `getToken`
5
+ * returns (shared in `pyric/sandbox/internal`), so a token minted on either
6
+ * plane verifies here. Shapes, TTL limits, error codes, and consume semantics
7
+ * follow the `app-check-admin-` captures; the rows are in
8
+ * packages/conformance/registry/app-check.ts (surface `app-check-admin`).
9
+ *
10
+ * One deliberate difference: the sandbox has no list of the project's
11
+ * registered apps, so `createToken` accepts any app id (row
12
+ * `app-check-admin#create-token-unknown-app`, diverged by design).
13
+ *
14
+ * A token whose `kid` is not the sandbox's came from Firebase. It goes to
15
+ * firebase-admin's own verifier when `@pyric/cli/register` installed one
16
+ * (production AI mode), and is refused with a message that says why otherwise
17
+ * (./firebase-token.ts).
18
+ */
19
+ import { checkAppCheckToken, mintAppCheckToken } from 'pyric/sandbox/internal';
20
+ import { getApp } from '../app/index.js';
21
+ import { explicitProjectId } from '../app/project-id.js';
22
+ import { assertAdminAppActive } from '../app/lifecycle.js';
23
+ import { FirebaseAppCheckError } from './error.js';
24
+ import { verifyFirebaseToken } from './firebase-token.js';
25
+ const MINUTE_MILLIS = 60 * 1000;
26
+ const MIN_TTL_MILLIS = 30 * MINUTE_MILLIS;
27
+ const MAX_TTL_MILLIS = 7 * 24 * 60 * MINUTE_MILLIS;
28
+ const DEFAULT_TTL_MILLIS = 60 * MINUTE_MILLIS;
29
+ const MESSAGES = {
30
+ notString: 'App check token must be a non-null string.',
31
+ options: 'VerifyAppCheckTokenOptions must be a non-null object.',
32
+ algorithm: (alg) => `The provided App Check token has incorrect algorithm. Expected "RS256" but got "${String(alg)}".`,
33
+ noSub: 'The provided App Check token has no "sub" (subject) claim.',
34
+ emptySub: 'The provided App Check token has an empty string "sub" (subject) claim.',
35
+ ttl: 'ttlMillis must be a duration in milliseconds between 30 minutes and 7 days (inclusive).',
36
+ appId: '`appId` must be a non-empty string.',
37
+ decode: 'Decoding App Check token failed. Make sure you passed the entire string JWT which represents the Firebase App Check token.',
38
+ signature: 'The provided App Check token has invalid signature.',
39
+ expired: 'The provided App Check token has expired. Get a fresh App Check token from your client app and try again.',
40
+ issuer: 'The provided App Check token has incorrect "iss" (issuer) claim.',
41
+ createNoProject: 'Failed to determine project ID. Initialize the SDK with service account credentials or set project ID as an app option. Alternatively, set the GOOGLE_CLOUD_PROJECT environment variable.',
42
+ noProject: 'Must initialize app with a cert credential or set your Firebase project ID as the GOOGLE_CLOUD_PROJECT environment variable to verify an App Check token.',
43
+ audience: (scoped, aud) => `The provided App Check token has incorrect "aud" (audience) claim. Expected "${scoped}" but got "${String(aud)}". Make sure the App Check token comes from the same Firebase project as the service account used to authenticate this SDK.`,
44
+ };
45
+ const APP_CHECK_ISSUER = 'https://firebaseappcheck.googleapis.com/';
46
+ /**
47
+ * Consumed token ids, per sandbox handle, for this process. The map sits
48
+ * behind Symbol.for, as the app registry in ../app/index.ts does, so the
49
+ * require and import copies of this module that the register hook can load
50
+ * share it. It is in memory: it does not survive a restart, and two processes
51
+ * on one dev server each keep their own. Production consumes once per
52
+ * project; moving this into the sandbox is #1297.
53
+ */
54
+ const CONSUMED_BY_SANDBOX = Symbol.for('pyric.admin.appCheck.consumedBySandbox');
55
+ function consumedFor(app) {
56
+ const global = globalThis;
57
+ const bySandbox = (global[CONSUMED_BY_SANDBOX] ??= new WeakMap());
58
+ const key = app.sandbox;
59
+ let consumed = bySandbox.get(key);
60
+ if (!consumed) {
61
+ consumed = new Set();
62
+ bySandbox.set(key, consumed);
63
+ }
64
+ return consumed;
65
+ }
66
+ function ttlFrom(options) {
67
+ const ttl = options?.ttlMillis ?? DEFAULT_TTL_MILLIS;
68
+ const inRange = typeof ttl === 'number' && ttl >= MIN_TTL_MILLIS && ttl <= MAX_TTL_MILLIS;
69
+ if (!inRange)
70
+ throw new FirebaseAppCheckError('invalid-argument', MESSAGES.ttl);
71
+ return ttl;
72
+ }
3
73
  export class AppCheck {
4
74
  #app;
5
75
  /** @internal */
6
- constructor(...args) {
7
- this.#app = guardDeferredServiceApp('app-check', 'AppCheck', args);
76
+ constructor(app) {
77
+ this.#app = app;
8
78
  }
9
79
  get app() {
10
80
  return this.#app;
11
81
  }
82
+ async createToken(appId, options) {
83
+ assertAdminAppActive(this.#app);
84
+ if (typeof appId !== 'string' || appId.length === 0)
85
+ throw new FirebaseAppCheckError('invalid-argument', MESSAGES.appId);
86
+ const ttlMillis = ttlFrom(options);
87
+ const projectId = explicitProjectId(this.#app);
88
+ if (!projectId)
89
+ throw new FirebaseAppCheckError('unknown-error', MESSAGES.createNoProject);
90
+ const minted = await mintAppCheckToken({ appId, projectId }, Math.floor(ttlMillis / 1000));
91
+ return { token: minted.token, ttlMillis };
92
+ }
93
+ /**
94
+ * Checks in firebase-admin's order (app-check.js, token-verifier.js,
95
+ * utils/jwt.js): the options and the token's type, synchronously as
96
+ * upstream throws them; then the project, decoding, content, key,
97
+ * signature, and last `nbf` and `exp` as jsonwebtoken reads them.
98
+ */
99
+ verifyToken(appCheckToken, options) {
100
+ assertAdminAppActive(this.#app);
101
+ const optionsAreObject = typeof options === 'object' && options !== null && !Array.isArray(options);
102
+ if (options !== undefined && !optionsAreObject)
103
+ throw new FirebaseAppCheckError('invalid-argument', MESSAGES.options);
104
+ if (typeof appCheckToken !== 'string')
105
+ throw new FirebaseAppCheckError('invalid-argument', MESSAGES.notString);
106
+ return this.#verify(appCheckToken, options);
107
+ }
108
+ async #verify(appCheckToken, options) {
109
+ const projectId = this.#projectId();
110
+ const check = await checkAppCheckToken(appCheckToken);
111
+ if (!check.ok && check.reason === 'malformed')
112
+ throw new FirebaseAppCheckError('invalid-argument', MESSAGES.decode);
113
+ // A kid that names a key other than the sandbox's: the token came from
114
+ // Firebase, so firebase-admin runs every check, in production's order.
115
+ const isFirebaseToken = !check.ok && check.reason === 'kid';
116
+ if (isFirebaseToken) {
117
+ const verified = await verifyFirebaseToken(appCheckToken, projectId);
118
+ return this.#recordConsume(verified, appCheckToken, options);
119
+ }
120
+ verifyContent(check.header, check.claims, projectId);
121
+ if (!check.ok)
122
+ throw new FirebaseAppCheckError('invalid-argument', MESSAGES.signature);
123
+ verifyNotBefore(check.claims.nbf);
124
+ verifyExpiry(check.claims.exp);
125
+ const claims = check.claims;
126
+ const result = { appId: claims.sub, token: { ...claims, app_id: claims.sub } };
127
+ return this.#recordConsume(result, appCheckToken, options);
128
+ }
129
+ /**
130
+ * `consume: true` marks the token used, in this process, for sandbox and
131
+ * Firebase tokens alike. It is never forwarded to Firebase, which would need
132
+ * a credential with App Check permissions.
133
+ */
134
+ #recordConsume(result, appCheckToken, options) {
135
+ if (!options?.consume)
136
+ return result;
137
+ const consumed = consumedFor(this.#app);
138
+ const jti = result.token.jti;
139
+ const tokenId = typeof jti === 'string' ? jti : appCheckToken;
140
+ result.alreadyConsumed = consumed.has(tokenId);
141
+ consumed.add(tokenId);
142
+ return result;
143
+ }
144
+ /** The project a token must be scoped to, resolved as firebase-admin resolves it. */
145
+ #projectId() {
146
+ const projectId = explicitProjectId(this.#app);
147
+ if (!projectId)
148
+ throw new FirebaseAppCheckError('invalid-credential', MESSAGES.noProject);
149
+ return projectId;
150
+ }
151
+ }
152
+ /** token-verifier.js verifyContent: algorithm, audience, issuer, then subject, first failure wins. */
153
+ function verifyContent(header, claims, projectId) {
154
+ if (header.alg !== 'RS256')
155
+ throw new FirebaseAppCheckError('invalid-argument', MESSAGES.algorithm(header.alg));
156
+ const scoped = `projects/${projectId}`;
157
+ const audienceMatches = Array.isArray(claims.aud) && claims.aud.length > 0 && claims.aud.includes(scoped);
158
+ if (!audienceMatches)
159
+ throw new FirebaseAppCheckError('invalid-argument', MESSAGES.audience(scoped, claims.aud));
160
+ const issuerMatches = typeof claims.iss === 'string' && claims.iss.startsWith(APP_CHECK_ISSUER);
161
+ if (!issuerMatches)
162
+ throw new FirebaseAppCheckError('invalid-argument', MESSAGES.issuer);
163
+ if (typeof claims.sub !== 'string')
164
+ throw new FirebaseAppCheckError('invalid-argument', MESSAGES.noSub);
165
+ if (claims.sub === '')
166
+ throw new FirebaseAppCheckError('invalid-argument', MESSAGES.emptySub);
167
+ }
168
+ /** jsonwebtoken's nbf rule, which runs before exp: firebase-admin reports both failures as an invalid signature. */
169
+ function verifyNotBefore(nbf) {
170
+ if (nbf === undefined)
171
+ return;
172
+ const nowSeconds = Math.floor(Date.now() / 1000);
173
+ const active = typeof nbf === 'number' && nbf <= nowSeconds;
174
+ if (!active)
175
+ throw new FirebaseAppCheckError('invalid-argument', MESSAGES.signature);
176
+ }
177
+ /**
178
+ * jsonwebtoken's exp rule: an absent `exp` never expires, a non-number is an
179
+ * error that firebase-admin reports as an invalid signature, and the token is
180
+ * expired once the current second reaches `exp`.
181
+ */
182
+ function verifyExpiry(exp) {
183
+ if (exp === undefined)
184
+ return;
185
+ if (typeof exp !== 'number')
186
+ throw new FirebaseAppCheckError('invalid-argument', MESSAGES.signature);
187
+ const nowSeconds = Math.floor(Date.now() / 1000);
188
+ if (nowSeconds >= exp)
189
+ throw new FirebaseAppCheckError('app-check-token-expired', MESSAGES.expired);
190
+ }
191
+ const instances = new WeakMap();
192
+ export function getAppCheck(app) {
193
+ const resolved = app ?? getApp();
194
+ assertAdminAppActive(resolved);
195
+ const existing = instances.get(resolved);
196
+ if (existing)
197
+ return existing;
198
+ const instance = new AppCheck(resolved);
199
+ instances.set(resolved, instance);
200
+ return instance;
12
201
  }
13
- export const getAppCheck = wireDeferredService(AppCheck, 'app-check', 'AppCheck', {
14
- async: ['createToken', 'verifyToken'],
15
- });
16
202
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/app-check/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE9E,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,OAAO,QAAQ;IACV,IAAI,CAAgB;IAE7B,gBAAgB;IAChB,YAAY,GAAG,IAAe;QAC5B,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC,WAAW,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;IACrE,CAAC;IAED,IAAI,GAAG;QACL,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;CAIF;AAED,MAAM,CAAC,MAAM,WAAW,GAAsC,mBAAmB,CAC/E,QAAQ,EACR,WAAW,EACX,UAAU,EACV;IACE,KAAK,EAAE,CAAC,aAAa,EAAE,aAAa,CAAC;CACtC,CACF,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/app-check/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pyric-admin",
3
- "version": "0.1.0-alpha.29",
3
+ "version": "0.1.0-alpha.30.1",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://pyric.dev",
6
6
  "repository": {
@@ -106,7 +106,7 @@
106
106
  "typecheck": "bun x tsc -p tsconfig.json --noEmit"
107
107
  },
108
108
  "dependencies": {
109
- "pyric": "^0.1.0-alpha.29"
109
+ "pyric": "^0.1.0-alpha.30.1"
110
110
  },
111
111
  "devDependencies": {
112
112
  "@types/bun": "1.3.14",
@@ -0,0 +1,46 @@
1
+ /**
2
+ * firebase-admin's error shape (lib/utils/error.js, 13.10.0), shared by the
3
+ * admin mirrors so each service's error class matches upstream in one place.
4
+ *
5
+ * Upstream keeps `{ code, message }` in an own `errorInfo` property and reads
6
+ * `code` through a prototype getter. Its `message` getter is shadowed by the
7
+ * own, non-enumerable `message` that `Error` sets, so only `code` needs one
8
+ * here. `toJSON()` returns both, and
9
+ * `hasCode()` tests the unprefixed code. It never sets `name`, so `name` reads
10
+ * `Error`. The own enumerable keys are `errorInfo` and `codePrefix`.
11
+ */
12
+ export interface ErrorInfo {
13
+ code: string;
14
+ message: string;
15
+ }
16
+
17
+ export class FirebaseError extends Error {
18
+ readonly errorInfo: ErrorInfo;
19
+
20
+ constructor(errorInfo: ErrorInfo) {
21
+ super(errorInfo.message);
22
+ this.errorInfo = errorInfo;
23
+ Object.setPrototypeOf(this, new.target.prototype);
24
+ }
25
+
26
+ get code(): string {
27
+ return this.errorInfo?.code;
28
+ }
29
+
30
+ toJSON(): ErrorInfo {
31
+ return { code: this.code, message: this.message };
32
+ }
33
+ }
34
+
35
+ export class PrefixedFirebaseError extends FirebaseError {
36
+ readonly codePrefix: string;
37
+
38
+ constructor(codePrefix: string, code: string, message: string) {
39
+ super({ code: `${codePrefix}/${code}`, message });
40
+ this.codePrefix = codePrefix;
41
+ }
42
+
43
+ hasCode(code: string): boolean {
44
+ return `${this.codePrefix}/${code}` === this.code;
45
+ }
46
+ }
package/src/app/index.ts CHANGED
@@ -15,6 +15,7 @@ import { parsePyricSandbox, processActivation } from 'pyric/sandbox/internal';
15
15
  import { readFileSync } from 'node:fs';
16
16
 
17
17
  import { assertAdminAppActive, markAdminAppDeleted } from './lifecycle.js';
18
+ import { keepAppCredentialProjectId, keepCredentialProjectId } from './project-id.js';
18
19
 
19
20
  /** Brand carried by every sandbox admin app. */
20
21
  export const ADMIN_APP_TARGET = Symbol.for('pyric.admin.app.target');
@@ -59,9 +60,16 @@ export function applicationDefault(): never {
59
60
  * Link-compatible mirror of `firebase-admin/app`'s `cert(serviceAccountPathOrObject)`.
60
61
  * Returns an inert sandbox credential token so unchanged application setup code
61
62
  * can assign `appOptions.credential = cert(...)` under `@pyric/cli/register`.
63
+ *
64
+ * It keeps one field, the service account's project id, because firebase-admin
65
+ * finds an app's project there when the options name none. A path is read as a
66
+ * JSON file; a file that is missing, unreadable, or has no project id gives a
67
+ * credential without one, and never throws, so apps run without a key on disk.
62
68
  */
63
69
  export function cert(serviceAccountPathOrObject: unknown): object {
64
- return { [Symbol.for('pyric.admin.credential')]: 'cert' };
70
+ const credential = { [Symbol.for('pyric.admin.credential')]: 'cert' };
71
+ keepCredentialProjectId(credential, serviceAccountPathOrObject);
72
+ return credential;
65
73
  }
66
74
 
67
75
  /**
@@ -139,6 +147,7 @@ export function initializeApp(
139
147
  throw alreadyExists(name, 'invalid-app-options');
140
148
  }
141
149
  const app = initializeAmbientApp(name, ambientAppOptions(config));
150
+ keepAppCredentialProjectId(app, config);
142
151
  appRegistry.set(name, app);
143
152
  ambientApps.add(app);
144
153
  return app;
@@ -158,6 +167,7 @@ export function initializeApp(
158
167
  name,
159
168
  options: appOptionsFrom(config),
160
169
  };
170
+ keepAppCredentialProjectId(app, config);
161
171
  appRegistry.set(name, app);
162
172
  return app;
163
173
  }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The project id an admin app resolves, in firebase-admin's order
3
+ * (`getExplicitProjectId`, firebase-admin 13 `lib/utils/index.js`): the
4
+ * `projectId` app option, then a cert credential's project id, then
5
+ * `GOOGLE_CLOUD_PROJECT`, then `GCLOUD_PROJECT`.
6
+ *
7
+ * Pyric's `cert()` is inert: it keeps the service account's project id and
8
+ * nothing else, so no private key or client email is held in the sandbox.
9
+ * Both keys sit behind `Symbol.for`, as the app registry does, so the require
10
+ * and import copies of this module that the register hook can load agree.
11
+ */
12
+ import { readFileSync } from 'node:fs';
13
+
14
+ const CREDENTIAL_PROJECT_ID = Symbol.for('pyric.admin.credential.projectId');
15
+ const APP_CREDENTIAL_PROJECT_ID = Symbol.for('pyric.admin.app.credentialProjectId');
16
+
17
+ type WithProjectId = { [CREDENTIAL_PROJECT_ID]?: unknown; [APP_CREDENTIAL_PROJECT_ID]?: unknown };
18
+
19
+ function nonEmptyString(value: unknown): string | undefined {
20
+ return typeof value === 'string' && value !== '' ? value : undefined;
21
+ }
22
+
23
+ /** firebase-admin's `ServiceAccount.fromPath`, except that a file it cannot read or parse yields no object. */
24
+ function readServiceAccountFile(path: string): unknown {
25
+ try {
26
+ return JSON.parse(readFileSync(path, 'utf8'));
27
+ } catch {
28
+ return undefined;
29
+ }
30
+ }
31
+
32
+ /** The project id of a service account object or file path: `projectId`, then `project_id`, as firebase-admin's `copyAttr` reads it. */
33
+ function serviceAccountProjectId(serviceAccountPathOrObject: unknown): string | undefined {
34
+ const isPath = typeof serviceAccountPathOrObject === 'string';
35
+ const account = isPath ? readServiceAccountFile(serviceAccountPathOrObject) : serviceAccountPathOrObject;
36
+ if (typeof account !== 'object' || account === null) return undefined;
37
+ const { projectId, project_id: projectIdSnake } = account as Record<string, unknown>;
38
+ return nonEmptyString(projectId) ?? nonEmptyString(projectIdSnake);
39
+ }
40
+
41
+ function defineHidden(target: object, key: symbol, value: string): void {
42
+ Object.defineProperty(target, key, { value, enumerable: false, writable: false, configurable: false });
43
+ }
44
+
45
+ /** Keeps the service account's project id on an inert credential, as a non-enumerable property. */
46
+ export function keepCredentialProjectId(credential: object, serviceAccountPathOrObject: unknown): void {
47
+ const projectId = serviceAccountProjectId(serviceAccountPathOrObject);
48
+ if (projectId !== undefined) defineHidden(credential, CREDENTIAL_PROJECT_ID, projectId);
49
+ }
50
+
51
+ /** Copies a cert credential's project id from initialization options onto the app, beside `options`. */
52
+ export function keepAppCredentialProjectId(app: object, config: unknown): void {
53
+ if (typeof config !== 'object' || config === null) return;
54
+ const credential = (config as { credential?: unknown }).credential;
55
+ if (typeof credential !== 'object' || credential === null) return;
56
+ const projectId = nonEmptyString((credential as WithProjectId)[CREDENTIAL_PROJECT_ID]);
57
+ if (projectId !== undefined) defineHidden(app, APP_CREDENTIAL_PROJECT_ID, projectId);
58
+ }
59
+
60
+ /** firebase-admin's `getExplicitProjectId` for a sandbox admin app. */
61
+ export function explicitProjectId(app: { readonly options: { readonly projectId?: string } }): string | undefined {
62
+ return (
63
+ nonEmptyString(app.options.projectId) ??
64
+ nonEmptyString((app as WithProjectId)[APP_CREDENTIAL_PROJECT_ID]) ??
65
+ nonEmptyString(process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT)
66
+ );
67
+ }
@@ -0,0 +1,10 @@
1
+ import { PrefixedFirebaseError } from '../app/firebase-error.js';
2
+
3
+ export type AppCheckErrorCode = 'invalid-argument' | 'invalid-credential' | 'app-check-token-expired' | 'unknown-error';
4
+
5
+ /** firebase-admin's FirebaseAppCheckError: a PrefixedFirebaseError with prefix `app-check`. */
6
+ export class FirebaseAppCheckError extends PrefixedFirebaseError {
7
+ constructor(code: AppCheckErrorCode, message: string) {
8
+ super('app-check', code, message);
9
+ }
10
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * App Check tokens from Firebase: a token whose `kid` names a key that is not
3
+ * the sandbox's (#1552, #1301).
4
+ *
5
+ * In production AI mode the page's `firebase/app-check` is the Firebase SDK,
6
+ * so its tokens come from Firebase, and only firebase-admin's own verifier can
7
+ * check them. `@pyric/cli/register` installs that verifier behind
8
+ * {@link FIREBASE_VERIFIER} in production AI mode only, the same kind of seam
9
+ * as its `pyric.remote.sandboxFactory`: this package never learns what an AI
10
+ * mode is, and never loads firebase-admin itself.
11
+ *
12
+ * With no verifier installed the token is refused with a message that says
13
+ * why. The code stays `invalid-argument`, so a server that branches on it
14
+ * still answers 401.
15
+ */
16
+ import { FirebaseAppCheckError } from './error.js';
17
+ import type { VerifyAppCheckTokenResponse } from './index.js';
18
+
19
+ const FIREBASE_VERIFIER = Symbol.for('pyric.admin.appCheck.firebaseVerifier');
20
+
21
+ /** firebase-admin's `getAppCheck(app).verifyToken(token)` for an app scoped to `projectId`, with no options. */
22
+ type FirebaseVerifier = (token: string, projectId: string) => Promise<VerifyAppCheckTokenResponse>;
23
+
24
+ const MESSAGES = {
25
+ notSandbox:
26
+ "This App Check token was not minted by the Pyric sandbox, so the sandbox's firebase-admin/app-check cannot verify it. " +
27
+ 'A page in production AI mode gets its App Check tokens from Firebase. Start this server in production AI mode too ' +
28
+ '(PYRIC_AI_MODE=production) and Pyric verifies them with firebase-admin. In sandbox AI mode, page tokens come from the sandbox and verify here.',
29
+ install:
30
+ 'Production AI mode verifies App Check tokens from Firebase with firebase-admin, which is not installed beside this server. ' +
31
+ 'Install it in the project that runs the server (npm install firebase-admin) and restart.',
32
+ };
33
+
34
+ function installedVerifier(): FirebaseVerifier | undefined {
35
+ const verifier = (globalThis as Record<symbol, unknown>)[FIREBASE_VERIFIER];
36
+ return typeof verifier === 'function' ? (verifier as FirebaseVerifier) : undefined;
37
+ }
38
+
39
+ /** Whether a failure is the `firebase-admin` package itself not resolving, from ESM or CJS. */
40
+ function isFirebaseAdminMissing(error: unknown): boolean {
41
+ const { code, message } = (error ?? {}) as { code?: unknown; message?: unknown };
42
+ const isNotFound = code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND';
43
+ const namesFirebaseAdmin = typeof message === 'string' && /'firebase-admin[/']/.test(message);
44
+ return isNotFound && namesFirebaseAdmin;
45
+ }
46
+
47
+ /**
48
+ * Verifies a token from Firebase with firebase-admin, through the verifier the
49
+ * register installs. Its result and its errors come back unchanged.
50
+ */
51
+ export async function verifyFirebaseToken(token: string, projectId: string): Promise<VerifyAppCheckTokenResponse> {
52
+ const verifier = installedVerifier();
53
+ if (verifier === undefined) throw new FirebaseAppCheckError('invalid-argument', MESSAGES.notSandbox);
54
+ try {
55
+ return await verifier(token, projectId);
56
+ } catch (error) {
57
+ if (isFirebaseAdminMissing(error)) throw new FirebaseAppCheckError('invalid-argument', MESSAGES.install);
58
+ throw error;
59
+ }
60
+ }
@@ -1,32 +1,231 @@
1
1
  /**
2
- * `pyric-admin/app-check`: deferred service handle for `firebase-admin/app-check`.
2
+ * `pyric-admin/app-check`: the sandbox mirror of `firebase-admin/app-check`.
3
+ *
4
+ * `createToken` mints the same sandbox token the web mirror's `getToken`
5
+ * returns (shared in `pyric/sandbox/internal`), so a token minted on either
6
+ * plane verifies here. Shapes, TTL limits, error codes, and consume semantics
7
+ * follow the `app-check-admin-` captures; the rows are in
8
+ * packages/conformance/registry/app-check.ts (surface `app-check-admin`).
9
+ *
10
+ * One deliberate difference: the sandbox has no list of the project's
11
+ * registered apps, so `createToken` accepts any app id (row
12
+ * `app-check-admin#create-token-unknown-app`, diverged by design).
13
+ *
14
+ * A token whose `kid` is not the sandbox's came from Firebase. It goes to
15
+ * firebase-admin's own verifier when `@pyric/cli/register` installed one
16
+ * (production AI mode), and is refused with a message that says why otherwise
17
+ * (./firebase-token.ts).
3
18
  */
4
- import type { PyricAdminApp } from '../app/index.js';
5
- import { guardDeferredServiceApp, wireDeferredService } from '../deferred.js';
19
+ import { checkAppCheckToken, mintAppCheckToken, type AppCheckTokenClaims } from 'pyric/sandbox/internal';
20
+ import { getApp, type PyricAdminApp } from '../app/index.js';
21
+ import { explicitProjectId } from '../app/project-id.js';
22
+ import { assertAdminAppActive } from '../app/lifecycle.js';
23
+ import { FirebaseAppCheckError } from './error.js';
24
+ import { verifyFirebaseToken } from './firebase-token.js';
6
25
 
7
- export { PyricDeferredApiError } from '../deferred.js';
26
+ const MINUTE_MILLIS = 60 * 1000;
27
+ const MIN_TTL_MILLIS = 30 * MINUTE_MILLIS;
28
+ const MAX_TTL_MILLIS = 7 * 24 * 60 * MINUTE_MILLIS;
29
+ const DEFAULT_TTL_MILLIS = 60 * MINUTE_MILLIS;
30
+
31
+ const MESSAGES = {
32
+ notString: 'App check token must be a non-null string.',
33
+ options: 'VerifyAppCheckTokenOptions must be a non-null object.',
34
+ algorithm: (alg: unknown) => `The provided App Check token has incorrect algorithm. Expected "RS256" but got "${String(alg)}".`,
35
+ noSub: 'The provided App Check token has no "sub" (subject) claim.',
36
+ emptySub: 'The provided App Check token has an empty string "sub" (subject) claim.',
37
+ ttl: 'ttlMillis must be a duration in milliseconds between 30 minutes and 7 days (inclusive).',
38
+ appId: '`appId` must be a non-empty string.',
39
+ decode: 'Decoding App Check token failed. Make sure you passed the entire string JWT which represents the Firebase App Check token.',
40
+ signature: 'The provided App Check token has invalid signature.',
41
+ expired: 'The provided App Check token has expired. Get a fresh App Check token from your client app and try again.',
42
+ issuer: 'The provided App Check token has incorrect "iss" (issuer) claim.',
43
+ createNoProject:
44
+ 'Failed to determine project ID. Initialize the SDK with service account credentials or set project ID as an app option. Alternatively, set the GOOGLE_CLOUD_PROJECT environment variable.',
45
+ noProject:
46
+ 'Must initialize app with a cert credential or set your Firebase project ID as the GOOGLE_CLOUD_PROJECT environment variable to verify an App Check token.',
47
+ audience: (scoped: string, aud: unknown) =>
48
+ `The provided App Check token has incorrect "aud" (audience) claim. Expected "${scoped}" but got "${String(aud)}". Make sure the App Check token comes from the same Firebase project as the service account used to authenticate this SDK.`,
49
+ };
50
+
51
+ const APP_CHECK_ISSUER = 'https://firebaseappcheck.googleapis.com/';
52
+
53
+ export interface AppCheckTokenOptions {
54
+ ttlMillis?: number;
55
+ }
56
+
57
+ export interface AppCheckToken {
58
+ token: string;
59
+ ttlMillis: number;
60
+ }
61
+
62
+ export interface VerifyAppCheckTokenOptions {
63
+ consume?: boolean;
64
+ }
65
+
66
+ export interface DecodedAppCheckToken extends AppCheckTokenClaims {
67
+ app_id: string;
68
+ }
69
+
70
+ export interface VerifyAppCheckTokenResponse {
71
+ appId: string;
72
+ token: DecodedAppCheckToken;
73
+ alreadyConsumed?: boolean;
74
+ }
75
+
76
+ /**
77
+ * Consumed token ids, per sandbox handle, for this process. The map sits
78
+ * behind Symbol.for, as the app registry in ../app/index.ts does, so the
79
+ * require and import copies of this module that the register hook can load
80
+ * share it. It is in memory: it does not survive a restart, and two processes
81
+ * on one dev server each keep their own. Production consumes once per
82
+ * project; moving this into the sandbox is #1297.
83
+ */
84
+ const CONSUMED_BY_SANDBOX = Symbol.for('pyric.admin.appCheck.consumedBySandbox');
85
+ type GlobalConsumed = { [CONSUMED_BY_SANDBOX]?: WeakMap<object, Set<string>> };
86
+
87
+ function consumedFor(app: PyricAdminApp): Set<string> {
88
+ const global = globalThis as GlobalConsumed;
89
+ const bySandbox = (global[CONSUMED_BY_SANDBOX] ??= new WeakMap());
90
+ const key = app.sandbox as object;
91
+ let consumed = bySandbox.get(key);
92
+ if (!consumed) {
93
+ consumed = new Set();
94
+ bySandbox.set(key, consumed);
95
+ }
96
+ return consumed;
97
+ }
98
+
99
+ function ttlFrom(options: AppCheckTokenOptions | undefined): number {
100
+ const ttl = options?.ttlMillis ?? DEFAULT_TTL_MILLIS;
101
+ const inRange = typeof ttl === 'number' && ttl >= MIN_TTL_MILLIS && ttl <= MAX_TTL_MILLIS;
102
+ if (!inRange) throw new FirebaseAppCheckError('invalid-argument', MESSAGES.ttl);
103
+ return ttl;
104
+ }
8
105
 
9
106
  export class AppCheck {
10
107
  readonly #app: PyricAdminApp;
11
108
 
12
109
  /** @internal */
13
- constructor(...args: unknown[]) {
14
- this.#app = guardDeferredServiceApp('app-check', 'AppCheck', args);
110
+ constructor(app: PyricAdminApp) {
111
+ this.#app = app;
15
112
  }
16
113
 
17
114
  get app(): PyricAdminApp {
18
115
  return this.#app;
19
116
  }
20
117
 
21
- declare createToken: (...args: unknown[]) => Promise<never>;
22
- declare verifyToken: (...args: unknown[]) => Promise<never>;
118
+ async createToken(appId: string, options?: AppCheckTokenOptions): Promise<AppCheckToken> {
119
+ assertAdminAppActive(this.#app);
120
+ if (typeof appId !== 'string' || appId.length === 0) throw new FirebaseAppCheckError('invalid-argument', MESSAGES.appId);
121
+ const ttlMillis = ttlFrom(options);
122
+ const projectId = explicitProjectId(this.#app);
123
+ if (!projectId) throw new FirebaseAppCheckError('unknown-error', MESSAGES.createNoProject);
124
+ const minted = await mintAppCheckToken({ appId, projectId }, Math.floor(ttlMillis / 1000));
125
+ return { token: minted.token, ttlMillis };
126
+ }
127
+
128
+ /**
129
+ * Checks in firebase-admin's order (app-check.js, token-verifier.js,
130
+ * utils/jwt.js): the options and the token's type, synchronously as
131
+ * upstream throws them; then the project, decoding, content, key,
132
+ * signature, and last `nbf` and `exp` as jsonwebtoken reads them.
133
+ */
134
+ verifyToken(appCheckToken: string, options?: VerifyAppCheckTokenOptions): Promise<VerifyAppCheckTokenResponse> {
135
+ assertAdminAppActive(this.#app);
136
+ const optionsAreObject = typeof options === 'object' && options !== null && !Array.isArray(options);
137
+ if (options !== undefined && !optionsAreObject) throw new FirebaseAppCheckError('invalid-argument', MESSAGES.options);
138
+ if (typeof appCheckToken !== 'string') throw new FirebaseAppCheckError('invalid-argument', MESSAGES.notString);
139
+ return this.#verify(appCheckToken, options);
140
+ }
141
+
142
+ async #verify(appCheckToken: string, options: VerifyAppCheckTokenOptions | undefined): Promise<VerifyAppCheckTokenResponse> {
143
+ const projectId = this.#projectId();
144
+ const check = await checkAppCheckToken(appCheckToken);
145
+ if (!check.ok && check.reason === 'malformed') throw new FirebaseAppCheckError('invalid-argument', MESSAGES.decode);
146
+ // A kid that names a key other than the sandbox's: the token came from
147
+ // Firebase, so firebase-admin runs every check, in production's order.
148
+ const isFirebaseToken = !check.ok && check.reason === 'kid';
149
+ if (isFirebaseToken) {
150
+ const verified = await verifyFirebaseToken(appCheckToken, projectId);
151
+ return this.#recordConsume(verified, appCheckToken, options);
152
+ }
153
+ verifyContent(check.header, check.claims, projectId);
154
+ if (!check.ok) throw new FirebaseAppCheckError('invalid-argument', MESSAGES.signature);
155
+ verifyNotBefore(check.claims.nbf);
156
+ verifyExpiry(check.claims.exp);
157
+ const claims = check.claims as AppCheckTokenClaims;
158
+ const result: VerifyAppCheckTokenResponse = { appId: claims.sub, token: { ...claims, app_id: claims.sub } };
159
+ return this.#recordConsume(result, appCheckToken, options);
160
+ }
161
+
162
+ /**
163
+ * `consume: true` marks the token used, in this process, for sandbox and
164
+ * Firebase tokens alike. It is never forwarded to Firebase, which would need
165
+ * a credential with App Check permissions.
166
+ */
167
+ #recordConsume(
168
+ result: VerifyAppCheckTokenResponse,
169
+ appCheckToken: string,
170
+ options: VerifyAppCheckTokenOptions | undefined,
171
+ ): VerifyAppCheckTokenResponse {
172
+ if (!options?.consume) return result;
173
+ const consumed = consumedFor(this.#app);
174
+ const jti = result.token.jti;
175
+ const tokenId = typeof jti === 'string' ? jti : appCheckToken;
176
+ result.alreadyConsumed = consumed.has(tokenId);
177
+ consumed.add(tokenId);
178
+ return result;
179
+ }
180
+
181
+ /** The project a token must be scoped to, resolved as firebase-admin resolves it. */
182
+ #projectId(): string {
183
+ const projectId = explicitProjectId(this.#app);
184
+ if (!projectId) throw new FirebaseAppCheckError('invalid-credential', MESSAGES.noProject);
185
+ return projectId;
186
+ }
23
187
  }
24
188
 
25
- export const getAppCheck: (app?: PyricAdminApp) => AppCheck = wireDeferredService(
26
- AppCheck,
27
- 'app-check',
28
- 'AppCheck',
29
- {
30
- async: ['createToken', 'verifyToken'],
31
- },
32
- );
189
+ /** token-verifier.js verifyContent: algorithm, audience, issuer, then subject, first failure wins. */
190
+ function verifyContent(header: Readonly<Record<string, unknown>>, claims: Readonly<Record<string, unknown>>, projectId: string): void {
191
+ if (header.alg !== 'RS256') throw new FirebaseAppCheckError('invalid-argument', MESSAGES.algorithm(header.alg));
192
+ const scoped = `projects/${projectId}`;
193
+ const audienceMatches = Array.isArray(claims.aud) && claims.aud.length > 0 && claims.aud.includes(scoped);
194
+ if (!audienceMatches) throw new FirebaseAppCheckError('invalid-argument', MESSAGES.audience(scoped, claims.aud));
195
+ const issuerMatches = typeof claims.iss === 'string' && claims.iss.startsWith(APP_CHECK_ISSUER);
196
+ if (!issuerMatches) throw new FirebaseAppCheckError('invalid-argument', MESSAGES.issuer);
197
+ if (typeof claims.sub !== 'string') throw new FirebaseAppCheckError('invalid-argument', MESSAGES.noSub);
198
+ if (claims.sub === '') throw new FirebaseAppCheckError('invalid-argument', MESSAGES.emptySub);
199
+ }
200
+
201
+ /** jsonwebtoken's nbf rule, which runs before exp: firebase-admin reports both failures as an invalid signature. */
202
+ function verifyNotBefore(nbf: unknown): void {
203
+ if (nbf === undefined) return;
204
+ const nowSeconds = Math.floor(Date.now() / 1000);
205
+ const active = typeof nbf === 'number' && nbf <= nowSeconds;
206
+ if (!active) throw new FirebaseAppCheckError('invalid-argument', MESSAGES.signature);
207
+ }
208
+
209
+ /**
210
+ * jsonwebtoken's exp rule: an absent `exp` never expires, a non-number is an
211
+ * error that firebase-admin reports as an invalid signature, and the token is
212
+ * expired once the current second reaches `exp`.
213
+ */
214
+ function verifyExpiry(exp: unknown): void {
215
+ if (exp === undefined) return;
216
+ if (typeof exp !== 'number') throw new FirebaseAppCheckError('invalid-argument', MESSAGES.signature);
217
+ const nowSeconds = Math.floor(Date.now() / 1000);
218
+ if (nowSeconds >= exp) throw new FirebaseAppCheckError('app-check-token-expired', MESSAGES.expired);
219
+ }
220
+
221
+ const instances = new WeakMap<PyricAdminApp, AppCheck>();
222
+
223
+ export function getAppCheck(app?: PyricAdminApp): AppCheck {
224
+ const resolved = app ?? getApp();
225
+ assertAdminAppActive(resolved);
226
+ const existing = instances.get(resolved);
227
+ if (existing) return existing;
228
+ const instance = new AppCheck(resolved);
229
+ instances.set(resolved, instance);
230
+ return instance;
231
+ }