├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── .remarkrc ├── .snyk ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── book.json ├── bower.json ├── config.js ├── dist ├── amd │ ├── aurelia-authentication.js │ ├── authFilterValueConverter.js │ ├── authenticatedFilterValueConverter.js │ ├── authenticatedValueConverter.js │ └── index.js ├── aurelia-authentication.d.ts ├── aurelia-authentication.js ├── authFilterValueConverter.js ├── authenticatedFilterValueConverter.js ├── authenticatedValueConverter.js ├── commonjs │ ├── aurelia-authentication.js │ ├── authFilterValueConverter.js │ ├── authenticatedFilterValueConverter.js │ ├── authenticatedValueConverter.js │ └── index.js ├── es2015 │ ├── aurelia-authentication.js │ ├── authFilterValueConverter.js │ ├── authenticatedFilterValueConverter.js │ ├── authenticatedValueConverter.js │ └── index.js ├── index.d.ts ├── native-modules │ ├── aurelia-authentication.js │ ├── authFilterValueConverter.js │ ├── authenticatedFilterValueConverter.js │ ├── authenticatedValueConverter.js │ └── index.js └── system │ ├── aurelia-authentication.js │ ├── authFilterValueConverter.js │ ├── authenticatedFilterValueConverter.js │ ├── authenticatedValueConverter.js │ └── index.js ├── doc ├── CHANGELOG.md ├── Quick start.md ├── README.md ├── SUMMARY.md ├── api.json ├── api_authService.md ├── api_fetchConfig.md ├── auth0.md ├── baseConfig.md ├── configuration.md ├── installation.md ├── license.md ├── oidc.md ├── pictures │ ├── TokenViaDevelopmentTools.png │ └── authHeader.png ├── refresh_token.md └── usage.md ├── gulpfile.js ├── package-lock.json ├── package.json ├── spoonx.js ├── src ├── aurelia-authentication.js ├── authFilterValueConverter.js ├── authLock.js ├── authService.js ├── authenticateStep.js ├── authenticatedFilterValueConverter.js ├── authenticatedValueConverter.js ├── authentication.js ├── authorizeStep.js ├── baseConfig.js ├── fetchClientConfig.js ├── logger.js ├── oAuth1.js ├── oAuth2.js ├── popup.js └── storage.js ├── test ├── aurelia-authentication.spec.js ├── authService.spec.js ├── authenticateStep.spec.js ├── authentication.spec.js ├── baseConfig.spec.js ├── fetchClientConfig.spec.js ├── oAuth1.spec.js ├── oAuth2.spec.js ├── popup.spec.js ├── setup.js └── storage.spec.js └── typings.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # 2 space indentation 12 | [**.*] 13 | indent_style = space 14 | indent_size = 2 -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/spoonx-tools/.eslintrc.json", 3 | "rules": {} 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | jspm_packages 3 | bower_components 4 | .idea 5 | .DS_STORE 6 | build/reports 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | jspm_packages 2 | bower_components 3 | .idea -------------------------------------------------------------------------------- /.remarkrc: -------------------------------------------------------------------------------- 1 | { 2 | "output": true, 3 | "plugins": { 4 | "lint": { 5 | "maximum-line-length": false, 6 | "heading-style": "atx", 7 | "no-duplicate-headings": false, 8 | "no-undefined-references": false, 9 | "no-shortcut-reference-link": false, 10 | "no-heading-punctuation": ".,;:!", 11 | "list-item-indent": false 12 | } 13 | }, 14 | "settings": { 15 | "gfm": true, 16 | "bullet": "*", 17 | "closeAtx": false, 18 | "fences": true, 19 | "listItemIndent": "1", 20 | "rule": "-", 21 | "ruleRepetition": 10, 22 | "ruleSpaces": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | ignore: {} 2 | patch: {} 3 | version: v1 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '5.7.0' 4 | before_install: 5 | - npm install -g jspm 6 | - jspm config registries.github.auth UldPdmVyZGlqazpiNmQxNWE5MmMwNGFiZGM5MDFhMjEwOTU0NjdkMjBiMGJhNzE0OTE3= 7 | before_script: 8 | - jspm -v 9 | - jspm i 10 | - export DISPLAY=:99.0 11 | - sh -e /etc/init.d/xvfb start 12 | notifications: 13 | email: 14 | on_success: change 15 | on_failure: change 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We'd love for you to contribute and to make this project even better than it is today! 4 | If this interests you, please begin by reading our [contributing guidelines](https://github.com/SpoonX/about/blob/master/CONTRIBUTING.md). 5 | The [contributing document](https://github.com/SpoonX/about/blob/master/CONTRIBUTING.md) will provide you with all the information you need to get started. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 SpoonX 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 | # Archived 2 | 3 | It was fun while it lasted, but we have to stop maintaining these repositories. We haven't used these projects for quite some time and maintaining them is becoming harder to do. 4 | 5 | You deserve better, and for that reason we've decided to archive some repositories, which includes this one. 6 | 7 | Feel free to fork and alter the repositories, and go forth making awesome stuff. 8 | 9 | # aurelia-authentication 10 | 11 | [![Build Status](https://travis-ci.org/SpoonX/aurelia-authentication.svg)](https://travis-ci.org/SpoonX/aurelia-authentication) 12 | [![Known Vulnerabilities](https://snyk.io/test/npm/name/badge.svg)](https://snyk.io/test/npm/aurelia-authentication) 13 | [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg?maxAge=2592000?style=plastic)](https://gitter.im/SpoonX/Dev) 14 | 15 | > Aurelia-authentication is a token-based authentication plugin for [Aurelia](http://aurelia.io/) with support for popular social authentication providers (Google, Twitter, Facebook, LinkedIn, Windows Live, FourSquare, Yahoo, Github, Instagram) and a local strategy, i.e. simple username / email and password. It developed of a fork of [paul van bladel's aurelia-auth](https://github.com/paulvanbladel/aurelia-auth/) which itself is a port of the great [Satellizer](https://github.com/sahat/satellizer/) library. 16 | 17 | Aurelia-authentication makes local and third-party authentication easy. Aurelia-authentication does not use any cookies but relies on a token (designed for JWT, but has basic support for others as well) stored in the local storage of the browser. If your server is setup right, it can be a simple as just to select your server endpoint from your [aurelia-api](https://github.com/SpoonX/aurelia-api) setup, add your third-party client ids and you are ready to go. 18 | 19 | You have multiple endpoints? No problem! In the recommended setting, aurelia-authentication makes use of [aurelia-api](https://github.com/SpoonX/aurelia-api) which can set up multiple endpoints. Just specify in your aurelia-authentication configuration which endpoint you want to use for your server and which further endpoints you want to be configured and your token will be sent automatically to your protected API when the user is authenticated. 20 | 21 | With aurelia-authentication you can: 22 | 23 | * Use local login or third-party providers to authenticate the user 24 | * Automatically add your token to requests to the specified endpoints 25 | * Automatically refresh your token 26 | * Extensively customize the settings 27 | * Use standalone or in conjunction with [aurelia-api](https://github.com/SpoonX/aurelia-api) 28 | * Use [Auth0](https://auth0.com) as your only authentication provider (see [the relevant section](auth0.md) for more info) 29 | * Update valueConverters using the 'authorization-change' binding signal. 30 | * Subscribe to the 'authorization-change' event. 31 | * And more 32 | 33 | ## Documentation 34 | 35 | You can find usage examples and the documentation at the [aurelia-authentication-docs](http://aurelia-authentication.spoonx.org/). 36 | 37 | The [changelog](doc/CHANGELOG.md) provides you with information about important changes. 38 | 39 | ## Installation 40 | 41 | ### Aurelia-Cli 42 | 43 | Run `npm i aurelia-authentication --save` from your project root. 44 | 45 | Aurelia-authentication needs an installation of [aurelia-api](https://www.npmjs.com/package/aurelia-api). It also has submodules (currently only the authFilter) and makes use of `extend` and `jwt-decode`. So, add following to the `build.bundles.dependencies` section of `aurelia-project/aurelia.json`. 46 | 47 | ```js 48 | "dependencies": [ 49 | // ... 50 | "extend", 51 | { 52 | "name": "aurelia-authentication", 53 | "path": "../node_modules/aurelia-authentication/dist/amd", 54 | "main": "aurelia-authentication" 55 | }, 56 | { 57 | "name": "jwt-decode", 58 | "path": "../node_modules/jwt-decode/lib", 59 | "main": "index" 60 | } 61 | // ... 62 | ], 63 | ``` 64 | 65 | ### Jspm 66 | 67 | Run `jspm i aurelia-authentication` 68 | 69 | Add `aurelia-authentication` to the `bundles.dist.aurelia.includes` section of `build/bundles.js`. 70 | 71 | Aurelia-authentication needs an installation of [aurelia-api](https://www.npmjs.com/package/aurelia-api). It also has submodules. They are imported in it's main file, so no further action is required. 72 | 73 | If the installation results in having forks, try resolving them by running: 74 | 75 | ```sh 76 | jspm inspect --forks 77 | jspm resolve --only registry:package-name@version 78 | ``` 79 | 80 | E.g. 81 | 82 | ```sh 83 | jspm inspect --forks 84 | > Installed Forks 85 | > npm:aurelia-dependency-injection 1.0.0-beta.1.2.3 1.0.0-beta.2.1.0 86 | 87 | jspm resolve --only npm:aurelia-dependency-injection@1.0.0-beta.2.1.0 88 | ``` 89 | 90 | ### Webpack 91 | 92 | Run `npm i aurelia-authentication --save` from your project root. 93 | 94 | The `authFilter` needs to be added to the `webpack.config.js`. 95 | 96 | Run `npm i ModuleDependenciesPlugin --save-dev` from your root project and include it the `webpack.config.js`, eg: 97 | 98 | ```js 99 | const { AureliaPlugin, ModuleDependenciesPlugin } = require('aurelia-webpack-plugin');` 100 | ``` 101 | 102 | In the `plugins` section add the `authFilter`, eg: 103 | 104 | ```js 105 | plugins: [ 106 | new AureliaPlugin(), 107 | new ModuleDependenciesPlugin({ 108 | "aurelia-authentication": [ "./authFilterValueConverter" ], 109 | }), 110 | ``` 111 | 112 | Aurelia-authentication needs an [aurelia-api](https://www.npmjs.com/package/aurelia-api). It also has submodules. They are listed as resources in the package.json. So, no further action is required. 113 | 114 | ### Typescript 115 | 116 | Npm-based installations pick up the typings automatically. For Jspm-based installations, add to your `typings.json`: 117 | 118 | ```js 119 | "aurelia-authentication": "github:spoonx/aurelia-authentication", 120 | ``` 121 | 122 | and run `typings i` 123 | 124 | or run 125 | 126 | ```sh 127 | typings i github:spoonx/aurelia-authentication 128 | ``` 129 | 130 | ## Usage 131 | 132 | ### Add a configuration file 133 | 134 | Set your custom configuration. You can find all options and the default values in the [baseConfig](http://aurelia-authentication.spoonx.org/baseConfig.html). 135 | 136 | ```js 137 | /* authConfig.js */ 138 | var baseConfig = { 139 | endpoint: 'auth', // use 'auth' endpoint for the auth server 140 | configureEndpoints: ['auth'], // add Authorization header to 'auth' endpoint 141 | facebook: { 142 | clientId: 'your client id' // set your third-party providers client ids 143 | } 144 | } 145 | ``` 146 | 147 | ### Configure the plugin 148 | 149 | Register the plugin and apply your `authConfig`. 150 | 151 | ```js 152 | /* main.js */ 153 | import authConfig from './authConfig'; 154 | 155 | aurelia.use 156 | /* Your other plugins and init code */ 157 | .plugin('aurelia-api', config => { 158 | // Register an authentication hosts 159 | config.registerEndpoint('auth'); 160 | }) 161 | /* configure aurelia-authentication */ 162 | .plugin('aurelia-authentication', baseConfig => { 163 | baseConfig.configure(authConfig); 164 | }); 165 | ``` 166 | 167 | ### Use AuthService in a view-model 168 | 169 | ```js 170 | import {AuthService} from 'aurelia-authentication'; 171 | import {inject} from 'aurelia-framework'; 172 | 173 | @inject(AuthService) 174 | export class Login { 175 | constructor(authService) { 176 | this.authService = authService; 177 | this.authenticated = false; 178 | }; 179 | 180 | // use authService.login(credentialsObject) to login to your auth server 181 | // authService.authenticated holds the current status 182 | // authService.getPayload() gives you the current payload object (for jwt) 183 | login(credentialsObject) { 184 | return this.authService.login(credentialsObject) 185 | .then(() => { 186 | this.authenticated = this.authService.authenticated; 187 | }); 188 | }; 189 | 190 | // use authService.logout to delete stored data 191 | // set expiredRedirect in your settings to automatically redirect 192 | logout() { 193 | return this.authService.logout() 194 | .then(() => { 195 | this.authenticated = this.authService.authenticated; 196 | }); 197 | } 198 | 199 | // use authService.authenticate(name) to get third-party authentication 200 | authenticateFacebook() { 201 | return this.authService.authenticate('facebook') 202 | .then(() => { 203 | this.authenticated = this.authService.authenticated; 204 | }); 205 | } 206 | } 207 | ``` 208 | 209 | ### Quick authService api overview 210 | 211 | ```js 212 | authService 213 | // the Rest instance of aurelia-api used for requests. '.client.client' is the used httpClient instance (from aurelia-fetch-client) 214 | .client 215 | // the current authentication status 216 | .authenticated 217 | // signup into server with credentials and optionally logs in 218 | signup(displayNameOrCredentials, emailOrOptions, passwordOrRedirectUri, options, redirectUri) 219 | // log into server with credentials. Stores response if successful 220 | login(emailOrCredentials, passwordOrOptions, optionsOrRedirectUri, redirectUri) 221 | // deletes stored response. If configured in the config, sends optionally a logout request. 222 | logout(redirectUri, query, name) 223 | // manually refresh authentication. Needs refreshToken options to be configured 224 | .updateToken() 225 | // link third-party account or log into server via third-party authentication. Stores response if successful 226 | authenticate(name, userData) 227 | // unlink third-party 228 | unlink(name, redirectUri) 229 | // get profile 230 | .getMe(criteriaOrId) 231 | // update profile 232 | .updateMe(body, criteriaOrId) 233 | // check if token is available and, if applicable, not expired 234 | .isAuthenticated() 235 | // get token payload if available 236 | .getTokenPayload() 237 | // get the token ttl if available 238 | .getTtl() 239 | // get the token exp if available 240 | .getExp() 241 | ``` 242 | 243 | Additionally, you can use `AuthFilterValueConverter` and `AuthenticatedStep` for UI feedback. 244 | 245 | You can find more information in the [aurelia-authentication-docs](http://aurelia-authentication.spoonx.org/). 246 | 247 | ## Note 248 | 249 | Some month ago, we've simplified installation and usage! This plugin should now be installed using `jspm i aurelia-authentication` or (for webpack) `npm i aurelia-authentication --save`. Make sure you update all references to `spoonx/aurelia-authentication` and `spoonx/aurelia-api` and remove the `spoonx/` prefix (don't forget your config.js, package.json, imports and bundles). 250 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "./doc", 3 | "plugins": [ 4 | "edit-link", 5 | "github", 6 | "versions" 7 | ], 8 | "pluginsConfig": { 9 | "edit-link": { 10 | "base": "https://github.com/SpoonX/aurelia-authentication/edit/master/doc", 11 | "label": "Edit This Page" 12 | }, 13 | "github": { 14 | "url": "https://github.com/spoonx/aurelia-authentication" 15 | }, 16 | "versions": { 17 | "type": "tags" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aurelia-authentication", 3 | "version": "3.8.3", 4 | "description": "Plugin for social media authentication and local authentication together with other authentication utilities.", 5 | "keywords": [ 6 | "aurelia", 7 | "oauth", 8 | "authentication" 9 | ], 10 | "homepage": "http://aurelia-authentication.spoonx.org/", 11 | "main": "dist/commonjs/aurelia-authentication..js", 12 | "moduleType": "node", 13 | "license": "MIT", 14 | "authors": [ 15 | "RWOverdijk (http://spoonx.nl/)" 16 | ], 17 | "contributors": [ 18 | "Dirk Eisinger " 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/SpoonX/aurelia-authentication" 23 | }, 24 | "dependencies": { 25 | "aurelia-api": "^3.0.0", 26 | "aurelia-dependency-injection": "^1.0.0", 27 | "aurelia-event-aggregator": "^1.0.0", 28 | "aurelia-fetch-client": "^1.0.0", 29 | "aurelia-logging": "^1.0.0", 30 | "aurelia-metadata": "^1.0.0", 31 | "aurelia-pal": "^1.8.0", 32 | "aurelia-path": "^1.0.0", 33 | "aurelia-router": "^1.0.0", 34 | "aurelia-templating-resources": "^1.0.0", 35 | "extend": "^3.0.0", 36 | "jwt-decode": "^2.0.0" 37 | } 38 | } -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | System.config({ 2 | defaultJSExtensions: true, 3 | transpiler: false, 4 | paths: { 5 | "github:*": "jspm_packages/github/*", 6 | "npm:*": "jspm_packages/npm/*" 7 | }, 8 | 9 | map: { 10 | "aurelia-api": "npm:aurelia-api@3.0.0", 11 | "aurelia-binding": "npm:aurelia-binding@1.0.9", 12 | "aurelia-bootstrapper": "npm:aurelia-bootstrapper@1.0.1", 13 | "aurelia-dependency-injection": "npm:aurelia-dependency-injection@1.1.0", 14 | "aurelia-event-aggregator": "npm:aurelia-event-aggregator@1.0.0", 15 | "aurelia-fetch-client": "npm:aurelia-fetch-client@1.0.1", 16 | "aurelia-logging": "npm:aurelia-logging@1.0.0", 17 | "aurelia-metadata": "npm:aurelia-metadata@1.0.1", 18 | "aurelia-pal": "npm:aurelia-pal@1.8.0", 19 | "aurelia-pal-browser": "npm:aurelia-pal-browser@1.0.0", 20 | "aurelia-path": "npm:aurelia-path@1.1.1", 21 | "aurelia-polyfills": "npm:aurelia-polyfills@1.1.1", 22 | "aurelia-router": "npm:aurelia-router@1.0.6", 23 | "aurelia-templating-resources": "npm:aurelia-templating-resources@1.1.1", 24 | "extend": "npm:extend@3.0.0", 25 | "fetch": "github:github/fetch@1.0.0", 26 | "jwt-decode": "npm:jwt-decode@2.1.0", 27 | "npm:aurelia-api@3.0.0": { 28 | "aurelia-dependency-injection": "npm:aurelia-dependency-injection@1.1.0", 29 | "aurelia-fetch-client": "npm:aurelia-fetch-client@1.0.1", 30 | "aurelia-framework": "npm:aurelia-framework@1.0.6", 31 | "aurelia-path": "npm:aurelia-path@1.1.1", 32 | "extend": "npm:extend@3.0.0" 33 | }, 34 | "npm:aurelia-binding@1.0.9": { 35 | "aurelia-logging": "npm:aurelia-logging@1.0.0", 36 | "aurelia-metadata": "npm:aurelia-metadata@1.0.1", 37 | "aurelia-pal": "npm:aurelia-pal@1.8.0", 38 | "aurelia-task-queue": "npm:aurelia-task-queue@1.1.0" 39 | }, 40 | "npm:aurelia-bootstrapper@1.0.1": { 41 | "aurelia-event-aggregator": "npm:aurelia-event-aggregator@1.0.0", 42 | "aurelia-framework": "npm:aurelia-framework@1.0.6", 43 | "aurelia-history": "npm:aurelia-history@1.0.0", 44 | "aurelia-history-browser": "npm:aurelia-history-browser@1.0.0", 45 | "aurelia-loader-default": "npm:aurelia-loader-default@1.0.0", 46 | "aurelia-logging-console": "npm:aurelia-logging-console@1.0.0", 47 | "aurelia-pal": "npm:aurelia-pal@1.8.0", 48 | "aurelia-pal-browser": "npm:aurelia-pal-browser@1.0.0", 49 | "aurelia-polyfills": "npm:aurelia-polyfills@1.1.1", 50 | "aurelia-router": "npm:aurelia-router@1.0.6", 51 | "aurelia-templating": "npm:aurelia-templating@1.1.1", 52 | "aurelia-templating-binding": "npm:aurelia-templating-binding@1.0.0", 53 | "aurelia-templating-resources": "npm:aurelia-templating-resources@1.1.1", 54 | "aurelia-templating-router": "npm:aurelia-templating-router@1.0.0" 55 | }, 56 | "npm:aurelia-dependency-injection@1.1.0": { 57 | "aurelia-metadata": "npm:aurelia-metadata@1.0.1", 58 | "aurelia-pal": "npm:aurelia-pal@1.8.0" 59 | }, 60 | "npm:aurelia-event-aggregator@1.0.0": { 61 | "aurelia-logging": "npm:aurelia-logging@1.0.0" 62 | }, 63 | "npm:aurelia-framework@1.0.6": { 64 | "aurelia-binding": "npm:aurelia-binding@1.0.9", 65 | "aurelia-dependency-injection": "npm:aurelia-dependency-injection@1.1.0", 66 | "aurelia-loader": "npm:aurelia-loader@1.0.0", 67 | "aurelia-logging": "npm:aurelia-logging@1.0.0", 68 | "aurelia-metadata": "npm:aurelia-metadata@1.0.1", 69 | "aurelia-pal": "npm:aurelia-pal@1.8.0", 70 | "aurelia-path": "npm:aurelia-path@1.1.1", 71 | "aurelia-task-queue": "npm:aurelia-task-queue@1.1.0", 72 | "aurelia-templating": "npm:aurelia-templating@1.1.1" 73 | }, 74 | "npm:aurelia-history-browser@1.0.0": { 75 | "aurelia-history": "npm:aurelia-history@1.0.0", 76 | "aurelia-pal": "npm:aurelia-pal@1.8.0" 77 | }, 78 | "npm:aurelia-loader-default@1.0.0": { 79 | "aurelia-loader": "npm:aurelia-loader@1.0.0", 80 | "aurelia-metadata": "npm:aurelia-metadata@1.0.1", 81 | "aurelia-pal": "npm:aurelia-pal@1.8.0" 82 | }, 83 | "npm:aurelia-loader@1.0.0": { 84 | "aurelia-metadata": "npm:aurelia-metadata@1.0.1", 85 | "aurelia-path": "npm:aurelia-path@1.1.1" 86 | }, 87 | "npm:aurelia-logging-console@1.0.0": { 88 | "aurelia-logging": "npm:aurelia-logging@1.0.0" 89 | }, 90 | "npm:aurelia-metadata@1.0.1": { 91 | "aurelia-pal": "npm:aurelia-pal@1.8.0" 92 | }, 93 | "npm:aurelia-pal-browser@1.0.0": { 94 | "aurelia-pal": "npm:aurelia-pal@1.8.0" 95 | }, 96 | "npm:aurelia-polyfills@1.1.1": { 97 | "aurelia-pal": "npm:aurelia-pal@1.8.0" 98 | }, 99 | "npm:aurelia-route-recognizer@1.1.0": { 100 | "aurelia-path": "npm:aurelia-path@1.1.1" 101 | }, 102 | "npm:aurelia-router@1.0.6": { 103 | "aurelia-dependency-injection": "npm:aurelia-dependency-injection@1.1.0", 104 | "aurelia-event-aggregator": "npm:aurelia-event-aggregator@1.0.0", 105 | "aurelia-history": "npm:aurelia-history@1.0.0", 106 | "aurelia-logging": "npm:aurelia-logging@1.0.0", 107 | "aurelia-path": "npm:aurelia-path@1.1.1", 108 | "aurelia-route-recognizer": "npm:aurelia-route-recognizer@1.1.0" 109 | }, 110 | "npm:aurelia-task-queue@1.1.0": { 111 | "aurelia-pal": "npm:aurelia-pal@1.8.0" 112 | }, 113 | "npm:aurelia-templating-binding@1.0.0": { 114 | "aurelia-binding": "npm:aurelia-binding@1.0.9", 115 | "aurelia-logging": "npm:aurelia-logging@1.0.0", 116 | "aurelia-templating": "npm:aurelia-templating@1.1.1" 117 | }, 118 | "npm:aurelia-templating-resources@1.1.1": { 119 | "aurelia-binding": "npm:aurelia-binding@1.0.9", 120 | "aurelia-dependency-injection": "npm:aurelia-dependency-injection@1.1.0", 121 | "aurelia-loader": "npm:aurelia-loader@1.0.0", 122 | "aurelia-logging": "npm:aurelia-logging@1.0.0", 123 | "aurelia-metadata": "npm:aurelia-metadata@1.0.1", 124 | "aurelia-pal": "npm:aurelia-pal@1.8.0", 125 | "aurelia-path": "npm:aurelia-path@1.1.1", 126 | "aurelia-task-queue": "npm:aurelia-task-queue@1.1.0", 127 | "aurelia-templating": "npm:aurelia-templating@1.1.1" 128 | }, 129 | "npm:aurelia-templating-router@1.0.0": { 130 | "aurelia-dependency-injection": "npm:aurelia-dependency-injection@1.1.0", 131 | "aurelia-logging": "npm:aurelia-logging@1.0.0", 132 | "aurelia-metadata": "npm:aurelia-metadata@1.0.1", 133 | "aurelia-pal": "npm:aurelia-pal@1.8.0", 134 | "aurelia-path": "npm:aurelia-path@1.1.1", 135 | "aurelia-router": "npm:aurelia-router@1.0.6", 136 | "aurelia-templating": "npm:aurelia-templating@1.1.1" 137 | }, 138 | "npm:aurelia-templating@1.1.1": { 139 | "aurelia-binding": "npm:aurelia-binding@1.0.9", 140 | "aurelia-dependency-injection": "npm:aurelia-dependency-injection@1.1.0", 141 | "aurelia-loader": "npm:aurelia-loader@1.0.0", 142 | "aurelia-logging": "npm:aurelia-logging@1.0.0", 143 | "aurelia-metadata": "npm:aurelia-metadata@1.0.1", 144 | "aurelia-pal": "npm:aurelia-pal@1.8.0", 145 | "aurelia-path": "npm:aurelia-path@1.1.1", 146 | "aurelia-task-queue": "npm:aurelia-task-queue@1.1.0" 147 | }, 148 | "npm:jwt-decode@2.1.0": { 149 | "fs": "github:jspm/nodelibs-fs@0.1.2", 150 | "systemjs-json": "github:systemjs/plugin-json@0.1.2" 151 | } 152 | } 153 | }); 154 | -------------------------------------------------------------------------------- /dist/amd/authFilterValueConverter.js: -------------------------------------------------------------------------------- 1 | define(['exports', 'aurelia-router'], function (exports, _aureliaRouter) { 2 | 'use strict'; 3 | 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | exports.AuthFilterValueConverter = undefined; 8 | 9 | 10 | 11 | var AuthFilterValueConverter = exports.AuthFilterValueConverter = function () { 12 | function AuthFilterValueConverter() { 13 | 14 | } 15 | 16 | AuthFilterValueConverter.prototype.toView = function toView(routes, isAuthenticated) { 17 | return routes.filter(function (route) { 18 | return typeof route.config.auth !== 'boolean' || route.config.auth === isAuthenticated; 19 | }); 20 | }; 21 | 22 | return AuthFilterValueConverter; 23 | }(); 24 | }); -------------------------------------------------------------------------------- /dist/amd/authenticatedFilterValueConverter.js: -------------------------------------------------------------------------------- 1 | define(['exports', 'aurelia-dependency-injection', './aurelia-authentication', 'aurelia-router'], function (exports, _aureliaDependencyInjection, _aureliaAuthentication, _aureliaRouter) { 2 | 'use strict'; 3 | 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | exports.AuthenticatedFilterValueConverter = undefined; 8 | 9 | 10 | 11 | var _dec, _class; 12 | 13 | var AuthenticatedFilterValueConverter = exports.AuthenticatedFilterValueConverter = (_dec = (0, _aureliaDependencyInjection.inject)(_aureliaAuthentication.AuthService), _dec(_class = function () { 14 | function AuthenticatedFilterValueConverter(authService) { 15 | 16 | 17 | this.authService = authService; 18 | } 19 | 20 | AuthenticatedFilterValueConverter.prototype.toView = function toView(routes) { 21 | var isAuthenticated = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.authService.authenticated; 22 | 23 | return routes.filter(function (route) { 24 | return typeof route.config.auth !== 'boolean' || route.config.auth === isAuthenticated; 25 | }); 26 | }; 27 | 28 | return AuthenticatedFilterValueConverter; 29 | }()) || _class); 30 | }); -------------------------------------------------------------------------------- /dist/amd/authenticatedValueConverter.js: -------------------------------------------------------------------------------- 1 | define(['exports', 'aurelia-dependency-injection', './aurelia-authentication'], function (exports, _aureliaDependencyInjection, _aureliaAuthentication) { 2 | 'use strict'; 3 | 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | exports.AuthenticatedValueConverter = undefined; 8 | 9 | 10 | 11 | var _dec, _class; 12 | 13 | var AuthenticatedValueConverter = exports.AuthenticatedValueConverter = (_dec = (0, _aureliaDependencyInjection.inject)(_aureliaAuthentication.AuthService), _dec(_class = function () { 14 | function AuthenticatedValueConverter(authService) { 15 | 16 | 17 | this.authService = authService; 18 | } 19 | 20 | AuthenticatedValueConverter.prototype.toView = function toView() { 21 | return this.authService.authenticated; 22 | }; 23 | 24 | return AuthenticatedValueConverter; 25 | }()) || _class); 26 | }); -------------------------------------------------------------------------------- /dist/amd/index.js: -------------------------------------------------------------------------------- 1 | define(['exports', './aurelia-authentication'], function (exports, _aureliaAuthentication) { 2 | 'use strict'; 3 | 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | Object.keys(_aureliaAuthentication).forEach(function (key) { 8 | if (key === "default" || key === "__esModule") return; 9 | Object.defineProperty(exports, key, { 10 | enumerable: true, 11 | get: function () { 12 | return _aureliaAuthentication[key]; 13 | } 14 | }); 15 | }); 16 | }); -------------------------------------------------------------------------------- /dist/authFilterValueConverter.js: -------------------------------------------------------------------------------- 1 | import {RouteConfig} from 'aurelia-router'; 2 | 3 | export class AuthFilterValueConverter { 4 | /** 5 | * route toView predictator on route.config.auth === isAuthenticated 6 | * @param {RouteConfig} routes the routes array to convert 7 | * @param {boolean} isAuthenticated authentication status 8 | * @return {boolean} show/hide element 9 | */ 10 | toView(routes: RouteConfig, isAuthenticated: boolean): boolean { 11 | return routes.filter(route => typeof route.config.auth !== 'boolean' || route.config.auth === isAuthenticated); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /dist/authenticatedFilterValueConverter.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {AuthService} from './aurelia-authentication'; 3 | import {RouteConfig} from 'aurelia-router'; 4 | 5 | @inject(AuthService) 6 | export class AuthenticatedFilterValueConverter { 7 | constructor(authService: AuthService) { 8 | this.authService = authService; 9 | } 10 | 11 | /** 12 | * route toView predictator on route.config.auth === (parameter || authService.isAuthenticated()) 13 | * @param {RouteConfig} routes the routes array to convert 14 | * @param {[boolean]} [isAuthenticated] optional isAuthenticated value. default: this.authService.authenticated 15 | * @return {boolean} show/hide element 16 | */ 17 | toView(routes: RouteConfig, isAuthenticated: boolean = this.authService.authenticated): boolean { 18 | return routes.filter(route => typeof route.config.auth !== 'boolean' || route.config.auth === isAuthenticated); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /dist/authenticatedValueConverter.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {AuthService} from './aurelia-authentication'; 3 | 4 | @inject(AuthService) 5 | export class AuthenticatedValueConverter { 6 | constructor(authService) { 7 | this.authService = authService; 8 | } 9 | 10 | /** 11 | * element toView predictator on authService.isAuthenticated() 12 | * @return {boolean} show/hide element 13 | */ 14 | toView() { 15 | return this.authService.authenticated; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /dist/commonjs/authFilterValueConverter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.AuthFilterValueConverter = undefined; 7 | 8 | var _aureliaRouter = require('aurelia-router'); 9 | 10 | 11 | 12 | var AuthFilterValueConverter = exports.AuthFilterValueConverter = function () { 13 | function AuthFilterValueConverter() { 14 | 15 | } 16 | 17 | AuthFilterValueConverter.prototype.toView = function toView(routes, isAuthenticated) { 18 | return routes.filter(function (route) { 19 | return typeof route.config.auth !== 'boolean' || route.config.auth === isAuthenticated; 20 | }); 21 | }; 22 | 23 | return AuthFilterValueConverter; 24 | }(); -------------------------------------------------------------------------------- /dist/commonjs/authenticatedFilterValueConverter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.AuthenticatedFilterValueConverter = undefined; 7 | 8 | var _dec, _class; 9 | 10 | var _aureliaDependencyInjection = require('aurelia-dependency-injection'); 11 | 12 | var _aureliaAuthentication = require('./aurelia-authentication'); 13 | 14 | var _aureliaRouter = require('aurelia-router'); 15 | 16 | 17 | 18 | var AuthenticatedFilterValueConverter = exports.AuthenticatedFilterValueConverter = (_dec = (0, _aureliaDependencyInjection.inject)(_aureliaAuthentication.AuthService), _dec(_class = function () { 19 | function AuthenticatedFilterValueConverter(authService) { 20 | 21 | 22 | this.authService = authService; 23 | } 24 | 25 | AuthenticatedFilterValueConverter.prototype.toView = function toView(routes) { 26 | var isAuthenticated = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.authService.authenticated; 27 | 28 | return routes.filter(function (route) { 29 | return typeof route.config.auth !== 'boolean' || route.config.auth === isAuthenticated; 30 | }); 31 | }; 32 | 33 | return AuthenticatedFilterValueConverter; 34 | }()) || _class); -------------------------------------------------------------------------------- /dist/commonjs/authenticatedValueConverter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.AuthenticatedValueConverter = undefined; 7 | 8 | var _dec, _class; 9 | 10 | var _aureliaDependencyInjection = require('aurelia-dependency-injection'); 11 | 12 | var _aureliaAuthentication = require('./aurelia-authentication'); 13 | 14 | 15 | 16 | var AuthenticatedValueConverter = exports.AuthenticatedValueConverter = (_dec = (0, _aureliaDependencyInjection.inject)(_aureliaAuthentication.AuthService), _dec(_class = function () { 17 | function AuthenticatedValueConverter(authService) { 18 | 19 | 20 | this.authService = authService; 21 | } 22 | 23 | AuthenticatedValueConverter.prototype.toView = function toView() { 24 | return this.authService.authenticated; 25 | }; 26 | 27 | return AuthenticatedValueConverter; 28 | }()) || _class); -------------------------------------------------------------------------------- /dist/commonjs/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _aureliaAuthentication = require('./aurelia-authentication'); 8 | 9 | Object.keys(_aureliaAuthentication).forEach(function (key) { 10 | if (key === "default" || key === "__esModule") return; 11 | Object.defineProperty(exports, key, { 12 | enumerable: true, 13 | get: function get() { 14 | return _aureliaAuthentication[key]; 15 | } 16 | }); 17 | }); -------------------------------------------------------------------------------- /dist/es2015/authFilterValueConverter.js: -------------------------------------------------------------------------------- 1 | import { RouteConfig } from 'aurelia-router'; 2 | 3 | export let AuthFilterValueConverter = class AuthFilterValueConverter { 4 | toView(routes, isAuthenticated) { 5 | return routes.filter(route => typeof route.config.auth !== 'boolean' || route.config.auth === isAuthenticated); 6 | } 7 | }; -------------------------------------------------------------------------------- /dist/es2015/authenticatedFilterValueConverter.js: -------------------------------------------------------------------------------- 1 | var _dec, _class; 2 | 3 | import { inject } from 'aurelia-dependency-injection'; 4 | import { AuthService } from './aurelia-authentication'; 5 | import { RouteConfig } from 'aurelia-router'; 6 | 7 | export let AuthenticatedFilterValueConverter = (_dec = inject(AuthService), _dec(_class = class AuthenticatedFilterValueConverter { 8 | constructor(authService) { 9 | this.authService = authService; 10 | } 11 | 12 | toView(routes, isAuthenticated = this.authService.authenticated) { 13 | return routes.filter(route => typeof route.config.auth !== 'boolean' || route.config.auth === isAuthenticated); 14 | } 15 | }) || _class); -------------------------------------------------------------------------------- /dist/es2015/authenticatedValueConverter.js: -------------------------------------------------------------------------------- 1 | var _dec, _class; 2 | 3 | import { inject } from 'aurelia-dependency-injection'; 4 | import { AuthService } from './aurelia-authentication'; 5 | 6 | export let AuthenticatedValueConverter = (_dec = inject(AuthService), _dec(_class = class AuthenticatedValueConverter { 7 | constructor(authService) { 8 | this.authService = authService; 9 | } 10 | 11 | toView() { 12 | return this.authService.authenticated; 13 | } 14 | }) || _class); -------------------------------------------------------------------------------- /dist/es2015/index.js: -------------------------------------------------------------------------------- 1 | export * from './aurelia-authentication'; -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from 'aurelia-authentication/aurelia-authentication'; -------------------------------------------------------------------------------- /dist/native-modules/authFilterValueConverter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.__esModule = true; 4 | exports.AuthFilterValueConverter = undefined; 5 | 6 | var _aureliaRouter = require('aurelia-router'); 7 | 8 | 9 | 10 | var AuthFilterValueConverter = exports.AuthFilterValueConverter = function () { 11 | function AuthFilterValueConverter() { 12 | 13 | } 14 | 15 | AuthFilterValueConverter.prototype.toView = function toView(routes, isAuthenticated) { 16 | return routes.filter(function (route) { 17 | return typeof route.config.auth !== 'boolean' || route.config.auth === isAuthenticated; 18 | }); 19 | }; 20 | 21 | return AuthFilterValueConverter; 22 | }(); -------------------------------------------------------------------------------- /dist/native-modules/authenticatedFilterValueConverter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.__esModule = true; 4 | exports.AuthenticatedFilterValueConverter = undefined; 5 | 6 | var _dec, _class; 7 | 8 | var _aureliaDependencyInjection = require('aurelia-dependency-injection'); 9 | 10 | var _aureliaAuthentication = require('./aurelia-authentication'); 11 | 12 | var _aureliaRouter = require('aurelia-router'); 13 | 14 | 15 | 16 | var AuthenticatedFilterValueConverter = exports.AuthenticatedFilterValueConverter = (_dec = (0, _aureliaDependencyInjection.inject)(_aureliaAuthentication.AuthService), _dec(_class = function () { 17 | function AuthenticatedFilterValueConverter(authService) { 18 | 19 | 20 | this.authService = authService; 21 | } 22 | 23 | AuthenticatedFilterValueConverter.prototype.toView = function toView(routes) { 24 | var isAuthenticated = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.authService.authenticated; 25 | 26 | return routes.filter(function (route) { 27 | return typeof route.config.auth !== 'boolean' || route.config.auth === isAuthenticated; 28 | }); 29 | }; 30 | 31 | return AuthenticatedFilterValueConverter; 32 | }()) || _class); -------------------------------------------------------------------------------- /dist/native-modules/authenticatedValueConverter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.__esModule = true; 4 | exports.AuthenticatedValueConverter = undefined; 5 | 6 | var _dec, _class; 7 | 8 | var _aureliaDependencyInjection = require('aurelia-dependency-injection'); 9 | 10 | var _aureliaAuthentication = require('./aurelia-authentication'); 11 | 12 | 13 | 14 | var AuthenticatedValueConverter = exports.AuthenticatedValueConverter = (_dec = (0, _aureliaDependencyInjection.inject)(_aureliaAuthentication.AuthService), _dec(_class = function () { 15 | function AuthenticatedValueConverter(authService) { 16 | 17 | 18 | this.authService = authService; 19 | } 20 | 21 | AuthenticatedValueConverter.prototype.toView = function toView() { 22 | return this.authService.authenticated; 23 | }; 24 | 25 | return AuthenticatedValueConverter; 26 | }()) || _class); -------------------------------------------------------------------------------- /dist/native-modules/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.__esModule = true; 4 | 5 | var _aureliaAuthentication = require('./aurelia-authentication'); 6 | 7 | Object.keys(_aureliaAuthentication).forEach(function (key) { 8 | if (key === "default" || key === "__esModule") return; 9 | Object.defineProperty(exports, key, { 10 | enumerable: true, 11 | get: function get() { 12 | return _aureliaAuthentication[key]; 13 | } 14 | }); 15 | }); -------------------------------------------------------------------------------- /dist/system/authFilterValueConverter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['aurelia-router'], function (_export, _context) { 4 | "use strict"; 5 | 6 | var RouteConfig, AuthFilterValueConverter; 7 | 8 | 9 | 10 | return { 11 | setters: [function (_aureliaRouter) { 12 | RouteConfig = _aureliaRouter.RouteConfig; 13 | }], 14 | execute: function () { 15 | _export('AuthFilterValueConverter', AuthFilterValueConverter = function () { 16 | function AuthFilterValueConverter() { 17 | 18 | } 19 | 20 | AuthFilterValueConverter.prototype.toView = function toView(routes, isAuthenticated) { 21 | return routes.filter(function (route) { 22 | return typeof route.config.auth !== 'boolean' || route.config.auth === isAuthenticated; 23 | }); 24 | }; 25 | 26 | return AuthFilterValueConverter; 27 | }()); 28 | 29 | _export('AuthFilterValueConverter', AuthFilterValueConverter); 30 | } 31 | }; 32 | }); -------------------------------------------------------------------------------- /dist/system/authenticatedFilterValueConverter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['aurelia-dependency-injection', './aurelia-authentication', 'aurelia-router'], function (_export, _context) { 4 | "use strict"; 5 | 6 | var inject, AuthService, RouteConfig, _dec, _class, AuthenticatedFilterValueConverter; 7 | 8 | 9 | 10 | return { 11 | setters: [function (_aureliaDependencyInjection) { 12 | inject = _aureliaDependencyInjection.inject; 13 | }, function (_aureliaAuthentication) { 14 | AuthService = _aureliaAuthentication.AuthService; 15 | }, function (_aureliaRouter) { 16 | RouteConfig = _aureliaRouter.RouteConfig; 17 | }], 18 | execute: function () { 19 | _export('AuthenticatedFilterValueConverter', AuthenticatedFilterValueConverter = (_dec = inject(AuthService), _dec(_class = function () { 20 | function AuthenticatedFilterValueConverter(authService) { 21 | 22 | 23 | this.authService = authService; 24 | } 25 | 26 | AuthenticatedFilterValueConverter.prototype.toView = function toView(routes) { 27 | var isAuthenticated = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.authService.authenticated; 28 | 29 | return routes.filter(function (route) { 30 | return typeof route.config.auth !== 'boolean' || route.config.auth === isAuthenticated; 31 | }); 32 | }; 33 | 34 | return AuthenticatedFilterValueConverter; 35 | }()) || _class)); 36 | 37 | _export('AuthenticatedFilterValueConverter', AuthenticatedFilterValueConverter); 38 | } 39 | }; 40 | }); -------------------------------------------------------------------------------- /dist/system/authenticatedValueConverter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['aurelia-dependency-injection', './aurelia-authentication'], function (_export, _context) { 4 | "use strict"; 5 | 6 | var inject, AuthService, _dec, _class, AuthenticatedValueConverter; 7 | 8 | 9 | 10 | return { 11 | setters: [function (_aureliaDependencyInjection) { 12 | inject = _aureliaDependencyInjection.inject; 13 | }, function (_aureliaAuthentication) { 14 | AuthService = _aureliaAuthentication.AuthService; 15 | }], 16 | execute: function () { 17 | _export('AuthenticatedValueConverter', AuthenticatedValueConverter = (_dec = inject(AuthService), _dec(_class = function () { 18 | function AuthenticatedValueConverter(authService) { 19 | 20 | 21 | this.authService = authService; 22 | } 23 | 24 | AuthenticatedValueConverter.prototype.toView = function toView() { 25 | return this.authService.authenticated; 26 | }; 27 | 28 | return AuthenticatedValueConverter; 29 | }()) || _class)); 30 | 31 | _export('AuthenticatedValueConverter', AuthenticatedValueConverter); 32 | } 33 | }; 34 | }); -------------------------------------------------------------------------------- /dist/system/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['./aurelia-authentication'], function (_export, _context) { 4 | "use strict"; 5 | 6 | return { 7 | setters: [function (_aureliaAuthentication) { 8 | var _exportObj = {}; 9 | 10 | for (var _key in _aureliaAuthentication) { 11 | if (_key !== "default" && _key !== "__esModule") _exportObj[_key] = _aureliaAuthentication[_key]; 12 | } 13 | 14 | _export(_exportObj); 15 | }], 16 | execute: function () {} 17 | }; 18 | }); -------------------------------------------------------------------------------- /doc/Quick start.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | This is a small guide that shows you how to use this module. 4 | In this document, we'll be modifying the skeleton to use aurelia-authentication. 5 | 6 | ## Prerequisites 7 | 8 | For this guide, we assume you have the [aurelia skeleton](https://github.com/aurelia/skeleton-navigation) set up. 9 | We'll also assume you have [node](https://nodejs.org/en/) and [npm](https://www.npmjs.com/) installed, and that you're familiar with [installing modules](https://docs.npmjs.com/). 10 | 11 | Finally, we'll assume that you have [jspm](http://jspm.io) installed. If you don't, run `npm i -g jspm`. 12 | 13 | ## Enough chat 14 | 15 | Now it's time to start doing something 16 | 17 | ### Installation 18 | 19 | First, head on over to your favorite terminal and run `jspm install aurelia-api aurelia-authentication` from your project root. This will install the module and aurelia-api to make using this module even easier. 20 | 21 | ### Configuration 22 | 23 | Add a javascript file to your project where you will store the aurelia-authentication configuration data. Call it for example authConfig.js. 24 | Since this file is available via the browser, it should never contain sensitive data. Note that for OAuth the clientId is not sensitive data. The client secret is sensitive data and should be only available server side. The aurelia-authentication config file is compatible with the original [Satellizer](https://github.com/sahat/satellizer/) config file, making it easy to find more configuration options and server configuration examples. 25 | 26 | Aurelia-authentication uses [aurelia-api](https://github.com/SpoonX/aurelia-api). This allows you to specify here the aurelia-api endpoint for the authorization requests and to list all endpoints you want to have configured for authorized requests. The access token will be added to all requests to those endpoints. 27 | 28 | Here is a sample of a close to minimal custom setting: 29 | 30 | ```js 31 | export default { 32 | endpoint: 'auth', 33 | configureEndpoints: ['auth', 'protected-api'], 34 | loginUrl: 'login', 35 | signupUrl: 'users', 36 | profileUrl: 'me', 37 | unlinkUrl: 'me/unlink', 38 | loginOnSignup: false, 39 | storageChangedReload: true, // ensure secondary tab reloading after auth status changes 40 | expiredRedirect: 1, // redirect to logoutRedirect after token expiration 41 | providers: { 42 | google: { 43 | url: 'google', 44 | clientId: '239531536023-ibk10mb9p7ullsw4j55a61og5lvnjrff6.apps.googleusercontent.com' 45 | }, 46 | facebook:{ 47 | url: 'facebook', 48 | clientId: '1465278217541708498' 49 | } 50 | } 51 | }; 52 | ``` 53 | 54 | ### Register the plugin 55 | 56 | In your aurelia configuration file, add the plugin and inject above aurelia-authentication configuration file. 57 | 58 | While not mandatory, aurelia-authentication is easiest to use in conjunction with [aurelia-api](https://github.com/SpoonX/aurelia-api). Aurelia-api allows to setup several endpoints for Rest services. This can be used to separate public and protected routes. For that, we first need to register the endpoints with aurelia-api. Bellow we setup the endpoints 'auth' and 'protected-api'. These will be setup in the proceeding aurelia-authentication configuration for authorized access (specified in above authConfig.js example). The endpoint 'public-api' bellow could be used for public access only. Since we didn't add it above to the 'configureEndpoints' array and the access token will not be added to it by aurelia-authentication. 59 | 60 | ```js 61 | import authConfig from './authConfig'; 62 | 63 | export function configure(aurelia) { 64 | aurelia.use 65 | /* Your other plugins and init code */ 66 | .standardConfiguration() 67 | .developmentLogging() 68 | /* setup the api endpoints first (if desired) */ 69 | .plugin('aurelia-api', configure => { 70 | configure 71 | .registerEndpoint('auth', 'https://myapi.org/auth') 72 | .registerEndpoint('protected-api', 'https://myapi.org/protected-api') 73 | .registerEndpoint('public-api', 'http://myapi.org/public-api'); 74 | }) 75 | /* configure aurelia-authentication */ 76 | .plugin('aurelia-authentication', baseConfig => { 77 | baseConfig.configure(authConfig); 78 | }); 79 | 80 | aurelia.start().then(a => a.setRoot()); 81 | } 82 | ``` 83 | 84 | ### Getting the current authentication status 85 | 86 | There are two options: 87 | 88 | * authService.isAuthenticated(): This function gets the current token on each call from the window storage to calculate the current authentication status. Since it's a function, Aurelia will use dirty checking, if you bind to it. 89 | * authService.authenticated: This property gets updated by timeout and storage events to keep it accurate all the time. No dirty-checking is needed, but you might not like that there is magic used to keep it updated. 90 | 91 | ### Provide a UI for a login, signup and profile 92 | 93 | Button actions are passed to the corresponding view model via a simple click.delegate: 94 | 95 | ```html 96 | 99 | ``` 100 | 101 | The login view model will speak directly with the aurelia-authentication service, which is made available via constructor injection. 102 | 103 | ```js 104 | import {AuthService} from 'aurelia-authentication'; 105 | import {inject, computedFrom} from 'aurelia-framework'; 106 | @inject(AuthService) 107 | 108 | export class Login { 109 | constructor(authService) { 110 | this.authService = authService; 111 | }; 112 | 113 | heading = 'Login'; 114 | 115 | email = ''; 116 | password = ''; 117 | 118 | // make a getter to get the authentication status. 119 | // use computedFrom to avoid dirty checking 120 | @computedFrom('authService.authenticated') 121 | get authenticated() { 122 | return this.authService.authenticated; 123 | } 124 | 125 | login() { 126 | return this.authService.login(this.email, this.password) 127 | .then(response => { 128 | console.log("success logged " + response); 129 | }) 130 | .catch(err => { 131 | console.log("login failure"); 132 | }); 133 | }; 134 | 135 | authenticate(name) { 136 | return this.authService.authenticate(name) 137 | .then(response => { 138 | console.log("auth response " + response); 139 | }); 140 | } 141 | } 142 | ``` 143 | 144 | On the profile page, social media accounts can be linked and unlinked. For a nice UI experience, use if.bind for either showing the link or unlink button: 145 | 146 | ```html 147 | 150 | 153 | ``` 154 | 155 | ### Making the Aurelia Router authentication aware 156 | 157 | The logout and profile links are only shown when the user is authenticated, whereas the login link is only visible when the user is not authenticated. 158 | 159 | ```html 160 | 188 | ``` 189 | 190 | Menu items visibility can also be linked with the authFilter to the `authenticated` value. 191 | 192 | In the router config function, you can specify an `auth` property in the routing map indicating whether or not the user needs to be authenticated in order to access the route: 193 | 194 | ```js 195 | import {AuthenticateStep} from 'aurelia-authentication'; 196 | 197 | export class App { 198 | configureRouter(config, router) { 199 | config.title = 'Aurelia'; 200 | 201 | config.addPipelineStep('authorize', AuthenticateStep); // Add a route filter so only authenticated uses are authorized to access some routes 202 | 203 | config.map([ 204 | { route: ['','welcome'], moduleId: './welcome', nav: true, title: 'Welcome' }, 205 | { route: 'flickr', moduleId: './flickr', nav: true, title: 'Flickr' }, 206 | { route: 'customer', moduleId: './customer', nav: true, title: 'CRM', auth: true }, 207 | ... 208 | ]); 209 | 210 | ... 211 | }; 212 | } 213 | ``` 214 | 215 | In the above example the customer route is only available for authenticated users. 216 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # Aurelia-authentication 2 | 3 | [Open on github](https://github.com/SpoonX/aurelia-authentication) 4 | 5 | ## What is aurelia-authentication? 6 | 7 | > Aurelia-authentication is a token-based authentication plugin for [Aurelia](http://aurelia.io/) with support for popular social authentication providers (Google, Twitter, Facebook, LinkedIn, Windows Live, FourSquare, Yahoo, Github, Instagram, Auth0) and a local strategy, i.e. simple username / email and password. It developed of a fork of [paul van bladel's aurelia-auth](https://github.com/paulvanbladel/aurelia-auth/) which itself is a port of the great [Satellizer](https://github.com/sahat/satellizer/) library. 8 | 9 | Aurelia-authentication makes local and third-party authentication easy. If your server is setup right, it can be a simple as just to select your server endpoint from your [aurelia-api](https://github.com/SpoonX/aurelia-api) setup, add your third-party client ids and you are ready to go. Basically, aurelia-authentication does not use any cookies but relies on a JWT (json web token; other token formats have basic support) stored in the local storage of the browser: 10 | 11 | ![JWT in local storage](./pictures/TokenViaDevelopmentTools.png) 12 | 13 | You have multiple endpoints? No problem! In the recommended setting, aurelia-authentication makes use of [aurelia-api](https://github.com/SpoonX/aurelia-api) which sets up multiple endpoints easily. Just specify in your aurelia-authentication configuration which endpoint you want to use for your server and which further endpoints you want to be configured and your token will be sent automatically to your protected API when the user is authenticated. 14 | 15 | ![Authentication header](./pictures/authHeader.png) 16 | 17 | With aurelia-authentication you can: 18 | 19 | * Use local login or third-party providers to authenticate the user 20 | * Automatically add your token to requests to the specified endpoints 21 | * Automatically refresh your token 22 | * Extensively customize the settings 23 | * Use standalone or in conjunction with [aurelia-api](https://github.com/SpoonX/aurelia-api) 24 | * Use [Auth0](https://auth0.com) as your only authentication provider (see [the relevant section](auth0.md) for more info) 25 | * Use an [OpenID Connect](http://openid.net/connect/) provider such as [IdentityServer](https://github.com/IdentityServer/IdentityServer4) 26 | or [OpenIddict](https://github.com/openiddict/openiddict-core) as your only authentication provider (see [the relevant section](oidc.md)) 27 | * Update valueConverters using the 'authorization-change' binding signal. 28 | * Subscribe to the 'authorization-change' event. 29 | * And more 30 | 31 | ## Important note 32 | 33 | The package name has changed (to make life easier). For installation, use `jspm i aurelia-authentication` or (for webpack) `npm i aurelia-authentication --save`. Make sure you update all references to `spoonx/aurelia-authentication` and `spoonx/aurelia-api` and remove the `spoonx/` prefix (don't forget your config.js, package.json, imports and bundles). 34 | 35 | ## How this differs from 'paulvanbladel/aurelia-auth' 36 | 37 | This repository was originally a fork of paulvanbladel/aurealia-auth. It was forked when the original repository was in a period of inactivity, and later made into a repository of it's own. We still aim to provide 100% backwards compatibility, so the transition to aurelia-authentication should propose no problems. 38 | As such we often get asked how this repository differs from the original. So, at the time of writing the differences are as follows: 39 | 40 | * Provides the option to use endpoints, introduced by [aurelia-api](https://github.com/SpoonX/aurelia-api), which simplifies API access. 41 | * By using aurelia-api the developer can specify which endpoints require the authentication patch. 42 | * TypeScript support added through the addition of d.ts (typescript definition) files 43 | * Lots of bug fixes 44 | * Refactored code to be more readable and performant 45 | 46 | **Aside:** Public SpoonX repositories are open to the community and actively maintained and used by the SpoonX company. They follow a strict deploy cycle with reviews and follow semantic versioning. This ensures code quality control and long term commitment. 47 | -------------------------------------------------------------------------------- /doc/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Introduction](README.md) 4 | * [Quick start](Quick start.md) 5 | * [Installation](installation.md) 6 | * [Configuration](configuration.md) 7 | * [Usage](usage.md) 8 | * [Options: BaseConfig](baseConfig.md) 9 | * [Api: FetchConfig](api_fetchConfig.md) 10 | * [Api: AuthService](api_authService.md) 11 | * [Refresh tokens](refresh_token.md) 12 | * [Auth0 Provider](auth0.md) 13 | * [OIDC Provider](oidc.md) 14 | * [Changelog](CHANGELOG.md) 15 | * [License](license.md) 16 | -------------------------------------------------------------------------------- /doc/api.json: -------------------------------------------------------------------------------- 1 | {"classes":[],"methods":[],"properties":[],"events":[]} -------------------------------------------------------------------------------- /doc/api_authService.md: -------------------------------------------------------------------------------- 1 | # AuthService class 2 | 3 | ```js 4 | import {AuthService} from 'aurelia-authentication'; 5 | ``` 6 | 7 | ---------- 8 | 9 | ## Binding signals 10 | 11 | ---------- 12 | 13 | ### authentication-change 14 | 15 | Whenever the authentication status changes, a binding signal 'authentication-change' is emitted. 16 | 17 | Example: 18 | 19 | ```html 20 |
Time: ${logTime | timeConverter & signal:'authentication-change'}
21 | ``` 22 | 23 | ---------- 24 | 25 | ## EventAggregator 26 | 27 | ---------- 28 | 29 | ### authentication-change 30 | 31 | Whenever the authentication status changes, the new status is published with the EventAggregator on the 'authentication-change' channel. 32 | 33 | Example: 34 | 35 | ```js 36 | this.eventAggregator.subscribe('authentication-change', authenticated => { 37 | // your code 38 | }); 39 | ``` 40 | 41 | ---------- 42 | 43 | 44 | ## Properties 45 | 46 | ---------- 47 | 48 | ### .client 49 | 50 | | Type | Description | 51 | | ---- | ----------------------------------------------------------------------------- | 52 | | Rest | The configured aurelia-api Rest instance used for all authentication requests | 53 | 54 | ---------- 55 | 56 | ### .config 57 | 58 | | Type | Description | 59 | | ---------- | --------------------------------------------------------------- | 60 | | BaseConfig | The BaseConfig instance with the current configuration settings | 61 | 62 | ---------- 63 | 64 | ### .authentication 65 | 66 | | Type | Description | 67 | | -------------- | ------------------------------------------------------------------------- | 68 | | Authentication | The Authentication instance which manages all the authentication requests | 69 | 70 | ---------- 71 | 72 | ### .timeoutID 73 | 74 | | Type | Description | 75 | | --------- | ---------------------------------------- | 76 | | timeoutID | Id of the currently set login timeout id | 77 | 78 | ---------- 79 | 80 | ### .authenticated 81 | 82 | | Type | Description | 83 | | ------- | ------------------------------------------- | 84 | | Boolean | Automatically updated authentication status | 85 | 86 | ---------- 87 | 88 | ## Methods 89 | 90 | ---------- 91 | 92 | ### .setTimeout(ttl) 93 | 94 | Sets the login timeout. 95 | 96 | CAUTION: .authenticated and isAuthenticated() might get different results when set manually. 97 | 98 | WARNING: Maximum timeout is 2^31 - 1 ms (ca. 24.85d). 99 | 100 | #### Parameters 101 | 102 | | Parameter | Type | Description | 103 | | --------- | ---------| ------------------ | 104 | | ttl | {Number} | Timeout time in ms | 105 | 106 | #### Example 107 | 108 | ```js 109 | this.authService.setTimeout(10000); 110 | ``` 111 | 112 | ---------- 113 | 114 | ### .clearTimeout() 115 | 116 | Clears the login timeout. CAUTION: .authenticated and isAuthenticated() might get different results when called manually. 117 | 118 | #### Example 119 | 120 | ```js 121 | this.authService.clearTimeout(); 122 | ``` 123 | 124 | ---------- 125 | 126 | ### .setResponseObject(response) 127 | 128 | Stores and analyses the servers response as Object. Sets login status and timeout. Publishes 'authentication-change' with the EventAggregator and emits the binding signal 'authentication-change' when the authorization status has changed. 129 | 130 | #### Parameters 131 | 132 | | Parameter | Type | Description | 133 | | --------- | -------- | -------------------------- | 134 | | response | {Object} | The servers login response | 135 | 136 | #### Example 137 | 138 | ```js 139 | this.authService.setResponseObject({access_token: 'a_fake_token'}); 140 | ``` 141 | 142 | ---------- 143 | 144 | ### .getMe([criteria]) 145 | 146 | Retrieves (GET) the profile from the BaseConfig.profileUrl. Accepts criteria. If the criteria is a string or a number, {id: criteria} will be passed to the server. 147 | 148 | #### Parameters 149 | 150 | | Parameter | Type | Description | 151 | | --------- | ------------------------ | ------------------------------------- | 152 | | criteria | {[{} | number | string]} | An ID, or object of supported filters | 153 | 154 | #### Returns 155 | 156 | A new `Promise` to be resolved with the request, or rejected with an error. 157 | 158 | #### Example 159 | 160 | ```js 161 | this.authService.getMe() 162 | .then(profile => { 163 | console.log(profile.username); 164 | }); 165 | ``` 166 | 167 | ---------- 168 | 169 | ### .updateMe(body[, criteria]) 170 | 171 | Updates the profile to the BaseConfig.profileUrl using BaseConfig.profileMethod (default PUT). Accepts criteria. If the criteria is a string or a number, {id: criteria} will be passed to the server. 172 | 173 | #### Parameters 174 | 175 | | Parameter | Type | Description | 176 | | --------- | ------------------------ | ------------------------------------- | 177 | | body | {} | The body | 178 | | criteria | [{} | number | string] | An ID, or object of supported filters | 179 | 180 | #### Returns 181 | 182 | A new `Promise` to be resolved with the request, or rejected with an error. 183 | 184 | #### Example 185 | 186 | ```js 187 | this.authService.updateMe({fullname: 'Jane Doe'}) 188 | .then(profile => { 189 | console.log(profile.fullname); 190 | }); 191 | ``` 192 | 193 | ---------- 194 | 195 | ### .getAccessToken() 196 | 197 | Gets the current access token from storage 198 | 199 | #### Returns 200 | 201 | A `string` with the access token or `null`. 202 | 203 | #### Example 204 | 205 | ```js 206 | let currentToken = this.authService.getAccessToken(); 207 | ``` 208 | 209 | ---------- 210 | 211 | ### .getRefreshToken() 212 | 213 | Gets the current refresh token from storage 214 | 215 | #### Returns 216 | 217 | A `string` with the refresh token or `null`. 218 | 219 | #### Example 220 | 221 | ```js 222 | let currentToken = this.authService.getRefreshToken(); 223 | ``` 224 | 225 | ---------- 226 | 227 | ### .isAuthenticated(callback) 228 | 229 | Checks if there is a (valid) token in storage. If the token is isExpired and BaseConfig.autoUpdateToken===true, it returns true and a new access token automatically requested using the refesh_token. If you use it in a getter, aurelia will dirty check on uodates. Hence, may better either use .authenticated or use the binding signal 'authentication-change' to ensure udpdates. 230 | 231 | CAUTION: When you cancel or manually set the timeout, .isAuthenticated and .authenticated could yield different results. 232 | 233 | #### Parameters 234 | 235 | | Parameter | Type | Description | 236 | | ---------- | -------------------------------- | -------------------------------------------------------- | 237 | | [callback] | (authenticated: boolean) => void | optional callback executed once the status is determined | 238 | 239 | #### Returns 240 | 241 | `true`, for Non-JWT and unexpired JWT, `false` for no token or expired JWT 242 | 243 | #### Example 244 | 245 | ```js 246 | isAuthenticated() { 247 | return this.authService.isAuthenticated(); 248 | } 249 | ``` 250 | 251 | ```html 252 |
  • 253 | ${row.title} 254 |
  • 255 | 256 | ``` 257 | 258 | ---------- 259 | 260 | ### .getExp() 261 | 262 | Gets exp of the access token in milliseconds 263 | 264 | #### Returns 265 | 266 | A `Number` for JWT or `NaN` for other tokens. 267 | 268 | #### Example 269 | 270 | ```js 271 | let exp = this.authService.getExp(); 272 | ``` 273 | 274 | ---------- 275 | 276 | ### .getTtl() 277 | 278 | Gets ttl of the access token in seconds 279 | 280 | #### Returns 281 | 282 | A `Number` for JWT or `NaN` for other tokens. 283 | 284 | #### Example 285 | 286 | ```js 287 | let ttl = this.authService.getTtl(); 288 | ``` 289 | 290 | ---------- 291 | 292 | ### .isTokenExpired() 293 | 294 | Checks whether the token is expired 295 | 296 | #### Returns 297 | 298 | A `boolean` for JWT or `undefined` for other tokens. 299 | 300 | #### Example 301 | 302 | ```js 303 | let isExpired = this.authService.isTokenExpired(); 304 | ``` 305 | 306 | ---------- 307 | 308 | ### .getTokenPayload() 309 | 310 | Gets the current token payload from storage 311 | 312 | #### Returns 313 | 314 | A `Object` for JWT or `null` for other tokens. 315 | 316 | #### Example 317 | 318 | ```js 319 | let payload = this.authService.getTokenPayload(); 320 | ``` 321 | 322 | ---------- 323 | 324 | ### .getIdTokenPayload() 325 | 326 | Gets the current id token payload from storage 327 | 328 | #### Returns 329 | 330 | A `Object` for JWT or `null` for other tokens. 331 | 332 | #### Example 333 | 334 | ```js 335 | let payload = this.authService.getIdTokenPayload(); 336 | ``` 337 | 338 | ---------- 339 | 340 | ### .updateToken() 341 | 342 | Request a new token using the refresh_token. Returns a Promise< boolean > with the isAuthenticated() result afterwards. Parallel calls will resolve with the same server response. 343 | 344 | #### Returns 345 | 346 | Promise< boolean > with the isAuthenticated() result. 347 | 348 | #### Example 349 | 350 | ```js 351 | this.authService.updateToken() 352 | .then(result => auth = result); 353 | ``` 354 | 355 | ---------- 356 | 357 | ### .signup(displayName, email, password[, options[, redirectUri]]) 358 | 359 | ### .signup(credentials[, options[, redirectUri]]) 360 | 361 | Signup locally using BaseConfig.signupUrl either with credentials strings or an object. Can pass options to aurelia-apis Rest.post request. Logs in if BaseConfig.loginOnSignup is set and saves the login response in local storage (default). Else redirects to BaseConfig.signupRedirect if set. The redirectUri parameter overwrites the BaseConfig.signupRedirect setting. Set to 0 it prevents redirection and set to a string, will redirect there. 362 | 363 | #### Parameters v1 364 | 365 | | Parameter | Type | Description | 366 | | ------------- | ----------------------- | ---------------------------------------------------- | 367 | | displayName | string | Passed on as displayName: displayName | 368 | | email | string | Passed on as email: email | 369 | | password | string | Passed on as password: password | 370 | | [options] | [{}] | Options object passed to aurelia-api | 371 | | [redirectUri] | [string] | redirectUri overwrite. '' = no redirection | 372 | 373 | #### Parameters v2 374 | 375 | | Parameter | Type | Description | 376 | | ------------- | ----------------------- | ---------------------------------------------------- | 377 | | credentials | {} | Passed on credentials object | 378 | | [options] | [{}] | Options object passed to aurelia-api | 379 | | [redirectUri] | [string] | redirectUri overwrite. '' = no redirection | 380 | 381 | #### Returns 382 | 383 | Promise: response 384 | 385 | #### Examples 386 | 387 | ```js 388 | this.authService.signup('Jane Doe', 'janedoe@example', 'securePasword') 389 | .then(response => { 390 | console.log(response); 391 | }); 392 | //or 393 | this.authService.signup({ 394 | fullname: 'Jane Doe', 395 | username: 'janedoe', 396 | password: 'securePasword' 397 | }, {headers: {'Content-Type': 'application/x-www-form-urlencoded'}}, 398 | "#/confirm-page") 399 | .then(response => { 400 | console.log(response); 401 | }); 402 | 403 | ``` 404 | 405 | ---------- 406 | 407 | ### .login(email, password[, options[, redirectUri]]) 408 | 409 | ### .login(credentials[, options[, redirectUri]]) 410 | 411 | Login locally using BaseConfig.loginUrl either with credentials strings or an object. Can pass options to aurelia-apis Rest.post request. The login response is saved in local storage (default). Redirects to BaseConfig.loginRedirect if set. The redirectUri parameter overwrites the BaseConfig.loginRedirect setting. Set to 0 it prevents redirection and set to a string, will redirect there. 412 | 413 | #### Parameters v1 414 | 415 | | Parameter | Type | Description | 416 | | ------------- | ----------------------- | ---------------------------------------------------- | 417 | | email | string | Passed on as email: email | 418 | | password | string | Passed on as password: password | 419 | | [options] | [{}] | Options object passed to aurelia-api | 420 | | [redirectUri] | [string] | redirectUri overwrite. '' = no redirection | 421 | 422 | #### Parameters v2 423 | 424 | | Parameter | Type | Description | 425 | | ------------- | ----------------------- | ---------------------------------------------------- | 426 | | credentials | {} | Passed on credentials object | 427 | | [options] | [{}] | Options object passed to aurelia-api | 428 | | [redirectUri] | [string] | redirectUri overwrite. '' = no redirection | 429 | 430 | #### Returns 431 | 432 | Promise: response 433 | 434 | #### Examples 435 | 436 | ```js 437 | this.authService.login('janedoe@example.com', 'securePasword') 438 | .then(response => { 439 | console.log(response); 440 | }); 441 | //or 442 | this.authService.login({ 443 | username: 'janedoe', 444 | password: 'securePasword' 445 | }, {headers: {Authorization: 'none'}}, 446 | "#/special-page") 447 | .then(response => { 448 | console.log(response); 449 | }); 450 | // or for eg OpenIddict 451 | this.authService.login({ 452 | username: this.email, 453 | password: this.password, 454 | grant_type: "password" 455 | }, {headers: {'Content-Type': 'application/x-www-form-urlencoded'}}) 456 | .then(response => { 457 | console.log(response); 458 | }); 459 | 460 | ``` 461 | 462 | ---------- 463 | 464 | ### .logout([redirectUri [, query [, name]]]) 465 | 466 | Logout locally by deleting the authentication information from the storage. Redirects to BaseConfig.logoutRedirect if set. The redirectUri parameter overwrites the BaseConfig.logoutRedirect setting. Set to 0 it prevents redirection. Set to a string, will redirect there. If BaseConfig.logoutUrl is set, a logout request is send to the server first using the BaseConfig.logoutMethod. 467 | 468 | #### Parameters 469 | 470 | | Parameter | Type | Description | 471 | | ------------- | --------- | ------------------------------------------- | 472 | | [redirectUri] | [string] | redirectUri overwrite. '' = no redirection | 473 | | [query] | [string] | optional query string for the uri | 474 | | [name] | [string] | optional provider logout and state checking | 475 | 476 | #### Returns 477 | 478 | Promise: undefined 479 | or if the logoutUrl is set 480 | Promise: response 481 | 482 | #### Examples 483 | 484 | ```js 485 | this.authService.logout("#/special-page"); 486 | //or 487 | this.authService.logout() 488 | .then(() => { 489 | alert('Bye'); 490 | }); 491 | 492 | ``` 493 | 494 | ---------- 495 | 496 | ### .authenticate(name[, redirectUri[, userData]]) 497 | 498 | Authenticate with third-party with the BaseConfig.providers settings. The login response is saved in local storage (default). Redirects to BaseConfig.loginRedirect if set. The redirectUri parameter overwrites the BaseConfig.loginRedirect setting. Set to 0 it prevents redirection and set to a string, will redirect there. An optional userData object can be passed on to the third-party server. 499 | 500 | #### Parameters 501 | 502 | | Parameter | Type | Description | 503 | | ------------- | --------- | ------------------------------------------ | 504 | | provider | string | Provider name of BaseConfig.providers | 505 | | [redirectUri] | [string] | redirectUri overwrite. '' = no redirection | 506 | | [userData] | [{}] | userData object passed to provider | 507 | 508 | #### Returns 509 | 510 | Promise: response 511 | 512 | #### Examples 513 | 514 | ```js 515 | this.authService.authenticate('facebook', '#/facebook-post-page') 516 | .then(response => { 517 | console.log(response); 518 | }); 519 | ``` 520 | 521 | ### .unlink(name[, redirectUri]) 522 | 523 | Unlink third-party with the BaseConfig.providers settings. Optionally redirects afterwards using the redirectUri parameter. 524 | 525 | #### Parameters 526 | 527 | | Parameter | Type | Description | 528 | | ------------- | --------- | ------------------------------------------ | 529 | | provider | string | Provider name of BaseConfig.providers | 530 | | [redirectUri] | [string] | redirectUri overwrite. '' = no redirection | 531 | 532 | #### Returns 533 | 534 | Promise: response 535 | 536 | #### Examples 537 | 538 | ```js 539 | this.authService.unlink('facebook', '#/facebook-post-unlink') 540 | .then(response => { 541 | console.log(response); 542 | }); 543 | ``` 544 | 545 | ---------- 546 | -------------------------------------------------------------------------------- /doc/api_fetchConfig.md: -------------------------------------------------------------------------------- 1 | # FetchConfig 2 | 3 | ## Configuring the aurelia fetch client 4 | 5 | Aurelia-authentication uses [aurelia-api](https://github.com/SpoonX/aurelia-api), which has support for [multiple endpoints](http://aurelia-api.spoonx.org/Quick%20start.html#multiple-endpoints). 6 | By default, aurelia-authentication uses the HttpClient from [aurelia-fetch-client](https://github.com/aurelia/fetch-client) when no specific endpoint has been configured, and if no [default endpoint](http://aurelia-api.spoonx.org/Quick%20start.html#default-endpoint) was configured. 7 | So, if you want aurelia-authentication to use your **default** endpoint, you only have to configure aurelia-api. 8 | If you wish to use a **specific** endpoint to have aurelia-authentication talk to, you have to set the `endpoint` config option to a string, being the endpoint name. 9 | 10 | ### Configure aurelia-api endpoints 11 | 12 | If you are using [aurelia-api](http://aurelia-api.spoonx.org/), you can simply use the `configureEndpoints` config option. Set this to an array of endpoint names to configure, and aurelia-authentication will do the rest, and make sure that all requests to these endpoints (when authenticated) get enriched with the authorization header. 13 | 14 | ## Configure the Fetch Client 15 | 16 | ### The aurelia Fetch Client singleton 17 | 18 | If you don't want to use aurelia-api, you have to configure the aurelia-fetch-client. In your aurelia app file, inject the {FetchConfig} class from aurelia-authentication. We need to explicitly opt-in for the configuration of your fetch client by calling the configure function of the FetchConfig class: 19 | 20 | ```js 21 | import {inject} from 'aurelia-framework'; 22 | import {Router} from 'aurelia-router'; 23 | import {FetchConfig} from 'aurelia-authentication'; 24 | 25 | @inject(Router,FetchConfig) 26 | export class App { 27 | 28 | constructor(router, fetchConfig) { 29 | this.router = router; 30 | this.fetchConfig = fetchConfig; 31 | //... 32 | } 33 | 34 | activate() { 35 | // this will add the interceptor for the Authorization header to the HttpClient singleton 36 | this.fetchConfig.configure(); 37 | } 38 | } 39 | ``` 40 | 41 | ### An own Fetch Client, Rest Client or endpoint 42 | 43 | You also can configure your own Fetch Client instance: 44 | 45 | ```js 46 | this.fetchConfig.configure(new HttpClient); 47 | ``` 48 | 49 | Or a Rest Client instance: 50 | 51 | ```js 52 | this.fetchConfig.configure(new Rest); 53 | ``` 54 | 55 | Or an endpoint by name: 56 | 57 | ```js 58 | this.fetchConfig.configure('api'); 59 | ``` 60 | 61 | Or any of the above in an Array: 62 | 63 | ```js 64 | this.fetchConfig.configure(['api', 'github']); 65 | ``` 66 | -------------------------------------------------------------------------------- /doc/auth0.md: -------------------------------------------------------------------------------- 1 | # Authenticating with Auth0 using the Lock widget 2 | 3 | The Auth0 provider is different than the other providers you normally use with `aurelia-authentication`. It should be the only provider configured in your app, and you need to set at least the `clientId` and `clientDomain` config properties. The `oauthType` property must be set equal to *auth0-lock*. 4 | 5 | At the time of writing this, it relies by default on the [Auth0 Lock](https://auth0.com/lock) library, that handles both the UI and the logic for all the authentication tasks. You can load it by including a script tag directly in your index.html file, or alternatively with a loader of your choice. It is highly encouraged to use Lock version 11 or greater as in mid 2018, Auth0 is beginning to decpreciate acccess to and ability to integrate with older API endpoints and versions of Lock. 6 | 7 | You cannot use the `open` method with this provider, but you'll always call `authenticate` instead (see the code snippet at the end of this article). The `authenticate` method will return an object with an `access_token` and `id_token` properties. The login flow is described better in this [article](https://auth0.com/docs/protocols#oauth-for-native-clients-and-javascript-in-the-browser). 8 | 9 | Previous versions of the Auth0 integration only returned an `access_token`, but despite its name the property contained an **ID Token** (in the form of a *JWT*) containing at least the user's `sub` id. To better conform with Auth0 standards, this has changed and you will now recieve both the `access_token` and `id_token`. With the new `getIdTokenPayload()` function you can then easily grab the payload of the `id_token` and use it within your application. 10 | 11 | With the change to the returned object, if you want to use the `access_token`, you must set the configuration option `getAccessTokenFromResponse` to `true`. The `access_token` returned from Auth0 will be opaque and not in a standard JWT format. However it can still be used to authenticate to certain Auth0 API endpoints such as [/userinfo](https://auth0.com/docs/api/authentication#user-profile). 12 | 13 | If you want an `access_token` in JWT format that is not opaque and be also be used to store/retreive information in it, then in the Auth0 portion of the config you must pass along an `audience`. It would look something like this (other options omitted for brevity): 14 | ``` 15 | { 16 | auth0: { 17 | lockOptions: { 18 | auth: { 19 | audience: 'https://YOUR_AUTH0_URL/api/v2/' 20 | } 21 | } 22 | } 23 | } 24 | ``` 25 | 26 | 27 | Other *Lock* properties should be set under the `lockOptions` config property, except for: 28 | 29 | - `popupOptions` should be set on the root of the provider config object, for consistency with other providers 30 | - `state` should be set on the root of the provider option, and its completely optional like with other providers 31 | 32 | ## Sample login method snippet (ES6) 33 | 34 | ```js 35 | import {Cookie} from 'aurelia-cookie'; 36 | 37 | ... 38 | 39 | login() { 40 | // AuthService.onLogout() will be called, when the token expires 41 | // or AuthService.logout() is called manually 42 | this.authService.onLogout = () => { 43 | Cookie.delete('cookie-bearer'); 44 | } 45 | 46 | return this.authService.authenticate('auth0') 47 | .then(response => { 48 | // you can set a cookie for cookie based authentication 49 | let jwtExp = this.authService.getExp(); 50 | let expiryDate = new Date(0); 51 | expiryDate.setUTCSeconds(jwtExp); 52 | Cookie.set('cookie-bearer', response.access_token, { 53 | expire: expiryDate, 54 | secure: window.location.protocol === 'https:' ? true : false // true in production 55 | }); 56 | }); 57 | }; 58 | ``` 59 | 60 | ## Setting auth0 lock params 61 | If you need to configure additional parameters to pass to Auth0, then you can pass those in the `lockOptions` object as seen below. Parameters are described here: [Lock Authentication params](https://auth0.com/docs/libraries/lock/v11/configuration) 62 | 63 | ``` 64 | const config = { 65 | baseUrl: endpoints.auth, 66 | configureEndpoints: ['auth', 'api'], 67 | providers: { 68 | auth0: { 69 | name: 'auth0', 70 | oauthType: 'auth0-lock', 71 | responseType: 'token', 72 | clientId: '.......', 73 | clientDomain: '..........auth0.com', 74 | lockOptions: { 75 | popup: false, 76 | auth: { 77 | params: { scope: 'openid email name picture' } 78 | } 79 | }, 80 | state: function () { 81 | return Math.random().toString(36).substr(2); 82 | } 83 | }, 84 | } 85 | }; 86 | ``` 87 | -------------------------------------------------------------------------------- /doc/baseConfig.md: -------------------------------------------------------------------------------- 1 | # Full configuration options 2 | 3 | ```js 4 | // If using aurelia-api: 5 | // ===================== 6 | 7 | // This is the name of the endpoint used for any requests made in relation to authentication (login, logout, etc.). An empty string selects the default endpoint of aurelia-api. 8 | endpoint : null; 9 | // When authenticated, these endpoints will have the token added to the header of any requests (for authorization). Accepts an array of endpoint names. An empty string selects the default endpoint of aurelia-api. 10 | configureEndpoints : null; 11 | 12 | 13 | // SPA related options 14 | // =================== 15 | 16 | // The SPA url to which the user is redirected after a successful login 17 | loginRedirect : '#/'; 18 | // The SPA url to which the user is redirected after a successful logout 19 | logoutRedirect : '#/'; 20 | // The SPA route used when an unauthenticated user tries to access an SPA page that requires authentication 21 | loginRoute : '/login'; 22 | // Whether or not an authentication token is provided in the response to a successful signup 23 | loginOnSignup : true; 24 | // If loginOnSignup == false: The SPA url to which the user is redirected after a successful signup (else loginRedirect is used) 25 | signupRedirect : '#/login'; 26 | // The SPA url to load when the token expires (null = don't redirect) 27 | expiredRedirect = '#/'; 28 | // The SPA url to load when the authentication status changed in other tabs/windows (detected through storageEvents) (null = don't redirect) 29 | storageChangedRedirect = '#/'; 30 | 31 | 32 | // API related options 33 | // =================== 34 | 35 | // The base url used for all authentication related requests, including provider.url below. 36 | // This appends to the httpClient/endpoint base url, it does not override it. 37 | baseUrl : ''; 38 | // The API endpoint to which login requests are sent 39 | loginUrl : '/auth/login'; 40 | // The API endpoint to which logout requests are sent (not needed for jwt) 41 | logoutUrl : null; 42 | // The HTTP method used for 'logout' requests (Options: 'get' or 'post') 43 | logoutMethod : 'get'; 44 | // The API endpoint to which signup requests are sent 45 | signupUrl : '/auth/signup'; 46 | // The API endpoint used in profile requests (inc. `find/get` and `update`) 47 | profileUrl : '/auth/me'; 48 | // The method used to update the profile ('put' or 'patch') 49 | profileMethod : 'put'; 50 | // The API endpoint used with oAuth to unlink authentication 51 | unlinkUrl : '/auth/unlink/'; 52 | // The HTTP method used for 'unlink' requests (Options: 'get' or 'post') 53 | unlinkMethod : 'get'; 54 | // The API endpoint to which refreshToken requests are sent. null = loginUrl 55 | refreshTokenUrl = null; 56 | 57 | // Token Options 58 | // ============= 59 | 60 | // The header property used to contain the authToken in the header of API requests that require authentication 61 | authHeader : 'Authorization'; 62 | // The token name used in the header of API requests that require authentication 63 | authTokenType : 'Bearer'; 64 | // Logout when the token is invalidated by the server 65 | logoutOnInvalidToken : false; 66 | // The property from which to get the access token after a successful login or signup 67 | accessTokenProp : 'access_token'; 68 | 69 | 70 | // If the property defined by `accessTokenProp` is an object: 71 | // ------------------------------------------------------------ 72 | 73 | //This is the property from which to get the token `{ "accessTokenProp": { "accessTokenName" : '...' } }` 74 | accessTokenName : 'token'; 75 | // This allows the token to be a further object deeper `{ "accessTokenProp": { "accessTokenRoot" : { "accessTokenName" : '...' } } }` 76 | accessTokenRoot : false; 77 | 78 | 79 | // Refresh Token Options 80 | // ===================== 81 | 82 | // Option to turn refresh tokens On/Off 83 | useRefreshToken : false; 84 | // The option to enable/disable the automatic refresh of Auth tokens using Refresh Tokens 85 | autoUpdateToken : true; 86 | // Oauth Client Id 87 | clientId : false; 88 | // Oauth Client secret 89 | clientSecret : null; 90 | // The property from which to get the refresh token after a successful token refresh 91 | refreshTokenProp : 'refresh_token'; 92 | // The property name used to send the existing token when refreshing `{ "refreshTokenSubmitProp": '...' }` 93 | refreshTokenSubmitProp : 'refresh_token'; 94 | // Option to maintain unchanged response properties. This allows to work with a single refresh_token that was received once and the expiration only is extend 95 | keepOldResponseProperties : false; 96 | 97 | // If the property defined by `refreshTokenProp` is an object: 98 | // ----------------------------------------------------------- 99 | 100 | // This is the property from which to get the token `{ "refreshTokenProp": { "refreshTokenName" : '...' } }` 101 | refreshTokenName : 'token'; 102 | // This allows the refresh token to be a further object deeper `{ "refreshTokenProp": { "refreshTokenRoot" : { "refreshTokenName" : '...' } } }` 103 | refreshTokenRoot : false; 104 | 105 | 106 | // Id Token Options 107 | // ===================== 108 | 109 | // The property name from which to get the user authentication token. Can also be dotted "anIdTokenProp.anIdTokenName" 110 | idTokenProp : 'id_token'; 111 | 112 | // If the property defined by `idTokenProp` is an object: 113 | // ----------------------------------------------------------- 114 | 115 | // This is the property from which to get the id token `{ "idTokenProp": { "idTokenName" : '...' } }` 116 | idTokenName : 'token'; 117 | // This allows the id token to be a further object deeper `{ "idTokenProp": { "idTokenRoot" : { "idTokenName" : '...' } } }` 118 | idTokenRoot : false; 119 | 120 | 121 | // Miscellaneous Options 122 | // ===================== 123 | 124 | // Whether to enable the fetch interceptor which automatically adds the authentication headers 125 | // (or not... e.g. if using a session based API or you want to override the default behaviour) 126 | httpInterceptor : true; 127 | // For OAuth only: Tell the API whether or not to include token cookies in the response (for session based APIs) 128 | withCredentials : true; 129 | // Controls how the popup is shown for different devices (Options: 'browser' or 'mobile') 130 | platform : 'browser'; 131 | // Determines the `window` property name upon which aurelia-authentication data is stored (Default: `window.localStorage`) 132 | storage : 'localStorage'; 133 | // The key used for storing the authentication response locally 134 | storageKey : 'aurelia_authentication'; 135 | // full page reload if authorization changed in another tab (recommended to set it to 'true') 136 | storageChangedReload : false; 137 | // optional function to extract the expiration date. Takes the server response as parameter and returns NumericDate = number of seconds! since 1 January 1970 00:00:00 UTC (Unix Epoch) 138 | // eg (expires_in in sec): getExpirationDateFromResponse = serverResponse => new Date().getTime() / 1000 + serverResponse.expires_in; 139 | getExpirationDateFromResponse : null; 140 | // optional function to extract the access token from the response. Takes the server response as parameter and returns a token 141 | // eg: getAccessTokenFromResponse = serverResponse => serverResponse.data[0].access_token; 142 | getAccessTokenFromResponse : null; 143 | // optional function to extract the refresh token from the response. Takes the server response as parameter and returns a token 144 | // eg: getRefreshTokenFromResponse = serverResponse => serverResponse.data[0].refresh_token; 145 | getRefreshTokenFromResponse : null; 146 | 147 | // List of value-converters to make global 148 | globalValueConverters : ['authFilterValueConverter']; 149 | 150 | // Default headers for login and token-update endpoint 151 | defaultHeadersForTokenRequests : { 152 | 'Content-Type': 'application/json' 153 | } 154 | 155 | //OAuth provider specific related configuration 156 | // ============================================ 157 | providers : { 158 | facebook: { 159 | name: 'facebook', 160 | url: '/auth/facebook', 161 | authorizationEndpoint: 'https://www.facebook.com/v2.5/dialog/oauth', 162 | redirectUri: window.location.origin + '/', 163 | requiredUrlParams: ['display', 'scope'], 164 | scope: ['email'], 165 | scopeDelimiter: ',', 166 | display: 'popup', 167 | oauthType: '2.0', 168 | popupOptions: { width: 580, height: 400 } 169 | }, 170 | google: { 171 | name: 'google', 172 | url: '/auth/google', 173 | authorizationEndpoint: 'https://accounts.google.com/o/oauth2/auth', 174 | redirectUri: window.location.origin, 175 | requiredUrlParams: ['scope'], 176 | optionalUrlParams: ['display', 'state'], 177 | scope: ['profile', 'email'], 178 | scopePrefix: 'openid', 179 | scopeDelimiter: ' ', 180 | display: 'popup', 181 | oauthType: '2.0', 182 | popupOptions: { width: 452, height: 633 }, 183 | state: function() { 184 | let rand = Math.random().toString(36).substr(2); 185 | return encodeURIComponent(rand); 186 | } 187 | }, 188 | github: { 189 | name: 'github', 190 | url: '/auth/github', 191 | authorizationEndpoint: 'https://github.com/login/oauth/authorize', 192 | redirectUri: window.location.origin, 193 | optionalUrlParams: ['scope'], 194 | scope: ['user:email'], 195 | scopeDelimiter: ' ', 196 | oauthType: '2.0', 197 | popupOptions: { width: 1020, height: 618 } 198 | }, 199 | instagram: { 200 | name: 'instagram', 201 | url: '/auth/instagram', 202 | authorizationEndpoint: 'https://api.instagram.com/oauth/authorize', 203 | redirectUri: window.location.origin, 204 | requiredUrlParams: ['scope'], 205 | scope: ['basic'], 206 | scopeDelimiter: '+', 207 | oauthType: '2.0' 208 | }, 209 | linkedin: { 210 | name: 'linkedin', 211 | url: '/auth/linkedin', 212 | authorizationEndpoint: 'https://www.linkedin.com/uas/oauth2/authorization', 213 | redirectUri: window.location.origin, 214 | requiredUrlParams: ['state'], 215 | scope: ['r_emailaddress'], 216 | scopeDelimiter: ' ', 217 | state: 'STATE', 218 | oauthType: '2.0', 219 | popupOptions: { width: 527, height: 582 } 220 | }, 221 | twitter: { 222 | name: 'twitter', 223 | url: '/auth/twitter', 224 | authorizationEndpoint: 'https://api.twitter.com/oauth/authenticate', 225 | redirectUri: window.location.origin, 226 | oauthType: '1.0', 227 | popupOptions: { width: 495, height: 645 } 228 | }, 229 | twitch: { 230 | name: 'twitch', 231 | url: '/auth/twitch', 232 | authorizationEndpoint: 'https://api.twitch.tv/kraken/oauth2/authorize', 233 | redirectUri: window.location.origin, 234 | requiredUrlParams: ['scope'], 235 | scope: ['user_read'], 236 | scopeDelimiter: ' ', 237 | display: 'popup', 238 | oauthType: '2.0', 239 | popupOptions: { width: 500, height: 560 } 240 | }, 241 | live: { 242 | name: 'live', 243 | url: '/auth/live', 244 | authorizationEndpoint: 'https://login.live.com/oauth20_authorize.srf', 245 | redirectUri: window.location.origin, 246 | requiredUrlParams: ['display', 'scope'], 247 | scope: ['wl.emails'], 248 | scopeDelimiter: ' ', 249 | display: 'popup', 250 | oauthType: '2.0', 251 | popupOptions: { width: 500, height: 560 } 252 | }, 253 | yahoo: { 254 | name: 'yahoo', 255 | url: '/auth/yahoo', 256 | authorizationEndpoint: 'https://api.login.yahoo.com/oauth2/request_auth', 257 | redirectUri: window.location.origin, 258 | scope: [], 259 | scopeDelimiter: ',', 260 | oauthType: '2.0', 261 | popupOptions: { width: 559, height: 519 } 262 | }, 263 | bitbucket: { 264 | name: 'bitbucket', 265 | url: '/auth/bitbucket', 266 | authorizationEndpoint: 'https://bitbucket.org/site/oauth2/authorize', 267 | redirectUri: window.location.origin + '/', 268 | requiredUrlParams: ['scope'], 269 | scope: ['email'], 270 | scopeDelimiter: ' ', 271 | oauthType: '2.0', 272 | popupOptions: { width: 1028, height: 529 } 273 | }, 274 | azure_ad: { 275 | name: 'azure_ad', 276 | url: '/auth/azure_ad', 277 | authorizationEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', 278 | redirectUri: window.location.origin, 279 | logoutEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/logout', 280 | postLogoutRedirectUri: window.location.origin, 281 | requiredUrlParams: ['scope'], 282 | scope: ['user.read'], 283 | scopeDelimiter: ' ', 284 | oauthType: '2.0' 285 | }, 286 | auth0: { 287 | name: 'auth0', 288 | oauthType: 'auth0-lock', 289 | clientId: 'your_client_id', 290 | clientDomain: 'your_domain_url', 291 | display: 'popup', 292 | lockOptions: { 293 | popup: true 294 | }, 295 | responseType: 'token', 296 | state: function() { 297 | return Math.random().toString(36).substr(2); 298 | } 299 | }, 300 | genericOIDCProvider: { 301 | name: 'identityServer', 302 | oauthType: '2.0', 303 | clientId: 'MustMatchConfiguredOIDCClientApplication', 304 | authorizationEndpoint: 'http://localhost:54540/connect/authorize', 305 | redirectUri: 'http://localhost:49862/', 306 | logoutEndpoint: 'http://localhost:54540/connect/logout', 307 | postLogoutRedirectUri: 'http://localhost:49862/', 308 | responseType: 'token id_token', 309 | scope: ['openid email profile'], 310 | requiredUrlParams: ['scope', 'nonce', 'resource'], 311 | state: function () { 312 | var text = ""; 313 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 314 | for (var i = 0; i < 32; i++) { 315 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 316 | } 317 | return text; 318 | }, 319 | nonce: function () { 320 | var text = ""; 321 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 322 | for (var i = 0; i < 32; i++) { 323 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 324 | } 325 | return text; 326 | }, 327 | popupOptions: { width: 1028, height: 529 }, 328 | resource: 'aurelia-openiddict-resources aurelia-openiddict-server' 329 | } 330 | }; 331 | ``` 332 | 333 | More non-Aurelia specific details can be found on the [Sattelizer Github page](https://github.com/sahat/satellizer/). 334 | 335 | ## Separate settings for development and productions 336 | 337 | One way of using different settings for development and production would be: 338 | 339 | ```js 340 | import extend from 'extend'; 341 | 342 | var baseConfig = { 343 | endpoint: 'auth', 344 | configureEndpoints: ['auth', 'protected-api'] 345 | }; 346 | 347 | var configForDevelopment = { 348 | providers: { 349 | google: { 350 | clientId: '239531826023-ibk10mb9p7ull54j55a61og5lvnjrff6.apps.googleusercontent.com' 351 | } 352 | , 353 | linkedin:{ 354 | clientId: '778mif8zyqbei7' 355 | }, 356 | facebook:{ 357 | clientId: '1452782111708498' 358 | } 359 | } 360 | }; 361 | 362 | var configForProduction = { 363 | providers: { 364 | google: { 365 | clientId: '239531826023-3ludu3934rmcra3oqscc1gid3l9o497i.apps.googleusercontent.com' 366 | } 367 | , 368 | linkedin: { 369 | clientId:'7561959vdub4x1' 370 | }, 371 | facebook: { 372 | clientId:'1653908914832509' 373 | } 374 | 375 | } 376 | }; 377 | 378 | var config; 379 | if (window.location.hostname === 'localhost') { 380 | config = extend(true, {}, baseConfig, configForDevelopment); 381 | } 382 | else { 383 | config = extend(true, {}, baseConfig, configForProduction); 384 | } 385 | 386 | export default config; 387 | ``` 388 | 389 | The above configuration file can cope with a development and production version (not mandatory of course). The strategy is that when your run on localhost, the development configuration file is used, otherwise the production configuration file is taken. 390 | -------------------------------------------------------------------------------- /doc/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | Aurelia-authentication comes mostly pre-configured. During plugin configuration you can apply your custom setting. For configuration options and defaults see [baseConfig](baseConfig.md). 4 | 5 | ## Configure with object or function 6 | 7 | Aurelia-authentication can be configured with either an object or an configuration function. 8 | 9 | ```js 10 | import authConfig from './authConfig'; 11 | 12 | export function configure(aurelia) { 13 | aurelia.use 14 | /* Your other plugins and init code */ 15 | 16 | /* configure aurelia-authentication with a config object */ 17 | .plugin('aurelia-authentication', {baseUrl: 'https://api.example.com/auth'}); 18 | 19 | /* configure aurelia-authentication from config file */ 20 | .plugin('aurelia-authentication', baseConfig => { 21 | baseConfig.configure(authConfig); 22 | }); 23 | }); 24 | ``` 25 | 26 | ## With aurelia-fetch-client 27 | 28 | Aurelia-authentication can be used with the aurelia-fetch-client. After configuration, the HttpClient is wrapped automatically in an aurelia-api Rest client. The Rest client is then available under baseConfig.client or authService.client and the HttpClient under baseConfig.client.client or authService.client.client. 29 | 30 | ```js 31 | import authConfig from './authConfig'; 32 | 33 | export function configure(aurelia) { 34 | aurelia.use 35 | /* Your other plugins and init code */ 36 | 37 | .plugin('aurelia-authentication', baseConfig => { 38 | baseConfig.configure(authConfig); 39 | }); 40 | 41 | /* At this point, baseConfig.client is the aurelia-api Rest client. The HttpClient is the baseConfig.client.client */ 42 | baseConfig.client.client 43 | .withBaseUrl('api/') 44 | .withDefaults({ 45 | credentials: 'same-origin', 46 | headers: { 47 | 'Accept': 'application/json', 48 | 'X-Requested-With': 'Fetch' 49 | } 50 | }) 51 | .withInterceptor({ 52 | request(request) { 53 | console.log(`Requesting ${request.method} ${request.url}`); 54 | return request; // you can return a modified Request, or you can short-circuit the request by returning a Response 55 | }, 56 | response(response) { 57 | console.log(`Received ${response.status} ${response.url}`); 58 | return response; // you can return a modified Response 59 | } 60 | }); 61 | }); 62 | ``` 63 | 64 | ## With aurelia-api 65 | 66 | Aurelia-authentication is best used with the endpoints of aurelia-api. 67 | 68 | ```js 69 | export function configure(aurelia) { 70 | aurelia.use 71 | /* Your other plugins and init code */ 72 | 73 | /* setup the api endpoints first */ 74 | .plugin('aurelia-api', configure => { 75 | configure 76 | .registerEndpoint('auth', 'https://myapi.org/auth') 77 | .registerEndpoint('protected-api', 'https://myapi.org/protected-api') 78 | .registerEndpoint('public-api', 'http://myapi.org/public-api'); 79 | .setDefaultEndpoint('auth'); 80 | }) 81 | 82 | /* configure aurelia-authentication to use above aurelia-api endpoints */ 83 | .plugin('aurelia-authentication', baseConfig => { 84 | baseConfig.configure({ 85 | endpoint: 'auth', // '' for the default endpoint 86 | configureEndpoints: ['auth', 'api'] // '' for the default endpoint 87 | }); 88 | 89 | /* At this point, baseConfig.client is the aurelia-api Rest client from the 'auth' endpoint. The HttpClient is baseConfig.client.client */ 90 | }); 91 | }); 92 | ``` 93 | -------------------------------------------------------------------------------- /doc/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ## Aurelia-Cli 4 | 5 | Run `npm i aurelia-authentication --save` from your project root. 6 | 7 | Aurelia-authentication needs an installation of [aurelia-api](https://www.npmjs.com/package/aurelia-api). It also has submodules (currently only the authFilter) and makes use of `extend` and `jwt-decode`. So, add following to the `build.bundles.dependencies` section of `aurelia-project/aurelia.json`. 8 | 9 | ```js 10 | "dependencies": [ 11 | // ... 12 | "extend", 13 | { 14 | "name": "aurelia-authentication", 15 | "path": "../node_modules/aurelia-authentication/dist/amd", 16 | "main": "aurelia-authentication" 17 | }, 18 | { 19 | "name": "jwt-decode", 20 | "path": "../node_modules/jwt-decode/lib", 21 | "main": "index" 22 | } 23 | // ... 24 | ], 25 | ``` 26 | 27 | ## Jspm 28 | 29 | Run `jspm i aurelia-authentication` 30 | 31 | Add `aurelia-authentication` to the `bundles.dist.aurelia.includes` section of `build/bundles.js`. 32 | 33 | Aurelia-authentication needs an installation of [aurelia-api](https://www.npmjs.com/package/aurelia-api). It also has submodules. They are imported in it's main file, so no further action is required. 34 | 35 | If the installation results in having forks, try resolving them by running: 36 | 37 | ```sh 38 | jspm inspect --forks 39 | jspm resolve --only registry:package-name@version 40 | ``` 41 | 42 | E.g. 43 | 44 | ```sh 45 | jspm inspect --forks 46 | > Installed Forks 47 | > npm:aurelia-dependency-injection 1.0.0-beta.1.2.3 1.0.0-beta.2.1.0 48 | 49 | jspm resolve --only npm:aurelia-dependency-injection@1.0.0-beta.2.1.0 50 | ``` 51 | 52 | ## Webpack 53 | 54 | Run `npm i aurelia-authentication --save` from your project root. 55 | 56 | The `authFilter` needs to be added to the `webpack.config.js`. 57 | 58 | Run `npm i ModuleDependenciesPlugin --save-dev` from your root project and include it the `webpack.config.js`, eg: 59 | 60 | ```js 61 | const { AureliaPlugin, ModuleDependenciesPlugin } = require('aurelia-webpack-plugin');` 62 | ``` 63 | 64 | In the `plugins` section add the `authFilter`, eg: 65 | 66 | ```js 67 | plugins: [ 68 | new AureliaPlugin(), 69 | new ModuleDependenciesPlugin({ 70 | "aurelia-authentication": [ "./authFilterValueConverter" ], 71 | }), 72 | ``` 73 | 74 | Aurelia-authentication needs an installation of [aurelia-api](https://www.npmjs.com/package/aurelia-api). It also has submodules. They are listed as resources in the package.json. So, no further action is required. 75 | 76 | ## Typescript 77 | 78 | Npm-based installations pick up the typings automatically. For Jspm-based installations, add to your `typings.json`: 79 | 80 | ```js 81 | "aurelia-authentication": "github:spoonx/aurelia-authentication", 82 | ``` 83 | 84 | and run `typings i` 85 | 86 | or run 87 | 88 | ```sh 89 | typings i github:spoonx/aurelia-authentication 90 | ``` 91 | -------------------------------------------------------------------------------- /doc/license.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2016 SpoonX 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /doc/oidc.md: -------------------------------------------------------------------------------- 1 | # OpenID Connect (OIDC) Providers 2 | 3 | Much like use of the [Auth0 provider](auth0.md), when the aurelia-authentication 4 | plugin is configured to use an [OpenID Connect](http://openid.net/connect/) provider such as 5 | [IdentityServer](https://github.com/IdentityServer/IdentityServer4) 6 | or [OpenIddict](https://github.com/openiddict/openiddict-core), that provider should probably be 7 | the only authentication provider configured in your application. Authenticating using social 8 | providers such as Facebook, Google, etc and even local user and password authentication 9 | is handled completely by the OpenID Connect **(OIDC)** provider. As an OIDC provider is 10 | by definition a simple identity wrapper over the OAuth2 protocol, to integrate an 11 | OIDC provider with aurelia-authentication involves defining a generic OAuth2 provider with 12 | a few additional configuration properties. These properties are discussed below. 13 | 14 | ## Configuring an OIDC Provider 15 | 16 | The following configuration properties should be defined for a OIDC provider. For more information, 17 | refer to the response given to this [stackoverflow question](http://stackoverflow.com/questions/34809639/openiddict-how-do-you-obtain-the-access-token-for-a-user) 18 | 19 | * name : name the aurelia client should use to reference the provider configuration options 20 | * oauthType : must be set to '2.0' to connect to an OIDC provider 21 | * clientId: name of the public client application as defined with the OIDC provider 22 | * authorizationEndpoint: the URL for the OIDC provider authorization endpoint 23 | * redirectUri: the URL defined with the OIDC provider and clientId that will be redirected to upon successful login 24 | * logoutEndpoint: the URL for the OIDC provider logout endpoint 25 | * postLogoutRedirectUri: similar to the redirectUri on successful login, the URL for the OIDC provider and clientId that will be redirected to upon successful logout 26 | * responseType: for an OIDC provider using the implicit flow, should always be set to 'id_token token' 27 | * scope: in order for the client application to return the display name of the user when calling the profileUrl endpoint, must be set to ['openid email profile'] 28 | * requiredUrlParams: an array of parameters that are required when submitting a request to the authorizationEndpoint. For an OIDC provider, this value should be defined as ['scope', 'nonce', 'resource'] 29 | * state: function returning a random string that is used to maing state between the request and the callback 30 | * nonce: function returning a random string that is used to associate a client session with an ID token and to mitigate replay attacks 31 | * popupOptions: defines the dimensions of the popup window displayed when the authenticate and logout methods are called 32 | * resource: defines the audiences that are returned in the access_token. These values need to match the defined [ValidAudience property](http://andrewlock.net/a-look-behind-the-jwt-bearer-authentication-middleware-in-asp-net-core/) of the JWT Bearer Authentication Middleware for the protected resource server. 33 | 34 | ## Example OIDC Provider Configuration 35 | ```json 36 | export default {| 37 | endpoint: 'auth', 38 | configureEndpoints: ['auth', 'resources'], 39 | profileUrl: '/userinfo', 40 | loginRedirect: false, 41 | providers: { 42 | openiddict: { 43 | name: 'openiddict', 44 | oauthType: '2.0', 45 | clientId: 'aurelia-openiddict', 46 | redirectUri: 'http://localhost:49862/', 47 | authorizationEndpoint: 'http://localhost:54540/connect/authorize', 48 | logoutEndpoint: 'http://localhost:54540/connect/logout', 49 | postLogoutRedirectUri: 'http://localhost:49862/', 50 | responseType: 'token id_token', 51 | scope: ['openid email profile'], 52 | requiredUrlParams: ['scope', 'nonce', 'resource'], 53 | state: function () { 54 | var text = ""; 55 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 56 | for (var i = 0; i < 32; i++) { 57 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 58 | } 59 | return text; 60 | }, 61 | nonce: function () { 62 | var text = ""; 63 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 64 | for (var i = 0; i < 32; i++) { 65 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 66 | } 67 | return text; 68 | }, 69 | popupOptions: { width: 1028, height: 529 }, 70 | resource: 'aurelia-openiddict-resources aurelia-openiddict-server' 71 | } 72 | } 73 | }; 74 | ``` 75 | 76 | ## Integrating a Client with an OIDC Provider 77 | 78 | Once properly configured, integration of an Aurelia application with an OpenId Connect provider 79 | using aurelia-authentication couldn't be any simpler. All that is needed to login and logout to 80 | an OIDC provider is to call the AuthService authenticate() and logout() methods. For example, 81 | the following code completes integration of an OIDC provider configured with the above sample 82 | script with an Aurelia application. 83 | ``` 84 | authenticate() { 85 | return this.auth.authenticate('openiddict', '/#') 86 | .then((response) => { 87 | this.logger.info("login successful"); 88 | this.updateDisplayName(); 89 | }); 90 | } 91 | 92 | logout() { 93 | return this.auth.logout('/#', undefined, 'openiddict') 94 | .then(response => { 95 | this.displayName = ''; 96 | }); 97 | } 98 | 99 | ``` 100 | 101 | 102 | -------------------------------------------------------------------------------- /doc/pictures/TokenViaDevelopmentTools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpoonX/aurelia-authentication/a760b13aa336635148466b1fcaf46b21651ecbcd/doc/pictures/TokenViaDevelopmentTools.png -------------------------------------------------------------------------------- /doc/pictures/authHeader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpoonX/aurelia-authentication/a760b13aa336635148466b1fcaf46b21651ecbcd/doc/pictures/authHeader.png -------------------------------------------------------------------------------- /doc/refresh_token.md: -------------------------------------------------------------------------------- 1 | # Refresh tokens 2 | 3 | A refresh_token is just another jwt with a longer ttl than the access_token. The refresh tokens is then used to request a new access_token, either automatically whenever the access_token expires, or manually by calling `authService.updateToken()` . 4 | 5 | For example, you could set the ttl of the access_token to 1 day and the ttl of the refresh_token to 30 days. As a result, a user stays logged in for 30 days after his last activity. 6 | 7 | ## Configuration 8 | 9 | ### Client 10 | 11 | Following are the configuration options for the refresh token. Most importantly, you want to set `useRefreshToken: true` in your `authConfig.js`. With default settings, the value of the `refresh_token` property in the login response body will then be used as refresh token later. 12 | 13 | ```js 14 | // Refresh Token Options 15 | // ===================== 16 | 17 | // The API endpoint to which refreshToken requests are sent. null = loginUrl 18 | refreshTokenUrl : null; 19 | 20 | // Option to turn refresh tokens On/Off 21 | useRefreshToken: false, 22 | // The option to enable/disable the automatic refresh of Auth tokens using Refresh Tokens 23 | autoUpdateToken: true, 24 | // Oauth Client Id 25 | clientId: false, 26 | // The the property from which to get the refresh token after a successful token refresh 27 | refreshTokenProp: 'refresh_token', 28 | // The proprety name used to send the existing token when refreshing `{ "refreshTokenSubmitProp": '...' }` 29 | refreshTokenSubmitProp : 'refresh_token'; 30 | 31 | // If the property defined by `refreshTokenProp` is an object: 32 | // ----------------------------------------------------------- 33 | 34 | // This is the property from which to get the token `{ "refreshTokenProp": { "refreshTokenName" : '...' } }` 35 | refreshTokenName: 'token', 36 | // This allows the refresh token to be a further object deeper `{ "refreshTokenProp": { "refreshTokenRoot" : { "refreshTokenName" : '...' } } }` 37 | refreshTokenRoot: false, 38 | 39 | ``` 40 | 41 | ### Server 42 | 43 | Upon login your server needs to send a second jwt with a longer ttl which will be used as refresh_token. So, the body of your server response might look like this: 44 | 45 | ```js 46 | { 47 | userId: 6143772, 48 | access_token: 'this_is_the.access_token.jwt', 49 | refresh_token: 'this_is_the.refresh_token.jwt' 50 | } 51 | ``` 52 | 53 | On calling `authService.updateToken()` or after expiration of the access_token if `autoUpdateToken` is set true (default), a post request is send to your config's `loginUrl` with the (probably now expired) access_token in the header and following body: 54 | 55 | ```js 56 | { 57 | grant_type: 'refresh_token', 58 | refresh_token: 'this_is_the.refresh_token.jwt' 59 | // client_id: 'the_client_id' // if selected in your config 60 | } 61 | ``` 62 | 63 | If all goes well, the server sends back the same response as after regular login with all new tokens. 64 | 65 | #### Sample middleware for express 66 | 67 | Here's a sample `refreshToken` middleware for express. Following would need to be executed after the json body parser. 68 | 69 | ```js 70 | // refreshToken.js 71 | 72 | var jwt = require('jsonwebtoken'); 73 | 74 | module.exports = function(options) { 75 | return function refreshToken(req, res, next) { 76 | // we only do something if grant_type is 'refresh_token' 77 | if (req.body.grant_type !== 'refresh_token') return next(); 78 | 79 | // verify refresh_token 80 | jwt.verify(req.body.refresh_token, "client_secret", function(err) { 81 | if (err) return next(err) 82 | 83 | // we use the old access_token as well. should be in the header 84 | var access_token = req.header('authorization'); 85 | if (!access_token) return next(); 86 | 87 | // remove Bearer if needed 88 | access_token = access_token.replace('Bearer ',''); 89 | 90 | // decode access_token to get the user data 91 | // since it might be expired we use ignoreExpiration: true 92 | jwt.verify(access_token, "client_secret", { 93 | ignoreExpiration: true, 94 | }, function(err, token) { 95 | if (err) return next(err); 96 | 97 | // remove old claims or jsonwebtoken complains or will wrongly re-use some claims 98 | token.exp = undefined; 99 | token.iat = undefined; 100 | token.nbf = undefined; 101 | token.aud = undefined; 102 | token.sub = undefined; 103 | 104 | // send back new tokens 105 | res.json({ 106 | access_token: jwt.sign(token, 'client_secret', {expiresIn: '1d'}), 107 | refresh_token: jwt.sign(token, 'client_secret', {expiresIn: '10d'}) 108 | }); 109 | }); 110 | }); 111 | }; 112 | } 113 | ``` 114 | -------------------------------------------------------------------------------- /doc/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ## Add a configuration file 4 | 5 | Set your custom configuration. You can find all options and the default values in the [baseConfig](baseConfig.md). 6 | 7 | ```js 8 | /* authConfig.js */ 9 | export default { 10 | endpoint: 'auth', // use 'auth' endpoint for the auth server 11 | configureEndpoints: ['auth'], // add Authorization header to 'auth' endpoint 12 | storageChangedReload: true, // ensure secondary tab reloading after auth status changes 13 | facebook: { 14 | clientId: 'your client id' // set your third-party providers client ids 15 | } 16 | ``` 17 | 18 | ## Configure the plugin 19 | 20 | Register the plugin and apply your `authConfig`. 21 | 22 | ```js 23 | /* main.js */ 24 | import authConfig from './authConfig'; 25 | 26 | aurelia.use 27 | /* Your other plugins and init code */ 28 | .plugin('aurelia-api', config => { 29 | // Register an authentication hosts 30 | config.registerEndpoint('auth'); 31 | }) 32 | /* configure aurelia-authentication */ 33 | .plugin('aurelia-authentication', config => { 34 | config.configure(authConfig); 35 | }); 36 | ``` 37 | 38 | ## Use AuthService in a view-model 39 | 40 | ```js 41 | import {AuthService} from 'aurelia-authentication'; 42 | import {inject, computedFrom} from 'aurelia-framework'; 43 | 44 | @inject(AuthService) 45 | export class Login { 46 | constructor(authService) { 47 | this.authService = authService; 48 | this.providers = []; 49 | }; 50 | 51 | // make a getter to get the authentication status. 52 | // use computedFrom to avoid dirty checking 53 | @computedFrom('authService.authenticated') 54 | get authenticated() { 55 | return this.authService.authenticated; 56 | } 57 | 58 | // use authService.login(credentialsObject) to login to your auth server 59 | login(username, password) { 60 | return this.authService.login({username, password}); 61 | }; 62 | 63 | // use authService.logout to delete stored tokens 64 | // if you are using JWTs, authService.logout() will be called automatically, 65 | // when the token expires. The expiredRedirect setting in your authConfig 66 | // will determine the redirection option 67 | logout() { 68 | return this.authService.logout(); 69 | } 70 | 71 | // use authenticate(providerName) to get third-party authentication 72 | authenticate(name) { 73 | return this.authService.authenticate(name) 74 | .then(response => { 75 | this.provider[name] = true; 76 | }); 77 | } 78 | } 79 | ``` 80 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | // all default gulp tasks are located in the ./node_modules/spoonx-tools/build-plugin/tasks directory 2 | // gulp default configuration is in files in ./node_modules/spoonx-tools/build-plugin directory 3 | require('require-dir')('node_modules/spoonx-tools/build-plugin/tasks'); 4 | 5 | // 'gulp help' lists the available default tasks 6 | // you can add additional tasks here 7 | // the testing express server can be imported and routes added 8 | var app = require('./node_modules/spoonx-tools/build-plugin/tasks/server').app; 9 | 10 | // unauthorized test path 11 | app.all('/unauthorized', function(req, res) { 12 | res.sendStatus(401); 13 | }); 14 | 15 | // default: all routes, all methods 16 | app.all('*', function(req, res) { 17 | res.send({ 18 | path : req.path, 19 | query : req.query, 20 | body : req.body, 21 | method : req.method, 22 | contentType : req.header('content-type'), 23 | Authorization: req.header('Authorization'), 24 | access_token : req.body.access_token, 25 | refresh_token: req.body.refresh_token 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aurelia-authentication", 3 | "version": "3.8.3", 4 | "description": "Plugin for social media authentication and local authentication together with other authentication utilities.", 5 | "keywords": [ 6 | "aurelia", 7 | "oauth", 8 | "authentication" 9 | ], 10 | "homepage": "https://github.com/spoonx/aurelia-authentication", 11 | "bugs": { 12 | "url": "https://github.com/spoonx/aurelia-authentication/issues" 13 | }, 14 | "license": "MIT", 15 | "author": "SpoonX (http://spoonx.nl/)", 16 | "contributors": [ 17 | "RWOverdijk ", 18 | "Dirk Eisinger ", 19 | "Paolo Furini (https://github.com/nexbit)" 20 | ], 21 | "main": "dist/commonjs/aurelia-authentication.js", 22 | "typings": "dist/aurelia-authentication.d.ts", 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/spoonx/aurelia-authentication" 26 | }, 27 | "scripts": { 28 | "test": "gulp test", 29 | "build": "gulp build", 30 | "preversion": "npm test", 31 | "version": "npm run build && gulp prepare-package && git add -A dist doc/CHANGELOG.md bower.json", 32 | "postpublish": "git push upstream master && git push upstream --tags" 33 | }, 34 | "jspm": { 35 | "registry": "npm", 36 | "jspmPackage": true, 37 | "main": "aurelia-authentication", 38 | "format": "amd", 39 | "directories": { 40 | "dist": "dist/amd" 41 | }, 42 | "dependencies": { 43 | "aurelia-api": "^3.0.0", 44 | "aurelia-dependency-injection": "^1.0.0", 45 | "aurelia-event-aggregator": "^1.0.0", 46 | "aurelia-fetch-client": "^1.0.0", 47 | "aurelia-logging": "^1.0.0", 48 | "aurelia-metadata": "^1.0.0", 49 | "aurelia-pal": "^1.8.0", 50 | "aurelia-path": "^1.0.0", 51 | "aurelia-router": "^1.0.0", 52 | "aurelia-templating-resources": "^1.0.0", 53 | "extend": "^3.0.0", 54 | "jwt-decode": "^2.0.0" 55 | }, 56 | "peerDependencies": { 57 | "aurelia-api": "^3.0.0", 58 | "aurelia-dependency-injection": "^1.0.0", 59 | "aurelia-event-aggregator": "^1.0.0", 60 | "aurelia-fetch-client": "^1.0.0", 61 | "aurelia-logging": "^1.0.0", 62 | "aurelia-metadata": "^1.0.0", 63 | "aurelia-pal": "^1.8.0", 64 | "aurelia-path": "^1.0.0", 65 | "aurelia-router": "^1.0.0", 66 | "aurelia-templating-resources": "^1.0.0", 67 | "extend": "^3.0.0", 68 | "jwt-decode": "^2.0.0" 69 | }, 70 | "devDependencies": { 71 | "aurelia-binding": "^1.0.0", 72 | "aurelia-bootstrapper": "^1.0.0", 73 | "aurelia-pal-browser": "^1.0.0", 74 | "aurelia-polyfills": "^1.0.0", 75 | "fetch": "github:github/fetch@^1.0.0" 76 | } 77 | }, 78 | "dependencies": { 79 | "aurelia-api": "^3.0.0", 80 | "aurelia-dependency-injection": "^1.0.0", 81 | "aurelia-event-aggregator": "^1.0.0", 82 | "aurelia-fetch-client": "^1.0.0", 83 | "aurelia-logging": "^1.0.0", 84 | "aurelia-metadata": "^1.0.0", 85 | "aurelia-pal": "^1.8.0", 86 | "aurelia-path": "^1.0.0", 87 | "aurelia-router": "^1.0.0", 88 | "aurelia-templating-resources": "^1.0.0", 89 | "extend": "^3.0.0", 90 | "jwt-decode": "^2.0.0" 91 | }, 92 | "devDependencies": { 93 | "spoonx-tools": "^1.0.0-0" 94 | }, 95 | "aurelia": { 96 | "build": { 97 | "resources": [ 98 | "aurelia-authentication/authFilterValueConverter", 99 | "aurelia-authentication/authenticatedFilterValueConverter", 100 | "aurelia-authentication/authenticatedValueConverter" 101 | ] 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /spoonx.js: -------------------------------------------------------------------------------- 1 | /************************************************/ 2 | /* spoonx-tools configuration */ 3 | /* @see https://github.com/SpoonX/spoonx-tools */ 4 | /************************************************/ 5 | 6 | var appRoot = 'src/'; 7 | 8 | module.exports = { 9 | path: { 10 | root: appRoot, 11 | 12 | /* options and their defaults */ 13 | 14 | /* js files to ignore 15 | * 16 | * ignore: [], 17 | */ 18 | 19 | /* future use: use TypeScript or Babel for transpiling 20 | * 21 | * useTypeScriptForDTS: false, 22 | */ 23 | 24 | /* Imports to append to the import block of the main file. 25 | * Add here eg. non-concated local imports in the main file as they will 26 | * get removed during the build process (ValueConverters, CustomElements). 27 | * 28 | * importsToAdd: ["import {AssociationSelect} from './association-select';"], 29 | */ 30 | importsToAdd: [ 31 | 'import {AuthFilterValueConverter} from "./authFilterValueConverter"', 32 | 'import {AuthenticatedValueConverter} from "./authenticatedValueConverter"', 33 | 'import {AuthenticatedFilterValueConverter} from "./authenticatedFilterValueConverter"' 34 | ], 35 | 36 | /* js to be transpiled, but not be concated 37 | * (ValueConverters, CustomElements) 38 | * 39 | * jsResources: [appRoot + 'association-select.js'], 40 | */ 41 | jsResources: [appRoot + '*ValueConverter.js'], 42 | 43 | /* other resources that need to get copied keeping their path 44 | * resources: appRoot + '{** / *.css,** / *.html}', 45 | */ 46 | resources: appRoot + '{**/*.css,**/*.html}', 47 | 48 | /* imports that are only used internally, eg 'extend'. no need to d.ts export them 49 | * 50 | * importsToIgnoreForDts: ['extend'], 51 | */ 52 | importsToIgnoreForDts: ['extend', 'jwt-decode'], 53 | 54 | /* sort when concating 55 | * sort: true, 56 | */ 57 | sort: true, 58 | 59 | /* concat js files 60 | * concat: true, 61 | */ 62 | concat: true, 63 | 64 | /* default options overwrites for karma 65 | * karma: {browsers: ['Chrome']} 66 | */ 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /src/aurelia-authentication.js: -------------------------------------------------------------------------------- 1 | import {PLATFORM} from 'aurelia-pal'; 2 | import {HttpClient} from 'aurelia-fetch-client'; 3 | import {Config, Rest} from 'aurelia-api'; 4 | import {BaseConfig} from './baseConfig'; 5 | import {FetchConfig} from './fetchClientConfig'; 6 | import {logger} from './logger'; 7 | import {Container} from 'aurelia-dependency-injection'; 8 | 9 | // added for bundling 10 | import {AuthFilterValueConverter} from './authFilterValueConverter'; // eslint-disable-line no-unused-vars 11 | import {AuthenticatedFilterValueConverter} from './authenticatedFilterValueConverter'; // eslint-disable-line no-unused-vars 12 | import {AuthenticatedValueConverter} from './authenticatedValueConverter'; // eslint-disable-line no-unused-vars 13 | 14 | /** 15 | * Configure the plugin. 16 | * 17 | * @export 18 | * @param {FrameworkConfiguration} frameworkConfig The FrameworkConfiguration instance 19 | * @param {{}|Function} config The Config instance 20 | * 21 | */ 22 | export function configure(frameworkConfig: { container: Container, globalResources: (...resources: string[]) => any }, config: {}|Function) { 23 | // ie9 polyfill 24 | if (!PLATFORM.location.origin) { 25 | PLATFORM.location.origin = PLATFORM.location.protocol + '//' + PLATFORM.location.hostname + (PLATFORM.location.port ? ':' + PLATFORM.location.port : ''); 26 | } 27 | 28 | const baseConfig = frameworkConfig.container.get(BaseConfig); 29 | 30 | if (typeof config === 'function') { 31 | config(baseConfig); 32 | } else if (typeof config === 'object') { 33 | baseConfig.configure(config); 34 | } 35 | 36 | // after baseConfig was configured 37 | for (let converter of baseConfig.globalValueConverters) { 38 | frameworkConfig.globalResources(PLATFORM.moduleName(`./${converter}`)); 39 | logger.info(`Add globalResources value-converter: ${converter}`); 40 | } 41 | const fetchConfig = frameworkConfig.container.get(FetchConfig); 42 | const clientConfig = frameworkConfig.container.get(Config); 43 | 44 | // Array? Configure the provided endpoints. 45 | if (Array.isArray(baseConfig.configureEndpoints)) { 46 | baseConfig.configureEndpoints.forEach(endpointToPatch => { 47 | fetchConfig.configure(endpointToPatch); 48 | }); 49 | } 50 | 51 | let client; 52 | 53 | // Let's see if there's a configured named or default endpoint or a HttpClient. 54 | if (baseConfig.endpoint !== null) { 55 | if (typeof baseConfig.endpoint === 'string') { 56 | const endpoint = clientConfig.getEndpoint(baseConfig.endpoint); 57 | 58 | if (!endpoint) { 59 | throw new Error(`There is no '${baseConfig.endpoint || 'default'}' endpoint registered.`); 60 | } 61 | client = endpoint; 62 | } else if (baseConfig.endpoint instanceof HttpClient) { 63 | client = new Rest(baseConfig.endpoint); 64 | } 65 | } 66 | 67 | // No? Fine. Default to HttpClient. BC all the way. 68 | if (!(client instanceof Rest)) { 69 | client = new Rest(frameworkConfig.container.get(HttpClient)); 70 | } 71 | 72 | // Set the client on the config, for use throughout the plugin. 73 | baseConfig.client = client; 74 | } 75 | -------------------------------------------------------------------------------- /src/authFilterValueConverter.js: -------------------------------------------------------------------------------- 1 | import {RouteConfig} from 'aurelia-router'; 2 | 3 | export class AuthFilterValueConverter { 4 | /** 5 | * route toView predictator on route.config.auth === isAuthenticated 6 | * @param {RouteConfig} routes the routes array to convert 7 | * @param {boolean} isAuthenticated authentication status 8 | * @return {boolean} show/hide element 9 | */ 10 | toView(routes: RouteConfig, isAuthenticated: boolean): boolean { 11 | return routes.filter(route => typeof route.config.auth !== 'boolean' || route.config.auth === isAuthenticated); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/authLock.js: -------------------------------------------------------------------------------- 1 | import {PLATFORM} from 'aurelia-pal'; 2 | import {inject} from 'aurelia-dependency-injection'; 3 | import extend from 'extend'; 4 | import {Storage} from './storage'; 5 | import {BaseConfig} from './baseConfig'; 6 | 7 | @inject(Storage, BaseConfig) 8 | export class AuthLock { 9 | constructor(storage: Storage, config: BaseConfig) { 10 | this.storage = storage; 11 | this.config = config; 12 | this.defaults = { 13 | name : null, 14 | state : null, 15 | scope : null, 16 | scopeDelimiter: ' ', 17 | redirectUri : null, 18 | clientId : null, 19 | clientDomain : null, 20 | display : 'popup', 21 | lockOptions : {}, 22 | popupOptions : null, 23 | responseType : 'token' 24 | }; 25 | } 26 | 27 | open(options: {}, userData?: {}): Promise { 28 | // check pre-conditions 29 | if (typeof PLATFORM.global.Auth0Lock !== 'function') { 30 | throw new Error('Auth0Lock was not found in global scope. Please load it before using this provider.'); 31 | } 32 | const provider = extend(true, {}, this.defaults, options); 33 | const stateName = provider.name + '_state'; 34 | 35 | if (typeof provider.state === 'function') { 36 | this.storage.set(stateName, provider.state()); 37 | } else if (typeof provider.state === 'string') { 38 | this.storage.set(stateName, provider.state); 39 | } 40 | 41 | // transform provider options into auth0-lock options 42 | let opts = { 43 | auth: { 44 | params: {} 45 | } 46 | }; 47 | 48 | if (Array.isArray(provider.scope) && provider.scope.length) { 49 | opts.auth.params.scope = provider.scope.join(provider.scopeDelimiter); 50 | } 51 | if (provider.state) { 52 | opts.auth.params.state = this.storage.get(provider.name + '_state'); 53 | } 54 | if (provider.display === 'popup') { 55 | opts.auth.redirect = false; 56 | } else if (typeof provider.redirectUri === 'string') { 57 | opts.auth.redirect = true; 58 | opts.auth.redirectUrl = provider.redirectUri; 59 | } 60 | if (typeof provider.popupOptions === 'object') { 61 | opts.popupOptions = provider.popupOptions; 62 | } 63 | if (typeof provider.responseType === 'string') { 64 | opts.auth.responseType = provider.responseType; 65 | } 66 | let lockOptions = extend(true, {}, provider.lockOptions, opts); 67 | 68 | this.lock = this.lock || new PLATFORM.global.Auth0Lock(provider.clientId, provider.clientDomain, lockOptions); 69 | 70 | const openPopup = new Promise((resolve, reject) => { 71 | this.lock.on('authenticated', authResponse => { 72 | if (!lockOptions.auth.redirect) { 73 | // hides the lock popup, as it doesn't do so automatically 74 | this.lock.hide(); 75 | } 76 | resolve({ 77 | access_token: authResponse.accessToken, 78 | id_token : authResponse.idToken 79 | }); 80 | }); 81 | this.lock.on('unrecoverable_error', err => { 82 | if (!lockOptions.auth.redirect) { 83 | // hides the lock popup, as it doesn't do so automatically 84 | this.lock.hide(); 85 | } 86 | reject(err); 87 | }); 88 | this.lock.show(); 89 | }); 90 | 91 | return openPopup 92 | .then(lockResponse => { 93 | if (provider.responseType === 'token' 94 | || provider.responseType === 'id_token%20token' 95 | || provider.responseType === 'token%20id_token' 96 | || provider.responseType === 'token id_token' 97 | ) { 98 | return lockResponse; 99 | } 100 | //NOTE: 'code' responseType is not supported, this is an OpenID response (JWT token) 101 | // and code flow is not secure client-side 102 | throw new Error('Only `token` responseType is supported'); 103 | }); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/authenticateStep.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Redirect} from 'aurelia-router'; 3 | import {AuthService} from './authService'; 4 | 5 | @inject(AuthService) 6 | export class AuthenticateStep { 7 | constructor(authService: AuthService) { 8 | this.authService = authService; 9 | } 10 | 11 | run(routingContext, next) { 12 | const isLoggedIn = this.authService.authenticated; 13 | const loginRoute = this.authService.config.loginRoute; 14 | 15 | if (routingContext.getAllInstructions().some(route => route.config.auth === true)) { 16 | if (!isLoggedIn) { 17 | return next.cancel(new Redirect(loginRoute)); 18 | } 19 | } else if (isLoggedIn && routingContext.getAllInstructions().some(route => route.fragment === loginRoute)) { 20 | return next.cancel(new Redirect(this.authService.config.loginRedirect)); 21 | } 22 | 23 | return next(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/authenticatedFilterValueConverter.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {AuthService} from './aurelia-authentication'; 3 | import {RouteConfig} from 'aurelia-router'; 4 | 5 | @inject(AuthService) 6 | export class AuthenticatedFilterValueConverter { 7 | constructor(authService: AuthService) { 8 | this.authService = authService; 9 | } 10 | 11 | /** 12 | * route toView predictator on route.config.auth === (parameter || authService.isAuthenticated()) 13 | * @param {RouteConfig} routes the routes array to convert 14 | * @param {[boolean]} [isAuthenticated] optional isAuthenticated value. default: this.authService.authenticated 15 | * @return {boolean} show/hide element 16 | */ 17 | toView(routes: RouteConfig, isAuthenticated: boolean = this.authService.authenticated): boolean { 18 | return routes.filter(route => typeof route.config.auth !== 'boolean' || route.config.auth === isAuthenticated); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/authenticatedValueConverter.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {AuthService} from './aurelia-authentication'; 3 | 4 | @inject(AuthService) 5 | export class AuthenticatedValueConverter { 6 | constructor(authService) { 7 | this.authService = authService; 8 | } 9 | 10 | /** 11 | * element toView predictator on authService.isAuthenticated() 12 | * @return {boolean} show/hide element 13 | */ 14 | toView() { 15 | return this.authService.authenticated; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/authentication.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-lines */ 2 | import {PLATFORM} from 'aurelia-pal'; 3 | import {buildQueryString} from 'aurelia-path'; 4 | import {inject} from 'aurelia-dependency-injection'; 5 | import {deprecated} from 'aurelia-metadata'; 6 | import jwtDecode from 'jwt-decode'; 7 | import {logger} from './logger'; 8 | import {BaseConfig} from './baseConfig'; 9 | import {Storage} from './storage'; 10 | import {OAuth1} from './oAuth1'; 11 | import {OAuth2} from './oAuth2'; 12 | import {AuthLock} from './authLock'; 13 | 14 | @inject(Storage, BaseConfig, OAuth1, OAuth2, AuthLock) 15 | export class Authentication { 16 | constructor(storage: Storage, config: BaseConfig, oAuth1: OAuth1, oAuth2: OAuth2, auth0Lock: AuthLock) { 17 | this.storage = storage; 18 | this.config = config; 19 | this.oAuth1 = oAuth1; 20 | this.oAuth2 = oAuth2; 21 | this.auth0Lock = auth0Lock; 22 | this.updateTokenCallstack = []; 23 | this.accessToken = null; 24 | this.refreshToken = null; 25 | this.idToken = null; 26 | this.payload = null; 27 | this.exp = null; 28 | this.responseAnalyzed = false; 29 | } 30 | 31 | /* deprecated methods */ 32 | @deprecated({message: 'Use baseConfig.loginRoute instead.'}) 33 | getLoginRoute() { 34 | return this.config.loginRoute; 35 | } 36 | 37 | @deprecated({message: 'Use baseConfig.loginRedirect instead.'}) 38 | getLoginRedirect() { 39 | return this.config.loginRedirect; 40 | } 41 | 42 | @deprecated({message: 'Use baseConfig.joinBase(baseConfig.loginUrl) instead.'}) 43 | getLoginUrl() { 44 | return this.Config.joinBase(this.config.loginUrl); 45 | } 46 | 47 | @deprecated({message: 'Use baseConfig.joinBase(baseConfig.signupUrl) instead.'}) 48 | getSignupUrl() { 49 | return this.Config.joinBase(this.config.signupUrl); 50 | } 51 | 52 | @deprecated({message: 'Use baseConfig.joinBase(baseConfig.profileUrl) instead.'}) 53 | getProfileUrl() { 54 | return this.Config.joinBase(this.config.profileUrl); 55 | } 56 | 57 | @deprecated({message: 'Use .getAccessToken() instead.'}) 58 | getToken() { 59 | return this.getAccessToken(); 60 | } 61 | 62 | get responseObject(): {} { 63 | logger.warn('Getter Authentication.responseObject is deprecated. Use Authentication.getResponseObject() instead.'); 64 | 65 | return this.getResponseObject(); 66 | } 67 | 68 | set responseObject(response: {}) { 69 | logger.warn('Setter Authentication.responseObject is deprecated. Use AuthServive.setResponseObject(response) instead.'); 70 | this.setResponseObject(response); 71 | } 72 | 73 | get hasDataStored(): boolean { 74 | logger.warn('Authentication.hasDataStored is deprecated. Use Authentication.responseAnalyzed instead.'); 75 | 76 | return this.responseAnalyzed; 77 | } 78 | 79 | /* get/set responseObject */ 80 | getResponseObject(): {} { 81 | return JSON.parse(this.storage.get(this.config.storageKey)); 82 | } 83 | 84 | setResponseObject(response: {}) { 85 | if (response) { 86 | if (this.config.keepOldResponseProperties) { 87 | let oldResponse = this.getResponseObject(); 88 | 89 | response = Object.assign({}, oldResponse, response); 90 | } 91 | this.getDataFromResponse(response); 92 | this.storage.set(this.config.storageKey, JSON.stringify(response)); 93 | 94 | return; 95 | } 96 | this.accessToken = null; 97 | this.refreshToken = null; 98 | this.idToken = null; 99 | this.payload = null; 100 | this.exp = null; 101 | this.responseAnalyzed = false; 102 | 103 | this.storage.remove(this.config.storageKey); 104 | } 105 | 106 | /* get data, update if needed first */ 107 | getAccessToken(): string { 108 | if (!this.responseAnalyzed) this.getDataFromResponse(this.getResponseObject()); 109 | 110 | return this.accessToken; 111 | } 112 | 113 | getRefreshToken(): string { 114 | if (!this.responseAnalyzed) this.getDataFromResponse(this.getResponseObject()); 115 | 116 | return this.refreshToken; 117 | } 118 | 119 | getIdToken(): string { 120 | if (!this.responseAnalyzed) this.getDataFromResponse(this.getResponseObject()); 121 | 122 | return this.idToken; 123 | } 124 | 125 | getPayload(): {} { 126 | if (!this.responseAnalyzed) this.getDataFromResponse(this.getResponseObject()); 127 | 128 | return this.payload; 129 | } 130 | 131 | getIdPayload(): {} { 132 | if (!this.responseAnalyzed) this.getDataFromResponse(this.getResponseObject()); 133 | 134 | return this.idPayload; 135 | } 136 | 137 | getExp(): number { 138 | if (!this.responseAnalyzed) this.getDataFromResponse(this.getResponseObject()); 139 | 140 | return this.exp; 141 | } 142 | 143 | /* get status from data */ 144 | getTtl(): number { 145 | const exp = this.getExp(); 146 | 147 | return Number.isNaN(exp) ? NaN : exp - Math.round(new Date().getTime() / 1000); 148 | } 149 | 150 | isTokenExpired(): boolean { 151 | const timeLeft = this.getTtl(); 152 | 153 | return Number.isNaN(timeLeft) ? undefined : timeLeft < 0; 154 | } 155 | 156 | isAuthenticated(): boolean { 157 | return !!this.getAccessToken() && !this.isTokenExpired(); 158 | } 159 | 160 | /* get and set from response */ 161 | getDataFromResponse(response: {}): {} { 162 | const config = this.config; 163 | 164 | // get access token either with from supplied parameters or with supplied function 165 | this.accessToken = typeof this.config.getAccessTokenFromResponse === 'function' 166 | ? this.config.getAccessTokenFromResponse(response) 167 | : this.getTokenFromResponse(response, config.accessTokenProp, config.accessTokenName, config.accessTokenRoot); 168 | 169 | this.refreshToken = null; 170 | if (config.useRefreshToken) { 171 | try { 172 | // get refresh token either with from supplied parameters or with supplied function 173 | this.refreshToken = typeof this.config.getRefreshTokenFromResponse === 'function' 174 | ? this.config.getRefreshTokenFromResponse(response) 175 | : this.getTokenFromResponse(response, config.refreshTokenProp, config.refreshTokenName, config.refreshTokenRoot); 176 | } catch (e) { 177 | this.refreshToken = null; 178 | 179 | logger.warn('useRefreshToken is set, but could not extract a refresh token'); 180 | } 181 | } 182 | 183 | this.idToken = null; 184 | try { 185 | this.idToken = this.getTokenFromResponse(response, config.idTokenProp, config.idTokenName, config.idTokenRoot); 186 | } catch (e) { 187 | this.idToken = null; 188 | } 189 | 190 | this.payload = getPayload(this.accessToken); 191 | this.idPayload = getPayload(this.idToken); 192 | 193 | // get exp either with from jwt or with supplied function 194 | this.exp = parseInt((typeof this.config.getExpirationDateFromResponse === 'function' 195 | ? this.config.getExpirationDateFromResponse(response) 196 | : this.payload && this.payload.exp), 10) || NaN; 197 | 198 | this.responseAnalyzed = true; 199 | 200 | return { 201 | accessToken : this.accessToken, 202 | refreshToken: this.refreshToken, 203 | idToken : this.idToken, 204 | payload : this.payload, 205 | exp : this.exp 206 | }; 207 | } 208 | 209 | /** 210 | * Extract the token from the server response 211 | * 212 | * @param {{}} response The response 213 | * @param {string} tokenProp tokenProp 214 | * @param {string} tokenName tokenName 215 | * @param {string} tokenRoot tokenRoot 216 | * @returns {string} The token 217 | * 218 | * @memberOf Authentication 219 | */ 220 | getTokenFromResponse(response: {}, tokenProp: string, tokenName: string, tokenRoot: string): string { 221 | if (!response) return undefined; 222 | 223 | const responseTokenProp = tokenProp.split('.').reduce((o, x) => o[x], response); 224 | 225 | if (typeof responseTokenProp === 'string') { 226 | return responseTokenProp; 227 | } 228 | 229 | if (typeof responseTokenProp === 'object') { 230 | const tokenRootData = tokenRoot && tokenRoot.split('.').reduce((o, x) => o[x], responseTokenProp); 231 | const token = tokenRootData ? tokenRootData[tokenName] : responseTokenProp[tokenName]; 232 | 233 | if (!token) { 234 | // if the token is not found in the response, 235 | // throw an error along with the response object as a key 236 | let error = new Error('Token not found in response'); 237 | 238 | error.responseObject = response; 239 | throw error; 240 | } 241 | 242 | return token; 243 | } 244 | 245 | const token = response[tokenName] === undefined ? null : response[tokenName]; 246 | 247 | if (!token) { 248 | // if the token is not found in the response, 249 | // throw an error along with the response object as a key 250 | let error = new Error('Token not found in response'); 251 | 252 | error.responseObject = response; 253 | throw error; 254 | } 255 | 256 | return token; 257 | } 258 | 259 | toUpdateTokenCallstack(): Promise { 260 | return new Promise(resolve => this.updateTokenCallstack.push(resolve)); 261 | } 262 | 263 | resolveUpdateTokenCallstack(response: {}) { 264 | this.updateTokenCallstack.map(resolve => resolve(response)); 265 | this.updateTokenCallstack = []; 266 | } 267 | 268 | /** 269 | * Authenticate with third-party 270 | * 271 | * @param {string} name Name of the provider 272 | * @param {[{}]} [userData] Additional data send to the authentication server 273 | * 274 | * @return {Promise} The authentication server response 275 | */ 276 | authenticate(name: string, userData: {} = {}): Promise { 277 | let oauthType = this.config.providers[name].type; 278 | 279 | if (oauthType) { 280 | logger.warn('DEPRECATED: Setting provider.type is deprecated and replaced by provider.oauthType'); 281 | } else { 282 | oauthType = this.config.providers[name].oauthType; 283 | } 284 | 285 | let providerLogin; 286 | 287 | if (oauthType === 'auth0-lock') { 288 | providerLogin = this.auth0Lock; 289 | } else { 290 | providerLogin = (oauthType === '1.0' ? this.oAuth1 : this.oAuth2); 291 | } 292 | 293 | return providerLogin.open(this.config.providers[name], userData); 294 | } 295 | 296 | /** 297 | * Send logout request to oauth provider 298 | * 299 | * @param {string} name The provider name 300 | * @returns {Promise} The server response 301 | * 302 | * @memberOf Authentication 303 | */ 304 | logout(name: string): Promise { 305 | let rtnValue = Promise.resolve('Not Applicable'); 306 | 307 | if (this.config.providers[name].oauthType !== '2.0' || !this.config.providers[name].logoutEndpoint) { 308 | return rtnValue; 309 | } 310 | 311 | return this.oAuth2.close(this.config.providers[name]); 312 | } 313 | 314 | /** 315 | * Redirect (page reload if applicable for the browsers save password option) 316 | * 317 | * @param {string} redirectUrl The redirect url. To not redirect use an empty string. 318 | * @param {[string]} defaultRedirectUrl The defaultRedirectUrl. Used when redirectUrl is undefined 319 | * @param {[string]} query The optional query string to add the the url 320 | * @returns {undefined} undefined 321 | * 322 | * @memberOf Authentication 323 | */ 324 | redirect(redirectUrl: string, defaultRedirectUrl?: string, query?: string) { 325 | // stupid rule to keep it BC 326 | if (redirectUrl === true) { 327 | logger.warn('DEPRECATED: Setting redirectUrl === true to actually *not redirect* is deprecated. Set redirectUrl === \'\' instead.'); 328 | 329 | return; 330 | } 331 | 332 | // stupid rule to keep it BC 333 | if (redirectUrl === false) { 334 | logger.warn('BREAKING CHANGE: Setting redirectUrl === false to actually *do redirect* is deprecated. Set redirectUrl to undefined or null to use the defaultRedirectUrl if so desired.'); 335 | } 336 | 337 | // BC hack. explicit 0 means don't redirect. deprecated in favor of an empty string 338 | if (redirectUrl === 0) { 339 | logger.warn('BREAKING CHANGE: Setting redirectUrl === 0 is deprecated. Set redirectUrl to \'\' instead.'); 340 | 341 | return; 342 | } 343 | 344 | // Empty string means don't redirect overwrite. 345 | if (redirectUrl === '') { 346 | return; 347 | } 348 | 349 | if (typeof redirectUrl === 'string') { 350 | PLATFORM.location.href = encodeURI(redirectUrl + (query ? `?${buildQueryString(query)}` : '')); 351 | } else if (defaultRedirectUrl) { 352 | PLATFORM.location.href = defaultRedirectUrl + (query ? `?${buildQueryString(query)}` : ''); 353 | } 354 | } 355 | } 356 | 357 | /* get payload from a token */ 358 | function getPayload(token: string): {} { 359 | let payload = null; 360 | 361 | try { 362 | payload =token ? jwtDecode(token) : null; 363 | } catch (_) {} // eslint-disable-line no-empty 364 | 365 | return payload; 366 | } 367 | -------------------------------------------------------------------------------- /src/authorizeStep.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Redirect} from 'aurelia-router'; 3 | import {logger} from './logger'; 4 | import {AuthService} from './authService'; 5 | 6 | @inject(AuthService) 7 | export class AuthorizeStep { 8 | constructor(authService: AuthService) { 9 | logger.warn('AuthorizeStep is deprecated. Use AuthenticateStep instead.'); 10 | 11 | this.authService = authService; 12 | } 13 | 14 | run(routingContext, next) { 15 | const isLoggedIn = this.authService.isAuthenticated(); 16 | const loginRoute = this.authService.config.loginRoute; 17 | 18 | if (routingContext.getAllInstructions().some(route => route.config.auth)) { 19 | if (!isLoggedIn) { 20 | return next.cancel(new Redirect(loginRoute)); 21 | } 22 | } else if (isLoggedIn && routingContext.getAllInstructions().some(route => route.fragment === loginRoute)) { 23 | return next.cancel(new Redirect(this.authService.config.loginRedirect)); 24 | } 25 | 26 | return next(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/fetchClientConfig.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {HttpClient} from 'aurelia-fetch-client'; 3 | import {Config, Rest} from 'aurelia-api'; 4 | import {AuthService} from './authService'; 5 | import {BaseConfig} from './baseConfig'; 6 | 7 | @inject(HttpClient, Config, AuthService, BaseConfig) 8 | export class FetchConfig { 9 | /** 10 | * Construct the FetchConfig 11 | * 12 | * @param {HttpClient} httpClient httpClient 13 | * @param {Config} clientConfig clientConfig 14 | * @param {Authentication} authService authService 15 | * @param {BaseConfig} config baseConfig 16 | */ 17 | constructor(httpClient: HttpClient, clientConfig: Config, authService: Authentication, config: BaseConfig) { 18 | this.httpClient = httpClient; 19 | this.clientConfig = clientConfig; 20 | this.authService = authService; 21 | this.config = config; 22 | } 23 | 24 | /** 25 | * Interceptor for HttpClient 26 | * 27 | * @return {{request: Function, response: Function}} The interceptor 28 | */ 29 | get interceptor(): {request: Function, response: Function} { 30 | return { 31 | request: request => { 32 | if (!this.config.httpInterceptor || !this.authService.isAuthenticated()) { 33 | return request; 34 | } 35 | let token = this.authService.getAccessToken(); 36 | 37 | if (this.config.authTokenType) { 38 | token = `${this.config.authTokenType} ${token}`; 39 | } 40 | 41 | request.headers.set(this.config.authHeader, token); 42 | 43 | return request; 44 | }, 45 | response: (response, request) => { 46 | return new Promise((resolve, reject) => { 47 | // resolve success 48 | if (response.ok) { 49 | return resolve(response); 50 | } 51 | // resolve all non-authorization errors 52 | if (response.status !== 401) { 53 | return resolve(response); 54 | } 55 | // when we get a 401 and are not logged in, there's not much to do except reject the request 56 | if (!this.authService.authenticated) { 57 | return reject(response); 58 | } 59 | // logout when server invalidated the authorization token but the token itself is still valid 60 | if (this.config.httpInterceptor && this.config.logoutOnInvalidToken && !this.authService.isTokenExpired()) { 61 | return reject(this.authService.logout()); 62 | } 63 | // resolve unexpected authorization errors (not a managed request or token not expired) 64 | if (!this.config.httpInterceptor || !this.authService.isTokenExpired()) { 65 | return resolve(response); 66 | } 67 | // resolve expected authorization error without refresh_token setup 68 | if (!this.config.useRefreshToken || !this.authService.getRefreshToken()) { 69 | return resolve(response); 70 | } 71 | 72 | // refresh token and try again 73 | resolve(this.authService.updateToken().then(() => { 74 | let token = this.authService.getAccessToken(); 75 | 76 | if (this.config.authTokenType) { 77 | token = `${this.config.authTokenType} ${token}`; 78 | } 79 | 80 | request.headers.set(this.config.authHeader, token); 81 | 82 | return this.httpClient.fetch(request).then(resolve); 83 | })); 84 | }); 85 | } 86 | }; 87 | } 88 | 89 | /** 90 | * Configure client(s) with authorization interceptor 91 | * 92 | * @param {HttpClient|Rest|string[]} client HttpClient, rest client or api endpoint name, or an array thereof 93 | * 94 | * @return {HttpClient[]} The configured client(s) 95 | */ 96 | configure(client: HttpClient|Rest|Array): HttpClient|Array { 97 | if (Array.isArray(client)) { 98 | let configuredClients = []; 99 | 100 | client.forEach(toConfigure => { 101 | configuredClients.push(this.configure(toConfigure)); 102 | }); 103 | 104 | return configuredClients; 105 | } 106 | 107 | if (typeof client === 'string') { 108 | const endpoint = this.clientConfig.getEndpoint(client); 109 | 110 | if (!endpoint) { 111 | throw new Error(`There is no '${client || 'default'}' endpoint registered.`); 112 | } 113 | client = endpoint.client; 114 | } else if (client instanceof Rest) { 115 | client = client.client; 116 | } else if (!(client instanceof HttpClient)) { 117 | client = this.httpClient; 118 | } 119 | 120 | client.interceptors.push(this.interceptor); 121 | 122 | return client; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/logger.js: -------------------------------------------------------------------------------- 1 | import {getLogger} from 'aurelia-logging'; 2 | 3 | export const logger = getLogger('aurelia-authentication'); 4 | -------------------------------------------------------------------------------- /src/oAuth1.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {buildQueryString} from 'aurelia-path'; 3 | import extend from 'extend'; 4 | import {Storage} from './storage'; 5 | import {Popup} from './popup'; 6 | import {BaseConfig} from './baseConfig'; 7 | 8 | @inject(Storage, Popup, BaseConfig) 9 | export class OAuth1 { 10 | constructor(storage: Storage, popup: Popup, config: BaseConfig) { 11 | this.storage = storage; 12 | this.config = config; 13 | this.popup = popup; 14 | this.defaults = { 15 | url : null, 16 | name : null, 17 | popupOptions : null, 18 | redirectUri : null, 19 | authorizationEndpoint: null 20 | }; 21 | } 22 | 23 | open(options: {}, userData: {}): Promise { 24 | const provider = extend(true, {}, this.defaults, options); 25 | const serverUrl = this.config.joinBase(provider.url); 26 | 27 | if (this.config.platform !== 'mobile') { 28 | this.popup = this.popup.open('', provider.name, provider.popupOptions); 29 | } 30 | 31 | return this.config.client.post(serverUrl) 32 | .then(response => { 33 | const url = provider.authorizationEndpoint + '?' + buildQueryString(response); 34 | 35 | if (this.config.platform === 'mobile') { 36 | this.popup = this.popup.open(url, provider.name, provider.popupOptions); 37 | } else { 38 | this.popup.popupWindow.location = url; 39 | } 40 | 41 | const popupListener = this.config.platform === 'mobile' 42 | ? this.popup.eventListener(provider.redirectUri) 43 | : this.popup.pollPopup(); 44 | 45 | return popupListener.then(result => this.exchangeForToken(result, userData, provider)); 46 | }); 47 | } 48 | 49 | exchangeForToken(oauthData: {}, userData: {}, provider: string): Promise { 50 | const data = extend(true, {}, userData, oauthData); 51 | const serverUrl = this.config.joinBase(provider.url); 52 | const credentials = this.config.withCredentials ? 'include' : 'same-origin'; 53 | 54 | return this.config.client.post(serverUrl, data, {credentials: credentials}); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/oAuth2.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {buildQueryString} from 'aurelia-path'; 3 | import extend from 'extend'; 4 | import {Storage} from './storage'; 5 | import {Popup} from './popup'; 6 | import {BaseConfig} from './baseConfig'; 7 | 8 | /** 9 | * OAuth2 service class 10 | * 11 | * @export 12 | * @class OAuth2 13 | */ 14 | @inject(Storage, Popup, BaseConfig) 15 | export class OAuth2 { 16 | /** 17 | * Creates an instance of OAuth2. 18 | * 19 | * @param {Storage} storage The Storage instance 20 | * @param {Popup} popup The Popup instance 21 | * @param {Config} config The Config instance 22 | * 23 | * @memberOf OAuth2 24 | */ 25 | constructor(storage: Storage, popup: Popup, config: BaseConfig) { 26 | this.storage = storage; 27 | this.config = config; 28 | this.popup = popup; 29 | this.defaults = { 30 | url : null, 31 | name : null, 32 | state : null, 33 | scope : null, 34 | scopeDelimiter : null, 35 | redirectUri : null, 36 | popupOptions : null, 37 | authorizationEndpoint: null, 38 | responseParams : null, 39 | requiredUrlParams : null, 40 | optionalUrlParams : null, 41 | defaultUrlParams : ['response_type', 'client_id', 'redirect_uri'], 42 | responseType : 'code' 43 | }; 44 | } 45 | 46 | /** 47 | * Open OAuth2 flow 48 | * 49 | * @param {{}} options OAuth2 and dialog options 50 | * @param {{}} userData Extra data for the authentications server 51 | * @returns {Promise} Authentication server response 52 | * 53 | * @memberOf OAuth2 54 | */ 55 | open(options: {}, userData: {}): Promise { 56 | const provider = extend(true, {}, this.defaults, options); 57 | const stateName = provider.name + '_state'; 58 | 59 | if (typeof provider.state === 'function') { 60 | this.storage.set(stateName, provider.state()); 61 | } else if (typeof provider.state === 'string') { 62 | this.storage.set(stateName, provider.state); 63 | } 64 | 65 | const url = provider.authorizationEndpoint 66 | + '?' + buildQueryString(this.buildQuery(provider)); 67 | const popup = this.popup.open(url, provider.name, provider.popupOptions); 68 | const openPopup = (this.config.platform === 'mobile') 69 | ? popup.eventListener(provider.redirectUri) 70 | : popup.pollPopup(); 71 | 72 | return openPopup 73 | .then(oauthData => { 74 | if (provider.responseType === 'token' 75 | || provider.responseType === 'id_token token' 76 | || provider.responseType === 'token id_token' 77 | ) { 78 | return oauthData; 79 | } 80 | if (oauthData.state && oauthData.state !== this.storage.get(stateName)) { 81 | return Promise.reject('OAuth 2.0 state parameter mismatch.'); 82 | } 83 | 84 | return this.exchangeForToken(oauthData, userData, provider); 85 | }); 86 | } 87 | 88 | /** 89 | * Exchange the code from the external provider by a token from the authentication server 90 | * 91 | * @param {{}} oauthData The oauth data from the external provider 92 | * @param {{}} userData Extra data for the authentications server 93 | * @param {string} provider The name of the provider 94 | * @returns {Promise} The authenticaion server response with the token 95 | * 96 | * @memberOf OAuth2 97 | */ 98 | exchangeForToken(oauthData: {}, userData: {}, provider: string): Promise { 99 | const data = extend(true, {}, userData, { 100 | clientId : provider.clientId, 101 | redirectUri: provider.redirectUri 102 | }, oauthData); 103 | 104 | const serverUrl = this.config.joinBase(provider.url); 105 | const credentials = this.config.withCredentials ? 'include' : 'same-origin'; 106 | 107 | return this.config.client.post(serverUrl, data, {credentials: credentials}); 108 | } 109 | 110 | /** 111 | * Create the query string for a provider 112 | * 113 | * @param {string} provider The provider name 114 | * @returns {string} The resulting query string 115 | * 116 | * @memberOf OAuth2 117 | */ 118 | buildQuery(provider: string): string { 119 | let query = {}; 120 | const urlParams = ['defaultUrlParams', 'requiredUrlParams', 'optionalUrlParams']; 121 | 122 | urlParams.forEach(params => { 123 | (provider[params] || []).forEach(paramName => { 124 | const camelizedName = camelCase(paramName); 125 | let paramValue = (typeof provider[paramName] === 'function') 126 | ? provider[paramName]() 127 | : provider[camelizedName]; 128 | 129 | if (paramName === 'state') { 130 | paramValue = encodeURIComponent(this.storage.get(provider.name + '_state')); 131 | } 132 | 133 | if (paramName === 'scope' && Array.isArray(paramValue)) { 134 | paramValue = paramValue.join(provider.scopeDelimiter); 135 | 136 | if (provider.scopePrefix) { 137 | paramValue = provider.scopePrefix + provider.scopeDelimiter + paramValue; 138 | } 139 | } 140 | 141 | query[paramName] = paramValue; 142 | }); 143 | }); 144 | 145 | return query; 146 | } 147 | 148 | /** 149 | * Send logout request to oath2 rpovider 150 | * 151 | * @param {[{}]} options Logout option 152 | * @returns {Promise} The OAuth provider response 153 | * 154 | * @memberOf OAuth2 155 | */ 156 | close(options?: {}): Promise { 157 | const provider = extend(true, {}, this.defaults, options); 158 | const url = provider.logoutEndpoint + '?' 159 | + buildQueryString(this.buildLogoutQuery(provider)); 160 | const popup = this.popup.open(url, provider.name, provider.popupOptions); 161 | const openPopup = (this.config.platform === 'mobile') 162 | ? popup.eventListener(provider.postLogoutRedirectUri) 163 | : popup.pollPopup(); 164 | 165 | return openPopup; 166 | } 167 | 168 | /** 169 | * Build query for logout request 170 | * 171 | * @param {string} provider The rpovider name 172 | * @returns {string} The logout query string 173 | * 174 | * @memberOf OAuth2 175 | */ 176 | buildLogoutQuery(provider: string): string { 177 | let query = {}; 178 | let authResponse = this.storage.get(this.config.storageKey); 179 | 180 | if (provider.postLogoutRedirectUri) { 181 | query.post_logout_redirect_uri = provider.postLogoutRedirectUri; 182 | } 183 | if (this.storage.get(provider.name + '_state')) { 184 | query.state = this.storage.get(provider.name + '_state'); 185 | } 186 | if (JSON.parse(authResponse).id_token) { 187 | query.id_token_hint = JSON.parse(authResponse).id_token; 188 | } 189 | 190 | return query; 191 | } 192 | } 193 | 194 | /** 195 | * camelCase a string 196 | * 197 | * @param {any} name String to be camelized 198 | * @returns {string} The camelized name 199 | */ 200 | function camelCase(name: string): string { 201 | return name.replace(/([:\-_]+(.))/g, function(_, separator, letter, offset) { 202 | return offset ? letter.toUpperCase() : letter; 203 | }); 204 | } 205 | -------------------------------------------------------------------------------- /src/popup.js: -------------------------------------------------------------------------------- 1 | import {PLATFORM, DOM} from 'aurelia-pal'; 2 | import {parseQueryString} from 'aurelia-path'; 3 | import extend from 'extend'; 4 | 5 | export class Popup { 6 | constructor() { 7 | this.popupWindow = null; 8 | this.polling = null; 9 | this.url = ''; 10 | } 11 | 12 | open(url: string, windowName: string, options?: {}): Popup { 13 | this.url = url; 14 | const optionsString = buildPopupWindowOptions(options || {}); 15 | 16 | this.popupWindow = PLATFORM.global.open(url, windowName, optionsString); 17 | 18 | if (this.popupWindow && this.popupWindow.focus) { 19 | this.popupWindow.focus(); 20 | } 21 | 22 | return this; 23 | } 24 | 25 | eventListener(redirectUri: string): Promise { 26 | return new Promise((resolve, reject) => { 27 | this.popupWindow.addEventListener('loadstart', event => { 28 | if (event.url.indexOf(redirectUri) !== 0) { 29 | return; 30 | } 31 | 32 | const parser = DOM.createElement('a'); 33 | 34 | parser.href = event.url; 35 | 36 | if (parser.search || parser.hash) { 37 | const qs = parseUrl(parser); 38 | 39 | if (qs.error) { 40 | reject({error: qs.error}); 41 | } else { 42 | resolve(qs); 43 | } 44 | 45 | this.popupWindow.close(); 46 | } 47 | }); 48 | 49 | this.popupWindow.addEventListener('exit', () => { 50 | reject({data: 'Provider Popup was closed'}); 51 | }); 52 | 53 | this.popupWindow.addEventListener('loaderror', () => { 54 | reject({data: 'Authorization Failed'}); 55 | }); 56 | }); 57 | } 58 | 59 | pollPopup(): Promise { 60 | return new Promise((resolve, reject) => { 61 | this.polling = PLATFORM.global.setInterval(() => { 62 | let errorData; 63 | 64 | try { 65 | if (this.popupWindow.location.host === PLATFORM.global.document.location.host 66 | && (this.popupWindow.location.search || this.popupWindow.location.hash)) { 67 | const qs = parseUrl(this.popupWindow.location); 68 | 69 | if (qs.error) { 70 | reject({error: qs.error}); 71 | } else { 72 | resolve(qs); 73 | } 74 | 75 | this.popupWindow.close(); 76 | PLATFORM.global.clearInterval(this.polling); 77 | } 78 | } catch (error) { 79 | errorData = error; 80 | } 81 | 82 | if (!this.popupWindow) { 83 | PLATFORM.global.clearInterval(this.polling); 84 | reject({ 85 | error: errorData, 86 | data : 'Provider Popup Blocked' 87 | }); 88 | } else if (this.popupWindow.closed) { 89 | PLATFORM.global.clearInterval(this.polling); 90 | reject({ 91 | error: errorData, 92 | data : 'Problem poll popup' 93 | }); 94 | } 95 | }, 35); 96 | }); 97 | } 98 | } 99 | 100 | const buildPopupWindowOptions = (options: {}): string => { 101 | const width = options.width || 500; 102 | const height = options.height || 500; 103 | 104 | const extended = extend({ 105 | width : width, 106 | height: height, 107 | left : PLATFORM.global.screenX + ((PLATFORM.global.outerWidth - width) / 2), 108 | top : PLATFORM.global.screenY + ((PLATFORM.global.outerHeight - height) / 2.5) 109 | }, options); 110 | 111 | let parts = []; 112 | 113 | Object.keys(extended).map(key => parts.push(key + '=' + extended[key])); 114 | 115 | return parts.join(','); 116 | }; 117 | 118 | const parseUrl = (url: string): {} => { 119 | let hash = (url.hash.charAt(0) === '#') ? url.hash.substr(1) : url.hash; 120 | 121 | return extend(true, {}, parseQueryString(url.search), parseQueryString(hash)); 122 | }; 123 | -------------------------------------------------------------------------------- /src/storage.js: -------------------------------------------------------------------------------- 1 | import {PLATFORM} from 'aurelia-pal'; 2 | import {inject} from 'aurelia-dependency-injection'; 3 | import {BaseConfig} from './baseConfig'; 4 | 5 | @inject(BaseConfig) 6 | export class Storage { 7 | constructor(config: BaseConfig) { 8 | this.config = config; 9 | } 10 | 11 | get(key: string): string { 12 | return PLATFORM.global[this.config.storage].getItem(key); 13 | } 14 | 15 | set(key: string, value: string) { 16 | PLATFORM.global[this.config.storage].setItem(key, value); 17 | } 18 | 19 | remove(key: string) { 20 | PLATFORM.global[this.config.storage].removeItem(key); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/aurelia-authentication.spec.js: -------------------------------------------------------------------------------- 1 | import {Container} from 'aurelia-dependency-injection'; 2 | import {Config, Rest} from 'aurelia-api'; 3 | import {HttpClient} from 'aurelia-fetch-client'; 4 | 5 | import {configure} from '../src/aurelia-authentication'; 6 | import {AuthService} from '../src/authService'; 7 | import {AuthorizeStep} from '../src/authorizeStep'; 8 | import {AuthenticateStep} from '../src/authenticateStep'; 9 | import {FetchConfig} from '../src/fetchClientConfig'; 10 | import {BaseConfig} from '../src/baseConfig'; 11 | 12 | let noop = () => {}; 13 | 14 | function getContainer() { 15 | let container = new Container(); 16 | let config = container.get(Config); 17 | 18 | config 19 | .registerEndpoint('sx/default', 'http://localhost:1927/') 20 | .registerEndpoint('sx/custom', 'http://localhost:1927/custom') 21 | .setDefaultEndpoint('sx/default'); 22 | 23 | return container; 24 | } 25 | 26 | describe('aurelia-authentication', function() { 27 | describe('export', function() { 28 | it('Should export configure', function() { 29 | expect(configure).toBeDefined(); 30 | }); 31 | 32 | it('Should export FetchConfig', function() { 33 | expect(FetchConfig).toBeDefined(); 34 | }); 35 | 36 | it('Should export AuthService', function() { 37 | expect(AuthService).toBeDefined(); 38 | }); 39 | 40 | it('Should export AuthorizeStep', function() { 41 | expect(AuthorizeStep).toBeDefined(); 42 | }); 43 | 44 | it('Should export AuthenticateStep', function() { 45 | expect(AuthenticateStep).toBeDefined(); 46 | }); 47 | }); 48 | 49 | describe('configure()', function() { 50 | it('Should call globalResources configuration to be passed as a function.', function() { 51 | let container = new Container(); 52 | 53 | let globalResources = []; 54 | configure({ 55 | container: container, globalResources: resource => { 56 | globalResources.push(resource); 57 | } 58 | }, noop); 59 | 60 | const expected = ['./authFilterValueConverter']; 61 | 62 | expect(globalResources.toString()).toEqual(expected.toString()); 63 | }); 64 | 65 | it('Should allow configuration with a function.', function() { 66 | let container = new Container(); 67 | let baseConfig = container.get(BaseConfig); 68 | 69 | configure({container: container, globalResources: noop}, builder => { 70 | expect(builder instanceof BaseConfig).toBe(true); 71 | const myConfig = {foo: 'bar'}; 72 | 73 | builder.configure(myConfig); 74 | expect(baseConfig.foo).toBe('bar'); 75 | }); 76 | }); 77 | 78 | it('Should allow configuration to be passed as an object.', function() { 79 | let container = new Container(); 80 | let baseConfig = container.get(BaseConfig); 81 | const myConfig = {foo: 'bar2'}; 82 | 83 | configure({container: container, globalResources: noop}, myConfig); 84 | expect(baseConfig.foo).toBe('bar2'); 85 | }); 86 | 87 | it('Should not allow configuration with unregistered endpoint', function() { 88 | let container = new Container(); 89 | 90 | let configureEndpoint = () => configure({container: container, globalResources: noop}, {endpoint: 'something'}); 91 | let configureConfigureEndpoints = () => configure({container: container, globalResources: noop}, {configureEndpoints: ['another']}); 92 | 93 | expect(configureEndpoint).toThrow(); 94 | expect(configureConfigureEndpoints).toThrow(); 95 | }); 96 | 97 | it('Should allow configuration with configured endpoints.', function() { 98 | let container = getContainer(); 99 | 100 | let configureEndpoint = () => configure({container: container, globalResources: noop}, {endpoint: 'sx/default'}); 101 | let configureConfigureEndpoints = () => configure({container: container, globalResources: noop}, {configureEndpoints: ['sx/default', 'sx/custom']}); 102 | 103 | expect(configureEndpoint).not.toThrow(); 104 | expect(configureConfigureEndpoints).not.toThrow(); 105 | }); 106 | 107 | it('Should allow configuration with default endpoint.', function() { 108 | let container = getContainer(); 109 | 110 | let configureEndpoint = () => configure({container: container, globalResources: noop}, {endpoint: ''}); 111 | let configureConfigureEndpoints = () => configure({container: container, globalResources: noop}, {configureEndpoints: ['']}); 112 | 113 | expect(configureEndpoint).not.toThrow(); 114 | expect(configureConfigureEndpoints).not.toThrow(); 115 | }); 116 | 117 | it('Should set the configured endpoint as a client.', function() { 118 | let container = getContainer(); 119 | let baseConfig = container.get(BaseConfig); 120 | let clientConfig = container.get(Config); 121 | 122 | configure({container: container, globalResources: noop}, {endpoint: 'sx/custom'}); 123 | 124 | expect(baseConfig.endpoint).toEqual('sx/custom'); 125 | expect(baseConfig.client).toBe(clientConfig.getEndpoint('sx/custom')); 126 | }); 127 | 128 | it('Should set the default endpoint as a client.', function() { 129 | let container = getContainer(); 130 | let baseConfig = container.get(BaseConfig); 131 | let clientConfig = container.get(Config); 132 | 133 | configure({container: container, globalResources: noop}, {endpoint: ''}); 134 | 135 | expect(baseConfig.endpoint).toEqual(''); 136 | expect(baseConfig.client).toBe(clientConfig.getEndpoint('sx/default')); 137 | }); 138 | 139 | it('Should set the default HttpClient as client if no endpoint was supplied.', function() { 140 | let container = new Container(); 141 | let baseConfig = container.get(BaseConfig); 142 | 143 | configure({container: container, globalResources: noop}, {}); 144 | 145 | expect(baseConfig.endpoint).toEqual(null); 146 | expect(baseConfig.client instanceof Rest).toBe(true); 147 | expect(baseConfig.client.client).toBe(container.get(HttpClient)); 148 | }); 149 | }); 150 | }); 151 | -------------------------------------------------------------------------------- /test/authenticateStep.spec.js: -------------------------------------------------------------------------------- 1 | import {Container} from 'aurelia-dependency-injection'; 2 | 3 | import {AuthenticateStep} from '../src/authenticateStep'; 4 | 5 | 6 | const routes = { 7 | onLoginRoute : [ 8 | {name: 'parent', fragment: '/login', config: {}}, 9 | {name: 'child', fragment: 'childUrl', config: {}} 10 | ], 11 | authenticateNone : [ 12 | {name: 'parent', fragment: 'parentUrl', config: {}}, 13 | {name: 'child', fragment: 'childUrl', config: {}} 14 | ], 15 | authenticateChild : [ 16 | {name: 'parent', fragment: 'parentUrl', config: {}}, 17 | {name: 'child', fragment: 'childUrl', config: {auth: true}} 18 | ], 19 | authenticateParent : [ 20 | {name: 'parent', fragment: 'parentUrl', config: {auth: true}}, 21 | {name: 'child', fragment: 'childUrl', config: {}} 22 | ]}; 23 | 24 | describe('AuthenticateStep', () => { 25 | describe('.run()', () => { 26 | const authenticateStep = new Container().get(AuthenticateStep); 27 | let loginRoute = authenticateStep.authService.config.loginRoute; 28 | 29 | beforeEach(() => { 30 | authenticateStep.authService.authenticated = false; 31 | }); 32 | afterEach(() => { 33 | authenticateStep.authService.authenticated = false; 34 | }); 35 | 36 | it('should not redirect when not authenticated and no route requires it', () => { 37 | let routingContext = { 38 | getAllInstructions: () => routes.authenticateNone 39 | }; 40 | 41 | function next() {return;} 42 | next.cancel = redirect => {throw new Error();}; 43 | spyOn(next, 'cancel'); 44 | 45 | authenticateStep.run(routingContext, next); 46 | 47 | expect(next.cancel).not.toHaveBeenCalled(); 48 | }); 49 | 50 | it('should redirect to login when not authenticated and child route requires it', done => { 51 | let routingContext = { 52 | getAllInstructions: () => routes.authenticateChild 53 | }; 54 | 55 | function next() {return;} 56 | next.cancel = redirect => { 57 | expect(redirect.url).toBe(loginRoute); 58 | done(); 59 | }; 60 | 61 | authenticateStep.run(routingContext, next); 62 | }); 63 | 64 | it('should redirect to login when not authenticated and parent route requires it', done => { 65 | let routingContext = { 66 | getAllInstructions: () => routes.authenticateParent 67 | }; 68 | 69 | function next() {return;} 70 | next.cancel = redirect => { 71 | expect(redirect.url).toBe(loginRoute); 72 | done(); 73 | }; 74 | 75 | authenticateStep.run(routingContext, next); 76 | }); 77 | 78 | it('should not redirect to login when authenticated and no route requires it', () => { 79 | let routingContext = { 80 | getAllInstructions: () => routes.authenticateNone 81 | }; 82 | 83 | function next() {return;} 84 | next.cancel = redirect => {throw new Error();}; 85 | spyOn(next, 'cancel'); 86 | 87 | authenticateStep.authService.authenticated = true; 88 | 89 | authenticateStep.run(routingContext, next); 90 | 91 | expect(next.cancel).not.toHaveBeenCalled(); 92 | }); 93 | 94 | it('should not redirect when authenticated and child route requires it', () => { 95 | let routingContext = { 96 | getAllInstructions: () => routes.authenticateChild 97 | }; 98 | 99 | function next() {return;} 100 | next.cancel = redirect => {throw new Error();}; 101 | spyOn(next, 'cancel'); 102 | 103 | authenticateStep.authService.authenticated = true; 104 | 105 | authenticateStep.run(routingContext, next); 106 | 107 | expect(next.cancel).not.toHaveBeenCalled(); 108 | }); 109 | 110 | it('should not redirect when not authenticated and parent route requires it', () => { 111 | let routingContext = { 112 | getAllInstructions: () => routes.authenticateParent 113 | }; 114 | 115 | function next() {return;} 116 | next.cancel = redirect => {throw new Error();}; 117 | spyOn(next, 'cancel'); 118 | 119 | authenticateStep.authService.authenticated = true; 120 | 121 | authenticateStep.run(routingContext, next); 122 | 123 | expect(next.cancel).not.toHaveBeenCalled(); 124 | }); 125 | 126 | it('should redirect when authenticated and parent route is login route', done => { 127 | let routingContext = { 128 | getAllInstructions: () => routes.onLoginRoute 129 | }; 130 | 131 | function next() {return;} 132 | next.cancel = redirect => { 133 | expect(redirect.url).toBe(authenticateStep.authService.config.loginRedirect); 134 | done(); 135 | }; 136 | 137 | authenticateStep.authService.authenticated = true; 138 | 139 | authenticateStep.run(routingContext, next); 140 | }); 141 | }); 142 | }); 143 | -------------------------------------------------------------------------------- /test/authentication.spec.js: -------------------------------------------------------------------------------- 1 | import {Container} from 'aurelia-dependency-injection'; 2 | 3 | import {Authentication} from '../src/authentication'; 4 | 5 | const tokenPast = { 6 | payload: { 7 | name: 'tokenPast', 8 | admin: false, 9 | exp: '0460017154' 10 | }, 11 | jwt: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidG9rZW5QYXN0IiwiYWRtaW4iOmZhbHNlLCJleHAiOiIwNDYwMDE3MTU0In0.Z7QE185hOWL6xxVDmlFpNEmgA-_Vg2bjV9uDRkkVaQY' 12 | }; 13 | 14 | const tokenFuture = { 15 | payload: { 16 | name: 'tokenFuture', 17 | admin: true, 18 | exp: '2460017154' 19 | }, 20 | jwt: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidG9rZW5GdXR1cmUiLCJhZG1pbiI6dHJ1ZSwiZXhwIjoiMjQ2MDAxNzE1NCJ9.iHXLzWGY5U9WwVT4IVRLuKTf65XpgrA1Qq_Jlynv6bc' 21 | }; 22 | 23 | const oidcProviderConfig = { 24 | providers: { 25 | oidcProvider: { 26 | name: 'oidcProvider', 27 | oauthType: '2.0', 28 | postLogoutRedirectUri: 'http://localhost:1927/', 29 | logoutEndpoint: 'http://localhost:54540/connect/logout', 30 | popupOptions: { width: 1028, height: 529 } 31 | } 32 | } 33 | }; 34 | 35 | describe('Authentication', () => { 36 | describe('.getResponseObject', () => { 37 | const container = new Container(); 38 | const authentication = container.get(Authentication); 39 | 40 | afterEach(() => { 41 | authentication.setResponseObject(null); 42 | }); 43 | 44 | it('Should get {} if no responseObject stored', () => { 45 | window.localStorage.removeItem('aurelia_authentication'); 46 | 47 | const responseObject = authentication.getResponseObject(); 48 | expect(typeof responseObject === 'object').toBe(true); 49 | expect(responseObject).toBe(null); 50 | }); 51 | 52 | it('Should get stored responseObject', () => { 53 | window.localStorage.setItem('aurelia_authentication', JSON.stringify({access_token: 'another'})); 54 | 55 | const responseObject = authentication.getResponseObject(); 56 | expect(typeof responseObject === 'object').toBe(true); 57 | expect(responseObject.access_token).toBe('another'); 58 | }); 59 | }); 60 | 61 | describe('.setResponseObject()', () => { 62 | const container = new Container(); 63 | const authentication = container.get(Authentication); 64 | 65 | afterEach(() => { 66 | authentication.setResponseObject(null); 67 | }); 68 | 69 | it('Should set with object', () => { 70 | authentication.setResponseObject({access_token: 'some'}); 71 | 72 | expect(JSON.parse(window.localStorage.getItem('aurelia_authentication')).access_token).toBe('some'); 73 | }); 74 | 75 | it('Should delete', () => { 76 | window.localStorage.setItem('aurelia_authentication', 'another'); 77 | 78 | authentication.setResponseObject(null); 79 | expect(window.localStorage.getItem('aurelia_authentication')).toBe(null); 80 | }); 81 | }); 82 | 83 | describe('.getAccessToken()', () => { 84 | const container = new Container(); 85 | const authentication = container.get(Authentication); 86 | 87 | afterEach(() => { 88 | authentication.setResponseObject(null); 89 | }); 90 | 91 | it('Should analyze response first and return accessToken', () => { 92 | authentication.setResponseObject({access_token: 'some'}); 93 | 94 | expect(authentication.getAccessToken()).toBe('some'); 95 | }); 96 | 97 | it('Should use custom function to analyze response first and return accessToken', () => { 98 | authentication.config.getAccessTokenFromResponse = response => response.custom; 99 | 100 | authentication.setResponseObject({custom: 'custom'}); 101 | 102 | expect(authentication.getAccessToken()).toBe('custom'); 103 | }); 104 | }); 105 | 106 | describe('.getRefreshToken()', () => { 107 | const container = new Container(); 108 | const authentication = container.get(Authentication); 109 | 110 | afterEach(() => { 111 | authentication.config.useRefreshToken = false; 112 | authentication.setResponseObject(null); 113 | }); 114 | 115 | it('Should analyze response first and return refreshToken', () => { 116 | authentication.config.useRefreshToken = true; 117 | 118 | authentication.setResponseObject({token: 'some', refresh_token: 'another'}); 119 | 120 | expect(authentication.getRefreshToken()).toBe('another'); 121 | }); 122 | 123 | it('Should use custom function to analyze response first and return refreshToken', () => { 124 | authentication.config.useRefreshToken = true; 125 | authentication.config.getRefreshTokenFromResponse = response => response.custom; 126 | 127 | authentication.setResponseObject({token: 'some', custom: 'other custom'}); 128 | 129 | expect(authentication.getRefreshToken()).toBe('other custom'); 130 | }); 131 | }); 132 | 133 | describe('.getIdToken()', () => { 134 | const container = new Container(); 135 | const authentication = container.get(Authentication); 136 | 137 | afterEach(() => { 138 | authentication.setResponseObject(null); 139 | }); 140 | 141 | it('Should analyze response first and return idToken', () => { 142 | authentication.setResponseObject({access_token: 'some', id_token: 'another'}); 143 | 144 | expect(authentication.getIdToken()).toBe('another'); 145 | }); 146 | }); 147 | 148 | describe('.getPayload()', () => { 149 | const container = new Container(); 150 | const authentication = container.get(Authentication); 151 | 152 | afterEach(() => { 153 | authentication.setResponseObject(null); 154 | }); 155 | 156 | it('Should return null for JWT-like token', () => { 157 | authentication.setResponseObject({token: 'xx.yy.zz'}); 158 | const payload = authentication.payload; 159 | 160 | expect(payload).toBe(null); 161 | }); 162 | 163 | it('Should return null for non-JWT-like token', () => { 164 | authentication.setResponseObject({token: 'some'}); 165 | const payload = authentication.payload; 166 | 167 | expect(payload).toBe(null); 168 | }); 169 | 170 | it('Should analyze response first and return payload', () => { 171 | authentication.setResponseObject({token: tokenFuture.jwt}); 172 | 173 | const payload = authentication.getPayload(); 174 | expect(typeof payload === 'object').toBe(true); 175 | expect(JSON.stringify(payload)).toBe(JSON.stringify(tokenFuture.payload)); 176 | }); 177 | }); 178 | 179 | describe('.getIdPayload()', () => { 180 | const container = new Container(); 181 | const authentication = container.get(Authentication); 182 | 183 | afterEach(() => { 184 | authentication.setResponseObject(null); 185 | }); 186 | 187 | it('Should return null for JWT-like token', () => { 188 | authentication.setResponseObject({token: tokenFuture.jwt, id_token: 'xx.yy.zz'}); 189 | const payload = authentication.idPayload; 190 | 191 | expect(payload).toBe(null); 192 | }); 193 | 194 | it('Should return null for non-JWT-like token', () => { 195 | authentication.setResponseObject({'token': tokenFuture.jwt, id_token: 'some'}); 196 | const payload = authentication.idPayload; 197 | 198 | expect(payload).toBe(null); 199 | }); 200 | 201 | it('Should analyze response first and return payload', () => { 202 | authentication.setResponseObject({token: 'some', id_token: tokenFuture.jwt}); 203 | 204 | const payload = authentication.getIdPayload(); 205 | expect(typeof payload === 'object').toBe(true); 206 | expect(JSON.stringify(payload)).toBe(JSON.stringify(tokenFuture.payload)); 207 | }); 208 | }); 209 | 210 | describe('.getExp()', () => { 211 | const container = new Container(); 212 | const authentication = container.get(Authentication); 213 | 214 | afterEach(() => { 215 | authentication.setResponseObject(null); 216 | }); 217 | 218 | it('Should analyze response first and return exp', () => { 219 | authentication.setResponseObject({token: tokenPast.jwt}); 220 | 221 | const exp = authentication.getExp(); 222 | expect(typeof exp === 'number').toBe(true); 223 | expect(exp).toBe(Number(tokenPast.payload.exp)); 224 | }); 225 | 226 | it('Should use custom function to analyze response first and return exp', () => { 227 | authentication.config.getExpirationDateFromResponse = response => response.custom; 228 | 229 | authentication.setResponseObject({token: 'some', custom: 2460017154}); 230 | 231 | const exp = authentication.getExp(); 232 | expect(typeof exp === 'number').toBe(true); 233 | expect(exp).toBe(2460017154); 234 | }); 235 | }); 236 | 237 | 238 | describe('.getTtl()', () => { 239 | const container = new Container(); 240 | const authentication = container.get(Authentication); 241 | 242 | afterEach(() => { 243 | authentication.setResponseObject(null); 244 | }); 245 | 246 | it('Should be NaN for Non-JWT', () => { 247 | authentication.setResponseObject({token: 'some'}); 248 | const timeLeft = authentication.getTtl(); 249 | 250 | expect(typeof timeLeft === 'number').toBe(true); 251 | expect(Number.isNaN(timeLeft)).toBe(true); 252 | }); 253 | 254 | it('Should be exp-currentTime for JWT', () => { 255 | authentication.setResponseObject({token: tokenPast.jwt}); 256 | 257 | const timeLeft = authentication.getTtl(); 258 | expect(typeof timeLeft === 'number').toBe(true); 259 | expect(timeLeft).toBe(tokenPast.payload.exp - Math.round(new Date().getTime() / 1000)); 260 | }); 261 | }); 262 | 263 | describe('.isTokenExpired()', () => { 264 | const container = new Container(); 265 | const authentication = container.get(Authentication); 266 | 267 | afterEach(() => { 268 | authentication.setResponseObject(null); 269 | }); 270 | 271 | it('Should be undefined for Non-JWT', () => { 272 | authentication.setResponseObject({token: 'some'}); 273 | const isTokenExpired = authentication.isTokenExpired(); 274 | 275 | expect(isTokenExpired).toBe(undefined); 276 | }); 277 | 278 | it('Should be true when JWT expired', () => { 279 | authentication.setResponseObject({token: tokenPast.jwt}); 280 | 281 | const isTokenExpired = authentication.isTokenExpired(); 282 | expect(typeof isTokenExpired === 'boolean').toBe(true); 283 | expect(isTokenExpired).toBe(true); 284 | }); 285 | 286 | it('Should false when JWT not expired', () => { 287 | authentication.setResponseObject({token: tokenFuture.jwt}); 288 | 289 | const isTokenExpired = authentication.isTokenExpired(); 290 | expect(typeof isTokenExpired === 'boolean').toBe(true); 291 | expect(isTokenExpired).toBe(false); 292 | }); 293 | }); 294 | 295 | 296 | describe('.isAuthenticated', () => { 297 | const container = new Container(); 298 | const authentication = container.get(Authentication); 299 | 300 | afterEach(() => { 301 | authentication.setResponseObject(null); 302 | }); 303 | 304 | it('Should be false when no token present', () => { 305 | const isAuthenticated = authentication.isAuthenticated(); 306 | 307 | expect(isAuthenticated).toBe(false); 308 | }); 309 | 310 | it('Should be true when non-JWT token present', () => { 311 | authentication.setResponseObject({token: 'some'}); 312 | const isAuthenticated = authentication.isAuthenticated(); 313 | 314 | expect(isAuthenticated).toBe(true); 315 | }); 316 | 317 | it('Should be true when JWT-like token present', () => { 318 | authentication.setResponseObject({token: 'xx.yy.zz'}); 319 | const isAuthenticated = authentication.isAuthenticated(); 320 | 321 | expect(isAuthenticated).toBe(true); 322 | }); 323 | 324 | it('Should be false when JWT expired', () => { 325 | authentication.setResponseObject({token: tokenPast.jwt}); 326 | 327 | const isAuthenticated = authentication.isAuthenticated(); 328 | expect(isAuthenticated).toBe(false); 329 | }); 330 | 331 | it('Should be false when JWT not expired', () => { 332 | authentication.setResponseObject({token: tokenFuture.jwt}); 333 | 334 | const isAuthenticated = authentication.isAuthenticated(); 335 | expect(isAuthenticated).toBe(true); 336 | }); 337 | }); 338 | 339 | 340 | describe('.getTokenFromResponse', () => { 341 | const container = new Container(); 342 | const authentication = container.get(Authentication); 343 | 344 | afterEach(() => { 345 | authentication.setResponseObject(null); 346 | }); 347 | 348 | it('Should not throw if response is empty', () => { 349 | const fail = () => authentication.getTokenFromResponse(); 350 | 351 | expect(fail).not.toThrow(); 352 | }); 353 | 354 | it('Should throw if response is not string or object', () => { 355 | const fail = () => authentication.getTokenFromResponse(1); 356 | 357 | expect(fail).toThrow(); 358 | }); 359 | 360 | it('Should return token if response has a string in tokenProp', () => { 361 | expect(authentication.getTokenFromResponse( 362 | {tokenProp: 'some'}, 363 | 'tokenProp' 364 | )).toBe('some'); 365 | }); 366 | 367 | it('Should return token if response has a string in dotted tokenProp', () => { 368 | expect(authentication.getTokenFromResponse( 369 | {tokenProp: {tokenName: 'some'}}, 370 | 'tokenProp.tokenName' 371 | )).toBe('some'); 372 | }); 373 | 374 | it('Should return token if response has a string in tokenName of tokenProp', () => { 375 | expect(authentication.getTokenFromResponse( 376 | {tokenProp: {tokenName: 'some'}}, 377 | 'tokenProp', 378 | 'tokenName' 379 | )).toBe('some'); 380 | }); 381 | 382 | it('Should throw if token not found in nested', () => { 383 | const fail = () => authentication.getTokenFromResponse( 384 | {tokenProp: {wrongTokenName: 'some'}}, 385 | 'tokenProp', 386 | 'tokenName' 387 | ); 388 | expect(fail).toThrow(); 389 | }); 390 | 391 | it('Should throw if token not found in nested', () => { 392 | const fail = () => authentication.getTokenFromResponse( 393 | {tokenProp: {wrongTokenName: 'some'}}, 394 | 'tokenProp', 395 | 'tokenName' 396 | ); 397 | expect(fail).toThrow(); 398 | }); 399 | 400 | it('Should return token if response has a string in tokenName in tokenRoot of tokenProp', () => { 401 | expect(authentication.getTokenFromResponse( 402 | {tokenProp: {tokenRoot1: {tokenRoot2: {tokenName: 'some'}}}}, 403 | 'tokenProp', 404 | 'tokenName', 405 | 'tokenRoot1.tokenRoot2' 406 | )).toBe('some'); 407 | }); 408 | }); 409 | 410 | 411 | describe('.getDataFromResponse', () => { 412 | const container = new Container(); 413 | const authentication = container.get(Authentication); 414 | 415 | afterEach(() => { 416 | authentication.setResponseObject(null); 417 | }); 418 | 419 | it('Should set data from non-JWT response', () => { 420 | authentication.getDataFromResponse({access_token: 'token'}); 421 | 422 | expect(authentication.responseAnalyzed).toBe(true); 423 | expect(authentication.accessToken).toBe('token'); 424 | expect(authentication.payload).toBe(null); 425 | expect(Number.isNaN(authentication.exp)).toBe(true); 426 | }); 427 | 428 | it('Should set data from JWT-like response', () => { 429 | authentication.getDataFromResponse({access_token: 'xx.yy.zz'}); 430 | 431 | expect(authentication.responseAnalyzed).toBe(true); 432 | expect(authentication.accessToken).toBe('xx.yy.zz'); 433 | expect(authentication.payload).toBe(null); 434 | expect(Number.isNaN(authentication.exp)).toBe(true); 435 | }); 436 | 437 | it('Should set data from JWT response', () => { 438 | authentication.getDataFromResponse({access_token: tokenFuture.jwt}); 439 | 440 | expect(authentication.responseAnalyzed).toBe(true); 441 | expect(authentication.accessToken).toBe(tokenFuture.jwt); 442 | expect(JSON.stringify(authentication.payload)).toBe(JSON.stringify(tokenFuture.payload)); 443 | expect(authentication.exp).toBe(Number(tokenFuture.payload.exp)); 444 | }); 445 | }); 446 | 447 | describe('.logout', () => { 448 | const container = new Container(); 449 | const authentication = container.get(Authentication); 450 | 451 | afterEach(() => { 452 | authentication.setResponseObject(null); 453 | }); 454 | 455 | it('should return Not Applicable when logoutEndpoint not defined', done => { 456 | authentication.config.configure(oidcProviderConfig); 457 | authentication.config.providers.oidcProvider.logoutEndpoint = null; 458 | authentication.logout('oidcProvider') 459 | .then( (value) => { 460 | expect(value).toBe('Not Applicable'); 461 | done(); 462 | }) 463 | .catch( err => { 464 | done(); 465 | }); 466 | }); 467 | 468 | it('should return Not Applicable when oauthType not equal to 2.0', done => { 469 | authentication.config.configure(oidcProviderConfig); 470 | authentication.config.providers.oidcProvider.oauthType = '1.0'; 471 | authentication.logout('oidcProvider') 472 | .then( (value) => { 473 | expect(value).toBe('Not Applicable'); 474 | done(); 475 | }) 476 | .catch( err => { 477 | done(); 478 | }); 479 | }); 480 | 481 | it('should return state', done => { 482 | let stateValue = '123456789'; 483 | authentication.config.configure(oidcProviderConfig); 484 | spyOn(authentication.oAuth2, 'close').and.callFake(() => { 485 | return Promise.resolve(stateValue); 486 | }); 487 | authentication.logout('oidcProvider') 488 | .then( state => { 489 | expect(state).toBe(stateValue); 490 | done(); 491 | }) 492 | .catch( err => { 493 | done(); 494 | }); 495 | }); 496 | }); 497 | 498 | describe('.redirect', () => { 499 | const container = new Container(); 500 | const authentication = container.get(Authentication); 501 | 502 | it('should not redirect with redirectUri===0', () => { 503 | authentication.redirect(0, 'somewhere'); 504 | 505 | // basically just don't get the window reload error 506 | expect(true).toBe(true); 507 | }); 508 | }); 509 | }); 510 | -------------------------------------------------------------------------------- /test/baseConfig.spec.js: -------------------------------------------------------------------------------- 1 | import {Container} from 'aurelia-dependency-injection'; 2 | 3 | import {BaseConfig} from '../src/baseConfig'; 4 | 5 | describe('BaseConfig', () => { 6 | describe('.configure()', () => { 7 | it('Should merge incomming with base', () => { 8 | const container = new Container(); 9 | const baseConfig = container.get(BaseConfig); 10 | 11 | 12 | baseConfig.configure({providers: {google: {name: 'not google'}}}); 13 | 14 | expect(baseConfig.providers.google.name).toBe('not google'); 15 | expect(JSON.stringify(baseConfig.providers.google.popupOptions)) 16 | .toBe(JSON.stringify({width: 452, height: 633})); 17 | expect(baseConfig.providers.facebook.name).toBe('facebook'); 18 | }); 19 | }); 20 | 21 | describe('.joinBase()', () => { 22 | it('Should join baseUrl with path', () => { 23 | const container = new Container(); 24 | const baseConfig = container.get(BaseConfig); 25 | baseConfig.baseUrl = 'http://localhost:1927/'; 26 | 27 | expect(baseConfig.joinBase('/xy')).toBe('http://localhost:1927/xy'); 28 | }); 29 | }); 30 | 31 | describe('.getOptionsForTokenRequests()', () => { 32 | it('When given empty or undefined object as input, should return default values set in config object', () => { 33 | const container = new Container(); 34 | const baseConfig = container.get(BaseConfig); 35 | const resultOptions = baseConfig.getOptionsForTokenRequests(); 36 | 37 | expect(resultOptions.headers['Content-Type']).toBe(baseConfig.defaultHeadersForTokenRequests['Content-Type']); 38 | }); 39 | 40 | it('When given object with values, should return object with values overridden by input object', () => { 41 | const container = new Container(); 42 | const baseConfig = container.get(BaseConfig); 43 | const contentType = 'test'; 44 | const resultOptions = baseConfig.getOptionsForTokenRequests({ 45 | headers: { 46 | 'Content-Type': contentType 47 | } 48 | }); 49 | 50 | expect(resultOptions.headers['Content-Type']).toBe(resultOptions.headers['Content-Type']); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/fetchClientConfig.spec.js: -------------------------------------------------------------------------------- 1 | import {Container} from 'aurelia-dependency-injection'; 2 | import {HttpClient} from 'aurelia-fetch-client'; 3 | import {Config} from 'aurelia-api'; 4 | 5 | import {FetchConfig} from '../src/fetchClientConfig'; 6 | import {AuthService} from '../src/authService'; 7 | 8 | const tokenFuture = { 9 | payload: { 10 | name : 'tokenFuture', 11 | admin: true, 12 | exp : '2460017154' 13 | }, 14 | jwt: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidG9rZW5GdXR1cmUiLCJhZG1pbiI6dHJ1ZSwiZXhwIjoiMjQ2MDAxNzE1NCJ9.iHXLzWGY5U9WwVT4IVRLuKTf65XpgrA1Qq_Jlynv6bc' 15 | }; 16 | 17 | function getContainer() { 18 | let container = new Container(); 19 | let config = container.get(Config); 20 | 21 | config 22 | .registerEndpoint('sx/default', 'http://localhost:1927/') 23 | .registerEndpoint('sx/custom', 'http://localhost:1927/custom') 24 | .setDefaultEndpoint('sx/default'); 25 | 26 | return container; 27 | } 28 | 29 | describe('FetchConfig', function() { 30 | let container = getContainer(); 31 | let clientConfig = container.get(Config); 32 | let fetchConfig = container.get(FetchConfig); 33 | let authService = container.get(AuthService); 34 | 35 | describe('.intercept()', function() { 36 | it('Should intercept requests when authenticated.', function(done) { 37 | let client = container.get(HttpClient); 38 | 39 | client.baseUrl = 'http://localhost:1927/'; 40 | authService.setResponseObject({token: 'xy'}); 41 | 42 | fetchConfig.configure(); 43 | client.fetch('some') 44 | .then(response => response.json()) 45 | .then(response => { 46 | expect(response.Authorization).toEqual('Bearer xy'); 47 | done(); 48 | }) 49 | .catch(err => { 50 | expect(err).toBeUndefined(); 51 | expect(true).toBe(false); 52 | done(); 53 | }); 54 | }); 55 | 56 | it('Should not intercept requests when unauthenticated.', function(done) { 57 | let client = new HttpClient(); 58 | 59 | client.baseUrl = 'http://localhost:1927/'; 60 | authService.accessToken = null; 61 | 62 | fetchConfig.configure(); 63 | client.fetch('some') 64 | .then(response => response.json()) 65 | .then(response => { 66 | expect(response.Authorization).toBeUndefined(); 67 | done(); 68 | }) 69 | .catch(err => { 70 | expect(err).toBeUndefined(); 71 | expect(true).toBe(false); 72 | done(); 73 | }); 74 | }); 75 | 76 | it('Should not intercept requests when authenticated with the httpInterceptor disabled.', function(done) { 77 | let client = new HttpClient(); 78 | 79 | client.baseUrl = 'http://localhost:1927/'; 80 | authService.accessToken = 'xy'; 81 | clientConfig.httpInterceptor = false; 82 | 83 | fetchConfig.configure(); 84 | client.fetch('some') 85 | .then(response => response.json()) 86 | .then(response => { 87 | expect(response.Authorization).toBeUndefined(); 88 | done(); 89 | }) 90 | .catch(err => { 91 | expect(err).toBeUndefined(); 92 | expect(true).toBe(false); 93 | done(); 94 | }); 95 | }); 96 | }); 97 | 98 | clientConfig.httpInterceptor = true; 99 | 100 | describe('.configure()', function() { 101 | it('Should configure the HttpClient singleton without any arguments.', function(done) { 102 | let client = container.get(HttpClient); 103 | 104 | client.baseUrl = 'http://localhost:1927/'; 105 | authService.accessToken = 'xy'; 106 | 107 | fetchConfig.configure(); 108 | client.fetch('some') 109 | .then(response => { 110 | expect(response instanceof Response).toEqual(true); 111 | 112 | return response.json(); 113 | }) 114 | .then(response => { 115 | expect(response.path).toEqual('/some'); 116 | expect(response.Authorization).toEqual('Bearer xy'); 117 | done(); 118 | }) 119 | .catch(err => { 120 | expect(err).toBeUndefined(); 121 | expect(true).toBe(false); 122 | done(); 123 | }); 124 | }); 125 | 126 | it('Should configure given client as instance of HttpClient.', function(done) { 127 | let client = new HttpClient(); 128 | 129 | client.baseUrl = 'http://localhost:1927/'; 130 | authService.accessToken = 'xy'; 131 | 132 | fetchConfig.configure(client); 133 | client.fetch('some') 134 | .then(response => { 135 | expect(response instanceof Response).toEqual(true); 136 | 137 | return response.json(); 138 | }) 139 | .then(response => { 140 | expect(response.path).toEqual('/some'); 141 | expect(response.Authorization).toEqual('Bearer xy'); 142 | done(); 143 | }) 144 | .catch(err => { 145 | expect(err).toBeUndefined(); 146 | expect(true).toBe(false); 147 | done(); 148 | }); 149 | }); 150 | 151 | it('Should configure given client as instance of Rest.', function(done) { 152 | let rest = clientConfig.getEndpoint('sx/default'); 153 | 154 | authService.accessToken = 'xy'; 155 | 156 | fetchConfig.configure(rest); 157 | rest.find('some') 158 | .then(response => { 159 | expect(response.path).toEqual('/some'); 160 | expect(response.Authorization).toEqual('Bearer xy'); 161 | done(); 162 | }) 163 | .catch(err => { 164 | expect(err).toBeUndefined(); 165 | expect(true).toBe(false); 166 | done(); 167 | }); 168 | }); 169 | 170 | it('Should not configure given client being an unknown endpoint string.', function() { 171 | let endpoint = 'unknown'; 172 | 173 | let configureWithTypo = () => fetchConfig.configure(endpoint); 174 | 175 | expect(configureWithTypo).toThrow(); 176 | }); 177 | 178 | it('Should configure given client being the default endpoint.', function(done) { 179 | let rest = clientConfig.getEndpoint('sx/default'); 180 | let endpoint = ''; 181 | 182 | authService.accessToken = 'xy'; 183 | 184 | fetchConfig.configure(endpoint); 185 | rest.find('some') 186 | .then(response => { 187 | expect(response.path).toEqual('/some'); 188 | expect(response.Authorization).toEqual('Bearer xy'); 189 | done(); 190 | }) 191 | .catch(err => { 192 | expect(err).toBeUndefined(); 193 | expect(true).toBe(false); 194 | done(); 195 | }); 196 | }); 197 | 198 | it('Should configure given client being an endpoint string.', function(done) { 199 | let rest = clientConfig.getEndpoint('sx/default'); 200 | let endpoint = 'sx/default'; 201 | 202 | authService.accessToken = 'xy'; 203 | 204 | fetchConfig.configure(endpoint); 205 | rest.find('some') 206 | .then(response => { 207 | expect(response.path).toEqual('/some'); 208 | expect(response.Authorization).toEqual('Bearer xy'); 209 | done(); 210 | }) 211 | .catch(err => { 212 | expect(err).toBeUndefined(); 213 | expect(true).toBe(false); 214 | done(); 215 | }); 216 | }); 217 | 218 | it('Should configure given client as array of HttpClient instances.', function(done) { 219 | let clients = [new HttpClient(), new HttpClient()]; 220 | 221 | clients[1].baseUrl = 'http://localhost:1927/'; 222 | authService.accessToken = 'xy'; 223 | 224 | fetchConfig.configure(clients); 225 | clients[1].fetch('some') 226 | .then(response => { 227 | expect(response instanceof Response).toEqual(true); 228 | 229 | return response.json(); 230 | }) 231 | .then(response => { 232 | expect(response.path).toEqual('/some'); 233 | expect(response.Authorization).toEqual('Bearer xy'); 234 | done(); 235 | }) 236 | .catch(err => { 237 | expect(err).toBeUndefined(); 238 | expect(true).toBe(false); 239 | done(); 240 | }); 241 | }); 242 | 243 | it('Should configure given client as array of Rest instances.', function(done) { 244 | let rests = [clientConfig.getEndpoint('sx/default'), clientConfig.getEndpoint('sx/custom')]; 245 | 246 | rests[1] = clientConfig.getEndpoint('sx/default'); 247 | authService.accessToken = 'xy'; 248 | 249 | fetchConfig.configure(rests); 250 | rests[1].find('some') 251 | .then(response => { 252 | expect(response.path).toEqual('/some'); 253 | expect(response.Authorization).toEqual('Bearer xy'); 254 | done(); 255 | }) 256 | .catch(err => { 257 | expect(err).toBeUndefined(); 258 | expect(true).toBe(false); 259 | done(); 260 | }); 261 | }); 262 | 263 | it('Should configure given client as array of strings.', function(done) { 264 | let endpoints = ['sx/default', 'sx/custom']; 265 | let rest = clientConfig.getEndpoint('sx/default'); 266 | 267 | authService.accessToken = 'xy'; 268 | 269 | fetchConfig.configure(endpoints); 270 | rest.find('some') 271 | .then(response => { 272 | expect(response.path).toEqual('/some'); 273 | expect(response.Authorization).toEqual('Bearer xy'); 274 | done(); 275 | }) 276 | .catch(err => { 277 | expect(err).toBeUndefined(); 278 | expect(true).toBe(false); 279 | done(); 280 | }); 281 | }); 282 | 283 | it('Should not logout on invalid token (default)', function(done) { 284 | let client = new HttpClient(); 285 | 286 | client.baseUrl = 'http://localhost:1927/'; 287 | authService.setResponseObject({access_token: tokenFuture.jwt}); 288 | fetchConfig.configure(client); 289 | 290 | client.fetch('unauthorized') 291 | .then(response => { 292 | expect(authService.isAuthenticated()).toBe(true); 293 | 294 | done(); 295 | }) 296 | .catch(err => { 297 | expect(true).toBe(false); 298 | 299 | done(); 300 | }); 301 | }); 302 | 303 | it('Should logout on invalid token (logoutOnInvalidToken = true)', function(done) { 304 | let client = new HttpClient(); 305 | 306 | client.baseUrl = 'http://localhost:1927/'; 307 | authService.setResponseObject({access_token: tokenFuture.jwt}); 308 | authService.config.logoutOnInvalidToken = true; 309 | fetchConfig.configure(client); 310 | 311 | client.fetch('unauthorized') 312 | .then(response => { 313 | expect(true).toBe(false); 314 | 315 | done(); 316 | }) 317 | .catch(err => { 318 | expect(authService.isAuthenticated()).toBe(false); 319 | 320 | done(); 321 | }); 322 | }); 323 | 324 | it('Should logout on invalid token (logoutOnInvalidtoken = true) (deprecated)', function(done) { 325 | let client = new HttpClient(); 326 | 327 | client.baseUrl = 'http://localhost:1927/'; 328 | authService.setResponseObject({access_token: tokenFuture.jwt}); 329 | authService.config.logoutOnInvalidtoken = true; 330 | fetchConfig.configure(client); 331 | 332 | client.fetch('unauthorized') 333 | .then(response => { 334 | expect(true).toBe(false); 335 | 336 | done(); 337 | }) 338 | .catch(err => { 339 | expect(authService.isAuthenticated()).toBe(false); 340 | 341 | done(); 342 | }); 343 | }); 344 | }); 345 | 346 | authService.accessToken = null; 347 | }); 348 | -------------------------------------------------------------------------------- /test/oAuth1.spec.js: -------------------------------------------------------------------------------- 1 | import {Container} from 'aurelia-dependency-injection'; 2 | import {Config} from 'aurelia-api'; 3 | 4 | import {configure} from '../src/aurelia-authentication'; 5 | import {BaseConfig} from '../src/baseConfig'; 6 | import {Storage} from '../src/storage'; 7 | import {OAuth1} from '../src/oAuth1'; 8 | 9 | let noop = () => {}; 10 | 11 | function getContainer() { 12 | const container = new Container(); 13 | const config = container.get(Config); 14 | 15 | config 16 | .registerEndpoint('sx/default', 'http://localhost:1927/') 17 | .registerEndpoint('sx/custom', 'http://localhost:1927/custom') 18 | .setDefaultEndpoint('sx/default'); 19 | 20 | configure({container: container, globalResources: noop}, { 21 | endpoint: '', 22 | loginRedirect: false, 23 | logoutRedirect: false, 24 | signupRedirect: false, 25 | baseUrl: 'http://localhost:1927/' 26 | }); 27 | 28 | return container; 29 | } 30 | 31 | 32 | describe('OAuth1', () => { 33 | const container = getContainer(); 34 | const storage = container.get(Storage); 35 | const baseConfig = container.get(BaseConfig); 36 | const popup = { 37 | open: () => popup, 38 | eventListener: ()=>{}, 39 | pollPopup: () => Promise.resolve({access_token: 'someToken'}), 40 | popupWindow: {location: null} 41 | }; 42 | const oAuth1 = new OAuth1(storage, popup, baseConfig); 43 | 44 | describe('.exchangeForToken()', () => { 45 | it('fails with defaults', done => { 46 | oAuth1.exchangeForToken( 47 | {access_token: 'someToken'}, 48 | {userData: 'some'}, 49 | baseConfig.providers['twitter'] 50 | ).then(res => { 51 | expect(res).toBeUndefined(); 52 | expect(false).toBe(true); 53 | done(); 54 | }) 55 | .catch(err =>{ 56 | expect(err instanceof TypeError); 57 | done(); 58 | }); 59 | }); 60 | 61 | it('not fails with withCredentials = false', done => { 62 | baseConfig.withCredentials = false; 63 | oAuth1.exchangeForToken( 64 | {access_token: 'someToken'}, 65 | {userData: 'some'}, 66 | baseConfig.providers['twitter'] 67 | ).then(res => { 68 | expect(res).toBeDefined(); 69 | expect(res.path).toBe('/auth/twitter'); 70 | expect(res.body.access_token).toBe('someToken'); 71 | expect(res.body.userData).toBe('some'); 72 | 73 | done(); 74 | }).catch(err =>{ 75 | expect(err).toBeUndefined(); 76 | expect(false).toBe(true); 77 | done(); 78 | }); 79 | }); 80 | }); 81 | 82 | describe('.open()', () => { 83 | it('not fails with withCredentials = false', done => { 84 | baseConfig.withCredentials = false; 85 | oAuth1.open(baseConfig.providers['twitter'], {userData: 'some'}) 86 | .then(res=>{ 87 | expect(res).toBeDefined(); 88 | expect(res.path).toBe('/auth/twitter'); 89 | expect(res.body.access_token).toBe('someToken'); 90 | expect(res.body.userData).toBe('some'); 91 | expect(popup.popupWindow.location).toContain('https://api.twitter.com/oauth/authenticate'); 92 | 93 | done(); 94 | }) 95 | .catch(err =>{ 96 | expect(err).toBeUndefined(); 97 | expect(false).toBe(true); 98 | done(); 99 | }); 100 | }); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /test/oAuth2.spec.js: -------------------------------------------------------------------------------- 1 | import {Container} from 'aurelia-dependency-injection'; 2 | import {Config} from 'aurelia-api'; 3 | 4 | import {configure} from '../src/aurelia-authentication'; 5 | import {BaseConfig} from '../src/baseConfig'; 6 | import {Storage} from '../src/storage'; 7 | import {OAuth2} from '../src/oAuth2'; 8 | 9 | let noop = () => {}; 10 | 11 | function getContainer() { 12 | const container = new Container(); 13 | const config = container.get(Config); 14 | 15 | config 16 | .registerEndpoint('sx/default', 'http://localhost:1927/') 17 | .registerEndpoint('sx/custom', 'http://localhost:1927/custom') 18 | .setDefaultEndpoint('sx/default'); 19 | 20 | configure({container: container, globalResources: noop}, { 21 | endpoint: '', 22 | loginRedirect: false, 23 | logoutRedirect: false, 24 | signupRedirect: false, 25 | baseUrl: 'http://localhost:1927/' 26 | }); 27 | 28 | return container; 29 | } 30 | 31 | let oidcProviderConfig = { 32 | providers: { 33 | oidcProvider: { 34 | name: 'oidcProvider', 35 | postLogoutRedirectUri: 'http://localhost:1927/', 36 | logoutEndpoint: 'http://localhost:54540/connect/logout', 37 | popupOptions: { width: 1028, height: 529 } 38 | } 39 | } 40 | }; 41 | 42 | describe('OAuth2', () => { 43 | const container = getContainer(); 44 | const storage = container.get(Storage); 45 | const baseConfig = container.get(BaseConfig); 46 | let popup = { 47 | open: (url, name, popupOptions, redirectUri) => { 48 | popup.url = url; 49 | popup.name = name; 50 | popup.popupOptions = popupOptions; 51 | popup.redirectUri = redirectUri; 52 | return popup; 53 | }, 54 | eventListener: ()=>{}, 55 | pollPopup: () => Promise.resolve({access_token: 'someToken'}), 56 | popupWindow: {location: null} 57 | }; 58 | const oAuth2 = new OAuth2(storage, popup, baseConfig); 59 | 60 | describe('.exchangeForToken()', () => { 61 | it('fails with defaults', done => { 62 | oAuth2.exchangeForToken( 63 | {access_token: 'someToken'}, 64 | {userData: 'some'}, 65 | baseConfig.providers.facebook 66 | ).then(res => { 67 | expect(res).toBeUndefined(); 68 | expect(false).toBe(true); 69 | done(); 70 | }) 71 | .catch(err =>{ 72 | expect(err instanceof TypeError); 73 | done(); 74 | }); 75 | }); 76 | 77 | it('not fails with withCredentials = false', done => { 78 | baseConfig.withCredentials = false; 79 | oAuth2.exchangeForToken( 80 | {access_token: 'someToken'}, 81 | {userData: 'some'}, 82 | baseConfig.providers.facebook 83 | ).then(res => { 84 | expect(res).toBeDefined(); 85 | expect(res.path).toBe('/auth/facebook'); 86 | expect(res.body.access_token).toBe('someToken'); 87 | expect(res.body.userData).toBe('some'); 88 | 89 | done(); 90 | }).catch(err =>{ 91 | expect(err).toBeUndefined(); 92 | expect(false).toBe(true); 93 | done(); 94 | }); 95 | }); 96 | }); 97 | 98 | describe('.open()', () => { 99 | it('not fails with withCredentials = false', done => { 100 | baseConfig.withCredentials = false; 101 | oAuth2.open(baseConfig.providers.facebook, {userData: 'some'}) 102 | .then(res=>{ 103 | expect(res).toBeDefined(); 104 | expect(res.path).toBe('/auth/facebook'); 105 | expect(res.body.access_token).toBe('someToken'); 106 | expect(res.body.userData).toBe('some'); 107 | expect(popup.url).toBe('https://www.facebook.com/v2.5/dialog/oauth?display=popup&redirect_uri=http%3A%2F%2Flocalhost%3A9876%2F&response_type=code&scope=email'); 108 | 109 | done(); 110 | }) 111 | .catch(err =>{ 112 | expect(err).toBeUndefined(); 113 | expect(false).toBe(true); 114 | done(); 115 | }); 116 | }); 117 | }); 118 | 119 | describe('.buildQuery()', () => { 120 | it('return query', () => { 121 | const query = oAuth2.buildQuery(baseConfig.providers.facebook); 122 | expect(query.display).toBe('popup'); 123 | expect(query.scope).toBe('email'); 124 | }); 125 | }); 126 | 127 | describe('.close()', () => { 128 | it('logout popup url correct', done => { 129 | const expectedIdToken = 'Some Id Token'; 130 | const expectedLogoutRedirect = 'http://localhost:1927/'; 131 | const expectedState = '1234567890'; 132 | 133 | spyOn(storage, 'get').and.callFake((key) => { 134 | if (key === 'oidcProvider_state') { 135 | return expectedState; 136 | } 137 | if (key === oAuth2.config.storageKey) { 138 | return `{ "id_token": "${expectedIdToken}" }`; 139 | } 140 | }); 141 | oAuth2.close(oidcProviderConfig.providers.oidcProvider) 142 | .then(res => { 143 | const expectedUrl = `http://localhost:54540/connect/logout?id_token_hint=${encodeURIComponent(expectedIdToken)}&post_logout_redirect_uri=${encodeURIComponent(expectedLogoutRedirect)}&state=${encodeURIComponent(expectedState)}`; 144 | expect(popup.url).toBe(expectedUrl); 145 | done(); 146 | }); 147 | }); 148 | }); 149 | 150 | describe('.buildLogoutQuery()', () => { 151 | it('return query parameters', () => { 152 | spyOn(storage, 'get').and.callFake((key) => { 153 | if (key === 'oidcProvider_state') { 154 | return '123456789'; 155 | } 156 | if (key === oAuth2.config.storageKey) { 157 | return '{ "id_token": "IdTokenHere" }'; 158 | } 159 | }); 160 | const query = oAuth2.buildLogoutQuery(oidcProviderConfig.providers.oidcProvider); 161 | expect(query.post_logout_redirect_uri).toBe('http://localhost:1927/'); 162 | expect(query.state).toBe('123456789'); 163 | expect(query.id_token_hint).toBe('IdTokenHere'); 164 | }); 165 | }); 166 | }); 167 | -------------------------------------------------------------------------------- /test/popup.spec.js: -------------------------------------------------------------------------------- 1 | import {Popup} from '../src/popup'; 2 | 3 | describe('Popup', () => { 4 | const popup = new Popup(); 5 | 6 | describe('.open()', () => { 7 | it('fails with defaults', done => { 8 | popup.open('http://localhost:1927', 'windowName', {options: 'none'}); 9 | expect(popup.popupWindow.name, 'windowName'); 10 | popup.popupWindow.close(); 11 | done(); 12 | }); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | import {initialize} from 'aurelia-pal-browser'; 2 | import 'aurelia-polyfills'; 3 | import 'fetch'; 4 | 5 | initialize(); 6 | -------------------------------------------------------------------------------- /test/storage.spec.js: -------------------------------------------------------------------------------- 1 | import {Container} from 'aurelia-dependency-injection'; 2 | 3 | import {Storage} from '../src/storage'; 4 | import {BaseConfig} from '../src/baseConfig'; 5 | 6 | describe('Storage', () => { 7 | const container = new Container(); 8 | const storage = container.get(Storage); 9 | const baseConfig = container.get(BaseConfig); 10 | const defaultKey = 'localStorage'; 11 | 12 | afterEach(() => { 13 | baseConfig.storage = defaultKey; 14 | }); 15 | 16 | describe('.get', () => { 17 | it('with defaults', () => { 18 | window.localStorage.setItem('key', 'value'); 19 | 20 | expect(storage.get('key')).toBe('value'); 21 | }); 22 | 23 | it('with sessionStorage', () => { 24 | window.sessionStorage.setItem('key', 'value'); 25 | baseConfig.storage = 'sessionStorage'; 26 | 27 | expect(storage.get('key')).toBe('value'); 28 | }); 29 | }); 30 | 31 | describe('.set', () => { 32 | it('with defaults', () => { 33 | storage.set('key', 'value'); 34 | 35 | expect(window.localStorage.getItem('key')).toBe('value'); 36 | }); 37 | 38 | it('with sessionStorage', () => { 39 | baseConfig.storage = 'sessionStorage'; 40 | 41 | storage.set('key', 'value'); 42 | expect(window.sessionStorage.getItem('key')).toBe('value'); 43 | }); 44 | }); 45 | 46 | describe('.remove', () => { 47 | it('with defaults', () => { 48 | window.localStorage.setItem('key', 'value'); 49 | 50 | storage.remove('key'); 51 | expect(window.localStorage.getItem('key')).toBe(null); 52 | }); 53 | 54 | it('with sessionStorage', () => { 55 | window.sessionStorage.setItem('key', 'value'); 56 | baseConfig.storage = 'sessionStorage'; 57 | 58 | storage.remove('key', 'value'); 59 | expect(window.sessionStorage.getItem('key')).toBe(null); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aurelia-authentication", 3 | "main": "dist/aurelia-authentication.d.ts" 4 | } 5 | --------------------------------------------------------------------------------