setup Firebase Auth middleware

pull/34/head
Yusuke Wada 2023-02-04 20:20:42 +09:00
parent 6fd67b0a1a
commit e0f68909ea
12 changed files with 3421 additions and 7010 deletions

View File

@ -38,6 +38,7 @@ module.exports = defineConfig({
{ {
types: { types: {
Function: false, Function: false,
'{}': false,
}, },
}, },
], ],

View File

@ -1,13 +1,20 @@
name: ci name: ci-firebase-auth
on: on:
push: push:
branches: [main] branches: [main]
paths:
- 'packages/firebase-auth/**'
pull_request: pull_request:
branches: ['*'] branches: ['*']
paths:
- 'packages/firebase-auth/**'
jobs: jobs:
ci: ci:
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults:
run:
working-directory: ./packages/firebase-auth
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v2 - uses: actions/setup-node@v2

View File

@ -11,6 +11,7 @@
"build:qwik-city": "yarn workspace @hono/qwik-city build", "build:qwik-city": "yarn workspace @hono/qwik-city build",
"build:graphql-server": "yarn workspace @hono/graphql-server build", "build:graphql-server": "yarn workspace @hono/graphql-server build",
"build:sentry": "yarn workspace @hono/sentry build", "build:sentry": "yarn workspace @hono/sentry build",
"build:firebase-auth": "yarn workspace @hono/firebase-auth build",
"build": "run-p build:*" "build": "run-p build:*"
}, },
"license": "MIT", "license": "MIT",

View File

@ -1,11 +0,0 @@
name: 'good first issue'
on: [issues]
jobs:
labels:
runs-on: ubuntu-latest
steps:
- uses: Code-Hex/first-label-interaction@v1.0.2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
issue-labels: '["good first issue"]'

View File

@ -1,9 +0,0 @@
dist
node_modules
.yarn/*
# for debug or playing
sandbox
*.log

View File

@ -1,13 +1 @@
module.exports = { module.exports = require('../../jest.config.js')
testMatch: [
"**/test/**/*.+(ts|tsx|js)",
"**/src/**/(*.)+(spec|test).+(ts|tsx|js)",
],
transform: {
"^.+\\.(ts|tsx)$": "ts-jest",
},
testEnvironment: "miniflare",
testEnvironmentOptions: {
kvNamespaces: ["PUBLIC_JWK_CACHE_KV"],
},
};

View File

@ -1,5 +1,5 @@
{ {
"name": "@honojs/firebase-auth", "name": "@hono/firebase-auth",
"version": "1.0.2", "version": "1.0.2",
"description": "A third-party firebase auth middleware for Hono", "description": "A third-party firebase auth middleware for Hono",
"main": "dist/index.js", "main": "dist/index.js",
@ -18,9 +18,9 @@
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/honojs/firebase-auth.git" "url": "https://github.com/honojs/middleware.git"
}, },
"homepage": "https://github.com/honojs/firebase-auth", "homepage": "https://github.com/honojs/middleware",
"author": "codehex", "author": "codehex",
"private": false, "private": false,
"publishConfig": { "publishConfig": {
@ -28,10 +28,13 @@
"access": "public" "access": "public"
}, },
"dependencies": { "dependencies": {
"firebase-auth-cloudflare-workers": "^1.0.0", "firebase-auth-cloudflare-workers": "^1.0.0"
"hono": "^2.1.3" },
"peerDependencies": {
"hono": "^2.7.2"
}, },
"devDependencies": { "devDependencies": {
"hono": "^2.7.2",
"@cloudflare/workers-types": "^3.14.1", "@cloudflare/workers-types": "^3.14.1",
"@types/jest": "^28.1.4", "@types/jest": "^28.1.4",
"firebase-tools": "^11.4.0", "firebase-tools": "^11.4.0",
@ -41,4 +44,4 @@
"ts-jest": "^28.0.5", "ts-jest": "^28.0.5",
"typescript": "^4.7.4" "typescript": "^4.7.4"
} }
} }

View File

