Skip to content

Commit 3fa3042

Browse files
committed
initial commit
0 parents  commit 3fa3042

15 files changed

+3835
-0
lines changed

.gitignore

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
dist
2+
sandbox
3+
4+
# Cloudflare Workers
5+
worker
6+
7+
# Logs
8+
logs
9+
*.log
10+
npm-debug.log*
11+
yarn-debug.log*
12+
yarn-error.log*
13+
lerna-debug.log*
14+
.pnpm-debug.log*
15+
16+
# TypeScript cache
17+
*.tsbuildinfo
18+
19+
# Dependency directories
20+
node_modules/

jest.config.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module.exports = {
2+
testMatch: [
3+
"**/test/**/*.+(ts|tsx|js)",
4+
"**/src/**/(*.)+(spec|test).+(ts|tsx|js)",
5+
],
6+
transform: {
7+
"^.+\\.(ts|tsx)$": "ts-jest",
8+
},
9+
// testPathIgnorePatterns: ["./examples"],
10+
testEnvironment: "miniflare",
11+
testEnvironmentOptions: {
12+
/*
13+
bindings: {
14+
__STATIC_CONTENT: {
15+
get: (key) => {
16+
const table = { 'index.abcdef.index': 'This is index' }
17+
return table[key]
18+
},
19+
},
20+
__STATIC_CONTENT_MANIFEST: JSON.stringify({
21+
'index.index': 'index.abcdef.index',
22+
}),
23+
},
24+
kvNamespaces: ['TEST_NAMESPACE'],
25+
*/
26+
},
27+
};

package.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "firebase-auth-cloudflare-workers",
3+
"version": "1.0.0",
4+
"description": "firebase auth token verifier for Cloudflare Workers.",
5+
"author": "Kei Kamikawa <[email protected]>",
6+
"license": "MIT",
7+
"scripts": {
8+
"test": "jest"
9+
},
10+
"dependencies": {
11+
"@cloudflare/workers-types": "^3.13.0"
12+
},
13+
"devDependencies": {
14+
"@types/jest": "^28.1.3",
15+
"jest": "^28.1.2",
16+
"jest-environment-miniflare": "^2.5.1",
17+
"ts-jest": "^28.0.5"
18+
}
19+
}

src/auth.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {
2+
createIdTokenVerifier,
3+
FirebaseIdToken,
4+
FirebaseTokenVerifier,
5+
} from "./token-verifier";
6+
7+
export class Auth {
8+
/** @internal */
9+
protected readonly idTokenVerifier: FirebaseTokenVerifier;
10+
11+
constructor(projectId: string, cfPublicKeyCacheNamespace: KVNamespace) {
12+
this.idTokenVerifier = createIdTokenVerifier(
13+
projectId,
14+
cfPublicKeyCacheNamespace
15+
);
16+
}
17+
18+
/**
19+
* Verifies a Firebase ID token (JWT). If the token is valid, the promise is
20+
* fulfilled with the token's decoded claims; otherwise, the promise is
21+
* rejected.
22+
*
23+
* See {@link https://firebase.google.com/docs/auth/admin/verify-id-tokens | Verify ID Tokens}
24+
* for code samples and detailed documentation.
25+
*
26+
* @returns A promise fulfilled with the
27+
* token's decoded claims if the ID token is valid; otherwise, a rejected
28+
* promise.
29+
*/
30+
public verifyIdToken(
31+
idToken: string,
32+
isEmulator = false
33+
): Promise<FirebaseIdToken> {
34+
return this.idTokenVerifier.verifyJWT(idToken, isEmulator);
35+
}
36+
}

src/base64.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export const decodeBase64Url = (str: string): string => {
2+
const pad = (s: string): string => {
3+
switch (s.length % 4) {
4+
case 2:
5+
return `${s}==`;
6+
case 3:
7+
return `${s}=`;
8+
default:
9+
return s;
10+
}
11+
};
12+
pad(str).replace(/_|-/g, (m) => ({ _: "/", "-": "+" }[m]!));
13+
return atob(str);
14+
};
15+
16+
export const decodeBase64UrlBytes = (str: string): Uint8Array =>
17+
new TextEncoder().encode(decodeBase64Url(str));
18+
19+
export const encodeBase64UrlBytes = (buf: ArrayBufferLike) => {
20+
return encodeBase64Url(String.fromCharCode(...new Uint8Array(buf)));
21+
};
22+
23+
export const encodeBase64Url = (str: string): string =>
24+
btoa(str).replace(/\/|\+|=/g, (m) => ({ "/": "_", "+": "-", "=": "" }[m]!));

