cloudflare-next-intl 0.7.7 → 0.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/firebase_auth/server/mint_server_app_check_token.d.ts +22 -14
- package/dist/src/firebase_auth/server/mint_server_app_check_token.js +53 -25
- package/dist/src/firebase_auth/server/sign_custom_token_remote.d.ts +18 -0
- package/dist/src/firebase_auth/server/sign_custom_token_remote.js +39 -0
- package/dist/src/types/types.d.ts +31 -8
- package/package.json +1 -1
|
@@ -3,20 +3,28 @@ import type { FirebaseAppCheckConfig } from '../../types/types';
|
|
|
3
3
|
* Mints a fresh App Check token server-side via a service account, for use
|
|
4
4
|
* when the client-written App Check cookie (see `appCheckTokenCookieName`)
|
|
5
5
|
* is absent — e.g. a cold navigation before `AuthUserProvider` has run and
|
|
6
|
-
* had a chance to write it. Requires `clientEmail`/`
|
|
7
|
-
* `firebaseAuth.appCheck
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* had a chance to write it. Requires `clientEmail`/`appId` on
|
|
7
|
+
* `firebaseAuth.appCheck`, plus either `privateKey` or the
|
|
8
|
+
* `oauthClientId`/`oauthClientSecret`/`oauthRefreshToken` triple; returns
|
|
9
|
+
* `undefined` (never throws) if the exchange fails, so a caller can always
|
|
10
|
+
* fall back to "no App Check token" exactly as before this existed.
|
|
10
11
|
*
|
|
11
|
-
* Signs a short-lived custom JWT
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
12
|
+
* Signs a short-lived custom JWT, then exchanges it for an App Check token
|
|
13
|
+
* via `exchangeCustomToken`, authenticated with the project's Web API key
|
|
14
|
+
* (`?key=`) — `exchangeCustomToken` otherwise rejects the call outright as
|
|
15
|
+
* an unregistered/unidentified caller (403 `PERMISSION_DENIED`), before the
|
|
16
|
+
* custom token itself is even evaluated. Not cached beyond the caller's own
|
|
17
|
+
* request-scoped `cache()` wrapper — a fresh mint costs one signing
|
|
18
|
+
* operation plus one network round-trip, acceptable per-request but not
|
|
19
|
+
* worth doing more than once per request.
|
|
20
|
+
*
|
|
21
|
+
* The custom token is signed one of two ways, `privateKey` taking priority
|
|
22
|
+
* when both are set:
|
|
23
|
+
* - `privateKey` set: signed locally (`jose`, Edge/WebCrypto-compatible —
|
|
24
|
+
* no `firebase-admin`).
|
|
25
|
+
* - OAuth triple set instead: signed remotely via
|
|
26
|
+
* `sign_custom_token_remote.ts` (IAM Credentials `signJwt`) — the way to
|
|
27
|
+
* mint tokens when a GCP org policy blocks creating the service-account
|
|
28
|
+
* key `privateKey` would otherwise require.
|
|
21
29
|
*/
|
|
22
30
|
export default function mintServerAppCheckToken(projectId: string, apiKey: string, appCheck: FirebaseAppCheckConfig | undefined): Promise<string | undefined>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import config from '@intl-config';
|
|
2
2
|
import reportError from '../../error_handling/report_error';
|
|
3
|
+
import signCustomTokenRemote from './sign_custom_token_remote';
|
|
3
4
|
// Matches `firebase-admin`'s own `AppCheckTokenGenerator.createCustomToken`
|
|
4
5
|
// exactly (`token-generator.js`) — this specific audience (the App Check
|
|
5
6
|
// TOKEN EXCHANGE service, not the App Check API resource name itself) is
|
|
@@ -21,36 +22,63 @@ const CUSTOM_TOKEN_LIFETIME = '5m';
|
|
|
21
22
|
* Mints a fresh App Check token server-side via a service account, for use
|
|
22
23
|
* when the client-written App Check cookie (see `appCheckTokenCookieName`)
|
|
23
24
|
* is absent — e.g. a cold navigation before `AuthUserProvider` has run and
|
|
24
|
-
* had a chance to write it. Requires `clientEmail`/`
|
|
25
|
-
* `firebaseAuth.appCheck
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* had a chance to write it. Requires `clientEmail`/`appId` on
|
|
26
|
+
* `firebaseAuth.appCheck`, plus either `privateKey` or the
|
|
27
|
+
* `oauthClientId`/`oauthClientSecret`/`oauthRefreshToken` triple; returns
|
|
28
|
+
* `undefined` (never throws) if the exchange fails, so a caller can always
|
|
29
|
+
* fall back to "no App Check token" exactly as before this existed.
|
|
28
30
|
*
|
|
29
|
-
* Signs a short-lived custom JWT
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
31
|
+
* Signs a short-lived custom JWT, then exchanges it for an App Check token
|
|
32
|
+
* via `exchangeCustomToken`, authenticated with the project's Web API key
|
|
33
|
+
* (`?key=`) — `exchangeCustomToken` otherwise rejects the call outright as
|
|
34
|
+
* an unregistered/unidentified caller (403 `PERMISSION_DENIED`), before the
|
|
35
|
+
* custom token itself is even evaluated. Not cached beyond the caller's own
|
|
36
|
+
* request-scoped `cache()` wrapper — a fresh mint costs one signing
|
|
37
|
+
* operation plus one network round-trip, acceptable per-request but not
|
|
38
|
+
* worth doing more than once per request.
|
|
39
|
+
*
|
|
40
|
+
* The custom token is signed one of two ways, `privateKey` taking priority
|
|
41
|
+
* when both are set:
|
|
42
|
+
* - `privateKey` set: signed locally (`jose`, Edge/WebCrypto-compatible —
|
|
43
|
+
* no `firebase-admin`).
|
|
44
|
+
* - OAuth triple set instead: signed remotely via
|
|
45
|
+
* `sign_custom_token_remote.ts` (IAM Credentials `signJwt`) — the way to
|
|
46
|
+
* mint tokens when a GCP org policy blocks creating the service-account
|
|
47
|
+
* key `privateKey` would otherwise require.
|
|
39
48
|
*/
|
|
40
49
|
export default async function mintServerAppCheckToken(projectId, apiKey, appCheck) {
|
|
41
|
-
if (!appCheck?.clientEmail || !appCheck.
|
|
50
|
+
if (!appCheck?.clientEmail || !appCheck.appId)
|
|
51
|
+
return undefined;
|
|
52
|
+
const hasOauthTriple = appCheck.oauthClientId && appCheck.oauthClientSecret && appCheck.oauthRefreshToken;
|
|
53
|
+
if (!appCheck.privateKey && !hasOauthTriple)
|
|
42
54
|
return undefined;
|
|
43
55
|
try {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
.
|
|
49
|
-
.
|
|
50
|
-
.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
const claims = {
|
|
57
|
+
iss: appCheck.clientEmail,
|
|
58
|
+
sub: appCheck.clientEmail,
|
|
59
|
+
aud: APP_CHECK_CUSTOM_TOKEN_AUDIENCE,
|
|
60
|
+
iat: Math.floor(Date.now() / 1000),
|
|
61
|
+
exp: Math.floor(Date.now() / 1000) + 300,
|
|
62
|
+
app_id: appCheck.appId,
|
|
63
|
+
};
|
|
64
|
+
const customToken = appCheck.privateKey
|
|
65
|
+
? await (async () => {
|
|
66
|
+
const { SignJWT, importPKCS8 } = await import('jose');
|
|
67
|
+
const privateKey = await importPKCS8(appCheck.privateKey.replace(/\\n/g, '\n'), 'RS256');
|
|
68
|
+
return new SignJWT({ app_id: appCheck.appId })
|
|
69
|
+
.setProtectedHeader({ alg: 'RS256', typ: 'JWT' })
|
|
70
|
+
.setIssuer(appCheck.clientEmail)
|
|
71
|
+
.setSubject(appCheck.clientEmail)
|
|
72
|
+
.setAudience(APP_CHECK_CUSTOM_TOKEN_AUDIENCE)
|
|
73
|
+
.setIssuedAt()
|
|
74
|
+
.setExpirationTime(CUSTOM_TOKEN_LIFETIME)
|
|
75
|
+
.sign(privateKey);
|
|
76
|
+
})()
|
|
77
|
+
: await signCustomTokenRemote(appCheck.clientEmail, claims, {
|
|
78
|
+
clientId: appCheck.oauthClientId,
|
|
79
|
+
clientSecret: appCheck.oauthClientSecret,
|
|
80
|
+
refreshToken: appCheck.oauthRefreshToken,
|
|
81
|
+
});
|
|
54
82
|
const url = `https://firebaseappcheck.googleapis.com/v1/projects/${projectId}/apps/${appCheck.appId}:exchangeCustomToken?key=${apiKey}`;
|
|
55
83
|
const res = await fetch(url, {
|
|
56
84
|
method: 'POST',
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signs the App Check custom token claims remotely via IAM Credentials'
|
|
3
|
+
* `signJwt`, instead of locally with a service-account private key. Lets
|
|
4
|
+
* `mintServerAppCheckToken` work under an org policy that enforces
|
|
5
|
+
* `iam.disableServiceAccountKeyCreation` — that constraint blocks
|
|
6
|
+
* `serviceAccounts.keys.create` only; it does not affect `signJwt`, which
|
|
7
|
+
* signs using a key Google holds and never exports.
|
|
8
|
+
*
|
|
9
|
+
* The caller's OAuth identity (the refresh token) must carry
|
|
10
|
+
* `roles/iam.serviceAccountTokenCreator` on `clientEmail` — grant it with:
|
|
11
|
+
* `gcloud iam service-accounts add-iam-policy-binding <clientEmail>
|
|
12
|
+
* --member="user:<you>" --role="roles/iam.serviceAccountTokenCreator"`.
|
|
13
|
+
*/
|
|
14
|
+
export default function signCustomTokenRemote(clientEmail: string, claims: Record<string, unknown>, oauth: {
|
|
15
|
+
clientId: string;
|
|
16
|
+
clientSecret: string;
|
|
17
|
+
refreshToken: string;
|
|
18
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signs the App Check custom token claims remotely via IAM Credentials'
|
|
3
|
+
* `signJwt`, instead of locally with a service-account private key. Lets
|
|
4
|
+
* `mintServerAppCheckToken` work under an org policy that enforces
|
|
5
|
+
* `iam.disableServiceAccountKeyCreation` — that constraint blocks
|
|
6
|
+
* `serviceAccounts.keys.create` only; it does not affect `signJwt`, which
|
|
7
|
+
* signs using a key Google holds and never exports.
|
|
8
|
+
*
|
|
9
|
+
* The caller's OAuth identity (the refresh token) must carry
|
|
10
|
+
* `roles/iam.serviceAccountTokenCreator` on `clientEmail` — grant it with:
|
|
11
|
+
* `gcloud iam service-accounts add-iam-policy-binding <clientEmail>
|
|
12
|
+
* --member="user:<you>" --role="roles/iam.serviceAccountTokenCreator"`.
|
|
13
|
+
*/
|
|
14
|
+
export default async function signCustomTokenRemote(clientEmail, claims, oauth) {
|
|
15
|
+
const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
18
|
+
body: new URLSearchParams({
|
|
19
|
+
client_id: oauth.clientId,
|
|
20
|
+
client_secret: oauth.clientSecret,
|
|
21
|
+
refresh_token: oauth.refreshToken,
|
|
22
|
+
grant_type: 'refresh_token',
|
|
23
|
+
}),
|
|
24
|
+
});
|
|
25
|
+
if (!tokenRes.ok) {
|
|
26
|
+
throw new Error(`oauth2 refresh_token exchange failed: ${tokenRes.status} ${await tokenRes.text()}`);
|
|
27
|
+
}
|
|
28
|
+
const { access_token: accessToken } = await tokenRes.json();
|
|
29
|
+
const signRes = await fetch(`https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${clientEmail}:signJwt`, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
|
|
32
|
+
body: JSON.stringify({ payload: JSON.stringify(claims) }),
|
|
33
|
+
});
|
|
34
|
+
if (!signRes.ok) {
|
|
35
|
+
throw new Error(`iamcredentials.signJwt failed: ${signRes.status} ${await signRes.text()}`);
|
|
36
|
+
}
|
|
37
|
+
const { signedJwt } = await signRes.json();
|
|
38
|
+
return signedJwt;
|
|
39
|
+
}
|
|
@@ -621,9 +621,10 @@ export interface FirebaseAppCheckConfig {
|
|
|
621
621
|
* Service account client email, used ONLY server-side to mint an App
|
|
622
622
|
* Check token when the client-written App Check cookie is absent (e.g.
|
|
623
623
|
* a cold navigation before `AuthUserProvider` has run — see
|
|
624
|
-
* `appCheckTokenCookieName`). Required alongside `
|
|
625
|
-
* `
|
|
626
|
-
*
|
|
624
|
+
* `appCheckTokenCookieName`). Required alongside `appId`, plus either
|
|
625
|
+
* `privateKey` or the `oauthClientId`/`oauthClientSecret`/
|
|
626
|
+
* `oauthRefreshToken` triple, for server-side minting. Never sent to the
|
|
627
|
+
* client — read only by `firebase_server.ts`.
|
|
627
628
|
*/
|
|
628
629
|
clientEmail: string;
|
|
629
630
|
/**
|
|
@@ -633,13 +634,35 @@ export interface FirebaseAppCheckConfig {
|
|
|
633
634
|
* (e.g. `process.env.FIREBASE_PRIVATE_KEY`), never exposed to the
|
|
634
635
|
* browser. Escaped `\n` sequences (common when stored in a single-line
|
|
635
636
|
* env var) are unescaped automatically before use.
|
|
636
|
-
|
|
637
|
-
|
|
637
|
+
*
|
|
638
|
+
* Omit this and set the `oauthClientId`/`oauthClientSecret`/
|
|
639
|
+
* `oauthRefreshToken` triple instead when your GCP org enforces
|
|
640
|
+
* `iam.disableServiceAccountKeyCreation`, which blocks issuing this key
|
|
641
|
+
* in the first place. When both are set, `privateKey` takes priority.
|
|
642
|
+
*/
|
|
643
|
+
privateKey?: string;
|
|
644
|
+
/**
|
|
645
|
+
* Application Default Credentials OAuth client ID — the `client_id`
|
|
646
|
+
* field from `application_default_credentials.json` (see
|
|
647
|
+
* `gcloud auth application-default login`). Paired with
|
|
648
|
+
* `oauthClientSecret` and `oauthRefreshToken` as an alternative to
|
|
649
|
+
* `privateKey`: instead of signing the App Check custom token locally,
|
|
650
|
+
* it's signed remotely via IAM Credentials `signJwt`, authenticated as
|
|
651
|
+
* this OAuth identity. That identity needs
|
|
652
|
+
* `roles/iam.serviceAccountTokenCreator` on `clientEmail`. Use this when
|
|
653
|
+
* a service-account key can't be created (see `privateKey`). Ignored
|
|
654
|
+
* when `privateKey` is set.
|
|
655
|
+
*/
|
|
656
|
+
oauthClientId?: string;
|
|
657
|
+
/** OAuth client secret paired with `oauthClientId`. Same ADC-JSON `client_secret` field, same secret-handling rules as `privateKey`. */
|
|
658
|
+
oauthClientSecret?: string;
|
|
659
|
+
/** OAuth refresh token paired with `oauthClientId`. Same ADC-JSON `refresh_token` field, same secret-handling rules as `privateKey`. */
|
|
660
|
+
oauthRefreshToken?: string;
|
|
638
661
|
/**
|
|
639
662
|
* Firebase App Check app ID (e.g. `"1:1234567890:web:abcdef123456"`),
|
|
640
|
-
* required alongside `clientEmail
|
|
641
|
-
*
|
|
642
|
-
*
|
|
663
|
+
* required alongside `clientEmail` for server-side minting. Distinct
|
|
664
|
+
* from the Firebase Auth `appId` on `FirebaseAuthRoutingConfig` itself —
|
|
665
|
+
* App Check registers apps separately.
|
|
643
666
|
*/
|
|
644
667
|
appId: string;
|
|
645
668
|
}
|