Simple Firebase Authentication for All Next.js Rendering Strategies

next-firebase-auth

Simple Firebase authentication for all Next.js rendering strategies.

What It Does

This package makes it simple to get the authenticated Firebase user and ID token during both client-side and server-side rendering (SSR).

     🌍   Support for all Next.js rendering strategies

     πŸ”’   Signed, secure, HTTP-only cookies by default

     πŸ†”   Server-side access to the user's Firebase ID token

     πŸͺ   Built-in cookie management

     ↩️   Built-in support for redirecting based on the user's auth status

We treat the Firebase JS SDK as the source of truth for auth status. When the user signs in, we call an endpoint to generate a refresh token and store the user info, ID token, and refresh token in cookies. Future requests to SSR pages receive the user info and ID token from cookies, refreshing the ID token as needed. When the user logs out, we unset the cookies.

Demo

See a live demo of the example app.

When (Not) to Use this Package

Depending on your app's needs, other approaches might work better for you.

If your app only uses static pages or doesn't need the Firebase user for SSR, use the Firebase JS SDK directly to load the user on the client side.

  • Pros: It's simpler and removes this package as a dependency.
  • Cons: You will not have access to the Firebase user when you use getServerSideProps.

If your app needs the Firebase user for SSR (but does not need the ID token server side), you could consider one of these approaches:

  1. On the client, set a JavaScript cookie with the Firebase user information once the Firebase JS SDK loads.
    • Pros: You won't need login/logout API endpoints. You can structure the authed user data however you'd like.
    • Cons: The cookie will be unsigned and accessible to other JavaScript, making this approach less secure. You won't always have access to the Firebase ID token server side, so you won't be able to access other Firebase services. (Note that you can set the ID token in the cookie, but it will expire after an hour and be invalid for future server-side-rendered pages.)
  2. Use Firebase's session cookies.
    • Pros: It removes this package as a dependency.
    • Cons: You won't have access to the Firebase ID token server side, so you won't be able to access other Firebase services. You'll need to implement the logic for verifying the session and managing the session state.

If your app needs a generalized authentication solutionβ€”not specifically Firebase authenticationβ€”you could consider using NextAuth.js. NextAuth.js does not use Firebase authentication but supports a wide variety of identity providers, including Google. Read more here about the differences between next-firebase-auth and NextAuth.js to see which works best for your needs.

This package will likely be helpful if you expect to use both static pages and SSR or if you need access to Firebase ID tokens server side.

A quick note on what this package does not do:

  • It does not provide authentication UI. Consider firebaseui-web or build your own.
  • It does not extend Firebase functionality beyond providing universal access to the authed user. Use the Firebase admin SDK and Firebase JS SDK for any other needs.

Get Started

Install:

Firebase v8: yarn add next-firebase-auth or npm i next-firebase-auth

Firebase v9+: yarn add next-firebase-auth@canary or npm i next-firebase-auth@canary

⚠️ If you're using v9 of the Firebase JS SDK, use next-firebase-auth@canary. This is an unstable v1 prerelease. Track progress on v1 in this issue.

Make sure peer dependencies are also installed:

yarn add firebase firebase-admin next react react-dom

Create a module to initialize next-firebase-auth.

Example config:

See config documentation for details

// ./initAuth.js
import { init } from 'next-firebase-auth'

const initAuth = () => {
  init({
    authPageURL: '/auth',
    appPageURL: '/',
    loginAPIEndpoint: '/api/login', // required
    logoutAPIEndpoint: '/api/logout', // required
    onLoginRequestError: (err) => {
      console.error(err)
    },
    onLogoutRequestError: (err) => {
      console.error(err)
    },
    firebaseAuthEmulatorHost: 'localhost:9099',
    firebaseAdminInitConfig: {
      credential: {
        projectId: 'my-example-app-id',
        clientEmail: 'example-abc123@my-example-app.iam.gserviceaccount.com',
        // The private key must not be accessible on the client side.
        privateKey: process.env.FIREBASE_PRIVATE_KEY,
      },
      databaseURL: 'https://my-example-app.firebaseio.com',
    },
    // Use application default credentials (takes precedence over firebaseAdminInitConfig if set)
    // useFirebaseAdminDefaultCredential: true,
    firebaseClientInitConfig: {
      apiKey: 'MyExampleAppAPIKey123', // required
      authDomain: 'my-example-app.firebaseapp.com',
      databaseURL: 'https://my-example-app.firebaseio.com',
      projectId: 'my-example-app-id',
    },
    cookies: {
      name: 'ExampleApp', // required
      // Keys are required unless you set `signed` to `false`.
      // The keys cannot be accessible on the client side.
      keys: [
        process.env.COOKIE_SECRET_CURRENT,
        process.env.COOKIE_SECRET_PREVIOUS,
      ],
      httpOnly: true,
      maxAge: 12 * 60 * 60 * 24 * 1000, // twelve days
      overwrite: true,
      path: '/',
      sameSite: 'strict',
      secure: true, // set this to false in local (non-HTTPS) development
      signed: true,
    },
    onVerifyTokenError: (err) => {
      console.error(err)
    },
    onTokenRefreshError: (err) => {
      console.error(err)
    },
  })
}

export default initAuth

Set the private environment variables FIREBASE_PRIVATE_KEY, COOKIE_SECRET_CURRENT, and COOKIE_SECRET_PREVIOUS in .env.local. If you have enabled the Firebase Authentication Emulator, you will also need to set the FIREBASE_AUTH_EMULATOR_HOST environment variable.

Initialize next-firebase-auth in _app.js:

// ./pages/_app.js
import initAuth from '../initAuth' // the module you created above

initAuth()

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp

Create login and logout API endpoints that set auth cookies:

// ./pages/api/login
import { setAuthCookies } from 'next-firebase-auth'
import initAuth from '../../initAuth' // the module you created above

initAuth()

const handler = async (req, res) => {
  try {
    await setAuthCookies(req, res)
  } catch (e) {
    return res.status(500).json({ error: 'Unexpected error.' })
  }
  return res.status(200).json({ success: true })
}

export default handler
// ./pages/api/logout
import { unsetAuthCookies } from 'next-firebase-auth'
import initAuth from '../../initAuth' // the module you created above

initAuth()

const handler = async (req, res) => {
  try {
    await unsetAuthCookies(req, res)
  } catch (e) {
    return res.status(500).json({ error: 'Unexpected error.' })
  }
  return res.status(200).json({ success: true })
}

export default handler

Finally, use the authenticated user in a page:

// ./pages/demo
import React from 'react'
import {
  useAuthUser,
  withAuthUser,
  withAuthUserTokenSSR,
} from 'next-firebase-auth'

const Demo = () => {
  const AuthUser = useAuthUser()
  return (
    <div>
      <p>Your email is {AuthUser.email ? AuthUser.email : 'unknown'}.</p>
    </div>
  )
}

// Note that this is a higher-order function.
export const getServerSideProps = withAuthUserTokenSSR()()

export default withAuthUser()(Demo)

API


init(config)

Initializes next-firebase-auth, taking a config object. Must be called before calling any other method.