@ -1,89 +1,85 @@
import { Context, Handler } from "hono"; import type { EmulatorEnv, KeyStorer, FirebaseIdToken } from 'firebase-auth-cloudflare-workers'
import { import { Auth, WorkersKVStoreSingle } from 'firebase-auth-cloudflare-workers'
EmulatorEnv, import type { Context, MiddlewareHandler } from 'hono'
Auth,
WorkersKVStoreSingle,
KeyStorer,
FirebaseIdToken,
} from "firebase-auth-cloudflare-workers";
export interface VerifyFirebaseAuthEnv extends EmulatorEnv { export interface VerifyFirebaseAuthEnv extends EmulatorEnv {
PUBLIC_JWK_CACHE_KEY?: string | undefined; PUBLIC_JWK_CACHE_KEY?: string | undefined
PUBLIC_JWK_CACHE_KV?: KVNamespace | undefined; PUBLIC_JWK_CACHE_KV?: KVNamespace | undefined
} }
export interface VerifyFirebaseAuthConfig { export interface VerifyFirebaseAuthConfig {
projectId: string; projectId: string
authorizationHeaderKey?: string; authorizationHeaderKey?: string
keyStore?: KeyStorer; keyStore?: KeyStorer
keyStoreInitializer?: (c: Context) => KeyStorer; keyStoreInitializer?: (c: Context) => KeyStorer
disableErrorLog?: boolean; disableErrorLog?: boolean
firebaseEmulatorHost?: string; firebaseEmulatorHost?: string
} }
const defaultKVStoreJWKCacheKey = "verify-firebase-auth-cached-public-key"; const defaultKVStoreJWKCacheKey = 'verify-firebase-auth-cached-public-key'
const defaultKeyStoreInitializer = (c: Context): KeyStorer => { const defaultKeyStoreInitializer = (c: Context): KeyStorer => {
return WorkersKVStoreSingle.getOrInitialize( return WorkersKVStoreSingle.getOrInitialize(
c.env.PUBLIC_JWK_CACHE_KEY ?? defaultKVStoreJWKCacheKey, c.env.PUBLIC_JWK_CACHE_KEY ?? defaultKVStoreJWKCacheKey,
c.env.PUBLIC_JWK_CACHE_KV c.env.PUBLIC_JWK_CACHE_KV
); )
}; }
export const verifyFirebaseAuth = ( type Env = {
userConfig: VerifyFirebaseAuthConfig Bindings: {
): Handler => { FIREBASE_AUTH_EMULATOR_HOST: string
}
}
export const verifyFirebaseAuth = (userConfig: VerifyFirebaseAuthConfig): MiddlewareHandler => {
const config = { const config = {
projectId: userConfig.projectId, projectId: userConfig.projectId,
AuthorizationHeaderKey: AuthorizationHeaderKey: userConfig.authorizationHeaderKey ?? 'Authorization',
userConfig.authorizationHeaderKey ?? "Authorization",
KeyStore: userConfig.keyStore, KeyStore: userConfig.keyStore,
keyStoreInitializer: keyStoreInitializer: userConfig.keyStoreInitializer ?? defaultKeyStoreInitializer,
userConfig.keyStoreInitializer ?? defaultKeyStoreInitializer,
disableErrorLog: userConfig.disableErrorLog, disableErrorLog: userConfig.disableErrorLog,
firebaseEmulatorHost: userConfig.firebaseEmulatorHost, firebaseEmulatorHost: userConfig.firebaseEmulatorHost,
}; }
return async (c, next) => { return async (c, next) => {
const authorization = c.req.headers.get(config.AuthorizationHeaderKey); const authorization = c.req.headers.get(config.AuthorizationHeaderKey)
if (authorization === null) { if (authorization === null) {
return new Response(null, { return new Response(null, {
status: 400, status: 400,
}); })
} }
const jwt = authorization.replace(/Bearer\s+/i, ""); const jwt = authorization.replace(/Bearer\s+/i, '')
const auth = Auth.getOrInitialize( const auth = Auth.getOrInitialize(
config.projectId, config.projectId,
config.KeyStore ?? config.keyStoreInitializer(c) config.KeyStore ?? config.keyStoreInitializer(c)
); )
try { try {
const idToken = await auth.verifyIdToken(jwt, { const idToken = await auth.verifyIdToken(jwt, {
FIREBASE_AUTH_EMULATOR_HOST: FIREBASE_AUTH_EMULATOR_HOST:
config.firebaseEmulatorHost ?? c.env.FIREBASE_AUTH_EMULATOR_HOST, config.firebaseEmulatorHost ?? c.env.FIREBASE_AUTH_EMULATOR_HOST,
}); })
setFirebaseToken(c, idToken); setFirebaseToken(c, idToken)
} catch (err) { } catch (err) {
if (!userConfig.disableErrorLog) { if (!userConfig.disableErrorLog) {
console.error({ console.error({
message: "failed to verify the requested firebase token", message: 'failed to verify the requested firebase token',
err, err,
}); })
} }
return new Response(null, { return new Response(null, {
status: 401, status: 401,
}); })
} }
await next(); await next()
}; }
}; }
const idTokenContextKey = "firebase-auth-cloudflare-id-token-key"; const idTokenContextKey = 'firebase-auth-cloudflare-id-token-key'
const setFirebaseToken = (c: Context, idToken: FirebaseIdToken) => const setFirebaseToken = (c: Context, idToken: FirebaseIdToken) => c.set(idTokenContextKey, idToken)
c.set(idTokenContextKey, idToken);
export const getFirebaseToken = (c: Context): FirebaseIdToken | null => { export const getFirebaseToken = (c: Context): FirebaseIdToken | null => {
const idToken = c.get(idTokenContextKey); const idToken = c.get(idTokenContextKey)
if (!idToken) return null; if (!idToken) return null
return idToken; return idToken
}; }

