├── .version ├── src ├── assets │ ├── .gitkeep │ └── example-resource.json ├── favicon.ico ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── styles.scss ├── tsconfig.app.json ├── tsconfig.spec.json ├── index.html ├── app │ ├── services │ │ ├── example-http.service.ts │ │ └── example-http.service.spec.ts │ ├── app.component.ts │ └── app.module.ts ├── tslint.json ├── main.ts ├── test.ts ├── karma.conf.js └── polyfills.ts ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── Feature Request.yml │ └── Bug Report.yml ├── actions │ ├── get-version │ │ └── action.yml │ ├── get-prerelease │ │ └── action.yml │ ├── tag-exists │ │ └── action.yml │ ├── release-create │ │ └── action.yml │ ├── npm-publish │ │ └── action.yml │ └── get-release-notes │ │ └── action.yml ├── workflows │ ├── semgrep.yml │ ├── release.yml │ ├── build.yml │ ├── snyk.yml │ └── npm-release.yml ├── stale.yml └── PULL_REQUEST_TEMPLATE.md ├── opslevel.yml ├── .shiprc ├── projects └── angular-jwt │ ├── src │ ├── lib │ │ ├── jwtoptions.token.ts │ │ ├── angular-jwt.module.ts │ │ ├── jwt.interceptor.ts │ │ ├── jwthelper.service.ts │ │ └── jwthelper.service.spec.ts │ ├── index.d.ts │ ├── index.ts │ ├── test.ts │ └── index.test-d.ts │ ├── ng-package.json │ ├── tsconfig.lib.prod.json │ ├── tsconfig.spec.json │ ├── tsconfig.lib.json │ ├── package.json │ ├── .eslintrc.json │ └── karma.conf.js ├── e2e ├── src │ ├── app.po.ts │ └── app.e2e-spec.ts ├── tsconfig.e2e.json └── protractor.conf.js ├── .editorconfig ├── .browserslistrc ├── CONTRIBUTING.md ├── tsconfig.json ├── .gitignore ├── LICENSE ├── .eslintrc.json ├── package.json ├── EXAMPLES.md ├── angular.json ├── CHANGELOG.md ├── API.md └── README.md /.version: -------------------------------------------------------------------------------- 1 | v5.2.0 -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @auth0/project-dx-sdks-engineer-codeowner 2 | -------------------------------------------------------------------------------- /src/assets/example-resource.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "ok" 3 | } 4 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/angular2-jwt/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /opslevel.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 1 3 | repository: 4 | owner: dx_sdks 5 | tier: 6 | tags: 7 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /.shiprc: -------------------------------------------------------------------------------- 1 | { 2 | "packagePath": "projects/angular-jwt", 3 | "files": { 4 | ".version": [] 5 | }, 6 | "postbump": "npm run build" 7 | } 8 | -------------------------------------------------------------------------------- /projects/angular-jwt/src/lib/jwtoptions.token.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from '@angular/core'; 2 | 3 | export const JWT_OPTIONS = new InjectionToken('JWT_OPTIONS'); 4 | -------------------------------------------------------------------------------- /projects/angular-jwt/src/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/jwt.interceptor'; 2 | export * from './lib/jwthelper.service'; 3 | export * from './lib/jwtoptions.token'; 4 | export * from './lib/angular-jwt.module'; 5 | -------------------------------------------------------------------------------- /projects/angular-jwt/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/angular-jwt", 4 | "lib": { 5 | "entryFile": "src/index.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 🤔 Help & Questions 4 | url: https://community.auth0.com 5 | about: Ask general support or usage questions in the Auth0 Community forums 6 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "main.ts", 9 | "polyfills.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /projects/angular-jwt/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "compilerOptions": { 4 | "declarationMap": false 5 | }, 6 | "angularCompilerOptions": { 7 | "compilationMode": "partial" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /projects/angular-jwt/src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of angular-jwt 3 | */ 4 | 5 | export * from './lib/jwt.interceptor'; 6 | export * from './lib/jwthelper.service'; 7 | export * from './lib/jwtoptions.token'; 8 | export * from './lib/angular-jwt.module'; 9 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /projects/angular-jwt/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | {{ res$ | async | json }} `,
7 | })
8 | export class AppComponent {
9 | res$ = this.exampleHttpService.testRequest();
10 | constructor(private exampleHttpService: ExampleHttpService) {}
11 | }
12 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule)
12 | .catch(err => console.error(err));
13 |
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 | #
5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed
6 |
7 | > 0.5%
8 | last 2 versions
9 | Firefox ESR
10 | not dead
11 | not IE 9-11
--------------------------------------------------------------------------------
/.github/actions/get-version/action.yml:
--------------------------------------------------------------------------------
1 | name: Return the version extracted from the branch name
2 |
3 | #
4 | # Returns the version from the .version file.
5 | #
6 | # TODO: Remove once the common repo is public.
7 | #
8 |
9 | outputs:
10 | version:
11 | value: ${{ steps.get_version.outputs.VERSION }}
12 |
13 | runs:
14 | using: composite
15 |
16 | steps:
17 | - id: get_version
18 | shell: bash
19 | run: |
20 | VERSION=$(head -1 .version)
21 | echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
22 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution
2 |
3 | Please read [Auth0's contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md).
4 |
5 | ## Documentation
6 |
7 | - PR for docs site update, if needed
8 | - Code-level documentation expectations
9 | - 100% documentation coverage for PRs
10 | - Include links to relevant Auth0 doc pages
11 |
12 | ## Code quality tools
13 |
14 | Running `npm run-script build` will perform necessary linting via tsLint and the project-adopted rules contained in `tslint.json`
15 |
--------------------------------------------------------------------------------
/projects/angular-jwt/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/lib",
5 | "declarationMap": true,
6 | "target": "es2020",
7 | "module": "es2015",
8 | "moduleResolution": "node",
9 | "declaration": true,
10 | "sourceMap": true,
11 | "inlineSources": true,
12 | "experimentalDecorators": true,
13 | "importHelpers": true,
14 | "types": [],
15 | "lib": [
16 | "dom",
17 | "es2018"
18 | ]
19 | },
20 | "exclude": [
21 | "src/test.ts",
22 | "**/*.spec.ts"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/.github/workflows/semgrep.yml:
--------------------------------------------------------------------------------
1 | name: semgrep
2 |
3 | on:
4 | pull_request:
5 | merge_group:
6 |
7 | push:
8 | branches: [main]
9 |
10 | schedule:
11 | - cron: '30 0 1,15 * *'
12 |
13 | jobs:
14 | scan:
15 | runs-on: ubuntu-latest
16 | container:
17 | image: returntocorp/semgrep
18 |
19 | # Skip any PR created by dependabot to avoid permission issues
20 | if: (github.actor != 'dependabot[bot]')
21 | steps:
22 | - uses: actions/checkout@main
23 | - name: Run Semgrep to check for vulnerabilities
24 | run: semgrep ci
25 | env:
26 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_TOKEN }}
27 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "downlevelIteration": true,
6 | "outDir": "./dist/out-tsc",
7 | "sourceMap": true,
8 | "declaration": false,
9 | "module": "esnext",
10 | "moduleResolution": "node",
11 | "experimentalDecorators": true,
12 | "importHelpers": true,
13 | "target": "es2020",
14 | "typeRoots": [
15 | "node_modules/@types"
16 | ],
17 | "lib": [
18 | "es2018",
19 | "dom"
20 | ],
21 | "paths": {
22 | "angular-jwt": [
23 | "projects/angular-jwt/src/index"
24 | ],
25 | "angular-jwt/*": [
26 | "projects/angular-jwt/src/*"
27 | ]
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * For easier debugging in development mode, you can import the following file
11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
12 | *
13 | * This import should be commented out in production mode because it will have a negative impact
14 | * on performance if an error is thrown.
15 | */
16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
17 |
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | declare const require: any;
11 |
12 | // First, initialize the Angular testing environment.
13 | getTestBed().initTestEnvironment(
14 | BrowserDynamicTestingModule,
15 | platformBrowserDynamicTesting(), {
16 | teardown: { destroyAfterEach: false }
17 | }
18 | );
19 | // Then we find all the tests.
20 | const context = require.context('./', true, /\.spec\.ts$/);
21 | // And load the modules.
22 | context.keys().map(context);
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 |
8 | # dependencies
9 | /node_modules
10 |
11 | # profiling files
12 | chrome-profiler-events.json
13 | speed-measure-plugin.json
14 |
15 | # IDEs and editors
16 | /.idea
17 | .project
18 | .classpath
19 | .c9/
20 | *.launch
21 | .settings/
22 | *.sublime-workspace
23 |
24 | # IDE - VSCode
25 | .vscode/*
26 | !.vscode/settings.json
27 | !.vscode/tasks.json
28 | !.vscode/launch.json
29 | !.vscode/extensions.json
30 |
31 | # misc
32 | /.angular/cache
33 | /.sass-cache
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | npm-debug.log
38 | yarn-error.log
39 | testem.log
40 | /typings
41 |
42 | # System Files
43 | .DS_Store
44 | Thumbs.db
45 |
--------------------------------------------------------------------------------
/projects/angular-jwt/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@auth0/angular-jwt",
3 | "version": "5.2.0",
4 | "description": "JSON Web Token helper library for Angular",
5 | "private": false,
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/auth0/angular2-jwt"
9 | },
10 | "author": "Sam Bellen",
11 | "license": "MIT",
12 | "bugs": {
13 | "url": "https://github.com/auth0/angular2-jwt/issues"
14 | },
15 | "keywords": [
16 | "angular",
17 | "angular 2",
18 | "authentication",
19 | "jwt"
20 | ],
21 | "homepage": "https://github.com/auth0/angular2-jwt",
22 | "peerDependencies": {
23 | "@angular/common": ">=14.0.0"
24 | },
25 | "dependencies": {
26 | "tslib": "^2.0.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/projects/angular-jwt/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'core-js/es/reflect';
4 | import 'zone.js';
5 | import 'zone.js/testing';
6 | import { getTestBed } from '@angular/core/testing';
7 | import {
8 | BrowserDynamicTestingModule,
9 | platformBrowserDynamicTesting
10 | } from '@angular/platform-browser-dynamic/testing';
11 |
12 | declare const require: any;
13 |
14 | // First, initialize the Angular testing environment.
15 | getTestBed().initTestEnvironment(
16 | BrowserDynamicTestingModule,
17 | platformBrowserDynamicTesting(), {
18 | teardown: { destroyAfterEach: false }
19 | }
20 | );
21 | // Then we find all the tests.
22 | const context = require.context('./', true, /\.spec\.ts$/);
23 | // And load the modules.
24 | context.keys().map(context);
25 |
--------------------------------------------------------------------------------
/.github/actions/get-prerelease/action.yml:
--------------------------------------------------------------------------------
1 | name: Return a boolean indicating if the version contains prerelease identifiers
2 |
3 | #
4 | # Returns a simple true/false boolean indicating whether the version indicates it's a prerelease or not.
5 | #
6 | # TODO: Remove once the common repo is public.
7 | #
8 |
9 | inputs:
10 | version:
11 | required: true
12 |
13 | outputs:
14 | prerelease:
15 | value: ${{ steps.get_prerelease.outputs.PRERELEASE }}
16 |
17 | runs:
18 | using: composite
19 |
20 | steps:
21 | - id: get_prerelease
22 | shell: bash
23 | run: |
24 | if [[ "${VERSION}" == *"beta"* || "${VERSION}" == *"alpha"* ]]; then
25 | echo "PRERELEASE=true" >> $GITHUB_OUTPUT
26 | else
27 | echo "PRERELEASE=false" >> $GITHUB_OUTPUT
28 | fi
29 | env:
30 | VERSION: ${{ inputs.version }}
31 |
--------------------------------------------------------------------------------
/e2e/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 11000,
8 | specs: [
9 | './src/**/*.e2e-spec.ts'
10 | ],
11 | capabilities: {
12 | 'browserName': 'chrome'
13 | },
14 | directConnect: true,
15 | baseUrl: 'http://localhost:4200/',
16 | framework: 'jasmine',
17 | jasmineNodeOpts: {
18 | showColors: true,
19 | defaultTimeoutInterval: 30000,
20 | print: function() {}
21 | },
22 | onPrepare() {
23 | require('ts-node').register({
24 | project: require('path').join(__dirname, './tsconfig.e2e.json')
25 | });
26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
27 | }
28 | };
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule } from '@angular/core';
3 | import { AppComponent } from './app.component';
4 | import { JwtModule, JWT_OPTIONS } from 'angular-jwt';
5 | import { HttpClientModule } from '@angular/common/http';
6 |
7 | export function tokenGetter() {
8 | return 'SOME_TOKEN';
9 | }
10 |
11 | export function getAuthScheme(request) {
12 | return 'Bearer ';
13 | }
14 |
15 | export function jwtOptionsFactory() {
16 | return {
17 | tokenGetter,
18 | authScheme: getAuthScheme,
19 | };
20 | }
21 |
22 | @NgModule({
23 | declarations: [AppComponent],
24 | imports: [
25 | BrowserModule,
26 | HttpClientModule,
27 | JwtModule.forRoot({
28 | jwtOptionsProvider: {
29 | provide: JWT_OPTIONS,
30 | useFactory: jwtOptionsFactory,
31 | },
32 | }),
33 | ],
34 | providers: [],
35 | bootstrap: [AppComponent],
36 | })
37 | export class AppModule {}
38 |
--------------------------------------------------------------------------------
/src/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('@angular-devkit/build-angular/plugins/karma')
13 | ],
14 | client: {
15 | clearContext: false // leave Jasmine Spec Runner output visible in browser
16 | },
17 | reporters: ['progress', 'kjhtml'],
18 | port: 9876,
19 | colors: true,
20 | logLevel: config.LOG_INFO,
21 | autoWatch: true,
22 | browsers: ['Chrome'],
23 | customLaunchers: {
24 | ChromeHeadlessCI: {
25 | base: 'ChromeHeadless',
26 | flags: ['--no-sandbox'],
27 | },
28 | },
29 | singleRun: false
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Configuration for probot-stale - https://github.com/probot/stale
2 |
3 | # Number of days of inactivity before an Issue or Pull Request becomes stale
4 | daysUntilStale: 90
5 |
6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
7 | daysUntilClose: 7
8 |
9 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
10 | exemptLabels: []
11 |
12 | # Set to true to ignore issues with an assignee (defaults to false)
13 | exemptAssignees: true
14 |
15 | # Label to use when marking as stale
16 | staleLabel: closed:stale
17 |
18 | # Comment to post when marking as stale. Set to `false` to disable
19 | markComment: >
20 | This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you have not received a response for our team (apologies for the delay) and this is still a blocker, please reply with additional information or just a ping. Thank you for your contribution! 🙇♂️
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Create npm and GitHub Release
2 |
3 | on:
4 | pull_request:
5 | types:
6 | - closed
7 | workflow_dispatch:
8 |
9 | permissions:
10 | contents: write
11 | id-token: write # For publishing to npm using --provenance
12 |
13 | ### TODO: Replace instances of './.github/workflows/' w/ `auth0/dx-sdk-actions/workflows/` and append `@latest` after the common `dx-sdk-actions` repo is made public.
14 | ### TODO: Also remove `get-prerelease`, `get-release-notes`, `get-version`, `npm-publish`, `release-create`, and `tag-exists` actions from this repo's .github/actions folder once the repo is public.
15 | ### TODO: Also remove `npm-release` workflow from this repo's .github/workflows folder once the repo is public.
16 |
17 | jobs:
18 | release:
19 | uses: ./.github/workflows/npm-release.yml
20 | with:
21 | node-version: 18
22 | require-build: true
23 | release-directory: './dist/angular-jwt'
24 | secrets:
25 | npm-token: ${{ secrets.NPM_TOKEN }}
26 | github-token: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/projects/angular-jwt/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../.eslintrc.json",
3 | "ignorePatterns": [
4 | "!**/*"
5 | ],
6 | "overrides": [
7 | {
8 | "files": [
9 | "*.ts"
10 | ],
11 | "parserOptions": {
12 | "project": [
13 | "projects/angular-jwt/tsconfig.lib.json",
14 | "projects/angular-jwt/tsconfig.spec.json"
15 | ],
16 | "createDefaultProgram": true
17 | },
18 | "rules": {
19 | "@angular-eslint/directive-selector": [
20 | "error",
21 | {
22 | "type": "attribute",
23 | "prefix": "",
24 | "style": "camelCase"
25 | }
26 | ],
27 | "@angular-eslint/component-selector": [
28 | "error",
29 | {
30 | "type": "element",
31 | "prefix": "",
32 | "style": "kebab-case"
33 | }
34 | ]
35 | }
36 | },
37 | {
38 | "files": [
39 | "*.html"
40 | ],
41 | "rules": {}
42 | }
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build and Test
2 |
3 | on:
4 | merge_group:
5 | workflow_dispatch:
6 | pull_request:
7 | branches:
8 | - main
9 | push:
10 | branches:
11 | - main
12 |
13 | permissions:
14 | contents: read
15 |
16 | concurrency:
17 | group: ${{ github.workflow }}-${{ github.ref }}
18 | cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
19 |
20 | env:
21 | NODE_VERSION: 18
22 | CACHE_KEY: '${{ github.ref }}-${{ github.run_id }}-${{ github.run_attempt }}'
23 |
24 | jobs:
25 | build:
26 | name: Build Package
27 | runs-on: ubuntu-latest
28 |
29 | steps:
30 | - name: Checkout code
31 | uses: actions/checkout@v4
32 | - name: Setup Node
33 | uses: actions/setup-node@v4
34 | with:
35 | node-version: 18
36 | - run: npm ci
37 | - run: npm run lint
38 | - run: npm run build
39 | - run: npm run test:ci
40 | - run: npm run test:types
41 | - name: Upload coverage
42 | uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d
43 |
--------------------------------------------------------------------------------
/.github/actions/tag-exists/action.yml:
--------------------------------------------------------------------------------
1 | name: Return a boolean indicating if a tag already exists for the repository
2 |
3 | #
4 | # Returns a simple true/false boolean indicating whether the tag exists or not.
5 | #
6 | # TODO: Remove once the common repo is public.
7 | #
8 |
9 | inputs:
10 | token:
11 | required: true
12 | tag:
13 | required: true
14 |
15 | outputs:
16 | exists:
17 | description: 'Whether the tag exists or not'
18 | value: ${{ steps.tag-exists.outputs.EXISTS }}
19 |
20 | runs:
21 | using: composite
22 |
23 | steps:
24 | - id: tag-exists
25 | shell: bash
26 | run: |
27 | GET_API_URL="https://api.github.com/repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG_NAME}"
28 | http_status_code=$(curl -LI $GET_API_URL -o /dev/null -w '%{http_code}\n' -s -H "Authorization: token ${GITHUB_TOKEN}")
29 | if [ "$http_status_code" -ne "404" ] ; then
30 | echo "EXISTS=true" >> $GITHUB_OUTPUT
31 | else
32 | echo "EXISTS=false" >> $GITHUB_OUTPUT
33 | fi
34 | env:
35 | TAG_NAME: ${{ inputs.tag }}
36 | GITHUB_TOKEN: ${{ inputs.token }}
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Auth0 Inc.
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 |
--------------------------------------------------------------------------------
/.github/workflows/snyk.yml:
--------------------------------------------------------------------------------
1 | name: Snyk
2 |
3 | on:
4 | merge_group:
5 | workflow_dispatch:
6 | pull_request:
7 | types:
8 | - opened
9 | - synchronize
10 | push:
11 | branches:
12 | - main
13 | schedule:
14 | - cron: '30 0 1,15 * *'
15 |
16 | permissions:
17 | contents: read
18 |
19 | concurrency:
20 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
21 | cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
22 |
23 | jobs:
24 | check:
25 | name: Check for Vulnerabilities
26 | runs-on: ubuntu-latest
27 |
28 | steps:
29 | - if: github.actor == 'dependabot[bot]' || github.event_name == 'merge_group'
30 | run: exit 0 # Skip unnecessary test runs for dependabot and merge queues. Artifically flag as successful, as this is a required check for branch protection.
31 |
32 | - uses: actions/checkout@v4
33 | with:
34 | ref: ${{ github.event.pull_request.head.sha || github.ref }}
35 |
36 | - uses: snyk/actions/node@b98d498629f1c368650224d6d212bf7dfa89e4bf # pin@0.4.0
37 | env:
38 | SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
39 |
--------------------------------------------------------------------------------
/projects/angular-jwt/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-coverage'),
11 | require('karma-chrome-launcher'),
12 | require('karma-jasmine-html-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageReporter: {
19 | type : 'lcov',
20 | dir : require('path').join(__dirname, '../../coverage'),
21 | },
22 | reporters: ['progress', 'kjhtml', 'coverage'],
23 | port: 9876,
24 | colors: true,
25 | logLevel: config.LOG_INFO,
26 | autoWatch: true,
27 | browsers: ['Chrome'],
28 | customLaunchers: {
29 | ChromeHeadlessCI: {
30 | base: 'ChromeHeadless',
31 | flags: ['--no-sandbox'],
32 | },
33 | },
34 | singleRun: false
35 | });
36 | };
37 |
--------------------------------------------------------------------------------
/.github/actions/release-create/action.yml:
--------------------------------------------------------------------------------
1 | name: Create a GitHub release
2 |
3 | #
4 | # Creates a GitHub release with the given version.
5 | #
6 | # TODO: Remove once the common repo is public.
7 | #
8 |
9 | inputs:
10 | token:
11 | required: true
12 | files:
13 | required: false
14 | name:
15 | required: true
16 | body:
17 | required: true
18 | tag:
19 | required: true
20 | commit:
21 | required: true
22 | draft:
23 | default: false
24 | required: false
25 | prerelease:
26 | default: false
27 | required: false
28 | fail_on_unmatched_files:
29 | default: true
30 | required: false
31 |
32 | runs:
33 | using: composite
34 |
35 | steps:
36 | - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844
37 | with:
38 | body: ${{ inputs.body }}
39 | name: ${{ inputs.name }}
40 | tag_name: ${{ inputs.tag }}
41 | target_commitish: ${{ inputs.commit }}
42 | draft: ${{ inputs.draft }}
43 | prerelease: ${{ inputs.prerelease }}
44 | fail_on_unmatched_files: ${{ inputs.fail_on_unmatched_files }}
45 | files: ${{ inputs.files }}
46 | env:
47 | GITHUB_TOKEN: ${{ inputs.token }}
48 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "ignorePatterns": [
4 | "projects/**/*"
5 | ],
6 | "overrides": [
7 | {
8 | "files": [
9 | "*.ts"
10 | ],
11 | "parserOptions": {
12 | "project": [
13 | "tsconfig.json",
14 | "e2e/tsconfig.json"
15 | ],
16 | "createDefaultProgram": true
17 | },
18 | "extends": [
19 | "plugin:@angular-eslint/recommended",
20 | "plugin:@angular-eslint/template/process-inline-templates"
21 | ],
22 | "rules": {
23 | "@angular-eslint/component-selector": [
24 | "error",
25 | {
26 | "prefix": "app",
27 | "style": "kebab-case",
28 | "type": "element"
29 | }
30 | ],
31 | "@angular-eslint/directive-selector": [
32 | "error",
33 | {
34 | "prefix": "app",
35 | "style": "camelCase",
36 | "type": "attribute"
37 | }
38 | ]
39 | }
40 | },
41 | {
42 | "files": [
43 | "*.html"
44 | ],
45 | "extends": [
46 | "plugin:@angular-eslint/template/recommended"
47 | ],
48 | "rules": {}
49 | }
50 | ]
51 | }
52 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Changes
2 |
3 | Please describe both what is changing and why this is important. Include:
4 |
5 | - Endpoints added, deleted, deprecated, or changed
6 | - Classes and methods added, deleted, deprecated, or changed
7 | - Screenshots of new or changed UI, if applicable
8 | - A summary of usage if this is a new feature or change to a public API (this should also be added to relevant documentation once released)
9 | - Any alternative designs or approaches considered
10 |
11 | ### References
12 |
13 | Please include relevant links supporting this change such as a:
14 |
15 | - support ticket
16 | - community post
17 | - StackOverflow post
18 | - support forum thread
19 |
20 | ### Checklist
21 |
22 | - [ ] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
23 | - [ ] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)
24 | - [ ] All code quality tools/guidelines in the [CONTRIBUTING documentation](https://github.com/auth0/open-source-template/blob/master/CONTRIBUTING.md) have been run/followed
25 | - [ ] All relevant assets have been compiled as directed in the [CONTRIBUTING documentation](https://github.com/auth0/open-source-template/blob/master/CONTRIBUTING.md), if applicable
26 |
--------------------------------------------------------------------------------
/.github/actions/npm-publish/action.yml:
--------------------------------------------------------------------------------
1 | name: Publish release to npm
2 |
3 | inputs:
4 | node-version:
5 | required: true
6 | npm-token:
7 | required: true
8 | version:
9 | required: true
10 | require-build:
11 | default: true
12 | release-directory:
13 | default: './'
14 |
15 | runs:
16 | using: composite
17 |
18 | steps:
19 | - name: Checkout code
20 | uses: actions/checkout@v4
21 |
22 | - name: Setup Node
23 | uses: actions/setup-node@v4
24 | with:
25 | node-version: ${{ inputs.node-version }}
26 | cache: 'npm'
27 | registry-url: 'https://registry.npmjs.org'
28 |
29 | - name: Install dependencies
30 | shell: bash
31 | run: npm ci --include=dev
32 |
33 | - name: Build package
34 | if: inputs.require-build == 'true'
35 | shell: bash
36 | run: npm run build
37 |
38 | - name: Publish release to NPM
39 | shell: bash
40 | working-directory: ${{ inputs.release-directory }}
41 | run: |
42 | if [[ "${VERSION}" == *"beta"* ]]; then
43 | TAG="beta"
44 | elif [[ "${VERSION}" == *"alpha"* ]]; then
45 | TAG="alpha"
46 | else
47 | TAG="latest"
48 | fi
49 | npm publish --provenance --tag $TAG
50 | env:
51 | NODE_AUTH_TOKEN: ${{ inputs.npm-token }}
52 | VERSION: ${{ inputs.version }}
53 |
--------------------------------------------------------------------------------
/projects/angular-jwt/src/index.test-d.ts:
--------------------------------------------------------------------------------
1 | import { expectType } from 'tsd';
2 | import { JwtHelperService } from './index';
3 |
4 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
5 | interface Token {}
6 |
7 | const service = new JwtHelperService();
8 |
9 | expectType
140 |
144 |
Auth0 is an easy to implement, adaptable authentication and authorization platform. To learn more checkout Why Auth0?
147 |148 | This project is licensed under the MIT license. See the LICENSE file for more info.
-------------------------------------------------------------------------------- /src/app/services/example-http.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { fakeAsync, flush, TestBed } from '@angular/core/testing'; 2 | import { ExampleHttpService } from './example-http.service'; 3 | import { 4 | HttpClientTestingModule, 5 | HttpTestingController, 6 | } from '@angular/common/http/testing'; 7 | import { JwtModule } from 'angular-jwt'; 8 | 9 | export function tokenGetter() { 10 | return 'TEST_TOKEN'; 11 | } 12 | 13 | export function tokenGetterWithRequest(request) { 14 | if (request.url.includes('1')) { 15 | return 'TEST_TOKEN_1'; 16 | } 17 | 18 | if (request.url.includes('2')) { 19 | return 'TEST_TOKEN_2'; 20 | } 21 | 22 | return 'TEST_TOKEN'; 23 | } 24 | 25 | export function tokenGetterWithPromise() { 26 | return Promise.resolve('TEST_TOKEN'); 27 | } 28 | 29 | describe('Example HttpService: with promise based tokken getter', () => { 30 | let service: ExampleHttpService; 31 | let httpMock: HttpTestingController; 32 | 33 | const validRoutes = [ 34 | `/assets/example-resource.json`, 35 | `http://allowed.com/api/`, 36 | `http://allowed.com/api/test`, 37 | `http://allowed.com:443/api/test`, 38 | `http://allowed-regex.com/api/`, 39 | `https://allowed-regex.com/api/`, 40 | `http://localhost:3000`, 41 | `http://localhost:3000/api`, 42 | ]; 43 | const invalidRoutes = [ 44 | `http://allowed.com/api/disallowed`, 45 | `http://allowed.com/api/disallowed-protocol`, 46 | `http://allowed.com:80/api/disallowed-protocol`, 47 | `http://allowed.com/api/disallowed-regex`, 48 | `http://allowed-regex.com/api/disallowed-regex`, 49 | `http://foo.com/bar`, 50 | 'http://localhost/api', 51 | 'http://localhost:4000/api', 52 | ]; 53 | 54 | beforeEach(() => { 55 | TestBed.configureTestingModule({ 56 | imports: [ 57 | HttpClientTestingModule, 58 | JwtModule.forRoot({ 59 | config: { 60 | tokenGetter: tokenGetterWithPromise, 61 | allowedDomains: ['allowed.com', /allowed-regex*/, 'localhost:3000'], 62 | disallowedRoutes: [ 63 | 'http://allowed.com/api/disallowed-protocol', 64 | '//allowed.com/api/disallowed', 65 | /disallowed-regex*/, 66 | ], 67 | }, 68 | }), 69 | ], 70 | }); 71 | service = TestBed.get(ExampleHttpService); 72 | httpMock = TestBed.get(HttpTestingController); 73 | }); 74 | 75 | it('should add Authorisation header', () => { 76 | expect(service).toBeTruthy(); 77 | }); 78 | 79 | validRoutes.forEach((route) => 80 | it(`should set the correct auth token for a allowed domain: ${route}`, fakeAsync(() => { 81 | service.testRequest(route).subscribe((response) => { 82 | expect(response).toBeTruthy(); 83 | }); 84 | 85 | flush(); 86 | const httpRequest = httpMock.expectOne(route); 87 | 88 | expect(httpRequest.request.headers.has('Authorization')).toEqual(true); 89 | expect(httpRequest.request.headers.get('Authorization')).toEqual( 90 | `Bearer TEST_TOKEN` 91 | ); 92 | })) 93 | ); 94 | 95 | invalidRoutes.forEach((route) => 96 | it(`should not set the auth token for a disallowed route: ${route}`, fakeAsync(() => { 97 | service.testRequest(route).subscribe((response) => { 98 | expect(response).toBeTruthy(); 99 | }); 100 | 101 | flush(); 102 | const httpRequest = httpMock.expectOne(route); 103 | expect(httpRequest.request.headers.has('Authorization')).toEqual(false); 104 | }) 105 | )); 106 | }); 107 | 108 | describe('Example HttpService: with simple tokken getter', () => { 109 | let service: ExampleHttpService; 110 | let httpMock: HttpTestingController; 111 | 112 | const validRoutes = [ 113 | `/assets/example-resource.json`, 114 | `http://allowed.com/api/`, 115 | `http://allowed.com/api/test`, 116 | `http://allowed.com:443/api/test`, 117 | `http://allowed-regex.com/api/`, 118 | `https://allowed-regex.com/api/`, 119 | `http://localhost:3000`, 120 | `http://localhost:3000/api`, 121 | ]; 122 | const invalidRoutes = [ 123 | `http://allowed.com/api/disallowed`, 124 | `http://allowed.com/api/disallowed-protocol`, 125 | `http://allowed.com:80/api/disallowed-protocol`, 126 | `http://allowed.com/api/disallowed-regex`, 127 | `http://allowed-regex.com/api/disallowed-regex`, 128 | `http://foo.com/bar`, 129 | 'http://localhost/api', 130 | 'http://localhost:4000/api', 131 | ]; 132 | 133 | beforeEach(() => { 134 | TestBed.configureTestingModule({ 135 | imports: [ 136 | HttpClientTestingModule, 137 | JwtModule.forRoot({ 138 | config: { 139 | tokenGetter: tokenGetter, 140 | allowedDomains: ['allowed.com', /allowed-regex*/, 'localhost:3000'], 141 | disallowedRoutes: [ 142 | 'http://allowed.com/api/disallowed-protocol', 143 | '//allowed.com/api/disallowed', 144 | /disallowed-regex*/, 145 | ], 146 | }, 147 | }), 148 | ], 149 | }); 150 | service = TestBed.get(ExampleHttpService); 151 | httpMock = TestBed.get(HttpTestingController); 152 | }); 153 | 154 | it('should add Authorisation header', () => { 155 | expect(service).toBeTruthy(); 156 | }); 157 | 158 | validRoutes.forEach((route) => 159 | it(`should set the correct auth token for a allowed domain: ${route}`, () => { 160 | service.testRequest(route).subscribe((response) => { 161 | expect(response).toBeTruthy(); 162 | }); 163 | 164 | const httpRequest = httpMock.expectOne(route); 165 | 166 | expect(httpRequest.request.headers.has('Authorization')).toEqual(true); 167 | expect(httpRequest.request.headers.get('Authorization')).toEqual( 168 | `Bearer ${tokenGetter()}` 169 | ); 170 | }) 171 | ); 172 | 173 | invalidRoutes.forEach((route) => 174 | it(`should not set the auth token for a disallowed route: ${route}`, () => { 175 | service.testRequest(route).subscribe((response) => { 176 | expect(response).toBeTruthy(); 177 | }); 178 | 179 | const httpRequest = httpMock.expectOne(route); 180 | expect(httpRequest.request.headers.has('Authorization')).toEqual(false); 181 | }) 182 | ); 183 | }); 184 | 185 | describe('Example HttpService: with request based tokken getter', () => { 186 | let service: ExampleHttpService; 187 | let httpMock: HttpTestingController; 188 | 189 | const routes = [ 190 | `http://example-1.com/api/`, 191 | `http://example-2.com/api/`, 192 | `http://example-3.com/api/`, 193 | ]; 194 | 195 | beforeEach(() => { 196 | TestBed.configureTestingModule({ 197 | imports: [ 198 | HttpClientTestingModule, 199 | JwtModule.forRoot({ 200 | config: { 201 | tokenGetter: tokenGetterWithRequest, 202 | allowedDomains: ['example-1.com', 'example-2.com', 'example-3.com'], 203 | }, 204 | }), 205 | ], 206 | }); 207 | service = TestBed.get(ExampleHttpService); 208 | httpMock = TestBed.get(HttpTestingController); 209 | }); 210 | 211 | it('should add Authorisation header', () => { 212 | expect(service).toBeTruthy(); 213 | }); 214 | 215 | routes.forEach((route) => 216 | it(`should set the correct auth token for a domain: ${route}`, () => { 217 | service.testRequest(route).subscribe((response) => { 218 | expect(response).toBeTruthy(); 219 | }); 220 | 221 | const httpRequest = httpMock.expectOne(route); 222 | expect(httpRequest.request.headers.has('Authorization')).toEqual(true); 223 | expect(httpRequest.request.headers.get('Authorization')).toEqual( 224 | `Bearer ${tokenGetterWithRequest({ url: route })}` 225 | ); 226 | }) 227 | ); 228 | }); 229 | 230 | const authSchemes = [ 231 | [undefined, 'Bearer '], 232 | ['Basic ', 'Basic '], 233 | [() => 'Basic ', 'Basic '], 234 | ]; 235 | 236 | authSchemes.forEach((scheme) => { 237 | let service: ExampleHttpService; 238 | let httpMock: HttpTestingController; 239 | 240 | describe(`Example HttpService: with ${ 241 | typeof scheme[0] === 'function' 242 | ? 'an authscheme getter function' 243 | : 'a simple authscheme getter' 244 | }`, () => { 245 | beforeEach(() => { 246 | TestBed.configureTestingModule({ 247 | imports: [ 248 | HttpClientTestingModule, 249 | JwtModule.forRoot({ 250 | config: { 251 | tokenGetter: tokenGetter, 252 | authScheme: scheme[0], 253 | allowedDomains: ['allowed.com'], 254 | }, 255 | }), 256 | ], 257 | }); 258 | service = TestBed.get(ExampleHttpService); 259 | httpMock = TestBed.get(HttpTestingController); 260 | }); 261 | 262 | it(`should set the correct auth scheme a request (${scheme[1]})`, () => { 263 | service.testRequest('http://allowed.com').subscribe((response) => { 264 | expect(response).toBeTruthy(); 265 | }); 266 | 267 | const httpRequest = httpMock.expectOne('http://allowed.com'); 268 | expect(httpRequest.request.headers.has('Authorization')).toEqual(true); 269 | expect(httpRequest.request.headers.get('Authorization')).toEqual( 270 | `${scheme[1]}${tokenGetter()}` 271 | ); 272 | }); 273 | }); 274 | }); 275 | -------------------------------------------------------------------------------- /projects/angular-jwt/src/lib/jwthelper.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { 3 | HttpClientTestingModule, 4 | } from '@angular/common/http/testing'; 5 | import { JwtModule, JwtHelperService } from 'angular-jwt'; 6 | 7 | describe('JwtHelperService: with simple based tokken getter', () => { 8 | let service: JwtHelperService; 9 | const tokenGetter = jasmine.createSpy('tokenGetter'); 10 | 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ 14 | HttpClientTestingModule, 15 | JwtModule.forRoot({ 16 | config: { 17 | tokenGetter: tokenGetter, 18 | allowedDomains: ['example-1.com', 'example-2.com', 'example-3.com'], 19 | }, 20 | }), 21 | ], 22 | }); 23 | service = TestBed.inject(JwtHelperService); 24 | 25 | tokenGetter.calls.reset(); 26 | }); 27 | 28 | describe('calling decodeToken', () => { 29 | it('should return null when tokenGetter returns null', () => { 30 | tokenGetter.and.returnValue(null); 31 | 32 | expect(service.decodeToken()).toBeNull(); 33 | }); 34 | 35 | it('should throw an error when token contains less than 2 dots', () => { 36 | tokenGetter.and.returnValue('a.b'); 37 | 38 | expect(() => service.decodeToken()).toThrow(); 39 | }); 40 | 41 | it('should throw an error when token contains more than 2 dots', () => { 42 | tokenGetter.and.returnValue('a.b.c.d'); 43 | 44 | expect(() => service.decodeToken()).toThrow(); 45 | }); 46 | 47 | it('should call the tokenGetter when no token passed', () => { 48 | tokenGetter.and.returnValue('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'); 49 | 50 | const result: any = service.decodeToken(); 51 | 52 | expect(tokenGetter).toHaveBeenCalled(); 53 | expect(result.name).toBe('John Doe'); 54 | }); 55 | 56 | it('should call the tokenGetter when undefined is passed', () => { 57 | tokenGetter.and.returnValue('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'); 58 | 59 | const result = service.decodeToken(undefined); 60 | 61 | expect(tokenGetter).toHaveBeenCalled(); 62 | expect(result.name).toBe('John Doe'); 63 | }); 64 | 65 | it('should not call the tokenGetter when token passed', () => { 66 | tokenGetter.and.returnValue(null); 67 | 68 | const result = service.decodeToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'); 69 | 70 | expect(tokenGetter).not.toHaveBeenCalled(); 71 | expect(result.name).toBe('John Doe'); 72 | }); 73 | 74 | it('should not call the tokenGetter when token passed as empty string', () => { 75 | tokenGetter.and.returnValue('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'); 76 | 77 | const result = service.decodeToken(''); 78 | 79 | expect(tokenGetter).not.toHaveBeenCalled(); 80 | expect(result).toBeNull(); 81 | }); 82 | }); 83 | 84 | describe('calling getTokenExpirationDate', () => { 85 | it('should call the tokenGetter when no token passed', () => { 86 | tokenGetter.and.returnValue('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 87 | 88 | const result = service.getTokenExpirationDate() as Date; 89 | 90 | expect(tokenGetter).toHaveBeenCalled(); 91 | expect(result.getFullYear()).toBe(2018); 92 | expect(result.getMonth() + 1).toBe(1); 93 | expect(result.getDate()).toBe(18); 94 | }); 95 | 96 | it('should call the tokenGetter when undefined is passed', () => { 97 | tokenGetter.and.returnValue('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 98 | 99 | const result = service.getTokenExpirationDate(undefined) as Date; 100 | 101 | expect(tokenGetter).toHaveBeenCalled(); 102 | expect(result.getFullYear()).toBe(2018); 103 | expect(result.getMonth() + 1).toBe(1); 104 | expect(result.getDate()).toBe(18); 105 | }); 106 | 107 | it('should not call the tokenGetter when token passed', () => { 108 | tokenGetter.and.returnValue(null); 109 | 110 | const result = service.getTokenExpirationDate('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 111 | 112 | expect(tokenGetter).not.toHaveBeenCalled(); 113 | expect(result.getFullYear()).toBe(2018); 114 | expect(result.getMonth() + 1).toBe(1); 115 | expect(result.getDate()).toBe(18); 116 | }); 117 | 118 | it('should not call the tokenGetter when token passed as empty string', () => { 119 | tokenGetter.and.returnValue('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 120 | 121 | const result = service.getTokenExpirationDate(''); 122 | 123 | expect(tokenGetter).not.toHaveBeenCalled(); 124 | expect(result).toBeNull(); 125 | }); 126 | }); 127 | 128 | describe('calling isTokenExpired', () => { 129 | it('should call the tokenGetter when no token passed', () => { 130 | tokenGetter.and.returnValue('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 131 | 132 | const result = service.isTokenExpired() as Boolean; 133 | 134 | expect(tokenGetter).toHaveBeenCalled(); 135 | expect(result).toBeTrue(); 136 | }); 137 | 138 | it('should call the tokenGetter when undefined is passed', () => { 139 | tokenGetter.and.returnValue('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 140 | 141 | const result = service.isTokenExpired(undefined); 142 | 143 | expect(tokenGetter).toHaveBeenCalled(); 144 | expect(result).toBeTrue(); 145 | }); 146 | 147 | it('should not call the tokenGetter when token passed', () => { 148 | tokenGetter.and.returnValue(null); 149 | 150 | const result = service.isTokenExpired('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 151 | 152 | expect(tokenGetter).not.toHaveBeenCalled(); 153 | expect(result).toBeTrue(); 154 | }); 155 | 156 | it('should not call the tokenGetter when token passed as empty string', () => { 157 | tokenGetter.and.returnValue('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 158 | 159 | const result = service.isTokenExpired(''); 160 | 161 | expect(tokenGetter).not.toHaveBeenCalled(); 162 | expect(result).toBeTrue(); 163 | }); 164 | }); 165 | 166 | }); 167 | 168 | describe('JwtHelperService: with a promise based tokken getter', () => { 169 | let service: JwtHelperService; 170 | const tokenGetter = jasmine.createSpy('tokenGetter'); 171 | 172 | beforeEach(() => { 173 | TestBed.configureTestingModule({ 174 | imports: [ 175 | HttpClientTestingModule, 176 | JwtModule.forRoot({ 177 | config: { 178 | tokenGetter: tokenGetter, 179 | allowedDomains: ['example-1.com', 'example-2.com', 'example-3.com'], 180 | }, 181 | }), 182 | ], 183 | }); 184 | service = TestBed.inject(JwtHelperService); 185 | tokenGetter.calls.reset(); 186 | }); 187 | 188 | describe('calling decodeToken', () => { 189 | it('should return null when tokenGetter returns null', async () => { 190 | tokenGetter.and.resolveTo(null); 191 | 192 | await expectAsync(service.decodeToken()).toBeResolvedTo(null); 193 | }); 194 | 195 | it('should throw an error when token contains less than 2 dots', async () => { 196 | tokenGetter.and.resolveTo('a.b'); 197 | 198 | await expectAsync(service.decodeToken()).toBeRejected(); 199 | }); 200 | 201 | it('should throw an error when token contains more than 2 dots', async () => { 202 | tokenGetter.and.resolveTo('a.b.c.d'); 203 | 204 | await expectAsync(service.decodeToken()).toBeRejected(); 205 | }); 206 | 207 | it('should return the token when tokenGetter returns a valid JWT', async () => { 208 | tokenGetter.and.resolveTo('eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2NjY2ODU4NjAsImV4cCI6MTY5ODIyMTg2MCwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.lXrRPRZ8VNUpwBsT9fLPPO0p0BotQle4siItqg4LqLQ'); 209 | 210 | await expectAsync(service.decodeToken()).toBeResolvedTo(jasmine.anything()); 211 | }); 212 | 213 | it('should call the tokenGetter when no token passed', async () => { 214 | tokenGetter.and.resolveTo('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'); 215 | 216 | const result = await service.decodeToken(); 217 | 218 | expect(tokenGetter).toHaveBeenCalled(); 219 | expect(result.name).toBe('John Doe'); 220 | }); 221 | 222 | it('should call the tokenGetter when undefined is passed', async () => { 223 | tokenGetter.and.resolveTo('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'); 224 | 225 | const result = await service.decodeToken(undefined); 226 | 227 | expect(tokenGetter).toHaveBeenCalled(); 228 | expect(result.name).toBe('John Doe'); 229 | }); 230 | 231 | it('should not call the tokenGetter when token passed', () => { 232 | tokenGetter.and.resolveTo(null); 233 | 234 | const result = service.decodeToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'); 235 | 236 | expect(tokenGetter).not.toHaveBeenCalled(); 237 | expect(result.name).toBe('John Doe'); 238 | }); 239 | 240 | it('should not call the tokenGetter when token passed as empty string', () => { 241 | tokenGetter.and.resolveTo('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'); 242 | 243 | const result = service.decodeToken(''); 244 | 245 | expect(tokenGetter).not.toHaveBeenCalled(); 246 | expect(result).toBeNull(); 247 | }); 248 | }); 249 | 250 | describe('calling getTokenExpirationDate', () => { 251 | it('should call the tokenGetter when no token passed', async () => { 252 | tokenGetter.and.resolveTo('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 253 | 254 | const result = await service.getTokenExpirationDate(); 255 | 256 | expect(tokenGetter).toHaveBeenCalled(); 257 | expect(result.getFullYear()).toBe(2018); 258 | expect(result.getMonth() + 1).toBe(1); 259 | expect(result.getDate()).toBe(18); 260 | }); 261 | 262 | it('should call the tokenGetter when undefined is passed', async () => { 263 | tokenGetter.and.resolveTo('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 264 | 265 | const result = await service.getTokenExpirationDate(undefined); 266 | 267 | expect(tokenGetter).toHaveBeenCalled(); 268 | expect(result.getFullYear()).toBe(2018); 269 | expect(result.getMonth() + 1).toBe(1); 270 | expect(result.getDate()).toBe(18); 271 | }); 272 | 273 | it('should not call the tokenGetter when token passed', () => { 274 | tokenGetter.and.resolveTo(null); 275 | 276 | const result = service.getTokenExpirationDate('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 277 | 278 | expect(tokenGetter).not.toHaveBeenCalled(); 279 | expect(result.getFullYear()).toBe(2018); 280 | expect(result.getMonth() + 1).toBe(1); 281 | expect(result.getDate()).toBe(18); 282 | }); 283 | 284 | it('should not call the tokenGetter when token passed as empty string', () => { 285 | tokenGetter.and.resolveTo('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 286 | 287 | const result = service.getTokenExpirationDate(''); 288 | 289 | expect(tokenGetter).not.toHaveBeenCalled(); 290 | expect(result).toBeNull(); 291 | }); 292 | }); 293 | 294 | describe('calling isTokenExpired', () => { 295 | it('should call the tokenGetter when no token passed', async () => { 296 | tokenGetter.and.resolveTo('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 297 | 298 | const result = await service.isTokenExpired(); 299 | 300 | expect(tokenGetter).toHaveBeenCalled(); 301 | expect(result).toBeTrue(); 302 | }); 303 | 304 | it('should call the tokenGetter when undefined is passed', async () => { 305 | tokenGetter.and.resolveTo('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 306 | 307 | const result = await service.isTokenExpired(undefined); 308 | 309 | expect(tokenGetter).toHaveBeenCalled(); 310 | expect(result).toBeTrue(); 311 | }); 312 | 313 | it('should not call the tokenGetter when token passed', () => { 314 | tokenGetter.and.resolveTo(null); 315 | 316 | const result = service.isTokenExpired('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 317 | 318 | expect(tokenGetter).not.toHaveBeenCalled(); 319 | expect(result).toBeTrue(); 320 | }); 321 | 322 | it('should not call the tokenGetter when token passed as empty string', () => { 323 | tokenGetter.and.resolveTo('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 324 | 325 | const result = service.isTokenExpired(''); 326 | 327 | expect(tokenGetter).not.toHaveBeenCalled(); 328 | expect(result).toBeTrue(); 329 | }); 330 | }); 331 | }); 332 | 333 | --------------------------------------------------------------------------------