withAuthUser({ ...options })(PageComponent)

A higher-order function to provide the AuthUser context to a component. Use this with any Next.js page that will access the authed user via the useAuthUser hook. Optionally, it can client-side redirect based on the user's auth status.

It accepts the following options:

OptionDescriptionDefault
whenAuthedThe action to take if the user is authenticated. One of AuthAction.RENDER or AuthAction.REDIRECT_TO_APP.AuthAction.RENDER
whenAuthedBeforeRedirectThe action to take while waiting for the browser to redirect. Relevant when the user is authenticated and whenAuthed is set to AuthAction.REDIRECT_TO_APP. One of: AuthAction.RENDER or AuthAction.SHOW_LOADER or AuthAction.RETURN_NULL.AuthAction.RETURN_NULL
whenUnauthedBeforeInitThe action to take if the user is not authenticated but the Firebase client JS SDK has not yet initialized. One of: AuthAction.RENDER, AuthAction.REDIRECT_TO_LOGIN, AuthAction.SHOW_LOADER.AuthAction.RENDER
whenUnauthedAfterInitThe action to take if the user is not authenticated and the Firebase client JS SDK has already initialized. One of: AuthAction.RENDER, AuthAction.REDIRECT_TO_LOGIN.AuthAction.RENDER
appPageURLThe redirect destination URL when we should redirect to the app. A PageURL.config.appPageURL
authPageURLThe redirect destination URL when we should redirect to the login page. A PageURL.config.authPageURL
LoaderComponentThe component to render when the user is unauthed and whenUnauthedBeforeInit is set to AuthAction.SHOW_LOADER.null

For example, this page will redirect to the login page if the user is not authenticated:

import { withAuthUser, AuthAction } from 'next-firebase-auth'

const DemoPage = () => <div>My demo page</div>

export default withAuthUser({
  whenUnauthedAfterInit: AuthAction.REDIRECT_TO_LOGIN,
  authPageURL: '/my-login-page/',
})(DemoPage)

Here's an example of a login page that shows a loader until Firebase is initialized, then redirects to the app if the user is already logged in:

import { withAuthUser, AuthAction } from 'next-firebase-auth'

const MyLoader = () => <div>Loading...</div>

const LoginPage = () => <div>My login page</div>

export default withAuthUser({
  whenAuthed: AuthAction.REDIRECT_TO_APP,
  whenUnauthedBeforeInit: AuthAction.SHOW_LOADER,
  whenUnauthedAfterInit: AuthAction.RENDER,
  LoaderComponent: MyLoader,
})(LoginPage)

For TypeScript usage, take a look here.

withAuthUserTokenSSR({ ...options })(getServerSidePropsFunc = ({ AuthUser }) => {})

A higher-order function that wraps a Next.js pages's getServerSideProps function to provide the AuthUser context during server-side rendering. Optionally, it can server-side redirect based on the user's auth status. A wrapped function is optional; if provided, it will be called with a context object that contains an AuthUser property.

It accepts the following options:

OptionDescriptionDefault
whenAuthedThe action to take if the user is authenticated. Either AuthAction.RENDER or AuthAction.REDIRECT_TO_APP.AuthAction.RENDER
whenUnauthedThe action to take if the user is not authenticated. Either AuthAction.RENDER or AuthAction.REDIRECT_TO_LOGIN.AuthAction.RENDER
appPageURLThe redirect destination URL when we should redirect to the app. A PageURL.config.appPageURL
authPageURLThe redirect destination URL when we should redirect to the login page. A PageURL.config.authPageURL

For example, this page will SSR for authenticated users, fetching props using their Firebase ID token, and will server-side redirect to the login page if the user is not authenticated:

import { withAuthUser, AuthAction } from 'next-firebase-auth'

const DemoPage = ({ thing }) => <div>The thing is: {thing}</div>

export const getServerSideProps = withAuthUserTokenSSR({
  whenUnauthed: AuthAction.REDIRECT_TO_LOGIN,
})(async ({ AuthUser }) => {
  // Optionally, get other props.
  const token = await AuthUser.getIdToken()
  const response = await fetch('/api/my-endpoint', {
    method: 'GET',
    headers: {
      Authorization: token,
    },
  })
  const data = await response.json()
  return {
    props: {
      thing: data.thing,
    },
  }
})

export default withAuthUser()(DemoPage)

withAuthUserSSR({ ...options })(getServerSidePropsFunc = ({ AuthUser }) => {})

Behaves nearly identically to withAuthUserTokenSSR, with one key difference: it does not validate an ID token. Instead, it simply uses the AuthUser data from a cookie. Consequently:

  • It does not provide an ID token on the server side. The AuthUser provided via context will resolve to null when you call AuthUser.getIdToken().
  • It does not need to make a network request to refresh an expired ID token, so it will, on average, be faster than withAuthUserTokenSSR.
  • It does not check for token revocation. If you need verification that the user's credentials haven't been revoked, you should always use withAuthUserTokenSSR.

⚠️ Do not use this when cookies.signed is set to false. Doing so is a potential security risk, because the authed user cookie values could be modified by the client.

This takes the same options as withAuthUserTokenSSR.

useAuthUser()

A hook that returns the current AuthUser. To use this, the Next.js page must be wrapped in withAuthUser. If the user is not authenticated, useAuthUser will return an AuthUser instance with a null id.

For example:

import { useAuthUser, withAuthUser } from 'next-firebase-auth'

const Demo = () => {
  const AuthUser = useAuthUser()
  return (
    <div>
      <p>Your email is {AuthUser.email ? AuthUser.email : 'unknown'}.</p>
    </div>
  )
}

export default withAuthUser()(Demo)

setAuthCookies(req, res)

Sets cookies to store the authenticated user's info. Call this from your "login" API endpoint.

Cookies are managed with cookies. See the config for cookie options.

The req argument should be an IncomingMessage / Next.js request object. The res argument should be a ServerResponse / Next.js response object. It requires that the Authorization request header be set to the Firebase user ID token, which this package handles automatically.

This can only be called on the server side.

unsetAuthCookies(req, res)

Unsets (expires) the auth cookies. Call this from your "logout" API endpoint.

The req argument should be an IncomingMessage / Next.js request object. The res argument should be a ServerResponse / Next.js response object.

This can only be called on the server side.

verifyIdToken(token) => Promise<AuthUser>

Verifies a Firebase ID token and resolves to an AuthUser instance. This serves a similar purpose as Firebase admin SDK's verifyIdToken.

AuthAction

An object that defines rendering/redirecting options for withAuthUser and withAuthUserTokenSSR. See AuthAction.

getFirebaseAdmin() => FirebaseAdmin

Added in v0.13.1

A convenience function that returns the configured Firebase admin module.

This can only be called from the server side. It will throw an error if called from the client side.

For example:

import { getFirebaseAdmin } from 'next-firebase-auth'
// ...other imports

const Artist = ({ artists }) => {
  return (
    <ul>
      {artists.map((artist) => (
        <li>{artist.name}</li>
      ))}
    </ul>
  )
}

