Dramatically improve server auth stuff (#826)
This commit is contained in:
parent
42548cea2a
commit
0568322c82
|
@ -34,6 +34,11 @@ export const FIREBASE_CONFIG = ENV_CONFIG.firebaseConfig
|
||||||
export const PROJECT_ID = ENV_CONFIG.firebaseConfig.projectId
|
export const PROJECT_ID = ENV_CONFIG.firebaseConfig.projectId
|
||||||
export const IS_PRIVATE_MANIFOLD = ENV_CONFIG.visibility === 'PRIVATE'
|
export const IS_PRIVATE_MANIFOLD = ENV_CONFIG.visibility === 'PRIVATE'
|
||||||
|
|
||||||
|
export const AUTH_COOKIE_NAME = `FBUSER_${PROJECT_ID.toUpperCase().replace(
|
||||||
|
/-/g,
|
||||||
|
'_'
|
||||||
|
)}`
|
||||||
|
|
||||||
// Manifold's domain or any subdomains thereof
|
// Manifold's domain or any subdomains thereof
|
||||||
export const CORS_ORIGIN_MANIFOLD = new RegExp(
|
export const CORS_ORIGIN_MANIFOLD = new RegExp(
|
||||||
'^https?://(?:[a-zA-Z0-9\\-]+\\.)*' + escapeRegExp(ENV_CONFIG.domain) + '$'
|
'^https?://(?:[a-zA-Z0-9\\-]+\\.)*' + escapeRegExp(ENV_CONFIG.domain) + '$'
|
||||||
|
|
|
@ -8,17 +8,20 @@ import {
|
||||||
getUserAndPrivateUser,
|
getUserAndPrivateUser,
|
||||||
setCachedReferralInfoForUser,
|
setCachedReferralInfoForUser,
|
||||||
} from 'web/lib/firebase/users'
|
} from 'web/lib/firebase/users'
|
||||||
import { deleteTokenCookies, setTokenCookies } from 'web/lib/firebase/auth'
|
|
||||||
import { createUser } from 'web/lib/firebase/api'
|
import { createUser } from 'web/lib/firebase/api'
|
||||||
import { randomString } from 'common/util/random'
|
import { randomString } from 'common/util/random'
|
||||||
import { identifyUser, setUserProperty } from 'web/lib/service/analytics'
|
import { identifyUser, setUserProperty } from 'web/lib/service/analytics'
|
||||||
import { useStateCheckEquality } from 'web/hooks/use-state-check-equality'
|
import { useStateCheckEquality } from 'web/hooks/use-state-check-equality'
|
||||||
|
import { AUTH_COOKIE_NAME } from 'common/envs/constants'
|
||||||
|
import { setCookie } from 'web/lib/util/cookie'
|
||||||
|
|
||||||
// Either we haven't looked up the logged in user yet (undefined), or we know
|
// Either we haven't looked up the logged in user yet (undefined), or we know
|
||||||
// the user is not logged in (null), or we know the user is logged in.
|
// the user is not logged in (null), or we know the user is logged in.
|
||||||
type AuthUser = undefined | null | UserAndPrivateUser
|
type AuthUser = undefined | null | UserAndPrivateUser
|
||||||
|
|
||||||
|
const TEN_YEARS_SECS = 60 * 60 * 24 * 365 * 10
|
||||||
const CACHED_USER_KEY = 'CACHED_USER_KEY_V2'
|
const CACHED_USER_KEY = 'CACHED_USER_KEY_V2'
|
||||||
|
|
||||||
// Proxy localStorage in case it's not available (eg in incognito iframe)
|
// Proxy localStorage in case it's not available (eg in incognito iframe)
|
||||||
const localStorage =
|
const localStorage =
|
||||||
typeof window !== 'undefined'
|
typeof window !== 'undefined'
|
||||||
|
@ -38,6 +41,16 @@ const ensureDeviceToken = () => {
|
||||||
return deviceToken
|
return deviceToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const setUserCookie = (cookie: string | undefined) => {
|
||||||
|
const data = setCookie(AUTH_COOKIE_NAME, cookie ?? '', [
|
||||||
|
['path', '/'],
|
||||||
|
['max-age', (cookie === undefined ? 0 : TEN_YEARS_SECS).toString()],
|
||||||
|
['samesite', 'lax'],
|
||||||
|
['secure'],
|
||||||
|
])
|
||||||
|
document.cookie = data
|
||||||
|
}
|
||||||
|
|
||||||
export const AuthContext = createContext<AuthUser>(undefined)
|
export const AuthContext = createContext<AuthUser>(undefined)
|
||||||
|
|
||||||
export function AuthProvider(props: {
|
export function AuthProvider(props: {
|
||||||
|
@ -59,10 +72,7 @@ export function AuthProvider(props: {
|
||||||
auth,
|
auth,
|
||||||
async (fbUser) => {
|
async (fbUser) => {
|
||||||
if (fbUser) {
|
if (fbUser) {
|
||||||
setTokenCookies({
|
setUserCookie(JSON.stringify(fbUser.toJSON()))
|
||||||
id: await fbUser.getIdToken(),
|
|
||||||
refresh: fbUser.refreshToken,
|
|
||||||
})
|
|
||||||
let current = await getUserAndPrivateUser(fbUser.uid)
|
let current = await getUserAndPrivateUser(fbUser.uid)
|
||||||
if (!current.user || !current.privateUser) {
|
if (!current.user || !current.privateUser) {
|
||||||
const deviceToken = ensureDeviceToken()
|
const deviceToken = ensureDeviceToken()
|
||||||
|
@ -75,7 +85,7 @@ export function AuthProvider(props: {
|
||||||
setCachedReferralInfoForUser(current.user)
|
setCachedReferralInfoForUser(current.user)
|
||||||
} else {
|
} else {
|
||||||
// User logged out; reset to null
|
// User logged out; reset to null
|
||||||
deleteTokenCookies()
|
setUserCookie(undefined)
|
||||||
setAuthUser(null)
|
setAuthUser(null)
|
||||||
localStorage.removeItem(CACHED_USER_KEY)
|
localStorage.removeItem(CACHED_USER_KEY)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
import { PROJECT_ID } from 'common/envs/constants'
|
|
||||||
import { setCookie, getCookies } from '../util/cookie'
|
|
||||||
import { IncomingMessage, ServerResponse } from 'http'
|
|
||||||
|
|
||||||
const ONE_HOUR_SECS = 60 * 60
|
|
||||||
const TEN_YEARS_SECS = 60 * 60 * 24 * 365 * 10
|
|
||||||
const TOKEN_KINDS = ['refresh', 'id', 'custom'] as const
|
|
||||||
const TOKEN_AGES = {
|
|
||||||
id: ONE_HOUR_SECS,
|
|
||||||
refresh: TEN_YEARS_SECS,
|
|
||||||
custom: ONE_HOUR_SECS,
|
|
||||||
} as const
|
|
||||||
export type TokenKind = typeof TOKEN_KINDS[number]
|
|
||||||
|
|
||||||
const getAuthCookieName = (kind: TokenKind) => {
|
|
||||||
const suffix = `${PROJECT_ID}_${kind}`.toUpperCase().replace(/-/g, '_')
|
|
||||||
return `FIREBASE_TOKEN_${suffix}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const COOKIE_NAMES = Object.fromEntries(
|
|
||||||
TOKEN_KINDS.map((k) => [k, getAuthCookieName(k)])
|
|
||||||
) as Record<TokenKind, string>
|
|
||||||
|
|
||||||
const getCookieDataIsomorphic = (req?: IncomingMessage) => {
|
|
||||||
if (req != null) {
|
|
||||||
return req.headers.cookie ?? ''
|
|
||||||
} else if (document != null) {
|
|
||||||
return document.cookie
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
'Neither request nor document is available; no way to get cookies.'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const setCookieDataIsomorphic = (cookies: string[], res?: ServerResponse) => {
|
|
||||||
if (res != null) {
|
|
||||||
res.setHeader('Set-Cookie', cookies)
|
|
||||||
} else if (document != null) {
|
|
||||||
for (const ck of cookies) {
|
|
||||||
document.cookie = ck
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
'Neither response nor document is available; no way to set cookies.'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getTokensFromCookies = (req?: IncomingMessage) => {
|
|
||||||
const cookies = getCookies(getCookieDataIsomorphic(req))
|
|
||||||
return Object.fromEntries(
|
|
||||||
TOKEN_KINDS.map((k) => [k, cookies[COOKIE_NAMES[k]]])
|
|
||||||
) as Partial<Record<TokenKind, string>>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setTokenCookies = (
|
|
||||||
cookies: Partial<Record<TokenKind, string | undefined>>,
|
|
||||||
res?: ServerResponse
|
|
||||||
) => {
|
|
||||||
const data = TOKEN_KINDS.filter((k) => k in cookies).map((k) => {
|
|
||||||
const maxAge = cookies[k] ? TOKEN_AGES[k as TokenKind] : 0
|
|
||||||
return setCookie(COOKIE_NAMES[k], cookies[k] ?? '', [
|
|
||||||
['path', '/'],
|
|
||||||
['max-age', maxAge.toString()],
|
|
||||||
['samesite', 'lax'],
|
|
||||||
['secure'],
|
|
||||||
])
|
|
||||||
})
|
|
||||||
setCookieDataIsomorphic(data, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const deleteTokenCookies = (res?: ServerResponse) =>
|
|
||||||
setTokenCookies({ id: undefined, refresh: undefined, custom: undefined }, res)
|
|
|
@ -1,165 +1,81 @@
|
||||||
import fetch from 'node-fetch'
|
|
||||||
import { IncomingMessage, ServerResponse } from 'http'
|
import { IncomingMessage, ServerResponse } from 'http'
|
||||||
import { FIREBASE_CONFIG, PROJECT_ID } from 'common/envs/constants'
|
import { Auth as FirebaseAuth, User as FirebaseUser } from 'firebase/auth'
|
||||||
import { getFunctionUrl } from 'common/api'
|
import { AUTH_COOKIE_NAME } from 'common/envs/constants'
|
||||||
import { UserCredential } from 'firebase/auth'
|
import { getCookies } from 'web/lib/util/cookie'
|
||||||
import {
|
|
||||||
getTokensFromCookies,
|
|
||||||
setTokenCookies,
|
|
||||||
deleteTokenCookies,
|
|
||||||
} from './auth'
|
|
||||||
import {
|
import {
|
||||||
GetServerSideProps,
|
GetServerSideProps,
|
||||||
GetServerSidePropsContext,
|
GetServerSidePropsContext,
|
||||||
GetServerSidePropsResult,
|
GetServerSidePropsResult,
|
||||||
} from 'next'
|
} from 'next'
|
||||||
|
|
||||||
// server firebase SDK
|
|
||||||
import * as admin from 'firebase-admin'
|
|
||||||
|
|
||||||
// client firebase SDK
|
// client firebase SDK
|
||||||
import { app as clientApp } from './init'
|
import { app as clientApp } from './init'
|
||||||
import { getAuth, signInWithCustomToken } from 'firebase/auth'
|
import { getAuth, updateCurrentUser } from 'firebase/auth'
|
||||||
|
|
||||||
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 { id_token: string; refresh_token: string }
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestManifoldCustomToken = async (idToken: string) => {
|
|
||||||
const functionUrl = getFunctionUrl('getcustomtoken')
|
|
||||||
const result = await fetch(functionUrl, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${idToken}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (!result.ok) {
|
|
||||||
throw new Error(`Could not get custom token: ${await result.text()}`)
|
|
||||||
}
|
|
||||||
return (await result.json()) as { token: string }
|
|
||||||
}
|
|
||||||
|
|
||||||
type RequestContext = {
|
type RequestContext = {
|
||||||
req: IncomingMessage
|
req: IncomingMessage
|
||||||
res: ServerResponse
|
res: ServerResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
const authAndRefreshTokens = async (ctx: RequestContext) => {
|
// The Firebase SDK doesn't really support persisting the logged-in state between
|
||||||
const adminAuth = (await ensureApp()).auth()
|
// devices, or anything like that. To get it from the client to the server:
|
||||||
const clientAuth = getAuth(clientApp)
|
//
|
||||||
console.debug('Initialized Firebase auth libraries.')
|
// 1. We pack up the user by calling (the undocumented) User.toJSON(). This is the
|
||||||
|
// same way the Firebase SDK saves it to disk, so it's gonna have the right stuff.
|
||||||
|
//
|
||||||
|
// 2. We put it into a cookie and read the cookie out here.
|
||||||
|
//
|
||||||
|
// 3. We use the Firebase "persistence manager" to write the cookie value into the persistent
|
||||||
|
// store on the server (an in-memory store), just as if the SDK had saved the user itself.
|
||||||
|
//
|
||||||
|
// 4. We ask the persistence manager for the current user, which reads what we just wrote,
|
||||||
|
// and creates a real puffed-up internal user object from the serialized user.
|
||||||
|
//
|
||||||
|
// 5. We set that user to be the current Firebase user in the SDK.
|
||||||
|
//
|
||||||
|
// 6. We ask for the ID token, which will refresh it if necessary (i.e. if this cookie
|
||||||
|
// is from an old browser session), so that we know the SDK is prepared to do real
|
||||||
|
// Firebase queries.
|
||||||
|
//
|
||||||
|
// This strategy should be robust, since it's repurposing Firebase's internal persistence
|
||||||
|
// machinery, but the details may eventually need updating for new versions of the SDK.
|
||||||
|
//
|
||||||
|
// References:
|
||||||
|
// Persistence manager: https://github.com/firebase/firebase-js-sdk/blob/39f4635ebc07316661324145f1b8c27f9bd7aedb/packages/auth/src/core/persistence/persistence_user_manager.ts#L64
|
||||||
|
// Token manager: https://github.com/firebase/firebase-js-sdk/blob/39f4635ebc07316661324145f1b8c27f9bd7aedb/packages/auth/src/core/user/token_manager.ts#L76
|
||||||
|
|
||||||
let { id, refresh, custom } = getTokensFromCookies(ctx.req)
|
interface FirebaseAuthInternal extends FirebaseAuth {
|
||||||
|
persistenceManager: {
|
||||||
// step 0: if you have no refresh token you are logged out
|
fullUserKey: string
|
||||||
if (refresh == null) {
|
getCurrentUser: () => Promise<FirebaseUser | null>
|
||||||
console.debug('User is unauthenticated.')
|
persistence: {
|
||||||
return null
|
_set: (k: string, obj: Record<string, unknown>) => Promise<void>
|
||||||
}
|
|
||||||
|
|
||||||
console.debug('User may be authenticated; checking cookies.')
|
|
||||||
|
|
||||||
// step 1: given a valid refresh token, ensure a valid ID token
|
|
||||||
if (id != null) {
|
|
||||||
// if they have an ID token, throw it out if it's invalid/expired
|
|
||||||
try {
|
|
||||||
await adminAuth.verifyIdToken(id)
|
|
||||||
console.debug('Verified ID token.')
|
|
||||||
} catch {
|
|
||||||
id = undefined
|
|
||||||
console.debug('Invalid existing ID token.')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (id == null) {
|
|
||||||
// ask for a new one from google using the refresh token
|
|
||||||
try {
|
|
||||||
const resp = await requestFirebaseIdToken(refresh)
|
|
||||||
console.debug('Obtained fresh ID token from Firebase.')
|
|
||||||
id = resp.id_token
|
|
||||||
refresh = resp.refresh_token
|
|
||||||
} catch (e) {
|
|
||||||
// big unexpected problem -- functionally, they are not logged in
|
|
||||||
console.error(e)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// step 2: given a valid ID token, ensure a valid custom token, and sign in
|
|
||||||
// to the client SDK with the custom token
|
|
||||||
if (custom != null) {
|
|
||||||
// sign in with this token, or throw it out if it's invalid/expired
|
|
||||||
try {
|
|
||||||
const creds = await signInWithCustomToken(clientAuth, custom)
|
|
||||||
console.debug('Signed in with custom token.')
|
|
||||||
return { creds, id, refresh, custom }
|
|
||||||
} catch {
|
|
||||||
custom = undefined
|
|
||||||
console.debug('Invalid existing custom token.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (custom == null) {
|
|
||||||
// ask for a new one from our cloud functions using the ID token, then sign in
|
|
||||||
try {
|
|
||||||
const resp = await requestManifoldCustomToken(id)
|
|
||||||
console.debug('Obtained fresh custom token from backend.')
|
|
||||||
custom = resp.token
|
|
||||||
const creds = await signInWithCustomToken(clientAuth, custom)
|
|
||||||
console.debug('Signed in with custom token.')
|
|
||||||
return { creds, id, refresh, custom }
|
|
||||||
} catch (e) {
|
|
||||||
// big unexpected problem -- functionally, they are not logged in
|
|
||||||
console.error(e)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const authenticateOnServer = async (ctx: RequestContext) => {
|
export const authenticateOnServer = async (ctx: RequestContext) => {
|
||||||
console.debug('Server authentication sequence starting.')
|
const user = getCookies(ctx.req.headers.cookie ?? '')[AUTH_COOKIE_NAME]
|
||||||
const tokens = await authAndRefreshTokens(ctx)
|
if (user == null) {
|
||||||
console.debug('Finished checking and refreshing tokens.')
|
console.debug('User is unauthenticated.')
|
||||||
const creds = tokens?.creds
|
return null
|
||||||
try {
|
}
|
||||||
if (tokens == null) {
|
try {
|
||||||
deleteTokenCookies(ctx.res)
|
const deserializedUser = JSON.parse(user)
|
||||||
console.debug('Not logged in; cleared token cookies.')
|
const clientAuth = getAuth(clientApp) as FirebaseAuthInternal
|
||||||
} else {
|
const persistenceManager = clientAuth.persistenceManager
|
||||||
setTokenCookies(tokens, ctx.res)
|
const persistence = persistenceManager.persistence
|
||||||
console.debug('Logged in; set current token cookies.')
|
await persistence._set(persistenceManager.fullUserKey, deserializedUser)
|
||||||
}
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
} catch (e) {
|
const fbUser = (await persistenceManager.getCurrentUser())!
|
||||||
// definitely not supposed to happen, but let's be maximally robust
|
await fbUser.getIdToken() // forces a refresh if necessary
|
||||||
console.error(e)
|
await updateCurrentUser(clientAuth, fbUser)
|
||||||
|
console.debug('Signed in with user from cookie.')
|
||||||
|
return fbUser
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
return creds ?? null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// note that we might want to define these types more generically if we want better
|
// note that we might want to define these types more generically if we want better
|
||||||
|
@ -167,7 +83,7 @@ export const authenticateOnServer = async (ctx: RequestContext) => {
|
||||||
|
|
||||||
type GetServerSidePropsAuthed<P> = (
|
type GetServerSidePropsAuthed<P> = (
|
||||||
context: GetServerSidePropsContext,
|
context: GetServerSidePropsContext,
|
||||||
creds: UserCredential
|
creds: FirebaseUser
|
||||||
) => Promise<GetServerSidePropsResult<P>>
|
) => Promise<GetServerSidePropsResult<P>>
|
||||||
|
|
||||||
export const redirectIfLoggedIn = <P extends { [k: string]: any }>(
|
export const redirectIfLoggedIn = <P extends { [k: string]: any }>(
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||||
const creds = await authenticateOnServer(ctx)
|
const creds = await authenticateOnServer(ctx)
|
||||||
const username = ctx.params!.username as string // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
const username = ctx.params!.username as string // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
||||||
const [auth, user] = (await Promise.all([
|
const [auth, user] = (await Promise.all([
|
||||||
creds != null ? getUserAndPrivateUser(creds.user.uid) : null,
|
creds != null ? getUserAndPrivateUser(creds.uid) : null,
|
||||||
getUserByUsername(username),
|
getUserByUsername(username),
|
||||||
])) as [UserAndPrivateUser | null, User | null]
|
])) as [UserAndPrivateUser | null, User | null]
|
||||||
return { props: { auth, user } }
|
return { props: { auth, user } }
|
||||||
|
|
|
@ -36,7 +36,7 @@ import { MultipleChoiceAnswers } from 'web/components/answers/multiple-choice-an
|
||||||
import { MINUTE_MS } from 'common/util/time'
|
import { MINUTE_MS } from 'common/util/time'
|
||||||
|
|
||||||
export const getServerSideProps = redirectIfLoggedOut('/', async (_, creds) => {
|
export const getServerSideProps = redirectIfLoggedOut('/', async (_, creds) => {
|
||||||
return { props: { auth: await getUserAndPrivateUser(creds.user.uid) } }
|
return { props: { auth: await getUserAndPrivateUser(creds.uid) } }
|
||||||
})
|
})
|
||||||
|
|
||||||
type NewQuestionParams = {
|
type NewQuestionParams = {
|
||||||
|
|
|
@ -28,7 +28,7 @@ import { Row } from 'web/components/layout/row'
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||||
const creds = await authenticateOnServer(ctx)
|
const creds = await authenticateOnServer(ctx)
|
||||||
const auth = creds ? await getUserAndPrivateUser(creds.user.uid) : null
|
const auth = creds ? await getUserAndPrivateUser(creds.uid) : null
|
||||||
return { props: { auth } }
|
return { props: { auth } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { usePrefetch } from 'web/hooks/use-prefetch'
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||||
const creds = await authenticateOnServer(ctx)
|
const creds = await authenticateOnServer(ctx)
|
||||||
const auth = creds ? await getUserAndPrivateUser(creds.user.uid) : null
|
const auth = creds ? await getUserAndPrivateUser(creds.uid) : null
|
||||||
return { props: { auth } }
|
return { props: { auth } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ import { UserLink } from 'web/components/user-link'
|
||||||
const LINKS_PER_PAGE = 24
|
const LINKS_PER_PAGE = 24
|
||||||
|
|
||||||
export const getServerSideProps = redirectIfLoggedOut('/', async (_, creds) => {
|
export const getServerSideProps = redirectIfLoggedOut('/', async (_, creds) => {
|
||||||
return { props: { auth: await getUserAndPrivateUser(creds.user.uid) } }
|
return { props: { auth: await getUserAndPrivateUser(creds.uid) } }
|
||||||
})
|
})
|
||||||
|
|
||||||
export function getManalinkUrl(slug: string) {
|
export function getManalinkUrl(slug: string) {
|
||||||
|
|
|
@ -23,7 +23,7 @@ import Textarea from 'react-expanding-textarea'
|
||||||
import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth'
|
import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth'
|
||||||
|
|
||||||
export const getServerSideProps = redirectIfLoggedOut('/', async (_, creds) => {
|
export const getServerSideProps = redirectIfLoggedOut('/', async (_, creds) => {
|
||||||
return { props: { auth: await getUserAndPrivateUser(creds.user.uid) } }
|
return { props: { auth: await getUserAndPrivateUser(creds.uid) } }
|
||||||
})
|
})
|
||||||
|
|
||||||
function EditUserField(props: {
|
function EditUserField(props: {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user