├── .nvmrc
├── .travis.yml
├── .gitignore
├── test
├── index.js
├── oidcTestConfig.js
├── id-token-2028-01-01.js
├── browser-event.test.js
├── setup.js
├── create-router-middleware.test.js
├── create-nuxt-router-middleware.test.js
├── utils.test.js
├── oidc-helper.test.js
└── create-store-module.test.js
├── .babelrc
├── src
├── router
│ ├── create-router-middleware.js
│ └── create-nuxt-router-middleware.js
├── services
│ ├── navigation.js
│ ├── utils.js
│ ├── browser-event.js
│ └── oidc-helpers.js
├── main.js
└── store
│ └── create-store-module.js
├── .eslintrc.js
├── LICENSE
├── README.md
├── rollup.config.js
├── package.json
├── index.d.ts
└── CHANGELOG.md
/.nvmrc:
--------------------------------------------------------------------------------
1 | v14.16.0
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | dist
4 | .nyc_output
5 | .idea
6 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | require('./setup')
2 |
3 | require('./oidc-helper.test')
4 | require('./create-store-module.test')
5 | require('./utils.test')
6 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", {
4 | "targets": {
5 | "browsers": ["last 2 versions", "safari >= 7"]
6 | },
7 | "modules": false
8 | }]
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/src/router/create-router-middleware.js:
--------------------------------------------------------------------------------
1 | export default (store, vuexNamespace) => {
2 | return (to, from, next) => {
3 | store.dispatch((vuexNamespace ? vuexNamespace + '/' : '') + 'oidcCheckAccess', to)
4 | .then((hasAccess) => {
5 | if (hasAccess) {
6 | next()
7 | }
8 | })
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es6: true
5 | },
6 | extends: [
7 | 'standard'
8 | ],
9 | globals: {
10 | Atomics: 'readonly',
11 | SharedArrayBuffer: 'readonly'
12 | },
13 | parserOptions: {
14 | ecmaVersion: 2018,
15 | sourceType: 'module'
16 | },
17 | rules: {
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/test/oidcTestConfig.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | authority: 'https://your_oidc_authority',
3 | client_id: 'your_client_id',
4 | redirect_uri: 'http://localhost:1337/oidc-callback',
5 | silent_redirect_uri: 'http://localhost:1337/oidc-silent-callback',
6 | automaticSilentRenew: true,
7 | response_type: 'openid profile email api1',
8 | scope: 'openid profile'
9 | }
10 |
--------------------------------------------------------------------------------
/src/router/create-nuxt-router-middleware.js:
--------------------------------------------------------------------------------
1 | export default (vuexNamespace) => {
2 | return (context) => {
3 | return new Promise((resolve, reject) => {
4 | context.store.dispatch((vuexNamespace ? vuexNamespace + '/' : '') + 'oidcCheckAccess', context.route)
5 | .then((hasAccess) => {
6 | if (hasAccess) {
7 | resolve()
8 | }
9 | })
10 | .catch(() => {
11 | })
12 | })
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/services/navigation.js:
--------------------------------------------------------------------------------
1 | export const openUrlWithIframe = (url) => {
2 | return new Promise((resolve, reject) => {
3 | if (typeof window === 'undefined') {
4 | reject(new Error('gotoUrlWithIframe does not work when window is undefined'))
5 | }
6 | const iframe = window.document.createElement('iframe')
7 | iframe.style.display = 'none'
8 | iframe.onload = () => {
9 | iframe.parentNode.removeChild(iframe)
10 | resolve(true)
11 | }
12 | iframe.src = url
13 | window.document.body.appendChild(iframe)
14 | })
15 | }
16 |
--------------------------------------------------------------------------------
/test/id-token-2028-01-01.js:
--------------------------------------------------------------------------------
1 | module.exports = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwic3ViIjoiMjQ4Mjg5NzYxMDAxIiwiYXVkIjoiczZCaGRSa3F0MyIsIm5vbmNlIjoibi0wUzZfV3pBMk1qIiwiZXhwIjoxODMwMjk3NjAwLCJpYXQiOjEzMTEyODA5NzAsIm5hbWUiOiJKYW5lIERvZSIsImdpdmVuX25hbWUiOiJKYW5lIiwiZmFtaWx5X25hbWUiOiJEb2UiLCJnZW5kZXIiOiJmZW1hbGUiLCJiaXJ0aGRhdGUiOiIwMDAwLTEwLTMxIiwiZW1haWwiOiJqYW5lZG9lQGV4YW1wbGUuY29tIiwicGljdHVyZSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9qYW5lZG9lL21lLmpwZyIsImp0aSI6IjhiZTkzYTkwLWMwOGMtNDIzMC05YTUzLWM0MDA4YjVjZDIzOSJ9.9m0iXjjJT7t3LfmYBxYzK-A3LP_JUUGZTgntGTuzLwE'
2 |
--------------------------------------------------------------------------------
/test/browser-event.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert')
2 | const sinon = require('sinon');
3 | let vuexDispatchCustomBrowserEvent;
4 |
5 | describe('browser-event.dispatchCustomBrowserEvent', function() {
6 | before(function () {
7 | vuexDispatchCustomBrowserEvent = require('../dist/vuex-oidc.cjs').vuexDispatchCustomBrowserEvent;
8 | });
9 |
10 | it('triggers an event on window', function() {
11 | const eventName = 'testEvent';
12 | sinon.spy(window, 'dispatchEvent');
13 | vuexDispatchCustomBrowserEvent(eventName);
14 | assert.equal(window.dispatchEvent.getCall(0).args[0].name, 'vuexoidc:' + eventName);
15 | window.dispatchEvent.restore();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/src/services/utils.js:
--------------------------------------------------------------------------------
1 | export const objectAssign = (objects) => {
2 | return objects.reduce(function (r, o) {
3 | Object.keys(o || {}).forEach(function (k) {
4 | r[k] = o[k]
5 | })
6 | return r
7 | }, {})
8 | }
9 |
10 | export const parseJwt = (token) => {
11 | try {
12 | var base64Url = token.split('.')[1]
13 | var base64 = base64Url.replace('-', '+').replace('_', '/')
14 | return JSON.parse(window.atob(base64))
15 | } catch (error) {
16 | return {}
17 | }
18 | }
19 |
20 | export const firstLetterUppercase = (string) => {
21 | return string && string.length > 0 ? string.charAt(0).toUpperCase() + string.slice(1) : ''
22 | }
23 |
24 | export const camelCaseToSnakeCase = (string) => {
25 | return string.split(/(?=[A-Z])/).join('_').toLowerCase()
26 | }
27 |
--------------------------------------------------------------------------------
/test/setup.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 | const StorageShim = require('node-storage-shim');
3 | const sinon = require('sinon');
4 | const atob = require('atob');
5 |
6 | const DEFAULT_HTML = '
';
7 |
8 | global.document = new jsdom.JSDOM(DEFAULT_HTML);
9 |
10 | global.window = global.document;
11 |
12 | global.navigator = window.navigator;
13 |
14 | window.localStorage = new StorageShim();
15 |
16 | global.localStorage = window.localStorage;
17 |
18 | window.sessionStorage = new StorageShim();
19 |
20 | global.sessionStorage = window.sessionStorage;
21 |
22 | global.XMLHttpRequest = sinon.useFakeXMLHttpRequest();
23 |
24 | window.atob = atob;
25 |
26 | window.dispatchEvent = function(event) {};
27 |
28 | window.CustomEvent = function(eventName, params) { return { name: eventName, params: params } };
29 |
--------------------------------------------------------------------------------
/test/create-router-middleware.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert')
2 | const sinon = require('sinon');
3 | let vuexOidc;
4 | let mockStore;
5 | let mockNext;
6 |
7 | describe('router-middleware', function() {
8 | before(function () {
9 | vuexOidc = require('../dist/vuex-oidc.cjs');
10 | mockStore = {
11 | dispatch: function(action, to) {
12 | return new Promise(function(resolve) {
13 | resolve(true);
14 | });
15 | }
16 | };
17 | mockNext = sinon.spy();
18 | });
19 |
20 | it('calls next after dispatching check access action', function(done) {
21 | const routerMiddleware = vuexOidc.vuexOidcCreateRouterMiddleware(mockStore);
22 | sinon.spy(mockNext);
23 | routerMiddleware({}, {}, mockNext);
24 | setTimeout(function() {
25 | assert.equal(mockNext.calledOnce, true);
26 | done();
27 | }, 10);
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/test/create-nuxt-router-middleware.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert')
2 | const sinon = require('sinon');
3 | let vuexOidc;
4 | let mockContext;
5 |
6 | describe('nuxt-router-middleware', function() {
7 | before(function () {
8 | vuexOidc = require('../dist/vuex-oidc.cjs');
9 | mockContext = {
10 | store: {
11 | dispatch: function(action, to) {
12 | console.log('dispatch', action, to)
13 | return new Promise(function(resolve) {
14 | resolve(true);
15 | });
16 | }
17 | },
18 | route: {
19 | path: '/'
20 | }
21 | };
22 | });
23 |
24 | it('returns resolving promise if check access is true', function(done) {
25 | const routerMiddleware = vuexOidc.vuexOidcCreateNuxtRouterMiddleware();
26 | routerMiddleware(mockContext)
27 | .then(() => {
28 | done();
29 | });
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/src/services/browser-event.js:
--------------------------------------------------------------------------------
1 | import { objectAssign } from './utils'
2 |
3 | // Use native custom event or DIY for IE
4 | function createCustomEvent (eventName, detail, params) {
5 | const prefixedEventName = 'vuexoidc:' + eventName
6 |
7 | if (typeof window.CustomEvent === 'function') {
8 | params = objectAssign([params, { detail: detail }])
9 | return new window.CustomEvent(prefixedEventName, params)
10 | }
11 |
12 | /* istanbul ignore next */
13 | params = params || { bubbles: false, cancelable: false }
14 | params = objectAssign([params, { detail: detail }])
15 | var evt = document.createEvent('CustomEvent')
16 | evt.initCustomEvent(
17 | prefixedEventName,
18 | params.bubbles,
19 | params.cancelable,
20 | params.detail
21 | )
22 | return evt
23 | }
24 |
25 | export function dispatchCustomBrowserEvent (eventName, detail = {}, params = {}) {
26 | if (window) {
27 | const event = createCustomEvent(
28 | eventName,
29 | objectAssign([{}, detail]),
30 | params
31 | )
32 | window.dispatchEvent(event)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Per Arnborg
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { createOidcUserManager, processSilentSignInCallback, processSignInCallback, getOidcCallbackPath } from './services/oidc-helpers'
2 | import createStoreModule from './store/create-store-module'
3 | import createRouterMiddleware from './router/create-router-middleware'
4 | import createNuxtRouterMiddleware from './router/create-nuxt-router-middleware'
5 | import * as utils from './services/utils'
6 | import { dispatchCustomBrowserEvent } from './services/browser-event'
7 |
8 | export const vuexOidcCreateUserManager = createOidcUserManager
9 |
10 | export const vuexOidcCreateStoreModule = createStoreModule
11 |
12 | export const vuexOidcCreateNuxtRouterMiddleware = createNuxtRouterMiddleware
13 |
14 | export const vuexOidcCreateRouterMiddleware = createRouterMiddleware
15 |
16 | export const vuexOidcProcessSilentSignInCallback = processSilentSignInCallback
17 |
18 | export const vuexOidcProcessSignInCallback = processSignInCallback
19 |
20 | export const vuexOidcGetOidcCallbackPath = getOidcCallbackPath
21 |
22 | export const vuexOidcUtils = utils
23 |
24 | export const vuexDispatchCustomBrowserEvent = dispatchCustomBrowserEvent
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vuex-oidc
2 |
3 | Vue.js implementation of [oidc-client-ts](https://github.com/authts/oidc-client-ts) (or [oidc-client](https://github.com/IdentityModel/oidc-client-js) in :warning: **Breaking changes**: vuex-oidc v4 introduces some breaking changes.
6 | >
7 | > * oidc-client-ts instead of oidc-client is now a required peer dependency
8 | > * The Implicit Flow is no longer supported, Authorization Code Flow with PKCE is the only supported OAuth flow type
9 | > * `vuexOidcProcessSilentSignInCallback`, which previously took no arguments, now needs the oidcSettings as an argument.
10 |
11 | ## Documentation
12 |
13 | See the [wiki](https://github.com/perarnborg/vuex-oidc/wiki) for documentation on how to implement vuex-oidc. Docs for v3 can be found [here](https://github.com/perarnborg/vuex-oidc/wiki/v3).
14 |
15 | ## Examples
16 |
17 | An example of an implementation can be found [here](https://github.com/perarnborg/vuex-oidc-example).
18 |
19 | An example using Nuxt can be found [here](https://github.com/perarnborg/vuex-oidc-example-nuxt).
20 |
21 | ## Build status
22 |
23 | Tests are run on https://travis-ci.org
24 |
25 | [](https://travis-ci.org/perarnborg/vuex-oidc)
26 |
27 | ## License
28 |
29 | [MIT](LICENSE).
30 |
--------------------------------------------------------------------------------
/test/utils.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert')
2 | const { vuexOidcUtils } = require('../dist/vuex-oidc.cjs')
3 |
4 | describe('utils.objectAssign', function() {
5 | it('should merge objects as a new object', function() {
6 | const objA = {prop1: 1, prop2: 'a'}
7 | const objB = {prop1: 2, prop3: 'b'}
8 | const merged = vuexOidcUtils.objectAssign([objA, objB])
9 | assert.equal(typeof merged, 'object')
10 | assert.equal(merged.prop1, objB.prop1)
11 | assert.equal(merged.prop2, objA.prop2)
12 | assert.equal(merged.prop3, objB.prop3)
13 | objB.prop1 = 3
14 | assert.notEqual(merged.prop1, objB.prop1)
15 | });
16 | });
17 |
18 | describe('utils.parseJwt', function() {
19 | it('parses a valid token', function() {
20 | const parsed = vuexOidcUtils.parseJwt(require('./id-token-2028-01-01'));
21 | assert.equal(parsed.email, 'janedoe@example.com');
22 | });
23 | it('parses a an object when parsing an invalid token', function() {
24 | assert.equal(typeof vuexOidcUtils.parseJwt('asd'), 'object');
25 | assert.equal(typeof vuexOidcUtils.parseJwt(null), 'object');
26 | });
27 | });
28 |
29 | describe('utils.firstLetterUppercase', function() {
30 | it('return a string with first letter uppercased', function() {
31 | assert.equal(vuexOidcUtils.firstLetterUppercase('userLoaded'), 'UserLoaded')
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import pkg from './package.json';
2 | import babel from 'rollup-plugin-babel';
3 | const fs = require('fs');
4 | let linter = false;
5 | try {
6 | fs.statSync('./node_modules/rollup-plugin-eslint/package.json');
7 | linter = require('rollup-plugin-eslint');
8 | console.log('Building with linting');
9 | }
10 | catch(err) {
11 | console.log('Building without linting');
12 | }
13 |
14 | const rollupPlugins = []
15 |
16 | if (linter) {
17 | rollupPlugins.push(
18 | linter.eslint({
19 | throwOnError: true,
20 | throwOnWarning: true,
21 | include: ['src/**'],
22 | exclude: ['node_modules/**', 'dist/**']
23 | })
24 | );
25 | }
26 | rollupPlugins.push(
27 | babel({
28 | exclude: ['node_modules/**']
29 | })
30 | );
31 |
32 | export default [
33 | // CommonJS (for Node) and ES module (for bundlers) build.
34 | // (We could have three entries in the configuration array
35 | // instead of two, but it's quicker to generate multiple
36 | // builds from a single configuration where possible, using
37 | // an array for the `output` option, where we can specify
38 | // `file` and `format` for each target)
39 | {
40 | input: 'src/main.js',
41 | output: [
42 | { file: pkg.main, format: 'cjs' },
43 | { file: pkg.module, format: 'es' }
44 | ],
45 | plugins: rollupPlugins,
46 | external: [ 'oidc-client-ts' ]
47 | }
48 | ];
49 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vuex-oidc",
3 | "description": "Vue.js implementation of oidc-client using vuex and vue-router",
4 | "keywords": [
5 | "vue",
6 | "vuejs",
7 | "oidc",
8 | "oidc-client",
9 | "open id",
10 | "open id client",
11 | "vue oidc",
12 | "vue open id"
13 | ],
14 | "version": "4.0.2",
15 | "homepage": "https://github.com/perarnborg/vuex-oidc#readme",
16 | "license": "MIT",
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/perarnborg/vuex-oidc.git"
20 | },
21 | "main": "dist/vuex-oidc.cjs.js",
22 | "module": "dist/vuex-oidc.esm.js",
23 | "typings": "index.d.ts",
24 | "peerDependencies": {
25 | "oidc-client-ts": ">= 2.0.0",
26 | "vue": ">= 2.5.0",
27 | "vue-router": ">= 3.0.0",
28 | "vuex": ">= 3.0.0"
29 | },
30 | "devDependencies": {
31 | "@babel/core": "^7.11.1",
32 | "@babel/preset-env": "^7.11.0",
33 | "acorn": "^7.0.0",
34 | "atob": "^2.1.2",
35 | "eslint": "^6.6.0",
36 | "eslint-config-standard": "^14.1.0",
37 | "eslint-plugin-import": "^2.18.2",
38 | "eslint-plugin-node": "^10.0.0",
39 | "eslint-plugin-promise": "^4.2.1",
40 | "eslint-plugin-standard": "^4.0.1",
41 | "jsdom": "^16.5.0",
42 | "oidc-client-ts": ">= 2.0.0",
43 | "mocha": "^10.2.0",
44 | "node-storage-shim": "^2.0.0",
45 | "nyc": "^14.1.1",
46 | "rollup": "^1.19.4",
47 | "rollup-plugin-babel": "^4.3.3",
48 | "rollup-plugin-commonjs": "^10.0.2",
49 | "rollup-plugin-node-resolve": "^5.2.0",
50 | "sinon": "^7.4.1"
51 | },
52 | "scripts": {
53 | "build": "rollup -c",
54 | "dev": "rollup -c -w",
55 | "install:linter": "npm i && npm i rollup-plugin-eslint && npm i eslint-plugin-node && npm i eslint-plugin-import && npm i eslint-plugin-promise && npm i eslint-plugin-standard && npm i eslint-config-standard",
56 | "lint": "eslint ./src --config=.eslintrc.js",
57 | "lint:fix": "eslint --fix ./src --config=.eslintrc.js",
58 | "test": "nyc mocha",
59 | "pretest": "npm run lint && npm run build",
60 | "preversion": "npm test"
61 | },
62 | "files": [
63 | "dist",
64 | "index.d.ts"
65 | ]
66 | }
67 |
--------------------------------------------------------------------------------
/test/oidc-helper.test.js:
--------------------------------------------------------------------------------
1 | const assert = require("assert");
2 | const oidcConfig = require("./oidcTestConfig");
3 | let vuexOidc;
4 |
5 | describe("oidcHelper.createOidcUserManager", function () {
6 | before(function () {
7 | vuexOidc = require("../dist/vuex-oidc.cjs");
8 | });
9 |
10 | it("should create a UserManager", function () {
11 | const userManager = vuexOidc.vuexOidcCreateUserManager(oidcConfig);
12 | assert.equal(typeof userManager, "object");
13 | });
14 | ["authority", "client_id", "redirect_uri", "response_type", "scope"].forEach(
15 | (requiredSetting) => {
16 | it(
17 | "should fail without oidc required setting " + requiredSetting,
18 | function () {
19 | const faultyOidcConfig = {
20 | ...oidcConfig,
21 | };
22 | delete faultyOidcConfig[requiredSetting];
23 | let userManager;
24 |
25 | try {
26 | userManager = vuexOidc.vuexOidcCreateUserManager(faultyOidcConfig);
27 | } catch (error) {}
28 | assert.notEqual(typeof userManager, "object");
29 | }
30 | );
31 | }
32 | );
33 | it("should translate settings that are snake_case in oidc-client from camelCase ", function () {
34 | const camelCaseOidcConfig = {
35 | ...oidcConfig,
36 | clientId: oidcConfig.client_id,
37 | clientSecret: oidcConfig.client_secret,
38 | redirectUri: oidcConfig.redirect_uri,
39 | responseType: oidcConfig.response_type,
40 | };
41 | delete camelCaseOidcConfig.client_id;
42 | delete camelCaseOidcConfig.client_secret;
43 | delete camelCaseOidcConfig.redirect_uri;
44 | delete camelCaseOidcConfig.response_type;
45 | let userManager;
46 |
47 | try {
48 | userManager = vuexOidc.vuexOidcCreateUserManager(camelCaseOidcConfig);
49 | } catch (error) {}
50 | assert.equal(typeof userManager, "object");
51 | });
52 | });
53 |
54 | describe("oidcHelper.getOidcCallbackPath", function () {
55 | before(function () {
56 | vuexOidc = require("../dist/vuex-oidc.cjs");
57 | });
58 |
59 | it("should return path when router base is /", function () {
60 | const path = vuexOidc.vuexOidcGetOidcCallbackPath(
61 | oidcConfig.redirect_uri,
62 | "/"
63 | );
64 | assert.equal(path, "/oidc-callback");
65 | });
66 |
67 | it("should return path when redirect_uri includes a sub path", function () {
68 | const path = vuexOidc.vuexOidcGetOidcCallbackPath(
69 | oidcConfig.redirect_uri.replace('/oidc-callback', '/auth/oidc-callback'),
70 | "/"
71 | );
72 | assert.equal(path, "/auth/oidc-callback");
73 | });
74 |
75 | it("should return path when router base is not /", function () {
76 | const routeBase = "/app/";
77 | const path = vuexOidc.vuexOidcGetOidcCallbackPath(
78 | "http://localhost:1337" + routeBase + "oidc-callback",
79 | routeBase
80 | );
81 | assert.equal(path, "/oidc-callback");
82 | });
83 |
84 | it("should return path without trailing /", function () {
85 | const path = vuexOidc.vuexOidcGetOidcCallbackPath(
86 | "http://localhost:1337/oidc-callback/",
87 | "/"
88 | );
89 | assert.equal(path, "/oidc-callback");
90 | });
91 | });
92 |
--------------------------------------------------------------------------------
/src/services/oidc-helpers.js:
--------------------------------------------------------------------------------
1 | import { objectAssign, parseJwt, firstLetterUppercase, camelCaseToSnakeCase } from './utils'
2 | import { UserManager, WebStorageStateStore } from 'oidc-client-ts'
3 |
4 | const defaultOidcConfig = {
5 | userStore: new WebStorageStateStore(),
6 | loadUserInfo: true,
7 | automaticSilentSignin: true
8 | }
9 |
10 | const requiredConfigProperties = [
11 | 'authority',
12 | 'client_id',
13 | 'redirect_uri',
14 | 'response_type',
15 | 'scope'
16 | ]
17 |
18 | const settingsThatAreSnakeCasedInOidcClient = [
19 | 'clientId',
20 | 'clientSecret',
21 | 'redirectUri',
22 | 'responseType',
23 | 'maxAge',
24 | 'uiLocales',
25 | 'loginHint',
26 | 'acrValues',
27 | 'postLogoutRedirectUri',
28 | 'popupRedirectUri',
29 | 'silentRedirectUri'
30 | ]
31 |
32 | const snakeCasedSettings = (oidcSettings) => {
33 | settingsThatAreSnakeCasedInOidcClient.forEach(setting => {
34 | if (typeof oidcSettings[setting] !== 'undefined') {
35 | oidcSettings[camelCaseToSnakeCase(setting)] = oidcSettings[setting]
36 | }
37 | })
38 | return oidcSettings
39 | }
40 |
41 | export const getOidcConfig = (oidcSettings) => {
42 | return objectAssign([
43 | defaultOidcConfig,
44 | snakeCasedSettings(oidcSettings),
45 | { automaticSilentRenew: false } // automaticSilentRenew is handled in vuex and not by user manager
46 | ])
47 | }
48 |
49 | export const getOidcCallbackPath = (callbackUri, routeBase = '/') => {
50 | if (callbackUri) {
51 | const domainStartsAt = '://'
52 | const hostAndPath = callbackUri.substr(callbackUri.indexOf(domainStartsAt) + domainStartsAt.length)
53 | return hostAndPath.substr(hostAndPath.indexOf(routeBase) + routeBase.length - 1).replace(/\/$/, '')
54 | }
55 | return null
56 | }
57 |
58 | export const createOidcUserManager = (oidcSettings) => {
59 | const oidcConfig = getOidcConfig(oidcSettings)
60 | requiredConfigProperties.forEach((requiredProperty) => {
61 | if (!oidcConfig[requiredProperty]) {
62 | throw new Error('Required oidc setting ' + requiredProperty + ' missing for creating UserManager')
63 | }
64 | })
65 | return new UserManager(oidcConfig)
66 | }
67 |
68 | export const addUserManagerEventListener = (oidcUserManager, eventName, eventListener) => {
69 | const addFnName = 'add' + firstLetterUppercase(eventName)
70 | if (typeof oidcUserManager.events[addFnName] === 'function' && typeof eventListener === 'function') {
71 | oidcUserManager.events[addFnName](eventListener)
72 | }
73 | }
74 |
75 | export const removeUserManagerEventListener = (oidcUserManager, eventName, eventListener) => {
76 | const removeFnName = 'remove' + firstLetterUppercase(eventName)
77 | if (typeof oidcUserManager.events[removeFnName] === 'function' && typeof eventListener === 'function') {
78 | oidcUserManager.events[removeFnName](eventListener)
79 | }
80 | }
81 |
82 | export const processSilentSignInCallback = (oidcSettings) => {
83 | return createOidcUserManager(oidcSettings).signinSilentCallback()
84 | }
85 |
86 | export const processSignInCallback = (oidcSettings) => {
87 | return new Promise((resolve, reject) => {
88 | const oidcUserManager = createOidcUserManager(oidcSettings)
89 | oidcUserManager.signinRedirectCallback()
90 | .then(user => {
91 | resolve(sessionStorage.getItem('vuex_oidc_active_route') || '/')
92 | })
93 | .catch(err => {
94 | reject(err)
95 | })
96 | })
97 | }
98 |
99 | export const tokenExp = (token) => {
100 | if (token) {
101 | const parsed = parseJwt(token)
102 | return parsed.exp ? parsed.exp * 1000 : null
103 | }
104 | return null
105 | }
106 |
107 | export const tokenIsExpired = (expiresAt) => {
108 | if (expiresAt) {
109 | return expiresAt < new Date().getTime()
110 | }
111 | return false
112 | }
113 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | import { ActionContext, Module, Store } from 'vuex';
2 | import { OidcClientSettings, User, UserManager } from 'oidc-client-ts';
3 | import { Route, RouteConfig } from 'vue-router';
4 |
5 | export interface VuexOidcClientSettings extends OidcClientSettings {
6 | authority: string;
7 | clientId?: string;
8 | clientSecret?: string;
9 | redirectUri?: string;
10 | responseType: string;
11 | scope: string;
12 | maxAge?: string;
13 | uiLocales?: string;
14 | loginHint?: string;
15 | acrValues?: string;
16 | postLogoutRedirectUri?: string;
17 | popupRedirectUri?: string;
18 | silentRedirectUri?: string;
19 | automaticSilentRenew?: boolean;
20 | automaticSilentSignin?: boolean;
21 | extraQueryParams?: Record;
22 | }
23 |
24 | export interface VuexOidcSigninRedirectOptions {
25 | useReplaceToNavigate?: boolean;
26 | skipUserInfo?: boolean;
27 | extraQueryParams?: Record;
28 | }
29 |
30 | export interface VuexOidcSigninSilentOptions {}
31 |
32 | export interface VuexOidcSigninPopupOptions {}
33 |
34 | export interface VuexOidcStoreSettings {
35 | namespaced?: boolean;
36 | dispatchEventsOnWindow?: boolean;
37 | isPublicRoute?: (route: Route) => boolean;
38 | publicRoutePaths?: string[];
39 | routeBase?: string;
40 | defaultSigninRedirectOptions?: VuexOidcSigninRedirectOptions;
41 | defaultSigninSilentOptions?: VuexOidcSigninSilentOptions;
42 | defaultSigninPopupOptions?: VuexOidcSigninPopupOptions;
43 | isAuthenticatedBy?: 'access_token'|'id_token';
44 | removeUserWhenTokensExpire?: boolean
45 | }
46 |
47 | export interface VuexOidcErrorPayload {
48 | context: string,
49 | error: any
50 | }
51 |
52 | export interface VuexOidcStoreListeners {
53 | userLoaded?: (user: User) => void;
54 | userUnloaded?: () => void;
55 | accessTokenExpiring?: () => void;
56 | accessTokenExpired?: () => void;
57 | silentRenewError?: () => void;
58 | userSignedOut?: () => void;
59 | oidcError?: (payload?: VuexOidcErrorPayload) => void;
60 | automaticSilentRenewError?: (payload?: VuexOidcErrorPayload) => void;
61 | }
62 |
63 | export interface VuexOidcState {
64 | access_token: string | null;
65 | id_token: string | null;
66 | user: any | null;
67 | expires_at: number | null;
68 | scopes: string[] | null;
69 | is_checked: boolean;
70 | events_are_bound: boolean;
71 | error: string | null;
72 | }
73 |
74 | export function vuexOidcCreateUserManager(settings: VuexOidcClientSettings): UserManager;
75 |
76 | export function vuexOidcCreateStoreModule(
77 | settings: VuexOidcClientSettings,
78 | storeSettings?: VuexOidcStoreSettings,
79 | listeners?: VuexOidcStoreListeners,
80 | ): Module;
81 |
82 | export function vuexOidcCreateNuxtRouterMiddleware(namespace?: string): any;
83 |
84 | export function vuexOidcCreateRouterMiddleware(store: Store, namespace?: string): any;
85 |
86 | export function vuexOidcProcessSilentSignInCallback(settings: VuexOidcClientSettings): Promise;
87 |
88 | export function vuexOidcProcessSignInCallback(settings: VuexOidcClientSettings): void;
89 |
90 | export function vuexOidcGetOidcCallbackPath(callbackUri: string, routeBase?: string): void;
91 |
92 | export namespace vuexOidcUtils {
93 | export function objectAssign(objs: any[]): any;
94 |
95 | export function parseJwt(jwt: string): T;
96 |
97 | export function firstLetterUppercase(str: string): string;
98 |
99 | export function camelCaseToSnakeCase(str: string): string;
100 | }
101 |
102 | export function vuexDispatchCustomBrowserEvent(
103 | name: string,
104 | detail?: T,
105 | params?: EventInit,
106 | ): any;
107 |
108 | // The following types are not exposed directly, they are part of the store
109 | // and mostly for reference, or for use with vuex-class.
110 |
111 | export interface VuexOidcStoreGetters {
112 | readonly oidcIsAuthenticated: boolean;
113 | readonly oidcUser: any | null;
114 | readonly oidcAccessToken: string | null;
115 | readonly oidcAccessTokenExp: number | null;
116 | readonly oidcScopes: string[] | null;
117 | readonly oidcIdToken: string | null;
118 | readonly oidcIdTokenExp: number | null;
119 | readonly oidcAuthenticationIsChecked: boolean | null;
120 | readonly oidcError: string | null;
121 | readonly oidcIsRoutePublic: (route: RouteConfig) => boolean;
122 | }
123 |
124 | export interface VuexOidcStoreActions {
125 | oidcCheckAccess: (route: Route) => Promise;
126 | authenticateOidc: (payload?: string | { [key: string]: any }) => Promise;
127 | authenticateOidcSilent: (payload?: { options?: VuexOidcSigninSilentOptions }) => Promise;
128 | authenticateOidcPopup: (payload?: { options?: VuexOidcSigninPopupOptions }) => Promise;
129 | oidcSignInCallback: (url?: string) => Promise;
130 | oidcSignInPopupCallback: (url?: string) => Promise;
131 | oidcWasAuthenticated: (user: User) => void;
132 | getOidcUser: () => Promise;
133 | addOidcEventListener: (payload: {
134 | eventName: string;
135 | eventListener: (...args: any[]) => void;
136 | }) => void;
137 | removeOidcEventListener: (payload: {
138 | eventName: string;
139 | eventListener: (...args: any[]) => void;
140 | }) => void;
141 | signOutOidc: (payload?: object) => void;
142 | signOutOidcCallback: () => void;
143 | signOutPopupOidc: (payload?: object) => void;
144 | signOutPopupOidcCallback: () => void;
145 | signOutOidcSilent: (payload?: object) => Promise;
146 | storeOidcUser: (user: User) => void;
147 | removeUser: () => void;
148 | removeOidcUser: () => void;
149 | clearStaleState: () => void;
150 | }
151 |
152 | export interface VuexOidcStoreMutations {
153 | setOidcAuth: (user: User) => void;
154 | setOidcUser: (user: User) => void;
155 | unsetOidcAuth: () => void;
156 | setOidcAuthIsChecked: () => void;
157 | setOidcEventsAreBound: () => void;
158 | setOidcError: (err: Error | string | null) => void;
159 | }
160 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # vuex-oidc changelog
2 |
3 | ## 4.0.2
4 | *2023-04-01*
5 |
6 | ### Fixes
7 | * Change ts interface to be compatible with oidc-client-ts interface
8 |
9 | ## 4.0.1
10 | *2022-12-22*
11 |
12 | ### Fixes
13 | * Change import in typings to oidc-client-ts
14 | * Add vue-router to peer dependencies
15 |
16 | ## 4.0.0
17 | *2022-12-18*
18 |
19 | ### Breaking Changes
20 | * oidc-client-ts instead of oidc-client is now a required peer dependency
21 | * The Implicit Flow is no longer supported, Authorization Code Flow with PKCE is the only supported OAuth flow type
22 | * `vuexOidcProcessSilentSignInCallback`, which previously took no arguments, now needs the oidcSettings as an argument.
23 |
24 | ## 3.11.0
25 | *2022-06-30*
26 |
27 | ### Features
28 | * Stop relying on exp claim to check if access tokens are expired
29 | * Allow not removing user when tokens expire
30 |
31 | ## 3.10.2
32 | *2021-02-09*
33 |
34 | ### Fixes
35 | * Add missing typing
36 |
37 | ## 3.10.1
38 | *2020-10-09*
39 |
40 | ### Fixes
41 | * Check expired user when signing in silently in check access
42 | * Add error payloads to typings
43 |
44 | ## 3.10.0
45 | *2020-09-17*
46 |
47 | ### Features
48 | * Support nuxt route meta arrays
49 |
50 | ### Fixes
51 | * Fix storeSettings.isPublicRoute being ignored when publicRoutePaths is set
52 | * Ignore oidcError in some silent authentications
53 |
54 | ## 3.9.8
55 | *2020-09-14*
56 |
57 | ### Fixes
58 | * Fix for automaticSilentRenew and automaticSilentSignin
59 |
60 | ## 3.9.7
61 | *2020-09-06*
62 |
63 | ### Fixes
64 | * Add automaticSilentRenewError event
65 |
66 | ## 3.9.6
67 | *2020-09-02*
68 |
69 | ### Fixes
70 | * Fix silentSignOut not removing user from storage
71 | * Minor type script fixes of action typings
72 |
73 | ## 3.9.5
74 | *2020-09-02*
75 |
76 | ### Fixes
77 | * Fix authenticateOidcSilent not getting rejected if it fails
78 |
79 | ## 3.9.4
80 | *2020-09-02*
81 |
82 | ### Fixes
83 | * Fix error in silentSignOut
84 |
85 | ## 3.9.3
86 | *2020-09-02*
87 |
88 | ### Fixes
89 | * Fix error in silentSignOut
90 |
91 | ## 3.9.2
92 | *2020-09-01*
93 |
94 | ### Fixes
95 | * Send id_token_hint in signOutOidcSilent
96 |
97 | ## 3.9.1
98 | *2020-08-29*
99 |
100 | ### Fixes
101 | * silent_redirect_uri is always public if it is an app route
102 |
103 | ## 3.9.0
104 | *2020-08-29*
105 |
106 | ### Features
107 | * vuexOidcProcessSilentSignInCallback returns promise
108 |
109 | ## 3.8.0
110 | *2020-08-18*
111 |
112 | ### Features
113 | * signOutOidcSilent action added
114 |
115 | ## 3.7.2
116 | *2020-08-18*
117 |
118 | ### Fixes
119 | * Change import of oidc-client
120 |
121 | ## 3.7.1
122 | *2020-08-17*
123 |
124 | ### Bug fixes
125 | * Fix authenticateOidcSilent action not returning promise
126 | * Fix incorrect type script type for getOidcUser action
127 |
128 | ## 3.7.0
129 | *2020-08-14*
130 |
131 | ### Feature
132 | * Attempt silent signin on protected routes
133 |
134 | ## 3.6.0
135 | *2020-08-14*
136 |
137 | ### Chore
138 | * Update Babel
139 | * Update oidc-client
140 |
141 | ## 3.5.3
142 | *2020-08-05*
143 |
144 | ### Fixes
145 | * Fix type script inconsistencies
146 |
147 | ## 3.5.2
148 | *2020-08-05*
149 |
150 | ### Bug fixes
151 | * Fix type error when dispatching getOidcUser if there is no user
152 |
153 | ## 3.5.1
154 | *2020-04-21*
155 |
156 | ### Features
157 | * Add 2 missing type script typings
158 |
159 | ## 3.5.0
160 | *2020-04-16*
161 |
162 | ### Features
163 | * Add type script typings
164 | * isAuthenticatedBy setting that can use access_token for isAuthenticated getter and access checker
165 | * Store refresh token in store
166 |
167 | ## 3.4.3
168 | *2020-03-11*
169 |
170 | ### Features
171 | * Add storeOidcUser action
172 | * Add clearStaleState action
173 |
174 | ## 3.4.2
175 | *2020-03-11*
176 |
177 | ### Features
178 | * Add signOutOidcCallback, signOutPopupOidc and signOutPopupOidcCallback actions
179 |
180 | ## 3.4.1
181 | *2020-02-06*
182 |
183 | ### Features
184 | * Add automaticSilentSignin option to config
185 |
186 | ## 3.4.0
187 | *2019-12-30*
188 |
189 | ### Chore
190 | * Update dependencies
191 |
192 | ### Features
193 | * Add payload to signoutOidc which is passed on as args to signoutRedirect
194 |
195 | ## 3.3.1
196 | *2019-11-06*
197 |
198 | ### Chore
199 | * Implementing linting with StandardJS
200 | * Remove vue-router as peer dependency
201 |
202 | ## 3.3.0
203 | *2019-10-22*
204 |
205 | ### Features
206 | * Implement signinPopup with authenticateOidcPopup action
207 |
208 | ### Chore
209 | * Change name of removeUser action to removeOidcUser. removeUser is still a synonym
210 |
211 | ## 3.2.0
212 | *2019-10-17*
213 |
214 | ### Features
215 | * Allow options for authenticateOidc and authenticateOidcSilent actions
216 |
217 | ## 3.1.6
218 | *2019-10-12*
219 |
220 | ### Features
221 | * Return promise in getOidcUser
222 |
223 | ## 3.1.5
224 | *2019-09-23*
225 |
226 | ### Features
227 | * Add oidcError event
228 |
229 | ## 3.1.4
230 | *2019-09-22*
231 |
232 | ### Features
233 | * Add removeUser action to have a client side signout
234 |
235 | ## 3.1.3
236 | *2019-09-22*
237 |
238 | ### Features
239 | * Remove special handling of router hash mode
240 |
241 | ## 3.1.2
242 | *2019-09-10*
243 |
244 | ### Features
245 | * Fix payload in window events
246 |
247 | ## 3.1.1
248 | *2019-09-03*
249 |
250 | ### Features
251 | * Add url paramater to oidcSignInCallback action
252 |
253 | ## 3.1.0
254 | *2019-09-01*
255 |
256 | ### Features
257 | * Enable support for vue-router hash mode.
258 |
259 | ## 3.0.0
260 | *2019-08-15*
261 |
262 | ### Breaking Changes
263 | * oidc-client is now a peer dependency, and it needs to be installed separately.
264 |
265 | ### Chore
266 | * Upgrade dev dependencies, to Babel 7 and Rollup 1.
267 |
268 | ## 2.0.4
269 | *2019-07-29*
270 |
271 | ### Features
272 | * Add isPublicRoute option to store in order to customize check from client.
273 |
274 | ## 2.0.3
275 | *2019-07-29*
276 |
277 | ### Bug fixes
278 | * publicRoutePaths works with trailing slashes.
279 |
280 | ## 2.0.2
281 | *2019-07-16*
282 |
283 | ### Bug fixes
284 | * Fix getOidcCallbackPath for trailing slash and routeBase + add tests.
285 |
286 | ## 2.0.1
287 | *2019-05-29*
288 |
289 | ### Features
290 | * Implemented scopes retrieval.
291 |
292 | ## 2.0.0
293 | *2019-05-11*
294 |
295 | ### Features
296 | * Nuxt support added.
297 |
298 | ## 1.15.3
299 | *2019-03-31*
300 |
301 | ### Bug fixes
302 | * Fix error on expiration events.
303 |
304 | ## 1.15.2
305 | *2019-03-28*
306 |
307 | ### Features
308 | * Dispatch userLoaded event when user is loaded from storage.
309 | * Make sure auto silent renew is starting after user is loaded from storage.
310 |
311 | ## 1.15.1
312 | *2019-03-27*
313 |
314 | ### Chore
315 | * Control minor version of oidc-client.
316 |
317 | ## 1.15.0
318 | *2019-03-25*
319 |
320 | ### Features
321 | * Check access checks userManager before reauthenticating.
322 |
323 | ## 1.14.0
324 | *2019-03-12*
325 |
326 | ### Chore
327 | * Translate settings from camelCase to snake_case.
328 |
329 | ## 1.13.0
330 | *2019-02-12*
331 |
332 | ### Chore
333 | * Update dependencies.
334 |
335 | ## 1.12.1
336 | *2018-12-06*
337 |
338 | ### Bug fixes
339 | * Catch silent auth error.
340 |
341 | ## 1.12.0
342 | *2018-12-04*
343 |
344 | ### Chore
345 | * Implement Travis CI.
346 |
347 | ### Features
348 | * Let oidc-client check expiration.
349 |
350 | ## 1.11.3
351 | *2018-11-15*
352 |
353 | ### Chore
354 | * Update oidc-client.
355 |
356 | ## 1.11.2
357 | *2018-11-09*
358 |
359 | ### Features
360 | * Only dispatch window events if setting is true.
361 |
362 | ## 1.11.1
363 | *2018-11-08*
364 |
365 | ### Chore
366 | * Start linting.
367 |
368 | ## 1.11.0
369 | *2018-11-09*
370 |
371 | ### Features
372 | * Check user in oidc-client when checking access
373 | * Sign out user in store if signed out in oidc-client
374 | * Dispatch browser events for each oidc-client event.
375 |
376 | ## 1.10.5
377 | *2018-10-29*
378 |
379 | ### Bug fixes
380 | * Fix bug in process silent renew.
381 |
382 | ## 1.10.4
383 | *2018-10-29*
384 |
385 | ### Bug fixes
386 | * Fix expiration check.
387 |
388 | ## 1.10.3
389 | *2018-10-29*
390 |
391 | ### Features
392 | * Implement oidc automatic renewal within vuex.
393 |
394 | ## 1.10.2
395 | *2018-10-29*
396 |
397 | ### Bug fixes
398 | * Fix expiration date.
399 |
400 | ## 1.10.1
401 | *2018-10-29*
402 |
403 | ### Bug fixes
404 | * Handle unexpected tokens.
405 |
406 | ## 1.10.0
407 | *2018-10-26*
408 |
409 | ### Features
410 | * Change interface for events.
411 |
--------------------------------------------------------------------------------
/test/create-store-module.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const sinon = require('sinon');
3 | const sinonSandbox = sinon.createSandbox();
4 | const oidcConfig = require('./oidcTestConfig');
5 | let vuexOidc;
6 | let storeModule;
7 | let UserManager;
8 |
9 | describe('createStoreModule', function() {
10 | before(function () {
11 | vuexOidc = require('../dist/vuex-oidc.cjs');
12 | storeModule = vuexOidc.vuexOidcCreateStoreModule(oidcConfig, {dispatchEventsOnWindow: true});
13 | UserManager = require('oidc-client-ts').UserManager;
14 | });
15 |
16 | afterEach(function () {
17 | sinonSandbox.restore();
18 | });
19 |
20 | it('factory function should return a vuex module object', function() {
21 | assert.equal(typeof storeModule, 'object');
22 | assert.equal(typeof storeModule.state, 'object');
23 | assert.equal(typeof storeModule.getters, 'object');
24 | assert.equal(typeof storeModule.actions, 'object');
25 | assert.equal(typeof storeModule.mutations, 'object');
26 | });
27 |
28 | describe('.actions.oidcCheckAccess', function() {
29 | describe('check public routes', function() {
30 | it('should resolve true for public routes if authenticated', function() {
31 | const context = authenticatedContext();
32 | return storeModule.actions.oidcCheckAccess(context, publicRoute())
33 | .then(function(hasAccess) {
34 | assert.equal(hasAccess, true);
35 | })
36 | });
37 | it('should resolve true for public routes if not authenticated', function() {
38 | const context = unAuthenticatedContext();
39 | return storeModule.actions.oidcCheckAccess(context, publicRoute())
40 | .then(function(hasAccess) {
41 | assert.equal(hasAccess, true);
42 | })
43 | });
44 | });
45 | describe('check callback routes', function() {
46 | it('should resolve true for oidcCallbackRoutes routes if authenticated', function() {
47 | return storeModule.actions.oidcCheckAccess(authenticatedContext(), oidcCallbackRoute())
48 | .then(function(hasAccess) {
49 | assert.equal(hasAccess, true);
50 | })
51 | });
52 | it('should resolve true for oidcCallbackRoutes routes if not authenticated', function() {
53 | return storeModule.actions.oidcCheckAccess(unAuthenticatedContext(), oidcCallbackRoute())
54 | .then(function(hasAccess) {
55 | assert.equal(hasAccess, true);
56 | })
57 | });
58 | });
59 | describe('check silent callback routes', function() {
60 | it('should resolve true for oidcSilentCallbackRoutes routes if authenticated', function() {
61 | return storeModule.actions.oidcCheckAccess(authenticatedContext(), oidcSilentCallbackRoute())
62 | .then(function(hasAccess) {
63 | assert.equal(hasAccess, true);
64 | })
65 | });
66 | it('should resolve true for oidcSilentCallbackRoutes routes if not authenticated', function() {
67 | return storeModule.actions.oidcCheckAccess(unAuthenticatedContext(), oidcSilentCallbackRoute())
68 | .then(function(hasAccess) {
69 | assert.equal(hasAccess, true);
70 | })
71 | });
72 | });
73 | describe('check protected routes', function() {
74 | it('should resolve false for protected routes if not authenticated, and also dispatch auth redirect action', function() {
75 | const context = unAuthenticatedContext();
76 | sinon.spy(context, 'dispatch');
77 | sinonSandbox.stub(UserManager.prototype, 'signinSilent').callsFake(silentSigningFailedPromise);
78 | sinonSandbox.stub(UserManager.prototype, 'getUser').callsFake(getNoUserPromise);
79 | return storeModule.actions.oidcCheckAccess(context, protectedRoute())
80 | .then(function(hasAccess) {
81 | assert.equal(hasAccess, false);
82 | assert.equal(context.dispatch.calledWith('authenticateOidc'), true);
83 | context.dispatch.restore();
84 | })
85 | });
86 | it('should resolve true for protected routes if authenticated in both vuex and in storage', function() {
87 | const context = authenticatedContext();
88 | sinonSandbox.stub(UserManager.prototype, 'getUser').callsFake(getUserPromise);
89 | return storeModule.actions.oidcCheckAccess(context, protectedRoute())
90 | .then(function(hasAccess) {
91 | assert.equal(hasAccess, true);
92 | })
93 | });
94 | it('should resolve true for protected routes if not authenticated in vuex, but in storage.', function() {
95 | const context = unAuthenticatedContext();
96 | sinonSandbox.stub(UserManager.prototype, 'getUser').callsFake(getUserPromise);
97 | return storeModule.actions.oidcCheckAccess(context, protectedRoute())
98 | .then(function(hasAccess) {
99 | assert.equal(hasAccess, true);
100 | })
101 | });
102 | it('should resolve false for protected routes if authenticated in vuex, but not in storage. Also signout user from vuex and dispatch auth redirect action.', function() {
103 | const context = authenticatedContext();
104 | sinon.spy(context, 'dispatch');
105 | sinon.spy(context, 'commit');
106 | sinonSandbox.stub(UserManager.prototype, 'signinSilent').callsFake(silentSigningFailedPromise);
107 | sinonSandbox.stub(UserManager.prototype, 'getUser').callsFake(getNoUserPromise);
108 | return storeModule.actions.oidcCheckAccess(context, protectedRoute())
109 | .then(function(hasAccess) {
110 | assert.equal(hasAccess, false);
111 | assert.equal(context.commit.calledWith('unsetOidcAuth'), true);
112 | assert.equal(context.dispatch.calledWith('authenticateOidc'), true);
113 | context.commit.restore();
114 | })
115 | });
116 | });
117 | });
118 |
119 | describe('.actions.oidcWasAuthenticated', function() {
120 | it('should set user in store and bind events', function() {
121 | const context = unAuthenticatedContext();
122 | sinon.spy(context, 'commit');
123 | storeModule.actions.oidcWasAuthenticated(context, oidcUser());
124 | assert.equal(context.commit.getCall(0).args[0], 'setOidcAuth');
125 | assert.equal(context.commit.getCall(0).args[1].id_token, oidcUser().id_token);
126 | assert.equal(context.commit.getCall(1).args[0], 'setOidcEventsAreBound');
127 | context.commit.restore();
128 | });
129 | });
130 |
131 | describe('.actions.oidcSignInCallback', function() {
132 | it('callback sets error if state is not found in store', function() {
133 | const context = unAuthenticatedContext();
134 | sinon.spy(context, 'commit');
135 | return storeModule.actions.oidcSignInCallback(context, oidcUser())
136 | .then(function(redirectUrl) {
137 | assert.equal(redirectUrl, false);
138 | context.commit.restore();
139 | })
140 | .catch(function(error) {
141 | assert.equal(typeof error, 'object');
142 | context.commit.restore();
143 | });
144 | });
145 | });
146 |
147 | describe('.actions.authenticateOidc', function() {
148 | it('performs signinRedirect with empty arguments by default', function() {
149 | const context = unAuthenticatedContext();
150 | const payload = {
151 | redirectPath: '/'
152 | };
153 | sinonSandbox.stub(UserManager.prototype, 'signinRedirect').callsFake(resolveArgumentsPromise);
154 | return context.actions.authenticateOidc(context, payload)
155 | .then(function(signinRedirectOptions) {
156 | assert.equal(typeof signinRedirectOptions, 'object');
157 | assert.equal(Object.keys(signinRedirectOptions).length, 0);
158 | });
159 | });
160 | it('performs signinRedirect with arguments if specified in payload', function() {
161 | const context = unAuthenticatedContext();
162 | const payload = {
163 | redirectPath: '/',
164 | options: {
165 | useReplaceToNavigate: true
166 | }
167 | };
168 | sinonSandbox.stub(UserManager.prototype, 'signinRedirect').callsFake(resolveArgumentsPromise);
169 | return context.actions.authenticateOidc(context, payload)
170 | .then(function(signinRedirectOptions) {
171 | assert.equal(typeof signinRedirectOptions, 'object');
172 | assert.equal(signinRedirectOptions.useReplaceToNavigate, true);
173 | });
174 | });
175 | it('performs signinRedirect with arguments if specified in default options', function() {
176 | const context = unAuthenticatedContext({
177 | defaultSigninRedirectOptions: {
178 | useReplaceToNavigate: true
179 | }
180 | });
181 | const payload = {
182 | redirectPath: '/'
183 | };
184 | sinonSandbox.stub(UserManager.prototype, 'signinRedirect').callsFake(resolveArgumentsPromise);
185 | return context.actions.authenticateOidc(context, payload)
186 | .then(function(signinRedirectOptions) {
187 | assert.equal(typeof signinRedirectOptions, 'object');
188 | assert.equal(signinRedirectOptions.useReplaceToNavigate, true);
189 | });
190 | });
191 | });
192 |
193 | describe('.getters.oidcIsRoutePublic', function() {
194 | it('should not call isPublicRoute when not a function', function() {
195 | const route = {
196 | path: '/',
197 | meta: {}
198 | };
199 | storeModule = vuexOidc.vuexOidcCreateStoreModule(oidcConfig, {isPublicRoute: 'not a function'});
200 | assert.equal(storeModule.getters.oidcIsRoutePublic(storeModule.state)(route), false);
201 | });
202 |
203 | it('should call isPublicRoute', function() {
204 | const route = {
205 | path: '/',
206 | meta: {}
207 | };
208 | const isPublicRoute = sinon.stub().returns(true);
209 |
210 | storeModule = vuexOidc.vuexOidcCreateStoreModule(oidcConfig, {isPublicRoute});
211 | assert.equal(storeModule.getters.oidcIsRoutePublic(storeModule.state)(route), true);
212 | assert.equal(isPublicRoute.calledWith(route), true);
213 | });
214 | });
215 | });
216 |
217 | function authenticatedContext(storeSettings = {}) {
218 | const context = Object.assign(vuexOidc.vuexOidcCreateStoreModule(oidcConfig, storeSettings, { oidcError: oidcMockOidcError }), {
219 | commit: function(mutation, payload) {},
220 | dispatch: function(action, payload) {}
221 | });
222 | context.state = Object.assign({}, context.state, oidcUser());
223 | return context;
224 | }
225 |
226 | function unAuthenticatedContext(storeSettings = {}) {
227 | return Object.assign(vuexOidc.vuexOidcCreateStoreModule(oidcConfig, storeSettings, { oidcError: oidcMockOidcError }), {
228 | commit: function(mutation, payload) {},
229 | dispatch: function(action, payload) {}
230 | });
231 | }
232 |
233 | function publicRoute() {
234 | return {
235 | path: '/',
236 | meta: {
237 | isPublic: true
238 | }
239 | }
240 | }
241 |
242 | function oidcCallbackRoute() {
243 | return {
244 | meta: {
245 | isOidcCallback: true
246 | }
247 | }
248 | }
249 |
250 | function oidcSilentCallbackRoute() {
251 | const silentUriParts = oidcConfig.silent_redirect_uri.split('/')
252 | return {
253 | path: `/${silentUriParts[silentUriParts.length - 1]}`,
254 | meta: {
255 | }
256 | }
257 | }
258 |
259 | function protectedRoute() {
260 | return {
261 | path: '/protected',
262 | meta: {
263 | }
264 | }
265 | }
266 |
267 | function getUserPromise() {
268 | return new Promise(function(resolve) {
269 | resolve(oidcUser());
270 | });
271 | }
272 |
273 | function getNoUserPromise() {
274 | return new Promise(function(resolve) {
275 | resolve(null);
276 | });
277 | }
278 |
279 | function silentSigningFailedPromise() {
280 | return new Promise(function(resolve, reject) {
281 | reject(new Error('No user'));
282 | });
283 | }
284 |
285 | function oidcUser() {
286 | return {
287 | id_token: require('./id-token-2028-01-01')
288 | }
289 | }
290 |
291 | function resolveArgumentsPromise(argument) {
292 | return new Promise(function(resolve) {
293 | resolve(argument);
294 | });
295 | }
296 |
297 | function oidcMockOidcError() {
298 | }
299 |
--------------------------------------------------------------------------------
/src/store/create-store-module.js:
--------------------------------------------------------------------------------
1 | import { objectAssign } from '../services/utils'
2 | import { getOidcConfig, getOidcCallbackPath, createOidcUserManager, addUserManagerEventListener, removeUserManagerEventListener, tokenIsExpired, tokenExp } from '../services/oidc-helpers'
3 | import { dispatchCustomBrowserEvent } from '../services/browser-event'
4 | import { openUrlWithIframe } from '../services/navigation'
5 |
6 | export default (oidcSettings, storeSettings = {}, oidcEventListeners = {}) => {
7 | const oidcConfig = getOidcConfig(oidcSettings)
8 | const oidcUserManager = createOidcUserManager(oidcSettings)
9 | storeSettings = objectAssign([
10 | {
11 | namespaced: false,
12 | isAuthenticatedBy: 'id_token',
13 | removeUserWhenTokensExpire: true
14 | },
15 | storeSettings
16 | ])
17 | const oidcCallbackPath = getOidcCallbackPath(oidcConfig.redirect_uri, storeSettings.routeBase || '/')
18 | const oidcPopupCallbackPath = getOidcCallbackPath(oidcConfig.popup_redirect_uri, storeSettings.routeBase || '/')
19 | const oidcSilentCallbackPath = getOidcCallbackPath(oidcConfig.silent_redirect_uri, storeSettings.routeBase || '/')
20 |
21 | // Add event listeners passed into factory function
22 | Object.keys(oidcEventListeners).forEach(eventName => {
23 | addUserManagerEventListener(oidcUserManager, eventName, oidcEventListeners[eventName])
24 | })
25 |
26 | if (storeSettings.dispatchEventsOnWindow) {
27 | // Dispatch oidc-client events on window (if in browser)
28 | const userManagerEvents = [
29 | 'userLoaded',
30 | 'userUnloaded',
31 | 'accessTokenExpiring',
32 | 'accessTokenExpired',
33 | 'silentRenewError',
34 | 'userSignedOut'
35 | ]
36 | userManagerEvents.forEach(eventName => {
37 | addUserManagerEventListener(oidcUserManager, eventName, (detail) => {
38 | dispatchCustomBrowserEvent(eventName, detail || {})
39 | })
40 | })
41 | }
42 |
43 | const state = {
44 | access_token: null,
45 | id_token: null,
46 | refresh_token: null,
47 | user: null,
48 | expires_at: null,
49 | scopes: null,
50 | is_checked: false,
51 | events_are_bound: false,
52 | error: null
53 | }
54 |
55 | const isAuthenticated = (state) => {
56 | if (state[storeSettings.isAuthenticatedBy]) {
57 | return true
58 | }
59 |
60 | return false
61 | }
62 |
63 | const authenticateOidcSilent = (context, payload = {}) => {
64 | // Take options for signinSilent from 1) payload or 2) storeSettings if defined there
65 | const options = payload.options || storeSettings.defaultSigninSilentOptions || {}
66 | return new Promise((resolve, reject) => {
67 | oidcUserManager.signinSilent(options)
68 | .then(user => {
69 | context.dispatch('oidcWasAuthenticated', user)
70 | resolve(user)
71 | })
72 | .catch(err => {
73 | context.commit('setOidcAuthIsChecked')
74 | if (payload.ignoreErrors) {
75 | resolve(null)
76 | } else {
77 | context.commit('setOidcError', errorPayload('authenticateOidcSilent', err))
78 | reject(err)
79 | }
80 | })
81 | })
82 | }
83 |
84 | const routeIsOidcCallback = (route) => {
85 | if (route.meta && route.meta.isOidcCallback) {
86 | return true
87 | }
88 | if (route.meta && Array.isArray(route.meta) && route.meta.reduce((isOidcCallback, meta) => meta.isOidcCallback || isOidcCallback, false)) {
89 | return true
90 | }
91 | if (route.path && route.path.replace(/\/$/, '') === oidcCallbackPath) {
92 | return true
93 | }
94 | if (route.path && route.path.replace(/\/$/, '') === oidcPopupCallbackPath) {
95 | return true
96 | }
97 | if (route.path && route.path.replace(/\/$/, '') === oidcSilentCallbackPath) {
98 | return true
99 | }
100 | return false
101 | }
102 |
103 | const routeIsPublic = (route) => {
104 | if (route.meta && route.meta.isPublic) {
105 | return true
106 | }
107 | if (route.meta && Array.isArray(route.meta) && route.meta.reduce((isPublic, meta) => meta.isPublic || isPublic, false)) {
108 | return true
109 | }
110 | if (storeSettings.publicRoutePaths && storeSettings.publicRoutePaths.map(path => path.replace(/\/$/, '')).indexOf(route.path.replace(/\/$/, '')) > -1) {
111 | return true
112 | }
113 | if (storeSettings.isPublicRoute && typeof storeSettings.isPublicRoute === 'function') {
114 | return storeSettings.isPublicRoute(route)
115 | }
116 | return false
117 | }
118 |
119 | /* istanbul ignore next */
120 | const getters = {
121 | oidcIsAuthenticated: (state) => {
122 | return isAuthenticated(state)
123 | },
124 | oidcUser: (state) => {
125 | return state.user
126 | },
127 | oidcAccessToken: (state) => {
128 | return tokenIsExpired(state.expires_at) ? null : state.access_token
129 | },
130 | oidcAccessTokenExp: (state) => {
131 | return state.expires_at
132 | },
133 | oidcScopes: (state) => {
134 | return state.scopes
135 | },
136 | oidcIdToken: (state) => {
137 | return storeSettings.removeUserWhenTokensExpire && tokenExp(state.expires_at) ? null : state.id_token
138 | },
139 | oidcIdTokenExp: (state) => {
140 | return tokenExp(state.id_token)
141 | },
142 | oidcRefreshToken: (state) => {
143 | return tokenIsExpired(state.refresh_token) ? null : state.refresh_token
144 | },
145 | oidcRefreshTokenExp: (state) => {
146 | return tokenExp(state.refresh_token)
147 | },
148 | oidcAuthenticationIsChecked: (state) => {
149 | return state.is_checked
150 | },
151 | oidcError: (state) => {
152 | return state.error
153 | },
154 | oidcIsRoutePublic: (state) => {
155 | return (route) => {
156 | return routeIsPublic(route)
157 | }
158 | }
159 | }
160 |
161 | const actions = {
162 | oidcCheckAccess (context, route) {
163 | return new Promise(resolve => {
164 | if (routeIsOidcCallback(route)) {
165 | resolve(true)
166 | return
167 | }
168 | let hasAccess = true
169 | const getUserPromise = new Promise(resolve => {
170 | oidcUserManager.getUser().then(user => {
171 | resolve(user)
172 | }).catch(() => {
173 | resolve(null)
174 | })
175 | })
176 | const isAuthenticatedInStore = isAuthenticated(context.state)
177 | getUserPromise.then(user => {
178 | if (!user || user.expired) {
179 | const authenticateSilently = oidcConfig.silent_redirect_uri && oidcConfig.automaticSilentSignin
180 | if (routeIsPublic(route)) {
181 | if (isAuthenticatedInStore) {
182 | context.commit('unsetOidcAuth')
183 | }
184 | if (authenticateSilently) {
185 | authenticateOidcSilent(context, { ignoreErrors: true })
186 | .catch(() => {})
187 | }
188 | } else {
189 | const authenticate = () => {
190 | if (isAuthenticatedInStore) {
191 | context.commit('unsetOidcAuth')
192 | }
193 | context.dispatch('authenticateOidc', {
194 | redirectPath: route.fullPath
195 | })
196 | }
197 | // If silent signin is set up, try to authenticate silently before denying access
198 | if (authenticateSilently) {
199 | authenticateOidcSilent(context, { ignoreErrors: true })
200 | .then(() => {
201 | oidcUserManager.getUser().then(user => {
202 | if (!user || user.expired) {
203 | authenticate()
204 | }
205 | resolve(!!user)
206 | }).catch(() => {
207 | authenticate()
208 | resolve(false)
209 | })
210 | })
211 | .catch(() => {
212 | authenticate()
213 | resolve(false)
214 | })
215 | return
216 | }
217 | // If no silent signin is set up, perform explicit authentication and deny access
218 | authenticate()
219 | hasAccess = false
220 | }
221 | } else {
222 | context.dispatch('oidcWasAuthenticated', user)
223 | if (!isAuthenticatedInStore) {
224 | if (oidcEventListeners && typeof oidcEventListeners.userLoaded === 'function') {
225 | oidcEventListeners.userLoaded(user)
226 | }
227 | if (storeSettings.dispatchEventsOnWindow) {
228 | dispatchCustomBrowserEvent('userLoaded', user)
229 | }
230 | }
231 | }
232 | resolve(hasAccess)
233 | })
234 | })
235 | },
236 | authenticateOidc (context, payload = {}) {
237 | if (typeof payload === 'string') {
238 | payload = { redirectPath: payload }
239 | }
240 | if (payload.redirectPath) {
241 | sessionStorage.setItem('vuex_oidc_active_route', payload.redirectPath)
242 | } else {
243 | sessionStorage.removeItem('vuex_oidc_active_route')
244 | }
245 | // Take options for signinRedirect from 1) payload or 2) storeSettings if defined there
246 | const options = payload.options || storeSettings.defaultSigninRedirectOptions || {}
247 | return oidcUserManager.signinRedirect(options).catch(err => {
248 | context.commit('setOidcError', errorPayload('authenticateOidc', err))
249 | })
250 | },
251 | oidcSignInCallback (context, url) {
252 | return new Promise((resolve, reject) => {
253 | oidcUserManager.signinRedirectCallback(url)
254 | .then(user => {
255 | context.dispatch('oidcWasAuthenticated', user)
256 | resolve(sessionStorage.getItem('vuex_oidc_active_route') || '/')
257 | })
258 | .catch(err => {
259 | context.commit('setOidcError', errorPayload('oidcSignInCallback', err))
260 | context.commit('setOidcAuthIsChecked')
261 | reject(err)
262 | })
263 | })
264 | },
265 | authenticateOidcSilent (context, payload = {}) {
266 | return authenticateOidcSilent(context, payload)
267 | },
268 | authenticateOidcPopup (context, payload = {}) {
269 | // Take options for signinPopup from 1) payload or 2) storeSettings if defined there
270 | const options = payload.options || storeSettings.defaultSigninPopupOptions || {}
271 | return oidcUserManager.signinPopup(options)
272 | .then(user => {
273 | context.dispatch('oidcWasAuthenticated', user)
274 | })
275 | .catch(err => {
276 | context.commit('setOidcError', errorPayload('authenticateOidcPopup', err))
277 | })
278 | },
279 | oidcSignInPopupCallback (context, url) {
280 | return new Promise((resolve, reject) => {
281 | oidcUserManager.signinPopupCallback(url)
282 | .catch(err => {
283 | context.commit('setOidcError', errorPayload('oidcSignInPopupCallback', err))
284 | context.commit('setOidcAuthIsChecked')
285 | reject(err)
286 | })
287 | })
288 | },
289 | oidcWasAuthenticated (context, user) {
290 | context.commit('setOidcAuth', user)
291 | if (!context.state.events_are_bound) {
292 | oidcUserManager.events.addAccessTokenExpired(() => {
293 | if (storeSettings.removeUserWhenTokensExpire) {
294 | context.commit('unsetOidcAuth')
295 | } else {
296 | context.commit('unsetOidcAccessToken')
297 | }
298 | })
299 | if (oidcSettings.automaticSilentRenew) {
300 | oidcUserManager.events.addAccessTokenExpiring(() => {
301 | authenticateOidcSilent(context)
302 | .catch((err) => {
303 | dispatchCustomErrorEvent('automaticSilentRenewError', errorPayload('authenticateOidcSilent', err))
304 | })
305 | })
306 | }
307 | context.commit('setOidcEventsAreBound')
308 | }
309 | context.commit('setOidcAuthIsChecked')
310 | },
311 | storeOidcUser (context, user) {
312 | return oidcUserManager.storeUser(user)
313 | .then(() => oidcUserManager.getUser())
314 | .then(user => context.dispatch('oidcWasAuthenticated', user))
315 | .then(() => {})
316 | .catch(err => {
317 | context.commit('setOidcError', errorPayload('storeOidcUser', err))
318 | context.commit('setOidcAuthIsChecked')
319 | throw err
320 | })
321 | },
322 | getOidcUser (context) {
323 | /* istanbul ignore next */
324 | return oidcUserManager.getUser().then(user => {
325 | context.commit('setOidcUser', user)
326 | return user
327 | })
328 | },
329 | addOidcEventListener (context, payload) {
330 | /* istanbul ignore next */
331 | addUserManagerEventListener(oidcUserManager, payload.eventName, payload.eventListener)
332 | },
333 | removeOidcEventListener (context, payload) {
334 | /* istanbul ignore next */
335 | removeUserManagerEventListener(oidcUserManager, payload.eventName, payload.eventListener)
336 | },
337 | signOutOidc (context, payload) {
338 | /* istanbul ignore next */
339 | return oidcUserManager.signoutRedirect(payload).then(() => {
340 | context.commit('unsetOidcAuth')
341 | })
342 | },
343 | signOutOidcCallback (context) {
344 | /* istanbul ignore next */
345 | return oidcUserManager.signoutRedirectCallback()
346 | },
347 | signOutPopupOidc (context, payload) {
348 | /* istanbul ignore next */
349 | return oidcUserManager.signoutPopup(payload).then(() => {
350 | context.commit('unsetOidcAuth')
351 | })
352 | },
353 | signOutPopupOidcCallback (context) {
354 | /* istanbul ignore next */
355 | return oidcUserManager.signoutPopupCallback()
356 | },
357 | signOutOidcSilent (context, payload) {
358 | /* istanbul ignore next */
359 | return new Promise((resolve, reject) => {
360 | try {
361 | oidcUserManager.getUser()
362 | .then((user) => {
363 | const args = objectAssign([
364 | payload || {},
365 | {
366 | id_token_hint: user ? user.id_token : null
367 | }
368 | ])
369 | if (payload && payload.id_token_hint) {
370 | args.id_token_hint = payload.id_token_hint
371 | }
372 | oidcUserManager.createSignoutRequest(args)
373 | .then((signoutRequest) => {
374 | openUrlWithIframe(signoutRequest.url)
375 | .then(() => {
376 | context.dispatch('removeOidcUser')
377 | resolve()
378 | })
379 | .catch((err) => reject(err))
380 | })
381 | .catch((err) => reject(err))
382 | })
383 | .catch((err) => reject(err))
384 | } catch (err) {
385 | reject(err)
386 | }
387 | })
388 | },
389 | removeUser (context) {
390 | /* istanbul ignore next */
391 | return context.dispatch('removeOidcUser')
392 | },
393 | removeOidcUser (context) {
394 | /* istanbul ignore next */
395 | return oidcUserManager.removeUser().then(() => {
396 | context.commit('unsetOidcAuth')
397 | })
398 | },
399 | clearStaleState () {
400 | return oidcUserManager.clearStaleState()
401 | },
402 | startSilentRenew () {
403 | return oidcUserManager.startSilentRenew()
404 | },
405 | stopSilentRenew () {
406 | return oidcUserManager.stopSilentRenew()
407 | }
408 | }
409 |
410 | /* istanbul ignore next */
411 | const mutations = {
412 | setOidcAuth (state, user) {
413 | state.id_token = user.id_token
414 | state.access_token = user.access_token
415 | state.refresh_token = user.refresh_token
416 | state.expires_at = user.expires_at ? user.expires_at * 1000 : null
417 | state.user = user.profile
418 | state.scopes = user.scopes
419 | state.error = null
420 | },
421 | setOidcUser (state, user) {
422 | state.user = user ? user.profile : null
423 | state.expires_at = user && user.expires_at ? user.expires_at * 1000 : null
424 | },
425 | unsetOidcAuth (state) {
426 | state.id_token = null
427 | state.access_token = null
428 | state.refresh_token = null
429 | state.user = null
430 | },
431 | unsetOidcAccessToken (state) {
432 | state.access_token = null
433 | state.refresh_token = null
434 | },
435 | setOidcAuthIsChecked (state) {
436 | state.is_checked = true
437 | },
438 | setOidcEventsAreBound (state) {
439 | state.events_are_bound = true
440 | },
441 | setOidcError (state, payload) {
442 | state.error = payload.error
443 | dispatchCustomErrorEvent('oidcError', payload)
444 | }
445 | }
446 |
447 | const errorPayload = (context, error) => {
448 | return {
449 | context,
450 | error: error && error.message ? error.message : error
451 | }
452 | }
453 |
454 | const dispatchCustomErrorEvent = (eventName, payload) => {
455 | // oidcError and automaticSilentRenewError are not UserManagement events, they are events implemeted in vuex-oidc,
456 | if (typeof oidcEventListeners[eventName] === 'function') {
457 | oidcEventListeners[eventName](payload)
458 | }
459 | if (storeSettings.dispatchEventsOnWindow) {
460 | dispatchCustomBrowserEvent(eventName, payload)
461 | }
462 | }
463 |
464 | const storeModule = objectAssign([
465 | storeSettings,
466 | {
467 | state,
468 | getters,
469 | actions,
470 | mutations
471 | }
472 | ])
473 |
474 | if (typeof storeModule.dispatchEventsOnWindow !== 'undefined') {
475 | delete storeModule.dispatchEventsOnWindow
476 | }
477 |
478 | return storeModule
479 | }
480 |
--------------------------------------------------------------------------------