├── addon
├── .gitkeep
└── services
│ └── ember-oauth2.js
├── tests
├── helpers
│ └── .gitkeep
├── unit
│ ├── .gitkeep
│ └── ember-oauth-test.js
├── integration
│ └── .gitkeep
├── dummy
│ ├── app
│ │ ├── helpers
│ │ │ └── .gitkeep
│ │ ├── models
│ │ │ └── .gitkeep
│ │ ├── routes
│ │ │ └── .gitkeep
│ │ ├── styles
│ │ │ └── app.css
│ │ ├── components
│ │ │ └── .gitkeep
│ │ ├── controllers
│ │ │ └── .gitkeep
│ │ ├── templates
│ │ │ └── application.hbs
│ │ ├── router.js
│ │ ├── app.js
│ │ └── index.html
│ ├── public
│ │ └── robots.txt
│ └── config
│ │ ├── optional-features.json
│ │ ├── ember-cli-update.json
│ │ ├── targets.js
│ │ └── environment.js
├── test-helper.js
└── index.html
├── .watchmanconfig
├── public
├── robots.txt
└── crossdomain.xml
├── .prettierrc.js
├── app
└── services
│ └── ember-oauth2.js
├── .template-lintrc.js
├── index.js
├── config
├── environment.js
└── ember-try.js
├── .ember-cli
├── .prettierignore
├── .eslintignore
├── yuidoc.json
├── .gitignore
├── .npmignore
├── testem.js
├── .editorconfig
├── CONTRIBUTING.md
├── ember-cli-build.js
├── LICENSE.md
├── .eslintrc.js
├── .github
└── workflows
│ └── ci.yml
├── package.json
├── CHANGELOG.md
└── README.md
/addon/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/helpers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/unit/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/integration/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/helpers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/models/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/routes/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/styles/app.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["tmp", "dist"]
3 | }
4 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | singleQuote: true,
5 | };
6 |
--------------------------------------------------------------------------------
/tests/dummy/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/app/services/ember-oauth2.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-oauth2/services/ember-oauth2';
2 |
--------------------------------------------------------------------------------
/.template-lintrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | extends: 'recommended',
5 | };
6 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | name: require('./package').name,
5 | };
6 |
--------------------------------------------------------------------------------
/config/environment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (/* environment, appConfig */) {
4 | return {};
5 | };
6 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/application.hbs:
--------------------------------------------------------------------------------
1 | {{page-title "Dummy"}}
2 |
3 |
Welcome to Ember
4 |
5 | {{outlet}}
--------------------------------------------------------------------------------
/tests/dummy/config/optional-features.json:
--------------------------------------------------------------------------------
1 | {
2 | "application-template-wrapper": false,
3 | "default-async-observers": true,
4 | "jquery-integration": false,
5 | "template-only-glimmer-components": true
6 | }
7 |
--------------------------------------------------------------------------------
/tests/dummy/app/router.js:
--------------------------------------------------------------------------------
1 | import EmberRouter from '@ember/routing/router';
2 | import config from 'dummy/config/environment';
3 |
4 | export default class Router extends EmberRouter {
5 | location = config.locationType;
6 | rootURL = config.rootURL;
7 | }
8 |
9 | Router.map(function () {});
10 |
--------------------------------------------------------------------------------
/.ember-cli:
--------------------------------------------------------------------------------
1 | {
2 | /**
3 | Ember CLI sends analytics information by default. The data is completely
4 | anonymous, but there are times when you might want to disable this behavior.
5 |
6 | Setting `disableAnalytics` to true will prevent any data from being sent.
7 | */
8 | "disableAnalytics": false
9 | }
10 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # unconventional js
2 | /blueprints/*/files/
3 | /vendor/
4 |
5 | # compiled output
6 | /dist/
7 | /tmp/
8 |
9 | # dependencies
10 | /bower_components/
11 | /node_modules/
12 |
13 | # misc
14 | /coverage/
15 | !.*
16 | .eslintcache
17 |
18 | # ember-try
19 | /.node_modules.ember-try/
20 | /bower.json.ember-try
21 | /package.json.ember-try
22 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # unconventional js
2 | /blueprints/*/files/
3 | /vendor/
4 |
5 | # compiled output
6 | /dist/
7 | /tmp/
8 |
9 | # dependencies
10 | /bower_components/
11 | /node_modules/
12 |
13 | # misc
14 | /coverage/
15 | !.*
16 | .*/
17 | .eslintcache
18 |
19 | # ember-try
20 | /.node_modules.ember-try/
21 | /bower.json.ember-try
22 | /package.json.ember-try
23 |
--------------------------------------------------------------------------------
/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import Application from 'dummy/app';
2 | import config from 'dummy/config/environment';
3 | import * as QUnit from 'qunit';
4 | import { setApplication } from '@ember/test-helpers';
5 | import { setup } from 'qunit-dom';
6 | import { start } from 'ember-qunit';
7 |
8 | setApplication(Application.create(config.APP));
9 |
10 | setup(QUnit.assert);
11 |
12 | start();
13 |
--------------------------------------------------------------------------------
/yuidoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Ember.OAuth2",
3 | "description": "OAuth2 library for Emberjs that stores tokens in the browsers localStorage",
4 | "version": "2.0.5-beta",
5 | "url": "https://github.com/amkirwan/ember-oauth2",
6 | "options": {
7 | "exclude": "node_modules,bower_components,scripts,tmp,vendor",
8 | "paths": "./addon/services/ember-oauth2.js",
9 | "outdir": "./doc"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tests/dummy/app/app.js:
--------------------------------------------------------------------------------
1 | import Application from '@ember/application';
2 | import Resolver from 'ember-resolver';
3 | import loadInitializers from 'ember-load-initializers';
4 | import config from 'dummy/config/environment';
5 |
6 | export default class App extends Application {
7 | modulePrefix = config.modulePrefix;
8 | podModulePrefix = config.podModulePrefix;
9 | Resolver = Resolver;
10 | }
11 |
12 | loadInitializers(App, config.modulePrefix);
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist/
5 | /tmp/
6 |
7 | # dependencies
8 | /bower_components/
9 | /node_modules/
10 |
11 | # misc
12 | /.env*
13 | /.pnp*
14 | /.sass-cache
15 | /.eslintcache
16 | /connect.lock
17 | /coverage/
18 | /libpeerconnection.log
19 | /npm-debug.log*
20 | /testem.log
21 | /yarn-error.log
22 |
23 | # ember-try
24 | /.node_modules.ember-try/
25 | /bower.json.ember-try
26 | /package.json.ember-try
27 |
--------------------------------------------------------------------------------
/tests/dummy/config/ember-cli-update.json:
--------------------------------------------------------------------------------
1 | {
2 | "schemaVersion": "1.0.0",
3 | "packages": [
4 | {
5 | "name": "ember-cli",
6 | "version": "3.28.6",
7 | "blueprints": [
8 | {
9 | "name": "addon",
10 | "outputRepo": "https://github.com/ember-cli/ember-addon-output",
11 | "codemodsSource": "ember-addon-codemods-manifest@1",
12 | "isBaseBlueprint": true,
13 | "options": [
14 | "--yarn",
15 | "--no-welcome"
16 | ]
17 | }
18 | ]
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist/
3 | /tmp/
4 |
5 | # dependencies
6 | /bower_components/
7 |
8 | # misc
9 | /.bowerrc
10 | /.editorconfig
11 | /.ember-cli
12 | /.env*
13 | /.eslintcache
14 | /.eslintignore
15 | /.eslintrc.js
16 | /.git/
17 | /.gitignore
18 | /.prettierignore
19 | /.prettierrc.js
20 | /.template-lintrc.js
21 | /.travis.yml
22 | /.watchmanconfig
23 | /bower.json
24 | /config/ember-try.js
25 | /CONTRIBUTING.md
26 | /ember-cli-build.js
27 | /testem.js
28 | /tests/
29 | /yarn-error.log
30 | /yarn.lock
31 | .gitkeep
32 |
33 | # ember-try
34 | /.node_modules.ember-try/
35 | /bower.json.ember-try
36 | /package.json.ember-try
37 |
--------------------------------------------------------------------------------
/public/crossdomain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/testem.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | test_page: 'tests/index.html?hidepassed',
5 | disable_watching: true,
6 | launch_in_ci: ['Chrome'],
7 | launch_in_dev: ['Chrome'],
8 | browser_start_timeout: 120,
9 | browser_args: {
10 | Chrome: {
11 | ci: [
12 | // --no-sandbox is needed when running Chrome inside a container
13 | process.env.CI ? '--no-sandbox' : null,
14 | '--headless',
15 | '--disable-dev-shm-usage',
16 | '--disable-software-rasterizer',
17 | '--mute-audio',
18 | '--remote-debugging-port=0',
19 | '--window-size=1440,900',
20 | ].filter(Boolean),
21 | },
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.js]
16 | indent_style = space
17 | indent_size = 2
18 |
19 | [*.hbs]
20 | insert_final_newline = false
21 | indent_style = space
22 | indent_size = 2
23 |
24 | [*.css]
25 | indent_style = space
26 | indent_size = 2
27 |
28 | [*.html]
29 | indent_style = space
30 | indent_size = 2
31 |
32 | [*.{diff,md}]
33 | trim_trailing_whitespace = false
34 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How To Contribute
2 |
3 | ## Installation
4 |
5 | * `git clone `
6 | * `cd ember-oauth2`
7 | * `yarn install`
8 |
9 | ## Linting
10 |
11 | * `yarn lint`
12 | * `yarn lint:fix`
13 |
14 | ## Running tests
15 |
16 | * `ember test` – Runs the test suite on the current Ember version
17 | * `ember test --server` – Runs the test suite in "watch mode"
18 | * `ember try:each` – Runs the test suite against multiple Ember versions
19 |
20 | ## Running the dummy application
21 |
22 | * `ember serve`
23 | * Visit the dummy application at [http://localhost:4200](http://localhost:4200).
24 |
25 | For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/).
26 |
--------------------------------------------------------------------------------
/tests/dummy/config/targets.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const browsers = [
4 | 'last 1 Chrome versions',
5 | 'last 1 Firefox versions',
6 | 'last 1 Safari versions',
7 | ];
8 |
9 | // Ember's browser support policy is changing, and IE11 support will end in
10 | // v4.0 onwards.
11 | //
12 | // See https://deprecations.emberjs.com/v3.x#toc_3-0-browser-support-policy
13 | //
14 | // If you need IE11 support on a version of Ember that still offers support
15 | // for it, uncomment the code block below.
16 | //
17 | // const isCI = Boolean(process.env.CI);
18 | // const isProduction = process.env.EMBER_ENV === 'production';
19 | //
20 | // if (isCI || isProduction) {
21 | // browsers.push('ie 11');
22 | // }
23 |
24 | module.exports = {
25 | browsers,
26 | };
27 |
--------------------------------------------------------------------------------
/tests/dummy/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy
7 |
8 |
9 |
10 | {{content-for "head"}}
11 |
12 |
13 |
14 |
15 | {{content-for "head-footer"}}
16 |
17 |
18 | {{content-for "body"}}
19 |
20 |
21 |
22 |
23 | {{content-for "body-footer"}}
24 |
25 |
26 |
--------------------------------------------------------------------------------
/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
4 |
5 | module.exports = function (defaults) {
6 | let app = new EmberAddon(defaults, {
7 | // Add options here
8 | });
9 |
10 | // Use `app.import` to add additional libraries to the generated
11 | // output files.
12 | //
13 | // If you need to use different assets in different
14 | // environments, specify an object as the first parameter. That
15 | // object's keys should be the environment name and the values
16 | // should be the asset to use in that environment.
17 | //
18 | // If the library that you are including contains AMD or ES6
19 | // modules that you would like to import into your application
20 | // please specify an object with the list of modules as keys
21 | // along with the exports of each module as its value.
22 |
23 | const { maybeEmbroider } = require('@embroider/test-setup');
24 | return maybeEmbroider(app, {
25 | skipBabel: [
26 | {
27 | package: 'qunit',
28 | },
29 | ],
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018
4 |
5 | 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:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | 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.
10 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | root: true,
5 | parser: 'babel-eslint',
6 | parserOptions: {
7 | ecmaVersion: 2018,
8 | sourceType: 'module',
9 | ecmaFeatures: {
10 | legacyDecorators: true,
11 | },
12 | },
13 | plugins: ['ember'],
14 | extends: [
15 | 'eslint:recommended',
16 | 'plugin:ember/recommended',
17 | 'plugin:prettier/recommended',
18 | ],
19 | env: {
20 | browser: true,
21 | },
22 | rules: {},
23 | overrides: [
24 | // node files
25 | {
26 | files: [
27 | './.eslintrc.js',
28 | './.prettierrc.js',
29 | './.template-lintrc.js',
30 | './ember-cli-build.js',
31 | './index.js',
32 | './testem.js',
33 | './blueprints/*/index.js',
34 | './config/**/*.js',
35 | './tests/dummy/config/**/*.js',
36 | ],
37 | parserOptions: {
38 | sourceType: 'script',
39 | },
40 | env: {
41 | browser: false,
42 | node: true,
43 | },
44 | plugins: ['node'],
45 | extends: ['plugin:node/recommended'],
46 | },
47 | {
48 | // Test files:
49 | files: ['tests/**/*-test.{js,ts}'],
50 | extends: ['plugin:qunit/recommended'],
51 | },
52 | ],
53 | };
54 |
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy Tests
7 |
8 |
9 |
10 | {{content-for "head"}}
11 | {{content-for "test-head"}}
12 |
13 |
14 |
15 |
16 |
17 | {{content-for "head-footer"}}
18 | {{content-for "test-head-footer"}}
19 |
20 |
21 | {{content-for "body"}}
22 | {{content-for "test-body"}}
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | {{content-for "body-footer"}}
38 | {{content-for "test-body-footer"}}
39 |
40 |
41 |
--------------------------------------------------------------------------------
/tests/dummy/config/environment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (environment) {
4 | let ENV = {
5 | modulePrefix: 'dummy',
6 | environment,
7 | rootURL: '/',
8 | locationType: 'auto',
9 | EmberENV: {
10 | FEATURES: {
11 | // Here you can enable experimental features on an ember canary build
12 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true
13 | },
14 | EXTEND_PROTOTYPES: {
15 | // Prevent Ember Data from overriding Date.parse.
16 | Date: false,
17 | },
18 | },
19 |
20 | APP: {
21 | // Here you can pass flags/options to your application instance
22 | // when it is created
23 | },
24 | };
25 |
26 | if (environment === 'development') {
27 | // ENV.APP.LOG_RESOLVER = true;
28 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
29 | // ENV.APP.LOG_TRANSITIONS = true;
30 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
31 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
32 | }
33 |
34 | if (environment === 'test') {
35 | // Testem prefers this...
36 | ENV.locationType = 'none';
37 |
38 | // keep test console output quieter
39 | ENV.APP.LOG_ACTIVE_GENERATION = false;
40 | ENV.APP.LOG_VIEW_LOOKUPS = false;
41 |
42 | ENV.APP.rootElement = '#ember-testing';
43 | ENV.APP.autoboot = false;
44 | }
45 |
46 | if (environment === 'production') {
47 | // here you can enable a production-specific feature
48 | }
49 |
50 | return ENV;
51 | };
52 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - master
8 | pull_request: {}
9 |
10 | concurrency:
11 | group: ci-${{ github.head_ref || github.ref }}
12 | cancel-in-progress: true
13 |
14 | jobs:
15 | test:
16 | name: 'Tests'
17 | runs-on: ubuntu-latest
18 | timeout-minutes: 10
19 |
20 | steps:
21 | - uses: actions/checkout@v3
22 | - name: Install Node
23 | uses: actions/setup-node@v3
24 | with:
25 | node-version: 18
26 | cache: yarn
27 | - name: Install Dependencies
28 | run: yarn install --frozen-lockfile
29 | - name: Lint
30 | run: yarn lint
31 | - name: Run Tests
32 | run: yarn test:ember
33 |
34 | floating:
35 | name: 'Floating Dependencies'
36 | runs-on: ubuntu-latest
37 | timeout-minutes: 10
38 |
39 | steps:
40 | - uses: actions/checkout@v3
41 | - uses: actions/setup-node@v3
42 | with:
43 | node-version: 18
44 | cache: yarn
45 | - name: Install Dependencies
46 | run: yarn install --no-lockfile
47 | - name: Run Tests
48 | run: yarn test:ember
49 |
50 | try-scenarios:
51 | name: ${{ matrix.try-scenario }}
52 | runs-on: ubuntu-latest
53 | needs: 'test'
54 | timeout-minutes: 10
55 |
56 | strategy:
57 | fail-fast: false
58 | matrix:
59 | try-scenario:
60 | - ember-lts-3.24
61 | - ember-lts-3.28
62 | - ember-release
63 | - ember-beta
64 | - ember-canary
65 | - embroider-safe
66 | - embroider-optimized
67 |
68 | steps:
69 | - uses: actions/checkout@v3
70 | - name: Install Node
71 | uses: actions/setup-node@v3
72 | with:
73 | node-version: 18
74 | cache: yarn
75 | - name: Install Dependencies
76 | run: yarn install --frozen-lockfile
77 | - name: Run Tests
78 | run: ./node_modules/.bin/ember try:one ${{ matrix.try-scenario }}
79 |
--------------------------------------------------------------------------------
/config/ember-try.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const getChannelURL = require('ember-source-channel-url');
4 | const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup');
5 |
6 | module.exports = async function () {
7 | return {
8 | useYarn: true,
9 | scenarios: [
10 | {
11 | name: 'ember-lts-3.24',
12 | npm: {
13 | devDependencies: {
14 | 'ember-source': '~3.24.3',
15 | },
16 | },
17 | },
18 | {
19 | name: 'ember-lts-3.28',
20 | npm: {
21 | devDependencies: {
22 | 'ember-source': '~3.28.0',
23 | },
24 | },
25 | },
26 | {
27 | name: 'ember-release',
28 | npm: {
29 | devDependencies: {
30 | 'ember-source': await getChannelURL('release'),
31 | },
32 | },
33 | },
34 | {
35 | name: 'ember-beta',
36 | npm: {
37 | devDependencies: {
38 | 'ember-source': await getChannelURL('beta'),
39 | },
40 | },
41 | },
42 | {
43 | name: 'ember-canary',
44 | npm: {
45 | devDependencies: {
46 | 'ember-source': await getChannelURL('canary'),
47 | },
48 | },
49 | },
50 | {
51 | name: 'ember-default-with-jquery',
52 | env: {
53 | EMBER_OPTIONAL_FEATURES: JSON.stringify({
54 | 'jquery-integration': true,
55 | }),
56 | },
57 | npm: {
58 | devDependencies: {
59 | '@ember/jquery': '^1.1.0',
60 | },
61 | },
62 | },
63 | {
64 | name: 'ember-classic',
65 | env: {
66 | EMBER_OPTIONAL_FEATURES: JSON.stringify({
67 | 'application-template-wrapper': true,
68 | 'default-async-observers': false,
69 | 'template-only-glimmer-components': false,
70 | }),
71 | },
72 | npm: {
73 | devDependencies: {
74 | 'ember-source': '~3.28.0',
75 | },
76 | ember: {
77 | edition: 'classic',
78 | },
79 | },
80 | },
81 | embroiderSafe(),
82 | embroiderOptimized(),
83 | ],
84 | };
85 | };
86 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-oauth2",
3 | "version": "2.0.5-beta",
4 | "description": "OAuth2 library for Emberjs that stores tokens in the browsers localStorage",
5 | "keywords": [
6 | "ember-addon",
7 | "ember-oauth2",
8 | "oauth2",
9 | "oauth2.0",
10 | "ember",
11 | "ember.js",
12 | "emberjs"
13 | ],
14 | "repository": {
15 | "type": "git",
16 | "url": "git://github.com/amkirwan/ember-oauth2.git"
17 | },
18 | "license": "MIT",
19 | "author": "Anthony Kirwan",
20 | "directories": {
21 | "doc": "doc",
22 | "test": "tests"
23 | },
24 | "scripts": {
25 | "build": "ember build --environment=production",
26 | "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"",
27 | "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix",
28 | "lint:hbs": "ember-template-lint .",
29 | "lint:hbs:fix": "ember-template-lint . --fix",
30 | "lint:js": "eslint . --cache",
31 | "lint:js:fix": "eslint . --fix",
32 | "start": "ember serve",
33 | "test": "npm-run-all lint test:*",
34 | "test:ember": "ember test",
35 | "test:ember-compatibility": "ember try:each"
36 | },
37 | "dependencies": {
38 | "ember-cli-babel": "^7.26.10"
39 | },
40 | "devDependencies": {
41 | "@ember/optional-features": "^2.0.0",
42 | "@ember/string": "^3.1.1",
43 | "@ember/test-helpers": "^2.6.0",
44 | "@embroider/test-setup": "^0.48.1",
45 | "@glimmer/component": "^1.0.4",
46 | "@glimmer/tracking": "^1.0.4",
47 | "babel-eslint": "^10.1.0",
48 | "broccoli-asset-rev": "^3.0.0",
49 | "ember-auto-import": "^2.7.2",
50 | "ember-cli": "~3.28.6",
51 | "ember-cli-dependency-checker": "^3.2.0",
52 | "ember-cli-htmlbars": "^5.7.2",
53 | "ember-cli-inject-live-reload": "^2.1.0",
54 | "ember-cli-sri": "^2.1.1",
55 | "ember-cli-terser": "^4.0.2",
56 | "ember-load-initializers": "^2.1.2",
57 | "ember-maybe-import-regenerator": "^0.1.6",
58 | "ember-page-title": "^6.2.2",
59 | "ember-qunit": "^5.1.5",
60 | "ember-resolver": "^8.0.3",
61 | "ember-sinon": "^2.2.0",
62 | "ember-source": "~3.28.8",
63 | "ember-source-channel-url": "^3.0.0",
64 | "ember-template-lint": "^3.15.0",
65 | "ember-try": "^1.4.0",
66 | "eslint": "^7.32.0",
67 | "eslint-config-prettier": "^8.3.0",
68 | "eslint-plugin-ember": "^10.5.8",
69 | "eslint-plugin-node": "^11.1.0",
70 | "eslint-plugin-prettier": "^3.4.1",
71 | "eslint-plugin-qunit": "^6.2.0",
72 | "loader.js": "^4.7.0",
73 | "npm-run-all": "^4.1.5",
74 | "prettier": "^2.5.1",
75 | "qunit": "^2.17.2",
76 | "qunit-dom": "^1.6.0",
77 | "webpack": "^5.89.0"
78 | },
79 | "engines": {
80 | "node": "12.* || 14.* || >= 16"
81 | },
82 | "homepage": "https://github.com/amkirwan/ember-ouath2",
83 | "ember": {
84 | "edition": "octane"
85 | },
86 | "ember-addon": {
87 | "configPath": "tests/dummy/config"
88 | },
89 | "bugs": {
90 | "url": "https://github.com/amkirwan/ember-oauth2/issues"
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog Ember-OAuth2
2 |
3 | ## v2.0.5-beta
4 | (Full Changelog)[https://github.com/amkirwan/ember-oauth2/compare/v2.0.4-beta...v2.0.5-beta]
5 | - Update ember-cli
6 | - Use ES6
7 | - Update Tests
8 | - cleanup
9 |
10 | ## v2.0.4-beta
11 | (Full Changelog)[https://github.com/amkirwan/ember-oauth2/compare/v2.0.3-beta...v2.0.4-beta]
12 | - Move ember-cli-babel to dev dependencies
13 |
14 | ## v2.0.3-beta
15 | (Full Changelog)[https://github.com/amkirwan/ember-oauth2/compare/v2.0.2-beta...v2.0.3-beta]
16 | - fix typo in scope
17 |
18 | ## v2.0.2-beta
19 | (Full Changelog)[https://github.com/amkirwan/ember-oauth2/compare/v2.0.1-beta...v2.0.2-beta]
20 |
21 | - add setProvider method
22 | - change init to not take providerId
23 |
24 | ## v2.0.1-beta
25 | (Full Changelog)[https://github.com/amkirwan/ember-oauth2/compare/v2.0.0-beta...v2.0.1-beta]
26 |
27 | - Update NPM author info
28 |
29 | ## v2.0.0-beta
30 | (Full Changelog)[https://github.com/amkirwan/ember-oauth2/compare/v1.1.0...v2.0.0-beta]
31 |
32 | - Converted to EmberAddon and turned EmberOAuth2 into a service
33 | - Updated testing to use Ember-Qunit
34 | - Update project README
35 |
36 | ## v1.1.0
37 | - Add verifyToken method to handle mitigation of the confused deputy
38 | - fix bug with checking of state
39 |
40 | ## v1.0.1
41 | - Update getState function to not take a param argument and use the configure statePrefix to find the state from localStorage.
42 | - This makes getState and getToken perform the same way and avoids confusion with the api
43 | - Fix bug where handleRedircect could not find the stateObj
44 |
45 | ## v1.0.0
46 | - Drop support for deprecated callback methods onSuccess, onError, and onRedirect. Callbacks can be called using Ember.Evented trigger with the targets success, error, and redirect.
47 | ## v0.7.0
48 | - Add support Authorization Grant Flow
49 | ## v0.6.0
50 | - Remove global distribution
51 | - Bump project bower and node dependencies
52 | - Bump Ember to ~0.13.0
53 |
54 | ## v0.5.5
55 | - Provide funtion to remove the token from localstorage
56 | - Provide funtion to remove the state from localstorage
57 | - Funtion to get the stateKeyName and tokenKeyName
58 |
59 | ## v0.5.4
60 | - Added support for versions of Ember >= 1.7
61 | ## v0.5.3
62 | - Can now use window.EmberENV for config along with window.ENV.
63 | - Fixed issue where mini files had wrong version header.
64 | - Better error reporting when configuration file is not formatted correctly.
65 | ## v0.5.2
66 | - Update Ember dependeny to 1.7
67 | - Moved bower install dependences from vendor to bower_components
68 | - Add dist dir to repo for bower installs
69 | ## v0.5.1
70 | - Fixed bug where the random UUID for the state was not being set.
71 | - openWindow resolves with a reference to the dialog window and rejects
72 | with an error if the dialog window fails to open.
73 | ## v0.5.0
74 | - Update library to use ES6 module
75 | - Export to both Global and AMD module
76 | - Use window.ENV['ember-oauth2'] for config
77 | - OpenWindow now returns reference to dialog on resolve, on reject object with reference to dialog and error.
78 | ## v0.4.0
79 | - Isolates function for opening window so that it can be overridden
80 | - Login dialog window returns a promise, it resolves on success and reject on error
81 | ## v0.3.2
82 | - Remove files not needed in package.
83 | - Add build files to dist dir for package manaagers.
84 | ## v0.3.1
85 | - Initial relase to Bower
86 | ## v0.3.0
87 | - Added YUIDoc to src.
88 | - Published to NPM.
89 | - Using get and set for properties on the instances.
90 | ## v0.2.4
91 | - Change to using Ember.Evented class for handling 'redirct', 'success' and 'error' callback.
92 | - Depricated old callback methods.
93 | ## v0.2.3
94 | - Using grunt-versioner for updating and building project
95 | - Fixed incorrect path to jQuery when build from source
96 | ## v0.2.2 (Jan 11, 2014)
97 | - [a132c65](https://github.com/amkirwan/ember-oauth2/commit/a132c657ae0a5173fc78ab192c6db11e4074232c) updated patch version to v0.2.2
98 | - [ffd5069](https://github.com/amkirwan/ember-oauth2/commit/ffd50691721e96091e3642c1ecc871d66c2f48f8) grunt-bump commit changes
99 | - [a051d44](https://github.com/amkirwan/ember-oauth2/commit/a051d44a15c3e27fbcafe07e5fee43695e4fd68c) config bump, added custom task to update readme version
100 | - [aabf4e0](https://github.com/amkirwan/ember-oauth2/commit/aabf4e055d1cec84a904033fdb4889283544f32d) added grunt bump
101 |
102 | ## v0.2.1 (Jan 9, 2014)
103 | - [b925f2e](https://github.com/amkirwan/ember-oauth2/commit/b925f2ea303930785227c424ecba5f7c772275a8) version bump to v0.2.1 added dist dir for bower and node
104 | - [0d5e7ed](https://github.com/amkirwan/ember-oauth2/commit/0d5e7eddfe5483853476def213caa999354a09dc) do not ignore dist dir
105 | - [ea17257](https://github.com/amkirwan/ember-oauth2/commit/ea172578b6dbb6ab13b22583de3368fd7a5aae95) moved ember to dependencies
106 | - [7d5e752](https://github.com/amkirwan/ember-oauth2/commit/7d5e75227aa304c77385f24ff4e66408c58d1498) Update README.md
107 |
108 | ## v0.2.0 (Jan 9, 2014)
109 | - [9769bf1](https://github.com/amkirwan/ember-oauth2/commit/9769bf1daae3c9035b03a27b7ceabda4e53b6874) version bump v0.2.0
110 | - [1305ae8](https://github.com/amkirwan/ember-oauth2/commit/1305ae8504eff1961e4c09d10611e9ce2dbdf4a2) updated Gruntfile to new directory layout structure
111 | - [c00930f](https://github.com/amkirwan/ember-oauth2/commit/c00930f58ed8e384071320a04b1fb87091b1a041) moved lib and spec to src
112 | - [114d85d](https://github.com/amkirwan/ember-oauth2/commit/114d85d729bb28056d624b2df94cc88013ec3973) renamed packages dir to src
113 |
114 | ## v.0.1.0 (Jan 7, 2014)
115 | - [8914512](https://github.com/amkirwan/ember-oauth2/commit/8914512d4cffb9c0de8f1a2455948569846d291a) version bump
116 | - [a3cb3e2](https://github.com/amkirwan/ember-oauth2/commit/a3cb3e289a9fda3930e6f90c103aa010af41a1c0) fixed jshint warnings
117 | - [b2c4aea](https://github.com/amkirwan/ember-oauth2/commit/b2c4aea31e4f80153d7a7217a50fd1a1caf244ee) added missing var
118 | - [f1befdf](https://github.com/amkirwan/ember-oauth2/commit/f1befdfb5040c757090e48826bbf0c936dc34b5c) removed dist directory
119 | - [ba25b1e](https://github.com/amkirwan/ember-oauth2/commit/ba25b1e79f9eae2d3e992633189821392987bf7b) remove dist js files
120 | - [8de0dc3](https://github.com/amkirwan/ember-oauth2/commit/8de0dc3f6cb2d93a0a16752e9306b18e998a906b) adjusted readme.md
121 | - [c4e5829](https://github.com/amkirwan/ember-oauth2/commit/c4e58292a43190fd6e9b7af7d1b3bde900e7776f) travis should run tests using grunt task
122 | - [17eb7f7](https://github.com/amkirwan/ember-oauth2/commit/17eb7f71a275c94b3053d334f77004fbf6ef03b4) fixed ember-oauth2.png image
123 |
124 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/amkirwan/ember-oauth2/actions/workflows/ci.yml)
2 |
3 | ember-oauth2
4 | ============
5 |
6 | JavaScript library for using OAuth 2.0 Implicit Grant flow (Client-Side Flow) or Authorization Grant flow (Server-Side Flow) for Ember.js
7 |
8 | This creates an OAuth 2.0 Ember object class for handling authentication with OAuth 2.0 providers.
9 |
10 | Current Version: **[2.0.5-beta](https://github.com/amkirwan/ember-oauth2/releases/tag/v2.0.5-beta)**
11 |
12 | The EmberCli addon [EmberTokenAuth](https://github.com/amkirwan/ember-token-auth) demonstrates how to use Ember-OAuth2 library for authentication.
13 |
14 | ## Dependencies
15 |
16 | Ember-OAuth2 requires Ember and jQuery.
17 |
18 |
19 | ## Browser Support
20 |
21 | Ember-OAuth2 uses localStorage for saving the tokens, localStorage is supported in Firefox 3.5+, Safari 4+, IE9+, and Chrome.
22 |
23 | The latest version of Ember-OAuth2 is an Ember Addon and uses the ES6 modules. This allows Ember-OAuth2 to be used in projects like [EmberCLI](https://github.com/stefanpenner/ember-cli) easier.
24 |
25 |
26 | ## Installation
27 |
28 | Ember-OAuth2 is an Ember Addon that can be installed with the following command from your ember project.
29 |
30 | ```bash
31 | $ ember install ember-oauth2
32 | ```
33 |
34 | Ember-OAuth2 is an Ember [service](https://guides.emberjs.com/v2.8.0/applications/services/) that you can inject to different parts of your app using the inject syntax
35 |
36 | ```js
37 | import Controller from '@ember/controller';
38 | import { inject as service } from '@ember/service';
39 |
40 | export default class ApplicationController extends Controller {
41 | @service emberOauth2;
42 | }
43 | ```
44 |
45 |
46 | ## Configure
47 |
48 | First you must configure your OAuth provider. For Google you would configure it like this.
49 |
50 | ```js
51 | window.EmberENV['ember-oauth2'] = {
52 | google: {
53 | clientId: 'xxxxxxxxxxxx',
54 | authBaseUri: 'https://accounts.google.com/o/oauth2/auth',
55 | redirectUri: 'https://oauth2-login-demo.appspot.com/oauth/callback',
56 | scope: 'public write'
57 | }
58 | }
59 | ```
60 |
61 | If using ember-cli, you can add the configuration to `config/environment.js`:
62 |
63 | ```js
64 | EmberENV: {
65 | FEATURES: {
66 | // Here you can enable experimental features on an ember canary build
67 | // e.g. 'with-controller': true
68 | },
69 | 'ember-oauth2': {
70 | google: {
71 | clientId: 'xxxxxxxxxxxx',
72 | authBaseUri: 'https://accounts.google.com/o/oauth2/auth',
73 | redirectUri: 'https://oauth2-login-demo.appspot.com/oauth/callback',
74 | scope: 'public write'
75 | }
76 | }
77 | }
78 | ```
79 |
80 | The example above sets *google* as a *providerId* along with configuration information for the provider. The following params are required for configuring a valid provider *clientId*, *authBaseUri* and *redirectUri*. Depending on the provider you might need to provide additional and/or optional configuration key/values.
81 |
82 | The configuration object allows you to also customize the prefix for the state and token that are stored in the browsers localStorage. The default value for the state prefix is *state* and the default for token is *token*. Using the previous example you can customize the prefixes by doing the following.
83 |
84 | ```js
85 | window.ENV['ember-oauth2'] = {
86 | google: {
87 | clientId: 'xxxxxxxxxxxx',
88 | authBaseUri: 'https://accounts.google.com/o/oauth2/auth',
89 | redirectUri: 'https://oauth2-login-demo.appspot.com/oauth/callback',
90 | scope: 'public write',
91 | statePrefix: 'foobar',
92 | tokenPrefix: 'qux'
93 | }
94 | }
95 | ```
96 |
97 | The following are the options available for configuring a provider:
98 |
99 | * `clientId`: (required) The client identifier that is used by the provider. Ember-OAuth2 uses the Implicit Grant flow (Client-Side Flow).
100 | * `authBaseUri`: (required) The authorization url for the OAuth2 provider.
101 | * `redirectUri`: (required) The URI that the OAuth2 provider will redirect back to when completed.
102 | * `scope`: Access your application is requesting from the OAuth2 provider.
103 | * `responseType`: The type of authorization your application is requesting. The default is `token` but can be set to `code` if using the Authorization Grant flow.
104 | * `statePrefix`: The prefix name for state key stored in the localStorage. The default value is `state` and the key would be `state-the_state_number`
105 | * `tokenPrefix`: The prefix name for token key stored in the localStorage. The default value is `token` and the key would be `token-the_provider_id`
106 |
107 |
108 |
109 | ## Authorization
110 |
111 | To sign into the OAuth2 provider create by injecting the service, set the provider with `setProvider` and call the `authorize`. You can inject this addon into your controller for example and when the user clicks a button fire the action to handle the request and set the service providerId and call authorize. This is a simple example and you would probably want to wrap this functionality in a session model. Checkout [ember-token-auth](https://github.com/amkirwan/ember-token-auth) for a full example.
112 |
113 | ```js
114 | // login route
115 | import Controller from '@ember/controller';
116 | import { inject as service } from '@ember/service';
117 | import { action } from '@ember/object';
118 |
119 | export default class LoginController extends Controller {
120 | @service emberOauth2;
121 |
122 | @action
123 | async authenticate(providerId) {
124 | this.emberOauth2.setProvider(providerId);
125 |
126 | try {
127 | let response = await this.emberOauth2.authorize();
128 | this.emberOauth2.trigger('redirect', response.location.hash);
129 | } catch (error) {
130 | this.emberOauth2.trigger('error', error);
131 | }
132 | }
133 | }
134 | ```
135 |
136 | Calling `authorize()` will open a new window and the OAuth provider's OAuth dialog will be displayed. If the user chooses to authenticate with your website upon authorization by OAuth provider the user will be redirected back to the redirectUri with the params access_token, token_type and state.
137 |
138 | At the redirectURI add the following to process the params returned from the OAuth provider
139 |
140 | ```html
141 |
142 |
143 |
144 | Authorize
145 |
150 |
151 |
152 | ```
153 |
154 |
155 |
156 |
157 | ## Implicit Grant Flow (Client-side flow)
158 |
159 | This will process the returned params and save the `provider_id`, `access_token`, `scope` and `expires_in` (the time the access_token will expire) to the localStorage. This localStorage can be accessed with the key `token-the_provider_id`.
160 |
161 |
162 | After successful authorization and saving the access_token to the localStorage the `success` event will be called. This will allow the user to do any cleanup necessary or to retrieve user information from the OAuth provider. To configure the callback bind event handlers to the `success` and `error` events.
163 |
164 | The `authorize` call returns a `Ember.RSVP.Promise`. Authorize will `resolve` with a reference to the dialog window when it opens successfully and `rejects` with an error when the window fails to open.
165 |
166 | })
167 |
168 | When using the client-side flow it is vital to validate the token received from the endpoint, failure to do so will make your application vulnerable to the [confused deputy problem](https://en.wikipedia.org/wiki/Confused_deputy_problem). As of version `v1.0.2` Ember-OAuth2 supports the `verifyToken` method for validating tokens when using the client-side flow. The user will need to override this method for validating the different server endpoints.
169 |
170 | Here is an example of how this might be accomplished in an Ember-CLI instance initializer using the Google token validation endpoint.
171 |
172 | ```js
173 | import Ember from 'ember';
174 | import EmberOAuth2 from 'ember-oauth2';
175 | import env from 'ember-pacu/config/environment';
176 |
177 | export function initialize(app) {
178 | verifyTokenInit(app);
179 | }
180 |
181 | function verifyTokenInit(app) {
182 | EmberOAuth2.reopen({
183 | // mitigate confused deputy
184 | verifyToken: function() {
185 | return new Ember.RSVP.Promise((resolve, reject) => {
186 | // implement the adapter with the url to the google tokeinfo endpoint
187 | var adapter = app.lookup('adapter:session');
188 | adapter.google_tokeninfo().then(function(response) {
189 | if (response.audience === env.APP.GOOGLE_CLIENT_ID) {
190 | resolve(response);
191 | } else {
192 | reject('app uid does not match');
193 | }
194 | }, function(error) {
195 | reject(error);
196 | });
197 | });
198 | }
199 | });
200 | }
201 |
202 | export default {
203 | name: 'ember-oauth2',
204 | initialize: initialize
205 | };
206 | ```
207 |
208 |
209 |
210 |
211 |
212 | ## Authorization Grant flow
213 |
214 | If using the Authorization Grant flow with your provider your backend server will need to handle the final steps of authorizing your application. Your success handler will need to send the `AUTHORIZATON_CODE` returned from OAuth2 provider to your backend server which can then retrieve an access token using the client_id, client_secret, and authorization_code.
215 |
216 | To enable the Authorization Grant flow for a provider set the `responseType` value to `code`.
217 |
218 | ```js
219 | window.ENV = window.ENV || {};
220 | window.ENV['ember-oauth2'] = {
221 | google: {
222 | clientId: 'xxxxxxxxxxxx',
223 | authBaseUri: 'https://accounts.google.com/o/oauth2/auth',
224 | redirectUri: 'https://oauth2-login-demo.appspot.com/oauth/callback',
225 | responseType: 'code'
226 | }
227 | }
228 | ```
229 |
230 | To build Ember.Oauth2 on your system you will need to have [Node.js](http://nodejs.org), and [npm](https://npmjs.org) installed.
231 |
232 | ```bash
233 | $ git clone https://github.com/amkirwan/ember-oauth2
234 | $ cd ember-oauth2
235 | $ npm install
236 | $ bower install
237 | ```
238 |
239 | ## Tests
240 |
241 | To run the tests you can run one of the following commands.
242 |
243 | ```bash
244 | $ ember test
245 | $ ember test --serve
246 | $ npm test
247 | ```
248 |
249 | ## Building API Docs
250 |
251 | The API Docs provide a detailed collection of methods and properties for Ember.OAuth2. To build the documentation for the project from the project directory run the following command.
252 |
253 | Requires node.js and yuidocjs to build. Follow the steps in [build](https://github.com/amkirwan/ember-oauth2#building) to install the dependencies before buiding the docs.
254 |
255 | ```bash
256 | $ yuidoc .
257 | ```
258 |
259 |
260 | ## Contributors
261 |
262 | [Contributors](https://github.com/amkirwan/ember-oauth2/graphs/contributors) to this project.
263 |
264 |
265 | ## Credits
266 |
267 | #### Thanks to the following projects for ideas on how to make this work.
268 |
269 | * [backbone-oauth](http://github.com/ptnplanet/backbone-oauth)
270 |
--------------------------------------------------------------------------------
/addon/services/ember-oauth2.js:
--------------------------------------------------------------------------------
1 | import { Promise as EmberPromise } from 'rsvp';
2 | import Evented, { on } from '@ember/object/evented';
3 | import Service from '@ember/service';
4 | import { warn } from '@ember/debug';
5 |
6 | /**
7 | * @overview OAuth2 addon for Emberjs that stores tokens in the browsers localStorage
8 | * @license Licensed under MIT license
9 | * See https://raw.github.com/amkirwan/ember-oauth2/master/LICENSE
10 | * @version 2.0.5-beta
11 | *
12 | * @module ember-oauth2
13 | * @class ember-oauth2
14 | */
15 | // eslint-disable-next-line ember/no-classic-classes
16 | export default Service.extend(Evented, {
17 | VERSION: '2.0.5-beta',
18 | /**
19 | * initialize with the providerId to find in
20 | * EmberENV['ember-oauth2'] config
21 | */
22 | init() {
23 | this._super(...arguments);
24 | if (!window.EmberENV['ember-oauth2']) {
25 | window.EmberENV['ember-oauth2'] = {};
26 | }
27 | this.set('config', window.EmberENV['ember-oauth2']);
28 |
29 | this.set('statePrefix', 'state');
30 | this.set('tokenPrefix', 'token');
31 | this.set('responseType', 'token');
32 | },
33 |
34 | /**
35 | * Set the provider for the ember-oauth2 service with the providerId configured
36 | * in EmberENV['ember-oauth2'].
37 | *
38 | * @method setProvider
39 | * @param {String} providerId the provider Id configured in EmberENV['ember-oauth2']
40 | */
41 | setProvider(providerId) {
42 | this.set('providerId', providerId);
43 | // if the provider id doesn't exist in the config throw an error
44 | if (!this.config[this.providerId]) {
45 | throw new Error(
46 | `Cannot find the providerId: ${this.providerId} in the config.`
47 | );
48 | } else {
49 | this.set('providerConfig', this.config[this.providerId]);
50 | this.setProperties(this.providerConfig);
51 | return this;
52 | }
53 | },
54 |
55 | /**
56 | * Open authorize window if the configuration object is valid.
57 | *
58 | * @method authorize
59 | * @return {Promise}
60 | */
61 | authorize() {
62 | if (!this.providerId) {
63 | throw new Error('No provider id given.');
64 | }
65 | if (!this.clientId) {
66 | throw new Error('No client id given.');
67 | }
68 | if (!this.authBaseUri) {
69 | throw new Error('No auth base uri given.');
70 | }
71 | if (!this.redirectUri) {
72 | throw new Error('No redirect uri given.');
73 | }
74 | this.clearStates();
75 | this.saveState(this.requestObj());
76 | return this.openWindow(this.authUri());
77 | },
78 |
79 | /**
80 | * Isolated function responsible for opening windows, to make it
81 | * easier to override this part in some environments (e.g. Phonegap)
82 |
83 | @param {String} url
84 | @return {Object} On resolve returns reference to the opened window.
85 | On reject returns Object with reference to dialog and error.
86 | */
87 | openWindow(url, windowWidth = 600, windowHeight = 800) {
88 | const windowLeft = window.screen.width / 2 - windowWidth / 2;
89 | const windowTop = window.screen.height / 2 - windowHeight / 2;
90 | const options = `menubar, width=${windowWidth}, height=${windowHeight}, top=${windowTop}, left=${windowLeft}`;
91 | const dialog = window.open(url, 'Authorize', options);
92 | if (window.focus && dialog) {
93 | dialog.focus();
94 | }
95 | return new EmberPromise(function (resolve, reject) {
96 | if (dialog) {
97 | resolve(dialog);
98 | } else {
99 | reject(new Error('Opening dialog login window failed.'));
100 | }
101 | });
102 | },
103 |
104 | /**
105 | *
106 | * Check if the token returned is valid and if so trigger `success` event else trigger `error`
107 | *
108 | * @method handleRedirect
109 | * @param {Object} hash The window location hash callback url
110 | * @param {Function} callback Optional callback
111 | */
112 |
113 | handleRedirect: on('redirect', function (hash, callback) {
114 | const self = this;
115 | const params = self.parseCallback(hash);
116 |
117 | if (self.authSuccess(params) && self.checkState(params.state)) {
118 | if (self.get('responseType') === 'token') {
119 | self.saveToken(self.generateToken(params));
120 | // verify the token on the client end
121 | self.verifyToken().then(
122 | function () {
123 | self.trigger('success');
124 | },
125 | function () {
126 | self.removeToken();
127 | self.trigger('error', 'Error: verifying token', params);
128 | }
129 | );
130 | } else {
131 | self.trigger('success', params.code);
132 | }
133 | } else {
134 | self.trigger('error', 'Error: authorization', params);
135 | }
136 |
137 | if (callback && typeof callback === 'function') {
138 | callback();
139 | }
140 | }),
141 |
142 | /**
143 | @method authSuccess
144 | @param {Object} The params returned from the OAuth2 callback
145 | @return {Boolean} True if success false otherwise
146 | */
147 | authSuccess(params) {
148 | return (
149 | (this.responseType === 'token' && params.access_token) ||
150 | (this.responseType === 'code' && params.code)
151 | );
152 | },
153 |
154 | /**
155 | * The key name to use for saving the token to localstorage
156 | *
157 | * @method tokenKeyName
158 | * @return {String} The token key name used for localstorage
159 | */
160 | tokenKeyName() {
161 | return this.tokenPrefix + '-' + this.providerId;
162 | },
163 |
164 | /**
165 | * saveToken stores the token by the tokenPrefix and the providerId
166 | * access_token
167 | * expires : time that the token expires
168 | * providerId: the providerId
169 | * scopes: array of scopes
170 | *
171 | * @method saveToken
172 | * @param {Object} token Saves the params in the response from the OAuth2 server to localStorage with the key 'tokenPrefix-providerId
173 | */
174 | saveToken(token) {
175 | window.localStorage.setItem(this.tokenKeyName(), JSON.stringify(token));
176 | return window.localStorage.getItem(this.tokenKeyName());
177 | },
178 |
179 | /**
180 | * Token properties
181 | * providerId
182 | * expiresIn
183 | * scope
184 | * token
185 |
186 | @method generateToken
187 | @return {Object} The access_token object with info about the token
188 | */
189 | generateToken(params) {
190 | const token = {};
191 | token.provider_id = this.providerId;
192 | token.expires_in = this.expiresIn(params.expires_in);
193 | token.scope = this.scope;
194 | token.access_token = params.access_token;
195 | return token;
196 | },
197 |
198 | /**
199 | * For Client-side flow verify the token with the endpoint. Mitigation for confused deputy.
200 | * This method should be replaced by the app using this library.
201 | *
202 | * @method verifyToken
203 | * @return {Promise} Checks with the endpoint if the token is valid
204 | */
205 | verifyToken() {
206 | return EmberPromise.resolve(true);
207 | },
208 |
209 | /**
210 | * Checks if the State returned from the server matches the state that was generated in the original request and saved in the browsers localStorage.
211 | *
212 | * @method checkState
213 | * @param {String} state The state to check
214 | * @return {Boolean} Will return true if the states false if they do not match
215 | */
216 | checkState(state) {
217 | if (!state) {
218 | return false;
219 | }
220 | // check the state returned with state saved in localstorage
221 | if (state === this.readState().state) {
222 | this.removeState(this.stateKeyName());
223 | return true;
224 | } else {
225 | warn(
226 | 'State returned from the server did not match the local saved state.',
227 | false,
228 | {
229 | id: 'ember-oauth2.invalid-state',
230 | }
231 | );
232 | return false;
233 | }
234 | },
235 |
236 | /**
237 | * Parse the callback function from the OAuth2 provider
238 | *
239 | * callback should have the following params if authentication is successful
240 | * state
241 | * access_token or code
242 | * token_type
243 | * expires_in
244 | *
245 | * @method parseCalback
246 | * @param {String} locationHash
247 | * @return {Object} The params returned from the OAuth2 provider
248 | */
249 | parseCallback(locationHash) {
250 | const oauthParams = {};
251 | const queryString = locationHash.substring(locationHash.indexOf('?'));
252 | const regex = /([^#?&=]+)=([^&]*)/g;
253 | let match;
254 | while ((match = regex.exec(queryString)) !== null) {
255 | oauthParams[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
256 | }
257 | return oauthParams;
258 | },
259 |
260 | /**
261 | * @method authUri
262 | * @return {String} Authorization uri for generating an OAuth2 token
263 | */
264 | authUri() {
265 | let uri = this.authBaseUri;
266 | uri +=
267 | '?response_type=' +
268 | encodeURIComponent(this.responseType) +
269 | '&redirect_uri=' +
270 | encodeURIComponent(this.redirectUri) +
271 | '&client_id=' +
272 | encodeURIComponent(this.clientId) +
273 | '&state=' +
274 | encodeURIComponent(this.state);
275 | if (this.scope) {
276 | uri += '&scope=' + encodeURIComponent(this.scope).replace('%20', '+');
277 | }
278 | return uri;
279 | },
280 |
281 | /**
282 | * Creates and returns the request object.
283 | *
284 | * @method requestObj
285 | * @return {Object} request object
286 | */
287 | requestObj() {
288 | const request = {};
289 | request.response_type = this.responseType;
290 | request.providerId = this.providerId;
291 | request.clientId = this.clientId;
292 | request.state = this.generateState();
293 | if (this.scope) {
294 | request.scope = this.scope;
295 | }
296 | return request;
297 | },
298 |
299 | /**
300 | * @method saveState
301 | * @param {Object} requestObj Properties of the request state to save in localStorage
302 | */
303 | saveState(requestObj) {
304 | window.localStorage.setItem(
305 | this.stateKeyName(),
306 | JSON.stringify(requestObj)
307 | );
308 | },
309 |
310 | /**
311 | * Remove any states from localStorage if they exist
312 | * @method clearStates
313 | * @return {Array} Keys used to remove states from localStorage
314 | */
315 | clearStates() {
316 | const regex = new RegExp('^' + this.statePrefix + '-.*', 'g');
317 |
318 | let name;
319 | const toRemove = [];
320 | for (let i = 0, l = window.localStorage.length; i < l; i++) {
321 | name = window.localStorage.key(i);
322 | if (name.match(regex)) {
323 | toRemove.push(name);
324 | }
325 | }
326 |
327 | for (let j = 0, len = toRemove.length; j < len; j++) {
328 | name = toRemove[j];
329 | this.removeState(name);
330 | }
331 | return toRemove;
332 | },
333 |
334 | /**
335 | * remove the state from localstorage
336 | *
337 | * @method removeState
338 | * @param {String} stateName The keyname of the state object in localstorage
339 | * @return {Object} The deleted state object from localstorage
340 | */
341 | removeState(stateName) {
342 | if (stateName) {
343 | return window.localStorage.removeItem(stateName);
344 | } else {
345 | return window.localStorage.removeItem(this.stateKeyName());
346 | }
347 | },
348 |
349 | /**
350 | * Return the saved state object from localStoage.
351 | *
352 | * @method getState
353 | * @return {Object} Properties of the request state
354 | */
355 | readState() {
356 | const stateObj = JSON.parse(
357 | window.localStorage.getItem(this.stateKeyName())
358 | );
359 | if (!stateObj) {
360 | return false;
361 | }
362 |
363 | return stateObj;
364 | },
365 |
366 | /**
367 | * @method uuid
368 | * @return {String} A pseudo random uuid
369 | */
370 | uuid() {
371 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
372 | /[xy]/g,
373 | function (c) {
374 | const r = (Math.random() * 16) | 0,
375 | v = c === 'x' ? r : (r & 0x3) | 0x8;
376 | return v.toString(16);
377 | }
378 | );
379 | },
380 |
381 | /**
382 | * @method now
383 | * @return {Number} The current time in seconds rounded
384 | */
385 | now() {
386 | return Math.round(new Date().getTime() / 1000.0);
387 | },
388 |
389 | /**
390 | * The key name to use for saving state to localstorage
391 | *
392 | * @method stateKeyName
393 | * @return {String} The state key name used for localstorage
394 | */
395 | stateKeyName() {
396 | if (!this.state) {
397 | this.generateState();
398 | }
399 | return this.statePrefix + '-' + this.state;
400 | },
401 |
402 | /**
403 | * @method generateState
404 | * @return {String} The state
405 | */
406 | generateState(clear = false) {
407 | if (!this.state || clear === true) {
408 | this.set('state', this.uuid());
409 | }
410 | return this.state;
411 | },
412 |
413 | /**
414 | * @method getToken
415 | * @return {Object} The params from the OAuth2 response from localStorage with the key 'tokenPrefix-providerId'.
416 | */
417 | getToken() {
418 | const token = JSON.parse(window.localStorage.getItem(this.tokenKeyName()));
419 | if (!token) {
420 | return null;
421 | }
422 | if (!token.access_token) {
423 | return null;
424 | }
425 | return token;
426 | },
427 |
428 | /**
429 | * @method getAccessToken
430 | * @return {Object} The access_token param from the OAuth2 response from localStorage with the key 'tokenPrefix-providerId'.
431 | */
432 | getAccessToken() {
433 | const token = this.getToken();
434 | if (!token) {
435 | return null;
436 | }
437 | return token.access_token;
438 | },
439 |
440 | /**
441 | * remove the token from localstorage
442 | *
443 | * @method removeToken
444 | * @return {Object} The token object in localstorage
445 | */
446 | removeToken() {
447 | return window.localStorage.removeItem(this.tokenKeyName());
448 | },
449 |
450 | /**
451 | * @method expiresIn
452 | * @param {String} expires lifetime left of token in seconds
453 | * @return {Number} When the token expires in seconds.
454 | */
455 | expiresIn(expires) {
456 | return this.now() + parseInt(expires, 10);
457 | },
458 |
459 | /**
460 | * @method accessTokenIsExpired
461 | * @return {Boolean} Check if the access_token is expired.
462 | */
463 | accessTokenIsExpired() {
464 | const token = this.getToken();
465 | if (!token) {
466 | return true;
467 | }
468 | if (this.now() >= token.expires_in) {
469 | return true;
470 | } else {
471 | return false;
472 | }
473 | },
474 |
475 | /**
476 | * Sets the access token expires_in time to 0 and saves the token to localStorage
477 | * @method expireAccessToken
478 | */
479 | expireAccessToken() {
480 | const token = this.getToken();
481 | if (!token) {
482 | return null;
483 | }
484 | token.expires_in = 0;
485 | this.saveToken(token);
486 | },
487 | });
488 |
--------------------------------------------------------------------------------
/tests/unit/ember-oauth-test.js:
--------------------------------------------------------------------------------
1 | import { reject } from 'rsvp';
2 | import { module, test } from 'qunit';
3 | import { setupTest } from 'ember-qunit';
4 | import sinon from 'sinon';
5 |
6 | let service, responseType, clientId, authBaseUri, redirectUri, scopes;
7 |
8 | module('Unit | Service | EmberOAuth2', function (hooks) {
9 | setupTest(hooks);
10 |
11 | hooks.beforeEach(function () {
12 | responseType = 'token';
13 | clientId = 'abcd';
14 | authBaseUri = 'https://foobar.dev/oauth/authorize';
15 | redirectUri = 'https://qux.dev/oauth/authorize/callback';
16 | scopes = 'public';
17 | // would be defined in the initializer of the app
18 | window.EmberENV['ember-oauth2'] = {
19 | model: 'user',
20 | test_auth: {
21 | clientId: clientId,
22 | authBaseUri: authBaseUri,
23 | redirectUri: redirectUri,
24 | scope: scopes,
25 | },
26 | };
27 | service = this.owner.lookup('service:ember-oauth2');
28 | service.setProvider('test_auth');
29 | });
30 |
31 | test('EmberENV defined', function (assert) {
32 | assert.ok(window.EmberENV);
33 | });
34 |
35 | test('adds ember-oauth2 object to EmberENV', function (assert) {
36 | assert.expect(2);
37 | assert.ok(window.EmberENV['ember-oauth2']);
38 | assert.equal(window.EmberENV['ember-oauth2'], service.get('config'));
39 | });
40 |
41 | test('base properties token configuration', function (assert) {
42 | assert.ok(service.get('statePrefix'));
43 | assert.ok(service.get('tokenPrefix'));
44 | assert.ok(service.get('responseType'));
45 | });
46 |
47 | test('#setProvider configures the provider from the providerId in the ember-oauth2 config', function (assert) {
48 | service.setProvider('test_auth');
49 | assert.expect(5);
50 | assert.equal(service.get('providerId'), 'test_auth');
51 | assert.deepEqual(
52 | service.get('providerConfig'),
53 | window.EmberENV['ember-oauth2']['test_auth']
54 | );
55 | // sets the properties from the providerConfig
56 | assert.equal(service.get('clientId'), clientId);
57 | assert.equal(service.get('authBaseUri'), authBaseUri);
58 | assert.equal(service.get('redirectUri'), redirectUri);
59 | });
60 |
61 | test('#setProvider providerId does not exists in ember-oauth2 config', function (assert) {
62 | assert.throws(function () {
63 | service.setProvider('qux');
64 | }, /Cannot find the providerId: qux in the config./);
65 | });
66 |
67 | test('it returns the version', function (assert) {
68 | assert.ok(service.VERSION);
69 | });
70 |
71 | test('#uuid returns a version 4 formatted uuid', function (assert) {
72 | let re = /[\d\w]{8}-[\d\w]{4}-4[\d\w]{3}-[\d\w]{4}-[\d\w]{12}/;
73 | assert.ok(re.test(service.uuid()));
74 | });
75 |
76 | test('#now returns the time rounded to the closest second', function (assert) {
77 | let stub = sinon.stub(Date.prototype, 'getTime').callsFake(() => '1000');
78 | assert.equal(service.now(), 1);
79 | stub.reset();
80 | });
81 |
82 | // tests #stateKeyName
83 | test('#statKeyName calls generateState if state empty', function (assert) {
84 | let spy = sinon.spy(service, 'generateState');
85 | service.stateKeyName();
86 | assert.ok(spy.calledOnce);
87 | });
88 |
89 | test('#statKeyName returns the name for saving state to localstorage', function (assert) {
90 | service.set('state', '12345');
91 | assert.equal(service.stateKeyName(), 'state-12345');
92 | });
93 |
94 | // tests #generateState
95 | test('#generateState creates a new state', function (assert) {
96 | assert.expect(3);
97 | let spy = sinon.spy(service, 'uuid');
98 | assert.notOk(service.get('state'));
99 | service.generateState();
100 | assert.ok(service.get('state'));
101 | assert.ok(spy.calledOnce);
102 | });
103 |
104 | // #expiresIn
105 | test('#expiresIn returns when the token will expires', function (assert) {
106 | let stub = sinon.stub(service, 'now').callsFake(() => 1000);
107 | assert.equal(service.expiresIn('3600'), 4600);
108 | stub.reset();
109 | });
110 |
111 | test('#saveState', function (assert) {
112 | assert.expect(2);
113 | let spy = sinon.spy(service, 'stateKeyName');
114 | let obj = { foo: 'bar' };
115 | service.saveState(obj);
116 | assert.ok(spy.calledOnce);
117 | assert.equal(
118 | window.localStorage.getItem(service.stateKeyName()),
119 | JSON.stringify(obj)
120 | );
121 | });
122 |
123 | test('#removeState', function (assert) {
124 | assert.expect(4);
125 |
126 | window.localStorage.setItem('foobar', {});
127 | assert.ok(window.localStorage.getItem('foobar'));
128 | service.removeState('foobar');
129 | assert.notOk(window.localStorage.getItem('foobar'));
130 |
131 | // without stateName use saved stateKeyName;
132 | let obj = { foo: 'bar' };
133 | service.saveState(obj);
134 | assert.ok(window.localStorage.getItem(service.stateKeyName()));
135 | service.removeState();
136 | assert.notOk(window.localStorage.getItem(service.stateKeyName()));
137 | });
138 |
139 | // clearStates all states with prefix
140 | test('remove any saved states with prefix', function (assert) {
141 | assert.expect(2);
142 | service = this.owner.lookup('service:ember-oauth2');
143 | service.setProvider('test_auth');
144 | let obj = { foo: 'bar' };
145 |
146 | service.saveState(obj);
147 | assert.equal(
148 | window.localStorage.getItem(service.stateKeyName()),
149 | JSON.stringify(obj)
150 | );
151 |
152 | service.clearStates();
153 | let count = 0;
154 | let regex = new RegExp('^' + service.get('statePrefix') + '-.*', 'g');
155 | for (let i = 0, l = window.localStorage.length; i < l; i++) {
156 | let name = window.localStorage.key(i);
157 | if (name.match(regex)) {
158 | count += 1;
159 | }
160 | }
161 | assert.equal(count, 0);
162 | });
163 |
164 | // requestObj
165 | test('#requestObj', function (assert) {
166 | let obj = service.requestObj();
167 |
168 | assert.equal(obj.response_type, 'token');
169 | assert.equal(obj.providerId, 'test_auth');
170 | assert.equal(obj.clientId, 'abcd');
171 | assert.equal(obj.state, service.get('state'));
172 | assert.equal(obj.scope, 'public');
173 | });
174 |
175 | test('#authUri generates the authorization uri', function (assert) {
176 | let uri = service.get('authBaseUri');
177 | uri +=
178 | '?response_type=' +
179 | encodeURIComponent(responseType) +
180 | '&redirect_uri=' +
181 | encodeURIComponent(redirectUri) +
182 | '&client_id=' +
183 | encodeURIComponent(clientId) +
184 | '&state=' +
185 | encodeURIComponent(service.get('state')) +
186 | '&scope=' +
187 | encodeURIComponent(scopes);
188 |
189 | assert.equal(service.authUri(), uri);
190 | });
191 |
192 | // #authorize
193 | test('#authorize success', function (assert) {
194 | let spyClearState = sinon.spy(service, 'clearStates');
195 | let spySaveState = sinon.spy(service, 'saveState');
196 | let spyOpenWindow = sinon.spy(service, 'openWindow');
197 | let prom = service.authorize();
198 | assert.strictEqual(prom.constructor.name, 'Promise');
199 | assert.ok(spyClearState.calledOnce);
200 | assert.ok(spySaveState.calledOnce);
201 | assert.ok(spyOpenWindow.calledOnce);
202 | // close the popup window
203 | prom.then(function (win) {
204 | win.close();
205 | });
206 | });
207 |
208 | // #authorize config errors
209 | test('should require a providerId in the config', function (assert) {
210 | service.set('providerId', null);
211 | assert.throws(function () {
212 | service.authorize();
213 | }, /No provider id given./);
214 | });
215 |
216 | test('should require a clientId in the config', function (assert) {
217 | service.set('clientId', null);
218 | assert.throws(function () {
219 | service.authorize();
220 | }, /No client id given./);
221 | });
222 |
223 | test('should require an authBaseUri in the config', function (assert) {
224 | service.set('authBaseUri', null);
225 | assert.throws(function () {
226 | service.authorize();
227 | }, /No auth base uri given./);
228 | });
229 |
230 | test('should require a redirectUri in the config', function (assert) {
231 | service.set('redirectUri', null);
232 | assert.throws(function () {
233 | service.authorize();
234 | }, /No redirect uri given./);
235 | });
236 |
237 | test('should error when dialog does not open', function (assert) {
238 | assert.expect(1);
239 | var stub = sinon.stub(window, 'open').returns(false);
240 |
241 | let prom = service.authorize();
242 | prom.then(
243 | function () {},
244 | function (error) {
245 | assert.equal(error.message, 'Opening dialog login window failed.');
246 | }
247 | );
248 | stub.reset();
249 | });
250 |
251 | // parse callback
252 | test('#parseCallback', function (assert) {
253 | let callbackUri = redirectUri;
254 | let state = service.generateState();
255 | callbackUri +=
256 | '#access_token=' +
257 | '12345abc' +
258 | '&token_type=' +
259 | 'Bearer' +
260 | '&expires_in=' +
261 | '3600' +
262 | '&state=' +
263 | state;
264 |
265 | assert.deepEqual(service.parseCallback(callbackUri), {
266 | access_token: '12345abc',
267 | token_type: 'Bearer',
268 | expires_in: '3600',
269 | state: state,
270 | });
271 | });
272 |
273 | test('#authSuccess', function (assert) {
274 | assert.expect(3);
275 | let params = { access_token: '12345abc' };
276 | assert.ok(service.authSuccess(params));
277 |
278 | service.set('responseType', 'code');
279 | params = { code: 'abcdefg' };
280 | assert.ok(service.authSuccess(params));
281 |
282 | // response and type do not match
283 | params = { access_token: '12345abc' };
284 | service.set('responseType', 'code');
285 | assert.notOk(service.authSuccess(params));
286 | });
287 |
288 | test('#checkState', function (assert) {
289 | assert.expect(3);
290 | assert.notOk(service.checkState());
291 |
292 | let state = '12345abcd';
293 | service.generateState();
294 | assert.notOk(service.checkState(state));
295 |
296 | state = service.generateState();
297 | service.saveState(service.requestObj());
298 | assert.ok(service.checkState(state));
299 | });
300 |
301 | test('#readState', function (assert) {
302 | assert.notOk(service.readState());
303 |
304 | let data = { foo: 'bar' };
305 | service.generateState();
306 | service.saveState(data);
307 | assert.deepEqual(service.readState(), data);
308 | });
309 |
310 | test('#generateToken should generate the token that will be saved to the localStorage', function (assert) {
311 | let stub = sinon.stub(service, 'expiresIn').callsFake(() => 1000);
312 | let params = { expires_in: 1000, scope: scopes, access_token: 'abcd12345' };
313 | let token = {
314 | provider_id: 'test_auth',
315 | expires_in: 1000,
316 | scope: scopes,
317 | access_token: 'abcd12345',
318 | };
319 |
320 | assert.deepEqual(service.generateToken(params), token);
321 | stub.reset();
322 | });
323 |
324 | test('#tokenKeyName returns tokenPrefx with providerId', function (assert) {
325 | // should return token-google
326 | assert.equal(service.tokenKeyName(), 'token-test_auth');
327 | });
328 |
329 | test('#saveToken should generated the token localStorage', function (assert) {
330 | let token = {
331 | provider_id: 'test_auth',
332 | expires_in: 1000,
333 | scope: scopes,
334 | access_token: 'abcd12345',
335 | };
336 | assert.deepEqual(
337 | service.saveToken(token),
338 | window.localStorage.getItem('token-test_auth')
339 | );
340 | });
341 |
342 | // handle redirect
343 | // success Implicit client-side flow
344 | test('#handleRedirect - success', function (assert) {
345 | let spy = sinon.spy(service, 'handleRedirect');
346 | let triggerSpy = sinon.spy(service, 'trigger');
347 |
348 | // create stubbed callback return
349 | let callbackUri = redirectUri;
350 | let state = service.generateState();
351 | service.saveState(service.requestObj({}));
352 | callbackUri +=
353 | '#access_token=' +
354 | '12345abc' +
355 | '&token_type=' +
356 | 'Bearer' +
357 | '&expires_in=' +
358 | '3600' +
359 | '&state=' +
360 | state;
361 |
362 | let parsed = {
363 | access_token: '12345abc',
364 | token_type: 'Bearer',
365 | expires_in: '3600',
366 | state: state,
367 | };
368 | let stub = sinon.stub(service, 'parseCallback').callsFake(() => parsed);
369 |
370 | service.trigger('redirect', callbackUri);
371 | assert.ok(spy.calledOnce);
372 | assert.ok(triggerSpy.withArgs('success', parsed));
373 | stub.reset();
374 | });
375 |
376 | // failure Implicit client-side flow
377 | // verifyToken failure
378 | test('#handleRedirect - verifyToken failure', function (assert) {
379 | let spy = sinon.spy(service, 'handleRedirect');
380 | let triggerSpy = sinon.spy(service, 'trigger');
381 | let verifyStub = sinon
382 | .stub(service, 'verifyToken')
383 | .callsFake(() => new reject('error'));
384 |
385 | // create stubbed callback return
386 | let callbackUri = redirectUri;
387 | let state = service.generateState();
388 | service.saveState(service.requestObj({}));
389 | callbackUri +=
390 | '#access_token=' +
391 | '12345abc' +
392 | '&token_type=' +
393 | 'Bearer' +
394 | '&expires_in=' +
395 | '3600' +
396 | '&state=' +
397 | state;
398 |
399 | let parsed = {
400 | access_token: '12345abc',
401 | token_type: 'Bearer',
402 | expires_in: '3600',
403 | state: state,
404 | };
405 | let stub = sinon.stub(service, 'parseCallback').callsFake(() => parsed);
406 |
407 | service.trigger('redirect', callbackUri);
408 | assert.ok(spy.calledOnce);
409 | assert.ok(triggerSpy.withArgs('error', 'Error: authorization', parsed));
410 | stub.reset();
411 | verifyStub.reset();
412 | });
413 |
414 | // failure Implicit client-side flow
415 | // state does not match failure
416 | test('#handleRedirect - failure state does not match', function (assert) {
417 | let spy = sinon.spy(service, 'handleRedirect');
418 | let triggerSpy = sinon.spy(service, 'trigger');
419 |
420 | // create stubbed callback return
421 | let callbackUri = redirectUri;
422 | let state = service.generateState();
423 | service.saveState(service.requestObj({}));
424 | callbackUri +=
425 | '#access_token=' +
426 | '12345abc' +
427 | '&token_type=' +
428 | 'Bearer' +
429 | '&expires_in=' +
430 | '3600' +
431 | '&state=' +
432 | '12345';
433 |
434 | let parsed = {
435 | access_token: '12345abc',
436 | token_type: 'Bearer',
437 | expires_in: '3600',
438 | state: state,
439 | };
440 | let stub = sinon.stub(service, 'parseCallback').callsFake(() => parsed);
441 |
442 | service.trigger('redirect', callbackUri);
443 | assert.ok(spy.calledOnce);
444 | assert.ok(triggerSpy.withArgs('error', 'Error: authorization', parsed));
445 | stub.reset();
446 | });
447 |
448 | // failure Implicit client-side flow
449 | // responseType is 'token' but response of the
450 | // callbackUri is 'code' instead of 'token'
451 | test('#handleRedirect - tokenType is incorrect', function (assert) {
452 | let spy = sinon.spy(service, 'handleRedirect');
453 | let triggerSpy = sinon.spy(service, 'trigger');
454 |
455 | // create stubbed callback return
456 | let callbackUri = redirectUri;
457 | let state = service.generateState();
458 | service.saveState(service.requestObj({}));
459 | callbackUri +=
460 | '#code=' +
461 | '12345abc' +
462 | '&token_type=' +
463 | 'Bearer' +
464 | '&expires_in=' +
465 | '3600' +
466 | '&state=' +
467 | state;
468 |
469 | let parsed = {
470 | code: '12345abc',
471 | token_type: 'Bearer',
472 | expires_in: '3600',
473 | state: state,
474 | };
475 | let stub = sinon.stub(service, 'parseCallback').callsFake(() => parsed);
476 |
477 | service.trigger('redirect', callbackUri);
478 | assert.ok(spy.calledOnce);
479 | assert.ok(triggerSpy.withArgs('error', 'Error: authorization', parsed));
480 | stub.reset();
481 | });
482 |
483 | // success authorization flow
484 | test('#handleRedirect - success authorization flow', function (assert) {
485 | service = this.owner
486 | .factoryFor('service:ember-oauth2')
487 | .create({ providerId: 'test_auth', responseType: 'code' });
488 | let spy = sinon.spy(service, 'handleRedirect');
489 | let triggerSpy = sinon.spy(service, 'trigger');
490 |
491 | // create stubbed callback return
492 | let callbackUri = redirectUri;
493 | let state = service.generateState();
494 | service.saveState(service.requestObj({}));
495 | callbackUri +=
496 | '#code=' +
497 | '12345abc' +
498 | '&token_type=' +
499 | 'Bearer' +
500 | '&expires_in=' +
501 | '3600' +
502 | '&state=' +
503 | state;
504 |
505 | let parsed = {
506 | code: '12345abc',
507 | token_type: 'Bearer',
508 | expires_in: '3600',
509 | state: state,
510 | };
511 | let stub = sinon.stub(service, 'parseCallback').callsFake(() => parsed);
512 |
513 | service.trigger('redirect', callbackUri);
514 | assert.ok(spy.calledOnce);
515 | assert.ok(triggerSpy.withArgs('success', parsed.code));
516 | stub.reset();
517 | });
518 |
519 | test('#getToken should return the token from localStorage', function (assert) {
520 | assert.expect(3);
521 | let invalidToken = { foo: 'bar' };
522 | let validToken = { access_token: 'abcd', foo: 'bar' };
523 | window.localStorage.removeItem(service.tokenKeyName());
524 | assert.notOk(service.getToken());
525 |
526 | service.saveToken(invalidToken);
527 | assert.notOk(service.getToken());
528 |
529 | service.saveToken(validToken);
530 | assert.deepEqual(service.getToken(), validToken);
531 | });
532 |
533 | test('#getAccessToken should return the access_token from the localStorage', function (assert) {
534 | assert.expect(2);
535 | let token = { access_token: 'abcd', foo: 'bar' };
536 | window.localStorage.removeItem(service.tokenKeyName());
537 | assert.notOk(service.getAccessToken());
538 |
539 | service.saveToken(token);
540 | assert.deepEqual(service.getAccessToken(), token.access_token);
541 | });
542 |
543 | test('#accessTokenIsExpired', function (assert) {
544 | assert.expect(3);
545 | let expiredToken = { access_token: 'abcd', foo: 'bar', expires_in: 3600 };
546 | let validToken = { access_token: 'abcd', foo: 'bar', expires_in: 3600 };
547 | window.localStorage.removeItem(service.tokenKeyName());
548 | let stub = sinon.stub(service, 'now');
549 | stub.onCall(0).returns(4200);
550 | stub.onCall(1).returns(1);
551 | // no tokens
552 | assert.ok(service.accessTokenIsExpired());
553 |
554 | service.saveToken(expiredToken);
555 | assert.ok(service.accessTokenIsExpired());
556 |
557 | service.saveToken(validToken);
558 | assert.notOk(service.accessTokenIsExpired());
559 | stub.reset();
560 | });
561 |
562 | test('#expiresIn', function (assert) {
563 | let stub = sinon.stub(service, 'now').callsFake(() => 1000);
564 |
565 | assert.equal(service.expiresIn(3600), 4600);
566 | stub.reset();
567 | });
568 |
569 | test('#removeToken', function (assert) {
570 | assert.expect(2);
571 | window.localStorage.removeItem(service.tokenKeyName());
572 | let token = { access_token: 'abcd', foo: 'bar' };
573 | service.saveToken(token);
574 | assert.equal(
575 | window.localStorage.getItem(service.tokenKeyName()),
576 | JSON.stringify(token)
577 | );
578 | service.removeToken();
579 | assert.equal(
580 | window.localStorage.getItem(service.tokenKeyName()),
581 | undefined
582 | );
583 | });
584 | });
585 |
--------------------------------------------------------------------------------