export async function getServerSideProps({ params: { id } }) {
  const db = getFirebaseAdmin().firestore()
  const doc = await db.collection('artists').get()
  return {
    props: {
      artists: artists.docs.map((a) => {
        return { ...a.data(), key: a.id }
      }),
    },
  }
}

export default withAuthUser()(Artist)

Config

See an example config here. Provide the config when you call init.

authPageURL

String|Function|Object – a PageURL

The default URL to navigate to when withAuthUser or withAuthUserTokenSSR need to redirect to login. Optional unless using the AuthAction.REDIRECT_TO_LOGIN auth action.

appPageURL

String|Function|Object – a PageURL

The default URL to navigate to when withAuthUser or withAuthUserTokenSSR need to redirect to the app. Optional unless using the AuthAction.REDIRECT_TO_APP auth action.

loginAPIEndpoint

String

The API endpoint this module will call when the auth state changes for an authenticated Firebase user.

Required unless a custom tokenChangedHandler is set, in which case it cannot be defined.

logoutAPIEndpoint

String

The API endpoint this module will call when the auth state changes for an unauthenticated Firebase user.

Required unless a custom tokenChangedHandler is set, in which case it cannot be defined.

onLoginRequestError

Added in v0.14.0

Function (optional)

A handler called if the login API endpoint returns a non-200 response. If a handler is not defined, this library will throw on any non-200 responses.

Not used or allowed if a custom tokenChangedHandler is set.

onLogoutRequestError

Added in v0.14.0

Function (optional)

A handler called if the logout API endpoint returns a non-200 response. If a handler is not defined, this library will throw on any non-200 responses.

Not used or allowed if a custom tokenChangedHandler is set.

tokenChangedHandler

Function

A callback that runs when the auth state changes for a particular user. Use this if you want to customize how your client-side app calls your login/logout API endpoints (for example, to use a custom fetcher or add custom headers). tokenChangedHandler receives an AuthUser as an argument and is called when the user's ID token changes, similarly to Firebase's onIdTokenChanged event.

If this callback is specified, user is responsible for:

  1. Calling their login/logout endpoints depending on the user's auth state.
  2. Passing the user's ID token in the Authorization header
  3. Ensuring it allows the request to set cookies.

See the default handler for guidance.

firebaseAuthEmulatorHost

String

The host and port for the local Firebase Auth Emulator. If this value is set, the auth emulator will be initialized with the provided host and port.

Must match the value of the FIREBASE_AUTH_EMULATOR_HOST environment variable, e.g., localhost:9099.

firebaseAdminInitConfig

Object

Configuration passed to firebase-admin's initializeApp. It should contain a credential property (a plain object) and a databaseURL property. Required unless you initialize firebase-admin yourself before initializing next-firebase-auth.

The firebaseAdminInitConfig.credential.privateKey cannot be defined on the client side and should live in a secret environment variable.

ℹ️ Using Vercel? See adding a private key to Vercel for guidance.

useFirebaseAdminDefaultCredential

Boolean

When true, firebase-admin will implicitly find your hosting environment service account during initializeApp. This is applicable for both Firebase, and Google Cloud Platform, and recommended over adding service account key to the code via file path or direct value.

Note: To setup firebase-admin, either firebaseAdminInitConfig or useFirebaseAdminDefaultCredential must be provided. Using the default credentials will override values passed to firebaseAdminInitConfig.credential if both are presented.

firebaseClientInitConfig

Object

Configuration passed to the Firebase JS SDK's initializeApp. The firebaseClientInitConfig.apiKey value is always required. Other properties are required unless you initialize the firebase app yourself before initializing next-firebase-auth.

cookies

Object

Settings used for auth cookies. We use cookies to manage cookies.

Properties include:

  • name: Used as a base for cookie names: if name is set to "MyExample", cookies will be named MyExample.AuthUser and MyExample.AuthUserTokens (plus MyExample.AuthUser.sig and MyExample.AuthUserTokens.sig if cookies are signed). Required.
  • keys: An array of strings that will be used to sign cookies; for instance, ['xD$WVv3qrP3ywY', '2x6#msoUeNhVHr']. As these strings are secrets, provide them via secret environment variables, such as [ process.env.COOKIE_SECRET_CURRENT, process.env.COOKIE_SECRET_PREVIOUS ]. The keys array is passed to the Keygrip constructor as described in the cookies package. Required unless signed is set to false.
  • All options for cookies.set.

The keys value cannot be defined on the client side and should live in a secret environment variable.

For security, the maxAge value must be two weeks or less. Note that maxAge is defined in milliseconds.

Note: The cookies' expirations will be extended automatically when the user loads the Firebase JS SDK.

The Firebase JS SDK is the source of truth for authentication, so if the cookies expire but the user is still authed with Firebase, the cookies will be automatically set again when the user loads the Firebase JS SDKβ€”but the user will not be authed during SSR on that first request.

onVerifyTokenError

Added in v0.14.0

Function (optional)

Error handler that will be called if there's an unexpected error while verifying the user's ID token server side. It will receive a Firebase auth error.

This library will not throw when it cannot verify an ID token. Instead, it will provide an unauthenticated user to the app. It will typically handle common auth-related errors such as auth/id-token-expired and auth/user-disabled without throwing. See #366 and #174 for additional background.

onTokenRefreshError

Added in v0.14.0

Function (optional)

Error handler that will be called if there's an unexpected error while refreshing the user's ID token server side.

This library will not throw when it cannot refresh an ID token. Instead, it will provide an unauthenticated user to the app. See #366 and #174 for additional background.

Types

AuthAction

Defines actions to take depending on a user's auth status, using the following constants:

AuthAction.RENDER: render the child component

AuthAction.SHOW_LOADER: show a loader component

AuthAction.RETURN_NULL: return null instead of any component

AuthAction.REDIRECT_TO_LOGIN: redirect to the login page

AuthAction.REDIRECT_TO_APP: redirect to the app

AuthUser

The auth user object is used across server-side and client-side contexts. This is a normalized representation of a Firebase user.

id - String|null

The Firebase user's ID, or null if the user is not authenticated.

email - String|null

The Firebase user's email address, or null if the user has no email address.

emailVerified - Boolean

Whether the user's email address is verified.

phoneNumber - String|null

Added in v0.13.1

The Firebase user's phone number, or null if the user has no phone number.

displayName - String|null

Added in v0.13.1

The Firebase user's display name, or null if the user has no display name.

photoURL - String|null

Added in v0.13.1

The Firebase user's photo URL, or null if the user has no photo URL.

claims - Object

Added in v0.13.0

Any custom Firebase claims.

getIdToken - Function => Promise<String|null>

An async function that resolves to a valid Firebase ID token string, or null if no valid token is available.

clientInitialized - Boolean

Whether the Firebase JS SDK has initialized. If true, we are no longer using any user info from server-side props.

firebaseUser - FirebaseUser|null

The user from the Firebase JS SDK, if it has been initialized. Otherwise, null.

signOut - Function => Promise<void>

A method that calls Firebase's signOut if the Firebase JS SDK has been initialized. If the SDK has not been initialized, this method is a no-op.

