Skip to content

Commit

Permalink
all components & tests written
Browse files Browse the repository at this point in the history
  • Loading branch information
maxmantz committed May 26, 2016
1 parent b7e92a3 commit b86e61a
Show file tree
Hide file tree
Showing 27 changed files with 825 additions and 570 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ node_js:
- "4"

before_install:
- npm install [email protected]
- npm install [email protected] immutable babel-polyfill

after_success:
- coveralls
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "A package for managing OpenID Connect authentication in redux apps",
"main": "dist/redux-oidc.js",
"scripts": {
"test": "mocha --compilers js:babel-register --require tests/setup.js tests/*.test.js",
"test": "mocha --compilers js:babel-register --require tests/setup.js tests/**/*.test.js",
"test:debug": "mocha --compilers js:babel-register --require tests/setup.js --debug tests/*.test.js",
"build": "webpack -p",
"prepublish": "npm run build",
Expand Down Expand Up @@ -61,7 +61,7 @@
"webpack": "^1.12.14"
},
"dependencies": {
"oidc-client": "1.0.0-beta.7"
"oidc-client": "1.0.0"
},
"peerDependencies": {
"react": ">=0.14.0",
Expand Down
29 changes: 14 additions & 15 deletions src/CallbackComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { redirectSuccess } from './actions';

class CallbackComponent extends React.Component {
static propTypes = {
successCallback: PropTypes.func,
dispatch: PropTypes.func.isRequired
successCallback: PropTypes.func.isRequired
};

static contextTypes = {
Expand All @@ -16,20 +15,20 @@ class CallbackComponent extends React.Component {
let { successCallback } = this.props;

this.context.userManager.signinRedirectCallback()
.then((user) => {
this.props.dispatch(redirectSuccess(user));

if (successCallback) {
successCallback(user);
}
window.localStorage.removeItem(STORAGE_KEY);
})
.catch((error) => {
window.localStorage.removeItem(STORAGE_KEY);
throw new Error(`Error handling redirect callback: ${error.message}`);
});
.then(this.onRedirectSuccess)
.catch(this.onRedirectError);
}

onRedirectSuccess = (user) => {
localStorage.removeItem(STORAGE_KEY);
this.context.store.dispatch(redirectSuccess(user));
this.props.successCallback(user);
};

onRedirectError = (error) => {
localStorage.removeItem(STORAGE_KEY);
throw new Error(`Error handling redirect callback: ${error.message}`);
};

get defaultContent() {
return <div>Redirecting...</div>;
Expand All @@ -38,7 +37,7 @@ class CallbackComponent extends React.Component {
render() {
return (
<div>
{ this.props.children || this.defaultContent }
{this.props.children || this.defaultContent}
</div>
);
}
Expand Down
53 changes: 48 additions & 5 deletions src/OidcProvider.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,67 @@
import React, { PropTypes } from 'react';
import { userExpired, userFound, silentRenewError, sessionTerminated, userExpiring } from './actions';

class OidcProvider extends React.Component {
static propTypes = {
userManager: PropTypes.object.isRequired,
children: PropTypes.element.isRequired
userManager: PropTypes.object.isRequired
};

static childContextTypes = {
userManager: PropTypes.object
};

constructor(props) {
super(props);
this.userManager = props.userManager;
}

getChildContext() {
return {
userManager: this.userManager
};
}

constructor(props) {
super(props);
this.userManager = props.userManager;
componentWillMount() {
// register the event callbacks
this.userManager.events.addUserLoaded(this.onUserLoaded);
this.userManager.events.addSilentRenewError(this.onSilentRenewError);
this.userManager.events.addAccessTokenExpired(this.onAccessTokenExpired);
this.userManager.events.addUserUnloaded(this.onUserUnloaded);
this.userManager.events.addUserExpiring(this.onUserExpiring);
}

componentWillUnmount() {
// unregister the event callbacks
this.userManager.events.removeUserLoaded(this.onUserLoaded);
this.userManager.events.removeSilentRenewError(this.onSilentRenewError);
this.userManager.events.removeAccessTokenExpired(this.onAccessTokenExpired);
this.userManager.events.removeUserUnloaded(this.onUserUnloaded);
this.userManager.events.removeUserExpiring(this.onUserExpiring);
}

// event callback when the user has been loaded (on silent renew or redirect)
onUserLoaded = (user) => {
this.context.store.dispatch(userFound(user));
};

// event callback when silent renew errored
onSilentRenewError = (error) => {
this.context.store.dispatch(silentRenewError(error));
};

// event callback when the access token expired
onAccessTokenExpired = () => {
this.context.store.dispatch(userExpired());
};

// event callback when the user is logged out
onUserUnloaded = () => {
this.context.store.dispatch(sessionTerminated());
};

// event callback when the user is expiring
onUserExpiring = () => {
this.context.store.dispatch(userExpiring());
}

render() {
Expand Down
28 changes: 27 additions & 1 deletion src/actions/index.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,47 @@
import { USER_EXPIRED, REDIRECT_SUCCESS, USER_FOUND } from '../constants'
import { USER_EXPIRED, REDIRECT_SUCCESS, USER_FOUND, SILENT_RENEW_ERROR, USER_EXPIRING, SESSION_TERMINATED } from '../constants'

// dispatched when the existing user expired
export function userExpired() {
return {
type: USER_EXPIRED
};
}

// dispatched after a successful redirect callback
export function redirectSuccess(user) {
return {
type: REDIRECT_SUCCESS,
payload: user
};
}

// dispatched when a user has been found in storage
export function userFound(user) {
return {
type: USER_FOUND,
payload: user
};
}

// dispatched when silent renew fails
// payload: the error
export function silentRenewError(error) {
return {
type: SILENT_RENEW_ERROR,
payload: error
};
}

// dispatched when the user is logged out
export function sessionTerminated() {
return {
type: SESSION_TERMINATED
};
}

// dispatched when the user is expiring (just before a silent renew is triggered)
export function userExpiring() {
return {
type: USER_EXPIRING
};
}
4 changes: 0 additions & 4 deletions src/constants.js

This file was deleted.

7 changes: 7 additions & 0 deletions src/constants/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const STORAGE_KEY = 'oidc.expired';
export const USER_EXPIRED = 'redux-oidc/USER_EXPIRED';
export const REDIRECT_SUCCESS = 'redux-oidc/REDIRECT_SUCCESS';
export const USER_LOADED = 'redux-oidc/USER_LOADED';
export const SILENT_RENEW_ERROR = 'redux-oidc/SILENT_RENEW_ERROR';
export const SESSION_TERMINATED = 'redux-oidc/SESSION_TERMINATED';
export const USER_EXPIRING = 'redux-oidc/USER_EXPIRING';
5 changes: 2 additions & 3 deletions src/helpers/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
import createUserManager from './createUserManager';

export default { createUserManager };
export const createUserManager = require('./createUserManager').default;
export const processSilentRenew = require('./processSilentRenew').default;
6 changes: 6 additions & 0 deletions src/helpers/processSilentRenew.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import createUserManager from './createUserManager';

export default function processSilentRenew() {
const mgr = createUserManager();
mgr.signinSilentCallback();
}
8 changes: 4 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import reducerImmutable from './reducer/reducer-immutable';
import standardReducer from './reducer/reducer';
import { USER_EXPIRED, REDIRECT_SUCCESS, STORAGE_KEY } from './constants';

export const createUserManager = helpers.createUserManager;
export const CallbackComponent = Callback;
export const immutableReducer = reducerImmutable;
export const reducer = standardReducer;
export const createUserManager = require('./helpers/createUserManager').default;
export const CallbackComponent = require('./CallbackComponent').default;
export const immutableReducer = require('./reducer/reducer-immutable').default;
export const reducer = require('./reducer/reducer').default;
export const constants = {
USER_EXPIRED,
REDIRECT_SUCCESS,
Expand Down
75 changes: 75 additions & 0 deletions src/oidcMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { STORAGE_KEY } from './constants';
import { userExpired, userFound, silentRenewError, sessionTerminated } from './actions';

// store the user here to prevent future promise calls to getUser()
export let storedUser = null;

// helper function to set the stored user manually (for testing)
export function setStoredUser(user) {
storedUser = user;
}

// helper function to remove the stored user manually (for testing)
export function removeStoredUser() {
storedUser = null;
}

// callback for the user manager's getUser().then()
export function getUserSuccessCallback(next, userManager, user, triggerAuthFlow, action) {
if (!user || user.expired) {
// IF: user is expired
next(userExpired());
if (triggerAuthFlow) {
// IF: auth flow should be triggered
userManager.signinRedirect({ data: {
redirectUrl: window.location.href
}
});
} else {
return next(action);
}
} else {
// ELSE: user is NOT expired
localStorage.removeItem(STORAGE_KEY);
storedUser = user;
next(userFound(user));
return next(action);
}
}

export function getUserErrorCallback(error) {
localStorage.removeItem(STORAGE_KEY);
throw new Error(`Error loading user: ${error.message}`);
}

// the middleware creator function
export default function createOidcMiddleware(userManager, shouldValidate, triggerAuthFlow = true) {
if (!userManager) {
throw new Error('You must provide a user manager!');
}

if (!shouldValidate || typeof(shouldValidate) !== 'function') {
// set the default shouldValidate()
shouldValidate = (state, action) => true;
}

// the middleware
return (store) => (next) => (action) => {
if (shouldValidate(store.getState(), action) && !localStorage.getItem(STORAGE_KEY)) {
// IF: validation should occur...
if (!storedUser || storedUser.expired) {
// IF: user hasn't been found or is expired...
localStorage.setItem(STORAGE_KEY, true);
userManager.getUser()
.then((user) => getUserSuccessCallback(next, userManager, user, triggerAuthFlow, action))
.catch(getUserErrorCallback);
} else {
// ELSE: user has been found and NOT is expired...
return next(action);
}
} else {
// ELSE: validation should NOT occur...
return next(action);
}
}
};
4 changes: 3 additions & 1 deletion src/reducer/reducer-immutable.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fromJS } from 'immutable';
import { USER_EXPIRED, REDIRECT_SUCCESS, USER_FOUND } from '../constants';
import { USER_EXPIRED, REDIRECT_SUCCESS, USER_FOUND, SILENT_RENEW_ERROR, SESSION_TERMINATED } from '../constants';

const initialState = fromJS({
user: null,
Expand All @@ -8,6 +8,8 @@ const initialState = fromJS({
export default function reducer(state = initialState, action) {
switch (action.type) {
case USER_EXPIRED:
case SILENT_RENEW_ERROR:
case SESSION_TERMINATED:
return state.set('user', null);
case REDIRECT_SUCCESS:
case USER_FOUND:
Expand Down
4 changes: 3 additions & 1 deletion src/reducer/reducer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { USER_EXPIRED, REDIRECT_SUCCESS, USER_FOUND } from '../constants';
import { USER_EXPIRED, REDIRECT_SUCCESS, USER_FOUND, SILENT_RENEW_ERROR, SESSION_TERMINATED } from '../constants';

const initialState = {
user: null
Expand All @@ -7,6 +7,8 @@ const initialState = {
export default function reducer(state = initialState, action) {
switch (action.type) {
case USER_EXPIRED:
case SILENT_RENEW_ERROR:
case SESSION_TERMINATED:
return Object.assign({}, { ...state }, { user: null });
case REDIRECT_SUCCESS:
case USER_FOUND:
Expand Down
Loading

0 comments on commit b86e61a

Please sign in to comment.