├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .nvmrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.d.ts ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── main.js ├── router │ ├── create-nuxt-router-middleware.js │ └── create-router-middleware.js ├── services │ ├── browser-event.js │ ├── navigation.js │ ├── oidc-helpers.js │ └── utils.js └── store │ └── create-store-module.js └── test ├── browser-event.test.js ├── create-nuxt-router-middleware.test.js ├── create-router-middleware.test.js ├── create-store-module.test.js ├── id-token-2028-01-01.js ├── index.js ├── oidc-helper.test.js ├── oidcTestConfig.js ├── setup.js └── utils.test.js /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | .nyc_output 5 | .idea 6 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v14.16.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | [![Build Status](https://travis-ci.org/perarnborg/vuex-oidc.svg?branch=master)](https://travis-ci.org/perarnborg/vuex-oidc) 26 | 27 | ## License 28 | 29 | [MIT](LICENSE). 30 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-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 | -------------------------------------------------------------------------------- /test/id-token-2028-01-01.js: -------------------------------------------------------------------------------- 1 | module.exports = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwic3ViIjoiMjQ4Mjg5NzYxMDAxIiwiYXVkIjoiczZCaGRSa3F0MyIsIm5vbmNlIjoibi0wUzZfV3pBMk1qIiwiZXhwIjoxODMwMjk3NjAwLCJpYXQiOjEzMTEyODA5NzAsIm5hbWUiOiJKYW5lIERvZSIsImdpdmVuX25hbWUiOiJKYW5lIiwiZmFtaWx5X25hbWUiOiJEb2UiLCJnZW5kZXIiOiJmZW1hbGUiLCJiaXJ0aGRhdGUiOiIwMDAwLTEwLTMxIiwiZW1haWwiOiJqYW5lZG9lQGV4YW1wbGUuY29tIiwicGljdHVyZSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9qYW5lZG9lL21lLmpwZyIsImp0aSI6IjhiZTkzYTkwLWMwOGMtNDIzMC05YTUzLWM0MDA4YjVjZDIzOSJ9.9m0iXjjJT7t3LfmYBxYzK-A3LP_JUUGZTgntGTuzLwE' 2 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | require('./setup') 2 | 3 | require('./oidc-helper.test') 4 | require('./create-store-module.test') 5 | require('./utils.test') 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | --------------------------------------------------------------------------------