PageURL

String|Function|Object

Used in appPageURL and authPageURL in the config and higher-order components, the PageURL defines a redirect destination URL or path.

It can be a string: /my-url/here/

Or an object:

{
  destination: '/my-url/here/', // Required string: the URL destination of a redirect
  basePath: true, // Optional boolean (defaults to true): whether to use the Next.js base path.
}

Or a function that receives { ctx, AuthUser } and returns a string or RedirectObject:

const redirect = ({ ctx, AuthUser }) => {
  // any custom logic here
  return `/my-url/here/?username=${AuthUser.displayName}`
}

The ctx is the Next.js context value if server side, or undefined if client side.

Examples

Adding a private key to Vercel

There are various ways to add your Firebase private key as an environment variable to Vercel.

Vercel console

In the Vercel console, add the private key in double quotes (screenshot here).

Then, use the private key in your next-firebase-auth config, in the firebaseAdminInitConfig.credential.privateKey property:

privateKey: process.env.FIREBASE_PRIVATE_KEY

Vercel CLI

Via the Vercel CLI, add the private key with double quotes:

vercel secrets add firebase-private-key '"my-key-here"'

Then, use JSON.parse in the firebaseAdminInitConfig.credential.privateKey property:

privateKey: process.env.FIREBASE_PRIVATE_KEY
  ? JSON.parse(process.env.FIREBASE_PRIVATE_KEY)
  : undefined

Alternative formatting

Others have taken different approaches to deal with escaped newline characters in the private key; for example, by using string replacement. This discussion includes other approaches: discussion #95

Using the Firebase Apps

You may want to access the Firebase admin module or Firebase JS SDK.