src/emulator.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
declare global {
2+
// Please set FIREBASE_AUTH_EMULATOR_HOST environment variable in your wrangler.toml.
3+
// see: https://developers.cloudflare.com/workers/platform/environment-variables/#environment-variables-via-wrangler
4+
//
5+
// Example for wrangler.toml
6+
// [vars]
7+
// FIREBASE_AUTH_EMULATOR_HOST = "localhost:8080"
8+
//
9+
// # Override values for `--env production` usage
10+
// [env.production.vars]
11+
// FIREBASE_AUTH_EMULATOR_HOST = ""
12+
const FIREBASE_AUTH_EMULATOR_HOST: string | undefined;
13+
}
14+
15+
function emulatorHost(): string | undefined {
16+
return FIREBASE_AUTH_EMULATOR_HOST;
17+
}
18+
19+
/**
20+
* When true the SDK should communicate with the Auth Emulator for all API
21+
* calls and also produce unsigned tokens.
22+
*/
23+
export const useEmulator = (): boolean => {
24+
return !!emulatorHost();
25+
};

src/errors.ts

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/**
2+
* Jwt error code structure.
3+
*
4+
* @param code - The error code.
5+
* @param message - The error message.
6+
* @constructor
7+
*/
8+
export class JwtError extends Error {
9+
constructor(readonly code: JwtErrorCode, readonly message: string) {
10+
super(message);
11+
(this as any).__proto__ = JwtError.prototype;
12+
}
13+
}
14+
15+
/**
16+
* JWT error codes.
17+
*/
18+
export enum JwtErrorCode {
19+
INVALID_ARGUMENT = "invalid-argument",
20+
INVALID_CREDENTIAL = "invalid-credential",
21+
TOKEN_EXPIRED = "token-expired",
22+
INVALID_SIGNATURE = "invalid-token",
23+
NO_MATCHING_KID = "no-matching-kid-error",
24+
NO_KID_IN_HEADER = "no-kid-error",
25+
KEY_FETCH_ERROR = "key-fetch-error",
26+
}
27+
28+
/**
29+
* App client error codes and their default messages.
30+
*/
31+
export class AppErrorCodes {
32+
public static INVALID_CREDENTIAL = "invalid-credential";
33+
}
34+
35+
/**
36+
* Auth client error codes and their default messages.
37+
*/
38+
export class AuthClientErrorCode {
39+
public static INVALID_ARGUMENT = {
40+
code: "argument-error",
41+
message: "Invalid argument provided.",
42+
};
43+
public static INVALID_CREDENTIAL = {
44+
code: "invalid-credential",
45+
message: "Invalid credential object provided.",
46+
};
47+
public static ID_TOKEN_EXPIRED = {
48+
code: "id-token-expired",
49+
message: "The provided Firebase ID token is expired.",
50+
};
51+
public static ID_TOKEN_REVOKED = {
52+
code: "id-token-revoked",
53+
message: "The Firebase ID token has been revoked.",
54+
};
55+
public static INTERNAL_ERROR = {
56+
code: "internal-error",
57+
message: "An internal error has occurred.",
58+
};
59+
public static USER_NOT_FOUND = {
60+
code: "user-not-found",
61+
message:
62+
"There is no user record corresponding to the provided identifier.",
63+
};
64+
public static USER_DISABLED = {
65+
code: "user-disabled",
66+
message: "The user record is disabled.",
67+
};
68+
}
69+
70+
/**
71+
* `FirebaseErrorInterface` is a subclass of the standard JavaScript `Error` object. In
72+
* addition to a message string and stack trace, it contains a string code.
73+
*/
74+
export interface FirebaseErrorInterface {
75+
/**
76+
* Error codes are strings using the following format: `"service/string-code"`.
77+
* Some examples include `"auth/invalid-uid"` and
78+
* `"messaging/invalid-recipient"`.
79+
*
80+
* While the message for a given error can change, the code will remain the same
81+
* between backward-compatible versions of the Firebase SDK.
82+
*/
83+
code: string;
84+
85+
/**
86+
* An explanatory message for the error that just occurred.
87+
*
88+
* This message is designed to be helpful to you, the developer. Because
89+
* it generally does not convey meaningful information to end users,
90+
* this message should not be displayed in your application.
91+
*/
92+
message: string;
93+
94+
/**
95+
* A string value containing the execution backtrace when the error originally
96+
* occurred.
97+
*
98+
* This information can be useful for troubleshooting the cause of the error with
99+
* {@link https://firebase.google.com/support | Firebase Support}.
100+
*/
101+
stack?: string;
102+
103+
/**
104+
* Returns a JSON-serializable object representation of this error.
105+
*
106+
* @returns A JSON-serializable representation of this object.
107+
*/
108+
toJSON(): object;
109+
}
110+
111+
/**
112+
* Firebase error code structure. This extends Error.
113+
*
114+
* @param errorInfo - The error information (code and message).
115+
* @constructor
116+
*/
117+
export class FirebaseError extends Error implements FirebaseErrorInterface {
118+
constructor(private errorInfo: ErrorInfo) {
119+
super(errorInfo.message);
120+
121+
/* tslint:disable:max-line-length */
122+
// Set the prototype explicitly. See the following link for more details:
123+
// https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
124+
/* tslint:enable:max-line-length */
125+
(this as any).__proto__ = FirebaseError.prototype;
126+
}
127+
128+
/** @returns The error code. */
129+
public get code(): string {
130+
return this.errorInfo.code;
131+
}
132+
133+
/** @returns The error message. */
134+
public get message(): string {
135+
return this.errorInfo.message;
136+
}
137+
138+
/** @returns The object representation of the error. */
139+
public toJSON(): object {
140+
return {
141+
code: this.code,
142+
message: this.message,
143+
};
144+
}
145+
}
146+
147+
/**
148+
* Defines error info type. This includes a code and message string.
149+
*/
150+
export interface ErrorInfo {
151+
code: string;
152+
message: string;
153+
}
154+
155+
/**
156+
* A FirebaseError with a prefix in front of the error code.
157+
*
158+
* @param codePrefix - The prefix to apply to the error code.
159+
* @param code - The error code.
160+
* @param message - The error message.
161+
* @constructor
162+
*/
163+
export class PrefixedFirebaseError extends FirebaseError {
164+
constructor(private codePrefix: string, code: string, message: string) {
165+
super({
166+
code: `${codePrefix}/${code}`,
167+
message,
168+
});
169+
170+
/* tslint:disable:max-line-length */
171+
// Set the prototype explicitly. See the following link for more details:
172+
// https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
173+
/* tslint:enable:max-line-length */
174+
(this as any).__proto__ = PrefixedFirebaseError.prototype;
175+
}
176+
177+
/**
178+
* Allows the error type to be checked without needing to know implementation details
179+
* of the code prefixing.
180+
*
181+
* @param code - The non-prefixed error code to test against.
182+
* @returns True if the code matches, false otherwise.
183+
*/
184+
public hasCode(code: string): boolean {
185+
return `${this.codePrefix}/${code}` === this.code;
186+
}
187+
}
188+
189+
/**
190+
* Firebase Auth error code structure. This extends PrefixedFirebaseError.
191+
*
192+
* @param info - The error code info.
193+
* @param [message] The error message. This will override the default
194+
* message if provided.
195+
* @constructor
196+
*/
197+
export class FirebaseAuthError extends PrefixedFirebaseError {
198+
constructor(info: ErrorInfo, message?: string) {
199+
// Override default message if custom message provided.
200+
super("auth", info.code, message || info.message);
201+
202+
/* tslint:disable:max-line-length */
203+
// Set the prototype explicitly. See the following link for more details:
204+
// https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
205+
/* tslint:enable:max-line-length */
206+
(this as any).__proto__ = FirebaseAuthError.prototype;
207+
}
208+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
const TOKEN_EXPIRY_THRESHOLD_MILLIS = 5 * 60 * 1000;

0 commit comments

Comments
 (0)