Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Let user specify the firebase app name #716

Merged
merged 12 commits into from
Jul 1, 2024
25 changes: 25 additions & 0 deletions src/__tests__/createUser.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { signOut } from 'firebase/auth'
import { setConfig } from 'src/config'
import createMockConfig from 'src/testHelpers/createMockConfig'
import {
createMockFirebaseUserClientSDK,
createMockFirebaseUserAdminSDK,
Expand All @@ -8,6 +10,8 @@ import {
jest.mock('firebase/auth')
jest.mock('firebase/app')

const mockSetConfig = jest.mocked(setConfig)

afterEach(() => {
jest.clearAllMocks()
})
Expand Down Expand Up @@ -117,6 +121,9 @@ describe('createUser: basic tests', () => {
describe('createUser: firebaseUserClientSDK', () => {
it('returns the expected data', () => {
expect.assertions(1)
mockSetConfig({
...createMockConfig(),
})
const createUser = require('src/createUser').default
const firebaseUserJSSDK = createMockFirebaseUserClientSDK()
expect(createUser({ firebaseUserClientSDK: firebaseUserJSSDK })).toEqual({
Expand All @@ -138,6 +145,9 @@ describe('createUser: firebaseUserClientSDK', () => {

it('returns the expected data when custom claims are included', () => {
expect.assertions(1)
mockSetConfig({
...createMockConfig(),
})
const createUser = require('src/createUser').default
const customClaims = {
foo: 'bar',
Expand Down Expand Up @@ -170,6 +180,9 @@ describe('createUser: firebaseUserClientSDK', () => {

it('does not throw when custom claims are defined but the client user is not defined', () => {
expect.assertions(1)
mockSetConfig({
...createMockConfig(),
})
const createUser = require('src/createUser').default
expect(() => {
createUser({
Expand All @@ -186,6 +199,9 @@ describe('createUser: firebaseUserClientSDK', () => {

it('returns the expected value from getIdToken', async () => {
expect.assertions(1)
mockSetConfig({
...createMockConfig(),
})
const createUser = require('src/createUser').default
const user = createUser({
firebaseUserClientSDK: createMockFirebaseUserClientSDK(),
Expand All @@ -196,6 +212,9 @@ describe('createUser: firebaseUserClientSDK', () => {

it('returns the expected value from serialize', async () => {
expect.assertions(1)
mockSetConfig({
...createMockConfig(),
})
const createUser = require('src/createUser').default
const user = createUser({
firebaseUserClientSDK: createMockFirebaseUserClientSDK(),
Expand All @@ -219,6 +238,9 @@ describe('createUser: firebaseUserClientSDK', () => {

it("calls Firebase's signOut method when we call user.signOut", async () => {
expect.assertions(1)
mockSetConfig({
...createMockConfig(),
})
const createUser = require('src/createUser').default
const user = createUser({
firebaseUserClientSDK: createMockFirebaseUserClientSDK(),
Expand Down Expand Up @@ -521,6 +543,9 @@ describe('createUser: serializedUser', () => {

it("does not call Firebase's signOut method when we call user.signOut (it should be a noop)", async () => {
expect.assertions(1)
mockSetConfig({
...createMockConfig(),
})
const createUser = require('src/createUser').default
const user = createUser({
serializedUser: createMockSerializedUser(),
Expand Down
15 changes: 9 additions & 6 deletions src/__tests__/initFirebaseClientSDK.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@ describe('initFirebaseClientSDK', () => {
expect.assertions(1)
const initFirebaseClientSDK = require('src/initFirebaseClientSDK').default
initFirebaseClientSDK()
expect(mockInitializeApp).toHaveBeenCalledWith({
apiKey: 'fakeAPIKey123',
authDomain: 'my-example-app.firebaseapp.com',
databaseURL: 'https://my-example-app.firebaseio.com',
projectId: 'my-example-app-id',
})
expect(mockInitializeApp).toHaveBeenCalledWith(
{
apiKey: 'fakeAPIKey123',
authDomain: 'my-example-app.firebaseapp.com',
databaseURL: 'https://my-example-app.firebaseio.com',
projectId: 'my-example-app-id',
},
'example-app-name'
)
})

it('does not call firebase.initializeApp if Firebase already has an initialized app', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/redirects.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { ParsedUrlQuery } from 'querystring'
import { getLoginRedirectInfo, getAppRedirectInfo } from 'src/redirects'
import getMockConfig from 'src/testHelpers/createMockConfig'
import { setConfig } from 'src/config'
import { User } from 'src/createUser'
import { User } from 'src/sharedTypes'
import { RedirectInput } from 'src/redirectTypes'

describe('redirects', () => {
Expand Down
3 changes: 2 additions & 1 deletion src/__tests__/withUser.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
createMockFirebaseUserClientSDK,
} from 'src/testHelpers/userInputs'
import useUser from 'src/useUser'
import createUser, { User } from 'src/createUser'
import createUser from 'src/createUser'
import { User } from 'src/sharedTypes'
import useFirebaseUser from 'src/useFirebaseUser'
import { AuthAction } from 'src/AuthAction'
import logDebug from 'src/logDebug'
Expand Down
4 changes: 3 additions & 1 deletion src/configTypes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Cookies from 'cookies'
import { PageURL } from './redirectTypes'
import { User } from './createUser'
import { User } from './sharedTypes'
import { Spread } from './Spread'

type OnErrorHandler = (error: Error) => void
Expand Down Expand Up @@ -90,6 +90,7 @@ export interface ConfigInput {
// "G-MEASUREMENT_ID"
measurementId?: string
}
firebaseClientAppName?: string
tenantId?: string
cookies: Omit<Cookies.Option & Cookies.SetOption, 'sameSite'> & {
// The base name for the auth cookies.
Expand All @@ -110,6 +111,7 @@ export const defaultConfig = {
onVerifyTokenError: (_err: Error) => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
onTokenRefreshError: (_err: Error) => {},
firebaseClientAppName: 'example-app-name',
MvRemmerden marked this conversation as resolved.
Show resolved Hide resolved
cookies: {
// Required to be provided by the user.
// name: undefined,
Expand Down
21 changes: 4 additions & 17 deletions src/createUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { User as FirebaseUser } from 'firebase/auth'
import { DecodedIdToken } from 'firebase-admin/auth'
import isClientSide from 'src/isClientSide'
import { Claims, filterStandardClaims } from 'src/claims'
import { User } from './sharedTypes'
import { getConfig } from './config'

interface UserDeserialized {
id?: string
Expand Down Expand Up @@ -30,22 +32,6 @@ interface CreateUserInput {

type getIdToken = (forceRefresh?: boolean) => Promise<string | null>

export interface User {
id: string | null
email: string | null
emailVerified: boolean
phoneNumber: string | null
displayName: string | null
photoURL: string | null
claims: Record<string, string | boolean>
tenantId: string | null
getIdToken: (forceRefresh?: boolean) => Promise<string | null>
clientInitialized: boolean
firebaseUser: FirebaseUser | null
signOut: () => Promise<void>
serialize: (a?: { includeToken?: boolean }) => string
}

/**
* Take a representation of a Firebase user from a maximum of one of:
* the Firebase JS SDK, Firebase admin SDK, or serialized User instance.
Expand Down Expand Up @@ -119,8 +105,9 @@ const createUser = ({
const { getApp } = require('firebase/app')
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const { getAuth, signOut } = require('firebase/auth')
const { firebaseClientAppName } = getConfig()

signOutFunc = async () => signOut(getAuth(getApp()))
signOutFunc = async () => signOut(getAuth(getApp(firebaseClientAppName)))
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/firebaseAdmin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getAuth as getAdminAuth } from 'firebase-admin/auth'
import initFirebaseAdminSDK from 'src/initFirebaseAdminSDK'
import createUser, { User } from 'src/createUser'
import createUser from 'src/createUser'
import { User } from 'src/sharedTypes'
import { getConfig } from 'src/config'
import logDebug from 'src/logDebug'
import { FirebaseError as FirebaseErrorType } from 'firebase-admin/app'
Expand Down
3 changes: 2 additions & 1 deletion src/getUserFromCookies.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import createUser, { User } from 'src/createUser'
import createUser from 'src/createUser'
import { User } from 'src/sharedTypes'
import { getCookie } from 'src/cookies'
import { verifyIdToken } from 'src/firebaseAdmin'
import {
Expand Down
15 changes: 11 additions & 4 deletions src/initFirebaseClientSDK.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@ import { getConfig } from 'src/config'
import logDebug from 'src/logDebug'

export default function initFirebaseClientSDK() {
const { firebaseClientInitConfig, firebaseAuthEmulatorHost, tenantId } =
getConfig()
const {
firebaseClientInitConfig,
firebaseAuthEmulatorHost,
tenantId,
firebaseClientAppName,
} = getConfig()
if (!getApps().length) {
if (!firebaseClientInitConfig) {
throw new Error(
'If not initializing the Firebase JS SDK elsewhere, you must provide "firebaseClientInitConfig" to next-firebase-auth.'
)
}

initializeApp(firebaseClientInitConfig)
initializeApp(firebaseClientInitConfig, firebaseClientAppName)
if (tenantId) {
getAuth().tenantId = tenantId
}
Expand All @@ -25,6 +29,9 @@ export default function initFirebaseClientSDK() {
}
// If the user has provided the firebaseAuthEmulatorHost address, set the emulator
if (firebaseAuthEmulatorHost) {
connectAuthEmulator(getAuth(getApp()), `http://${firebaseAuthEmulatorHost}`)
connectAuthEmulator(
getAuth(getApp(firebaseClientAppName)),
`http://${firebaseAuthEmulatorHost}`
)
}
}
2 changes: 1 addition & 1 deletion src/redirectTypes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { GetServerSidePropsContext, Redirect } from 'next'
import type { ParsedUrlQuery } from 'querystring'
import { User } from './createUser'
import { User } from './sharedTypes'

export type URLResolveFunction = (obj: {
ctx?: GetServerSidePropsContext<ParsedUrlQuery>
Expand Down
2 changes: 1 addition & 1 deletion src/redirects.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { GetServerSidePropsContext } from 'next'

import { User } from 'src/createUser'
import { User } from 'src/sharedTypes'
import { getConfig } from 'src/config'
import {
PageURL,
Expand Down
3 changes: 2 additions & 1 deletion src/setAuthCookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { setCookie } from 'src/cookies'
import { getUserCookieName, getUserTokensCookieName } from 'src/authCookies'
import { getConfig } from 'src/config'
import logDebug from 'src/logDebug'
import createUser, { User } from 'src/createUser'
import createUser from 'src/createUser'
import { User } from 'src/sharedTypes'
import { NextApiRequest, NextApiResponse } from 'next'

export type SetAuthCookies = (
Expand Down
17 changes: 17 additions & 0 deletions src/sharedTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { User as FirebaseUser } from 'firebase/auth'

export interface User {
id: string | null
email: string | null
emailVerified: boolean
phoneNumber: string | null
displayName: string | null
photoURL: string | null
claims: Record<string, string | boolean>
tenantId: string | null
getIdToken: (forceRefresh?: boolean) => Promise<string | null>
clientInitialized: boolean
firebaseUser: FirebaseUser | null
signOut: () => Promise<void>
serialize: (a?: { includeToken?: boolean }) => string
}
1 change: 1 addition & 0 deletions src/testHelpers/createMockConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const createMockConfig = ({ clientSide }: { clientSide?: boolean } = {}) => {
databaseURL: 'https://my-example-app.firebaseio.com',
projectId: 'my-example-app-id',
},
firebaseClientAppName: 'example-app-name',
cookies: {
name: 'someExample',
keys: useClientSideConfig ? [] : ['abc', 'def'],
Expand Down
8 changes: 6 additions & 2 deletions src/useFirebaseUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
onIdTokenChanged,
} from 'firebase/auth'
import { getConfig } from 'src/config'
import createUser, { User } from 'src/createUser'
import createUser from 'src/createUser'
import { User } from 'src/sharedTypes'
import { Claims, filterStandardClaims } from 'src/claims'
import logDebug from 'src/logDebug'

Expand Down Expand Up @@ -175,7 +176,10 @@ const useFirebaseUser = () => {
}

// https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onidtokenchanged
const unsubscribe = onIdTokenChanged(getAuth(getApp()), onIdTokenChange)

const { firebaseClientAppName } = getConfig()
const app = getApp(firebaseClientAppName)
const unsubscribe = onIdTokenChanged(getAuth(app), onIdTokenChange)
return () => {
unsubscribe()
isCancelled = true
Expand Down
2 changes: 1 addition & 1 deletion src/useUser.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createContext, useContext } from 'react'
import { User } from './createUser'
import { User } from './sharedTypes'

type UserContext =
| (User & {
Expand Down
2 changes: 1 addition & 1 deletion src/withUserTokenSSR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import getUserFromCookies from 'src/getUserFromCookies'
import { AuthAction } from 'src/AuthAction'
import { getLoginRedirectInfo, getAppRedirectInfo } from 'src/redirects'
import logDebug from 'src/logDebug'
import { User } from './createUser'
import { User } from './sharedTypes'
import { PageURL } from './redirectTypes'

export interface WithUserSSROptions {
Expand Down
Loading