提交 34f1aefa 编写于 作者: K Kevin Jennison 提交者: Joe Haddad

Add example: with-firebase-authentication-serverless (#10078)

* Start from existing example

* Upgrade some dependencies

* Use dotenv

* Remove custom server

* Add serverless Firebase auth

* Add TODOs

* Update project name

* Fix build script

* Remove server middleware from client JS bundle

* Add logout functionality

* Redirect to auth page on logout

* Remove TODO

* Add comments about the cookie-session approach

* Remove the sessions folder

* Add comments for eslint

* Remove unused files

* Clarify comment

* Update README.md

* Rename variable for clarity

* Update README.md

* Change some comments

* Add more to gitignore

* Remove the bundle analyzer

* Move server-side auth user logic from _app.js to a HOC to support static HTML rendering
Co-authored-by: NJoe Haddad <timer150@gmail.com>
上级 04f1dd52
# For variables you need accessible at build time, add the variable to
# next.config.js. For secret values in local development, add the variable
# to .env.local, outside of source control.
# Update these with your Firebase app's values.
FIREBASE_AUTH_DOMAIN=my-example-app.firebaseapp.com
FIREBASE_CLIENT_EMAIL=my-example-app-email@example.com
FIREBASE_DATABASE_URL=https://my-example-app.firebaseio.com
FIREBASE_PROJECT_ID=my-example-app-id
FIREBASE_PUBLIC_API_KEY=MyExampleAppAPIKey123
# Create another file in this directory named ".env.local", which you
# should not include in source control. In .env.local, set these secret
# environment variables:
# Your Firebase private key.
# FIREBASE_PRIVATE_KEY=some-key-here
# Secrets used by cookie-session.
# SESSION_SECRET_CURRENT=someSecretValue
# SESSION_SECRET_PREVIOUS=anotherSecretValue
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
.env.*local
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Local dotenv files. We're following the file structure used in
# create-react-app and documented in the Ruby dotenv:
# https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
.env.*local
# Example: Firebase authentication with a serverless API
## How to use
### Using `create-next-app`
Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example:
```bash
npx create-next-app --example with-firebase-authentication-serverless with-firebase-authentication-serverless-app
# or
yarn create next-app --example with-firebase-authentication-serverless with-firebase-authentication-serverless-app
```
### Download manually
Download the example:
```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-firebase-authentication-serverless
cd with-firebase-authentication-serverless
```
Set up Firebase:
- Create a project at the [Firebase console](https://console.firebase.google.com/).
- Get your account credentials from the Firebase console at _Project settings > Service accounts_, where you can click on _Generate new private key_ and download the credentials as a json file. It will contain keys such as `project_id`, `client_email` and `client_id`. Set them as environment variables in the `.env` file at the root of this project.
- Get your authentication credentials from the Firebase console under _Project settings > General> Your apps_ Add a new web app if you don't already have one. Under _Firebase SDK snippet_ choose _Config_ to get the configuration as JSON. It will include keys like `apiKey`, `authDomain` and `databaseUrl`. Set the appropriate environment variables in the `.env` file at the root of this project.
- Set the environment variables `SESSION_SECRET_CURRENT` and `SESSION_SECRET_PREVIOUS` in the `.env` file. (These are used by [`cookie-session`](https://github.com/expressjs/cookie-session/#secret).]
Install it and run:
```bash
npm install
npm run dev
# or
yarn
yarn dev
```
Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))
```bash
now
```
After `now` successfully deploys, a URL will for your site will be displayed. Copy that URL and navigate to your Firebase project's Authentication tab. Scroll down in the page to "Authorized domains" and add that URL to the list.
## The idea behind the example
This example includes Firebase authentication and serverless [API routes](https://nextjs.org/docs/api-routes/introduction). On login, the app calls `/api/login`, which stores the user's info (their decoded Firebase token) in a cookie so that it's available server-side in `getInitialProps`. On logout, the app calls `/api/logout` to destroy the cookie.
/* globals window */
import React, { useEffect, useState } from 'react'
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth'
import firebase from 'firebase/app'
import 'firebase/auth'
import initFirebase from '../utils/auth/initFirebase'
// Init the Firebase app.
initFirebase()
const firebaseAuthConfig = {
signInFlow: 'popup',
// Auth providers
// https://github.com/firebase/firebaseui-web#configure-oauth-providers
signInOptions: [
{
provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
requireDisplayName: false,
},
],
signInSuccessUrl: '/',
credentialHelper: 'none',
}
const FirebaseAuth = () => {
// Do not SSR FirebaseUI, because it is not supported.
// https://github.com/firebase/firebaseui-web/issues/213
const [renderAuth, setRenderAuth] = useState(false)
useEffect(() => {
if (typeof window !== 'undefined') {
setRenderAuth(true)
}
}, [])
return (
<div>
{renderAuth ? (
<StyledFirebaseAuth
uiConfig={firebaseAuthConfig}
firebaseAuth={firebase.auth()}
/>
) : null}
</div>
)
}
export default FirebaseAuth
// Responsible for setting environment variables.
// Note: this isn't strictly required for this example – you can
// inline your Firebase config or set environment variables howevever
// else you wish – but it's a convenient way to make sure the private
// key doesn't end up in source control.
const fs = require('fs')
const { NODE_ENV } = process.env
if (!NODE_ENV) {
throw new Error(
'The NODE_ENV environment variable is required but was not specified.'
)
}
// Set env vars from appropiate `.env` files. We're following the
// file structure used in create-react-app and documented in the
// Ruby dotenv. See:
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
const dotEnvPath = './.env'
const dotEnvFiles = [
`${dotEnvPath}.${NODE_ENV}.local`,
`${dotEnvPath}.${NODE_ENV}`,
// Don't include `.env.local` for the test environment.
NODE_ENV !== 'test' && `${dotEnvPath}.local`,
dotEnvPath,
].filter(Boolean)
dotEnvFiles.forEach(dotenvFile => {
if (fs.existsSync(dotenvFile)) {
// eslint-disable-next-line global-require
require('dotenv').config({
path: dotenvFile,
})
}
})
require('./env.js')
module.exports = {
// Public, build-time env vars.
// https://nextjs.org/docs#build-time-configuration
env: {
FIREBASE_AUTH_DOMAIN: process.env.FIREBASE_AUTH_DOMAIN,
FIREBASE_DATABASE_URL: process.env.FIREBASE_DATABASE_URL,
FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID,
FIREBASE_PUBLIC_API_KEY: process.env.FIREBASE_PUBLIC_API_KEY,
},
}
{
"name": "with-firebase-auth-serverless",
"version": "1.0.0",
"scripts": {
"dev": "NODE_ENV=development next dev",
"build": "NODE_ENV=production next build",
"start": "NODE_ENV=production next start"
},
"dependencies": {
"cookie-session": "1.4.0",
"dotenv": "8.2.0",
"firebase": "^7.6.1",
"firebase-admin": "^8.9.0",
"isomorphic-unfetch": "^3.0.0",
"lodash": "4.17.15",
"next": "latest",
"prop-types": "15.7.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-firebaseui": "4.0.0"
},
"devDependencies": {}
}
/* eslint react/no-danger: 0 */
import React from 'react'
import PropTypes from 'prop-types'
import { get } from 'lodash/object'
import Document, { Html, Head, Main, NextScript } from 'next/document'
class CustomDocument extends Document {
render() {
// Store initial props from request data that we need to use again on
// the client. See:
// https://github.com/zeit/next.js/issues/3043#issuecomment-334521241
// https://github.com/zeit/next.js/issues/2252#issuecomment-353992669
// Alternatively, you could use a store, like Redux.
const { AuthUserInfo } = this.props
return (
<Html>
<Head>
<script
id="__MY_AUTH_USER_INFO"
type="application/json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(AuthUserInfo, null, 2),
}}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
CustomDocument.getInitialProps = async ctx => {
// Get the AuthUserInfo object. This is set if the server-rendered page
// is wrapped in the `withAuthUser` higher-order component.
const AuthUserInfo = get(ctx, 'myCustomData.AuthUserInfo', null)
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps, AuthUserInfo }
}
CustomDocument.propTypes = {
AuthUserInfo: PropTypes.shape({
AuthUser: PropTypes.shape({
id: PropTypes.string.isRequired,
email: PropTypes.string.isRequired,
emailVerified: PropTypes.bool.isRequired,
}),
token: PropTypes.string,
}).isRequired,
}
export default CustomDocument
import commonMiddleware from '../../utils/middleware/commonMiddleware'
import { verifyIdToken } from '../../utils/auth/firebaseAdmin'
const handler = (req, res) => {
if (!req.body) {
return res.status(400)
}
const { token } = req.body
// Here, we decode the user's Firebase token and store it in a cookie. Use
// express-session (or similar) to store the session data server-side.
// An alternative approach is to use Firebase's `createSessionCookie`. See:
// https://firebase.google.com/docs/auth/admin/manage-cookies
// Firebase docs:
// "This is a low overhead operation. The public certificates are initially
// queried and cached until they expire. Session cookie verification can be
// done with the cached public certificates without any additional network
// requests."
// However, in a serverless environment, we shouldn't rely on caching, so
// it's possible Firebase's `verifySessionCookie` will make frequent network
// requests in a serverless context.
return verifyIdToken(token)
.then(decodedToken => {
req.session.decodedToken = decodedToken
req.session.token = token
return decodedToken
})
.then(decodedToken => {
return res.status(200).json({ status: true, decodedToken })
})
.catch(error => {
return res.status(500).json({ error })
})
}
export default commonMiddleware(handler)
import commonMiddleware from '../../utils/middleware/commonMiddleware'
const handler = (req, res) => {
// Destroy the session.
// https://github.com/expressjs/cookie-session#destroying-a-session
req.session = null
res.status(200).json({ status: true })
}
export default commonMiddleware(handler)
import React from 'react'
import FirebaseAuth from '../components/FirebaseAuth'
const Auth = () => {
return (
<div>
<p>Sign in</p>
<div>
<FirebaseAuth />
</div>
</div>
)
}
Auth.propTypes = {}
export default Auth
import React from 'react'
import Link from 'next/link'
const Example = props => {
return (
<div>
<p>
This page is static because it does not fetch any data or include the
authed user info.
</p>
<Link href={'/'}>
<a>Home</a>
</Link>
</div>
)
}
Example.displayName = 'Example'
Example.propTypes = {}
Example.defaultProps = {}
export default Example
import React from 'react'
import PropTypes from 'prop-types'
import { get } from 'lodash/object'
import Link from 'next/link'
import Router from 'next/router'
import withAuthUser from '../utils/pageWrappers/withAuthUser'
import withAuthUserInfo from '../utils/pageWrappers/withAuthUserInfo'
import logout from '../utils/auth/logout'
const Index = props => {
const { AuthUserInfo, data } = props
const AuthUser = get(AuthUserInfo, 'AuthUser', null)
const { favoriteFood } = data
return (
<div>
<p>Hi there!</p>
{!AuthUser ? (
<p>
You are not signed in.{' '}
<Link href={'/auth'}>
<a>Sign in</a>
</Link>
</p>
) : (
<div>
<p>You're signed in. Email: {AuthUser.email}</p>
<p
style={{
display: 'inlinelock',
color: 'blue',
textDecoration: 'underline',
cursor: 'pointer',
}}
onClick={async () => {
try {
await logout()
Router.push('/auth')
} catch (e) {
console.error(e)
}
}}
>
Log out
</p>
</div>
)}
<div>
<Link href={'/example'}>
<a>Another example page</a>
</Link>
</div>
<div>
<div>Your favorite food is {favoriteFood}.</div>
</div>
</div>
)
}
// Just an example.
const mockFetchData = async userId => ({
user: {
...(userId && {
id: userId,
}),
},
favoriteFood: 'pizza',
})
Index.getInitialProps = async ctx => {
// Get the AuthUserInfo object. This is set in `withAuthUser.js`.
// The AuthUserInfo object is available on both the server and client.
const AuthUserInfo = get(ctx, 'myCustomData.AuthUserInfo', null)
const AuthUser = get(AuthUserInfo, 'AuthUser', null)
// You can also get the token (e.g., to authorize a request when fetching data)
// const AuthUserToken = get(AuthUserInfo, 'token', null)
// You can fetch data here.
const data = await mockFetchData(get(AuthUser, 'id'))
return {
data,
}
}
Index.displayName = 'Index'
Index.propTypes = {
AuthUserInfo: PropTypes.shape({
AuthUser: PropTypes.shape({
id: PropTypes.string.isRequired,
email: PropTypes.string.isRequired,
emailVerified: PropTypes.bool.isRequired,
}),
token: PropTypes.string,
}),
data: PropTypes.shape({
user: PropTypes.shape({
id: PropTypes.string,
}).isRequired,
favoriteFood: PropTypes.string.isRequired,
}).isRequired,
}
Index.defaultProps = {
AuthUserInfo: null,
}
// Use `withAuthUser` to get the authed user server-side, which
// disables static rendering.
// Use `withAuthUserInfo` to include the authed user as a prop
// to your component.
export default withAuthUser(withAuthUserInfo(Index))
import * as admin from 'firebase-admin'
export const verifyIdToken = token => {
const firebasePrivateKey = process.env.FIREBASE_PRIVATE_KEY
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert({
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
// https://stackoverflow.com/a/41044630/1332513
privateKey: firebasePrivateKey.replace(/\\n/g, '\n'),
}),
databaseURL: process.env.FIREBASE_DATABASE_URL,
})
}
return admin
.auth()
.verifyIdToken(token)
.catch(error => {
throw error
})
}
// From:
// https://github.com/zeit/next.js/blob/canary/examples/with-firebase-authentication/pages/index.js
import fetch from 'isomorphic-unfetch'
export const setSession = user => {
// Log in.
if (user) {
return user.getIdToken().then(token => {
return fetch('/api/login', {
method: 'POST',
// eslint-disable-next-line no-undef
headers: new Headers({ 'Content-Type': 'application/json' }),
credentials: 'same-origin',
body: JSON.stringify({ token }),
})
})
}
// Log out.
return fetch('/api/logout', {
method: 'POST',
credentials: 'same-origin',
})
}
import React, { useEffect, useState } from 'react'
import firebase from 'firebase/app'
import 'firebase/auth'
import initFirebase from './initFirebase'
import { setSession } from './firebaseSessionHandler'
import { createAuthUserInfo } from './user'
initFirebase()
// https://benmcmahen.com/using-firebase-with-react-hooks/
// Defaults to empty AuthUserInfo object.
export const AuthUserInfoContext = React.createContext(createAuthUserInfo())
export const useAuthUserInfo = () => {
return React.useContext(AuthUserInfoContext)
}
// Returns a Firebase JS SDK user object.
export const useFirebaseAuth = () => {
const [state, setState] = useState(() => {
const user = firebase.auth().currentUser
return {
initializing: !user,
user,
}
})
function onChange(user) {
setState({ initializing: false, user })
// Call server to update session.
setSession(user)
}
useEffect(() => {
// Listen for auth state changes.
const unsubscribe = firebase.auth().onAuthStateChanged(onChange)
// Unsubscribe to the listener when unmounting.
return () => unsubscribe()
}, [])
return state
}
import firebase from 'firebase/app'
import 'firebase/auth'
const config = {
apiKey: process.env.FIREBASE_PUBLIC_API_KEY,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.FIREBASE_DATABASE_URL,
projectId: process.env.FIREBASE_PROJECT_ID,
}
export default () => {
if (!firebase.apps.length) {
firebase.initializeApp(config)
}
}
/* globals window */
import firebase from 'firebase/app'
import 'firebase/auth'
export default async () => {
return firebase
.auth()
.signOut()
.then(() => {
// Sign-out successful.
if (typeof window !== 'undefined') {
// Remove the server-side rendered user data element. See:
// https://github.com/zeit/next.js/issues/2252#issuecomment-353992669
try {
const elem = window.document.getElementById('__MY_AUTH_USER_INFO')
elem.parentNode.removeChild(elem)
} catch (e) {
console.error(e)
}
}
return true
})
.catch(e => {
console.error(e)
return false
})
}
import { get, has } from 'lodash/object'
/**
* Take the user object from Firebase (from either the Firebase admin SDK or
* or the client-side Firebase JS SDK) and return a consistent AuthUser object.
* @param {Object} firebaseUser - A decoded Firebase user token or JS SDK
* Firebase user object.
* @return {Object|null} AuthUser - The user object.
* @return {String} AuthUser.id - The user's ID
* @return {String} AuthUser.email - The user's email
* @return {Boolean} AuthUser.emailVerified - Whether the user has verified their email
*/
export const createAuthUser = firebaseUser => {
if (!firebaseUser || !firebaseUser.uid) {
return null
}
return {
id: get(firebaseUser, 'uid'),
email: get(firebaseUser, 'email'),
emailVerified: has(firebaseUser, 'emailVerified')
? get(firebaseUser, 'emailVerified') // Firebase JS SDK
: get(firebaseUser, 'email_verified'), // Firebase admin SDK
}
}
/**
* Create an object with an AuthUser object and AuthUserToken value.
* @param {Object} firebaseUser - A decoded Firebase user token or JS SDK
* Firebase user object.
* @param {String} firebaseToken - A Firebase auth token string.
* @return {Object|null} AuthUserInfo - The auth user info object.
* @return {String} AuthUserInfo.AuthUser - An AuthUser object (see
* `createAuthUser` above).
* @return {String} AuthUser.token - The user's encoded Firebase token.
*/
export const createAuthUserInfo = ({
firebaseUser = null,
token = null,
} = {}) => {
return {
AuthUser: createAuthUser(firebaseUser),
token,
}
}
import cookieSession from './cookieSession'
import cookieSessionRefresh from './cookieSessionRefresh'
// Load environment variables.
require('../../env')
export default handler => cookieSession(cookieSessionRefresh(handler))
import cookieSession from 'cookie-session'
export const addSession = (req, res) => {
// Ensure that session secrets are set.
if (
!(process.env.SESSION_SECRET_CURRENT && process.env.SESSION_SECRET_PREVIOUS)
) {
throw new Error(
'Session secrets must be set as env vars `SESSION_SECRET_CURRENT` and `SESSION_SECRET_PREVIOUS`.'
)
}
// An array is useful for rotating secrets without invalidating old sessions.
// The first will be used to sign cookies, and the rest to validate them.
// https://github.com/expressjs/cookie-session#keys
const sessionSecrets = [
process.env.SESSION_SECRET_CURRENT,
process.env.SESSION_SECRET_PREVIOUS,
]
// Example:
// https://github.com/billymoon/micro-cookie-session
const includeSession = cookieSession({
keys: sessionSecrets,
// TODO: set other options, such as "secure", "sameSite", etc.
// https://github.com/expressjs/cookie-session#cookie-options
maxAge: 604800000, // week
httpOnly: true,
overwrite: true,
})
includeSession(req, res, () => {})
}
export default handler => (req, res) => {
try {
addSession(req, res)
} catch (e) {
return res.status(500).json({ error: 'Could not get user session.' })
}
return handler(req, res)
}
// Update a value in the cookie so that the set-cookie will be sent.
// Only changes every minute so that it's not sent with every request.
// https://github.com/expressjs/cookie-session#extending-the-session-expiration
export default handler => (req, res) => {
if (req.session) {
req.session.nowInMinutes = Math.floor(Date.now() / 60e3)
}
handler(req, res)
}
/* eslint react/jsx-props-no-spreading: 0 */
import React from 'react'
import PropTypes from 'prop-types'
import { get, set } from 'lodash/object'
import { AuthUserInfoContext, useFirebaseAuth } from '../auth/hooks'
import { createAuthUser, createAuthUserInfo } from '../auth/user'
// Gets the authenticated user from the Firebase JS SDK, when client-side,
// or from the request object, when server-side. Add the AuthUserInfo to
// context.
export default ComposedComponent => {
const WithAuthUserComp = props => {
const { AuthUserInfo, ...otherProps } = props
// We'll use the authed user from client-side auth (Firebase JS SDK)
// when available. On the server side, we'll use the authed user from
// the session. This allows us to server-render while also using Firebase's
// client-side auth functionality.
const { user: firebaseUser } = useFirebaseAuth()
const AuthUserFromClient = createAuthUser(firebaseUser)
const { AuthUser: AuthUserFromSession, token } = AuthUserInfo
const AuthUser = AuthUserFromClient || AuthUserFromSession || null
return (
<AuthUserInfoContext.Provider value={{ AuthUser, token }}>
<ComposedComponent {...otherProps} />
</AuthUserInfoContext.Provider>
)
}
WithAuthUserComp.getInitialProps = async ctx => {
const { req, res } = ctx
// Get the AuthUserInfo object.
let AuthUserInfo
if (typeof window === 'undefined') {
// If server-side, get AuthUserInfo from the session in the request.
// Don't include server middleware in the client JS bundle. See:
// https://arunoda.me/blog/ssr-and-server-only-modules
const { addSession } = require('../middleware/cookieSession')
addSession(req, res)
AuthUserInfo = createAuthUserInfo({
firebaseUser: get(req, 'session.decodedToken', null),
token: get(req, 'session.token', null),
})
} else {
// If client-side, get AuthUserInfo from stored data. We store it
// in _document.js. See:
// https://github.com/zeit/next.js/issues/2252#issuecomment-353992669
try {
const jsonData = JSON.parse(
window.document.getElementById('__MY_AUTH_USER_INFO').textContent
)
if (jsonData) {
AuthUserInfo = jsonData
} else {
// Use the default (unauthed) user info if there's no data.
AuthUserInfo = createAuthUserInfo()
}
} catch (e) {
// If there's some error, use the default (unauthed) user info.
AuthUserInfo = createAuthUserInfo()
}
}
// Explicitly add the user to a custom prop in the getInitialProps
// context for ease of use in child components.
set(ctx, 'myCustomData.AuthUserInfo', AuthUserInfo)
// Evaluate the composed component's getInitialProps().
let composedInitialProps = {}
if (ComposedComponent.getInitialProps) {
composedInitialProps = await ComposedComponent.getInitialProps(ctx)
}
return {
...composedInitialProps,
AuthUserInfo,
}
}
WithAuthUserComp.displayName = `WithAuthUser(${ComposedComponent.displayName})`
WithAuthUserComp.propTypes = {
AuthUserInfo: PropTypes.shape({
AuthUser: PropTypes.shape({
id: PropTypes.string.isRequired,
email: PropTypes.string.isRequired,
emailVerified: PropTypes.bool.isRequired,
}),
token: PropTypes.string,
}).isRequired,
}
WithAuthUserComp.defaultProps = {}
return WithAuthUserComp
}
/* eslint react/jsx-props-no-spreading: 0 */
import React from 'react'
import PropTypes from 'prop-types'
import { get } from 'lodash/object'
import { AuthUserInfoContext } from '../auth/hooks'
// Provides an AuthUserInfo prop to the composed component.
export default ComposedComponent => {
const WithAuthUserInfoComp = props => {
const { AuthUserInfo: AuthUserInfoFromSession, ...otherProps } = props
return (
<AuthUserInfoContext.Consumer>
{AuthUserInfo => (
<ComposedComponent
{...otherProps}
AuthUserInfo={AuthUserInfo || AuthUserInfoFromSession}
/>
)}
</AuthUserInfoContext.Consumer>
)
}
WithAuthUserInfoComp.getInitialProps = async ctx => {
const AuthUserInfo = get(ctx, 'myCustomData.AuthUserInfo', null)
// Evaluate the composed component's getInitialProps().
let composedInitialProps = {}
if (ComposedComponent.getInitialProps) {
composedInitialProps = await ComposedComponent.getInitialProps(ctx)
}
return {
...composedInitialProps,
AuthUserInfo,
}
}
WithAuthUserInfoComp.displayName = `WithAuthUserInfo(${ComposedComponent.displayName})`
WithAuthUserInfoComp.propTypes = {
AuthUserInfo: PropTypes.shape({
AuthUser: PropTypes.shape({
id: PropTypes.string.isRequired,
email: PropTypes.string.isRequired,
emailVerified: PropTypes.bool.isRequired,
}),
token: PropTypes.string,
}),
}
WithAuthUserInfoComp.defaultProps = {}
return WithAuthUserInfoComp
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册