├── .editorconfig ├── .ember-cli ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .npmrc ├── .template-lintrc.js ├── .travis.yml ├── .watchmanconfig ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── addon-test-support ├── index.js ├── mock-keycloak-instance.js └── mock-keycloak-session.js ├── addon ├── components │ ├── keycloak-session-config.hbs │ ├── keycloak-session-config.ts │ ├── keycloak-session-link.hbs │ ├── keycloak-session-link.ts │ ├── keycloak-session-profile.hbs │ ├── keycloak-session-profile.ts │ ├── keycloak-session-status.hbs │ └── keycloak-session-status.ts ├── helpers │ ├── in-role.ts │ └── not-in-role.ts └── services │ └── keycloak-session.ts ├── app ├── components │ ├── keycloak-session-config.js │ ├── keycloak-session-link.js │ ├── keycloak-session-profile.js │ └── keycloak-session-status.js ├── helpers │ ├── in-role.js │ └── not-in-role.js └── services │ └── keycloak-session.js ├── blueprints └── ember-keycloak-auth │ └── index.js ├── config ├── addon-docs.js ├── deploy.js ├── ember-try.js └── environment.js ├── ember-cli-build.js ├── index.js ├── package.json ├── testem.js ├── tests ├── .eslintrc.js ├── dummy │ ├── app │ │ ├── adapters │ │ │ ├── application.ts │ │ │ └── json-api-adapter-snippet.js │ │ ├── app.js │ │ ├── components │ │ │ ├── demo-configuration.hbs │ │ │ └── demo-configuration.js │ │ ├── config │ │ │ └── environment.d.ts │ │ ├── index.html │ │ ├── models │ │ │ ├── model-a.js │ │ │ └── model-b.js │ │ ├── pods │ │ │ ├── application │ │ │ │ └── template.hbs │ │ │ ├── config │ │ │ │ └── template.hbs │ │ │ ├── demo-error │ │ │ │ └── template.hbs │ │ │ ├── demo │ │ │ │ ├── controller.js │ │ │ │ ├── logged-in │ │ │ │ │ └── template.hbs │ │ │ │ ├── logged-out │ │ │ │ │ └── template.hbs │ │ │ │ ├── model-a │ │ │ │ │ ├── index │ │ │ │ │ │ └── template.hbs │ │ │ │ │ ├── model-b │ │ │ │ │ │ ├── route.js │ │ │ │ │ │ └── template.hbs │ │ │ │ │ └── route.js │ │ │ │ ├── route-snippet.js │ │ │ │ ├── route.js │ │ │ │ ├── status │ │ │ │ │ └── template.hbs │ │ │ │ ├── template.hbs │ │ │ │ └── unprotected │ │ │ │ │ ├── route.js │ │ │ │ │ └── template.hbs │ │ │ ├── docs │ │ │ │ ├── helpers │ │ │ │ │ └── template.md │ │ │ │ ├── index │ │ │ │ │ └── template.md │ │ │ │ ├── template.hbs │ │ │ │ └── test-support │ │ │ │ │ └── template.md │ │ │ └── index │ │ │ │ └── template.hbs │ │ ├── router.js │ │ ├── serializers │ │ │ ├── application.js │ │ │ ├── class.js │ │ │ ├── component.js │ │ │ └── project.js │ │ ├── styles │ │ │ └── app.css │ │ └── templates │ │ │ └── .gitkeep │ ├── config │ │ ├── environment.js │ │ ├── optional-features.json │ │ └── targets.js │ ├── mirage │ │ ├── config.js │ │ ├── scenarios │ │ │ └── default.js │ │ └── serializers │ │ │ └── application.js │ └── public │ │ ├── crossdomain.xml │ │ └── robots.txt ├── helpers │ ├── destroy-app.js │ ├── module-for-acceptance.js │ ├── resolver.js │ └── start-app.js ├── index.html ├── integration │ ├── components │ │ ├── keycloak-session-config-test.js │ │ ├── keycloak-session-link-test.js │ │ ├── keycloak-session-profile-test.js │ │ └── keycloak-session-status-test.js │ └── helpers │ │ ├── in-role-test.js │ │ └── not-in-role-test.js ├── test-helper.js └── unit │ └── services │ ├── keycloak-session-test.js │ └── mock-keycloak-session-test.js ├── tsconfig.json ├── types ├── dummy │ └── index.d.ts ├── ember-data │ └── types │ │ └── registries │ │ └── model.d.ts └── global.d.ts ├── vendor └── .gitkeep └── yarn.lock /.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 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | # ember-try 18 | /.node_modules.ember-try/ 19 | /bower.json.ember-try 20 | /package.json.ember-try 21 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | ecmaVersion: 2018, 6 | sourceType: 'module', 7 | ecmaFeatures: { 8 | legacyDecorators: true 9 | } 10 | }, 11 | plugins: [ 12 | 'ember' 13 | ], 14 | extends: [ 15 | 'eslint:recommended', 16 | 'plugin:ember/recommended' 17 | ], 18 | env: { 19 | browser: true 20 | }, 21 | rules: { 22 | 'ember/no-jquery': 'error' 23 | }, 24 | overrides: [ 25 | // node files 26 | { 27 | files: [ 28 | '.eslintrc.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 | excludedFiles: [ 38 | 'addon/**', 39 | 'addon-test-support/**', 40 | 'app/**', 41 | 'tests/dummy/app/**' 42 | ], 43 | parserOptions: { 44 | sourceType: 'script' 45 | }, 46 | env: { 47 | browser: false, 48 | node: true 49 | }, 50 | plugins: ['node'], 51 | rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { 52 | // add your custom rules and overrides for node files here 53 | }) 54 | } 55 | ] 56 | }; 57 | -------------------------------------------------------------------------------- /.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 | /connect.lock 16 | /coverage/ 17 | /libpeerconnection.log 18 | /npm-debug.log* 19 | /testem.log 20 | /yarn-error.log 21 | 22 | # ember-try 23 | /.node_modules.ember-try/ 24 | /bower.json.ember-try 25 | /package.json.ember-try 26 | 27 | .idea/ 28 | -------------------------------------------------------------------------------- /.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 | /.eslintignore 14 | /.eslintrc.js 15 | /.git/ 16 | /.gitignore 17 | /.template-lintrc.js 18 | /.travis.yml 19 | /.watchmanconfig 20 | /bower.json 21 | /config/ember-try.js 22 | /CONTRIBUTING.md 23 | /ember-cli-build.js 24 | /testem.js 25 | /tests/ 26 | /yarn.lock 27 | .gitkeep 28 | 29 | # ember-try 30 | /.node_modules.ember-try/ 31 | /bower.json.ember-try 32 | /package.json.ember-try 33 | /config/addon-docs.js 34 | 35 | .idea/ 36 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'octane' 5 | }; 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | # we recommend testing addons with the same minimum supported node version as Ember CLI 5 | # so that your addon works for all apps 6 | - "10" 7 | 8 | dist: trusty 9 | 10 | addons: 11 | chrome: stable 12 | 13 | cache: 14 | yarn: true 15 | 16 | env: 17 | global: 18 | # See https://git.io/vdao3 for details. 19 | - JOBS=1 20 | 21 | branches: 22 | only: 23 | - master 24 | # npm version tags 25 | - /^v\d+\.\d+\.\d+/ 26 | 27 | jobs: 28 | fast_finish: true 29 | allow_failures: 30 | - env: EMBER_TRY_SCENARIO=ember-canary 31 | 32 | include: 33 | # runs linting and tests with current locked deps 34 | - stage: "Tests" 35 | name: "Tests" 36 | script: 37 | - yarn lint:hbs 38 | - yarn lint:js 39 | - yarn test 40 | 41 | - stage: "Additional Tests" 42 | name: "Floating Dependencies" 43 | install: 44 | - yarn install --no-lockfile --non-interactive 45 | script: 46 | - yarn test 47 | 48 | # we recommend new addons test the current and previous LTS 49 | # as well as latest stable release (bonus points to beta/canary) 50 | - env: EMBER_TRY_SCENARIO=ember-lts-3.12 51 | - env: EMBER_TRY_SCENARIO=ember-lts-3.16 52 | - env: EMBER_TRY_SCENARIO=ember-release 53 | - env: EMBER_TRY_SCENARIO=ember-beta 54 | - env: EMBER_TRY_SCENARIO=ember-canary 55 | - env: EMBER_TRY_SCENARIO=ember-default-with-jquery 56 | - env: EMBER_TRY_SCENARIO=ember-classic 57 | 58 | before_install: 59 | - curl -o- -L https://yarnpkg.com/install.sh | bash 60 | - export PATH=$HOME/.yarn/bin:$PATH 61 | 62 | install: 63 | - yarn install --non-interactive 64 | 65 | script: 66 | - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO 67 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How To Contribute 2 | 3 | ## Installation 4 | 5 | * `git clone ` 6 | * `cd @jftechnology/ember-keycloak-auth` 7 | * `yarn install` 8 | 9 | ## Linting 10 | 11 | * `yarn lint:hbs` 12 | * `yarn lint:js` 13 | * `yarn lint:js --fix` 14 | 15 | ## Running tests 16 | 17 | * `ember test` – Runs the test suite on the current Ember version 18 | * `ember test --server` – Runs the test suite in "watch mode" 19 | * `ember try:each` – Runs the test suite against multiple Ember versions 20 | 21 | ## Running the dummy application 22 | 23 | * `ember serve` 24 | * Visit the dummy application at [http://localhost:4200](http://localhost:4200). 25 | 26 | For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 JF Technology (UK) Ltd 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 | [![Build Status](https://travis-ci.org/JFTechnology/ember-keycloak-auth.svg)](https://travis-ci.org/JFTechnology/ember-keycloak-auth) 2 | [![Ember Observer Score](https://emberobserver.com/badges/-jftechnology-ember-keycloak-auth.svg)](https://emberobserver.com/addons/@jftechnology/ember-keycloak-auth) 3 | [![npm version](https://badge.fury.io/js/%40jftechnology%2Fember-keycloak-auth.svg)](https://badge.fury.io/js/%40jftechnology%2Fember-keycloak-auth) 4 | [![Dependency Status](https://david-dm.org/JFTechnology/ember-keycloak-auth.svg)](https://david-dm.org/JFTechnology/ember-keycloak-auth) 5 | [![devDependency Status](https://david-dm.org/JFTechnology/ember-keycloak-auth/dev-status.svg)](https://david-dm.org/JFTechnology/ember-keycloak-auth#info=devDependencies) 6 | 7 | @jftechnology/ember-keycloak-auth 8 | ============================================================================== 9 | 10 | This README outlines the details of collaborating on this Ember addon. 11 | 12 | @jftechnology/ember-keycloak-auth is an addon that can be installed with Ember CLI. It is intended for EmberJS applications accessing 13 | REST services secured by the Keycloak authentication server from Redhat/JBoss (http://keycloak.jboss.org). 14 | 15 | See [addon docs](https://jftechnology.github.io/ember-keycloak-auth) for full API details. 16 | 17 | 18 | ## Features overview 19 | 20 | * Presents the Keycloak JS adapter in a service that can be injected into an EmberJS app. 21 | * Tracks transitions via the Ember Router API and checks authentication based on route info metadata. 22 | * Provides a mixin that can be used with Ember data adapters to manage authentication headers whenever calls 23 | are made to a Keycloak secured backend via the Ember data framework. 24 | * Small utility components for displaying user login state. 25 | 26 | ## Ember version 27 | Versions 0.9+ of this library require the LTS version 3.8 of Ember or greater. Versions 0.9+ uses native classes 28 | and Stage 1 decorators and requires ember-decorators-polyfill for environments before Ember 3.10. 29 | 30 | If you are using a version of Ember older than 3.8, please use ember-keycloak-auth version 0.3.0 (note - no @jftechnology scope). 31 | 32 | Compatibility 33 | ------------------------------------------------------------------------------ 34 | 35 | ## @jftechnology/ember-keycloak-auth v0.9+ 36 | * Ember.js (LTS) v3.8 or above (requires ember-decorators-polyfill for Ember versions < 3.10) 37 | * Ember CLI (LTS) v3.8 or above 38 | 39 | ### Breaking changes v0.9+ 40 | * Package name now scoped (ember-keycloak-auth:0.3.x -> @jftechnology/ember-keycloak-auth:0.9.x) 41 | * Keycloak 'checkLoginIframe' option now defaults to false 42 | 43 | ## ember-keycloak-auth v0.3 44 | * Ember.js v2.18 or above 45 | * Ember CLI v2.18 or above 46 | 47 | Installation 48 | ------------------------------------------------------------------------------ 49 | 50 | Run: 51 | 52 | ``` 53 | ember install @jftechnology/ember-keycloak-auth 54 | ``` 55 | 56 | For Ember 3.8 / 3.9 you need to install the decorator polyfill as well... 57 | 58 | ``` 59 | ember install ember-decorators-polyfill 60 | ``` 61 | 62 | 63 | Usage 64 | ------------------------------------------------------------------------------ 65 | 66 | See [addon docs](https://jftechnology.github.io/ember-keycloak-auth) for usage and API details. 67 | 68 | ## Running 69 | 70 | There is a trivial demo app that allows testing of the service and route mixin. Enter the details of your Keycloak server 71 | and then navigate around a selection of access protected and unprotected routes. 72 | 73 | * `ember serve` 74 | * Visit the dummy application at [http://localhost:4200](http://localhost:4200). 75 | 76 | ## Running Tests 77 | 78 | * `yarn test` (Runs `ember try:each` to test your addon against multiple Ember versions) 79 | * `ember test` 80 | * `ember test --server` 81 | 82 | ## Building 83 | 84 | * `ember build` 85 | 86 | For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). 87 | For more information on Keycloak, visit [http://keycloak.jboss.com/](http://keycloak.jboss.com/). 88 | 89 | Contributing 90 | ------------------------------------------------------------------------------ 91 | 92 | See the [Contributing](CONTRIBUTING.md) guide for details. 93 | 94 | 95 | License 96 | ------------------------------------------------------------------------------ 97 | 98 | This project is licensed under the [MIT License](LICENSE.md). 99 | -------------------------------------------------------------------------------- /addon-test-support/index.js: -------------------------------------------------------------------------------- 1 | import MockKeycloakSession from './mock-keycloak-session'; 2 | import {assert} from '@ember/debug'; 3 | 4 | export const OPENID_RESPONSE = { 5 | "access_token": "the-access-token-the-real-thing-is-much-much-longer", 6 | "expires_in": 120, 7 | "refresh_expires_in": 7200, 8 | "refresh_token": "the-refresh-token", 9 | "token_type": "bearer", 10 | "id_token": "the-id-token", 11 | "not-before-policy": 1418191510, 12 | "session_state": "8f229c34-cef0-409b-8707-9797ae721efb", 13 | "scope": "openid" 14 | }; 15 | 16 | export const PROFILE = { 17 | "username": "stephen.flynn@jftechnology.com", 18 | "firstName": "Stephen", 19 | "lastName": "Flynn", 20 | "email": "stephen.flynn@jftechnology.com", 21 | "emailVerified": true, 22 | "attributes": {} 23 | }; 24 | 25 | export const PARSED_TOKEN = { 26 | "iss": "https://localhost:4200/auth/realms/my-realm", 27 | "aud": "my-client-id", 28 | "sub": "3c3fa2d3-5691-4581-a122-950327a6c424", 29 | "typ": "Bearer", 30 | "azp": "my-client-id", 31 | "nonce": "d2e3f77b-1912-42be-bab1-f9bb13638dbd", 32 | "auth_time": 1559159364, 33 | "session_state": "8f229c34-cef0-409b-8707-9797ae721efb", 34 | "acr": "0", 35 | "allowed-origins": ["https://localhost:4200"], 36 | "realm_access": { 37 | "roles": ["realm-role-1", "realm-role-1"] 38 | }, 39 | "resource_access": { 40 | "resource-A": { 41 | "roles": ["resource-A-role-1", "resource-A-role-2"], 42 | }, 43 | "resource-B": { 44 | "roles": ["resource-B-role-1", "resource-B-role-2"], 45 | } 46 | }, 47 | "scope": "openid", 48 | "name": "Stephen Flynn", 49 | "preferred_username": "stephen.flynn@jftechnology.com", 50 | "given_name": "Stephen", 51 | "family_name": "Flynn", 52 | "email": "stephen.flynn@jftechnology.com" 53 | }; 54 | 55 | class AuthStore { 56 | response = OPENID_RESPONSE; 57 | parsedToken = PARSED_TOKEN; 58 | profile = PROFILE; 59 | } 60 | 61 | export const AUTH_STORE = new AuthStore(); 62 | 63 | export function setupKeycloakSession(hooks) { 64 | 65 | hooks.beforeEach(function() { 66 | 67 | this.owner.register('service:keycloak-session', MockKeycloakSession); 68 | 69 | let session = this.owner.lookup('service:keycloak-session'); 70 | 71 | session.installKeycloak({ 72 | url: 'https://localhost', 73 | realm: 'my-realm', 74 | clientId: 'my-client-id' 75 | } 76 | ); 77 | 78 | assert('Keycloak-session registration failed ! Called the hook too late ?', session.isStub); 79 | assert('Keycloak-session install failed ! Called the hook too late ?', session.keycloak); 80 | }); 81 | } 82 | 83 | 84 | export function login(owner) { 85 | 86 | let session = owner.lookup('service:keycloak-session'); 87 | 88 | session.initKeycloak(); 89 | 90 | assert('Keycloak-session init failed !', session.ready, true); 91 | 92 | return session.login(); 93 | } 94 | -------------------------------------------------------------------------------- /addon-test-support/mock-keycloak-instance.js: -------------------------------------------------------------------------------- 1 | import {AUTH_STORE} from '@jftechnology/ember-keycloak-auth/test-support'; 2 | 3 | /** 4 | * Mock of the Keycloak Adapter instance - installed by the Keycloak Session mock instead of a live KeycloakInstance. 5 | * 6 | * @class MockKeycloakInstance 7 | * @public 8 | */ 9 | export default class MockKeycloakInstance { 10 | 11 | parameters; 12 | 13 | options; 14 | 15 | _response; 16 | 17 | _parsedToken; 18 | 19 | _profile; 20 | 21 | get token() { 22 | return this._response.access_token; 23 | } 24 | 25 | get tokenParsed() { 26 | return this._parsedToken; 27 | } 28 | 29 | get subject() { 30 | return this.tokenParsed ? this.tokenParsed.sub : undefined; 31 | } 32 | 33 | constructor(parameters) { 34 | 35 | this.parameters = parameters; 36 | } 37 | 38 | init(options) { 39 | console.log(`MockKeycloak :: init :: ${options}`); 40 | this.options = options; 41 | this.onReady(false); 42 | return new MockPromise(true, null); 43 | } 44 | 45 | login(options) { 46 | console.log(`MockKeycloak :: login :: ${options}`); 47 | this._response = AUTH_STORE.response; 48 | this._parsedToken = AUTH_STORE.parsedToken; 49 | this._profile = AUTH_STORE.profile; 50 | this.onAuthSuccess(); 51 | return new MockPromise(true, false); 52 | } 53 | 54 | logout(options) { 55 | console.log(`MockKeycloak :: logout :: ${options}`); 56 | this.onAuthLogout(); 57 | return new MockPromise(true, false); 58 | } 59 | 60 | updateToken(minValidity) { 61 | console.log(`MockKeycloak :: updateToken :: ${minValidity}`); 62 | return new MockPromise(true, false); 63 | } 64 | 65 | clearToken() { 66 | console.log(`MockKeycloak :: clearToken`); 67 | this._response = null; 68 | this._parsedToken = undefined; 69 | this._profile = undefined; 70 | } 71 | 72 | loadUserProfile() { 73 | console.log(`MockKeycloak :: loadUserProfile`); 74 | return new MockPromise(this._profile, false); 75 | } 76 | 77 | hasRealmRole(role) { 78 | 79 | if (this.tokenParsed && this.tokenParsed.realm_access) { 80 | return (this.tokenParsed.realm_access.roles || []).includes(role); 81 | } 82 | 83 | return false; 84 | } 85 | 86 | hasResourceRole(role, resource) { 87 | 88 | if (this.tokenParsed && this.tokenParsed.resource_access && this.tokenParsed.resource_access[resource]) { 89 | return (this.tokenParsed.resource_access[resource].roles || []).includes(role); 90 | } 91 | 92 | return false; 93 | } 94 | 95 | /** 96 | * Redirects to registration form. 97 | * @method {register} 98 | * @param options {object} Supports same options as Keycloak#login but `action` is 99 | * set to `'register'`. 100 | */ 101 | register() { 102 | throw new Error("not implemented"); 103 | } 104 | 105 | /** 106 | * Redirects to the Account Management Console. 107 | * @method {accountManagement} 108 | */ 109 | accountManagement() { 110 | throw new Error("not implemented"); 111 | } 112 | 113 | /** 114 | * Returns the URL to login form. 115 | * @method {createLoginUrl} 116 | * @param options {object} Supports same options as Keycloak#login. 117 | */ 118 | createLoginUrl() { 119 | throw new Error("not implemented"); 120 | } 121 | 122 | /** 123 | * 124 | * Returns the URL to logout the user. 125 | * @method {createLogoutUrl} 126 | * @param options {object} Logout options. 127 | * @param options.redirectUri {string} Specifies the uri to redirect to after logout. 128 | */ 129 | createLogoutUrl() { 130 | throw new Error("not implemented"); 131 | } 132 | 133 | /** 134 | * Returns the URL to registration page. 135 | * @method {createRegisterUrl} 136 | * @param options {object} Supports same options as Keycloak#createLoginUrl but 137 | * `action` is set to `'register'`. 138 | */ 139 | createRegisterUrl() { 140 | throw new Error("not implemented"); 141 | } 142 | 143 | /** 144 | * Returns the URL to the Account Management Console. 145 | * @method {createAccountUrl} 146 | */ 147 | createAccountUrl() { 148 | throw new Error("not implemented"); 149 | } 150 | 151 | /** 152 | * Returns true if the token has less than `minValidity` seconds left before 153 | * it expires. 154 | * @method {isTokenExpired} 155 | * @param minValidity {number} If not specified, `0` is used. 156 | */ 157 | isTokenExpired() { 158 | throw new Error("not implemented"); 159 | } 160 | 161 | /** 162 | * @private Undocumented. 163 | * @method {loadUserInfo} 164 | */ 165 | loadUserInfo() { 166 | throw new Error("not implemented"); 167 | } 168 | 169 | /** 170 | * Called when the adapter is initialized. 171 | * @method {onReady} 172 | */ 173 | onReady(authenticated) { 174 | console.log(`on ready ${authenticated}`); 175 | } 176 | 177 | /** 178 | * Called when a user is successfully authenticated. 179 | * @method {onAuthSuccess} 180 | */ 181 | onAuthSuccess() { 182 | 183 | } 184 | 185 | /** 186 | * Called if the user is logged out (will only be called if the session 187 | * status iframe is enabled, or in Cordova mode). 188 | * @method {onAuthLogout} 189 | */ 190 | onAuthLogout() { 191 | 192 | } 193 | } 194 | 195 | class MockPromise extends Promise { 196 | 197 | successValue; 198 | 199 | errorValue; 200 | 201 | resolve; 202 | 203 | reject; 204 | 205 | constructor(successValue, errorValue) { 206 | super((/*resolve, reject*/) => { 207 | // this.resolve = resolve; 208 | // this.reject = reject; 209 | }); 210 | this.successValue = successValue; 211 | this.errorValue = errorValue; 212 | } 213 | 214 | then(onfulfilled, onrejected) { 215 | 216 | if (this.successValue) { 217 | if (onfulfilled) { 218 | onfulfilled(this.successValue); 219 | } 220 | } 221 | 222 | if (this.errorValue) { 223 | if (onrejected) { 224 | onrejected(this.errorValue); 225 | } 226 | } 227 | 228 | return this; 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /addon-test-support/mock-keycloak-session.js: -------------------------------------------------------------------------------- 1 | import KeycloakSessionService from '@jftechnology/ember-keycloak-auth/services/keycloak-session'; 2 | import MockKeycloakInstance from './mock-keycloak-instance'; 3 | 4 | export default class MockKeycloakSessionService extends KeycloakSessionService { 5 | 6 | installKeycloak(parameters) { 7 | 8 | console.debug('Mock Keycloak Session :: installKeycloak'); 9 | 10 | let keycloak = new MockKeycloakInstance(parameters); 11 | 12 | this._installKeycloak(keycloak); 13 | } 14 | 15 | get isStub() { 16 | return true; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /addon/components/keycloak-session-config.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
minValidity{{this.keycloakSession.minValidity}}
onLoad{{this.keycloakSession.onLoad}}
responseMode{{this.keycloakSession.responseMode}}
flow{{this.keycloakSession.flow}}
checkLoginIframe{{this.keycloakSession.checkLoginIframe}}
checkLoginIframeInterval{{this.keycloakSession.checkLoginIframeInterval}}
idpHint{{this.keycloakSession.idpHint}}
verbose{{this.keycloakSession.verbose}}
37 | -------------------------------------------------------------------------------- /addon/components/keycloak-session-config.ts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | 3 | import {inject as service} from '@ember/service'; 4 | import {KeycloakAdapterService} from "@jftechnology/ember-keycloak-auth"; 5 | 6 | /** 7 | * @class KeycloakSessionConfig 8 | * @public 9 | */ 10 | export default class KeycloakSessionConfig extends Component { 11 | 12 | /** 13 | * An injected keycloak session. 14 | * 15 | * @property keycloakSession 16 | * @type {KeycloakAdapterService} 17 | */ 18 | @service 19 | keycloakSession!: KeycloakAdapterService; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /addon/components/keycloak-session-link.hbs: -------------------------------------------------------------------------------- 1 | {{#if this.keycloakSession.ready}} 2 | {{#if this.keycloakSession.authenticated}} 3 | 4 | Sign out 5 | 6 | {{else}} 7 | 8 | Sign in 9 | 10 | {{/if}} 11 | {{else}} 12 | No session 13 | {{/if}} 14 | -------------------------------------------------------------------------------- /addon/components/keycloak-session-link.ts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | 3 | import {action} from '@ember/object'; 4 | import {inject as service} from '@ember/service'; 5 | import {KeycloakAdapterService} from "@jftechnology/ember-keycloak-auth"; 6 | 7 | /** 8 | * @class KeycloakSessionLink 9 | * @public 10 | */ 11 | export default class KeycloakSessionLink extends Component { 12 | 13 | /** 14 | * An injected keycloak session. 15 | * 16 | * @property keycloakSession 17 | * @type {KeycloakAdapterService} 18 | */ 19 | @service 20 | keycloakSession!: KeycloakAdapterService; 21 | 22 | @action 23 | login() { 24 | this.keycloakSession.login().then(result => { 25 | console.debug(result); 26 | }); 27 | } 28 | 29 | @action 30 | logout() { 31 | this.keycloakSession.logout().then(result => { 32 | console.debug(result); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /addon/components/keycloak-session-profile.hbs: -------------------------------------------------------------------------------- 1 | {{#if this.keycloakSession.ready}} 2 | 3 |
4 | 11 |
12 | 13 | {{#with this.keycloakSession.profile as |profile|}} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 36 | 37 | {{#each-in this.resourceRoles as |resource roles|}} 38 | 39 | 40 | 45 | 46 | {{/each-in}} 47 | 48 |
Id{{profile.id}}
Username{{profile.username}}
Email{{profile.email}}
Realm roles 32 | {{#each this.realmRoles.roles as |role|}} 33 |

{{role}}

34 | {{/each}} 35 |
Roles for resource '{{resource}}' 41 | {{#each roles.roles as |role|}} 42 |

{{role}}

43 | {{/each}} 44 |
49 | 50 | {{/with}} 51 | 52 | {{else}} 53 | No session 54 | {{/if}} 55 | -------------------------------------------------------------------------------- /addon/components/keycloak-session-profile.ts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | 3 | import {action, computed} from '@ember/object'; 4 | import {alias} from '@ember/object/computed'; 5 | import {inject as service} from '@ember/service'; 6 | import {KeycloakAdapterService} from "@jftechnology/ember-keycloak-auth"; 7 | 8 | import {KeycloakTokenParsed} from 'keycloak-js'; 9 | 10 | /** 11 | * @class KeycloakSessionProfile 12 | * @public 13 | */ 14 | export default class KeycloakSessionProfile extends Component { 15 | 16 | /** 17 | * An injected keycloak session. 18 | * 19 | * @property keycloakSession 20 | * @type {KeycloakAdapterService} 21 | */ 22 | @service 23 | keycloakSession!: KeycloakAdapterService; 24 | 25 | @alias('keycloakSession.tokenParsed') 26 | tokenParsed!: KeycloakTokenParsed; 27 | 28 | @computed('keycloakSession.timestamp') 29 | get resourceRoles() { 30 | 31 | if (this.tokenParsed) { 32 | return this.tokenParsed['resource_access']; 33 | } 34 | 35 | return {}; 36 | } 37 | 38 | @computed('keycloakSession.timestamp') 39 | get realmRoles() { 40 | 41 | if (this.tokenParsed) { 42 | return this.tokenParsed['realm_access']; 43 | } 44 | 45 | return {}; 46 | } 47 | 48 | @action 49 | loadUserProfile() { 50 | this.keycloakSession.loadUserProfile().then(result => { 51 | console.debug(result); 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /addon/components/keycloak-session-status.hbs: -------------------------------------------------------------------------------- 1 | {{#if this.keycloakSession.ready}} 2 | 3 |
4 | 11 | 18 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | 49 | 52 | 53 | 54 |
Ready 32 | {{this.keycloakSession.ready}} 33 |
Authenticated 38 | {{this.keycloakSession.authenticated}} 39 |
Subject 44 | {{this.keycloakSession.subject}} 45 |
Refresh token 50 | {{this.keycloakSession.refreshToken}} 51 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 69 | 72 | 77 | 78 | 79 | 80 | 83 | 86 | 91 | 92 | 93 |
ActionRouteRedirect
Login 67 | {{this.keycloakSession.defaultLoginRoute}} 68 | 70 | {{this.keycloakSession.defaultLoginRedirectUri}} 71 | 73 | 74 | "View" 75 | 76 |
Logout 81 | {{this.keycloakSession.defaultLogoutRoute}} 82 | 84 | {{this.keycloakSession.defaultLogoutRedirectUri}} 85 | 87 | 88 | "View" 89 | 90 |
94 | 95 | {{else}} 96 | No session 97 | {{/if}} 98 | -------------------------------------------------------------------------------- /addon/components/keycloak-session-status.ts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | 3 | import {action} from '@ember/object'; 4 | import {inject as service} from '@ember/service'; 5 | 6 | import {KeycloakAdapterService} from '@jftechnology/ember-keycloak-auth'; 7 | 8 | /** 9 | * @class KeycloakSessionStatus 10 | * @public 11 | */ 12 | export default class KeycloakSessionStatus extends Component { 13 | 14 | /** 15 | * An injected keycloak session. 16 | * 17 | * @property keycloakSession 18 | * @type {KeycloakAdapterService} 19 | */ 20 | @service 21 | keycloakSession!: KeycloakAdapterService; 22 | 23 | @action 24 | refresh() { 25 | this.keycloakSession.updateToken().then(result => { 26 | console.debug(result); 27 | }); 28 | } 29 | 30 | @action 31 | login() { 32 | this.keycloakSession.login().then(result => { 33 | console.debug(result); 34 | }); 35 | } 36 | 37 | @action 38 | logout() { 39 | this.keycloakSession.logout().then(result => { 40 | console.debug(result); 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /addon/helpers/in-role.ts: -------------------------------------------------------------------------------- 1 | import Helper from '@ember/component/helper'; 2 | 3 | import {inject as service} from '@ember/service'; 4 | 5 | import {KeycloakAdapterService} from '@jftechnology/ember-keycloak-auth'; 6 | 7 | /** 8 | * Helper that checks a keycloak session for realm or resource roles. 9 | * 10 | * Usage @enabled = {{in-role 'my-role'}} 11 | * Usage @enabled = {{in-role 'my-role' 'my-resource'}} 12 | * 13 | * @class InRoleHelper 14 | * @public 15 | */ 16 | export default class InRoleHelper extends Helper { 17 | 18 | @service 19 | keycloakSession!: KeycloakAdapterService; 20 | 21 | /** 22 | * Delegates to the wrapped Keycloak instance's method. 23 | * 24 | * @method compute 25 | * @param role {string} The role to check 26 | * @param resource {string} The resource to check 27 | * @return {boolean} True if user in role, else false. 28 | */ 29 | compute([role, resource]: any) { 30 | 31 | return this.keycloakSession.inRole(role, resource); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /addon/helpers/not-in-role.ts: -------------------------------------------------------------------------------- 1 | import Helper from '@ember/component/helper'; 2 | 3 | import {inject as service} from '@ember/service'; 4 | import {KeycloakAdapterService} from "@jftechnology/ember-keycloak-auth"; 5 | 6 | /** 7 | * Helper that checks a keycloak session for realm or resource roles. 8 | * 9 | * Usage @disabled = {{not-in-role 'my-role'}} 10 | * Usage @disabled = {{not-in-role 'my-role' 'my-resource'}} 11 | * 12 | * @class HasRoleHelper 13 | * @public 14 | */ 15 | export default class NotInRoleHelper extends Helper { 16 | 17 | @service 18 | keycloakSession!: KeycloakAdapterService; 19 | 20 | /** 21 | * Delegates to the wrapped Keycloak instance's method. 22 | * 23 | * @method compute 24 | * @param role {string} The role to check 25 | * @param resource {string} The resource to check 26 | * @return {boolean} True if user in role, else false. 27 | */ 28 | compute([role, resource]: any) { 29 | 30 | return !this.keycloakSession.inRole(role, resource); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /addon/services/keycloak-session.ts: -------------------------------------------------------------------------------- 1 | /*eslint no-undef: "error"*/ 2 | import Service, {inject as service} from '@ember/service'; 3 | import {KeycloakAdapterService} from '@jftechnology/ember-keycloak-auth'; 4 | 5 | import Keycloak, { 6 | KeycloakConfig, 7 | KeycloakError, 8 | KeycloakFlow, 9 | KeycloakInitOptions, 10 | KeycloakInstance, 11 | KeycloakLoginOptions, 12 | KeycloakOnLoad, 13 | KeycloakProfile, 14 | KeycloakResponseMode, 15 | KeycloakTokenParsed 16 | } from 'keycloak-js'; 17 | 18 | import RSVP from 'rsvp'; 19 | import {computed, set} from '@ember/object'; 20 | import RouterService from "ember__routing/router-service"; 21 | 22 | const {Promise} = RSVP; 23 | 24 | /** 25 | * Injectable Ember service that wraps an application wide Keycloak js instance. 26 | * 27 | * @class KeycloakSessionService 28 | * @public 29 | */ 30 | export default class KeycloakSessionService extends Service implements KeycloakAdapterService { 31 | 32 | /** 33 | * The injected Ember router service. 34 | * 35 | * @property router 36 | * @type {RouterService} 37 | */ 38 | @service 39 | router!: RouterService; 40 | 41 | _keycloak?: KeycloakInstance<"native">; 42 | 43 | profile?: KeycloakProfile; 44 | 45 | /** 46 | * Value in seconds used in calls to KeyCloak.updateToken(minValidity). Default 30. 47 | * 48 | * @property minValidity 49 | * @type {number} 50 | */ 51 | minValidity: number = 30; 52 | 53 | /** 54 | * Bound property to track session state. Indicates that a keycloak session has been successfully created. Default false. 55 | * 56 | * @property ready 57 | * @type {boolean} 58 | */ 59 | ready: boolean = false; 60 | 61 | /** 62 | * Bound property to track session state. Indicates that the session has authenticated. Default false. 63 | * 64 | * @property authenticated 65 | * @type {boolean} 66 | */ 67 | authenticated: boolean = false; 68 | 69 | /** 70 | * Bound property to track session state. Track last activity time. 71 | * 72 | * @property timestamp 73 | * @type {Date} 74 | */ 75 | timestamp?: Date; 76 | 77 | /** 78 | * Keycloak.init() option. Should be one of 'check-sso' or 'login-required'. Default 'login-required'. 79 | * See http://www.keycloak.org/documentation.html for complete details. 80 | * 81 | * @property onLoad 82 | * @type {String} 83 | */ 84 | onLoad: KeycloakOnLoad = 'login-required'; 85 | 86 | /** 87 | * Keycloak.init() option. Should be one of 'query' or 'fragment'. Default 'fragment'. 88 | * See http://www.keycloak.org/documentation.html for complete details. 89 | * 90 | * @property responseMode 91 | * @type {String} 92 | */ 93 | responseMode: KeycloakResponseMode = 'fragment'; 94 | 95 | /** 96 | * Keycloak.init() option. Should be one of 'standard', 'implicit' or 'hybrid'. Default 'standard'. 97 | * See http://www.keycloak.org/documentation.html for complete details. 98 | * 99 | * @property flow 100 | * @type {String} 101 | */ 102 | flow: KeycloakFlow = 'standard'; 103 | 104 | /** 105 | * Keycloak.init() option. Default 'false'. 106 | * 107 | * @property checkLoginIframe 108 | * @type {boolean} 109 | */ 110 | checkLoginIframe: boolean = false; 111 | 112 | /** 113 | * Keycloak.init() option. Default '5'. 114 | * 115 | * @property checkLoginIframeInterval 116 | * @type {number} 117 | */ 118 | checkLoginIframeInterval: number = 5; 119 | 120 | /** 121 | * Keycloak.login() option. 122 | * 123 | * @property idpHint 124 | * @type {String} 125 | */ 126 | idpHint?: string; 127 | 128 | /** 129 | * Keycloak.login() option. 130 | * 131 | * @property idpHint 132 | * @type {String} 133 | */ 134 | verbose: boolean = false; 135 | 136 | /** 137 | * @method installKeycloak 138 | * @param {*[]} parameters Constructor parameters for Keycloak object - see Keycloak JS adapter docs for details 139 | */ 140 | installKeycloak(parameters: KeycloakConfig | string) { 141 | 142 | if (this.verbose) { 143 | console.debug('KeycloakSessionService :: install'); 144 | } 145 | 146 | let keycloak: KeycloakInstance<"native"> = Keycloak<"native">(parameters); 147 | 148 | this._installKeycloak(keycloak); 149 | } 150 | 151 | _installKeycloak(keycloak: KeycloakInstance<"native">) { 152 | 153 | keycloak.onReady = this.onReady; 154 | keycloak.onAuthSuccess = this.onAuthSuccess; 155 | keycloak.onAuthError = this.onAuthError; 156 | keycloak.onAuthRefreshSuccess = this.onAuthRefreshSuccess; 157 | keycloak.onAuthRefreshError = this.onAuthRefreshError; 158 | keycloak.onTokenExpired = this.onTokenExpired; 159 | keycloak.onAuthLogout = this.onAuthLogout; 160 | 161 | set(this, '_keycloak', keycloak); 162 | set(this, 'timestamp', new Date()); 163 | 164 | if (this.verbose) { 165 | console.debug('KeycloakSessionService :: install :: completed'); 166 | } 167 | } 168 | 169 | /** 170 | * @method initKeycloak 171 | */ 172 | initKeycloak(): Promise | void { 173 | 174 | if (this.verbose) { 175 | console.debug('KeycloakSessionService :: init'); 176 | } 177 | 178 | let options: KeycloakInitOptions = this.getProperties('onLoad', 'responseMode', 'checkLoginIframe', 'checkLoginIframeInterval', 'flow'); 179 | 180 | options.promiseType = "native"; 181 | 182 | if (this.keycloak) { 183 | let keycloak = this.keycloak; 184 | return new Promise((resolve, reject) => { 185 | keycloak.init(options) 186 | .then( 187 | authenticated => { 188 | console.info('KeycloakSessionService :: init complete'); 189 | resolve(authenticated); 190 | }, 191 | reason => { 192 | console.warn('KeycloakSessionService :: init failed'); 193 | reject(reason); 194 | }); 195 | }); 196 | } 197 | } 198 | 199 | /** 200 | * The wrapped Keycloak instance. 201 | * 202 | * @property keycloak 203 | * @type {Keycloak} 204 | */ 205 | @computed('_keycloak', 'timestamp') 206 | get keycloak(): KeycloakInstance<"native"> | undefined { 207 | return this._keycloak; 208 | } 209 | 210 | /** 211 | * The current Keycloak subject. 212 | * 213 | * @property subject 214 | * @type {string | undefined} 215 | */ 216 | get subject(): string | undefined { 217 | return this.keycloak ? this.keycloak.subject : undefined; 218 | } 219 | 220 | /** 221 | * The current Keycloak refreshToken. 222 | * 223 | * @property refreshToken 224 | * @type {string} 225 | */ 226 | get refreshToken(): string | undefined { 227 | return this.keycloak ? this.keycloak.refreshToken : undefined; 228 | } 229 | 230 | /** 231 | * The current Keycloak token. 232 | * 233 | * @property token 234 | * @type {string} 235 | */ 236 | get token(): string | undefined { 237 | return this.keycloak ? this.keycloak.token : undefined; 238 | } 239 | 240 | /** 241 | * The current Keycloak tokenParsed. 242 | * 243 | * @property tokenParsed 244 | * @type {string} 245 | */ 246 | get tokenParsed(): KeycloakTokenParsed | undefined { 247 | return this.keycloak ? this.keycloak.tokenParsed : undefined; 248 | } 249 | 250 | /** 251 | * Convenience property presents the current token as the Authorization header typically required by calls to a back end service. 252 | * @property headers 253 | * @type {Object} 254 | */ 255 | get headers(): {} { 256 | 257 | return { 258 | 'Authorization': `Bearer ${this.token}` 259 | }; 260 | } 261 | 262 | /** 263 | * Delegates to the wrapped Keycloak instance's method. 264 | * 265 | * @method hasRealmRole 266 | * @param role {string} The role to check 267 | * @return {boolean} True if user in role. 268 | */ 269 | hasRealmRole(role: string): boolean { 270 | return !!(this.keycloak && this.keycloak.hasRealmRole(role)); 271 | } 272 | 273 | /** 274 | * Delegates to the wrapped Keycloak instance's method. 275 | * 276 | * @method hasResourceRole 277 | * @param role {string} The role to check 278 | * @param resource {string} The resource to check 279 | * @return {boolean} True if user in role. 280 | */ 281 | hasResourceRole(role: string, resource: string): boolean { 282 | return !!(this.keycloak && this.keycloak.hasResourceRole(role, resource)); 283 | } 284 | 285 | inRole(role: string, resource: string): boolean { 286 | 287 | if (role && resource) { 288 | return this.hasResourceRole(role, resource); 289 | } 290 | 291 | if (role) { 292 | return this.hasRealmRole(role); 293 | } 294 | 295 | return false; 296 | } 297 | 298 | /** 299 | * Delegates to the wrapped Keycloak instance's method using the minValidity property. 300 | * 301 | * @method updateToken 302 | * @return {Promise} Wrapped promise. 303 | */ 304 | updateToken(): RSVP.Promise { 305 | 306 | return new RSVP.Promise((resolve, reject) => { 307 | 308 | if (this.keycloak) { 309 | this.keycloak.updateToken(this.minValidity) 310 | .then( 311 | refreshed => { 312 | resolve(refreshed); 313 | }, 314 | () => { 315 | console.debug('update token resolved as error'); 316 | reject(new Error('authentication token update failed')); 317 | }); 318 | } else { 319 | reject(new Error("No installed keycloak instance")); 320 | } 321 | }); 322 | } 323 | 324 | /** 325 | * Delegates to the wrapped Keycloak instance's method. 326 | * 327 | * @method clearToken 328 | * @return {Promise} Wrapped promise. 329 | */ 330 | clearToken() { 331 | if (this.keycloak) { 332 | this.keycloak.clearToken(); 333 | } 334 | } 335 | 336 | /** 337 | * Parses the redirect url from the intended 'to' route of a transition. 338 | * 339 | * @method _parseRedirectUrl 340 | * @param {RouterService} router The ember router service. 341 | * @param {Transition} transition The transition in progress. 342 | * @return {String} URL to include as the Keycloak redirect 343 | * @private 344 | */ 345 | _parseRedirectUrl() { 346 | 347 | // @ts-ignore 348 | console.debug(`KeycloakSessionService :: _parseRedirectUrl :: ${window.location.origin} + ${this.router.rootURL} + ${this.router.currentURL}`); 349 | 350 | let redirect = '/'; 351 | 352 | // @ts-ignore 353 | if (this.router.rootURL) { 354 | // @ts-ignore 355 | redirect = redirect + this.router.rootURL; 356 | } 357 | 358 | if (this.router.currentURL) { 359 | redirect = redirect + this.router.currentURL; 360 | } 361 | 362 | redirect = window.location.origin + redirect.replace(new RegExp('//', 'g'), '/'); 363 | 364 | console.debug(`KeycloakSessionService :: _parseRedirectUrl :: ${redirect}`); 365 | 366 | return redirect; 367 | } 368 | 369 | /** 370 | * Delegates to the wrapped Keycloak instance's method. 371 | * 372 | * @method loadUserProfile 373 | * @return {RSVP.Promise} Resolves on server response 374 | */ 375 | loadUserProfile(): RSVP.Promise { 376 | 377 | return new RSVP.Promise((resolve, reject) => { 378 | 379 | if (this.keycloak) { 380 | this.keycloak.loadUserProfile() 381 | .then( 382 | profile => { 383 | console.debug(`Loaded profile for ${profile.id}`); 384 | set(this, 'profile', profile); 385 | resolve(profile); 386 | }, 387 | error => { 388 | reject(error); 389 | }); 390 | } else { 391 | reject(new Error("KeycloakSessionService :: no installed keycloak instance")); 392 | } 393 | }); 394 | } 395 | 396 | /** 397 | * Delegates to the wrapped Keycloak instance's method. 398 | * 399 | * @method login 400 | * @param {String} redirectUri Optional redirect url 401 | * @return {Promise} Resolves on server response 402 | */ 403 | login(redirectUri: string): RSVP.Promise { 404 | 405 | let options: KeycloakLoginOptions = {redirectUri}; 406 | 407 | // Add idpHint to options, if it is populated 408 | if (this.idpHint) { 409 | options.idpHint = this.get('idpHint'); 410 | } 411 | 412 | return new RSVP.Promise((resolve, reject) => { 413 | 414 | if (this.keycloak) { 415 | this.keycloak.login(options) 416 | .then( 417 | () => { 418 | console.debug('KeycloakSessionService :: login :: success'); 419 | resolve('login OK'); 420 | }, 421 | () => { 422 | console.debug('KeycloakSessionService :: login error'); 423 | reject(new Error('login failed')); 424 | }); 425 | } else { 426 | reject(new Error("KeycloakSessionService :: no installed keycloak instance")); 427 | } 428 | }); 429 | } 430 | 431 | /** 432 | * Delegates to the wrapped Keycloak instance's method. 433 | * 434 | * @method logout 435 | * @param {String} redirectUri Optional redirect url 436 | * @return {RSVP.Promise} Resolves on server response. 437 | */ 438 | logout(redirectUri: string): RSVP.Promise { 439 | 440 | let options = {redirectUri}; 441 | 442 | return new RSVP.Promise((resolve, reject) => { 443 | 444 | if (this.keycloak) { 445 | let keycloak = this.keycloak; 446 | keycloak.logout(options) 447 | .then( 448 | () => { 449 | console.debug('KeycloakSessionService :: logout :: success'); 450 | keycloak.clearToken(); 451 | resolve('logout OK'); 452 | }); 453 | } else { 454 | reject(new Error("KeycloakSessionService :: no installed keycloak instance")); 455 | } 456 | }); 457 | } 458 | 459 | wrappedCall(call: () => {}): RSVP.Promise { 460 | 461 | return this.updateToken() 462 | .then(result => { 463 | if (result && this.verbose) { 464 | console.debug(`KeycloakSessionService :: token was refreshed prior to wrapped call`); 465 | } 466 | return true; 467 | }) 468 | .then( 469 | call, 470 | (reason: any) => { 471 | console.warn(`KeycloakSessionService :: update token :: rejected :: ${reason}`); 472 | let url = this._parseRedirectUrl(); 473 | this.login(url); 474 | throw reason; 475 | }); 476 | } 477 | 478 | /** 479 | * Keycloak callback function. 480 | * 481 | * @property onReady 482 | * @type {Function} 483 | */ 484 | onReady = (authenticated: boolean) => { 485 | set(this, 'ready', true); 486 | set(this, 'authenticated', authenticated); 487 | set(this, 'timestamp', new Date()); 488 | console.info(`KeycloakSessionService :: onReady -> ${authenticated}`); 489 | }; 490 | 491 | /** 492 | * Keycloak callback function. 493 | * 494 | * @property onAuthSuccess 495 | * @type {Function} 496 | */ 497 | onAuthSuccess = () => { 498 | set(this, 'authenticated', true); 499 | set(this, 'timestamp', new Date()); 500 | 501 | if (this.verbose) { 502 | console.debug(`KeycloakSessionService :: onAuthSuccess :: token -> ${this.token}`); 503 | } 504 | }; 505 | 506 | /** 507 | * Keycloak callback function. 508 | * 509 | * @property onAuthError 510 | * @type {Function} 511 | */ 512 | onAuthError = (errorData: KeycloakError) => { 513 | set(this, 'authenticated', false); 514 | set(this, 'timestamp', new Date()); 515 | 516 | console.warn(`KeycloakSessionService :: onAuthError :: error -> ${errorData.error}, description -> ${errorData.error_description}`); 517 | }; 518 | 519 | /** 520 | * Keycloak callback function. 521 | * 522 | * @property onAuthRefreshSuccess 523 | * @type {Function} 524 | */ 525 | onAuthRefreshSuccess = () => { 526 | set(this, 'authenticated', true); 527 | set(this, 'timestamp', new Date()); 528 | 529 | if (this.verbose) { 530 | console.debug(`KeycloakSessionService :: onAuthRefreshSuccess :: token -> ${this.token}`); 531 | } 532 | }; 533 | 534 | /** 535 | * Keycloak callback function. 536 | * 537 | * @property onAuthRefreshError 538 | * @type {Function} 539 | */ 540 | onAuthRefreshError = () => { 541 | set(this, 'authenticated', false); 542 | set(this, 'timestamp', new Date()); 543 | this.clearToken(); 544 | console.warn('KeycloakSessionService :: onAuthRefreshError'); 545 | }; 546 | 547 | /** 548 | * Keycloak callback function. 549 | * 550 | * @property onTokenExpired 551 | * @type {Function} 552 | */ 553 | onTokenExpired = () => { 554 | set(this, 'authenticated', false); 555 | set(this, 'timestamp', new Date()); 556 | console.info('KeycloakSessionService :: onTokenExpired'); 557 | }; 558 | 559 | /** 560 | * Keycloak callback function. 561 | * 562 | * @property onAuthLogout 563 | * @type {Function} 564 | */ 565 | onAuthLogout = () => { 566 | set(this, 'authenticated', false); 567 | set(this, 'timestamp', new Date()); 568 | console.info('KeycloakSessionService :: onAuthLogout'); 569 | }; 570 | } 571 | 572 | -------------------------------------------------------------------------------- /app/components/keycloak-session-config.js: -------------------------------------------------------------------------------- 1 | export { default } from '@jftechnology/ember-keycloak-auth/components/keycloak-session-config'; 2 | -------------------------------------------------------------------------------- /app/components/keycloak-session-link.js: -------------------------------------------------------------------------------- 1 | export { default } from '@jftechnology/ember-keycloak-auth/components/keycloak-session-link'; 2 | -------------------------------------------------------------------------------- /app/components/keycloak-session-profile.js: -------------------------------------------------------------------------------- 1 | export { default } from '@jftechnology/ember-keycloak-auth/components/keycloak-session-profile'; 2 | -------------------------------------------------------------------------------- /app/components/keycloak-session-status.js: -------------------------------------------------------------------------------- 1 | export { default } from '@jftechnology/ember-keycloak-auth/components/keycloak-session-status'; 2 | -------------------------------------------------------------------------------- /app/helpers/in-role.js: -------------------------------------------------------------------------------- 1 | export { default, hasRole } from '@jftechnology/ember-keycloak-auth/helpers/in-role'; 2 | -------------------------------------------------------------------------------- /app/helpers/not-in-role.js: -------------------------------------------------------------------------------- 1 | export { default, hasRole } from '@jftechnology/ember-keycloak-auth/helpers/not-in-role'; 2 | -------------------------------------------------------------------------------- /app/services/keycloak-session.js: -------------------------------------------------------------------------------- 1 | export { default } from '@jftechnology/ember-keycloak-auth/services/keycloak-session'; 2 | -------------------------------------------------------------------------------- /blueprints/ember-keycloak-auth/index.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | /* eslint-disable node/no-extraneous-require */ 3 | const chalk = require('chalk'); 4 | 5 | module.exports = { 6 | 7 | description: '@jftechnology/ember-keycloak-auth', 8 | 9 | normalizeEntityName() { 10 | // this prevents an error when the entityName is 11 | // not specified (since that doesn't actually matter 12 | // to us 13 | }, 14 | 15 | afterInstall() { 16 | 17 | this.ui.writeLine(chalk.green(` adding keycloak-js package`)); 18 | 19 | return this.addPackagesToProject([ 20 | {name: 'keycloak-js'} 21 | ]); 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /config/addon-docs.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | const AddonDocsConfig = require('ember-cli-addon-docs/lib/config'); 5 | 6 | module.exports = class extends AddonDocsConfig { 7 | // See https://ember-learn.github.io/ember-cli-addon-docs/docs/deploying 8 | // for details on configuration you can override here. 9 | }; 10 | -------------------------------------------------------------------------------- /config/deploy.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | module.exports = function(deployTarget) { 5 | let ENV = { 6 | build: {} 7 | // include other plugin configuration that applies to all deploy targets here 8 | }; 9 | 10 | if (deployTarget === 'development') { 11 | ENV.build.environment = 'development'; 12 | // configure other plugins for development deploy target here 13 | } 14 | 15 | if (deployTarget === 'staging') { 16 | ENV.build.environment = 'production'; 17 | // configure other plugins for staging deploy target here 18 | } 19 | 20 | if (deployTarget === 'production') { 21 | ENV.build.environment = 'production'; 22 | // configure other plugins for production deploy target here 23 | } 24 | 25 | // Note: if you need to build some configuration asynchronously, you can return 26 | // a promise that resolves with the ENV object instead of returning the 27 | // ENV object synchronously. 28 | return ENV; 29 | }; 30 | -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getChannelURL = require('ember-source-channel-url'); 4 | 5 | module.exports = async function() { 6 | return { 7 | useYarn: true, 8 | scenarios: [ 9 | { 10 | name: 'ember-lts-3.12', 11 | npm: { 12 | devDependencies: { 13 | 'ember-source': '~3.12.0' 14 | } 15 | } 16 | }, 17 | { 18 | name: 'ember-lts-3.16', 19 | npm: { 20 | devDependencies: { 21 | 'ember-source': '~3.16.0' 22 | } 23 | } 24 | }, 25 | { 26 | name: 'ember-release', 27 | npm: { 28 | devDependencies: { 29 | 'ember-source': await getChannelURL('release') 30 | } 31 | } 32 | }, 33 | { 34 | name: 'ember-beta', 35 | npm: { 36 | devDependencies: { 37 | 'ember-source': await getChannelURL('beta') 38 | } 39 | } 40 | }, 41 | { 42 | name: 'ember-canary', 43 | npm: { 44 | devDependencies: { 45 | 'ember-source': await getChannelURL('canary') 46 | } 47 | } 48 | }, 49 | // The default `.travis.yml` runs this scenario via `yarn test`, 50 | // not via `ember try`. It's still included here so that running 51 | // `ember try:each` manually or from a customized CI config will run it 52 | // along with all the other scenarios. 53 | { 54 | name: 'ember-default', 55 | npm: { 56 | devDependencies: {} 57 | } 58 | }, 59 | { 60 | name: 'ember-default-with-jquery', 61 | env: { 62 | EMBER_OPTIONAL_FEATURES: JSON.stringify({ 63 | 'jquery-integration': true 64 | }) 65 | }, 66 | npm: { 67 | devDependencies: { 68 | '@ember/jquery': '^0.5.1' 69 | } 70 | } 71 | }, 72 | { 73 | name: 'ember-classic', 74 | env: { 75 | EMBER_OPTIONAL_FEATURES: JSON.stringify({ 76 | 'application-template-wrapper': true, 77 | 'default-async-observers': false, 78 | 'template-only-glimmer-components': false 79 | }) 80 | }, 81 | npm: { 82 | ember: { 83 | edition: 'classic' 84 | } 85 | } 86 | } 87 | ] 88 | }; 89 | }; 90 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(/* environment, appConfig */) { 4 | return { }; 5 | }; 6 | -------------------------------------------------------------------------------- /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 | snippetPaths: ['tests/dummy/app','addon'] 9 | }); 10 | 11 | /* 12 | This build file specifies the options for the dummy test app of this 13 | addon, located in `/tests/dummy` 14 | This build file does *not* influence how the addon or the app using it 15 | behave. You most likely want to be modifying `./index.js` or app's build file 16 | */ 17 | 18 | return app.toTree(); 19 | }; 20 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | name: require('./package').name, 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jftechnology/ember-keycloak-auth", 3 | "version": "1.0.2", 4 | "description": "Keycloak JS Adapter as an Ember service and utilities", 5 | "keywords": [ 6 | "authentication", 7 | "keycloak", 8 | "oauth2", 9 | "services", 10 | "components", 11 | "helpers", 12 | "ember-addon", 13 | "octane" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/JFTechnology/ember-keycloak-auth" 18 | }, 19 | "license": "MIT", 20 | "author": "stephen.flynn@jftechnology.com", 21 | "directories": { 22 | "doc": "doc", 23 | "test": "tests" 24 | }, 25 | "scripts": { 26 | "build": "ember build --environment=production", 27 | "lint:hbs": "ember-template-lint .", 28 | "lint:js": "eslint .", 29 | "start": "ember serve", 30 | "test": "ember test && yarn lint:js && yarn lint:hbs", 31 | "test:all": "ember try:each", 32 | "preversion": "yarn test", 33 | "postversion": "git push && git push --tags && yarn publish && echo \"Successfully released version $npm_package_version!\"", 34 | "prepublishOnly": "ember ts:precompile", 35 | "postpublish": "ember ts:clean" 36 | }, 37 | "url": "https://github.com/JFTechnology/ember-keycloak-auth/issues", 38 | "email": "support@jftechnology.com", 39 | "dependencies": { 40 | "ember-auto-import": "^1.5.3", 41 | "ember-cli-babel": "^7.17.2", 42 | "ember-cli-htmlbars": "^4.2.2", 43 | "ember-cli-typescript": "^3.1.3", 44 | "keycloak-js": "^9.0.0" 45 | }, 46 | "devDependencies": { 47 | "@ember/optional-features": "^1.3.0", 48 | "@ember/render-modifiers": "^1.0.2", 49 | "@glimmer/component": "^1.0.0", 50 | "@glimmer/tracking": "^1.0.0", 51 | "@types/ember": "^3.1.1", 52 | "@types/ember-data": "^3.1.9", 53 | "@types/ember-qunit": "^3.4.7", 54 | "@types/ember__test-helpers": "^0.7.9", 55 | "@types/qunit": "^2.9.0", 56 | "@types/rsvp": "^4.0.3", 57 | "babel-eslint": "^10.0.3", 58 | "broccoli-asset-rev": "^3.0.0", 59 | "ember-cli": "~3.16.0", 60 | "ember-cli-addon-docs": "^0.6.16", 61 | "ember-cli-addon-docs-yuidoc": "~0.2.3", 62 | "ember-cli-dependency-checker": "^3.2.0", 63 | "ember-cli-deploy": "^1.0.2", 64 | "ember-cli-deploy-build": "^1.1.1", 65 | "ember-cli-deploy-git": "^1.3.3", 66 | "ember-cli-deploy-git-ci": "^1.0.1", 67 | "ember-cli-inject-live-reload": "^2.0.2", 68 | "ember-cli-mirage": "~1.1.0", 69 | "ember-cli-sri": "^2.1.1", 70 | "ember-cli-template-lint": "^2.0.0", 71 | "ember-cli-typescript-blueprints": "^3.0.0", 72 | "ember-cli-uglify": "^3.0.0", 73 | "ember-cookies": "^0.5.2", 74 | "ember-data": "~3.16.0", 75 | "ember-decorators": "~6.1.1", 76 | "ember-disable-prototype-extensions": "^1.1.3", 77 | "ember-export-application-global": "^2.0.1", 78 | "ember-fetch": "^7.0.0", 79 | "ember-load-initializers": "^2.1.1", 80 | "ember-maybe-import-regenerator": "^0.1.6", 81 | "ember-qunit": "^4.6.0", 82 | "ember-resolver": "^7.0.0", 83 | "ember-source": "~3.16.0", 84 | "ember-source-channel-url": "^2.0.1", 85 | "ember-try": "^1.4.0", 86 | "eslint": "^6.8.0", 87 | "eslint-plugin-ember": "^7.7.2", 88 | "eslint-plugin-node": "^11.0.0", 89 | "loader.js": "^4.7.0", 90 | "qunit-dom": "^1.0.0", 91 | "typescript": "~3.7.2" 92 | }, 93 | "engines": { 94 | "node": "10.* || >= 12" 95 | }, 96 | "ember": { 97 | "edition": "octane" 98 | }, 99 | "ember-addon": { 100 | "configPath": "tests/dummy/config" 101 | }, 102 | "homepage": "https://JFTechnology.github.io/ember-keycloak-auth" 103 | } 104 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | test_page: 'tests/index.html?hidepassed', 3 | disable_watching: true, 4 | launch_in_ci: [ 5 | 'Chrome' 6 | ], 7 | launch_in_dev: [ 8 | 'Chrome' 9 | ], 10 | browser_start_timeout: 120, 11 | browser_args: { 12 | Chrome: { 13 | ci: [ 14 | // --no-sandbox is needed when running Chrome inside a container 15 | process.env.CI ? '--no-sandbox' : null, 16 | '--headless', 17 | '--disable-dev-shm-usage', 18 | '--disable-software-rasterizer', 19 | '--mute-audio', 20 | '--remote-debugging-port=0', 21 | '--window-size=1440,900' 22 | ].filter(Boolean) 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /tests/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | embertest: true 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /tests/dummy/app/adapters/application.ts: -------------------------------------------------------------------------------- 1 | import JSONAPIAdapter from 'ember-data/adapters/json-api'; 2 | 3 | import KeycloakSession from '@jftechnology/ember-keycloak-auth/services/keycloak-session'; 4 | 5 | import {inject as service} from "@ember/service"; 6 | import RSVP from "rsvp"; 7 | 8 | export default class ApplicationAdapter extends JSONAPIAdapter { 9 | 10 | /** 11 | * An injected keycloak session. 12 | * 13 | * @property keycloakSession 14 | * @type {KeycloakSession} 15 | */ 16 | @service 17 | keycloakSession!: KeycloakSession; 18 | 19 | get headers(): {} { 20 | console.log(`ApplicationAdapter :: headers() -> ${JSON.stringify(this.keycloakSession.headers, null, 2)}`); 21 | return this.keycloakSession.headers; 22 | } 23 | 24 | /** 25 | * Wrap the adapter ajax method to ensure that the call to the secured back end is made only after the session token has been updated. 26 | * 27 | * @method ajax 28 | * @param url {String} 29 | * @param type {String} 30 | * @param hash {Object} 31 | * @return {Promise} 32 | * @private 33 | */ 34 | ajax(url: string, type: string, hash: {}): RSVP.Promise { 35 | console.log(`ApplicationAdapter :: ajax(${url} / ${type} / ${JSON.stringify(hash, null, 2)})`); 36 | return this.keycloakSession.wrappedCall(() => super.ajax(url, type, hash)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/dummy/app/adapters/json-api-adapter-snippet.js: -------------------------------------------------------------------------------- 1 | import JSONAPIAdapter from 'ember-data/adapters/json-api'; 2 | 3 | import {inject as service} from "@ember/service"; 4 | 5 | export default class ApplicationAdapter extends JSONAPIAdapter { 6 | 7 | /** 8 | * An injected keycloak session. 9 | * 10 | * @property keycloakSession 11 | * @type {KeycloakSession} 12 | */ 13 | @service 14 | keycloakSession; 15 | 16 | /** 17 | * Keycloak session will present the current token as Authentication headers. 18 | * @return {Object} 19 | */ 20 | get headers() { 21 | return this.keycloakSession.headers; 22 | } 23 | 24 | /** 25 | * Wrap the JSONAPIAdapter ajax method to ensure that calls to a secured back end is made only after the session token has been updated. 26 | * 27 | * @method ajax 28 | * @param url {String} 29 | * @param type {String} 30 | * @param hash {Object} 31 | * @return {Promise} 32 | */ 33 | ajax(url, type, hash) { 34 | return this.keycloakSession.wrappedCall(() => super.ajax(url, type, hash)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /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 './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 | -------------------------------------------------------------------------------- /tests/dummy/app/components/demo-configuration.hbs: -------------------------------------------------------------------------------- 1 |
5 | 6 |
7 |
8 |
9 | 10 | 15 |
16 |
17 |
18 | 19 | 20 |
21 |
22 | 23 | 24 |
25 |
26 | 35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | {{#each this.allCookies as |cookie|}} 46 | 47 | 48 | 49 | 50 | {{/each}} 51 | 52 |
Cookie NameValue
{{cookie.name}}{{cookie.value}}
53 | -------------------------------------------------------------------------------- /tests/dummy/app/components/demo-configuration.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | 3 | import {tracked} from '@glimmer/tracking'; 4 | import {inject as service} from '@ember/service'; 5 | import {action, computed} from '@ember/object'; 6 | 7 | export default class DemoConfiguration extends Component { 8 | 9 | @service 10 | keycloakSession; 11 | 12 | @service 13 | cookies; 14 | 15 | @tracked 16 | url; 17 | 18 | @tracked 19 | realm; 20 | 21 | @tracked 22 | clientId; 23 | 24 | @computed('cookies') 25 | get allCookies() { 26 | 27 | let cookies = this.cookies.read(); 28 | 29 | return Object.keys(cookies).reduce((acc, key) => { 30 | let value = cookies[key]; 31 | acc.push({name: key, value}); 32 | 33 | return acc; 34 | }, []); 35 | } 36 | 37 | @action 38 | didInsert() { 39 | 40 | this.url = this.cookies.read('keycloak-url'); 41 | this.realm = this.cookies.read('keycloak-realm'); 42 | this.clientId = this.cookies.read('keycloak-clientId'); 43 | } 44 | 45 | @action 46 | initKeycloak() { 47 | 48 | let url = this.url; 49 | let realm = this.realm; 50 | let clientId = this.clientId; 51 | 52 | if (url && realm && clientId) { 53 | 54 | // save details as cookies for subsequent initializations 55 | this.cookies.write('keycloak-url', url); 56 | this.cookies.write('keycloak-realm', realm); 57 | this.cookies.write('keycloak-clientId', clientId); 58 | 59 | this.keycloakSession.installKeycloak({url, realm, clientId}); 60 | this.keycloakSession.initKeycloak(); 61 | 62 | } else { 63 | 64 | alert('Config details incomplete'); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/dummy/app/config/environment.d.ts: -------------------------------------------------------------------------------- 1 | export default config; 2 | 3 | /** 4 | * Type declarations for 5 | * import config from './config/environment' 6 | * 7 | * For now these need to be managed by the developer 8 | * since different ember addons can materialize new entries. 9 | */ 10 | declare const config: { 11 | environment: any; 12 | modulePrefix: string; 13 | podModulePrefix: string; 14 | locationType: string; 15 | rootURL: string; 16 | }; 17 | -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | 17 | 18 | 21 | 22 | {{content-for "head-footer"}} 23 | 24 | 25 | {{content-for "body"}} 26 | 27 | 28 | 29 | 30 | {{content-for "body-footer"}} 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/dummy/app/models/model-a.js: -------------------------------------------------------------------------------- 1 | import Model from 'ember-data/model'; 2 | 3 | import attr from 'ember-data/attr'; 4 | 5 | export default class ModelA extends Model { 6 | 7 | @attr('string') 8 | name; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /tests/dummy/app/models/model-b.js: -------------------------------------------------------------------------------- 1 | import Model from 'ember-data/model'; 2 | 3 | import attr from 'ember-data/attr'; 4 | 5 | export default class ModelB extends Model { 6 | 7 | @attr('string') 8 | name; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /tests/dummy/app/pods/application/template.hbs: -------------------------------------------------------------------------------- 1 | {{! template-lint-disable no-bare-strings }} 2 | 3 | {{#header.link "config"}} 4 | Server configuration 5 | {{/header.link}} 6 | {{#header.link "demo"}} 7 | Demo routes 8 | {{/header.link}} 9 | 10 | 11 | 12 | {{outlet}} -------------------------------------------------------------------------------- /tests/dummy/app/pods/config/template.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | Server details 6 |
7 | 8 |
9 |
10 |
11 | Configuration 12 |
13 | 14 |
15 |
16 |
17 | 18 | -------------------------------------------------------------------------------- /tests/dummy/app/pods/demo-error/template.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | {{@model}} 6 |

7 |
8 |
9 |
-------------------------------------------------------------------------------- /tests/dummy/app/pods/demo/controller.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | import {inject as service} from '@ember/service'; 4 | import {action, computed} from '@ember/object'; 5 | 6 | export default class DemoController extends Controller { 7 | 8 | @service 9 | keycloakSession; 10 | 11 | @service 12 | cookies; 13 | 14 | @computed() 15 | get allCookies() { 16 | 17 | let cookieService = this.get('cookies'); 18 | 19 | let cookies = cookieService.read(); 20 | 21 | return Object.keys(cookies).reduce((acc, key) => { 22 | let value = cookies[key]; 23 | acc.push({name: key, value}); 24 | 25 | return acc; 26 | }, []); 27 | } 28 | 29 | init() { 30 | 31 | super.init(...arguments); 32 | 33 | let cookies = this.cookies; 34 | 35 | this.set('url', cookies.read('keycloak-url')); 36 | this.set('realm', cookies.read('keycloak-realm')); 37 | this.set('clientId', cookies.read('keycloak-clientId')); 38 | } 39 | 40 | @action 41 | initKeycloak() { 42 | 43 | let session = this.keycloakSession; 44 | let cookies = this.cookies; 45 | 46 | let url = this.get('url'); 47 | let realm = this.get('realm'); 48 | let clientId = this.get('clientId'); 49 | 50 | // save details as cookies for subsequent initializations 51 | cookies.write('keycloak-url', url); 52 | cookies.write('keycloak-realm', realm); 53 | cookies.write('keycloak-clientId', clientId); 54 | 55 | if (url && realm && clientId) { 56 | 57 | let options = { 58 | url, 59 | realm, 60 | clientId, 61 | }; 62 | 63 | session.installKeycloak(options); 64 | session.initKeycloak(); 65 | 66 | } else { 67 | 68 | alert('Config details incomplete'); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/dummy/app/pods/demo/logged-in/template.hbs: -------------------------------------------------------------------------------- 1 |

Default logged in route

2 | -------------------------------------------------------------------------------- /tests/dummy/app/pods/demo/logged-out/template.hbs: -------------------------------------------------------------------------------- 1 |

Default logged out route

2 | -------------------------------------------------------------------------------- /tests/dummy/app/pods/demo/model-a/index/template.hbs: -------------------------------------------------------------------------------- 1 |

2 | Keycloak protected route -> Model A 3 |

4 |

5 | route name : 'model-a.index' 6 |

7 |

8 | model name : {{@model.name}} 9 |

-------------------------------------------------------------------------------- /tests/dummy/app/pods/demo/model-a/model-b/route.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default class ModelBRoute extends Route { 4 | 5 | /* 6 | * Implements the RouteInfo Metadata API 7 | */ 8 | buildRouteInfoMetadata() { 9 | return { 10 | updateKeycloakToken: true, 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/dummy/app/pods/demo/model-a/model-b/template.hbs: -------------------------------------------------------------------------------- 1 |

2 | Keycloak protected route -> Model B 3 |

4 |

5 | route name : 'model-a.model.b' 6 |

7 |

8 | model name : {{@model.name}} 9 |

-------------------------------------------------------------------------------- /tests/dummy/app/pods/demo/model-a/route.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default class ModelARoute extends Route { 4 | 5 | /* 6 | * Implements the RouteInfo Metadata API 7 | */ 8 | buildRouteInfoMetadata() { 9 | return { 10 | updateKeycloakToken: true, 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/dummy/app/pods/demo/route-snippet.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | import { inject as service } from '@ember/service'; 4 | 5 | export default class ApplicationRoute extends Route { 6 | 7 | @service 8 | keycloakSession; 9 | 10 | /** 11 | * Collect keycloak options and install the Keycloak service. 12 | */ 13 | init() { 14 | 15 | super.init(...arguments); 16 | 17 | let options = { 18 | url: 'https://auth.myserver.com/auth', 19 | realm: 'my-realm', 20 | clientId: 'my-client-id', 21 | }; 22 | 23 | this.keycloakSession.verbose = true; 24 | this.keycloakSession.installKeycloak(options); 25 | } 26 | 27 | /** 28 | * Use before model hook to initiate the wrapped Keycloak service. This returns a promise that the framework will 29 | * resolve before the application transitions to child routes. 30 | */ 31 | beforeModel() { 32 | return this.keycloakSession.initKeycloak(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/dummy/app/pods/demo/route.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | import {inject as service} from '@ember/service'; 4 | 5 | export default class ApplicationRoute extends Route { 6 | 7 | @service 8 | keycloakSession; 9 | 10 | @service 11 | store; 12 | 13 | @service() 14 | cookies; 15 | 16 | init() { 17 | 18 | super.init(...arguments); 19 | 20 | // if required constructor parameters are available as cookies go ahead in init the service. 21 | // this should be replaced by initialization code when used in an application 22 | let cookies = this.cookies; 23 | 24 | let url = cookies.read('keycloak-url'); 25 | let realm = cookies.read('keycloak-realm'); 26 | let clientId = cookies.read('keycloak-clientId'); 27 | 28 | if (url && realm && clientId) { 29 | 30 | let options = { 31 | url, 32 | realm, 33 | clientId, 34 | }; 35 | 36 | this.keycloakSession.verbose = true; 37 | this.keycloakSession.installKeycloak(options); 38 | } 39 | } 40 | 41 | beforeModel() { 42 | return this.keycloakSession.initKeycloak(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/dummy/app/pods/demo/status/template.hbs: -------------------------------------------------------------------------------- 1 |

2 | Status route 3 |

4 | 5 |
6 |
7 |
Keycloak session status component
8 | 9 |
10 |
11 |
Keycloak session profile component
12 | 13 |
14 |
-------------------------------------------------------------------------------- /tests/dummy/app/pods/demo/template.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 |

7 | Demo routes 8 |

9 | 10 |
11 | 12 | 13 | Status 14 | 15 | 16 | 17 | 18 | Route to model A 19 | 20 | 21 | Adapter will refresh token before ajax call 22 | 23 | 24 | 25 | 26 | Route to nested model B 27 | 28 | 29 | Adapter will refresh token before ajax calls 30 | 31 | 32 | 33 | 34 | Unprotected route 35 | 36 | 37 |
38 |
39 |
40 | 41 |
42 |
43 | {{outlet}} 44 |
45 |
46 |
47 | 48 | -------------------------------------------------------------------------------- /tests/dummy/app/pods/demo/unprotected/route.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default class UnprotectedRoute extends Route { 4 | } 5 | -------------------------------------------------------------------------------- /tests/dummy/app/pods/demo/unprotected/template.hbs: -------------------------------------------------------------------------------- 1 |

2 | Keycloak unprotected route 3 |

4 |

5 | route name : 'unprotected' 6 |

-------------------------------------------------------------------------------- /tests/dummy/app/pods/docs/helpers/template.md: -------------------------------------------------------------------------------- 1 | ## Role helpers 2 | 3 | This addon provides two simple helpers. 4 | 5 | ### `{{in-role 'role' ['resource']}}` 6 | 7 | Helper returns true if the current access-token grants the subject realm or resource role permission. 8 | 9 | ### `{{not-in-role 'role' ['resource']}}` 10 | 11 | Helper returns true if the current access-token does not grant the subject realm or resource role permission. 12 | -------------------------------------------------------------------------------- /tests/dummy/app/pods/docs/index/template.md: -------------------------------------------------------------------------------- 1 | ## Configuring the service in an Ember application 2 | 3 | The keycloak session service needs to be initialised. One place to do this might be in the application route (this could also be done via initializers)... 4 | 5 | 6 | {{demo.snippet name="pods/demo/route-snippet.js" label="Application route"}} 7 | 8 | 9 | ## Ember data 10 | 11 | Calls to an OAuth2 secured back end via Ember Data adapters typically require an Authorization bearer header... 12 | 13 | { 14 | Authorization: "Bearer 1234...etc" 15 | } 16 | 17 | This header can be obtained from the 'headers' property on the Keycloak session service. However it is before each call 18 | to the secured back end is made we need ensure that the token is refreshed - and refresh it if stale (this is an asynchronous task). 19 | 20 | The simplest way to do this is to intercept the JSONAPIAdapter (or RESTAdapter) ajax method. All backend requests pass 21 | through this method which returns a Promise to the Ember data framework. The Keycloak session service provides a wrappedCall() 22 | method which adds a token refresh check to the Promise chain to ensure that a fresh token is avai. 23 | 24 | The following snippet shows the few lines of code that need to be added to a JSONAPIAdapter to refresh the token (if required) 25 | and ensure that the header is up-to-date... 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/dummy/app/pods/docs/template.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{nav.item "Introduction" "docs.index"}} 5 | {{nav.item "Helpers" "docs.helpers"}} 6 | {{nav.item "Test support" "docs.test-support"}} 7 | 8 | 9 | 10 | {{outlet}} 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/dummy/app/pods/docs/test-support/template.md: -------------------------------------------------------------------------------- 1 | # Test support 2 | -------------------------------------------------------------------------------- /tests/dummy/app/pods/index/template.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import AddonDocsRouter, {docsRoute} from 'ember-cli-addon-docs/router'; 2 | import config from './config/environment'; 3 | 4 | export default class Router extends AddonDocsRouter { 5 | location = config.locationType; 6 | rootURL = config.rootURL; 7 | } 8 | 9 | Router.map(function() { 10 | 11 | docsRoute(this, function() { 12 | this.route('index'); 13 | this.route('helpers'); 14 | this.route('test-support'); 15 | this.route('usage'); 16 | }); 17 | 18 | this.route('config'); 19 | 20 | this.route('demo', function() { 21 | 22 | this.route('status'); 23 | this.route('logged-out'); 24 | this.route('logged-in'); 25 | 26 | this.route('unprotected'); 27 | 28 | this.route('model-a', {path: '/model-a/:model_a_id'}, function() { 29 | this.route('model-b', {path: '/model-b/:model_b_id'}); 30 | }); 31 | }); 32 | 33 | this.route('not-found', {path: '/*path'}); 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /tests/dummy/app/serializers/application.js: -------------------------------------------------------------------------------- 1 | export { default } from '@ember-data/serializer/json-api'; 2 | -------------------------------------------------------------------------------- /tests/dummy/app/serializers/class.js: -------------------------------------------------------------------------------- 1 | import Serializer from '@ember-data/serializer/json'; 2 | 3 | export default Serializer; 4 | -------------------------------------------------------------------------------- /tests/dummy/app/serializers/component.js: -------------------------------------------------------------------------------- 1 | import Serializer from '@ember-data/serializer/json'; 2 | 3 | export default Serializer; 4 | -------------------------------------------------------------------------------- /tests/dummy/app/serializers/project.js: -------------------------------------------------------------------------------- 1 | import JSONAPISerializer from '@ember-data/serializer/json-api'; 2 | 3 | export default class ProjectSerializer extends JSONAPISerializer { 4 | 5 | keyForAttribute(key) { 6 | console.debug(`ProjectSerializer attribute ${key}`); 7 | return key; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JFTechnology/ember-keycloak-auth/61684562605efc7fd4c5b74420f48323d60f226f/tests/dummy/app/styles/app.css -------------------------------------------------------------------------------- /tests/dummy/app/templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JFTechnology/ember-keycloak-auth/61684562605efc7fd4c5b74420f48323d60f226f/tests/dummy/app/templates/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(environment) { 4 | let ENV = { 5 | modulePrefix: 'dummy', 6 | podModulePrefix: 'dummy/pods', 7 | environment, 8 | rootURL: '/', 9 | locationType: 'auto', 10 | EmberENV: { 11 | FEATURES: { 12 | // Here you can enable experimental features on an ember canary build 13 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 14 | }, 15 | EXTEND_PROTOTYPES: { 16 | // Prevent Ember Data from overriding Date.parse. 17 | Date: false, 18 | }, 19 | }, 20 | 21 | APP: { 22 | // Here you can pass flags/options to your application instance 23 | // when it is created 24 | }, 25 | }; 26 | 27 | if (environment === 'development') { 28 | // ENV.APP.LOG_RESOLVER = true; 29 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 30 | // ENV.APP.LOG_TRANSITIONS = true; 31 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 32 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 33 | } 34 | 35 | if (environment === 'test') { 36 | // Testem prefers this... 37 | ENV.locationType = 'none'; 38 | 39 | // keep test console output quieter 40 | ENV.APP.LOG_ACTIVE_GENERATION = false; 41 | ENV.APP.LOG_VIEW_LOOKUPS = false; 42 | 43 | ENV.APP.rootElement = '#ember-testing'; 44 | ENV.APP.autoboot = false; 45 | } 46 | 47 | if (environment === 'production') { 48 | // Allow ember-cli-addon-docs to update the rootURL in compiled assets 49 | ENV.rootURL = 'ADDON_DOCS_ROOT_URL'; 50 | 51 | // here you can enable a production-specific feature 52 | } 53 | 54 | return ENV; 55 | }; 56 | -------------------------------------------------------------------------------- /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/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 | const isCI = !!process.env.CI; 10 | const isProduction = process.env.EMBER_ENV === 'production'; 11 | 12 | if (isCI || isProduction) { 13 | browsers.push('ie 11'); 14 | } 15 | 16 | module.exports = { 17 | browsers 18 | }; 19 | -------------------------------------------------------------------------------- /tests/dummy/mirage/config.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | 3 | // These comments are here to help you get started. Feel free to delete them. 4 | 5 | /* 6 | Config (with defaults). 7 | 8 | Note: these only affect routes defined *after* them! 9 | */ 10 | 11 | // this.urlPrefix = ''; // make this `http://localhost:8080`, for example, if your API is on a different server 12 | // this.namespace = '/api'; // make this `/api`, for example, if your API is namespaced 13 | // this.timing = 400; // delay for each request, automatically set to 0 during testing 14 | 15 | this.get('/model-as'); 16 | this.get('/model-as/:id'); 17 | this.post('/model-as'); 18 | 19 | this.get('/model-bs'); 20 | this.get('/model-bs/:id'); 21 | this.post('/model-bs'); 22 | 23 | // addon documentation not handled by mirage 24 | this.passthrough('/docs/**'); 25 | 26 | // POST requests will all be OAuth2 related - so they get passed through to configured Keycloak server 27 | this.passthrough(request => { 28 | // console.log(`passthrough ${JSON.stringify(request, null, 2)}`); 29 | if (request.url === "/model-as" || request.url === "/model-as/1" || request.url === "/model-bs") { 30 | return false; 31 | } 32 | return request.method === "POST" || request.url.startsWith("https://"); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /tests/dummy/mirage/scenarios/default.js: -------------------------------------------------------------------------------- 1 | export default function(server) { 2 | 3 | /* 4 | Seed your development database using your factories. 5 | This data will not be loaded in your tests. 6 | */ 7 | server.createList('model-a', 2, {name: 'Model A instance'}); 8 | server.createList('model-b', 2, {name: 'Model B instance'}); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /tests/dummy/mirage/serializers/application.js: -------------------------------------------------------------------------------- 1 | import { JSONAPISerializer } from 'ember-cli-mirage'; 2 | 3 | export default JSONAPISerializer.extend({ 4 | }); 5 | -------------------------------------------------------------------------------- /tests/dummy/public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /tests/helpers/destroy-app.js: -------------------------------------------------------------------------------- 1 | import { run } from '@ember/runloop'; 2 | 3 | export default function destroyApp(application) { 4 | run(application, 'destroy'); 5 | if (window.server) { 6 | window.server.shutdown(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/helpers/module-for-acceptance.js: -------------------------------------------------------------------------------- 1 | import { Promise } from 'rsvp'; 2 | import { module } from 'qunit'; 3 | import startApp from '../helpers/start-app'; 4 | import destroyApp from '../helpers/destroy-app'; 5 | 6 | export default function(name, options = {}) { 7 | module(name, { 8 | beforeEach() { 9 | this.application = startApp(); 10 | 11 | if (options.beforeEach) { 12 | return options.beforeEach.apply(this, arguments); 13 | } 14 | }, 15 | 16 | afterEach() { 17 | let afterEach = options.afterEach && options.afterEach.apply(this, arguments); 18 | return Promise.resolve(afterEach).then(() => destroyApp(this.application),err=>console.warn(err)); 19 | }, 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from '../../resolver'; 2 | import config from '../../config/environment'; 3 | 4 | const resolver = Resolver.create(); 5 | 6 | resolver.namespace = { 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix, 9 | }; 10 | 11 | export default resolver; 12 | -------------------------------------------------------------------------------- /tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import Application from '../../app'; 2 | import config from '../../config/environment'; 3 | import { merge } from '@ember/polyfills'; 4 | import { run } from '@ember/runloop'; 5 | 6 | export default function startApp(attrs) { 7 | 8 | let attributes = merge({}, config.APP); 9 | attributes = merge(attributes, attrs); // use defaults, but you can override; 10 | 11 | return run(() => { 12 | let application = Application.create(attributes); 13 | application.setupForTesting(); 14 | application.injectTestHelpers(); 15 | return application; 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /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 | 26 | 27 | 28 | 29 | 30 | {{content-for "body-footer"}} 31 | {{content-for "test-body-footer"}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/integration/components/keycloak-session-config-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import { hbs } from 'ember-cli-htmlbars'; 5 | 6 | import {setupKeycloakSession} from '@jftechnology/ember-keycloak-auth/test-support'; 7 | 8 | module('Integration | Component | keycloak-session-config', function(hooks) { 9 | 10 | setupRenderingTest(hooks); 11 | setupKeycloakSession(hooks); 12 | 13 | test('test rendered output', async function(assert) { 14 | 15 | assert.expect(14); 16 | 17 | await render(hbs`{{keycloak-session-config}}`); 18 | 19 | assert.dom(this.element.querySelector('table tbody tr:nth-child(1) th')).hasText('minValidity'); 20 | assert.dom(this.element.querySelector('table tbody tr:nth-child(1) td')).hasText('30'); 21 | 22 | assert.dom(this.element.querySelector('table tbody tr:nth-child(2) th')).hasText('onLoad'); 23 | assert.dom(this.element.querySelector('table tbody tr:nth-child(2) td')).hasText('login-required'); 24 | 25 | assert.dom(this.element.querySelector('table tbody tr:nth-child(3) th')).hasText('responseMode'); 26 | assert.dom(this.element.querySelector('table tbody tr:nth-child(3) td')).hasText('fragment'); 27 | 28 | assert.dom(this.element.querySelector('table tbody tr:nth-child(4) th')).hasText('flow'); 29 | assert.dom(this.element.querySelector('table tbody tr:nth-child(4) td')).hasText('standard'); 30 | 31 | assert.dom(this.element.querySelector('table tbody tr:nth-child(5) th')).hasText('checkLoginIframe'); 32 | assert.dom(this.element.querySelector('table tbody tr:nth-child(5) td')).hasText('false'); 33 | 34 | assert.dom(this.element.querySelector('table tbody tr:nth-child(6) th')).hasText('checkLoginIframeInterval'); 35 | assert.dom(this.element.querySelector('table tbody tr:nth-child(6) td')).hasText('5'); 36 | 37 | assert.dom(this.element.querySelector('table tbody tr:nth-child(7) th')).hasText('idpHint'); 38 | assert.dom(this.element.querySelector('table tbody tr:nth-child(7) td')).hasText(''); 39 | 40 | }); 41 | 42 | }); 43 | 44 | 45 | -------------------------------------------------------------------------------- /tests/integration/components/keycloak-session-link-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import { hbs } from 'ember-cli-htmlbars'; 5 | 6 | import {setupKeycloakSession} from '@jftechnology/ember-keycloak-auth/test-support'; 7 | 8 | module('Integration | Component | keycloak session link', function(hooks) { 9 | 10 | setupRenderingTest(hooks); 11 | setupKeycloakSession(hooks); 12 | 13 | test('test rendered output', async function(assert) { 14 | 15 | assert.expect(3); 16 | 17 | let service = this.owner.lookup('service:keycloak-session'); 18 | 19 | await render(hbs`{{keycloak-session-link}}`); 20 | 21 | assert.dom(this.element).hasText('No session'); 22 | 23 | await service.initKeycloak(); 24 | await render(hbs`{{keycloak-session-link}}`); 25 | 26 | assert.dom(this.element).hasText('Sign in'); 27 | 28 | await service.login(); 29 | await render(hbs`{{keycloak-session-link}}`); 30 | 31 | assert.dom(this.element).hasText('Sign out'); 32 | 33 | }); 34 | 35 | test('it renders block', async function(assert) { 36 | 37 | let service = this.owner.lookup('service:keycloak-session'); 38 | 39 | assert.expect(3); 40 | 41 | // Template block usage: 42 | await render(hbs`{{#keycloak-session-link}}xyz{{/keycloak-session-link}}`); 43 | 44 | assert.dom(this.element).hasText('No session'); 45 | 46 | await service.initKeycloak(); 47 | await render(hbs`{{#keycloak-session-link}}xyz{{/keycloak-session-link}}`); 48 | 49 | assert.dom(this.element).hasText('Sign in'); 50 | 51 | await service.login(); 52 | await render(hbs`{{#keycloak-session-link}}xyz{{/keycloak-session-link}}`); 53 | 54 | assert.dom(this.element).hasText('Sign out'); 55 | 56 | }); 57 | 58 | }); 59 | 60 | 61 | -------------------------------------------------------------------------------- /tests/integration/components/keycloak-session-profile-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import { hbs } from 'ember-cli-htmlbars'; 5 | 6 | import {setupKeycloakSession} from '@jftechnology/ember-keycloak-auth/test-support'; 7 | 8 | module('Integration | Component | keycloak session profile', function(hooks) { 9 | 10 | setupRenderingTest(hooks); 11 | setupKeycloakSession(hooks); 12 | 13 | test('test rendered output', async function(assert) { 14 | 15 | let service = this.owner.lookup('service:keycloak-session'); 16 | 17 | assert.expect(3); 18 | 19 | await render(hbs`{{keycloak-session-profile}}`); 20 | 21 | assert.dom(this.element).hasText('No session'); 22 | 23 | await service.initKeycloak(); 24 | await render(hbs`{{keycloak-session-profile}}`); 25 | 26 | assert.dom(this.element.querySelector('.btn:nth-child(1)')).hasText('Load user profile'); 27 | 28 | await service.login(); 29 | await render(hbs`{{keycloak-session-profile}}`); 30 | 31 | assert.dom(this.element.querySelector('.btn:nth-child(1)')).hasText('Load user profile'); 32 | 33 | }); 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /tests/integration/components/keycloak-session-status-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import { hbs } from 'ember-cli-htmlbars'; 5 | 6 | import { setupKeycloakSession } from '@jftechnology/ember-keycloak-auth/test-support'; 7 | 8 | module('Integration | Component | keycloak session status', function(hooks) { 9 | 10 | setupRenderingTest(hooks); 11 | setupKeycloakSession(hooks); 12 | 13 | test('test rendered output', async function(assert) { 14 | 15 | let service = this.owner.lookup('service:keycloak-session'); 16 | 17 | await render(hbs`{{keycloak-session-status}}`); 18 | 19 | assert.dom(this.element).hasText('No session'); 20 | 21 | await service.initKeycloak(); 22 | await render(hbs`{{keycloak-session-status}}`); 23 | 24 | assert.dom(this.element.querySelector('.btn:nth-child(1)')).hasText('Refresh'); 25 | assert.dom(this.element.querySelector('.btn:nth-child(2)')).hasText('Login'); 26 | assert.dom(this.element.querySelector('.btn:nth-child(3)')).hasText('Logout'); 27 | 28 | await service.login(); 29 | await render(hbs`{{keycloak-session-status}}`); 30 | 31 | assert.dom(this.element.querySelector('.btn:nth-child(1)')).hasText('Refresh'); 32 | assert.dom(this.element.querySelector('.btn:nth-child(2)')).hasText('Login'); 33 | assert.dom(this.element.querySelector('.btn:nth-child(3)')).hasText('Logout'); 34 | 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /tests/integration/helpers/in-role-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import { hbs } from 'ember-cli-htmlbars'; 5 | 6 | import { login, setupKeycloakSession } from '@jftechnology/ember-keycloak-auth/test-support'; 7 | 8 | module('Integration | Helper | in-role', function(hooks) { 9 | 10 | setupRenderingTest(hooks); 11 | setupKeycloakSession(hooks); 12 | 13 | test('check realm role #1', async function(assert) { 14 | 15 | assert.expect(1); 16 | 17 | await login(this.owner); 18 | 19 | this.set('role', 'realm-role-1'); 20 | 21 | await render(hbs`yes {{#if (in-role role)}}yes{{/if}} yes`); 22 | 23 | assert.equal(this.element.textContent.trim(), 'yes yes yes'); 24 | }); 25 | 26 | 27 | test('check realm role #2', async function(assert) { 28 | 29 | assert.expect(1); 30 | 31 | await login(this.owner); 32 | 33 | this.set('role', 'not-a-realm-role'); 34 | 35 | await render(hbs`no {{#if (in-role role)}}yes{{/if}} no`); 36 | 37 | assert.equal(this.element.textContent.trim(), 'no no'); 38 | }); 39 | 40 | test('check resource role #1', async function(assert) { 41 | 42 | assert.expect(1); 43 | 44 | await login(this.owner); 45 | 46 | this.set('resource', 'resource-A'); 47 | this.set('role', 'resource-A-role-1'); 48 | 49 | await render(hbs`yes {{#if (in-role role resource)}}yes{{/if}} yes`); 50 | 51 | assert.equal(this.element.textContent.trim(), 'yes yes yes'); 52 | }); 53 | 54 | test('check resource role #2', async function(assert) { 55 | 56 | assert.expect(1); 57 | 58 | await login(this.owner); 59 | 60 | this.set('resource', 'resource-A'); 61 | this.set('role', 'resource-B-role-1'); 62 | 63 | await render(hbs`no {{#if (in-role role resource)}}yes{{/if}} no`); 64 | 65 | assert.equal(this.element.textContent.trim(), 'no no'); 66 | }); 67 | 68 | }); 69 | -------------------------------------------------------------------------------- /tests/integration/helpers/not-in-role-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import { hbs } from 'ember-cli-htmlbars'; 5 | 6 | import { login, setupKeycloakSession } from '@jftechnology/ember-keycloak-auth/test-support'; 7 | 8 | module('Integration | Helper | not-in-role', function(hooks) { 9 | 10 | setupRenderingTest(hooks); 11 | setupKeycloakSession(hooks); 12 | 13 | test('check realm role #1', async function(assert) { 14 | 15 | assert.expect(1); 16 | 17 | await login(this.owner); 18 | 19 | this.set('role', 'realm-role-1'); 20 | 21 | await render(hbs`Not in role realm-role-1 : {{not-in-role role}}`); 22 | 23 | assert.equal(this.element.textContent.trim(), 'Not in role realm-role-1 : false'); 24 | }); 25 | 26 | 27 | test('check realm role #2', async function(assert) { 28 | 29 | assert.expect(1); 30 | 31 | await login(this.owner); 32 | 33 | this.set('role', 'not-a-realm-role'); 34 | 35 | await render(hbs`Not in role realm-role-1 : {{not-in-role role}}`); 36 | 37 | assert.equal(this.element.textContent.trim(), 'Not in role realm-role-1 : true'); 38 | }); 39 | 40 | test('check resource role #1', async function(assert) { 41 | 42 | assert.expect(1); 43 | 44 | await login(this.owner); 45 | 46 | this.set('resource', 'resource-A'); 47 | this.set('role', 'resource-A-role-1'); 48 | 49 | await render(hbs`yes {{#if (not-in-role role resource)}}yes{{/if}} yes`); 50 | 51 | assert.equal(this.element.textContent.trim(), 'yes yes'); 52 | }); 53 | 54 | test('check resource role #2', async function(assert) { 55 | 56 | assert.expect(1); 57 | 58 | await login(this.owner); 59 | 60 | this.set('resource', 'resource-A'); 61 | this.set('role', 'resource-B-role-1'); 62 | 63 | await render(hbs`no {{#if (not-in-role role resource)}}yes{{/if}} no`); 64 | 65 | assert.equal(this.element.textContent.trim(), 'no yes no'); 66 | }); 67 | 68 | }); 69 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from '../app'; 2 | import config from '../config/environment'; 3 | import { setApplication } from '@ember/test-helpers'; 4 | import { start } from 'ember-qunit'; 5 | 6 | setApplication(Application.create(config.APP)); 7 | 8 | start(); 9 | -------------------------------------------------------------------------------- /tests/unit/services/keycloak-session-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupTest } from 'ember-qunit'; 3 | import { setupKeycloakSession } from '@jftechnology/ember-keycloak-auth/test-support'; 4 | 5 | module('Unit | Services | keycloak session', function(hooks) { 6 | 7 | setupTest(hooks); 8 | setupKeycloakSession(hooks); 9 | 10 | test('check login/logout cycle', function(assert) { 11 | 12 | assert.expect(9); 13 | 14 | let session = this.owner.lookup('service:keycloak-session'); 15 | 16 | assert.ok(session); 17 | 18 | assert.equal(session.ready, false); 19 | assert.equal(session.authenticated, false); 20 | 21 | session.initKeycloak(); 22 | 23 | assert.equal(session.ready, true); 24 | assert.equal(session.authenticated, false); 25 | 26 | session.login(); 27 | 28 | assert.equal(session.ready, true); 29 | assert.equal(session.authenticated, true); 30 | 31 | session.logout(); 32 | 33 | assert.equal(session.ready, true); 34 | assert.equal(session.authenticated, false); 35 | 36 | }); 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /tests/unit/services/mock-keycloak-session-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupTest } from 'ember-qunit'; 3 | 4 | import { setupKeycloakSession } from '@jftechnology/ember-keycloak-auth/test-support'; 5 | 6 | module('Unit | Services | mock keycloak session', function(hooks) { 7 | 8 | setupTest(hooks); 9 | setupKeycloakSession(hooks); 10 | 11 | test('service ok', async function(assert) { 12 | 13 | let service = this.owner.lookup('service:keycloak-session'); 14 | 15 | assert.ok(service); 16 | 17 | assert.equal(false, service.ready); 18 | assert.equal(false, service.authenticated); 19 | 20 | await service.initKeycloak(); 21 | assert.equal(true, service.ready); 22 | assert.equal(false, service.authenticated); 23 | 24 | await service.login(); 25 | assert.equal(true, service.ready); 26 | assert.equal(true, service.authenticated); 27 | 28 | await service.logout(); 29 | assert.equal(true, service.ready); 30 | assert.equal(false, service.authenticated); 31 | 32 | }); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "allowJs": true, 5 | "moduleResolution": "node", 6 | "allowSyntheticDefaultImports": true, 7 | "noImplicitAny": true, 8 | "noImplicitThis": true, 9 | "alwaysStrict": true, 10 | "strictNullChecks": true, 11 | "strictPropertyInitialization": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noEmitOnError": false, 17 | "noEmit": true, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "baseUrl": ".", 21 | "module": "es6", 22 | "experimentalDecorators": true, 23 | "paths": { 24 | "dummy/tests/*": [ 25 | "tests/*" 26 | ], 27 | "dummy/mirage/*": [ 28 | "tests/dummy/mirage/*" 29 | ], 30 | "dummy/*": [ 31 | "tests/dummy/app/*", 32 | "app/*" 33 | ], 34 | "@jftechnology/ember-keycloak-auth": [ 35 | "addon" 36 | ], 37 | "@jftechnology/ember-keycloak-auth/*": [ 38 | "addon/*" 39 | ], 40 | "@jftechnology/ember-keycloak-auth/test-support": [ 41 | "addon-test-support" 42 | ], 43 | "@jftechnology/ember-keycloak-auth/test-support/*": [ 44 | "addon-test-support/*" 45 | ], 46 | "*": [ 47 | "types/*" 48 | ] 49 | } 50 | }, 51 | "include": [ 52 | "app/**/*", 53 | "addon/**/*", 54 | "tests/**/*", 55 | "types/**/*", 56 | "test-support/**/*", 57 | "addon-test-support/**/*" 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /types/dummy/index.d.ts: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /types/ember-data/types/registries/model.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Catch-all for ember-data. 3 | */ 4 | export default interface ModelRegistry { 5 | [key: string]: any; 6 | } 7 | -------------------------------------------------------------------------------- /types/global.d.ts: -------------------------------------------------------------------------------- 1 | // Types for compiled templates 2 | declare module '@jftechnology/ember-keycloak-auth/templates/*' { 3 | import {TemplateFactory} from 'htmlbars-inline-precompile'; 4 | const tmpl: TemplateFactory; 5 | export default tmpl; 6 | } 7 | 8 | declare module '@jftechnology/ember-keycloak-auth' { 9 | import {KeycloakTokenParsed} from "keycloak-js"; 10 | import RSVP from "rsvp"; 11 | 12 | export interface KeycloakAdapterService { 13 | 14 | /** 15 | * The current Keycloak tokenParsed. 16 | * 17 | * @property tokenParsed 18 | * @type {string} 19 | */ 20 | tokenParsed: KeycloakTokenParsed | undefined 21 | 22 | login(redirectUri?: string): RSVP.Promise 23 | 24 | logout(redirectUri?: string): RSVP.Promise 25 | 26 | loadUserProfile(): RSVP.Promise 27 | 28 | updateToken(): RSVP.Promise 29 | 30 | inRole(role: string, resource?: string): boolean 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JFTechnology/ember-keycloak-auth/61684562605efc7fd4c5b74420f48323d60f226f/vendor/.gitkeep --------------------------------------------------------------------------------