react + redux + RR
It uses https://gist.github.com/iNikNik/3c1b870f63dc0de67c38 for stores and actions.
react + redux + RR
It uses https://gist.github.com/iNikNik/3c1b870f63dc0de67c38 for stores and actions.
| import { buildRoles, buildAccessLevels } from './core/auth-helpers.js'; | |
| /* | |
| List all the roles you wish to use in the app | |
| You have a max of 31 before the bit shift pushes the accompanying integer out of | |
| the memory footprint for an integer | |
| */ | |
| const roles = [ | |
| 'banned', | |
| 'public', | |
| 'user', | |
| 'admin' | |
| ]; | |
| /* | |
| Build out all the access levels you want referencing the roles listed above | |
| You can use the "*" symbol to represent access to all roles. | |
| The left-hand side specifies the name of the access level, and the right-hand side | |
| specifies what user roles have access to that access level. E.g. users with user role | |
| 'user' and 'admin' have access to the access level 'user'. | |
| */ | |
| const levels = { | |
| 'public': '*', | |
| 'anon': ['public'], | |
| 'user': ['user', 'admin'], | |
| 'admin': ['admin'] | |
| }; | |
| export const userRoles = buildRoles(roles); | |
| export const accessLevels = buildAccessLevels(levels, userRoles); |
| import 'babel/polyfill'; | |
| import React from 'react'; | |
| import { Router } from 'react-router'; | |
| import BrowserHistory from 'react-router/lib/BrowserHistory'; | |
| import { Provider } from 'redux/react'; | |
| import createRedux from './redux/createRedux'; | |
| import { createRoutes } from './routes'; | |
| import getInitialState from './core/getInitialState'; | |
| import bindCheckAuth from './core/auth-helpers.js'; | |
| function run() { | |
| const reactRoot = window.document.getElementById('app'); | |
| const state = getInitialState('#__INITIAL_STATE__'); | |
| const redux = createRedux(state); | |
| const requireAccess = bindCheckAuth(redux, (nextState, transition) => { | |
| /* | |
| Current version need this hack to avoid infinite /login redirect | |
| Need to fetch error states: | |
| 1) "user not authorized" => [401] => redirect to login | |
| 2) "access denied" => [403] => redirect to 403 page | |
| */ | |
| if (nextState.location.pathname === '/login') { | |
| return; | |
| } | |
| transition.to('/login', { | |
| next: nextState.location.pathname | |
| }); | |
| }); | |
| const routes = createRoutes(redux, requireAccess); | |
| const history = new BrowserHistory(); | |
| React.render(( | |
| <Provider redux={redux}> | |
| {() => <Router history={history} children={routes}/> } | |
| </Provider> | |
| ), reactRoot); | |
| } | |
| // Run the application when both DOM is ready | |
| // and page content is loaded | |
| Promise.all([ | |
| new Promise((resolve) => { | |
| if (window.addEventListener) { | |
| window.addEventListener('DOMContentLoaded', resolve); | |
| } else { | |
| window.attachEvent('onload', resolve); | |
| } | |
| }) | |
| ]).then(run); |
| import _ from 'lodash'; | |
| import invariant from 'react/lib/invariant'; | |
| export function checkAccess(requiredLevel, currentLevel) { | |
| return !!(requiredLevel.bitMask & currentLevel.bitMask); | |
| } | |
| export function accessEquals(requiredLevel, currentLevel) { | |
| return requiredLevel.bitMask === currentLevel.bitMask; | |
| } | |
| export class NeedRedirect { | |
| constructor(to = '/login') { | |
| this.redirectTo = to; | |
| } | |
| } | |
| /* | |
| Method to build a distinct bit mask for each role | |
| It starts off with "1" and shifts the bit to the left for each element in the | |
| roles array parameter | |
| */ | |
| export function buildRoles(roles) { | |
| let bitMask = '01'; | |
| invariant( | |
| roles.length <= 31, | |
| 'You have too many roles!' + | |
| 'Max=31 before the bit shift pushes the accompanying integer out of the memory footprint for an integer' | |
| ); | |
| // dbg | |
| const userRoles = _.reduce(roles, (result, role) => { | |
| const intCode = parseInt(bitMask, 2); | |
| result[role] = { | |
| bitMask: intCode, | |
| title: role | |
| }; | |
| bitMask = (intCode << 1 ).toString(2); | |
| return result; | |
| }, {}); | |
| return userRoles; | |
| } | |
| /* | |
| This method builds access level bit masks based on the accessLevelDeclaration parameter which must | |
| contain an array for each access level containing the allowed user roles. | |
| */ | |
| export function buildAccessLevels(accessLevelDeclarations, userRoles) { | |
| /* | |
| Zero step - transform | |
| { level1Name: level1, level2Name: level2 } object | |
| => | |
| [ { name: level1Name, level: level1 }, { name: level2Name, level: level2 } ] array | |
| */ | |
| const declarationsArr = _.map(accessLevelDeclarations, (level, name) => ({ name, level })); | |
| /* | |
| First step: filter access levels like: | |
| 'public': '*', | |
| That means every user role enabled, so bitMask => sum of all bit masks | |
| */ | |
| let accessLevels = _ | |
| .filter(declarationsArr, ({ level }) => typeof level === 'string') // eslint-disable-line no-shadow | |
| .reduce((result, { level, name }) => { // eslint-disable-line no-shadow | |
| invariant( | |
| level === '*', | |
| 'Access Control Error: Could not parse "' + level + '" as access definition for level "' + name + '"' | |
| ); | |
| const resultBitMask = _.reduce(userRoles, (result) => result + '1', ''); // eslint-disable-line no-shadow | |
| result[name] = { | |
| bitMask: parseInt(resultBitMask, 2) | |
| }; | |
| return result; | |
| }, {}) | |
| ; | |
| /* | |
| Second step: filter access levels like: | |
| 'user': ['user', 'admin'], | |
| That means we need to iterate on ['user', 'admin'] array and summ bit mask for 'user' and 'admin' | |
| */ | |
| accessLevels = _ | |
| .filter(declarationsArr, ({ level }) => typeof level !== 'string') // eslint-disable-line no-shadow | |
| .reduce((result, { level, name }) => { // eslint-disable-line no-shadow | |
| const levelName = name; | |
| const levelsArr = level; | |
| const resultBitMask = _.reduce(levelsArr, (resultBitMask, roleName) => { // eslint-disable-line no-shadow | |
| invariant( | |
| userRoles.hasOwnProperty(roleName) === true, | |
| 'Access Control Error: Could not find role "' + roleName + '" in registered roles while building access for "' + levelName + '"' | |
| ); | |
| return resultBitMask | userRoles[roleName].bitMask; | |
| }, 0); | |
| result[name] = { | |
| bitMask: resultBitMask | |
| }; | |
| return result; | |
| }, accessLevels) | |
| ; | |
| return accessLevels; | |
| } |
| import { createStore, getActionIds } from '../redux/helpers.js'; | |
| import { AuthActions } from '../actions/AuthActions'; | |
| import { userRoles } from '../access'; | |
| const actions = getActionIds(AuthActions); | |
| const initialState = { | |
| accessLvl: userRoles.public | |
| }; | |
| export const auth = createStore(initialState, { | |
| [actions.authenticate.success]: (state, action) => { | |
| return { | |
| ...state, | |
| accessLvl: action.result.accessLvl | |
| }; | |
| }, | |
| }); |
| import { createActions, asyncAction } from '../redux/helpers.js'; | |
| import { userRoles } from '../access'; | |
| export const AuthActions = createActions({ | |
| @asyncAction() | |
| authenticate(login, pass) { | |
| // success authentication mock - just for example :) | |
| const promise = | |
| new Promise((resolve) => { | |
| setTimeout(() => { | |
| resolve({accessLvl: userRoles.user}); | |
| }, 1000); | |
| }) | |
| ; | |
| return promise; | |
| }, | |
| }); |
| import { checkAccess } from './core/auth-helpers.js'; | |
| export default function bindCheckAuth(redux, accessFailHandler) { | |
| return (accessLevel, customCheckFun) => (nextState, transition) => { | |
| const currentAccessLvl = redux.getState().auth.accessLvl; | |
| if (customCheckFun && !customCheckFun(accessLevel, currentAccessLvl) | |
| || !checkAccess(accessLevel, currentAccessLvl)) { | |
| accessFailHandler(nextState, transition); | |
| } | |
| }; | |
| } |
| import React from 'react'; | |
| import { Router, Route, DefaultRoute, Redirect } from 'react-router'; // eslint-disable-line no-unused-vars | |
| import { accessEquals } from './core/auth-helpers.js'; | |
| import { accessLevels } from './access'; | |
| import App from './containers/App'; | |
| import HomePage from './containers/HomePage'; | |
| import InfoPage from './containers/InfoPage'; | |
| import LoginPage from './containers/LoginPage'; | |
| export const createRoutes = (redux, requireAccess) => { | |
| return ( | |
| <Route name="app" component={App}> | |
| <Route name="home" path="/" components={HomePage} onEnter={requireAccess(accessLevels.user)}/> | |
| <Route name="info" path="/info" components={InfoPage} onEnter={requireAccess(accessLevels.user)}/> | |
| <Route name="login" path="/login" components={LoginPage} onEnter={requireAccess(accessLevels.anon, accessEquals)}/> | |
| </Route> | |
| ); | |
| }; |
| // ... | |
| import { createRoutes } from './routes'; | |
| import createRedux from './redux/createRedux'; | |
| import fetchComponentsData from './core/fetchComponentsData'; | |
| import renderTemplate from './core/renderTemplate'; | |
| import { NeedRedirect } from './core/auth-helpers.js'; | |
| import bindCheckAuth from './core/bindCheckAuth'; | |
| // ... | |
| // server configuration | |
| // ... | |
| server.get('*', (req, res, next) => { | |
| try { | |
| const redux = createRedux(); | |
| const requireAccess = bindCheckAuth(redux, (nextState) => { | |
| if (nextState.location.pathname === '/login') { | |
| return; | |
| } | |
| throw new NeedRedirect('/login?next=' + nextState.location.pathname); | |
| }); | |
| const routes = createRoutes(redux, requireAccess); | |
| const location = new Location(req.path, req.query); | |
| Router.run(routes, location, async (error, initialState) => { | |
| try { | |
| const state = await fetchComponentsData(initialState.components, redux); | |
| const html = renderTemplate(redux, state, initialState, location); | |
| res.send(html).end(); | |
| } catch (err) { | |
| res.status(500).send(err.stack); | |
| next(err); | |
| } | |
| }); | |
| } catch (err) { | |
| if (err instanceof NeedRedirect) { | |
| // Redirect | |
| res.set('Content-Type', 'text/html'); | |
| res.status(401).send('<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=' + err.redirectTo + '"></head></html>'); | |
| } else { | |
| res.status(500).send(err.stack); | |
| next(err); | |
| } | |
| } | |
| }); |
I think only accessEquals is in that incorrectly named file.
What is createRedux?
This code is for a really old 0.x version of Redux.
For up to date info check this out: https://auth0.com/blog/2016/01/04/secure-your-react-and-redux-app-with-jwt-authentication/
Wondering whats in authAccessLevels?