To use the Firebase admin module, you can use getFirebaseAdmin. (If you prefer, you can instead choose to initialize Firebase yourself prior to initializing next-firebase-auth. Here's some example code with this pattern.)

To use the Firebase JS SDK, simply import Firebase as you normally would. For example:

import firebase from 'firebase/app'
import 'firebase/firestore'
import { useEffect } from 'react'

const Artists = () => {
  const [artists, setArtists] = useState(artists)

  useEffect(() => {
    return firebase
      .firestore()
      .collection('artists')
      .onSnapshot((snap) => {
        if (!snap) {
          return
        }
        setArtists(snap.docs.map((doc) => ({ ...doc.data(), key: doc.id })))
      })
  }, [])

  return (
    <div>
      {artists.map((artist) => (
        <div>{artist.name}</div>
      ))}
    </div>
  )
}

TypeScript

When using withAuthUser with TypeScript, use TypeScript Generics. For example:

// /pages/demo.tsx
import { VFC } from 'react'
import { Loading } from 'components/Loading/Loading'
import { AuthAction, withAuthUser } from 'next-firebase-auth'

type DemoDataType = {
  name: string
}

const Demo: VFC<DemoDataType> = ({ name }) => {
  return <div>Hello {name}!</div>
}

export default withAuthUser<DemoDataType>({ // <--- Ensure that the type is provided
  whenUnauthedBeforeInit: AuthAction.SHOW_LOADER,
  whenUnauthedAfterInit: AuthAction.REDIRECT_TO_LOGIN,
  LoaderComponent: Loading,
})(Demo)

For a full example with server-side data fetching, see the TypeScript demo page in the example app.

Dynamic Redirects

This package makes it easy to redirect to a login page or app page depending on whether a user is logged in. The destination URLs can also be dynamic: the PageURL can be a function that returns the URL at runtime.

The example app uses this to set a post-login destination URL:

// ./utils/initAuth.js
import { init } from 'next-firebase-auth'
import absoluteUrl from 'next-absolute-url'

const initAuth = () => init({
  // This demonstrates setting a dynamic destination URL when
  // redirecting from app pages. Alternatively, you can simply
  // specify `authPageURL: '/auth-ssr'`.
  authPageURL: ({ ctx }) => {
    const isServerSide = typeof window === 'undefined'
    const origin = isServerSide
      ? absoluteUrl(ctx.req).origin
      : window.location.origin
    const destPath =
      typeof window === 'undefined' ? ctx.resolvedUrl : window.location.href
    const destURL = new URL(destPath, origin)
    return `auth-ssr?destination=${encodeURIComponent(destURL)}`
  },

  // This demonstrates setting a dynamic destination URL when
  // redirecting from auth pages. Alternatively, you can simply
  // specify `appPageURL: '/'`.
  appPageURL: ({ ctx }) => {
    const isServerSide = typeof window === 'undefined'
    const origin = isServerSide
      ? absoluteUrl(ctx.req).origin
      : window.location.origin
    const params = isServerSide
      ? new URL(ctx.req.url, origin).searchParams
      : new URLSearchParams(window.location.search)
    const destinationParamVal = params.get('destination')
      ? decodeURIComponent(params.get('destination'))
      : undefined

    // By default, go to the index page if the destination URL
    // is invalid or unspecified.
    let destURL = '/'
    if (destinationParamVal) {
      // Verify the redirect URL host is allowed.
      // https://owasp.org/www-project-web-security-testing-guide/v41/4-Web_Application_Security_Testing/11-Client_Side_Testing/04-Testing_for_Client_Side_URL_Redirect
      const allowedHosts = ['localhost:3000', 'nfa-example.vercel.app']
      const allowed =
        allowedHosts.indexOf(new URL(destinationParamVal).host) > -1
      if (allowed) {
        destURL = destinationParamVal
      } else {
        // eslint-disable-next-line no-console
        console.warn(
          `Redirect destination host must be one of ${allowedHosts.join(
            ', '
          )}.`
        )
      }
    }
    return destURL
  },

  // ... other config
}

export default initAuth

Testing and Mocking with Jest

To test components wrapped with functions from next-firebase-auth, you will likely want to mock the next-firebase-auth library. This can be achieved using the manual mocks feature of Jest.

It can be helpful to define the default mock behavior of next-firebase-auth across your tests. To do so, stub out the module in a top-level __mocks__ folder (alongside the node_modules in your application):

β”œβ”€β”€ __mocks__
β”‚   └── next-firebase-auth
β”‚       └── index.js
β”œβ”€β”€ node_modules
β”‚   └── ... all your deps
β”œβ”€β”€ src
β”‚   └── ... all your source code

In index.js, export a mock of next-firebase-auth:

const { AuthAction } = require('next-firebase-auth')
const NFAMock = jest.createMockFromModule('next-firebase-auth')

module.exports = {
  ...NFAMock,
  // Customize any mocks as needed.
  init: jest.fn(),
  // For example, in tests, this will automatically render the child component of
  // `withAuthUser`.
  withAuthUser: jest.fn(() => (wrappedComponent) => wrappedComponent),
  useAuthUser: jest.fn(() => ({
    // ... you could return a default AuthUser here
  }),
  AuthAction,
}

See our implementation of this in our tab-web repository for a more robust example.

You will also likely want to have a utility to mock the AuthUser object that is passed around via the hooks and higher-order functions in next-firebase-auth. You might put this in a utils folder in your app.

// Create a mock FirebaseUser instance with the fields that you use.
const mockFirebaseUser = {
  displayName: 'Banana Manana',
  // ... other fields from firebaseUser that you may use
}

/**
 * Build and return a dummy AuthUser instance to use in tests.
 *
 * @arg {boolean} isLoggedIn - Pass `false` to mimic a logged out user.
 * @returns {AuthUserContext} - A mocked AuthUser instance, with 'serialize' added.
 */
const getMockAuthUser = (isLoggedIn = true) => ({
  id: isLoggedIn ? 'abcd1234' : null,
  email: isLoggedIn ? 'banana@banana.com' : null,
  emailVerified: isLoggedIn,
  getIdToken: jest.fn(async () => (isLoggedIn ? 'i_am_a_token' : null)),
  clientInitialized: isLoggedIn,
  firebaseUser: isLoggedIn ? mockFirebaseUser : null,
  signOut: jest.fn(),
  serialize: jest.fn(() => 'serialized_auth_user'),
})

export default getMockAuthUser

Now, you can use and customize the mock behavior in your tests.

If you're modifying higher-order functions, components being tested need to be required inside a beforeEach function or each test case. This is because mocking next-firebase-auth has to happen before your component is imported, because the call to the next-firebase-auth function is part of the default export of your component (e.g., export default withAuthUser()(MyComponent)).

Given the following component:

import React from 'react'
import { useAuthUser, withAuthUser } from 'next-firebase-auth'

function UserDisplayName() {
  const AuthUser = useAuthUser()
  const { displayName = 'anonymous' } = AuthUser.firebaseUser
  return <span>{displayName}</span>
}

export default withAuthUser()(UserDisplayName)

you can write a test suite like this:

import { render, screen } from '@testing-library/react'

// Import the functions that the component module calls, which allows jest to mock them
// in the context of this test run. This allows you to manipulate the return value of each
// function within this test suite.
import { useAuthUser, withAuthUser } from 'next-firebase-auth'

// Import your mock AuthUser generator
import getMockAuthUser from '../../utils/test-utils/get-mock-auth-user'

// Mock all of `next-firebase-auth`. This is *not* necessary if you set up manual mocks,
// because Jest will automatically mock the module in every test.
jest.mock('next-firebase-auth')

describe('UserDisplayName', () => {

  // Create a placeholder for your component that you want to test
  let UserDisplayName

  beforeEach(() => {
    // Mock the functions that your component uses, and import your component before each test.
    useAuthUser.mockReturnValue(getMockAuthUser())
    withAuthUser.mockImplementation(() => (wrappedComponent) => wrappedComponent))
    UserDisplayName = require('./').default
  })

  afterAll(() => {
    // Reset the mocks so that they don't bleed into the next test suite.
    jest.resetAllMocks()
  })

  it('renders the logged in user\'s display name', () => {
    // The default value for the mocked implementation of `withAuthUser` is a fully logged in and verified
    // user. Rendering your component directly with the setup above will result in a "logged in" user being
    // passed to your component.
    render(<UserDisplayName />)
    expect(screen.queryByTest(getMockAuthUser().firebaseUser.displayName)).toBeInTheDocument()
  })

  it('renders "anonymous" when user is not logged in', () => {
    // If you want to test a "logged out" state, then you can mock the function again inside any test,
    // passing a falsy value to `getMockAuthUser`, which will return a logged out AuthUser object.
    useAuthUser.mockReturnValue(getMockAuthUser(false))
    render(<Header />)
    expect(screen.getByText('anonymous')).toBeInTheDocument()
  })
})

Mocks and Typescript

When using TypeScript for your test files, you will have to cast the mocked functions to get access to the mockImplementation and mockReturnValue methods. If we were to rewrite the above example in TS, it might look something like this:

import type { ComponentType } from 'react'
import { render, screen } from '@testing-library/react'

// Import the functions that the component module calls, which allows jest to mock them
// in the context of this test run. This allows you to manipulate the return value of each
// function within this test suite.
import { useAuthUser, withAuthUser } from 'next-firebase-auth'

// Import your mock AuthUser generator
import getMockAuthUser from '../../utils/test-utils/get-mock-auth-user'

// Mock all of `next-firebase-auth`. This is *not* necessary if you set up manual mocks,
// because Jest will automatically mock the module
jest.mock('next-firebase-auth')

describe('UserDisplayName', () => {

  // Create a placeholder for your component that you want to test
  let UserDisplayName: ComponentType

  beforeEach(() => {
    // Mock the functions that your component uses, and import your component before each test.
    (useAuthUser as jest.Mock).mockReturnValue(getMockAuthUser())
    (withAuthUser as jest.Mock).mockImplementation(() => (wrappedComponent: ComponentType) => wrappedComponent: ComponentType))
    UserDisplayName = require('./').default as ComponentType
  })

  afterAll(() => {
    // Reset the mocks so that they don't bleed into the next test suite.
    jest.resetAllMocks()
  })

  it('renders the logged in user\'s display name', () => {
    // The default value for the mocked implementation of `withAuthUser` is a fully logged in and verified
    // user. Rendering your component directly with the setup above will result in a "logged in" user being
    // passed to your component.
    render(<UserDisplayName />)
    expect(screen.getByText(getMockAuthUser().firebaseUser.displayName)).toBeInTheDocument()
  })

  it('renders "anonymous" when user is not logged in', () => {
    // If you want to test a "logged out" state, then you can mock the function again inside any test,
    // passing a falsy value to `getMockAuthUser`, which will return a logged out AuthUser object.
    (useAuthUser as jest.Mock).mockReturnValue(getMockAuthUser(false))
    render(<Header />)
    expect(screen.getByText('anonymous')).toBeInTheDocument()
  })
})

Troubleshooting

Stuck? Search discussions or open your own Q&A discussion describing what you've already tried.

I get the error "[Some setting] should not be available on the client side."

We expect certain sensitive config values to be falsy on the client side (see the config validation code). This is a precaution to make sure developers aren't accidentally bundling something like their Firebase private key with client JS.

To fix this, ensure the config setting is undefined on the client side by logging it to your browser console. You can use Next's .env support to set server-only variables. Never use the NEXT_PUBLIC* prefix for any secret values.

I get an "INVALID_CUSTOM_TOKEN" error when trying to get a refresh token.

This package will call a Google endpoint when it needs to refresh a token server side. You're seeing an error from that request.

To fix this, confirm that your firebaseAdminInitConfig.credential.clientEmail is correct. It should be the email paired with your Firebase private key.

If that doesn't help, try inspecting the custom token to manually validate the values and structure. Some people encounter this problem when their server time is incorrect.

Server-side auth is not working. The user and token are always null when using withAuthUserTokenSSR, but client-side auth works.

If auth is working on the client side but not on the server side, the auth cookies are most likely not set.

To fix this, confirm the auth cookies are set in your browser's dev tools. If they're not set, please check that the secure, sameSite, and path options passed in the next-firebase-auth config make sense for your environment. For example, if you're testing on non-HTTPS localhost, make sure secure is false.

In addition, please double-check your server logs for any errors to ensure the Firebase admin app is initializing properly.

I get an "auth/argument-error" with message "Firebase ID token has invalid signature".

Often, this is caused by an incorrect email in Firebase credentials. Please verify that the email is correct and is from the same Firebase account as your private key, or try generating a new key: https://firebase.google.com/docs/admin/setup

You can try setting up your credentials in the example app to be sure your app code isn't a problem.

In local development, try clearing data/cookies for localhost in case you previously signed in with another Firebase account and still have auth cookies signed by another private key.

You can also try disabling the Firebase Authentication Emulator. Remove firebaseAuthEmulatorHost from your config and remove FIREBASE_AUTH_EMULATOR_HOST from your .env file.

I get the error, "Failed to parse private key: Error: Invalid PEM formatted message."

See adding a private key to Vercel and this discussion on private key formatting.

Limitations & Feedback

We expect some apps will need some features that are not currently available:

  • Supporting custom session logic: Currently, this package doesn't allow using a custom cookie or session module. Some developers may need this flexibility to, for example, keep auth user data in server-side session storage.
  • Setting a single auth cookie: This package currently sets more than one cookie to store authentication state. It's not currently possible to use a single cookie with a customized name: #190

We'd love to hear your feedback on these or other features. Please feel free to open a discussion!

Contributing

We welcome contributions! Please see contributing docs to get started.

Download details:

Author: gladly-team
Source code: https://github.com/gladly-team/next-firebase-auth
License: MIT license

#nextjs #react #reactjs #javascript #auth #firebase

What is GEEK

Buddha Community

Simple Firebase Authentication for All Next.js Rendering Strategies

NBB: Ad-hoc CLJS Scripting on Node.js

Nbb

Not babashka. Node.js babashka!?

Ad-hoc CLJS scripting on Node.js.

Status

Experimental. Please report issues here.

Goals and features

Nbb's main goal is to make it easy to get started with ad hoc CLJS scripting on Node.js.

Additional goals and features are:

  • Fast startup without relying on a custom version of Node.js.
  • Small artifact (current size is around 1.2MB).
  • First class macros.
  • Support building small TUI apps using Reagent.
  • Complement babashka with libraries from the Node.js ecosystem.

Requirements

Nbb requires Node.js v12 or newer.

How does this tool work?

CLJS code is evaluated through SCI, the same interpreter that powers babashka. Because SCI works with advanced compilation, the bundle size, especially when combined with other dependencies, is smaller than what you get with self-hosted CLJS. That makes startup faster. The trade-off is that execution is less performant and that only a subset of CLJS is available (e.g. no deftype, yet).

Usage

Install nbb from NPM:

$ npm install nbb -g

Omit -g for a local install.

Try out an expression:

$ nbb -e '(+ 1 2 3)'
6

And then install some other NPM libraries to use in the script. E.g.:

$ npm install csv-parse shelljs zx

Create a script which uses the NPM libraries:

(ns script
  (:require ["csv-parse/lib/sync$default" :as csv-parse]
            ["fs" :as fs]
            ["path" :as path]
            ["shelljs$default" :as sh]
            ["term-size$default" :as term-size]
            ["zx$default" :as zx]
            ["zx$fs" :as zxfs]
            [nbb.core :refer [*file*]]))

(prn (path/resolve "."))

(prn (term-size))

(println (count (str (fs/readFileSync *file*))))

(prn (sh/ls "."))

(prn (csv-parse "foo,bar"))

(prn (zxfs/existsSync *file*))

(zx/$ #js ["ls"])

Call the script:

$ nbb script.cljs
"/private/tmp/test-script"
#js {:columns 216, :rows 47}
510
#js ["node_modules" "package-lock.json" "package.json" "script.cljs"]
#js [#js ["foo" "bar"]]
true
$ ls
node_modules
package-lock.json
package.json
script.cljs

Macros

Nbb has first class support for macros: you can define them right inside your .cljs file, like you are used to from JVM Clojure. Consider the plet macro to make working with promises more palatable:

(defmacro plet
  [bindings & body]
  (let [binding-pairs (reverse (partition 2 bindings))
        body (cons 'do body)]
    (reduce (fn [body [sym expr]]
              (let [expr (list '.resolve 'js/Promise expr)]
                (list '.then expr (list 'clojure.core/fn (vector sym)
                                        body))))
            body
            binding-pairs)))

Using this macro we can look async code more like sync code. Consider this puppeteer example:

(-> (.launch puppeteer)
      (.then (fn [browser]
               (-> (.newPage browser)
                   (.then (fn [page]
                            (-> (.goto page "https://clojure.org")
                                (.then #(.screenshot page #js{:path "screenshot.png"}))
                                (.catch #(js/console.log %))
                                (.then #(.close browser)))))))))

Using plet this becomes:

(plet [browser (.launch puppeteer)
       page (.newPage browser)
       _ (.goto page "https://clojure.org")
       _ (-> (.screenshot page #js{:path "screenshot.png"})
             (.catch #(js/console.log %)))]
      (.close browser))

See the puppeteer example for the full code.

Since v0.0.36, nbb includes promesa which is a library to deal with promises. The above plet macro is similar to promesa.core/let.

Startup time

$ time nbb -e '(+ 1 2 3)'
6
nbb -e '(+ 1 2 3)'   0.17s  user 0.02s system 109% cpu 0.168 total

The baseline startup time for a script is about 170ms seconds on my laptop. When invoked via npx this adds another 300ms or so, so for faster startup, either use a globally installed nbb or use $(npm bin)/nbb script.cljs to bypass npx.

Dependencies

NPM dependencies

Nbb does not depend on any NPM dependencies. All NPM libraries loaded by a script are resolved relative to that script. When using the Reagent module, React is resolved in the same way as any other NPM library.

Classpath

To load .cljs files from local paths or dependencies, you can use the --classpath argument. The current dir is added to the classpath automatically. So if there is a file foo/bar.cljs relative to your current dir, then you can load it via (:require [foo.bar :as fb]). Note that nbb uses the same naming conventions for namespaces and directories as other Clojure tools: foo-bar in the namespace name becomes foo_bar in the directory name.

To load dependencies from the Clojure ecosystem, you can use the Clojure CLI or babashka to download them and produce a classpath:

$ classpath="$(clojure -A:nbb -Spath -Sdeps '{:aliases {:nbb {:replace-deps {com.github.seancorfield/honeysql {:git/tag "v2.0.0-rc5" :git/sha "01c3a55"}}}}}')"

and then feed it to the --classpath argument:

$ nbb --classpath "$classpath" -e "(require '[honey.sql :as sql]) (sql/format {:select :foo :from :bar :where [:= :baz 2]})"
["SELECT foo FROM bar WHERE baz = ?" 2]

Currently nbb only reads from directories, not jar files, so you are encouraged to use git libs. Support for .jar files will be added later.

Current file

The name of the file that is currently being executed is available via nbb.core/*file* or on the metadata of vars:

(ns foo
  (:require [nbb.core :refer [*file*]]))

(prn *file*) ;; "/private/tmp/foo.cljs"

(defn f [])
(prn (:file (meta #'f))) ;; "/private/tmp/foo.cljs"

Reagent

Nbb includes reagent.core which will be lazily loaded when required. You can use this together with ink to create a TUI application:

$ npm install ink

ink-demo.cljs:

(ns ink-demo
  (:require ["ink" :refer [render Text]]
            [reagent.core :as r]))

(defonce state (r/atom 0))

(doseq [n (range 1 11)]
  (js/setTimeout #(swap! state inc) (* n 500)))

(defn hello []
  [:> Text {:color "green"} "Hello, world! " @state])

(render (r/as-element [hello]))

Promesa

Working with callbacks and promises can become tedious. Since nbb v0.0.36 the promesa.core namespace is included with the let and do! macros. An example:

(ns prom
  (:require [promesa.core :as p]))

(defn sleep [ms]
  (js/Promise.
   (fn [resolve _]
     (js/setTimeout resolve ms))))

(defn do-stuff
  []
  (p/do!
   (println "Doing stuff which takes a while")
   (sleep 1000)
   1))

(p/let [a (do-stuff)
        b (inc a)
        c (do-stuff)
        d (+ b c)]
  (prn d))
$ nbb prom.cljs
Doing stuff which takes a while
Doing stuff which takes a while
3

Also see API docs.

Js-interop

Since nbb v0.0.75 applied-science/js-interop is available:

(ns example
  (:require [applied-science.js-interop :as j]))

(def o (j/lit {:a 1 :b 2 :c {:d 1}}))

(prn (j/select-keys o [:a :b])) ;; #js {:a 1, :b 2}
(prn (j/get-in o [:c :d])) ;; 1

Most of this library is supported in nbb, except the following:

  • destructuring using :syms
  • property access using .-x notation. In nbb, you must use keywords.

See the example of what is currently supported.

Examples

See the examples directory for small examples.

Also check out these projects built with nbb:

API

See API documentation.

Migrating to shadow-cljs

See this gist on how to convert an nbb script or project to shadow-cljs.

Build

Prequisites:

  • babashka >= 0.4.0
  • Clojure CLI >= 1.10.3.933
  • Node.js 16.5.0 (lower version may work, but this is the one I used to build)

To build:

  • Clone and cd into this repo
  • bb release

Run bb tasks for more project-related tasks.

Download Details:
Author: borkdude
Download Link: Download The Source Code
Official Website: https://github.com/borkdude/nbb 
License: EPL-1.0

#node #javascript

How To Set Up Two-Factor Authentication in cPanel

What is 2FA
Two-Factor Authentication (or 2FA as it often referred to) is an extra layer of security that is used to provide users an additional level of protection when securing access to an account.
Employing a 2FA mechanism is a vast improvement in security over the Singe-Factor Authentication method of simply employing a username and password. Using this method, accounts that have 2FA enabled, require the user to enter a one-time passcode that is generated by an external application. The 2FA passcode (usually a six-digit number) is required to be input into the passcode field before access is granted. The 2FA input is usually required directly after the username and password are entered by the client.

#tutorials #2fa #access #account security #authentication #authentication method #authentication token #cli #command line #cpanel #feature manager #google authenticator #one time password #otp #otp authentication #passcode #password #passwords #qr code #security #security code #security policy #security practices #single factor authentication #time-based one-time password #totp #two factor authentication #whm

Eva  Murphy

Eva Murphy

1625674200

Google analytics Setup with Next JS, React JS using Router Events - 14

In this video, we are going to implement Google Analytics to our Next JS application. Tracking page views of an application is very important.

Google analytics will allow us to track analytics information.

Frontend: https://github.com/amitavroy/video-reviews
API: https://github.com/amitavdevzone/video-review-api
App link: https://video-reviews.vercel.app

You can find me on:
Twitter: https://twitter.com/amitavroy7​
Discord: https://discord.gg/Em4nuvQk

#next js #js #react js #react #next #google analytics

What is firebase,firebase bangla tutorial.

https://youtu.be/8BnVKuf1E5M

#firebase #firebase database #c# with firebase #c# with firebase tutorials #c# with firebase database #asp.net with firebase database

Beth  Cooper

Beth Cooper

1659694200

Easy Activity Tracking for Models, Similar to Github's Public Activity

PublicActivity

public_activity provides easy activity tracking for your ActiveRecord, Mongoid 3 and MongoMapper models in Rails 3 and 4.

Simply put: it can record what happens in your application and gives you the ability to present those recorded activities to users - in a similar way to how GitHub does it.

!! WARNING: README for unreleased version below. !!

You probably don't want to read the docs for this unreleased version 2.0.

For the stable 1.5.X readme see: https://github.com/chaps-io/public_activity/blob/1-5-stable/README.md

About

Here is a simple example showing what this gem is about:

Example usage

Tutorials

Screencast

Ryan Bates made a great screencast describing how to integrate Public Activity.

Tutorial

A great step-by-step guide on implementing activity feeds using public_activity by Ilya Bodrov.

Online demo

You can see an actual application using this gem here: http://public-activity-example.herokuapp.com/feed

The source code of the demo is hosted here: https://github.com/pokonski/activity_blog

Setup

Gem installation

You can install public_activity as you would any other gem:

gem install public_activity

or in your Gemfile:

gem 'public_activity'

Database setup

By default public_activity uses Active Record. If you want to use Mongoid or MongoMapper as your backend, create an initializer file in your Rails application with the corresponding code inside:

For Mongoid:

# config/initializers/public_activity.rb
PublicActivity.configure do |config|
  config.orm = :mongoid
end

For MongoMapper:

# config/initializers/public_activity.rb
PublicActivity.configure do |config|
  config.orm = :mongo_mapper
end

(ActiveRecord only) Create migration for activities and migrate the database (in your Rails project):

rails g public_activity:migration
rake db:migrate

Model configuration

Include PublicActivity::Model and add tracked to the model you want to keep track of:

For ActiveRecord:

class Article < ActiveRecord::Base
  include PublicActivity::Model
  tracked
end

For Mongoid:

class Article
  include Mongoid::Document
  include PublicActivity::Model
  tracked
end

For MongoMapper:

class Article
  include MongoMapper::Document
  include PublicActivity::Model
  tracked
end

And now, by default create/update/destroy activities are recorded in activities table. This is all you need to start recording activities for basic CRUD actions.

Optional: If you don't need #tracked but still want the comfort of #create_activity, you can include only the lightweight Common module instead of Model.

Custom activities

You can trigger custom activities by setting all your required parameters and triggering create_activity on the tracked model, like this:

@article.create_activity key: 'article.commented_on', owner: current_user

See this entry http://rubydoc.info/gems/public_activity/PublicActivity/Common:create_activity for more details.

Displaying activities

To display them you simply query the PublicActivity::Activity model:

# notifications_controller.rb
def index
  @activities = PublicActivity::Activity.all
end

And in your views:

<%= render_activities(@activities) %>

Note: render_activities is an alias for render_activity and does the same.

Layouts

You can also pass options to both activity#render and #render_activity methods, which are passed deeper to the internally used render_partial method. A useful example would be to render activities wrapped in layout, which shares common elements of an activity, like a timestamp, owner's avatar etc:

<%= render_activities(@activities, layout: :activity) %>

The activity will be wrapped with the app/views/layouts/_activity.html.erb layout, in the above example.

Important: please note that layouts for activities are also partials. Hence the _ prefix.

Locals

Sometimes, it's desirable to pass additional local variables to partials. It can be done this way:

<%= render_activity(@activity, locals: {friends: current_user.friends}) %>

Note: Before 1.4.0, one could pass variables directly to the options hash for #render_activity and access it from activity parameters. This functionality is retained in 1.4.0 and later, but the :locals method is preferred, since it prevents bugs from shadowing variables from activity parameters in the database.

Activity views

public_activity looks for views in app/views/public_activity.

For example, if you have an activity with :key set to "activity.user.changed_avatar", the gem will look for a partial in app/views/public_activity/user/_changed_avatar.html.(|erb|haml|slim|something_else).

Hint: the "activity." prefix in :key is completely optional and kept for backwards compatibility, you can skip it in new projects.

If you would like to fallback to a partial, you can utilize the fallback parameter to specify the path of a partial to use when one is missing:

<%= render_activity(@activity, fallback: 'default') %>

When used in this manner, if a partial with the specified :key cannot be located it will use the partial defined in the fallback instead. In the example above this would resolve to public_activity/_default.html.(|erb|haml|slim|something_else).

If a view file does not exist then ActionView::MisingTemplate will be raised. If you wish to fallback to the old behaviour and use an i18n based translation in this situation you can specify a :fallback parameter of text to fallback to this mechanism like such:

<%= render_activity(@activity, fallback: :text) %>

i18n

Translations are used by the #text method, to which you can pass additional options in form of a hash. #render method uses translations when view templates have not been provided. You can render pure i18n strings by passing {display: :i18n} to #render_activity or #render.

Translations should be put in your locale .yml files. To render pure strings from I18n Example structure:

activity:
  article:
    create: 'Article has been created'
    update: 'Someone has edited the article'
    destroy: 'Some user removed an article!'

This structure is valid for activities with keys "activity.article.create" or "article.create". As mentioned before, "activity." part of the key is optional.

Testing

For RSpec you can first disable public_activity and add require helper methods in the rails_helper.rb with:

#rails_helper.rb
require 'public_activity/testing'

PublicActivity.enabled = false

In your specs you can then blockwise decide whether to turn public_activity on or off.

# file_spec.rb
PublicActivity.with_tracking do
  # your test code goes here
end

PublicActivity.without_tracking do
  # your test code goes here
end

Documentation

For more documentation go here

Common examples

Set the Activity's owner to current_user by default

You can set up a default value for :owner by doing this:

  1. Include PublicActivity::StoreController in your ApplicationController like this:
class ApplicationController < ActionController::Base
  include PublicActivity::StoreController
end
  1. Use Proc in :owner attribute for tracked class method in your desired model. For example:
class Article < ActiveRecord::Base
  tracked owner: Proc.new{ |controller, model| controller.current_user }
end

Note: current_user applies to Devise, if you are using a different authentication gem or your own code, change the current_user to a method you use.

Disable tracking for a class or globally

If you need to disable tracking temporarily, for example in tests or db/seeds.rb then you can use PublicActivity.enabled= attribute like below:

# Disable p_a globally
PublicActivity.enabled = false

# Perform some operations that would normally be tracked by p_a:
Article.create(title: 'New article')

# Switch it back on
PublicActivity.enabled = true

You can also disable public_activity for a specific class:

# Disable p_a for Article class
Article.public_activity_off

# p_a will not do anything here:
@article = Article.create(title: 'New article')

# But will be enabled for other classes:
# (creation of the comment will be recorded if you are tracking the Comment class)
@article.comments.create(body: 'some comment!')

# Enable it again for Article:
Article.public_activity_on

Create custom activities

Besides standard, automatic activities created on CRUD actions on your model (deactivatable), you can post your own activities that can be triggered without modifying the tracked model. There are a few ways to do this, as PublicActivity gives three tiers of options to be set.

Instant options

Because every activity needs a key (otherwise: NoKeyProvided is raised), the shortest and minimal way to post an activity is:

@user.create_activity :mood_changed
# the key of the action will be user.mood_changed
@user.create_activity action: :mood_changed # this is exactly the same as above

Besides assigning your key (which is obvious from the code), it will take global options from User class (given in #tracked method during class definition) and overwrite them with instance options (set on @user by #activity method). You can read more about options and how PublicActivity inherits them for you here.

Note the action parameter builds the key like this: "#{model_name}.#{action}". You can read further on options for #create_activity here.

To provide more options, you can do:

@user.create_activity action: 'poke', parameters: {reason: 'bored'}, recipient: @friend, owner: current_user

In this example, we have provided all the things we could for a standard Activity.

Use custom fields on Activity

Besides the few fields that every Activity has (key, owner, recipient, trackable, parameters), you can also set custom fields. This could be very beneficial, as parameters are a serialized hash, which cannot be queried easily from the database. That being said, use custom fields when you know that you will set them very often and search by them (don't forget database indexes :) ).

Set owner and recipient based on associations

class Comment < ActiveRecord::Base
  include PublicActivity::Model
  tracked owner: :commenter, recipient: :commentee

  belongs_to :commenter, :class_name => "User"
  belongs_to :commentee, :class_name => "User"
end

Resolve parameters from a Symbol or Proc

class Post < ActiveRecord::Base
  include PublicActivity::Model
  tracked only: [:update], parameters: :tracked_values
  
  def tracked_values
   {}.tap do |hash|
     hash[:tags] = tags if tags_changed?
   end
  end
end

Setup

Skip this step if you are using ActiveRecord in Rails 4 or Mongoid

The first step is similar in every ORM available (except mongoid):

PublicActivity::Activity.class_eval do
  attr_accessible :custom_field
end

place this code under config/initializers/public_activity.rb, you have to create it first.

To be able to assign to that field, we need to move it to the mass assignment sanitizer's whitelist.

Migration

If you're using ActiveRecord, you will also need to provide a migration to add the actual field to the Activity. Taken from our tests:

class AddCustomFieldToActivities < ActiveRecord::Migration
  def change
    change_table :activities do |t|
      t.string :custom_field
    end
  end
end

Assigning custom fields

Assigning is done by the same methods that you use for normal parameters: #tracked, #create_activity. You can just pass the name of your custom variable and assign its value. Even better, you can pass it to #tracked to tell us how to harvest your data for custom fields so we can do that for you.

class Article < ActiveRecord::Base
  include PublicActivity::Model
  tracked custom_field: proc {|controller, model| controller.some_helper }
end

Help

If you need help with using public_activity please visit our discussion group and ask a question there:

https://groups.google.com/forum/?fromgroups#!forum/public-activity

Please do not ask general questions in the Github Issues.


Author: public-activity
Source code: https://github.com/public-activity/public_activity
License: MIT license

#ruby  #ruby-on-rails