Skip to content

Commit 82eb130

Browse files
committed
added key-store.ts
1 parent bb6996a commit 82eb130

File tree

7 files changed

+72
-48
lines changed

7 files changed

+72
-48
lines changed

src/auth.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Env, useEmulator } from "./emulator";
2+
import { KeyStorer } from "./key-store";
23
import {
34
createIdTokenVerifier,
45
FirebaseIdToken,
@@ -11,13 +12,11 @@ export class BaseAuth {
1112

1213
constructor(
1314
projectId: string,
14-
cacheKey: string,
15-
cfPublicKeyCacheNamespace: KVNamespace
15+
keyStore: KeyStorer,
1616
) {
1717
this.idTokenVerifier = createIdTokenVerifier(
1818
projectId,
19-
cacheKey,
20-
cfPublicKeyCacheNamespace
19+
keyStore
2120
);
2221
}
2322

src/index.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,39 @@
11
import { BaseAuth } from "./auth"
2-
2+
import { WorkersKVStore, KeyStorer } from "./key-store";
33

44
export {
55
emulatorHost,
66
useEmulator
77
} from "./emulator"
8+
export type { KeyStorer }
89
export type { Env } from './emulator'
910

1011
export class Auth extends BaseAuth {
1112
private static instance?: Auth;
1213

13-
private constructor(
14-
projectId: string,
15-
cacheKey: string,
16-
cfPublicKeyCacheNamespace: KVNamespace
17-
) {
18-
super(projectId, cacheKey, cfPublicKeyCacheNamespace)
14+
private constructor(projectId: string, keyStore: KeyStorer) {
15+
super(projectId, keyStore)
1916
}
2017

21-
static getOrInitialize(
22-
projectId: string,
23-
cacheKey: string,
24-
cfPublicKeyCacheNamespace: KVNamespace
25-
): Auth {
18+
static getOrInitialize(projectId: string, keyStore: KeyStorer): Auth {
2619
if (!Auth.instance) {
27-
Auth.instance = new Auth(
28-
projectId,
29-
cacheKey,
30-
cfPublicKeyCacheNamespace,
31-
)
20+
Auth.instance = new Auth(projectId, keyStore)
3221
}
3322
return Auth.instance
3423
}
3524
}
25+
26+
export class WorkersKVStoreSingle extends WorkersKVStore {
27+
private static instance?: WorkersKVStoreSingle;
28+
29+
private constructor(cacheKey: string, cfKVNamespace: KVNamespace) {
30+
super(cacheKey, cfKVNamespace)
31+
}
32+
33+
static getOrInitialize(cacheKey: string, cfKVNamespace: KVNamespace): WorkersKVStoreSingle {
34+
if (!WorkersKVStoreSingle.instance) {
35+
WorkersKVStoreSingle.instance = new WorkersKVStoreSingle(cacheKey, cfKVNamespace)
36+
}
37+
return WorkersKVStoreSingle.instance
38+
}
39+
}

src/jwk-fetcher.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { JsonWebKeyWithKid } from "./jwt-decoder";
2+
import { KeyStorer } from "./key-store";
23
import { isArray, isNonNullObject, isURL } from "./validator";
34

45
export interface KeyFetcher {
@@ -18,20 +19,16 @@ const isJWKMetadata = (value: any): value is JWKMetadata =>
1819
export class UrlKeyFetcher implements KeyFetcher {
1920
constructor(
2021
private readonly fetcher: Fetcher,
21-
private readonly cacheKey: string,
22-
private readonly cfKVNamespace: KVNamespace
23-
) {}
22+
private readonly keyStorer: KeyStorer,
23+
) { }
2424

2525
/**
2626
* Fetches the public keys for the Google certs.
2727
*
2828
* @returns A promise fulfilled with public keys for the Google certs.
2929
*/
3030
public async fetchPublicKeys(): Promise<Array<JsonWebKeyWithKid>> {
31-
const publicKeys = await this.cfKVNamespace.get<Array<JsonWebKeyWithKid>>(
32-
this.cacheKey,
33-
"json"
34-
);
31+
const publicKeys = await this.keyStorer.get<Array<JsonWebKeyWithKid>>();
3532
if (publicKeys === null || typeof publicKeys !== "object") {
3633
return await this.refresh();
3734
}
@@ -58,12 +55,9 @@ export class UrlKeyFetcher implements KeyFetcher {
5855
// store the public keys cache in the KV store.
5956
const maxAge = parseMaxAge(cacheControlHeader)
6057
if (!isNaN(maxAge)) {
61-
this.cfKVNamespace.put(
62-
this.cacheKey,
58+
await this.keyStorer.put(
6359
JSON.stringify(publicKeys.keys),
64-
{
65-
expirationTtl: maxAge,
66-
}
60+
maxAge,
6761
);
6862
}
6963

src/jws-verifier.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { JwtError, JwtErrorCode } from "./errors";
22
import { HTTPFetcher, KeyFetcher, UrlKeyFetcher } from "./jwk-fetcher";
33
import { JsonWebKeyWithKid, RS256Token } from "./jwt-decoder";
4+
import { KeyStorer } from "./key-store";
45
import { isNonNullObject } from "./validator";
56

67
// https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_a_third-party_jwt_library
@@ -28,12 +29,11 @@ export class PublicKeySignatureVerifier implements SignatureVerifier {
2829

2930
public static withCertificateUrl(
3031
clientCertUrl: string,
31-
cacheKey: string,
32-
cfKVNamespace: KVNamespace
32+
keyStorer: KeyStorer,
3333
): PublicKeySignatureVerifier {
3434
const fetcher = new HTTPFetcher(clientCertUrl)
3535
return new PublicKeySignatureVerifier(
36-
new UrlKeyFetcher(fetcher, cacheKey, cfKVNamespace)
36+
new UrlKeyFetcher(fetcher, keyStorer)
3737
);
3838
}
3939

src/key-store.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
export interface KeyStorer {
2+
get<ExpectedValue = unknown>(): Promise<ExpectedValue | null>;
3+
put(value: string, expirationTtl: number): Promise<void>;
4+
}
5+
6+
/**
7+
* Class to get or store fetched public keys from a client certificates URL.
8+
*/
9+
export class WorkersKVStore implements KeyStorer {
10+
constructor(
11+
private readonly cacheKey: string,
12+
private readonly cfKVNamespace: KVNamespace
13+
) {}
14+
15+
public async get<ExpectedValue = unknown>(): Promise<ExpectedValue | null> {
16+
return await this.cfKVNamespace.get<ExpectedValue>(
17+
this.cacheKey,
18+
"json"
19+
);
20+
}
21+
22+
public async put(value: string, expirationTtl: number): Promise<void> {
23+
await this.cfKVNamespace.put(
24+
this.cacheKey,
25+
value,
26+
{
27+
expirationTtl,
28+
}
29+
);
30+
}
31+
}

src/token-verifier.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from "./errors";
88
import { EmulatorSignatureVerifier, PublicKeySignatureVerifier, SignatureVerifier } from "./jws-verifier";
99
import { DecodedPayload, RS256Token } from "./jwt-decoder";
10+
import { KeyStorer } from "./key-store";
1011
import { isNonEmptyString, isNonNullObject, isString, isURL } from "./validator";
1112

1213
// Audience to use for Firebase Auth Custom tokens
@@ -454,13 +455,11 @@ export const ID_TOKEN_INFO: FirebaseTokenInfo = {
454455
*/
455456
export function createIdTokenVerifier(
456457
projectID: string,
457-
cacheKey: string,
458-
cfPublicKeyCacheNamespace: KVNamespace
458+
keyStorer: KeyStorer
459459
): FirebaseTokenVerifier {
460460
const signatureVerifier = PublicKeySignatureVerifier.withCertificateUrl(
461461
CLIENT_JWK_URL,
462-
cacheKey,
463-
cfPublicKeyCacheNamespace
462+
keyStorer
464463
);
465464
return createFirebaseTokenVerifier(signatureVerifier, projectID)
466465
}

tests/jwk-fetcher.test.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Fetcher, parseMaxAge, UrlKeyFetcher } from "../src/jwk-fetcher"
2+
import { WorkersKVStore } from "../src/key-store";
23

34
class HTTPMockFetcher implements Fetcher {
45
constructor(private readonly response: Response) { }
@@ -62,8 +63,7 @@ describe("UrlKeyFetcher", () => {
6263
)
6364
const urlKeyFetcher = new UrlKeyFetcher(
6465
mockedFetcher,
65-
cacheKey,
66-
TEST_NAMESPACE,
66+
new WorkersKVStore(cacheKey, TEST_NAMESPACE),
6767
)
6868

6969
const httpFetcherSpy = jest.spyOn(mockedFetcher, "fetch")
@@ -96,8 +96,7 @@ describe("UrlKeyFetcher", () => {
9696
)
9797
const urlKeyFetcher = new UrlKeyFetcher(
9898
mockedFetcher,
99-
cacheKey,
100-
TEST_NAMESPACE,
99+
new WorkersKVStore(cacheKey, TEST_NAMESPACE),
101100
)
102101

103102
const httpFetcherSpy = jest.spyOn(mockedFetcher, "fetch")
@@ -123,8 +122,7 @@ describe("UrlKeyFetcher", () => {
123122
)
124123
const urlKeyFetcher = new UrlKeyFetcher(
125124
mockedFetcher,
126-
cacheKey,
127-
TEST_NAMESPACE,
125+
new WorkersKVStore(cacheKey, TEST_NAMESPACE),
128126
)
129127

130128
expect(() => urlKeyFetcher.fetchPublicKeys()).rejects.toThrowError(
@@ -141,8 +139,7 @@ describe("UrlKeyFetcher", () => {
141139
)
142140
const urlKeyFetcher = new UrlKeyFetcher(
143141
mockedFetcher,
144-
cacheKey,
145-
TEST_NAMESPACE,
142+
new WorkersKVStore(cacheKey, TEST_NAMESPACE),
146143
)
147144

148145
expect(() => urlKeyFetcher.fetchPublicKeys()).rejects.toThrowError(

0 commit comments

Comments
 (0)