├── .eslintignore
├── .babelrc
├── .npmignore
├── src
├── services
│ └── auth.js
├── lib
│ ├── helpers.js
│ ├── redirect.js
│ ├── request.js
│ ├── session.js
│ └── auth.js
└── index.js
├── .gitignore
├── .eslintrc
├── package.json
└── README.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | /*
2 | !/src/
3 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next", "@babel/preset-env"]
3 | }
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # .npmignore
2 | src
3 | examples
4 | .babelrc
5 | .gitignore
6 | webpack.config.js
--------------------------------------------------------------------------------
/src/services/auth.js:
--------------------------------------------------------------------------------
1 | import { get } from '../lib/request'
2 | import { to } from '../lib/helpers'
3 |
4 | export const profile = async token => {
5 | const [err, data] = await to(
6 | get(process.env.GET_SESSION_URL || '/user/session', token)
7 | )
8 | return (err || !data.success) ? null : data.data
9 | }
10 |
--------------------------------------------------------------------------------
/src/lib/helpers.js:
--------------------------------------------------------------------------------
1 | export const to = promise => promise
2 | .then(res => [null, { ...res.data, statusCode: res.status }, res])
3 | .catch(err => {
4 | try {
5 | return [
6 | { ...err.response.data, statusCode: err.response.status } || 'Unknown error'
7 | ]
8 | } catch (e) { return ['Unknown error'] }
9 | })
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore all files in root dir
2 | /*
3 |
4 | # Excepts source files
5 | !/src/
6 | !/__mocks__/
7 | !/__tests__/
8 |
9 | # Excepts documentation
10 | !/doc/
11 |
12 | # Excepts config files
13 | !.babelrc
14 | !.eslintignore
15 | !.eslintrc
16 | !jest.config.js
17 | !jest.setup.js
18 | !.gitignore
19 | !.gitlab-ci.yml
20 | !.npmignore
21 | !package.json
22 | !README.md
23 |
--------------------------------------------------------------------------------
/src/lib/redirect.js:
--------------------------------------------------------------------------------
1 | import Router from 'next/router'
2 |
3 | export default (target, ctx = {}) => {
4 | if (ctx.res) {
5 | // server 303: "See other"
6 | ctx.res.writeHead(303, { Location: target })
7 | ctx.res.end()
8 | } else {
9 | // In the browser, we just pretend like this never even happened ;)
10 | Router.push(target)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/lib/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | export const getUrl = endpoint => process.env.API_HOST + endpoint
4 |
5 | const buildHeader = jwt => ({
6 | 'Content-Type': 'application/json',
7 | ...(jwt ? { Authorization: `Bearer ${jwt}` } : {})
8 | })
9 |
10 | export const get = async (endpoint, jwt, params = {}) => {
11 | const headers = buildHeader(jwt)
12 |
13 | return axios.get(endpoint.startsWith('https') ? endpoint : getUrl(endpoint), {
14 | headers,
15 | params
16 | })
17 | }
18 |
--------------------------------------------------------------------------------
/src/lib/session.js:
--------------------------------------------------------------------------------
1 | import cookie from 'js-cookie'
2 |
3 | export const setCookie = (key, value, sessionCookie = false) => {
4 | if (process.browser) {
5 | const options = {
6 | expires: sessionCookie ? (1 / 288) : 1,
7 | domain: process.env.COOKIE_DOMAIN
8 | }
9 | if (!process.env.COOKIE_DOMAIN) delete options.domain
10 | cookie.set(key, value, options)
11 | }
12 | }
13 |
14 | export const removeCookie = (key, opt) => {
15 | if (process.browser)
16 | cookie.remove(key, opt)
17 | }
18 |
19 | export const getCookie = (key, req) => (process.browser
20 | ? getCookieFromBrowser(key)
21 | : getCookieFromServer(key, req))
22 |
23 | const getCookieFromBrowser = key => cookie.get(key)
24 |
25 | const getCookieFromServer = (key, req) => {
26 | if (!req.headers.cookie)
27 | return undefined
28 |
29 | const rawCookie = req.headers.cookie
30 | .split(';')
31 | .find(c => c.trim().startsWith(`${key}=`))
32 | if (!rawCookie)
33 | return undefined
34 |
35 | return rawCookie.split('=')[1]
36 | }
37 |
--------------------------------------------------------------------------------
/src/lib/auth.js:
--------------------------------------------------------------------------------
1 | import intersection from 'lodash/intersection'
2 | import redirect from './redirect'
3 | import { getCookie } from './session'
4 |
5 | export const getJwt = req => getCookie(process.env.JWT_COOKIE_NAME, req)
6 |
7 | const check_acl = (ACL, roles) => (
8 | !ACL
9 | ? true
10 | : intersection(ACL, roles).length > 0
11 | )
12 |
13 | export const redirectIfAuthenticated = async (ctx, user) => (
14 | user && redirect(process.env.REDIRECT_IF_AUTHENTICATED, ctx)
15 | )
16 |
17 | export const redirectIfNotAuthenticated = async (ctx, { user, ACL, pathname }) => {
18 | // if not logged in
19 | if (!user) {
20 | const url = process.env.REDIRECT_IF_NOT_AUTHENTICATED + (
21 | process.env.REDIRECT_IF_NOT_AUTHENTICATED && process.env.REDIRECT_IF_NOT_AUTHENTICATED.startsWith('http')
22 | ? `?ref=${process.env.REFERER}${pathname || ''}`
23 | : ''
24 | )
25 | return redirect(url)
26 | }
27 |
28 | // if do not have access
29 | if (user && !check_acl(ACL, user.roles))
30 | return redirect(process.env.REDIRECT_IF_NO_ACCESS, ctx)
31 | }
32 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "plugins": ["react"],
4 | "parserOptions": {
5 | "ecmaFeatures": {
6 | "jsx": true,
7 | "modules": true
8 | }
9 | },
10 | "env": {
11 | "browser": true
12 | },
13 | "settings": {
14 | "react": {
15 | "version": "detect"
16 | }
17 | },
18 | "extends" : [
19 | "eslint:recommended",
20 | "plugin:react/recommended",
21 | "airbnb-base"
22 | ],
23 | "rules" : {
24 | "camelcase": "off",
25 | "no-return-assign": ["error", "except-parens"],
26 | "implicit-arrow-linebreak": "off",
27 | "no-use-before-define": "off",
28 | "consistent-return": "off",
29 | "semi": ["error", "never"],
30 | "indent": ["error", 4, { "SwitchCase": 1 }],
31 | "no-underscore-dangle": "off",
32 | "curly": ["error", "multi", "consistent"],
33 | "nonblock-statement-body-position": ["error", "any"],
34 | "one-var": "off",
35 | "arrow-parens": ["error", "as-needed"],
36 | "no-class-assign": "off",
37 | "comma-dangle": ["error", "never"],
38 | "padded-blocks": ["error", "never"],
39 | "class-methods-use-this": "off",
40 | "import/prefer-default-export": "off",
41 | "react/jsx-indent": ["error", 4],
42 | "react/jsx-indent-props": ["error", 4],
43 | "react/display-name": "off"
44 | }
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjs-auth-hoc",
3 | "version": "1.0.5",
4 | "description": "A Higher Order Component for restricting page access.",
5 | "homepage": "https://github.com/hackerart/nextjs-auth-hoc#readme",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/hackerart/nextjs-auth-hoc"
9 | },
10 | "keywords": [
11 | "next",
12 | "nextjs",
13 | "react",
14 | "auth",
15 | "nextjs-auth",
16 | "next-auth"
17 | ],
18 | "main": "index.js",
19 | "scripts": {
20 | "test": "jest",
21 | "start": "webpack-dev-server --mode development",
22 | "transpile": "babel src -d . --copy-files",
23 | "prepublishOnly": "npm run transpile",
24 | "lint": "eslint ."
25 | },
26 | "author": "hackerart",
27 | "license": "ISC",
28 | "peerDependencies": {
29 | "prop-types": "^15.7.2",
30 | "react": "^16.3.0",
31 | "react-dom": "^16.3.0"
32 | },
33 | "devDependencies": {
34 | "@babel/cli": "^7.0.0-beta.44",
35 | "@babel/core": "^7.2.2",
36 | "@babel/plugin-transform-classes": "^7.4.4",
37 | "@babel/preset-env": "^7.4.4",
38 | "@babel/preset-react": "^7.0.0",
39 | "@babel/register": "^7.4.4",
40 | "@babel/runtime": "^7.4.4",
41 | "babel-eslint": "^10.0.1",
42 | "babel-loader": "^8.0.0-beta.0",
43 | "eslint": "^6.0.1",
44 | "eslint-config-airbnb-base": "^13.1.0",
45 | "eslint-import-resolver-babel-module": "^5.1.0",
46 | "eslint-plugin-import": "^2.18.0",
47 | "eslint-plugin-react": "^7.11.1",
48 | "html-webpack-plugin": "^3.2.0",
49 | "react": "^16.8.6",
50 | "react-dom": "^16.8.6",
51 | "webpack": "^4.30.0",
52 | "webpack-cli": "^3.3.1"
53 | },
54 | "dependencies": {
55 | "axios": "^0.19.0",
56 | "babel-core": "^6.26.3",
57 | "babel-plugin-transform-class-properties": "^6.24.1",
58 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
59 | "babel-preset-next": "^1.2.0",
60 | "js-cookie": "^2.2.0",
61 | "lodash": "^4.17.15",
62 | "next": "^9.0.3",
63 | "prop-types": "^15.7.2"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { getJwt, redirectIfAuthenticated, redirectIfNotAuthenticated } from './lib/auth'
4 | import { profile } from './services/auth'
5 |
6 | const UserContext = React.createContext()
7 |
8 | export const Auth = options =>
9 | Component => {
10 | class AuthHOC extends React.Component {
11 | static async getInitialProps(ctx) {
12 | const { ACL, action } = options
13 | const token = getJwt(ctx.req)
14 | const user = await profile(token)
15 | switch (action) {
16 | case 'RINA':
17 | await redirectIfNotAuthenticated(ctx, { user, ACL, pathname: ctx.pathname })
18 | break
19 | case 'RIA':
20 | await redirectIfAuthenticated(ctx, user)
21 | break
22 | default:
23 | break
24 | }
25 | const props = (
26 | Component.getInitialProps
27 | ? await Component.getInitialProps(ctx)
28 | : null
29 | ) || {}
30 | return { ...props, token, user }
31 | }
32 |
33 | render() {
34 | const { user, token, ...rest } = this.props
35 | return (
36 |