generated from StanfordBDHG/NextJSTemplate
-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6077e2c
commit bf97a38
Showing
7 changed files
with
158 additions
and
99 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// | ||
// This source file is part of the Stanford Biodesign Digital Health ENGAGE-HF open-source project | ||
// | ||
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
// | ||
// SPDX-License-Identifier: MIT | ||
// | ||
'use client' | ||
import { useRouter } from 'next/navigation' | ||
import { useEffect } from 'react' | ||
import { | ||
useAuthUser, | ||
useRegisterAuthServiceWorker, | ||
} from '@stanfordbdhg/design-system/modules/auth/hooks' | ||
import { auth } from './clientApp' | ||
import { firebaseConfig } from './config' | ||
import { routes } from '../routes' | ||
|
||
export const AuthProvider = () => { | ||
const router = useRouter() | ||
const user = useAuthUser(auth) | ||
|
||
useRegisterAuthServiceWorker(firebaseConfig) | ||
|
||
useEffect(() => { | ||
// TODO: This is not ideal, results with double redirect. To investigate | ||
const isSignIn = window.location.pathname === routes.signIn | ||
if (isSignIn && user) { | ||
window.location.assign(routes.home) | ||
} else if (!isSignIn && user === null) { | ||
window.location.assign(routes.signIn) | ||
} | ||
}, [router, user]) | ||
|
||
return null | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import type { User } from '@firebase/auth-types' | ||
import { type FirebaseOptions } from 'firebase/app' | ||
import { type Auth, onAuthStateChanged } from 'firebase/auth' | ||
import { useEffect, useState } from 'react' | ||
|
||
/** | ||
* Registers Auth Service Worker | ||
* */ | ||
export const useRegisterAuthServiceWorker = ( | ||
firebaseOptions: FirebaseOptions, | ||
) => { | ||
useEffect(() => { | ||
if ('serviceWorker' in navigator) { | ||
const serializedFirebaseConfig = encodeURIComponent( | ||
JSON.stringify(firebaseOptions), | ||
) | ||
const serviceWorkerUrl = `/authServiceWorker.js?firebaseConfig=${serializedFirebaseConfig}` | ||
|
||
navigator.serviceWorker.register(serviceWorkerUrl).catch(() => { | ||
console.error( | ||
'Registering service worker failed. Make sure public/authServiceWorker.js exists', | ||
) | ||
}) | ||
} | ||
}, [firebaseOptions]) | ||
} | ||
|
||
/** | ||
* Returns currently authenticated user | ||
* null = no user is authenticated | ||
* undefined = initial state | ||
* */ | ||
export const useAuthUser = (auth: Auth) => { | ||
const [user, setUser] = useState<User | null>() | ||
|
||
useEffect(() => { | ||
const unsubscribe = onAuthStateChanged(auth, (user) => { | ||
// @ts-expect-error Nested methods are not used anyway | ||
setUser(user) | ||
}) | ||
return () => unsubscribe() | ||
}, [auth]) | ||
|
||
return user | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// | ||
// This source file is part of the Stanford Biodesign Digital Health ENGAGE-HF open-source project | ||
// | ||
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
// | ||
// SPDX-License-Identifier: MIT | ||
// | ||
/// <reference lib="webworker" /> | ||
|
||
import { type FirebaseOptions, initializeApp } from 'firebase/app' | ||
import { getAuth, getIdToken, type Auth } from 'firebase/auth' | ||
import { getInstallations, getToken } from 'firebase/installations' | ||
|
||
/** | ||
* Get Firebase config from query string | ||
* */ | ||
export const getFirebaseConfig = () => { | ||
const serializedFirebaseConfig = new URL(location.href).searchParams.get( | ||
'firebaseConfig', | ||
) | ||
if (!serializedFirebaseConfig) { | ||
throw new Error( | ||
'Firebase Config object not found in service worker query string.', | ||
) | ||
} | ||
return JSON.parse(serializedFirebaseConfig) as FirebaseOptions | ||
} | ||
|
||
export const getAuthIdToken = async (auth: Auth) => { | ||
await auth.authStateReady() | ||
if (!auth.currentUser) return | ||
return getIdToken(auth.currentUser) | ||
} | ||
|
||
export const fetchWithFirebaseHeaders = async ( | ||
firebaseConfig: FirebaseOptions, | ||
request: FetchEvent['request'], | ||
) => { | ||
const app = initializeApp(firebaseConfig) | ||
const auth = getAuth(app) | ||
const installations = getInstallations(app) | ||
const headers = new Headers(request.headers) | ||
const [authIdToken, installationToken] = await Promise.all([ | ||
getAuthIdToken(auth), | ||
getToken(installations), | ||
]) | ||
headers.append('Firebase-Instance-ID-Token', installationToken) | ||
if (authIdToken) headers.append('Authorization', `Bearer ${authIdToken}`) | ||
const newRequest = new Request(request, { headers }) | ||
return fetch(newRequest) | ||
} | ||
|
||
/** | ||
* Appends Bearer token to every fetch request | ||
* This allows RSC and API endpoints to identify user | ||
* */ | ||
export const handleFetchEvent = ( | ||
firebaseConfig: FirebaseOptions, | ||
event: FetchEvent, | ||
) => { | ||
const { origin } = new URL(event.request.url) | ||
if (origin !== self.location.origin) return | ||
event.respondWith(fetchWithFirebaseHeaders(firebaseConfig, event.request)) | ||
} |