View File

@ -1,84 +1,76 @@
import { import type { KeyStorer } from 'firebase-auth-cloudflare-workers'
Auth, import { Auth, WorkersKVStoreSingle } from 'firebase-auth-cloudflare-workers'
KeyStorer, import { Hono } from 'hono'
WorkersKVStoreSingle, import type { VerifyFirebaseAuthEnv } from '../src'
} from "firebase-auth-cloudflare-workers"; import { verifyFirebaseAuth, getFirebaseToken } from '../src'
import { Hono } from "hono";
import {
VerifyFirebaseAuthEnv,
verifyFirebaseAuth,
getFirebaseToken,
} from "../src";
describe("verifyFirebaseAuth middleware", () => { describe('verifyFirebaseAuth middleware', () => {
const emulatorHost = "127.0.0.1:9099"; const emulatorHost = '127.0.0.1:9099'
const validProjectId = "example-project12345"; // see package.json const validProjectId = 'example-project12345' // see package.json
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
const { PUBLIC_JWK_CACHE_KV } = getMiniflareBindings(); const { PUBLIC_JWK_CACHE_KV } = getMiniflareBindings()
let user: signUpResponse; let user: signUpResponse
beforeAll(async () => { beforeAll(async () => {
await deleteAccountEmulator(emulatorHost, validProjectId); await deleteAccountEmulator(emulatorHost, validProjectId)
user = await signUpEmulator(emulatorHost, { user = await signUpEmulator(emulatorHost, {
email: "codehex@hono.js", email: 'codehex@hono.js',
password: "honojs", password: 'honojs',
}); })
await sleep(1000); // wait for iat await sleep(1000) // wait for iat
}); })
describe("service worker syntax", () => { describe('service worker syntax', () => {
test("valid case, should be 200", async () => { test('valid case, should be 200', async () => {
const app = new Hono(); const app = new Hono()
resetAuth(); resetAuth()
// This is assumed to be obtained from an environment variable. // This is assumed to be obtained from an environment variable.
const PUBLIC_JWK_CACHE_KEY = "testing-cache-key"; const PUBLIC_JWK_CACHE_KEY = 'testing-cache-key'
app.use( app.use(
"*", '*',
verifyFirebaseAuth({ verifyFirebaseAuth({
projectId: validProjectId, projectId: validProjectId,
keyStore: WorkersKVStoreSingle.getOrInitialize( keyStore: WorkersKVStoreSingle.getOrInitialize(PUBLIC_JWK_CACHE_KEY, PUBLIC_JWK_CACHE_KV),
PUBLIC_JWK_CACHE_KEY,
PUBLIC_JWK_CACHE_KV
),
disableErrorLog: true, disableErrorLog: true,
firebaseEmulatorHost: emulatorHost, firebaseEmulatorHost: emulatorHost,
}) })
); )
app.get("/hello", (c) => c.json(getFirebaseToken(c))); app.get('/hello', (c) => c.json(getFirebaseToken(c)))
const req = new Request("http://localhost/hello", { const req = new Request('http://localhost/hello', {
headers: { headers: {
Authorization: `Bearer ${user.idToken}`, Authorization: `Bearer ${user.idToken}`,
}, },
}); })
const res = await app.request(req); const res = await app.request(req)
expect(res).not.toBeNull(); expect(res).not.toBeNull()
expect(res.status).toBe(200); expect(res.status).toBe(200)
const json = await res.json<{ aud: string; email: string }>(); const json = await res.json<{ aud: string; email: string }>()
expect(json.aud).toBe(validProjectId); expect(json.aud).toBe(validProjectId)
expect(json.email).toBe("codehex@hono.js"); expect(json.email).toBe('codehex@hono.js')
}); })
}); })
describe("module worker syntax", () => { describe('module worker syntax', () => {
test.each([ test.each([
[ [
"valid case, should be 200", 'valid case, should be 200',
{ {
headerKey: "Authorization", headerKey: 'Authorization',
env: { env: {
FIREBASE_AUTH_EMULATOR_HOST: "localhost:9099", FIREBASE_AUTH_EMULATOR_HOST: 'localhost:9099',
PUBLIC_JWK_CACHE_KEY: "testing-cache-key", PUBLIC_JWK_CACHE_KEY: 'testing-cache-key',
PUBLIC_JWK_CACHE_KV, PUBLIC_JWK_CACHE_KV,
}, },
config: { config: {
@ -88,28 +80,28 @@ describe("verifyFirebaseAuth middleware", () => {
}, },
], ],
[ [
"valid specified headerKey, should be 200", 'valid specified headerKey, should be 200',
{ {
headerKey: "X-Authorization", headerKey: 'X-Authorization',
env: { env: {
FIREBASE_AUTH_EMULATOR_HOST: "localhost:9099", FIREBASE_AUTH_EMULATOR_HOST: 'localhost:9099',
PUBLIC_JWK_CACHE_KEY: "testing-cache-key", PUBLIC_JWK_CACHE_KEY: 'testing-cache-key',
PUBLIC_JWK_CACHE_KV, PUBLIC_JWK_CACHE_KV,
}, },
config: { config: {
projectId: validProjectId, projectId: validProjectId,
authorizationHeaderKey: "X-Authorization", authorizationHeaderKey: 'X-Authorization',
}, },
wantStatus: 200, wantStatus: 200,
}, },
], ],
[ [
"invalid authorization header, should be 400", 'invalid authorization header, should be 400',
{ {
headerKey: "X-Authorization", headerKey: 'X-Authorization',
env: { env: {
FIREBASE_AUTH_EMULATOR_HOST: "localhost:9099", FIREBASE_AUTH_EMULATOR_HOST: 'localhost:9099',
PUBLIC_JWK_CACHE_KEY: "testing-cache-key", PUBLIC_JWK_CACHE_KEY: 'testing-cache-key',
PUBLIC_JWK_CACHE_KV, PUBLIC_JWK_CACHE_KV,
}, },
config: { config: {
@ -120,176 +112,177 @@ describe("verifyFirebaseAuth middleware", () => {
}, },
], ],
[ [
"invalid project ID, should be 401", 'invalid project ID, should be 401',
{ {
headerKey: "Authorization", headerKey: 'Authorization',
env: { env: {
FIREBASE_AUTH_EMULATOR_HOST: "localhost:9099", FIREBASE_AUTH_EMULATOR_HOST: 'localhost:9099',
PUBLIC_JWK_CACHE_KEY: "testing-cache-key", PUBLIC_JWK_CACHE_KEY: 'testing-cache-key',
PUBLIC_JWK_CACHE_KV, PUBLIC_JWK_CACHE_KV,
}, },
config: { config: {
projectId: "invalid-projectId", projectId: 'invalid-projectId',
}, },
wantStatus: 401, wantStatus: 401,
}, },
], ],
])("%s", async (_, { headerKey, env, config, wantStatus }) => { ])('%s', async (_, { headerKey, env, config, wantStatus }) => {
const app = new Hono<{ Bindings: VerifyFirebaseAuthEnv }>(); const app = new Hono<{ Bindings: VerifyFirebaseAuthEnv }>()
resetAuth(); resetAuth()
app.use( app.use(
"*", '*',
verifyFirebaseAuth({ verifyFirebaseAuth({
...config, ...config,
disableErrorLog: true, disableErrorLog: true,
}) })
); )
app.get("/hello", (c) => c.text("OK")); app.get('/hello', (c) => c.text('OK'))
const req = new Request("http://localhost/hello", { const req = new Request('http://localhost/hello', {
headers: { headers: {
[headerKey]: `Bearer ${user.idToken}`, [headerKey]: `Bearer ${user.idToken}`,
}, },
}); })
const res = await app.fetch(req, env); const res = await app.fetch(req, env)
expect(res).not.toBeNull(); expect(res).not.toBeNull()
expect(res.status).toBe(wantStatus); expect(res.status).toBe(wantStatus)
}); })
test("specified keyStore is used", async () => { test('specified keyStore is used', async () => {
const testingJWT = generateDummyJWT(); const testingJWT = generateDummyJWT()
const nopKeyStore = new NopKeyStore(); const nopKeyStore = new NopKeyStore()
const getSpy = jest.spyOn(nopKeyStore, "get"); const getSpy = jest.spyOn(nopKeyStore, 'get')
const putSpy = jest.spyOn(nopKeyStore, "put"); const putSpy = jest.spyOn(nopKeyStore, 'put')
const app = new Hono<{ Bindings: VerifyFirebaseAuthEnv }>(); const app = new Hono<{ Bindings: VerifyFirebaseAuthEnv }>()
resetAuth(); resetAuth()
app.use( app.use(
"*", '*',
verifyFirebaseAuth({ verifyFirebaseAuth({
projectId: validProjectId, projectId: validProjectId,
keyStore: nopKeyStore, keyStore: nopKeyStore,
disableErrorLog: true, disableErrorLog: true,
}) })
); )
app.get("/hello", (c) => c.text("OK")); app.get('/hello', (c) => c.text('OK'))
const req = new Request("http://localhost/hello", { const req = new Request('http://localhost/hello', {
headers: { headers: {
Authorization: `Bearer ${testingJWT}`, Authorization: `Bearer ${testingJWT}`,
}, },
}); })
// not use firebase emulator to check using key store // not use firebase emulator to check using key store
const res = await app.fetch(req, { const res = await app.fetch(req, {
FIREBASE_AUTH_EMULATOR_HOST: undefined, FIREBASE_AUTH_EMULATOR_HOST: undefined,
}); })
expect(res).not.toBeNull(); expect(res).not.toBeNull()
expect(res.status).toBe(401); expect(res.status).toBe(401)
expect(getSpy).toHaveBeenCalled(); expect(getSpy).toHaveBeenCalled()
expect(putSpy).toHaveBeenCalled(); expect(putSpy).toHaveBeenCalled()
}); })
test("usable id-token in main handler", async () => { test('usable id-token in main handler', async () => {
const testingJWT = generateDummyJWT(); const testingJWT = generateDummyJWT()
const nopKeyStore = new NopKeyStore(); const nopKeyStore = new NopKeyStore()
const app = new Hono<{ Bindings: VerifyFirebaseAuthEnv }>(); const app = new Hono<{ Bindings: VerifyFirebaseAuthEnv }>()
resetAuth(); resetAuth()
app.use( app.use(
"*", '*',
verifyFirebaseAuth({ verifyFirebaseAuth({
projectId: validProjectId, projectId: validProjectId,
keyStore: nopKeyStore, keyStore: nopKeyStore,
disableErrorLog: true, disableErrorLog: true,
}) })
); )
app.get("/hello", (c) => c.json(getFirebaseToken(c))); app.get('/hello', (c) => c.json(getFirebaseToken(c)))
const req = new Request("http://localhost/hello", { const req = new Request('http://localhost/hello', {
headers: { headers: {
Authorization: `Bearer ${testingJWT}`, Authorization: `Bearer ${testingJWT}`,
}, },
}); })
const res = await app.fetch(req, { const res = await app.fetch(req, {
FIREBASE_AUTH_EMULATOR_HOST: emulatorHost, FIREBASE_AUTH_EMULATOR_HOST: emulatorHost,
}); })
expect(res).not.toBeNull(); expect(res).not.toBeNull()
expect(res.status).toBe(200); expect(res.status).toBe(200)
const json = await res.json<{ aud: string; email: string }>(); const json = await res.json<{ aud: string; email: string }>()
expect(json.aud).toBe(validProjectId); expect(json.aud).toBe(validProjectId)
expect(json.email).toBe("codehex@hono.js"); expect(json.email).toBe('codehex@hono.js')
}); })
}); })
}); })
class NopKeyStore implements KeyStorer { class NopKeyStore implements KeyStorer {
// eslint-disable-next-line @typescript-eslint/no-empty-function
constructor() {} constructor() {}
get(): Promise<null> { get(): Promise<null> {
return new Promise((resolve) => resolve(null)); return new Promise((resolve) => resolve(null))
} }
put(): Promise<void> { put(): Promise<void> {
return new Promise((resolve) => resolve()); return new Promise((resolve) => resolve())
} }
} }
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
// magic to reset state of static object for "firebase-auth-cloudflare-workers" // magic to reset state of static object for "firebase-auth-cloudflare-workers"
const resetAuth = () => delete Auth["instance"]; const resetAuth = () => delete Auth['instance']
const generateDummyJWT = () => { const generateDummyJWT = () => {
const header = JSON.stringify({ const header = JSON.stringify({
alg: "RS256", alg: 'RS256',
kid: "kid", kid: 'kid',
typ: "JWT", typ: 'JWT',
}); })
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000)
const payload = JSON.stringify({ const payload = JSON.stringify({
iss: `https://securetoken.google.com/example-project12345`, iss: 'https://securetoken.google.com/example-project12345',
aud: "example-project12345", aud: 'example-project12345',
auth_time: now - 1000, auth_time: now - 1000,
user_id: "t1aLdTkAs0S0J0P6TNbjwbmry5B3", user_id: 't1aLdTkAs0S0J0P6TNbjwbmry5B3',
sub: "t1aLdTkAs0S0J0P6TNbjwbmry5B3", sub: 't1aLdTkAs0S0J0P6TNbjwbmry5B3',
iat: now - 1000, iat: now - 1000,
exp: now + 3000, // + 3s exp: now + 3000, // + 3s
email: "codehex@hono.js", email: 'codehex@hono.js',
email_verified: false, email_verified: false,
firebase: { firebase: {
identities: { identities: {
email: ["codehex@hono.js"], email: ['codehex@hono.js'],
}, },
sign_in_provider: "password", sign_in_provider: 'password',
}, },
}); })
return `${btoa(header)}.${btoa(payload)}.`; return `${btoa(header)}.${btoa(payload)}.`
}; }
interface EmailPassword { interface EmailPassword {
email: string; email: string
password: string; password: string
} }
export interface signUpResponse { export interface signUpResponse {
kind: string; kind: string
localId: string; localId: string
email: string; email: string
idToken: string; idToken: string
refreshToken: string; refreshToken: string
expiresIn: string; expiresIn: string
} }
const signUpEmulator = async ( const signUpEmulator = async (
@ -297,36 +290,33 @@ const signUpEmulator = async (
body: EmailPassword body: EmailPassword
): Promise<signUpResponse> => { ): Promise<signUpResponse> => {
// http://localhost:9099/identitytoolkit.googleapis.com/v1/accounts:signUp?key=dummy // http://localhost:9099/identitytoolkit.googleapis.com/v1/accounts:signUp?key=dummy
const url = `http://${emulatorHost}/identitytoolkit.googleapis.com/v1/accounts:signUp?key=dummy`; const url = `http://${emulatorHost}/identitytoolkit.googleapis.com/v1/accounts:signUp?key=dummy`
const resp = await fetch(url, { const resp = await fetch(url, {
method: "POST", method: 'POST',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ body: JSON.stringify({
...body, ...body,
returnSecureToken: true, returnSecureToken: true,
}), }),
}); })
if (resp.status !== 200) { if (resp.status !== 200) {
console.log({ status: resp.status }); console.log({ status: resp.status })
throw new Error("error"); throw new Error('error')
} }
return await resp.json(); return await resp.json()
}; }
const deleteAccountEmulator = async ( const deleteAccountEmulator = async (emulatorHost: string, projectId: string): Promise<void> => {
emulatorHost: string,
projectId: string
): Promise<void> => {
// https://firebase.google.com/docs/reference/rest/auth#section-auth-emulator-clearaccounts // https://firebase.google.com/docs/reference/rest/auth#section-auth-emulator-clearaccounts
const url = `http://${emulatorHost}/emulator/v1/projects/${projectId}/accounts`; const url = `http://${emulatorHost}/emulator/v1/projects/${projectId}/accounts`
const resp = await fetch(url, { const resp = await fetch(url, {
method: "DELETE", method: 'DELETE',
}); })
if (resp.status !== 200) { if (resp.status !== 200) {
console.log({ status: resp.status }); console.log({ status: resp.status })
throw new Error("error when clear accounts"); throw new Error('error when clear accounts')
} }
return; return
}; }

View File

@ -1,22 +1,6 @@
{ {
"extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"target": "es2020",
"module": "commonjs",
"declaration": true,
"moduleResolution": "Node",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"strictPropertyInitialization": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"types": [
"jest",
"node",
"@cloudflare/workers-types"
],
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist", "outDir": "./dist",
}, },

File diff suppressed because it is too large Load Diff

3268
yarn.lock

File diff suppressed because it is too large Load Diff