87 lines
2.9 KiB
TypeScript
87 lines
2.9 KiB
TypeScript
|
import * as admin from 'firebase-admin'
|
||
|
import fetch from 'node-fetch'
|
||
|
import { IncomingMessage, ServerResponse } from 'http'
|
||
|
import { FIREBASE_CONFIG, PROJECT_ID } from 'common/envs/constants'
|
||
|
import { getAuthCookies, setAuthCookies } from './auth'
|
||
|
import { GetServerSideProps, GetServerSidePropsContext } from 'next'
|
||
|
|
||
|
const ensureApp = async () => {
|
||
|
// Note: firebase-admin can only be imported from a server context,
|
||
|
// because it relies on Node standard library dependencies.
|
||
|
if (admin.apps.length === 0) {
|
||
|
// never initialize twice
|
||
|
return admin.initializeApp({ projectId: PROJECT_ID })
|
||
|
}
|
||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||
|
return admin.apps[0]!
|
||
|
}
|
||
|
|
||
|
const requestFirebaseIdToken = async (refreshToken: string) => {
|
||
|
// See https://firebase.google.com/docs/reference/rest/auth/#section-refresh-token
|
||
|
const refreshUrl = new URL('https://securetoken.googleapis.com/v1/token')
|
||
|
refreshUrl.searchParams.append('key', FIREBASE_CONFIG.apiKey)
|
||
|
const result = await fetch(refreshUrl.toString(), {
|
||
|
method: 'POST',
|
||
|
headers: {
|
||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||
|
},
|
||
|
body: new URLSearchParams({
|
||
|
grant_type: 'refresh_token',
|
||
|
refresh_token: refreshToken,
|
||
|
}),
|
||
|
})
|
||
|
if (!result.ok) {
|
||
|
throw new Error(`Could not refresh ID token: ${await result.text()}`)
|
||
|
}
|
||
|
return (await result.json()) as any
|
||
|
}
|
||
|
|
||
|
type RequestContext = {
|
||
|
req: IncomingMessage
|
||
|
res: ServerResponse
|
||
|
}
|
||
|
|
||
|
export const getServerAuthenticatedUid = async (ctx: RequestContext) => {
|
||
|
const app = await ensureApp()
|
||
|
const auth = app.auth()
|
||
|
const { idToken, refreshToken } = getAuthCookies(ctx.req)
|
||
|
|
||
|
// If we have a valid ID token, verify the user immediately with no network trips.
|
||
|
// If the ID token doesn't verify, we'll have to refresh it to see who they are.
|
||
|
// If they don't have any tokens, then we have no idea who they are.
|
||
|
if (idToken != null) {
|
||
|
try {
|
||
|
return (await auth.verifyIdToken(idToken))?.uid
|
||
|
} catch (e) {
|
||
|
if (refreshToken != null) {
|
||
|
const resp = await requestFirebaseIdToken(refreshToken)
|
||
|
setAuthCookies(resp.id_token, resp.refresh_token, ctx.res)
|
||
|
return (await auth.verifyIdToken(resp.id_token))?.uid
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return undefined
|
||
|
}
|
||
|
|
||
|
export const redirectIfLoggedIn = (dest: string, fn?: GetServerSideProps) => {
|
||
|
return async (ctx: GetServerSidePropsContext) => {
|
||
|
const uid = await getServerAuthenticatedUid(ctx)
|
||
|
if (uid == null) {
|
||
|
return fn != null ? await fn(ctx) : { props: {} }
|
||
|
} else {
|
||
|
return { redirect: { destination: dest, permanent: false } }
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export const redirectIfLoggedOut = (dest: string, fn?: GetServerSideProps) => {
|
||
|
return async (ctx: GetServerSidePropsContext) => {
|
||
|
const uid = await getServerAuthenticatedUid(ctx)
|
||
|
if (uid == null) {
|
||
|
return { redirect: { destination: dest, permanent: false } }
|
||
|
} else {
|
||
|
return fn != null ? await fn(ctx) : { props: {} }
|
||
|
}
|
||
|
}
|
||
|
}
|