Compare commits
26 Commits
da0d9855da
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e662d7d123 | |||
| dff1e1f9a6 | |||
| 3aabcd6442 | |||
| 0d957dfb1c | |||
| 144e4c2f97 | |||
| de07175ff4 | |||
| 41f2b04c45 | |||
| ba13828093 | |||
| fe10ed2850 | |||
| c8c4fc847d | |||
| 25a27e1998 | |||
| 57ae2191ae | |||
| 9eb1239842 | |||
| 13886502b6 | |||
| 58d7b3c89e | |||
| f00c1d1aef | |||
| 26a06fdbf0 | |||
| eacbbb2815 | |||
| 6a03a2bd62 | |||
| 28da493462 | |||
| c9d0066d64 | |||
| 316361bd3c | |||
| 733b697ee2 | |||
| ca5f4984a4 | |||
| 7ff7e891fc | |||
| 1785df25e2 |
BIN
.github/angular-auth-logo.png
vendored
BIN
.github/angular-auth-logo.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 134 KiB |
BIN
.github/angular-auth-oidc-client-schematics-720.gif
vendored
BIN
.github/angular-auth-oidc-client-schematics-720.gif
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 MiB |
263
.github/workflows/build.yml
vendored
263
.github/workflows/build.yml
vendored
@@ -8,258 +8,51 @@ on:
|
||||
types: [opened, synchronize, reopened, closed]
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build_job:
|
||||
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
|
||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.action != 'closed')
|
||||
runs-on: ubuntu-latest
|
||||
name: Built, Lint and Test Library
|
||||
name: Build, Lint and Test Library
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
- name: Setup Node and Install Dependencies
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
node-version: 20
|
||||
version: 10
|
||||
run_install: false
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Installing Dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Linting Library
|
||||
run: npm run lint-lib
|
||||
run: npm run lint
|
||||
|
||||
- name: Testing Frontend
|
||||
run: npm run test-lib-ci
|
||||
run: npm run test-ci
|
||||
|
||||
- name: Coveralls
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
path-to-lcov: './coverage/oidc-client-rx/lcov.info'
|
||||
|
||||
- name: Coveralls Finished
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.github_token }}
|
||||
parallel-finished: true
|
||||
- name: 'Report Coverage'
|
||||
if: (github.event_name == 'pull_request' && github.event.action != 'closed')
|
||||
uses: davelosert/vitest-coverage-report-action@v2
|
||||
|
||||
- name: Building Frontend
|
||||
run: npm run build-lib-prod
|
||||
|
||||
- name: Copying essential additional files
|
||||
run: npm run copy-files
|
||||
run: npm run build
|
||||
|
||||
- name: Show files
|
||||
run: ls
|
||||
|
||||
- name: Upload Artefact
|
||||
uses: actions/upload-artifact@v3
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: angular_auth_oidc_client_artefact
|
||||
path: dist/oidc-client-rx
|
||||
|
||||
AngularLatestVersion:
|
||||
needs: build_job
|
||||
runs-on: ubuntu-latest
|
||||
name: Angular latest
|
||||
steps:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Download Artefact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: angular_auth_oidc_client_artefact
|
||||
path: oidc-client-rx-artefact
|
||||
|
||||
- name: Install AngularCLI globally
|
||||
run: sudo npm install -g @angular/cli
|
||||
|
||||
- name: Show ng Version
|
||||
run: ng version
|
||||
|
||||
- name: Create Angular Project
|
||||
run: sudo ng new oidc-client-rx-test --skip-git
|
||||
|
||||
- name: Npm Install & Install Library from local artefact
|
||||
run: |
|
||||
sudo cp -R oidc-client-rx-artefact oidc-client-rx-test/
|
||||
cd oidc-client-rx-test
|
||||
sudo npm install --unsafe-perm=true
|
||||
sudo ng add ./oidc-client-rx-artefact --authority-url-or-tenant-id "my-authority-url" --flow-type "OIDC Code Flow PKCE using refresh tokens" --use-local-package=true --skip-confirmation
|
||||
|
||||
- name: Test Angular Application
|
||||
working-directory: ./oidc-client-rx-test
|
||||
run: npm test -- --watch=false --browsers=ChromeHeadless
|
||||
|
||||
- name: Build Angular Application
|
||||
working-directory: ./oidc-client-rx-test
|
||||
run: sudo npm run build
|
||||
|
||||
AngularLatestVersionWithSchematics:
|
||||
needs: build_job
|
||||
runs-on: ubuntu-latest
|
||||
name: Angular latest & Schematics Job
|
||||
steps:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Download Artefact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: angular_auth_oidc_client_artefact
|
||||
path: oidc-client-rx-artefact
|
||||
|
||||
- name: Install AngularCLI globally
|
||||
run: sudo npm install -g @angular/cli
|
||||
|
||||
- name: Show ng Version
|
||||
run: ng version
|
||||
|
||||
- name: Create Angular Project
|
||||
run: sudo ng new oidc-client-rx-test --skip-git
|
||||
|
||||
- name: Npm Install & Install Library from local artefact
|
||||
run: |
|
||||
sudo cp -R oidc-client-rx-artefact oidc-client-rx-test/
|
||||
cd oidc-client-rx-test
|
||||
sudo npm install --unsafe-perm=true
|
||||
sudo ng add ./oidc-client-rx-artefact --authority-url-or-tenant-id "my-authority-url" --flow-type "Default config" --use-local-package=true --skip-confirmation
|
||||
|
||||
- name: Test Angular Application
|
||||
working-directory: ./oidc-client-rx-test
|
||||
run: npm test -- --watch=false --browsers=ChromeHeadless
|
||||
|
||||
- name: Build Angular Application
|
||||
working-directory: ./oidc-client-rx-test
|
||||
run: sudo npm run build
|
||||
|
||||
AngularLatestVersionWithNgModuleSchematics:
|
||||
needs: build_job
|
||||
runs-on: ubuntu-latest
|
||||
name: Angular latest Standalone & Schematics Job
|
||||
steps:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Download Artefact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: angular_auth_oidc_client_artefact
|
||||
path: oidc-client-rx-artefact
|
||||
|
||||
- name: Install AngularCLI globally
|
||||
run: sudo npm install -g @angular/cli
|
||||
|
||||
- name: Show ng Version
|
||||
run: ng version
|
||||
|
||||
- name: Create Angular Project
|
||||
run: sudo ng new oidc-client-rx-test --skip-git --standalone=false
|
||||
|
||||
- name: Npm Install & Install Library from local artefact
|
||||
run: |
|
||||
sudo cp -R oidc-client-rx-artefact oidc-client-rx-test/
|
||||
cd oidc-client-rx-test
|
||||
sudo npm install --unsafe-perm=true
|
||||
sudo ng add ./oidc-client-rx-artefact --authority-url-or-tenant-id "my-authority-url" --flow-type "OIDC Code Flow PKCE using refresh tokens" --use-local-package=true --skip-confirmation
|
||||
|
||||
- name: Test Angular Application
|
||||
working-directory: ./oidc-client-rx-test
|
||||
run: npm test -- --watch=false --browsers=ChromeHeadless
|
||||
|
||||
- name: Build Angular Application
|
||||
working-directory: ./oidc-client-rx-test
|
||||
run: sudo npm run build
|
||||
|
||||
Angular16VersionWithRxJs6:
|
||||
needs: build_job
|
||||
runs-on: ubuntu-latest
|
||||
name: Angular 16 & RxJs 6
|
||||
steps:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Download Artefact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: angular_auth_oidc_client_artefact
|
||||
path: oidc-client-rx-artefact
|
||||
|
||||
- name: Install AngularCLI globally
|
||||
run: sudo npm install -g @angular/cli@16
|
||||
|
||||
- name: Show ng Version
|
||||
run: ng version
|
||||
|
||||
- name: Create Angular Project
|
||||
run: sudo ng new oidc-client-rx-test --skip-git
|
||||
|
||||
- name: npm install RxJs 6
|
||||
working-directory: ./oidc-client-rx-test
|
||||
run: sudo npm install rxjs@6.5.3
|
||||
|
||||
- name: Npm Install & Install Library from local artefact
|
||||
run: |
|
||||
sudo cp -R oidc-client-rx-artefact oidc-client-rx-test/
|
||||
cd oidc-client-rx-test
|
||||
sudo npm install --unsafe-perm=true
|
||||
sudo ng add ./oidc-client-rx-artefact --authority-url-or-tenant-id "my-authority-url" --flow-type "OIDC Code Flow PKCE using refresh tokens" --use-local-package=true --skip-confirmation
|
||||
|
||||
- name: Test Angular Application
|
||||
working-directory: ./oidc-client-rx-test
|
||||
run: npm test -- --watch=false --browsers=ChromeHeadless
|
||||
|
||||
- name: Build Angular Application
|
||||
working-directory: ./oidc-client-rx-test
|
||||
run: sudo npm run build
|
||||
|
||||
LibWithAngularV16:
|
||||
needs: build_job
|
||||
runs-on: ubuntu-latest
|
||||
name: Angular V16
|
||||
steps:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Download Artefact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: angular_auth_oidc_client_artefact
|
||||
path: oidc-client-rx-artefact
|
||||
|
||||
- name: Install AngularCLI globally
|
||||
run: sudo npm install -g @angular/cli@16
|
||||
|
||||
- name: Show ng Version
|
||||
run: ng version
|
||||
|
||||
- name: Create Angular Project
|
||||
run: sudo ng new oidc-client-rx-test --skip-git
|
||||
|
||||
- name: Npm Install & Install Library from local artefact
|
||||
run: |
|
||||
sudo cp -R oidc-client-rx-artefact oidc-client-rx-test/
|
||||
cd oidc-client-rx-test
|
||||
sudo npm install --unsafe-perm=true
|
||||
sudo ng add ./oidc-client-rx-artefact --authority-url-or-tenant-id "my-authority-url" --flow-type "OIDC Code Flow PKCE using refresh tokens" --use-local-package=true --skip-confirmation
|
||||
|
||||
- name: Test Angular Application
|
||||
working-directory: ./oidc-client-rx-test
|
||||
run: npm test -- --watch=false --browsers=ChromeHeadless
|
||||
|
||||
- name: Build Angular Application
|
||||
working-directory: ./oidc-client-rx-test
|
||||
run: sudo npm run build
|
||||
name: oidc_client_rx_artifact
|
||||
path: dist
|
||||
|
||||
61
.github/workflows/deploy-docs.yml
vendored
61
.github/workflows/deploy-docs.yml
vendored
@@ -1,61 +0,0 @@
|
||||
name: Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, closed]
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build_and_deploy_job:
|
||||
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
|
||||
runs-on: ubuntu-latest
|
||||
name: Build and Deploy Docs Job
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Installing Dependencies
|
||||
run: sudo npm install
|
||||
|
||||
- name: Installing Dependencies for docs - in docs folder
|
||||
run: sudo npm install
|
||||
working-directory: docs/site/oidc-client-rx
|
||||
|
||||
- name: Building Documentation
|
||||
run: sudo npm run build
|
||||
working-directory: docs/site/oidc-client-rx
|
||||
|
||||
- name: Build And Deploy
|
||||
if: ${{ github.actor == 'damienbod' || github.actor == 'FabianGosebrink' }}
|
||||
id: builddeploy
|
||||
uses: Azure/static-web-apps-deploy@v1
|
||||
with:
|
||||
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
|
||||
action: 'upload'
|
||||
###### Repository/Build Configurations - These values can be configured to match you app requirements. ######
|
||||
app_location: '/docs/site/oidc-client-rx' # App source code path
|
||||
app_artifact_location: 'build' # Built app content directory - optional
|
||||
###### End of Repository/Build Configurations ######
|
||||
|
||||
close_pull_request_job:
|
||||
if: github.event_name == 'pull_request' && github.event.action == 'closed'
|
||||
runs-on: ubuntu-latest
|
||||
name: Close Pull Request Job
|
||||
steps:
|
||||
- name: Close Pull Request
|
||||
id: closepullrequest
|
||||
uses: Azure/static-web-apps-deploy@v1
|
||||
with:
|
||||
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
|
||||
action: 'close'
|
||||
27
.github/workflows/playwright.yml
vendored
27
.github/workflows/playwright.yml
vendored
@@ -1,27 +0,0 @@
|
||||
name: Playwright Tests
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
pull_request:
|
||||
branches: [ main, master ]
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
run: npm install -g pnpm && pnpm install
|
||||
- name: Install Playwright Browsers
|
||||
run: pnpm exec playwright install --with-deps
|
||||
- name: Run Playwright tests
|
||||
run: pnpm exec playwright test
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -2,6 +2,7 @@
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
**/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
# Only exists if Bazel was run
|
||||
@@ -32,7 +33,6 @@ speed-measure-plugin*.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.angular/cache
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
@@ -46,7 +46,6 @@ testem.log
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
/.angulardoc.json
|
||||
debug.log
|
||||
|
||||
/.husky
|
||||
@@ -55,3 +54,6 @@ debug.log
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
/.vitest
|
||||
/.rslib
|
||||
**/*.tsbuildinfo
|
||||
|
||||
223
README.md
223
README.md
@@ -1,207 +1,66 @@
|
||||
# Angular Lib for OpenID Connect & OAuth2
|
||||
<h1 align="center">
|
||||
<img src="./assets/logo-512.png" height="150" alt="OUTPOSTS">
|
||||
<div style="color: #232848; font-weight: 700;">OIDC-CLIENT-RX</div>
|
||||
<div align="center">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/lonelyhentxi/oidc-client-rx/build.yml?branch=main" alt="build-status" />
|
||||
<img src="https://img.shields.io/badge/status-work--in--progress-blue" alt="status-badge" />
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
 [](https://www.npmjs.com/package/oidc-client-rx) [](https://www.npmjs.com/package/oidc-client-rx) [](https://www.npmjs.com/package/oidc-client-rx) [](https://github.com/prettier/prettier) [](https://coveralls.io/github/damienbod/oidc-client-rx?branch=main)
|
||||
<p align="center">ReactiveX enhanced OIDC and OAuth2 protocol support for browser-based JavaScript applications.</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://oidc-client-rx.com/"><img src="https://raw.githubusercontent.com/damienbod/oidc-client-rx/main/.github/angular-auth-logo.png" alt="" width="350" /></a>
|
||||
</p>
|
||||
## Quick Start
|
||||
|
||||
Secure your Angular app using the latest standards for OpenID Connect & OAuth2. Provides support for token refresh, all modern OIDC Identity Providers and more.
|
||||
@TODO Add More Details
|
||||
|
||||
## Acknowledgements
|
||||
### Install
|
||||
|
||||
This library is <a href="http://openid.net/certification/#RPs">certified</a> by OpenID Foundation. (RP Implicit and Config RP)
|
||||
|
||||
<p align="center">
|
||||
<a href="http://openid.net/certification/#RPs"><img src="https://damienbod.files.wordpress.com/2017/06/oid-l-certification-mark-l-rgb-150dpi-90mm.png" alt="" width="400" /></a>
|
||||
</p>
|
||||
|
||||
## Features
|
||||
|
||||
- [Code samples](https://oidc-client-rx.com/docs/samples/) for most of the common use cases
|
||||
- Supports schematics via `ng add` support
|
||||
- Supports all modern OIDC identity providers
|
||||
- Supports OpenID Connect Code Flow with PKCE
|
||||
- Supports Code Flow PKCE with Refresh tokens
|
||||
- [Supports OpenID Connect Implicit Flow](http://openid.net/specs/openid-connect-implicit-1_0.html)
|
||||
- [Supports OpenID Connect Session Management 1.0](http://openid.net/specs/openid-connect-session-1_0.html)
|
||||
- [Supports RFC7009 - OAuth 2.0 Token Revocation](https://tools.ietf.org/html/rfc7009)
|
||||
- [Supports RFC7636 - Proof Key for Code Exchange (PKCE)](https://tools.ietf.org/html/rfc7636)
|
||||
- [Supports OAuth 2.0 Pushed authorisation requests (PAR) draft](https://tools.ietf.org/html/draft-ietf-oauth-par-06)
|
||||
- Semantic releases
|
||||
- Github actions
|
||||
- Modern coding guidelines with prettier, husky
|
||||
- Up to date documentation
|
||||
- Implements OIDC validation as specified, complete client side validation for REQUIRED features
|
||||
- Supports authentication using redirect or popup
|
||||
|
||||
## Installation
|
||||
|
||||
### Ng Add
|
||||
|
||||
You can use the schematics and `ng add` the library.
|
||||
|
||||
```shell
|
||||
ng add oidc-client-rx
|
||||
```sh
|
||||
pnpm add oidc-client-rx @outposts/injection-js @abraham/reflection
|
||||
# npm install oidc-client-rx @outposts/injection-js @abraham/reflection
|
||||
# yarn add oidc-client-rx @outposts/injection-js @abraham/reflection
|
||||
```
|
||||
|
||||
And answer the questions. A module will be created which encapsulates your configuration.
|
||||
### Basic Usage
|
||||
|
||||

|
||||
```typescript
|
||||
import '@abraham/reflection'; // or 'reflect-metadata' | 'core-js/es7/reflect'
|
||||
import { type Injector, ReflectiveInjector } from '@outposts/injection-js';
|
||||
import { LogLevel, OidcSecurityService, provideAuth, withDefaultFeatures } from 'oidc-client-rx';
|
||||
|
||||
### Npm / Yarn
|
||||
|
||||
Navigate to the level of your `package.json` and type
|
||||
|
||||
```shell
|
||||
npm install oidc-client-rx
|
||||
```
|
||||
|
||||
or with yarn
|
||||
|
||||
```shell
|
||||
yarn add oidc-client-rx
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
[Read the docs here](https://oidc-client-rx.com/)
|
||||
|
||||
## Samples
|
||||
|
||||
[Explore the Samples here](https://oidc-client-rx.com/docs/samples/)
|
||||
|
||||
## Quickstart
|
||||
|
||||
For the example of the Code Flow. For further examples please check the [Samples](https://oidc-client-rx.com/docs/samples/) Section.
|
||||
|
||||
> If you have done the installation with the schematics, these modules and files should be available already!
|
||||
|
||||
### Configuration
|
||||
|
||||
Import the `AuthModule` in your module.
|
||||
|
||||
```ts
|
||||
import { NgModule } from '@angular/core';
|
||||
import { AuthModule, LogLevel } from 'oidc-client-rx';
|
||||
// ...
|
||||
|
||||
@NgModule({
|
||||
// ...
|
||||
imports: [
|
||||
// ...
|
||||
AuthModule.forRoot({
|
||||
const injector = ReflectiveInjector.resolveAndCreate(
|
||||
provideAuth(
|
||||
{
|
||||
config: {
|
||||
authority: '<your authority address here>',
|
||||
redirectUrl: window.location.origin,
|
||||
authority: '<your-authority>',
|
||||
redirectUrl: `${window.location.origin}/auth/callback`,
|
||||
postLogoutRedirectUri: window.location.origin,
|
||||
clientId: '<your clientId>',
|
||||
clientId: '<your-client-id>',
|
||||
scope: 'openid profile email offline_access',
|
||||
responseType: 'code',
|
||||
silentRenew: true,
|
||||
useRefreshToken: true,
|
||||
logLevel: LogLevel.Debug,
|
||||
...
|
||||
},
|
||||
}),
|
||||
],
|
||||
// ...
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
And call the method `checkAuth()` from your `app.component.ts`. The method `checkAuth()` is needed to process the redirect from your Security Token Service and set the correct states. This method must be used to ensure the correct functioning of the library.
|
||||
|
||||
```ts
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { OidcSecurityService } from 'oidc-client-rx';
|
||||
|
||||
@Component({
|
||||
/*...*/
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
private readonly oidcSecurityService = inject(OidcSecurityService);
|
||||
|
||||
ngOnInit() {
|
||||
this.oidcSecurityService
|
||||
.checkAuth()
|
||||
.subscribe((loginResponse: LoginResponse) => {
|
||||
const { isAuthenticated, userData, accessToken, idToken, configId } =
|
||||
loginResponse;
|
||||
|
||||
/*...*/
|
||||
});
|
||||
}
|
||||
|
||||
login() {
|
||||
this.oidcSecurityService.authorize();
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.oidcSecurityService
|
||||
.logoff()
|
||||
.subscribe((result) => console.log(result));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using the access token
|
||||
|
||||
You can get the access token by calling the method `getAccessToken()` on the `OidcSecurityService`
|
||||
|
||||
```ts
|
||||
const token = this.oidcSecurityService.getAccessToken().subscribe(...);
|
||||
```
|
||||
|
||||
And then you can use it in the HttpHeaders
|
||||
|
||||
```ts
|
||||
import { HttpHeaders } from '@angular/common/http';
|
||||
|
||||
const token = this.oidcSecurityServices.getAccessToken().subscribe((token) => {
|
||||
const httpOptions = {
|
||||
headers: new HttpHeaders({
|
||||
Authorization: 'Bearer ' + token,
|
||||
}),
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
You can use the built in interceptor to add the accesstokens to your request
|
||||
|
||||
```ts
|
||||
AuthModule.forRoot({
|
||||
config: {
|
||||
// ...
|
||||
secureRoutes: ['https://my-secure-url.com/', 'https://my-second-secure-url.com/'],
|
||||
},
|
||||
}),
|
||||
withDefaultFeatures()
|
||||
)
|
||||
) as Injector;
|
||||
|
||||
const oidcSecurityService = injector.get(OidcSecurityService);
|
||||
|
||||
oidcSecurityService.checkAuth().subscribe((result) => {
|
||||
console.debug('checkAuth result: ', result);
|
||||
});
|
||||
|
||||
const isAuthenticated$ = oidcSecurityService.isAuthenticated$;
|
||||
```
|
||||
|
||||
```ts
|
||||
providers: [
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
|
||||
],
|
||||
```
|
||||
### More Examples
|
||||
|
||||
## Versions
|
||||
|
||||
Current Version is Version 19.x
|
||||
|
||||
- [Info about Version 18](https://github.com/damienbod/oidc-client-rx/tree/version-18)
|
||||
- [Info about Version 17](https://github.com/damienbod/oidc-client-rx/tree/version-17)
|
||||
- [Info about Version 16](https://github.com/damienbod/oidc-client-rx/tree/version-16)
|
||||
- [Info about Version 15](https://github.com/damienbod/oidc-client-rx/tree/version-15)
|
||||
- [Info about Version 14](https://github.com/damienbod/oidc-client-rx/tree/version-14)
|
||||
- [Info about Version 13](https://github.com/damienbod/oidc-client-rx/tree/version-13)
|
||||
- [Info about Version 12](https://github.com/damienbod/oidc-client-rx/tree/version-12)
|
||||
- [Info about Version 11](https://github.com/damienbod/oidc-client-rx/tree/version-11)
|
||||
- [Info about Version 10](https://github.com/damienbod/oidc-client-rx/tree/version-10)
|
||||
- [React + TanStack Router](https://github.com/lonelyhentxi/oidc-client-rx/tree/main/examples/react-tanstack-router)
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://choosealicense.com/licenses/mit/)
|
||||
|
||||
## Authors
|
||||
|
||||
- [@DamienBod](https://www.github.com/damienbod)
|
||||
- [@FabianGosebrink](https://www.github.com/FabianGosebrink)
|
||||
|
||||
BIN
assets/logo-512.png
Normal file
BIN
assets/logo-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
25
biome.json
25
biome.json
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"extends": ["ultracite"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
"noNonNullAssertion": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "off"
|
||||
},
|
||||
"correctness": {
|
||||
"noUnusedImports": {
|
||||
"fix": "none",
|
||||
"level": "warn"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"ignore": [
|
||||
".vscode/*.json"
|
||||
]
|
||||
}
|
||||
}
|
||||
41
biome.jsonc
Normal file
41
biome.jsonc
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"extends": ["ultracite"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
"noNonNullAssertion": "off",
|
||||
"noParameterAssign": "off",
|
||||
"useFilenamingConvention": "off",
|
||||
"noParameterProperties": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "off"
|
||||
},
|
||||
"complexity": {
|
||||
"noForEach": "off"
|
||||
},
|
||||
"correctness": {
|
||||
"noUnusedImports": {
|
||||
"fix": "none",
|
||||
"level": "warn"
|
||||
}
|
||||
},
|
||||
"nursery": {
|
||||
"noEnum": "off",
|
||||
"useConsistentMemberAccessibility": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"ignore": [".vscode/*.json"]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"include": ["src/**/*.spec.ts", "src/test.ts", "test"],
|
||||
"javascript": {
|
||||
"globals": ["describe", "beforeEach", "it", "expect"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
13
examples/react-tanstack-router/.gitignore
vendored
Normal file
13
examples/react-tanstack-router/.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Local
|
||||
.DS_Store
|
||||
*.local
|
||||
*.log*
|
||||
|
||||
# Dist
|
||||
node_modules
|
||||
dist/
|
||||
|
||||
# IDE
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
29
examples/react-tanstack-router/README.md
Normal file
29
examples/react-tanstack-router/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Rsbuild project
|
||||
|
||||
## Setup
|
||||
|
||||
Install the dependencies:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
## Get started
|
||||
|
||||
Start the dev server:
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Build the app for production:
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
Preview the production build locally:
|
||||
|
||||
```bash
|
||||
pnpm preview
|
||||
```
|
||||
31
examples/react-tanstack-router/package.json
Normal file
31
examples/react-tanstack-router/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "react-tanstack-router",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "rsbuild dev",
|
||||
"build": "rsbuild build",
|
||||
"preview": "rsbuild preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@abraham/reflection": "^0.12.0",
|
||||
"@outposts/injection-js": "^2.5.1",
|
||||
"@tanstack/react-router": "^1.99.6",
|
||||
"@tanstack/router-devtools": "^1.99.6",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"observable-hooks": "^4.2.4",
|
||||
"oidc-client-rx": "workspace:*",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tailwindcss": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rsbuild/core": "^1.2.3",
|
||||
"@rsbuild/plugin-react": "^1.1.0",
|
||||
"@tanstack/router-plugin": "^1.99.6",
|
||||
"@types/react": "^19.0.8",
|
||||
"@types/react-dom": "^19.0.3",
|
||||
"postcss": "^8.5.1",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
6
examples/react-tanstack-router/postcss.config.mjs
Normal file
6
examples/react-tanstack-router/postcss.config.mjs
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
0
examples/react-tanstack-router/public/.gitkeep
Normal file
0
examples/react-tanstack-router/public/.gitkeep
Normal file
12
examples/react-tanstack-router/rsbuild.config.ts
Normal file
12
examples/react-tanstack-router/rsbuild.config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from '@rsbuild/core';
|
||||
import { pluginReact } from '@rsbuild/plugin-react';
|
||||
import { TanStackRouterRspack } from '@tanstack/router-plugin/rspack';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [pluginReact()],
|
||||
tools: {
|
||||
rspack: {
|
||||
plugins: [TanStackRouterRspack()],
|
||||
},
|
||||
},
|
||||
});
|
||||
1
examples/react-tanstack-router/src/env.d.ts
vendored
Normal file
1
examples/react-tanstack-router/src/env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="@rsbuild/core/types" />
|
||||
87
examples/react-tanstack-router/src/index.tsx
Normal file
87
examples/react-tanstack-router/src/index.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import '@abraham/reflection'; // or 'reflect-metadata' | 'core-js/es7/reflect'
|
||||
import { type Injector, ReflectiveInjector } from '@outposts/injection-js';
|
||||
import { RouterProvider, createRouter } from '@tanstack/react-router';
|
||||
import {
|
||||
LogLevel,
|
||||
OidcSecurityService,
|
||||
provideAuth,
|
||||
withDefaultFeatures,
|
||||
} from 'oidc-client-rx';
|
||||
import { withTanstackRouter } from 'oidc-client-rx/adapters/@tanstack/react-router';
|
||||
import {
|
||||
InjectorContextVoidInjector,
|
||||
InjectorProvider,
|
||||
} from 'oidc-client-rx/adapters/react';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { routeTree } from './routeTree.gen';
|
||||
|
||||
import './style.css';
|
||||
|
||||
// Set up a Router instance
|
||||
const router = createRouter({
|
||||
routeTree,
|
||||
defaultPreload: 'intent',
|
||||
scrollRestoration: true,
|
||||
context: {
|
||||
injector: InjectorContextVoidInjector,
|
||||
oidcSecurityService: {} as OidcSecurityService,
|
||||
},
|
||||
});
|
||||
|
||||
// Register things for typesafety
|
||||
declare module '@tanstack/react-router' {
|
||||
interface Register {
|
||||
router: typeof router;
|
||||
}
|
||||
}
|
||||
|
||||
const injector = ReflectiveInjector.resolveAndCreate(
|
||||
provideAuth(
|
||||
{
|
||||
config: {
|
||||
authority: 'https://k9bor3.logto.app/oidc',
|
||||
redirectUrl: `${window.location.origin}/auth/callback`,
|
||||
postLogoutRedirectUri: window.location.origin,
|
||||
clientId: 'zz5vo27wtvtjf36srwtbp',
|
||||
scope: 'openid offline_access',
|
||||
responseType: 'code',
|
||||
silentRenew: true,
|
||||
useRefreshToken: true,
|
||||
logLevel: LogLevel.Debug,
|
||||
autoUserInfo: true,
|
||||
renewUserInfoAfterTokenRenew: true,
|
||||
customParamsAuthRequest: {
|
||||
prompt: 'consent',
|
||||
},
|
||||
},
|
||||
},
|
||||
withDefaultFeatures(
|
||||
// the after feature will replace the before same type feature
|
||||
// so the following line can be ignored
|
||||
{ router: { enabled: false } }
|
||||
),
|
||||
withTanstackRouter(router)
|
||||
)
|
||||
) as Injector;
|
||||
|
||||
// if needed, check when init
|
||||
const oidcSecurityService = injector.get(OidcSecurityService);
|
||||
oidcSecurityService.checkAuth().subscribe();
|
||||
|
||||
const rootEl = document.getElementById('root');
|
||||
|
||||
if (rootEl) {
|
||||
const root = ReactDOM.createRoot(rootEl);
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<InjectorProvider injector={injector}>
|
||||
<RouterProvider
|
||||
router={router}
|
||||
context={{ injector, oidcSecurityService }}
|
||||
/>
|
||||
</InjectorProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
}
|
||||
111
examples/react-tanstack-router/src/routeTree.gen.ts
Normal file
111
examples/react-tanstack-router/src/routeTree.gen.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
/* eslint-disable */
|
||||
|
||||
// @ts-nocheck
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
// This file was automatically generated by TanStack Router.
|
||||
// You should NOT make any changes in this file as it will be overwritten.
|
||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||
|
||||
// Import Routes
|
||||
|
||||
import { Route as rootRoute } from './routes/__root';
|
||||
import { Route as AuthCallbackImport } from './routes/auth/callback';
|
||||
import { Route as IndexImport } from './routes/index';
|
||||
|
||||
// Create/Update Routes
|
||||
|
||||
const IndexRoute = IndexImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any);
|
||||
|
||||
const AuthCallbackRoute = AuthCallbackImport.update({
|
||||
id: '/auth/callback',
|
||||
path: '/auth/callback',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any);
|
||||
|
||||
// Populate the FileRoutesByPath interface
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {
|
||||
'/': {
|
||||
id: '/';
|
||||
path: '/';
|
||||
fullPath: '/';
|
||||
preLoaderRoute: typeof IndexImport;
|
||||
parentRoute: typeof rootRoute;
|
||||
};
|
||||
'/auth/callback': {
|
||||
id: '/auth/callback';
|
||||
path: '/auth/callback';
|
||||
fullPath: '/auth/callback';
|
||||
preLoaderRoute: typeof AuthCallbackImport;
|
||||
parentRoute: typeof rootRoute;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export the route tree
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute;
|
||||
'/auth/callback': typeof AuthCallbackRoute;
|
||||
}
|
||||
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute;
|
||||
'/auth/callback': typeof AuthCallbackRoute;
|
||||
}
|
||||
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRoute;
|
||||
'/': typeof IndexRoute;
|
||||
'/auth/callback': typeof AuthCallbackRoute;
|
||||
}
|
||||
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath;
|
||||
fullPaths: '/' | '/auth/callback';
|
||||
fileRoutesByTo: FileRoutesByTo;
|
||||
to: '/' | '/auth/callback';
|
||||
id: '__root__' | '/' | '/auth/callback';
|
||||
fileRoutesById: FileRoutesById;
|
||||
}
|
||||
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute;
|
||||
AuthCallbackRoute: typeof AuthCallbackRoute;
|
||||
}
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
AuthCallbackRoute: AuthCallbackRoute,
|
||||
};
|
||||
|
||||
export const routeTree = rootRoute
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._addFileTypes<FileRouteTypes>();
|
||||
|
||||
/* ROUTE_MANIFEST_START
|
||||
{
|
||||
"routes": {
|
||||
"__root__": {
|
||||
"filePath": "__root.tsx",
|
||||
"children": [
|
||||
"/",
|
||||
"/auth/callback"
|
||||
]
|
||||
},
|
||||
"/": {
|
||||
"filePath": "index.tsx"
|
||||
},
|
||||
"/auth/callback": {
|
||||
"filePath": "auth/callback.tsx"
|
||||
}
|
||||
}
|
||||
}
|
||||
ROUTE_MANIFEST_END */
|
||||
38
examples/react-tanstack-router/src/routes/__root.tsx
Normal file
38
examples/react-tanstack-router/src/routes/__root.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { Injector } from '@outposts/injection-js';
|
||||
import {
|
||||
Link,
|
||||
Outlet,
|
||||
createRootRouteWithContext,
|
||||
} from '@tanstack/react-router';
|
||||
import { TanStackRouterDevtools } from '@tanstack/router-devtools';
|
||||
import type { OidcSecurityService } from 'oidc-client-rx';
|
||||
|
||||
export interface RouterContext {
|
||||
injector: Injector;
|
||||
oidcSecurityService: OidcSecurityService;
|
||||
}
|
||||
|
||||
export const Route = createRootRouteWithContext<RouterContext>()({
|
||||
component: RootComponent,
|
||||
});
|
||||
|
||||
function RootComponent() {
|
||||
return (
|
||||
<>
|
||||
<div className="flex gap-2 p-2 text-lg">
|
||||
<Link
|
||||
to="/"
|
||||
activeProps={{
|
||||
className: 'font-bold',
|
||||
}}
|
||||
activeOptions={{ exact: true }}
|
||||
>
|
||||
Home
|
||||
</Link>{' '}
|
||||
</div>
|
||||
<hr />
|
||||
<Outlet />
|
||||
<TanStackRouterDevtools position="bottom-right" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
13
examples/react-tanstack-router/src/routes/auth/callback.tsx
Normal file
13
examples/react-tanstack-router/src/routes/auth/callback.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
|
||||
export const Route = createFileRoute('/auth/callback')({
|
||||
component: AuthCallbackComponent,
|
||||
});
|
||||
|
||||
function AuthCallbackComponent() {
|
||||
return (
|
||||
<div className="p-2">
|
||||
<h3>Auth Callback: validating...</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
40
examples/react-tanstack-router/src/routes/index.tsx
Normal file
40
examples/react-tanstack-router/src/routes/index.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { useObservableEagerState } from 'observable-hooks';
|
||||
import { useOidcClient } from 'oidc-client-rx/adapters/react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const Route = createFileRoute('/')({
|
||||
component: HomeComponent,
|
||||
});
|
||||
|
||||
function HomeComponent() {
|
||||
const { oidcSecurityService } = useOidcClient();
|
||||
|
||||
const { isAuthenticated } = useObservableEagerState(
|
||||
oidcSecurityService.isAuthenticated$
|
||||
);
|
||||
|
||||
const handleLogin = useCallback(() => {
|
||||
oidcSecurityService.authorize().subscribe();
|
||||
}, [oidcSecurityService]);
|
||||
|
||||
const handleLogout = useCallback(() => {
|
||||
oidcSecurityService.logoff().subscribe();
|
||||
}, [oidcSecurityService]);
|
||||
|
||||
return (
|
||||
<div className="p-2 text-center">
|
||||
<h1>Welcome OIDC-CLIENT-RX DEMO of react-tanstack-router</h1>
|
||||
<p>Is authenticated? {isAuthenticated ? 'True' : 'False'}</p>
|
||||
{isAuthenticated ? (
|
||||
<button onClick={handleLogout} type="button">
|
||||
Click to Logout
|
||||
</button>
|
||||
) : (
|
||||
<button onClick={handleLogin} type="button">
|
||||
Click to Login
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
13
examples/react-tanstack-router/src/style.css
Normal file
13
examples/react-tanstack-router/src/style.css
Normal file
@@ -0,0 +1,13 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
* {
|
||||
@apply border-gray-200 dark:border-gray-800;
|
||||
}
|
||||
body {
|
||||
@apply bg-gray-50 text-gray-950 dark:bg-gray-900 dark:text-gray-200;
|
||||
}
|
||||
4
examples/react-tanstack-router/tailwind.config.mjs
Normal file
4
examples/react-tanstack-router/tailwind.config.mjs
Normal file
@@ -0,0 +1,4 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ['./src/**/*.{js,jsx,ts,tsx}', './index.html'],
|
||||
};
|
||||
18
examples/react-tanstack-router/tsconfig.json
Normal file
18
examples/react-tanstack-router/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": ".",
|
||||
"lib": ["ES2021", "DOM", "DOM.Iterable"],
|
||||
"useDefineForClassFields": true,
|
||||
"resolveJsonModule": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"noEmit": true,
|
||||
"outDir": "./dist",
|
||||
"declarationDir": "./dist",
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
4
examples/react-tanstack-router/tsr.config.json
Normal file
4
examples/react-tanstack-router/tsr.config.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"routesDirectory": "./src/routes",
|
||||
"generatedRouteTree": "./src/routeTree.gen.ts"
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage'),
|
||||
require('@angular-devkit/build-angular/plugins/karma'),
|
||||
],
|
||||
client: {
|
||||
jasmine: {
|
||||
// you can add configuration options for Jasmine here
|
||||
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||
// for example, you can disable the random execution with `random: false`
|
||||
// or set a specific seed with `seed: 4321`
|
||||
},
|
||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
jasmineHtmlReporter: {
|
||||
suppressAll: true, // removes the duplicated traces
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: require('path').join(
|
||||
__dirname,
|
||||
'../../coverage/oidc-client-rx'
|
||||
),
|
||||
subdir: '.',
|
||||
reporters: [{ type: 'html' }, { type: 'text-summary' }, { type: 'lcov' }],
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
customLaunchers: {
|
||||
ChromeHeadlessNoSandbox: {
|
||||
base: 'ChromeHeadless',
|
||||
flags: ['--no-sandbox'],
|
||||
},
|
||||
},
|
||||
singleRun: false,
|
||||
restartOnFileChange: true,
|
||||
});
|
||||
};
|
||||
41
lefthook.yml
41
lefthook.yml
@@ -1,41 +0,0 @@
|
||||
# EXAMPLE USAGE
|
||||
# Refer for explanation to following link:
|
||||
# https://github.com/evilmartians/lefthook/blob/master/docs/full_guide.md
|
||||
#
|
||||
pre-push:
|
||||
commands:
|
||||
fix-prettier:
|
||||
tags: frontend security
|
||||
glob: '*.{js,ts}'
|
||||
run: npm run fix-prettier {staged_files}
|
||||
|
||||
pre-commit:
|
||||
parallel: true
|
||||
commands:
|
||||
check-blockwords:
|
||||
run: npm run check-blockwords
|
||||
|
||||
lint:
|
||||
run: npm run lint-lib
|
||||
#
|
||||
# pre-commit:
|
||||
# parallel: true
|
||||
# commands:
|
||||
# eslint:
|
||||
# glob: "*.{js,ts}"
|
||||
# run: yarn eslint {staged_files}
|
||||
# rubocop:
|
||||
# tags: backend style
|
||||
# glob: "*.rb"
|
||||
# exclude: "application.rb|routes.rb"
|
||||
# run: bundle exec rubocop --force-exclusion {all_files}
|
||||
# govet:
|
||||
# tags: backend style
|
||||
# files: git ls-files -m
|
||||
# glob: "*.go"
|
||||
# run: go vet {files}
|
||||
# scripts:
|
||||
# "hello.js":
|
||||
# runner: node
|
||||
# "any.go":
|
||||
# runner: go run
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* @license oidc-client-rx
|
||||
* MIT license
|
||||
*/
|
||||
21
licenses/angular-auth-oidc-client.LICENSE
Normal file
21
licenses/angular-auth-oidc-client.LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 damienbod
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
21
licenses/angular.LICENSE
Normal file
21
licenses/angular.LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2010-2025 Google LLC. https://angular.dev/license
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
109
package.json
109
package.json
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "oidc-client-rx",
|
||||
"version": "0.1.0-alpha.8",
|
||||
"homepage": "https://github.com/lonelyhentxi/oidc-client-rx",
|
||||
"author": "lonelyhentxi",
|
||||
"description": "ReactiveX enhanced OIDC and OAuth2 protocol support for browser-based JavaScript applications",
|
||||
@@ -10,48 +11,103 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/lonelyhentxi/oidc-client-rx/issues"
|
||||
},
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs"
|
||||
},
|
||||
"./adapters/react": {
|
||||
"types": "./dist/adapters/react/index.d.ts",
|
||||
"import": "./dist/adapters/react/index.js",
|
||||
"require": "./dist/adapters/react.cjs"
|
||||
},
|
||||
"./adapters/solid-js": {
|
||||
"types": "./dist/adapters/solid-js/index.d.ts",
|
||||
"import": "./dist/adapters/solid-js/index.js",
|
||||
"require": "./dist/adapters/solid-js.cjs"
|
||||
},
|
||||
"./adapters/@tanstack/react-router": {
|
||||
"types": "./dist/adapters/@tanstack/react-router.d.ts",
|
||||
"import": "./dist/adapters/@tanstack/react-router.js",
|
||||
"require": "./dist/adapters/@tanstack/react-router.cjs"
|
||||
},
|
||||
"./adapters/@tanstack/solid-router": {
|
||||
"types": "./dist/adapters/@tanstack/solid-router.d.ts",
|
||||
"import": "./dist/adapters/@tanstack/solid-router.js",
|
||||
"require": "./dist/adapters/@tanstack/solid-router.cjs"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"files": ["dist", "licenses", "LICENSE", "README.md"],
|
||||
"scripts": {
|
||||
"build": "rslib build",
|
||||
"dev": "rslib build --watch",
|
||||
"test": "vitest --code-coverage",
|
||||
"test-ci": "vitest --watch=false --browsers=ChromeHeadlessNoSandbox --code-coverage",
|
||||
"pack": "npm run build && npm pack ./dist",
|
||||
"publish": "npm run build && npm publish ./dist",
|
||||
"coverage": "vitest run --coverage",
|
||||
"test": "vitest --coverage",
|
||||
"test-ci": "vitest --watch=false --coverage",
|
||||
"prepublishOnly": "npm run build",
|
||||
"lint": "ultracite lint",
|
||||
"format": "ultracite format"
|
||||
"format": "ultracite format",
|
||||
"cli": "tsx scripts/cli.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ngify/http": "^2.0.4",
|
||||
"injection-js": "git+https://github.com/mgechev/injection-js.git#81a10e0",
|
||||
"rxjs": ">=7.4.0"
|
||||
"@outposts/injection-js": "^2.5.1",
|
||||
"rfc4648": "^1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tanstack/react-router": "*",
|
||||
"@tanstack/solid-router": "*",
|
||||
"react": ">=16.8.0",
|
||||
"rxjs": "^7.4.0||>=8.0.0",
|
||||
"solid-js": "^1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@evilmartians/lefthook": "^1.0.3",
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@biomejs/js-api": "0.7.1",
|
||||
"@biomejs/wasm-nodejs": "^1.9.4",
|
||||
"@playwright/test": "^1.49.1",
|
||||
"@rslib/core": "^0.3.1",
|
||||
"@types/jasmine": "^4.0.0",
|
||||
"@types/node": "^22.10.1",
|
||||
"@vitest/coverage-v8": "^3.0.1",
|
||||
"rfc4648": "^1.5.0",
|
||||
"@rslib/core": "^0.5.3",
|
||||
"@swc/core": "^1.10.12",
|
||||
"@tanstack/react-router": "^1.112.11",
|
||||
"@tanstack/solid-router": "^1.112.11",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^22.12.0",
|
||||
"@types/react": "^19.0.8",
|
||||
"@vitest/coverage-v8": "^3.0.4",
|
||||
"commander": "^13.1.0",
|
||||
"happy-dom": "^17.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"oxc-parser": "^0.54.0",
|
||||
"oxc-walker": "^0.2.2",
|
||||
"playwright": "^1.50.0",
|
||||
"react": "^19.0.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.4.0",
|
||||
"solid-js": "^1.9.5",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.7.3",
|
||||
"ultracite": "^4.1.15",
|
||||
"vitest": "^3.0.1"
|
||||
"unplugin-swc": "^1.5.1",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.0.4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"@tanstack/react-router": {
|
||||
"optional": true
|
||||
},
|
||||
"@tanstack/solid-router": {
|
||||
"optional": true
|
||||
},
|
||||
"solid-js": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"keywords": [
|
||||
"rxjs",
|
||||
@@ -68,6 +124,19 @@
|
||||
"certified",
|
||||
"oauth",
|
||||
"authorization",
|
||||
"reactivex"
|
||||
"reactivex",
|
||||
"injection-js",
|
||||
"injection"
|
||||
],
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@biomejs/biome",
|
||||
"@swc/core",
|
||||
"core-js",
|
||||
"edgedriver",
|
||||
"esbuild",
|
||||
"geckodriver",
|
||||
"msw"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
// TODO
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
|
||||
5819
pnpm-lock.yaml
generated
5819
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
- 'examples/*'
|
||||
@@ -2,17 +2,40 @@ import { defineConfig } from '@rslib/core';
|
||||
|
||||
export default defineConfig({
|
||||
source: {
|
||||
tsconfigPath: './tsconfig.lib.json'
|
||||
tsconfigPath: './tsconfig.lib.json',
|
||||
},
|
||||
lib: [
|
||||
{
|
||||
format: 'esm',
|
||||
syntax: 'es2021',
|
||||
dts: true,
|
||||
bundle: false,
|
||||
dts: {
|
||||
bundle: false,
|
||||
build: false,
|
||||
distPath: './dist',
|
||||
},
|
||||
source: {
|
||||
entry: {
|
||||
index: ['src/**/*.ts', '!**/*.spec.ts', '!src/testing/**/*'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
format: 'cjs',
|
||||
syntax: 'es2021',
|
||||
dts: false,
|
||||
bundle: true,
|
||||
source: {
|
||||
entry: {
|
||||
index: './src/index.ts',
|
||||
'adapters/react': './src/adapters/react/index.ts',
|
||||
'adapters/solid-js': './src/adapters/solid-js/index.ts',
|
||||
'adapters/@tanstack/react-router':
|
||||
'./src/adapters/@tanstack/react-router.ts',
|
||||
'adapters/@tanstack/solid-router':
|
||||
'./src/adapters/@tanstack/solid-router.ts',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
output: {
|
||||
|
||||
19
scripts/cli.ts
Normal file
19
scripts/cli.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { rewriteAllObservableSubscribeTofirstValueFrom } from './code-transform';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.version('1.0.0')
|
||||
.description('A CLI tool to help develop oidc-client-rx');
|
||||
|
||||
program
|
||||
.command('rewrite <pattern>')
|
||||
.description('Rewrite files matching the given glob pattern')
|
||||
.action(async (pattern: string) => {
|
||||
await rewriteAllObservableSubscribeTofirstValueFrom(pattern);
|
||||
});
|
||||
|
||||
program.parse(process.argv);
|
||||
82
scripts/code-transform.spec.ts
Normal file
82
scripts/code-transform.spec.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { describe, it } from 'node:test';
|
||||
import { Biome, Distribution } from '@biomejs/js-api';
|
||||
import { rewriteObservableSubscribeTofirstValueFrom } from './code-transform';
|
||||
|
||||
describe('rewriteSpecObservableSubscribeTofirstValueFrom', () => {
|
||||
it('should transform simple example valid string', async () => {
|
||||
const actual = await rewriteObservableSubscribeTofirstValueFrom(
|
||||
'index.ts',
|
||||
`refreshSessionIframeService
|
||||
.refreshSessionWithIframe(allConfigs[0]!, allConfigs)
|
||||
.subscribe((result) => {
|
||||
expect(
|
||||
result
|
||||
).toHaveBeenCalledExactlyOnceWith(
|
||||
'a-url',
|
||||
allConfigs[0]!,
|
||||
allConfigs
|
||||
);
|
||||
});`
|
||||
);
|
||||
|
||||
const expect = `const result = await firstValueFrom(refreshSessionIframeService.refreshSessionWithIframe(allConfigs[0]!, allConfigs));
|
||||
expect(result).toHaveBeenCalledExactlyOnceWith('a-url',allConfigs[0]!,allConfigs);`;
|
||||
|
||||
const biome = await Biome.create({
|
||||
distribution: Distribution.NODE,
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
biome.formatContent(actual, { filePath: 'index.ts' }).content,
|
||||
biome.formatContent(expect, { filePath: 'index.ts' }).content
|
||||
);
|
||||
});
|
||||
|
||||
it('should rewrite complex exmaple to valid string', async () => {
|
||||
const actual = await rewriteObservableSubscribeTofirstValueFrom(
|
||||
'index.ts',
|
||||
`codeFlowCallbackService
|
||||
.authenticatedCallbackWithCode('some-url4', config, [config])
|
||||
.subscribe({
|
||||
error: (err: any) => {
|
||||
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
||||
expect(resetCodeFlowInProgressSpy).toHaveBeenCalled();
|
||||
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
|
||||
expect(err).toBeTruthy();
|
||||
},
|
||||
next: (abc) => {
|
||||
expect(abc).toBeTruthy();
|
||||
},
|
||||
complete () {
|
||||
expect.fail('complete')
|
||||
}
|
||||
});`
|
||||
);
|
||||
|
||||
const expect = `
|
||||
try {
|
||||
const abc = await firstValueFrom(codeFlowCallbackService.authenticatedCallbackWithCode('some-url4', config, [config]));
|
||||
expect(abc).toBeTruthy();
|
||||
} catch (err: any) {
|
||||
if (err instanceof EmptyError) {
|
||||
expect.fail('complete')
|
||||
} else {
|
||||
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
||||
expect(resetCodeFlowInProgressSpy).toHaveBeenCalled();
|
||||
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
|
||||
expect(err).toBeTruthy();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const biome = await Biome.create({
|
||||
distribution: Distribution.NODE,
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
biome.formatContent(actual, { filePath: 'index.ts' }).content,
|
||||
biome.formatContent(expect, { filePath: 'index.ts' }).content
|
||||
);
|
||||
});
|
||||
});
|
||||
173
scripts/code-transform.ts
Normal file
173
scripts/code-transform.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fsp from 'node:fs/promises';
|
||||
import {
|
||||
type ArrowFunctionExpression,
|
||||
// biome-ignore lint/suspicious/noShadowRestrictedNames: <explanation>
|
||||
type Function,
|
||||
type MagicString,
|
||||
type Statement,
|
||||
parseSync,
|
||||
} from 'oxc-parser';
|
||||
import { walk } from 'oxc-walker';
|
||||
|
||||
function sourceTextFromNode(
|
||||
context: { magicString?: MagicString },
|
||||
node: { start: number; end: number }
|
||||
): string {
|
||||
const magicString = context.magicString;
|
||||
assert(magicString, 'magicString should be defined');
|
||||
const start = node.start;
|
||||
const end = node.end;
|
||||
return magicString.getSourceText(start, end);
|
||||
}
|
||||
|
||||
export async function rewriteObservableSubscribeTofirstValueFrom(
|
||||
filename: string,
|
||||
content?: string
|
||||
) {
|
||||
const code = content ?? (await fsp.readFile(filename, 'utf-8'));
|
||||
const parsedResult = parseSync('index.ts', code);
|
||||
const magicString = parsedResult.magicString;
|
||||
walk(parsedResult, {
|
||||
leave(node, _, context) {
|
||||
const transformExprs = <T extends Statement[]>(
|
||||
children: T
|
||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: <explanation>
|
||||
): T => {
|
||||
const newChildren: T = [] as any as T;
|
||||
for (const child of children) {
|
||||
if (
|
||||
child.type === 'ExpressionStatement' &&
|
||||
child.expression.type === 'CallExpression' &&
|
||||
child.expression.callee.type === 'StaticMemberExpression' &&
|
||||
child.expression.callee.property.name === 'subscribe'
|
||||
) {
|
||||
let next: ArrowFunctionExpression | Function | undefined;
|
||||
let error: ArrowFunctionExpression | Function | undefined;
|
||||
let complete: ArrowFunctionExpression | Function | undefined;
|
||||
|
||||
if (child.expression.arguments[0]?.type === 'ObjectExpression') {
|
||||
const obj = child.expression.arguments[0];
|
||||
for (const prop of obj.properties) {
|
||||
if (
|
||||
prop.type === 'ObjectProperty' &&
|
||||
prop.key.type === 'Identifier' &&
|
||||
(prop.value.type === 'FunctionExpression' ||
|
||||
prop.value.type === 'ArrowFunctionExpression')
|
||||
) {
|
||||
if (prop.key.name === 'next') {
|
||||
next = prop.value;
|
||||
} else if (prop.key.name === 'error') {
|
||||
error = prop.value;
|
||||
} else if (prop.key.name === 'complete') {
|
||||
complete = prop.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
child.expression.arguments.find(
|
||||
(arg) =>
|
||||
arg.type === 'FunctionExpression' ||
|
||||
arg.type === 'ArrowFunctionExpression'
|
||||
)
|
||||
) {
|
||||
const args: Array<
|
||||
Function | ArrowFunctionExpression | undefined
|
||||
> = child.expression.arguments.map((arg) =>
|
||||
arg.type === 'FunctionExpression' ||
|
||||
arg.type === 'ArrowFunctionExpression'
|
||||
? arg
|
||||
: undefined
|
||||
);
|
||||
next = args[0];
|
||||
error = args[1];
|
||||
complete = args[2];
|
||||
}
|
||||
let newContent = `await firstValueFrom(${sourceTextFromNode(context, child.expression.callee.object)});`;
|
||||
|
||||
if (next) {
|
||||
const nextParam =
|
||||
next?.params?.items?.[0]?.type === 'FormalParameter'
|
||||
? sourceTextFromNode(context, next.params.items[0])
|
||||
: undefined;
|
||||
|
||||
if (nextParam) {
|
||||
newContent = `const ${nextParam} = ${newContent}`;
|
||||
}
|
||||
newContent += (next.body?.statements || [])
|
||||
.map((s) => sourceTextFromNode(context, s))
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
if (error || complete) {
|
||||
const errorParam =
|
||||
error?.params?.items?.[0]?.type === 'FormalParameter' &&
|
||||
error.params.items[0].pattern.type === 'Identifier'
|
||||
? sourceTextFromNode(context, error.params.items[0])
|
||||
: 'err';
|
||||
const errorParamName =
|
||||
error?.params?.items?.[0]?.type === 'FormalParameter' &&
|
||||
error.params.items[0].pattern.type === 'Identifier'
|
||||
? error.params.items[0].pattern.name
|
||||
: 'err';
|
||||
|
||||
let errorBody = '';
|
||||
if (error) {
|
||||
errorBody += (error.body?.statements || [])
|
||||
.map((s) => sourceTextFromNode(context, s))
|
||||
.join('\n');
|
||||
}
|
||||
if (complete) {
|
||||
const completBody = `if (${errorParamName} instanceof EmptyError) { ${(complete.body?.statements || []).map((s) => sourceTextFromNode(context, s)).join('\n')}}`;
|
||||
if (errorBody) {
|
||||
errorBody = `${completBody} else { ${errorBody} }`;
|
||||
} else {
|
||||
errorBody = completBody;
|
||||
}
|
||||
}
|
||||
|
||||
newContent = `try { ${newContent} } catch (${errorParam}) { ${errorBody} }`;
|
||||
}
|
||||
|
||||
const newNodes = parseSync('index.html', newContent).program.body;
|
||||
|
||||
magicString.remove(child.start, child.end);
|
||||
magicString.appendLeft(child.start, newContent);
|
||||
|
||||
newChildren.push(...newNodes);
|
||||
} else {
|
||||
newChildren.push(child as any);
|
||||
}
|
||||
}
|
||||
return newChildren;
|
||||
};
|
||||
if ('body' in node && Array.isArray(node.body) && node.body.length > 0) {
|
||||
const children = node.body;
|
||||
node.body = transformExprs(children as any)!;
|
||||
} else if (
|
||||
'body' in node &&
|
||||
node.body &&
|
||||
'type' in node.body &&
|
||||
node.body.type === 'FunctionBody'
|
||||
) {
|
||||
const children = node.body.statements;
|
||||
node.body.statements = transformExprs(children)!;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const result = magicString.toString();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function rewriteAllObservableSubscribeTofirstValueFrom(
|
||||
pattern: string | string[]
|
||||
) {
|
||||
const files = fsp.glob(pattern);
|
||||
for await (const file of files) {
|
||||
const result = await rewriteObservableSubscribeTofirstValueFrom(file);
|
||||
|
||||
await fsp.writeFile(file, result, 'utf-8');
|
||||
}
|
||||
}
|
||||
41
src/adapters/@tanstack/react-router.ts
Normal file
41
src/adapters/@tanstack/react-router.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { InjectionToken, inject } from '@outposts/injection-js';
|
||||
import type { AnyRouter } from '@tanstack/react-router';
|
||||
import type { AuthFeature } from '../../features';
|
||||
import { AbstractRouter, ROUTER_ABS_PATH_PATTERN } from '../../router';
|
||||
|
||||
export type TanStackRouter = AnyRouter;
|
||||
|
||||
export const TANSTACK_ROUTER = new InjectionToken<TanStackRouter>(
|
||||
'TANSTACK_ROUTER'
|
||||
);
|
||||
|
||||
export class TanStackRouterAdapter implements AbstractRouter<string> {
|
||||
private router = inject(TANSTACK_ROUTER);
|
||||
|
||||
navigateByUrl(url: string): void {
|
||||
this.router.navigate({
|
||||
href: ROUTER_ABS_PATH_PATTERN.test(url) ? url : `/${url}`,
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentNavigation() {
|
||||
return {
|
||||
extractedUrl: this.router.state.location.href,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function withTanstackRouter(router: TanStackRouter): AuthFeature {
|
||||
return {
|
||||
ɵproviders: [
|
||||
{
|
||||
provide: TANSTACK_ROUTER,
|
||||
useValue: router,
|
||||
},
|
||||
{
|
||||
provide: AbstractRouter,
|
||||
useClass: TanStackRouterAdapter,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
41
src/adapters/@tanstack/solid-router.ts
Normal file
41
src/adapters/@tanstack/solid-router.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { InjectionToken, inject } from '@outposts/injection-js';
|
||||
import type { AnyRouter } from '@tanstack/solid-router';
|
||||
import type { AuthFeature } from '../../features';
|
||||
import { AbstractRouter, ROUTER_ABS_PATH_PATTERN } from '../../router';
|
||||
|
||||
export type TanStackRouter = AnyRouter;
|
||||
|
||||
export const TANSTACK_ROUTER = new InjectionToken<TanStackRouter>(
|
||||
'TANSTACK_ROUTER'
|
||||
);
|
||||
|
||||
export class TanStackRouterAdapter implements AbstractRouter<string> {
|
||||
private router = inject(TANSTACK_ROUTER);
|
||||
|
||||
navigateByUrl(url: string): void {
|
||||
this.router.navigate({
|
||||
href: ROUTER_ABS_PATH_PATTERN.test(url) ? url : `/${url}`,
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentNavigation() {
|
||||
return {
|
||||
extractedUrl: this.router.state.location.href,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function withTanstackRouter(router: TanStackRouter): AuthFeature {
|
||||
return {
|
||||
ɵproviders: [
|
||||
{
|
||||
provide: TANSTACK_ROUTER,
|
||||
useValue: router,
|
||||
},
|
||||
{
|
||||
provide: AbstractRouter,
|
||||
useClass: TanStackRouterAdapter,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
47
src/adapters/react/index.ts
Normal file
47
src/adapters/react/index.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import type { InjectionToken, Injector, Type } from '@outposts/injection-js';
|
||||
import {
|
||||
type PropsWithChildren,
|
||||
createContext,
|
||||
createElement,
|
||||
useContext,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
import { OidcSecurityService } from '../../oidc.security.service';
|
||||
|
||||
export const InjectorContextVoidInjector: Injector = {
|
||||
get: <T>(_token: Type<T> | InjectionToken<T>, _notFoundValue?: T): T => {
|
||||
throw new Error('Please wrap with a InjectorContext.Provider first');
|
||||
},
|
||||
};
|
||||
|
||||
export const InjectorContext = createContext<Injector>(
|
||||
InjectorContextVoidInjector
|
||||
);
|
||||
|
||||
export function InjectorProvider({
|
||||
injector,
|
||||
...props
|
||||
}: PropsWithChildren<{ injector: Injector }>) {
|
||||
return createElement(InjectorContext, {
|
||||
...props,
|
||||
value: injector,
|
||||
});
|
||||
}
|
||||
|
||||
export function useInjector() {
|
||||
return useContext(InjectorContext);
|
||||
}
|
||||
|
||||
export function useOidcClient() {
|
||||
const injector = useInjector();
|
||||
|
||||
const oidcSecurityService = useMemo(
|
||||
() => injector.get(OidcSecurityService),
|
||||
[injector]
|
||||
);
|
||||
|
||||
return {
|
||||
injector,
|
||||
oidcSecurityService,
|
||||
};
|
||||
}
|
||||
43
src/adapters/solid-js/index.ts
Normal file
43
src/adapters/solid-js/index.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { InjectionToken, Injector, Type } from '@outposts/injection-js';
|
||||
import {
|
||||
type FlowProps,
|
||||
createContext,
|
||||
createMemo,
|
||||
mergeProps,
|
||||
splitProps,
|
||||
useContext,
|
||||
} from 'solid-js';
|
||||
import { OidcSecurityService } from '../../oidc.security.service';
|
||||
|
||||
export const InjectorContextVoidInjector: Injector = {
|
||||
get: <T>(_token: Type<T> | InjectionToken<T>, _notFoundValue?: T): T => {
|
||||
throw new Error('Please wrap with a InjectorContext.Provider first');
|
||||
},
|
||||
};
|
||||
|
||||
export const InjectorContext = createContext<Injector>(
|
||||
InjectorContextVoidInjector
|
||||
);
|
||||
|
||||
export function InjectorProvider(props: FlowProps<{ injector: Injector }>) {
|
||||
const [local, others] = splitProps(props, ['injector']);
|
||||
const providerProps = mergeProps(others, { value: local.injector });
|
||||
return InjectorContext.Provider(providerProps);
|
||||
}
|
||||
|
||||
export function useInjector() {
|
||||
return useContext(InjectorContext);
|
||||
}
|
||||
|
||||
export function useOidcClient() {
|
||||
const injector = useInjector();
|
||||
|
||||
const oidcSecurityService = createMemo(() =>
|
||||
injector.get(OidcSecurityService)
|
||||
);
|
||||
|
||||
return {
|
||||
injector,
|
||||
oidcSecurityService: oidcSecurityService(),
|
||||
};
|
||||
}
|
||||
@@ -1,35 +1,25 @@
|
||||
import { TestBed } from '@/testing';
|
||||
import {
|
||||
HttpHeaders,
|
||||
provideHttpClient,
|
||||
withInterceptorsFromDi,
|
||||
} from '@angular/common/http';
|
||||
import {
|
||||
HttpTestingController,
|
||||
type DefaultHttpTestingController,
|
||||
HTTP_CLIENT_TEST_CONTROLLER,
|
||||
provideHttpClientTesting,
|
||||
} from '@angular/common/http/testing';
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
} from '@/testing/http';
|
||||
import { HttpHeaders } from '@ngify/http';
|
||||
import { ReplaySubject, firstValueFrom, share } from 'rxjs';
|
||||
import { DataService } from './data.service';
|
||||
import { HttpBaseService } from './http-base.service';
|
||||
|
||||
describe('Data Service', () => {
|
||||
let dataService: DataService;
|
||||
let httpMock: HttpTestingController;
|
||||
let httpMock: DefaultHttpTestingController;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [],
|
||||
providers: [
|
||||
DataService,
|
||||
HttpBaseService,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
providers: [DataService, HttpBaseService, provideHttpClientTesting()],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
dataService = TestBed.inject(DataService);
|
||||
httpMock = TestBed.inject(HttpTestingController);
|
||||
httpMock = TestBed.inject(HTTP_CLIENT_TEST_CONTROLLER);
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
@@ -37,14 +27,20 @@ describe('Data Service', () => {
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
it('get call sets the accept header', waitForAsync(() => {
|
||||
it('get call sets the accept header', async () => {
|
||||
const url = 'testurl';
|
||||
|
||||
dataService
|
||||
.get(url, { configId: 'configId1' })
|
||||
.subscribe((data: unknown) => {
|
||||
expect(data).toBe('bodyData');
|
||||
});
|
||||
const test$ = dataService.get(url, { configId: 'configId1' }).pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(1),
|
||||
resetOnError: false,
|
||||
resetOnComplete: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
|
||||
test$.subscribe();
|
||||
|
||||
const req = httpMock.expectOne(url);
|
||||
|
||||
expect(req.request.method).toBe('GET');
|
||||
@@ -52,37 +48,55 @@ describe('Data Service', () => {
|
||||
|
||||
req.flush('bodyData');
|
||||
|
||||
httpMock.verify();
|
||||
}));
|
||||
const data = await firstValueFrom(test$);
|
||||
expect(data).toBe('bodyData');
|
||||
|
||||
it('get call with token the accept header and the token', waitForAsync(() => {
|
||||
httpMock.verify();
|
||||
});
|
||||
|
||||
it('get call with token the accept header and the token', async () => {
|
||||
const url = 'testurl';
|
||||
const token = 'token';
|
||||
|
||||
dataService
|
||||
.get(url, { configId: 'configId1' }, token)
|
||||
.subscribe((data: unknown) => {
|
||||
expect(data).toBe('bodyData');
|
||||
});
|
||||
const test$ = dataService.get(url, { configId: 'configId1' }, token).pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(1),
|
||||
resetOnError: false,
|
||||
resetOnComplete: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
|
||||
test$.subscribe();
|
||||
|
||||
const req = httpMock.expectOne(url);
|
||||
|
||||
expect(req.request.method).toBe('GET');
|
||||
expect(req.request.headers.get('Accept')).toBe('application/json');
|
||||
expect(req.request.headers.get('Authorization')).toBe('Bearer ' + token);
|
||||
expect(req.request.headers.get('Authorization')).toBe(`Bearer ${token}`);
|
||||
|
||||
req.flush('bodyData');
|
||||
|
||||
httpMock.verify();
|
||||
}));
|
||||
const data = await firstValueFrom(test$);
|
||||
expect(data).toBe('bodyData');
|
||||
|
||||
it('call without ngsw-bypass param by default', waitForAsync(() => {
|
||||
httpMock.verify();
|
||||
});
|
||||
|
||||
it('call without ngsw-bypass param by default', async () => {
|
||||
const url = 'testurl';
|
||||
|
||||
dataService
|
||||
.get(url, { configId: 'configId1' })
|
||||
.subscribe((data: unknown) => {
|
||||
expect(data).toBe('bodyData');
|
||||
});
|
||||
const test$ = dataService.get(url, { configId: 'configId1' }).pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(1),
|
||||
resetOnError: false,
|
||||
resetOnComplete: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
|
||||
test$.subscribe();
|
||||
|
||||
const req = httpMock.expectOne(url);
|
||||
|
||||
expect(req.request.method).toBe('GET');
|
||||
@@ -91,36 +105,67 @@ describe('Data Service', () => {
|
||||
|
||||
req.flush('bodyData');
|
||||
|
||||
httpMock.verify();
|
||||
}));
|
||||
const data = await firstValueFrom(test$);
|
||||
expect(data).toBe('bodyData');
|
||||
|
||||
it('call with ngsw-bypass param', waitForAsync(() => {
|
||||
httpMock.verify();
|
||||
});
|
||||
|
||||
it('call with ngsw-bypass param', async () => {
|
||||
const url = 'testurl';
|
||||
|
||||
dataService
|
||||
.get(url, { configId: 'configId1', ngswBypass: true })
|
||||
.subscribe((data: unknown) => {
|
||||
expect(data).toBe('bodyData');
|
||||
});
|
||||
const req = httpMock.expectOne(url + '?ngsw-bypass=');
|
||||
const test$ = dataService
|
||||
.get(url, {
|
||||
configId: 'configId1',
|
||||
ngswBypass: true,
|
||||
})
|
||||
.pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(1),
|
||||
resetOnError: false,
|
||||
resetOnComplete: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
|
||||
test$.subscribe();
|
||||
|
||||
const req = httpMock.expectOne(`${url}?ngsw-bypass=`);
|
||||
|
||||
expect(req.request.method).toBe('GET');
|
||||
expect(req.request.headers.get('Accept')).toBe('application/json');
|
||||
expect(req.request.params.get('ngsw-bypass')).toBe('');
|
||||
|
||||
// @TODO: should make a issue to ngify
|
||||
// expect(req.request.params.('ngsw-bypass')).toBe('');
|
||||
|
||||
expect(req.request.params.has('ngsw-bypass')).toBeTruthy();
|
||||
|
||||
req.flush('bodyData');
|
||||
|
||||
const data = await firstValueFrom(test$);
|
||||
expect(data).toBe('bodyData');
|
||||
|
||||
httpMock.verify();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('post', () => {
|
||||
it('call sets the accept header when no other params given', waitForAsync(() => {
|
||||
it('call sets the accept header when no other params given', async () => {
|
||||
const url = 'testurl';
|
||||
|
||||
dataService
|
||||
const test$ = dataService
|
||||
.post(url, { some: 'thing' }, { configId: 'configId1' })
|
||||
.subscribe();
|
||||
.pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(1),
|
||||
resetOnError: false,
|
||||
resetOnComplete: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
|
||||
test$.subscribe();
|
||||
|
||||
const req = httpMock.expectOne(url);
|
||||
|
||||
expect(req.request.method).toBe('POST');
|
||||
@@ -128,18 +173,30 @@ describe('Data Service', () => {
|
||||
|
||||
req.flush('bodyData');
|
||||
|
||||
httpMock.verify();
|
||||
}));
|
||||
await firstValueFrom(test$);
|
||||
|
||||
it('call sets custom headers ONLY (No ACCEPT header) when custom headers are given', waitForAsync(() => {
|
||||
await httpMock.verify();
|
||||
});
|
||||
|
||||
it('call sets custom headers ONLY (No ACCEPT header) when custom headers are given', async () => {
|
||||
const url = 'testurl';
|
||||
let headers = new HttpHeaders();
|
||||
|
||||
headers = headers.set('X-MyHeader', 'Genesis');
|
||||
|
||||
dataService
|
||||
const test$ = dataService
|
||||
.post(url, { some: 'thing' }, { configId: 'configId1' }, headers)
|
||||
.subscribe();
|
||||
.pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(1),
|
||||
resetOnError: false,
|
||||
resetOnComplete: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
|
||||
test$.subscribe();
|
||||
|
||||
const req = httpMock.expectOne(url);
|
||||
|
||||
expect(req.request.method).toBe('POST');
|
||||
@@ -148,15 +205,27 @@ describe('Data Service', () => {
|
||||
|
||||
req.flush('bodyData');
|
||||
|
||||
httpMock.verify();
|
||||
}));
|
||||
await firstValueFrom(test$);
|
||||
|
||||
it('call without ngsw-bypass param by default', waitForAsync(() => {
|
||||
httpMock.verify();
|
||||
});
|
||||
|
||||
it('call without ngsw-bypass param by default', async () => {
|
||||
const url = 'testurl';
|
||||
|
||||
dataService
|
||||
const test$ = dataService
|
||||
.post(url, { some: 'thing' }, { configId: 'configId1' })
|
||||
.subscribe();
|
||||
.pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(1),
|
||||
resetOnError: false,
|
||||
resetOnComplete: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
|
||||
test$.subscribe();
|
||||
|
||||
const req = httpMock.expectOne(url);
|
||||
|
||||
expect(req.request.method).toBe('POST');
|
||||
@@ -165,28 +234,46 @@ describe('Data Service', () => {
|
||||
|
||||
req.flush('bodyData');
|
||||
|
||||
httpMock.verify();
|
||||
}));
|
||||
await firstValueFrom(test$);
|
||||
|
||||
it('call with ngsw-bypass param', waitForAsync(() => {
|
||||
httpMock.verify();
|
||||
});
|
||||
|
||||
it('call with ngsw-bypass param', async () => {
|
||||
const url = 'testurl';
|
||||
|
||||
dataService
|
||||
const test$ = dataService
|
||||
.post(
|
||||
url,
|
||||
{ some: 'thing' },
|
||||
{ configId: 'configId1', ngswBypass: true }
|
||||
)
|
||||
.subscribe();
|
||||
const req = httpMock.expectOne(url + '?ngsw-bypass=');
|
||||
.pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(1),
|
||||
resetOnError: false,
|
||||
resetOnComplete: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
|
||||
test$.subscribe();
|
||||
|
||||
const req = httpMock.expectOne(`${url}?ngsw-bypass=`);
|
||||
|
||||
expect(req.request.method).toBe('POST');
|
||||
expect(req.request.headers.get('Accept')).toBe('application/json');
|
||||
expect(req.request.params.get('ngsw-bypass')).toBe('');
|
||||
|
||||
// @TODO: should make a issue to ngify
|
||||
// expect(req.request.params.('ngsw-bypass')).toBe('');
|
||||
|
||||
expect(req.request.params.has('ngsw-bypass')).toBeTruthy();
|
||||
|
||||
req.flush('bodyData');
|
||||
|
||||
await firstValueFrom(test$);
|
||||
|
||||
httpMock.verify();
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { HttpHeaders, HttpParams } from '@ngify/http';
|
||||
import { Injectable, inject } from 'injection-js';
|
||||
import { Observable } from 'rxjs';
|
||||
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import { HttpHeaders } from '@ngify/http';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import { HttpParams } from '../http';
|
||||
import { HttpBaseService } from './http-base.service';
|
||||
|
||||
const NGSW_CUSTOM_PARAM = 'ngsw-bypass';
|
||||
@@ -41,10 +42,10 @@ export class DataService {
|
||||
|
||||
headers = headers.set('Accept', 'application/json');
|
||||
|
||||
if (!!token) {
|
||||
if (token) {
|
||||
headers = headers.set(
|
||||
'Authorization',
|
||||
'Bearer ' + decodeURIComponent(token)
|
||||
`Bearer ${decodeURIComponent(token)}`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
import { HttpClient } from '@ngify/http';
|
||||
import { Injectable, inject } from 'injection-js';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import type { Observable } from 'rxjs';
|
||||
import { HTTP_CLIENT, type HttpHeaders, type HttpParams } from '../http';
|
||||
|
||||
@Injectable()
|
||||
export class HttpBaseService {
|
||||
constructor() {}
|
||||
private readonly http = inject(HTTP_CLIENT);
|
||||
|
||||
private readonly http = inject(HttpClient);
|
||||
|
||||
get<T>(url: string, params?: { [key: string]: unknown }): Observable<T> {
|
||||
return this.http.get<T>(url, params);
|
||||
get<T>(
|
||||
url: string,
|
||||
options: { headers?: HttpHeaders; params?: HttpParams } = {}
|
||||
): Observable<T> {
|
||||
return this.http.get<T>(url, {
|
||||
...options,
|
||||
params: options.params?.toNgify(),
|
||||
});
|
||||
}
|
||||
|
||||
post<T>(
|
||||
url: string,
|
||||
body: unknown,
|
||||
params?: { [key: string]: unknown }
|
||||
options: { headers?: HttpHeaders; params?: HttpParams } = {}
|
||||
): Observable<T> {
|
||||
return this.http.post<T>(url, body, params);
|
||||
return this.http.post<T>(url, body as any, {
|
||||
...options,
|
||||
params: options.params?.toNgify(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PassedInitialConfig, createStaticLoader } from './auth-config';
|
||||
import { type PassedInitialConfig, createStaticLoader } from './auth-config';
|
||||
|
||||
describe('AuthConfig', () => {
|
||||
describe('createStaticLoader', () => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { InjectionToken, Provider } from 'injection-js';
|
||||
import { InjectionToken, type Provider } from '@outposts/injection-js';
|
||||
import {
|
||||
StsConfigLoader,
|
||||
type StsConfigLoader,
|
||||
StsConfigStaticLoader,
|
||||
} from './config/loader/config-loader';
|
||||
import { OpenIdConfiguration } from './config/openid-configuration';
|
||||
import type { OpenIdConfiguration } from './config/openid-configuration';
|
||||
|
||||
export interface PassedInitialConfig {
|
||||
config?: OpenIdConfiguration | OpenIdConfiguration[];
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
|
||||
import { Observable } from 'rxjs';
|
||||
import { mockProvider } from '../../test/auto-mock';
|
||||
import { vi } from 'vitest';
|
||||
import { LoggerService } from '../logging/logger.service';
|
||||
import { EventTypes } from '../public-events/event-types';
|
||||
import { PublicEventsService } from '../public-events/public-events.service';
|
||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||
import { mockProvider } from '../testing/mock';
|
||||
import { PlatformProvider } from '../utils/platform-provider/platform.provider';
|
||||
import { TokenValidationService } from '../validation/token-validation.service';
|
||||
import { ValidationResult } from '../validation/validation-result';
|
||||
import type { ValidationResult } from '../validation/validation-result';
|
||||
import { AuthStateService } from './auth-state.service';
|
||||
|
||||
describe('Auth State Service', () => {
|
||||
@@ -27,9 +28,6 @@ describe('Auth State Service', () => {
|
||||
mockProvider(StoragePersistenceService),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
authStateService = TestBed.inject(AuthStateService);
|
||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||
eventsService = TestBed.inject(PublicEventsService);
|
||||
@@ -40,13 +38,13 @@ describe('Auth State Service', () => {
|
||||
expect(authStateService).toBeTruthy();
|
||||
});
|
||||
|
||||
it('public authorize$ is observable$', () => {
|
||||
expect(authStateService.authenticated$).toEqual(jasmine.any(Observable));
|
||||
it('authorize$ is observable$', () => {
|
||||
expect(authStateService.authenticated$).toBeInstanceOf(Observable);
|
||||
});
|
||||
|
||||
describe('setAuthorizedAndFireEvent', () => {
|
||||
it('throws correct event with single config', () => {
|
||||
const spy = spyOn(
|
||||
const spy = vi.spyOn(
|
||||
(authStateService as any).authenticatedInternal$,
|
||||
'next'
|
||||
);
|
||||
@@ -55,7 +53,7 @@ describe('Auth State Service', () => {
|
||||
{ configId: 'configId1' },
|
||||
]);
|
||||
|
||||
expect(spy).toHaveBeenCalledOnceWith({
|
||||
expect(spy).toHaveBeenCalledExactlyOnceWith({
|
||||
isAuthenticated: true,
|
||||
allConfigsAuthenticated: [
|
||||
{ configId: 'configId1', isAuthenticated: true },
|
||||
@@ -64,7 +62,7 @@ describe('Auth State Service', () => {
|
||||
});
|
||||
|
||||
it('throws correct event with multiple configs', () => {
|
||||
const spy = spyOn(
|
||||
const spy = vi.spyOn(
|
||||
(authStateService as any).authenticatedInternal$,
|
||||
'next'
|
||||
);
|
||||
@@ -74,7 +72,7 @@ describe('Auth State Service', () => {
|
||||
{ configId: 'configId2' },
|
||||
]);
|
||||
|
||||
expect(spy).toHaveBeenCalledOnceWith({
|
||||
expect(spy).toHaveBeenCalledExactlyOnceWith({
|
||||
isAuthenticated: false,
|
||||
allConfigsAuthenticated: [
|
||||
{ configId: 'configId1', isAuthenticated: false },
|
||||
@@ -86,26 +84,34 @@ describe('Auth State Service', () => {
|
||||
it('throws correct event with multiple configs, one is authenticated', () => {
|
||||
const allConfigs = [{ configId: 'configId1' }, { configId: 'configId2' }];
|
||||
|
||||
spyOn(storagePersistenceService, 'getAccessToken')
|
||||
.withArgs(allConfigs[0])
|
||||
.and.returnValue('someAccessToken')
|
||||
.withArgs(allConfigs[1])
|
||||
.and.returnValue('');
|
||||
mockImplementationWhenArgsEqual(
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'getAccessToken'),
|
||||
[allConfigs[0]!],
|
||||
() => 'someAccessToken'
|
||||
),
|
||||
[allConfigs[1]!],
|
||||
() => ''
|
||||
);
|
||||
|
||||
spyOn(storagePersistenceService, 'getIdToken')
|
||||
.withArgs(allConfigs[0])
|
||||
.and.returnValue('someIdToken')
|
||||
.withArgs(allConfigs[1])
|
||||
.and.returnValue('');
|
||||
mockImplementationWhenArgsEqual(
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'getIdToken'),
|
||||
[allConfigs[0]!],
|
||||
() => 'someIdToken'
|
||||
),
|
||||
[allConfigs[1]!],
|
||||
() => ''
|
||||
);
|
||||
|
||||
const spy = spyOn(
|
||||
const spy = vi.spyOn(
|
||||
(authStateService as any).authenticatedInternal$,
|
||||
'next'
|
||||
);
|
||||
|
||||
authStateService.setAuthenticatedAndFireEvent(allConfigs);
|
||||
|
||||
expect(spy).toHaveBeenCalledOnceWith({
|
||||
expect(spy).toHaveBeenCalledExactlyOnceWith({
|
||||
isAuthenticated: false,
|
||||
allConfigsAuthenticated: [
|
||||
{ configId: 'configId1', isAuthenticated: true },
|
||||
@@ -117,17 +123,20 @@ describe('Auth State Service', () => {
|
||||
|
||||
describe('setUnauthorizedAndFireEvent', () => {
|
||||
it('persist AuthState In Storage', () => {
|
||||
const spy = spyOn(storagePersistenceService, 'resetAuthStateInStorage');
|
||||
const spy = vi.spyOn(
|
||||
storagePersistenceService,
|
||||
'resetAuthStateInStorage'
|
||||
);
|
||||
|
||||
authStateService.setUnauthenticatedAndFireEvent(
|
||||
{ configId: 'configId1' },
|
||||
[{ configId: 'configId1' }]
|
||||
);
|
||||
expect(spy).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||
expect(spy).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' });
|
||||
});
|
||||
|
||||
it('throws correct event with single config', () => {
|
||||
const spy = spyOn(
|
||||
const spy = vi.spyOn(
|
||||
(authStateService as any).authenticatedInternal$,
|
||||
'next'
|
||||
);
|
||||
@@ -137,7 +146,7 @@ describe('Auth State Service', () => {
|
||||
[{ configId: 'configId1' }]
|
||||
);
|
||||
|
||||
expect(spy).toHaveBeenCalledOnceWith({
|
||||
expect(spy).toHaveBeenCalledExactlyOnceWith({
|
||||
isAuthenticated: false,
|
||||
allConfigsAuthenticated: [
|
||||
{ configId: 'configId1', isAuthenticated: false },
|
||||
@@ -146,7 +155,7 @@ describe('Auth State Service', () => {
|
||||
});
|
||||
|
||||
it('throws correct event with multiple configs', () => {
|
||||
const spy = spyOn(
|
||||
const spy = vi.spyOn(
|
||||
(authStateService as any).authenticatedInternal$,
|
||||
'next'
|
||||
);
|
||||
@@ -156,7 +165,7 @@ describe('Auth State Service', () => {
|
||||
[{ configId: 'configId1' }, { configId: 'configId2' }]
|
||||
);
|
||||
|
||||
expect(spy).toHaveBeenCalledOnceWith({
|
||||
expect(spy).toHaveBeenCalledExactlyOnceWith({
|
||||
isAuthenticated: false,
|
||||
allConfigsAuthenticated: [
|
||||
{ configId: 'configId1', isAuthenticated: false },
|
||||
@@ -166,19 +175,27 @@ describe('Auth State Service', () => {
|
||||
});
|
||||
|
||||
it('throws correct event with multiple configs, one is authenticated', () => {
|
||||
spyOn(storagePersistenceService, 'getAccessToken')
|
||||
.withArgs({ configId: 'configId1' })
|
||||
.and.returnValue('someAccessToken')
|
||||
.withArgs({ configId: 'configId2' })
|
||||
.and.returnValue('');
|
||||
mockImplementationWhenArgsEqual(
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'getAccessToken'),
|
||||
[{ configId: 'configId1' }],
|
||||
() => 'someAccessToken'
|
||||
),
|
||||
[{ configId: 'configId2' }],
|
||||
() => ''
|
||||
);
|
||||
|
||||
spyOn(storagePersistenceService, 'getIdToken')
|
||||
.withArgs({ configId: 'configId1' })
|
||||
.and.returnValue('someIdToken')
|
||||
.withArgs({ configId: 'configId2' })
|
||||
.and.returnValue('');
|
||||
mockImplementationWhenArgsEqual(
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'getIdToken'),
|
||||
[{ configId: 'configId1' }],
|
||||
() => 'someIdToken'
|
||||
),
|
||||
[{ configId: 'configId2' }],
|
||||
() => ''
|
||||
);
|
||||
|
||||
const spy = spyOn(
|
||||
const spy = vi.spyOn(
|
||||
(authStateService as any).authenticatedInternal$,
|
||||
'next'
|
||||
);
|
||||
@@ -188,7 +205,7 @@ describe('Auth State Service', () => {
|
||||
[{ configId: 'configId1' }, { configId: 'configId2' }]
|
||||
);
|
||||
|
||||
expect(spy).toHaveBeenCalledOnceWith({
|
||||
expect(spy).toHaveBeenCalledExactlyOnceWith({
|
||||
isAuthenticated: false,
|
||||
allConfigsAuthenticated: [
|
||||
{ configId: 'configId1', isAuthenticated: true },
|
||||
@@ -200,24 +217,27 @@ describe('Auth State Service', () => {
|
||||
|
||||
describe('updateAndPublishAuthState', () => {
|
||||
it('calls eventsService', () => {
|
||||
spyOn(eventsService, 'fireEvent');
|
||||
vi.spyOn(eventsService, 'fireEvent');
|
||||
|
||||
authStateService.updateAndPublishAuthState({
|
||||
const arg = {
|
||||
isAuthenticated: false,
|
||||
isRenewProcess: false,
|
||||
validationResult: {} as ValidationResult,
|
||||
});
|
||||
};
|
||||
|
||||
expect(eventsService.fireEvent).toHaveBeenCalledOnceWith(
|
||||
authStateService.updateAndPublishAuthState(arg);
|
||||
|
||||
expect(eventsService.fireEvent).toHaveBeenCalledOnce();
|
||||
expect(eventsService.fireEvent).toHaveBeenCalledExactlyOnceWith(
|
||||
EventTypes.NewAuthenticationResult,
|
||||
jasmine.any(Object)
|
||||
arg
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setAuthorizationData', () => {
|
||||
it('stores accessToken', () => {
|
||||
const spy = spyOn(storagePersistenceService, 'write');
|
||||
const spy = vi.spyOn(storagePersistenceService, 'write');
|
||||
const authResult = {
|
||||
id_token: 'idtoken',
|
||||
access_token: 'accesstoken',
|
||||
@@ -237,18 +257,19 @@ describe('Auth State Service', () => {
|
||||
[{ configId: 'configId1' }]
|
||||
);
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
expect(spy.calls.allArgs()).toEqual([
|
||||
expect(spy.mock.calls).toEqual([
|
||||
['authzData', 'accesstoken', { configId: 'configId1' }],
|
||||
[
|
||||
'access_token_expires_at',
|
||||
jasmine.any(Number),
|
||||
expect.any(Number),
|
||||
{ configId: 'configId1' },
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not crash and store accessToken when authResult is null', () => {
|
||||
const spy = spyOn(storagePersistenceService, 'write');
|
||||
const spy = vi.spyOn(storagePersistenceService, 'write');
|
||||
// biome-ignore lint/suspicious/noEvolvingTypes: <explanation>
|
||||
const authResult = null;
|
||||
|
||||
authStateService.setAuthorizationData(
|
||||
@@ -262,7 +283,7 @@ describe('Auth State Service', () => {
|
||||
});
|
||||
|
||||
it('calls setAuthenticatedAndFireEvent() method', () => {
|
||||
const spy = spyOn(authStateService, 'setAuthenticatedAndFireEvent');
|
||||
const spy = vi.spyOn(authStateService, 'setAuthenticatedAndFireEvent');
|
||||
const authResult = {
|
||||
id_token: 'idtoken',
|
||||
access_token: 'accesstoken',
|
||||
@@ -288,28 +309,29 @@ describe('Auth State Service', () => {
|
||||
|
||||
describe('getAccessToken', () => {
|
||||
it('isAuthorized is false returns null', () => {
|
||||
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue('');
|
||||
spyOn(storagePersistenceService, 'getIdToken').and.returnValue('');
|
||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue('');
|
||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue('');
|
||||
const result = authStateService.getAccessToken({ configId: 'configId1' });
|
||||
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('returns false if storagePersistenceService returns something falsy but authorized', () => {
|
||||
spyOn(authStateService, 'isAuthenticated').and.returnValue(true);
|
||||
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue('');
|
||||
vi.spyOn(authStateService, 'isAuthenticated').mockReturnValue(true);
|
||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue('');
|
||||
const result = authStateService.getAccessToken({ configId: 'configId1' });
|
||||
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('isAuthorized is true returns decodeURIComponent(token)', () => {
|
||||
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(
|
||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
|
||||
'HenloLegger'
|
||||
);
|
||||
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(
|
||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
|
||||
'HenloFuriend'
|
||||
);
|
||||
|
||||
const result = authStateService.getAccessToken({ configId: 'configId1' });
|
||||
|
||||
expect(result).toBe(decodeURIComponent('HenloLegger'));
|
||||
@@ -318,12 +340,14 @@ describe('Auth State Service', () => {
|
||||
|
||||
describe('getAuthenticationResult', () => {
|
||||
it('isAuthorized is false returns null', () => {
|
||||
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue('');
|
||||
spyOn(storagePersistenceService, 'getIdToken').and.returnValue('');
|
||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue('');
|
||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue('');
|
||||
|
||||
spyOn(storagePersistenceService, 'getAuthenticationResult')
|
||||
.withArgs({ configId: 'configId1' })
|
||||
.and.returnValue({});
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'getAuthenticationResult'),
|
||||
[{ configId: 'configId1' }],
|
||||
() => ({})
|
||||
);
|
||||
|
||||
const result = authStateService.getAuthenticationResult({
|
||||
configId: 'configId1',
|
||||
@@ -333,10 +357,13 @@ describe('Auth State Service', () => {
|
||||
});
|
||||
|
||||
it('returns false if storagePersistenceService returns something falsy but authorized', () => {
|
||||
spyOn(authStateService, 'isAuthenticated').and.returnValue(true);
|
||||
spyOn(storagePersistenceService, 'getAuthenticationResult')
|
||||
.withArgs({ configId: 'configId1' })
|
||||
.and.returnValue({});
|
||||
vi.spyOn(authStateService, 'isAuthenticated').mockReturnValue(true);
|
||||
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'getAuthenticationResult'),
|
||||
[{ configId: 'configId1' }],
|
||||
() => ({})
|
||||
);
|
||||
|
||||
const result = authStateService.getAuthenticationResult({
|
||||
configId: 'configId1',
|
||||
@@ -346,15 +373,18 @@ describe('Auth State Service', () => {
|
||||
});
|
||||
|
||||
it('isAuthorized is true returns object', () => {
|
||||
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(
|
||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
|
||||
'HenloLegger'
|
||||
);
|
||||
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(
|
||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
|
||||
'HenloFuriend'
|
||||
);
|
||||
spyOn(storagePersistenceService, 'getAuthenticationResult')
|
||||
.withArgs({ configId: 'configId1' })
|
||||
.and.returnValue({ scope: 'HenloFuriend' });
|
||||
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'getAuthenticationResult'),
|
||||
[{ configId: 'configId1' }],
|
||||
() => ({ scope: 'HenloFuriend' })
|
||||
);
|
||||
|
||||
const result = authStateService.getAuthenticationResult({
|
||||
configId: 'configId1',
|
||||
@@ -366,18 +396,18 @@ describe('Auth State Service', () => {
|
||||
|
||||
describe('getIdToken', () => {
|
||||
it('isAuthorized is false returns null', () => {
|
||||
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue('');
|
||||
spyOn(storagePersistenceService, 'getIdToken').and.returnValue('');
|
||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue('');
|
||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue('');
|
||||
const result = authStateService.getIdToken({ configId: 'configId1' });
|
||||
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('isAuthorized is true returns decodeURIComponent(token)', () => {
|
||||
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(
|
||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
|
||||
'HenloLegger'
|
||||
);
|
||||
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(
|
||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
|
||||
'HenloFuriend'
|
||||
);
|
||||
const result = authStateService.getIdToken({ configId: 'configId1' });
|
||||
@@ -388,8 +418,8 @@ describe('Auth State Service', () => {
|
||||
|
||||
describe('getRefreshToken', () => {
|
||||
it('isAuthorized is false returns null', () => {
|
||||
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue('');
|
||||
spyOn(storagePersistenceService, 'getIdToken').and.returnValue('');
|
||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue('');
|
||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue('');
|
||||
const result = authStateService.getRefreshToken({
|
||||
configId: 'configId1',
|
||||
});
|
||||
@@ -398,13 +428,13 @@ describe('Auth State Service', () => {
|
||||
});
|
||||
|
||||
it('isAuthorized is true returns decodeURIComponent(token)', () => {
|
||||
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(
|
||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
|
||||
'HenloLegger'
|
||||
);
|
||||
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(
|
||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
|
||||
'HenloFuriend'
|
||||
);
|
||||
spyOn(storagePersistenceService, 'getRefreshToken').and.returnValue(
|
||||
vi.spyOn(storagePersistenceService, 'getRefreshToken').mockReturnValue(
|
||||
'HenloRefreshLegger'
|
||||
);
|
||||
const result = authStateService.getRefreshToken({
|
||||
@@ -417,105 +447,105 @@ describe('Auth State Service', () => {
|
||||
|
||||
describe('areAuthStorageTokensValid', () => {
|
||||
it('isAuthorized is false returns false', () => {
|
||||
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue('');
|
||||
spyOn(storagePersistenceService, 'getIdToken').and.returnValue('');
|
||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue('');
|
||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue('');
|
||||
const result = authStateService.areAuthStorageTokensValid({
|
||||
configId: 'configId1',
|
||||
});
|
||||
|
||||
expect(result).toBeFalse();
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('isAuthorized is true and id_token is expired returns true', () => {
|
||||
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(
|
||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
|
||||
'HenloLegger'
|
||||
);
|
||||
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(
|
||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
|
||||
'HenloFuriend'
|
||||
);
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
authStateService as any,
|
||||
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
||||
).and.returnValue(true);
|
||||
spyOn(
|
||||
).mockReturnValue(true);
|
||||
vi.spyOn(
|
||||
authStateService as any,
|
||||
'hasAccessTokenExpiredIfExpiryExists'
|
||||
).and.returnValue(false);
|
||||
).mockReturnValue(false);
|
||||
const result = authStateService.areAuthStorageTokensValid({
|
||||
configId: 'configId1',
|
||||
});
|
||||
|
||||
expect(result).toBeFalse();
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('isAuthorized is true and access_token is expired returns true', () => {
|
||||
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(
|
||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
|
||||
'HenloLegger'
|
||||
);
|
||||
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(
|
||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
|
||||
'HenloFuriend'
|
||||
);
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
authStateService as any,
|
||||
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
||||
).and.returnValue(false);
|
||||
spyOn(
|
||||
).mockReturnValue(false);
|
||||
vi.spyOn(
|
||||
authStateService as any,
|
||||
'hasAccessTokenExpiredIfExpiryExists'
|
||||
).and.returnValue(true);
|
||||
).mockReturnValue(true);
|
||||
const result = authStateService.areAuthStorageTokensValid({
|
||||
configId: 'configId1',
|
||||
});
|
||||
|
||||
expect(result).toBeFalse();
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('isAuthorized is true and id_token is not expired returns true', () => {
|
||||
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(
|
||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
|
||||
'HenloLegger'
|
||||
);
|
||||
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(
|
||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
|
||||
'HenloFuriend'
|
||||
);
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
authStateService as any,
|
||||
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
||||
).and.returnValue(false);
|
||||
spyOn(
|
||||
).mockReturnValue(false);
|
||||
vi.spyOn(
|
||||
authStateService as any,
|
||||
'hasAccessTokenExpiredIfExpiryExists'
|
||||
).and.returnValue(false);
|
||||
).mockReturnValue(false);
|
||||
const result = authStateService.areAuthStorageTokensValid({
|
||||
configId: 'configId1',
|
||||
});
|
||||
|
||||
expect(result).toBeTrue();
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
|
||||
it('authState is AuthorizedState.Authorized and id_token is not expired fires event', () => {
|
||||
spyOn(storagePersistenceService, 'getAccessToken').and.returnValue(
|
||||
vi.spyOn(storagePersistenceService, 'getAccessToken').mockReturnValue(
|
||||
'HenloLegger'
|
||||
);
|
||||
spyOn(storagePersistenceService, 'getIdToken').and.returnValue(
|
||||
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
|
||||
'HenloFuriend'
|
||||
);
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
authStateService as any,
|
||||
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
||||
).and.returnValue(false);
|
||||
spyOn(
|
||||
).mockReturnValue(false);
|
||||
vi.spyOn(
|
||||
authStateService as any,
|
||||
'hasAccessTokenExpiredIfExpiryExists'
|
||||
).and.returnValue(false);
|
||||
).mockReturnValue(false);
|
||||
const result = authStateService.areAuthStorageTokensValid({
|
||||
configId: 'configId1',
|
||||
});
|
||||
|
||||
expect(result).toBeTrue();
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -527,56 +557,65 @@ describe('Auth State Service', () => {
|
||||
triggerRefreshWhenIdTokenExpired: true,
|
||||
};
|
||||
|
||||
spyOn(storagePersistenceService, 'getIdToken')
|
||||
.withArgs(config)
|
||||
.and.returnValue('idToken');
|
||||
const spy = spyOn(
|
||||
tokenValidationService,
|
||||
'hasIdTokenExpired'
|
||||
).and.callFake((_a, _b) => true);
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'getIdToken'),
|
||||
[config],
|
||||
() => 'idToken'
|
||||
);
|
||||
const spy = vi
|
||||
.spyOn(tokenValidationService, 'hasIdTokenExpired')
|
||||
.mockImplementation((_a, _b) => true);
|
||||
|
||||
authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config);
|
||||
|
||||
expect(spy).toHaveBeenCalledOnceWith('idToken', config, 30);
|
||||
expect(spy).toHaveBeenCalledExactlyOnceWith('idToken', config, 30);
|
||||
});
|
||||
|
||||
it('fires event if idToken is expired', () => {
|
||||
spyOn(tokenValidationService, 'hasIdTokenExpired').and.callFake(
|
||||
vi.spyOn(tokenValidationService, 'hasIdTokenExpired').mockImplementation(
|
||||
(_a, _b) => true
|
||||
);
|
||||
|
||||
const spy = spyOn(eventsService, 'fireEvent');
|
||||
const spy = vi.spyOn(eventsService, 'fireEvent');
|
||||
const config = {
|
||||
configId: 'configId1',
|
||||
renewTimeBeforeTokenExpiresInSeconds: 30,
|
||||
triggerRefreshWhenIdTokenExpired: true,
|
||||
};
|
||||
|
||||
spyOn(storagePersistenceService, 'read')
|
||||
.withArgs('authnResult', config)
|
||||
.and.returnValue('idToken');
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'read'),
|
||||
['authnResult', config],
|
||||
() => 'idToken'
|
||||
);
|
||||
|
||||
const result =
|
||||
authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(spy).toHaveBeenCalledOnceWith(EventTypes.IdTokenExpired, true);
|
||||
expect(spy).toHaveBeenCalledExactlyOnceWith(
|
||||
EventTypes.IdTokenExpired,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('does NOT fire event if idToken is NOT expired', () => {
|
||||
spyOn(tokenValidationService, 'hasIdTokenExpired').and.callFake(
|
||||
vi.spyOn(tokenValidationService, 'hasIdTokenExpired').mockImplementation(
|
||||
(_a, _b) => false
|
||||
);
|
||||
|
||||
const spy = spyOn(eventsService, 'fireEvent');
|
||||
const spy = vi.spyOn(eventsService, 'fireEvent');
|
||||
const config = {
|
||||
configId: 'configId1',
|
||||
renewTimeBeforeTokenExpiresInSeconds: 30,
|
||||
};
|
||||
|
||||
spyOn(storagePersistenceService, 'read')
|
||||
.withArgs('authnResult', config)
|
||||
.and.returnValue('idToken');
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'read'),
|
||||
['authnResult', config],
|
||||
() => 'idToken'
|
||||
);
|
||||
|
||||
const result =
|
||||
authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config);
|
||||
|
||||
@@ -595,41 +634,45 @@ describe('Auth State Service', () => {
|
||||
renewTimeBeforeTokenExpiresInSeconds: 5,
|
||||
};
|
||||
|
||||
spyOn(storagePersistenceService, 'read')
|
||||
.withArgs('access_token_expires_at', config)
|
||||
.and.returnValue(date);
|
||||
const spy = spyOn(
|
||||
tokenValidationService,
|
||||
'validateAccessTokenNotExpired'
|
||||
).and.returnValue(validateAccessTokenNotExpiredResult);
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'read'),
|
||||
['access_token_expires_at', config],
|
||||
() => date
|
||||
);
|
||||
const spy = vi
|
||||
.spyOn(tokenValidationService, 'validateAccessTokenNotExpired')
|
||||
.mockReturnValue(validateAccessTokenNotExpiredResult);
|
||||
const result =
|
||||
authStateService.hasAccessTokenExpiredIfExpiryExists(config);
|
||||
|
||||
expect(spy).toHaveBeenCalledOnceWith(date, config, 5);
|
||||
expect(spy).toHaveBeenCalledExactlyOnceWith(date, config, 5);
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
it('throws event when token is expired', () => {
|
||||
const validateAccessTokenNotExpiredResult = false;
|
||||
const expectedResult = !validateAccessTokenNotExpiredResult;
|
||||
// spyOn(configurationProvider, 'getOpenIDConfiguration').and.returnValue({ renewTimeBeforeTokenExpiresInSeconds: 5 });
|
||||
// vi.spyOn(configurationProvider, 'getOpenIDConfiguration').mockReturnValue({ renewTimeBeforeTokenExpiresInSeconds: 5 });
|
||||
const date = new Date(new Date().toUTCString());
|
||||
const config = {
|
||||
configId: 'configId1',
|
||||
renewTimeBeforeTokenExpiresInSeconds: 5,
|
||||
};
|
||||
|
||||
spyOn(eventsService, 'fireEvent');
|
||||
vi.spyOn(eventsService, 'fireEvent');
|
||||
|
||||
spyOn(storagePersistenceService, 'read')
|
||||
.withArgs('access_token_expires_at', config)
|
||||
.and.returnValue(date);
|
||||
spyOn(
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'read'),
|
||||
['access_token_expires_at', config],
|
||||
() => date
|
||||
);
|
||||
|
||||
vi.spyOn(
|
||||
tokenValidationService,
|
||||
'validateAccessTokenNotExpired'
|
||||
).and.returnValue(validateAccessTokenNotExpiredResult);
|
||||
).mockReturnValue(validateAccessTokenNotExpiredResult);
|
||||
authStateService.hasAccessTokenExpiredIfExpiryExists(config);
|
||||
expect(eventsService.fireEvent).toHaveBeenCalledOnceWith(
|
||||
expect(eventsService.fireEvent).toHaveBeenCalledExactlyOnceWith(
|
||||
EventTypes.TokenExpired,
|
||||
expectedResult
|
||||
);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Injectable, inject } from 'injection-js';
|
||||
import { BehaviorSubject, Observable, throwError } from 'rxjs';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import { BehaviorSubject, type Observable, throwError } from 'rxjs';
|
||||
import { distinctUntilChanged } from 'rxjs/operators';
|
||||
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import { AuthResult } from '../flows/callback-context';
|
||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import type { AuthResult } from '../flows/callback-context';
|
||||
import { LoggerService } from '../logging/logger.service';
|
||||
import { EventTypes } from '../public-events/event-types';
|
||||
import { PublicEventsService } from '../public-events/public-events.service';
|
||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||
import { TokenValidationService } from '../validation/token-validation.service';
|
||||
import { AuthenticatedResult } from './auth-result';
|
||||
import { AuthStateResult } from './auth-state';
|
||||
import type { AuthenticatedResult } from './auth-result';
|
||||
import type { AuthStateResult } from './auth-state';
|
||||
|
||||
const DEFAULT_AUTHRESULT = {
|
||||
isAuthenticated: false,
|
||||
@@ -257,9 +257,8 @@ export class AuthStateService {
|
||||
private decodeURIComponentSafely(token: string): string {
|
||||
if (token) {
|
||||
return decodeURIComponent(token);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private persistAccessTokenExpirationTime(
|
||||
@@ -293,7 +292,7 @@ export class AuthStateService {
|
||||
};
|
||||
}
|
||||
|
||||
return this.checkAllConfigsIfTheyAreAuthenticated(allConfigs);
|
||||
return this.checkallConfigsIfTheyAreAuthenticated(allConfigs);
|
||||
}
|
||||
|
||||
private composeUnAuthenticatedResult(
|
||||
@@ -310,10 +309,10 @@ export class AuthStateService {
|
||||
};
|
||||
}
|
||||
|
||||
return this.checkAllConfigsIfTheyAreAuthenticated(allConfigs);
|
||||
return this.checkallConfigsIfTheyAreAuthenticated(allConfigs);
|
||||
}
|
||||
|
||||
private checkAllConfigsIfTheyAreAuthenticated(
|
||||
private checkallConfigsIfTheyAreAuthenticated(
|
||||
allConfigs: OpenIdConfiguration[]
|
||||
): AuthenticatedResult {
|
||||
const allConfigsAuthenticated = allConfigs.map((config) => ({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ValidationResult } from '../validation/validation-result';
|
||||
import type { ValidationResult } from '../validation/validation-result';
|
||||
|
||||
export interface AuthStateResult {
|
||||
isAuthenticated: boolean;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,15 @@
|
||||
import { inject, Injectable } from 'injection-js';
|
||||
import { forkJoin, Observable, of, throwError } from 'rxjs';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import { type Observable, forkJoin, of, throwError } from 'rxjs';
|
||||
import { catchError, map, switchMap, tap } from 'rxjs/operators';
|
||||
import { AutoLoginService } from '../auto-login/auto-login.service';
|
||||
import { CallbackService } from '../callback/callback.service';
|
||||
import { PeriodicallyTokenCheckService } from '../callback/periodically-token-check.service';
|
||||
import { RefreshSessionService } from '../callback/refresh-session.service';
|
||||
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import { CheckSessionService } from '../iframe/check-session.service';
|
||||
import { SilentRenewService } from '../iframe/silent-renew.service';
|
||||
import { LoggerService } from '../logging/logger.service';
|
||||
import { LoginResponse } from '../login/login-response';
|
||||
import type { LoginResponse } from '../login/login-response';
|
||||
import { PopUpService } from '../login/popup/popup.service';
|
||||
import { EventTypes } from '../public-events/event-types';
|
||||
import { PublicEventsService } from '../public-events/public-events.service';
|
||||
@@ -57,7 +57,7 @@ export class CheckAuthService {
|
||||
const stateParamFromUrl =
|
||||
this.currentUrlService.getStateParamFromCurrentUrl(url);
|
||||
|
||||
return Boolean(stateParamFromUrl)
|
||||
return stateParamFromUrl
|
||||
? this.getConfigurationWithUrlState([configuration], stateParamFromUrl)
|
||||
: configuration;
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { mockProvider } from '../test/auto-mock';
|
||||
import { PASSED_CONFIG } from './auth-config';
|
||||
import { AuthModule } from './auth.module';
|
||||
import { ConfigurationService } from './config/config.service';
|
||||
import {
|
||||
StsConfigHttpLoader,
|
||||
StsConfigLoader,
|
||||
StsConfigStaticLoader,
|
||||
} from './config/loader/config-loader';
|
||||
|
||||
describe('AuthModule', () => {
|
||||
describe('APP_CONFIG', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AuthModule.forRoot({ config: { authority: 'something' } })],
|
||||
providers: [mockProvider(ConfigurationService)],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
it('should create', () => {
|
||||
expect(AuthModule).toBeDefined();
|
||||
expect(AuthModule.forRoot({})).toBeDefined();
|
||||
});
|
||||
|
||||
it('should provide config', () => {
|
||||
const config = TestBed.inject(PASSED_CONFIG);
|
||||
|
||||
expect(config).toEqual({ config: { authority: 'something' } });
|
||||
});
|
||||
|
||||
it('should create StsConfigStaticLoader if config is passed', () => {
|
||||
const configLoader = TestBed.inject(StsConfigLoader);
|
||||
|
||||
expect(configLoader instanceof StsConfigStaticLoader).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('StsConfigHttpLoader', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
AuthModule.forRoot({
|
||||
loader: {
|
||||
provide: StsConfigLoader,
|
||||
useFactory: () => new StsConfigHttpLoader(of({})),
|
||||
},
|
||||
}),
|
||||
],
|
||||
providers: [mockProvider(ConfigurationService)],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
it('should create StsConfigStaticLoader if config is passed', () => {
|
||||
const configLoader = TestBed.inject(StsConfigLoader);
|
||||
|
||||
expect(configLoader instanceof StsConfigHttpLoader).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {
|
||||
provideHttpClient,
|
||||
withInterceptorsFromDi,
|
||||
} from '@ngify/http';
|
||||
import { ModuleWithProviders, NgModule } from 'injection-js';
|
||||
import { PassedInitialConfig } from './auth-config';
|
||||
import { _provideAuth } from './provide-auth';
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
exports: [],
|
||||
imports: [CommonModule],
|
||||
providers: [provideHttpClient(withInterceptorsFromDi())],
|
||||
})
|
||||
export class AuthModule {
|
||||
static forRoot(
|
||||
passedConfig: PassedInitialConfig
|
||||
): ModuleWithProviders<AuthModule> {
|
||||
return {
|
||||
ngModule: AuthModule,
|
||||
providers: [..._provideAuth(passedConfig)],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { type MockRouter, TestBed, mockRouterProvider } from '@/testing';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { mockProvider } from '../../test/auto-mock';
|
||||
AbstractRouter,
|
||||
type ActivatedRouteSnapshot,
|
||||
type RouterStateSnapshot,
|
||||
} from 'oidc-client-rx';
|
||||
import { firstValueFrom, of } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import { AuthStateService } from '../auth-state/auth-state.service';
|
||||
import { CheckAuthService } from '../auth-state/check-auth.service';
|
||||
import { ConfigurationService } from '../config/config.service';
|
||||
import { LoginService } from '../login/login.service';
|
||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||
import { mockProvider } from '../testing/mock';
|
||||
import {
|
||||
AutoLoginPartialRoutesGuard,
|
||||
autoLoginPartialRoutesGuard,
|
||||
@@ -19,11 +19,13 @@ import {
|
||||
} from './auto-login-partial-routes.guard';
|
||||
import { AutoLoginService } from './auto-login.service';
|
||||
|
||||
describe(`AutoLoginPartialRoutesGuard`, () => {
|
||||
describe('AutoLoginPartialRoutesGuard', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [RouterTestingModule],
|
||||
imports: [],
|
||||
providers: [
|
||||
AutoLoginPartialRoutesGuard,
|
||||
mockRouterProvider(),
|
||||
AutoLoginService,
|
||||
mockProvider(AuthStateService),
|
||||
mockProvider(LoginService),
|
||||
@@ -41,7 +43,7 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
|
||||
let storagePersistenceService: StoragePersistenceService;
|
||||
let configurationService: ConfigurationService;
|
||||
let autoLoginService: AutoLoginService;
|
||||
let router: Router;
|
||||
let router: MockRouter;
|
||||
|
||||
beforeEach(() => {
|
||||
authStateService = TestBed.inject(AuthStateService);
|
||||
@@ -49,15 +51,16 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
|
||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||
configurationService = TestBed.inject(ConfigurationService);
|
||||
|
||||
spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue(
|
||||
vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue(
|
||||
of({ configId: 'configId1' })
|
||||
);
|
||||
|
||||
guard = TestBed.inject(AutoLoginPartialRoutesGuard);
|
||||
autoLoginService = TestBed.inject(AutoLoginService);
|
||||
router = TestBed.inject(Router);
|
||||
router = TestBed.inject(AbstractRouter);
|
||||
});
|
||||
|
||||
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
|
||||
afterEach(() => {
|
||||
storagePersistenceService.clear({});
|
||||
});
|
||||
@@ -67,281 +70,263 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
|
||||
});
|
||||
|
||||
describe('canActivate', () => {
|
||||
it('should save current route and call `login` if not authenticated already', waitForAsync(() => {
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
it('should save current route and call `login` if not authenticated already', async () => {
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
false
|
||||
);
|
||||
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'checkSavedRedirectRouteAndNavigate'
|
||||
);
|
||||
const saveRedirectRouteSpy = spyOn(
|
||||
const saveRedirectRouteSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'saveRedirectRoute'
|
||||
);
|
||||
const loginSpy = spyOn(loginService, 'login');
|
||||
const loginSpy = vi.spyOn(loginService, 'login');
|
||||
|
||||
guard
|
||||
.canActivate(
|
||||
await firstValueFrom(
|
||||
guard.canActivate(
|
||||
{} as ActivatedRouteSnapshot,
|
||||
{ url: 'some-url1' } as RouterStateSnapshot
|
||||
)
|
||||
.subscribe(() => {
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||
);
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
{ configId: 'configId1' },
|
||||
'some-url1'
|
||||
);
|
||||
expect(loginSpy).toHaveBeenCalledOnceWith({
|
||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
|
||||
configId: 'configId1',
|
||||
});
|
||||
expect(
|
||||
checkSavedRedirectRouteAndNavigateSpy
|
||||
).not.toHaveBeenCalled();
|
||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should save current route and call `login` if not authenticated already and add custom params', waitForAsync(() => {
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
it('should save current route and call `login` if not authenticated already and add custom params', async () => {
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
false
|
||||
);
|
||||
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'checkSavedRedirectRouteAndNavigate'
|
||||
);
|
||||
const saveRedirectRouteSpy = spyOn(
|
||||
const saveRedirectRouteSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'saveRedirectRoute'
|
||||
);
|
||||
const loginSpy = spyOn(loginService, 'login');
|
||||
const loginSpy = vi.spyOn(loginService, 'login');
|
||||
|
||||
guard
|
||||
.canActivate(
|
||||
await firstValueFrom(
|
||||
guard.canActivate(
|
||||
{ data: { custom: 'param' } } as unknown as ActivatedRouteSnapshot,
|
||||
{ url: 'some-url1' } as RouterStateSnapshot
|
||||
)
|
||||
.subscribe(() => {
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||
);
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
{ configId: 'configId1' },
|
||||
'some-url1'
|
||||
);
|
||||
expect(loginSpy).toHaveBeenCalledOnceWith(
|
||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
{ configId: 'configId1' },
|
||||
{ customParams: { custom: 'param' } }
|
||||
);
|
||||
expect(
|
||||
checkSavedRedirectRouteAndNavigateSpy
|
||||
).not.toHaveBeenCalled();
|
||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', waitForAsync(() => {
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', async () => {
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
true
|
||||
);
|
||||
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'checkSavedRedirectRouteAndNavigate'
|
||||
);
|
||||
const saveRedirectRouteSpy = spyOn(
|
||||
const saveRedirectRouteSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'saveRedirectRoute'
|
||||
);
|
||||
const loginSpy = spyOn(loginService, 'login');
|
||||
const loginSpy = vi.spyOn(loginService, 'login');
|
||||
|
||||
guard
|
||||
.canActivate(
|
||||
await firstValueFrom(
|
||||
guard.canActivate(
|
||||
{} as ActivatedRouteSnapshot,
|
||||
{ url: 'some-url1' } as RouterStateSnapshot
|
||||
)
|
||||
.subscribe(() => {
|
||||
);
|
||||
expect(saveRedirectRouteSpy).not.toHaveBeenCalled();
|
||||
expect(loginSpy).not.toHaveBeenCalled();
|
||||
expect(
|
||||
checkSavedRedirectRouteAndNavigateSpy
|
||||
).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||
).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' });
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('canActivateChild', () => {
|
||||
it('should save current route and call `login` if not authenticated already', waitForAsync(() => {
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
it('should save current route and call `login` if not authenticated already', async () => {
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
false
|
||||
);
|
||||
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'checkSavedRedirectRouteAndNavigate'
|
||||
);
|
||||
const saveRedirectRouteSpy = spyOn(
|
||||
const saveRedirectRouteSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'saveRedirectRoute'
|
||||
);
|
||||
const loginSpy = spyOn(loginService, 'login');
|
||||
const loginSpy = vi.spyOn(loginService, 'login');
|
||||
|
||||
guard
|
||||
.canActivateChild(
|
||||
await firstValueFrom(
|
||||
guard.canActivateChild(
|
||||
{} as ActivatedRouteSnapshot,
|
||||
{ url: 'some-url1' } as RouterStateSnapshot
|
||||
)
|
||||
.subscribe(() => {
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||
);
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
{ configId: 'configId1' },
|
||||
'some-url1'
|
||||
);
|
||||
expect(loginSpy).toHaveBeenCalledOnceWith({
|
||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
|
||||
configId: 'configId1',
|
||||
});
|
||||
expect(
|
||||
checkSavedRedirectRouteAndNavigateSpy
|
||||
).not.toHaveBeenCalled();
|
||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should save current route and call `login` if not authenticated already with custom params', waitForAsync(() => {
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
it('should save current route and call `login` if not authenticated already with custom params', async () => {
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
false
|
||||
);
|
||||
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'checkSavedRedirectRouteAndNavigate'
|
||||
);
|
||||
const saveRedirectRouteSpy = spyOn(
|
||||
const saveRedirectRouteSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'saveRedirectRoute'
|
||||
);
|
||||
const loginSpy = spyOn(loginService, 'login');
|
||||
const loginSpy = vi.spyOn(loginService, 'login');
|
||||
|
||||
guard
|
||||
.canActivateChild(
|
||||
await firstValueFrom(
|
||||
guard.canActivateChild(
|
||||
{ data: { custom: 'param' } } as unknown as ActivatedRouteSnapshot,
|
||||
{ url: 'some-url1' } as RouterStateSnapshot
|
||||
)
|
||||
.subscribe(() => {
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||
);
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
{ configId: 'configId1' },
|
||||
'some-url1'
|
||||
);
|
||||
expect(loginSpy).toHaveBeenCalledOnceWith(
|
||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
{ configId: 'configId1' },
|
||||
{ customParams: { custom: 'param' } }
|
||||
);
|
||||
expect(
|
||||
checkSavedRedirectRouteAndNavigateSpy
|
||||
).not.toHaveBeenCalled();
|
||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', waitForAsync(() => {
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', async () => {
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
true
|
||||
);
|
||||
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'checkSavedRedirectRouteAndNavigate'
|
||||
);
|
||||
const saveRedirectRouteSpy = spyOn(
|
||||
const saveRedirectRouteSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'saveRedirectRoute'
|
||||
);
|
||||
const loginSpy = spyOn(loginService, 'login');
|
||||
const loginSpy = vi.spyOn(loginService, 'login');
|
||||
|
||||
guard
|
||||
.canActivateChild(
|
||||
await firstValueFrom(
|
||||
guard.canActivateChild(
|
||||
{} as ActivatedRouteSnapshot,
|
||||
{ url: 'some-url1' } as RouterStateSnapshot
|
||||
)
|
||||
.subscribe(() => {
|
||||
);
|
||||
expect(saveRedirectRouteSpy).not.toHaveBeenCalled();
|
||||
expect(loginSpy).not.toHaveBeenCalled();
|
||||
expect(
|
||||
checkSavedRedirectRouteAndNavigateSpy
|
||||
).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||
).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' });
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('canLoad', () => {
|
||||
it('should save current route (empty) and call `login` if not authenticated already', waitForAsync(() => {
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
it('should save current route (empty) and call `login` if not authenticated already', async () => {
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
false
|
||||
);
|
||||
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'checkSavedRedirectRouteAndNavigate'
|
||||
);
|
||||
const saveRedirectRouteSpy = spyOn(
|
||||
const saveRedirectRouteSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'saveRedirectRoute'
|
||||
);
|
||||
const loginSpy = spyOn(loginService, 'login');
|
||||
const loginSpy = vi.spyOn(loginService, 'login');
|
||||
|
||||
guard.canLoad().subscribe(() => {
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||
await firstValueFrom(guard.canLoad());
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
{ configId: 'configId1' },
|
||||
''
|
||||
);
|
||||
expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
|
||||
configId: 'configId1',
|
||||
});
|
||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should save current route (with router extractedUrl) and call `login` if not authenticated already', waitForAsync(() => {
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
it('should save current route (with router extractedUrl) and call `login` if not authenticated already', async () => {
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
false
|
||||
);
|
||||
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'checkSavedRedirectRouteAndNavigate'
|
||||
);
|
||||
const saveRedirectRouteSpy = spyOn(
|
||||
const saveRedirectRouteSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'saveRedirectRoute'
|
||||
);
|
||||
const loginSpy = spyOn(loginService, 'login');
|
||||
const loginSpy = vi.spyOn(loginService, 'login');
|
||||
|
||||
spyOn(router, 'getCurrentNavigation').and.returnValue({
|
||||
vi.spyOn(router, 'getCurrentNavigation').mockReturnValue({
|
||||
extractedUrl: router.parseUrl(
|
||||
'some-url12/with/some-param?queryParam=true'
|
||||
),
|
||||
extras: {},
|
||||
id: 1,
|
||||
initialUrl: router.parseUrl(''),
|
||||
previousNavigation: null,
|
||||
trigger: 'imperative',
|
||||
});
|
||||
|
||||
guard.canLoad().subscribe(() => {
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||
await firstValueFrom(guard.canLoad());
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
{ configId: 'configId1' },
|
||||
'some-url12/with/some-param?queryParam=true'
|
||||
);
|
||||
expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
|
||||
configId: 'configId1',
|
||||
});
|
||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', waitForAsync(() => {
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', async () => {
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
true
|
||||
);
|
||||
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'checkSavedRedirectRouteAndNavigate'
|
||||
);
|
||||
const saveRedirectRouteSpy = spyOn(
|
||||
const saveRedirectRouteSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'saveRedirectRoute'
|
||||
);
|
||||
const loginSpy = spyOn(loginService, 'login');
|
||||
const loginSpy = vi.spyOn(loginService, 'login');
|
||||
|
||||
guard.canLoad().subscribe(() => {
|
||||
await firstValueFrom(guard.canLoad());
|
||||
expect(saveRedirectRouteSpy).not.toHaveBeenCalled();
|
||||
expect(loginSpy).not.toHaveBeenCalled();
|
||||
expect(
|
||||
checkSavedRedirectRouteAndNavigateSpy
|
||||
).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||
).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' });
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -352,7 +337,7 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
|
||||
let storagePersistenceService: StoragePersistenceService;
|
||||
let configurationService: ConfigurationService;
|
||||
let autoLoginService: AutoLoginService;
|
||||
let router: Router;
|
||||
let router: MockRouter;
|
||||
|
||||
beforeEach(() => {
|
||||
authStateService = TestBed.inject(AuthStateService);
|
||||
@@ -360,98 +345,97 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
|
||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||
configurationService = TestBed.inject(ConfigurationService);
|
||||
|
||||
spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue(
|
||||
of({ configId: 'configId1' })
|
||||
);
|
||||
vi.spyOn(
|
||||
configurationService,
|
||||
'getOpenIDConfiguration'
|
||||
).mockReturnValue(of({ configId: 'configId1' }));
|
||||
|
||||
autoLoginService = TestBed.inject(AutoLoginService);
|
||||
router = TestBed.inject(Router);
|
||||
router = TestBed.inject(AbstractRouter);
|
||||
});
|
||||
|
||||
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
|
||||
afterEach(() => {
|
||||
storagePersistenceService.clear({});
|
||||
});
|
||||
|
||||
it('should save current route (empty) and call `login` if not authenticated already', waitForAsync(() => {
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
it('should save current route (empty) and call `login` if not authenticated already', async () => {
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
false
|
||||
);
|
||||
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'checkSavedRedirectRouteAndNavigate'
|
||||
);
|
||||
const saveRedirectRouteSpy = spyOn(
|
||||
const saveRedirectRouteSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'saveRedirectRoute'
|
||||
);
|
||||
const loginSpy = spyOn(loginService, 'login');
|
||||
const loginSpy = vi.spyOn(loginService, 'login');
|
||||
|
||||
const guard$ = TestBed.runInInjectionContext(
|
||||
autoLoginPartialRoutesGuard
|
||||
);
|
||||
|
||||
guard$.subscribe(() => {
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||
await firstValueFrom(guard$);
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
{ configId: 'configId1' },
|
||||
''
|
||||
);
|
||||
expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
|
||||
configId: 'configId1',
|
||||
});
|
||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should save current route (with router extractedUrl) and call `login` if not authenticated already', waitForAsync(() => {
|
||||
spyOn(router, 'getCurrentNavigation').and.returnValue({
|
||||
it('should save current route (with router extractedUrl) and call `login` if not authenticated already', async () => {
|
||||
vi.spyOn(router, 'getCurrentNavigation').mockReturnValue({
|
||||
extractedUrl: router.parseUrl(
|
||||
'some-url12/with/some-param?queryParam=true'
|
||||
),
|
||||
extras: {},
|
||||
id: 1,
|
||||
initialUrl: router.parseUrl(''),
|
||||
previousNavigation: null,
|
||||
trigger: 'imperative',
|
||||
});
|
||||
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
false
|
||||
);
|
||||
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'checkSavedRedirectRouteAndNavigate'
|
||||
);
|
||||
const saveRedirectRouteSpy = spyOn(
|
||||
const saveRedirectRouteSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'saveRedirectRoute'
|
||||
);
|
||||
const loginSpy = spyOn(loginService, 'login');
|
||||
const loginSpy = vi.spyOn(loginService, 'login');
|
||||
|
||||
const guard$ = TestBed.runInInjectionContext(
|
||||
autoLoginPartialRoutesGuard
|
||||
);
|
||||
|
||||
guard$.subscribe(() => {
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||
await firstValueFrom(guard$);
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
{ configId: 'configId1' },
|
||||
'some-url12/with/some-param?queryParam=true'
|
||||
);
|
||||
expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
|
||||
configId: 'configId1',
|
||||
});
|
||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should save current route and call `login` if not authenticated already and add custom params', waitForAsync(() => {
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
it('should save current route and call `login` if not authenticated already and add custom params', async () => {
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
false
|
||||
);
|
||||
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'checkSavedRedirectRouteAndNavigate'
|
||||
);
|
||||
const saveRedirectRouteSpy = spyOn(
|
||||
const saveRedirectRouteSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'saveRedirectRoute'
|
||||
);
|
||||
const loginSpy = spyOn(loginService, 'login');
|
||||
const loginSpy = vi.spyOn(loginService, 'login');
|
||||
|
||||
const guard$ = TestBed.runInInjectionContext(() =>
|
||||
autoLoginPartialRoutesGuard({
|
||||
@@ -459,45 +443,43 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
|
||||
} as unknown as ActivatedRouteSnapshot)
|
||||
);
|
||||
|
||||
guard$.subscribe(() => {
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||
await firstValueFrom(guard$);
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
{ configId: 'configId1' },
|
||||
''
|
||||
);
|
||||
expect(loginSpy).toHaveBeenCalledOnceWith(
|
||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
{ configId: 'configId1' },
|
||||
{ customParams: { custom: 'param' } }
|
||||
);
|
||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', waitForAsync(() => {
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
it('should call `checkSavedRedirectRouteAndNavigate` if authenticated already', async () => {
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
true
|
||||
);
|
||||
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'checkSavedRedirectRouteAndNavigate'
|
||||
);
|
||||
const saveRedirectRouteSpy = spyOn(
|
||||
const saveRedirectRouteSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'saveRedirectRoute'
|
||||
);
|
||||
const loginSpy = spyOn(loginService, 'login');
|
||||
const loginSpy = vi.spyOn(loginService, 'login');
|
||||
|
||||
const guard$ = TestBed.runInInjectionContext(
|
||||
autoLoginPartialRoutesGuard
|
||||
);
|
||||
|
||||
guard$.subscribe(() => {
|
||||
await firstValueFrom(guard$);
|
||||
expect(saveRedirectRouteSpy).not.toHaveBeenCalled();
|
||||
expect(loginSpy).not.toHaveBeenCalled();
|
||||
expect(
|
||||
checkSavedRedirectRouteAndNavigateSpy
|
||||
).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||
).toHaveBeenCalledExactlyOnceWith({ configId: 'configId1' });
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('autoLoginPartialRoutesGuardWithConfig', () => {
|
||||
@@ -513,44 +495,47 @@ describe(`AutoLoginPartialRoutesGuard`, () => {
|
||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||
configurationService = TestBed.inject(ConfigurationService);
|
||||
|
||||
spyOn(configurationService, 'getOpenIDConfiguration').and.callFake(
|
||||
(configId) => of({ configId })
|
||||
);
|
||||
vi.spyOn(
|
||||
configurationService,
|
||||
'getOpenIDConfiguration'
|
||||
).mockImplementation((configId) => of({ configId }));
|
||||
|
||||
autoLoginService = TestBed.inject(AutoLoginService);
|
||||
});
|
||||
|
||||
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
|
||||
afterEach(() => {
|
||||
storagePersistenceService.clear({});
|
||||
});
|
||||
|
||||
it('should save current route (empty) and call `login` if not authenticated already', waitForAsync(() => {
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
it('should save current route (empty) and call `login` if not authenticated already', async () => {
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
false
|
||||
);
|
||||
const checkSavedRedirectRouteAndNavigateSpy = spyOn(
|
||||
const checkSavedRedirectRouteAndNavigateSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'checkSavedRedirectRouteAndNavigate'
|
||||
);
|
||||
const saveRedirectRouteSpy = spyOn(
|
||||
const saveRedirectRouteSpy = vi.spyOn(
|
||||
autoLoginService,
|
||||
'saveRedirectRoute'
|
||||
);
|
||||
const loginSpy = spyOn(loginService, 'login');
|
||||
const loginSpy = vi.spyOn(loginService, 'login');
|
||||
|
||||
const guard$ = TestBed.runInInjectionContext(
|
||||
autoLoginPartialRoutesGuardWithConfig('configId1')
|
||||
);
|
||||
|
||||
guard$.subscribe(() => {
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledOnceWith(
|
||||
await firstValueFrom(guard$);
|
||||
expect(saveRedirectRouteSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
{ configId: 'configId1' },
|
||||
''
|
||||
);
|
||||
expect(loginSpy).toHaveBeenCalledOnceWith({ configId: 'configId1' });
|
||||
expect(loginSpy).toHaveBeenCalledExactlyOnceWith({
|
||||
configId: 'configId1',
|
||||
});
|
||||
expect(checkSavedRedirectRouteAndNavigateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { inject, Injectable } from 'injection-js';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { AuthOptions } from '../auth-options';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import { type Observable, of } from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import type { AuthOptions } from '../auth-options';
|
||||
import { AuthStateService } from '../auth-state/auth-state.service';
|
||||
import { ConfigurationService } from '../config/config.service';
|
||||
import { injectAbstractType } from '../injection';
|
||||
import { LoginService } from '../login/login.service';
|
||||
import {
|
||||
AbstractRouter,
|
||||
type ActivatedRouteSnapshot,
|
||||
type RouterStateSnapshot,
|
||||
} from '../router';
|
||||
import { AutoLoginService } from './auto-login.service';
|
||||
|
||||
@Injectable()
|
||||
@@ -22,7 +23,7 @@ export class AutoLoginPartialRoutesGuard {
|
||||
|
||||
private readonly configurationService = inject(ConfigurationService);
|
||||
|
||||
private readonly router = inject(Router);
|
||||
private readonly router = injectAbstractType(AbstractRouter);
|
||||
|
||||
canLoad(): Observable<boolean> {
|
||||
const url =
|
||||
@@ -79,14 +80,14 @@ export class AutoLoginPartialRoutesGuard {
|
||||
|
||||
export function autoLoginPartialRoutesGuard(
|
||||
route?: ActivatedRouteSnapshot,
|
||||
state?: RouterStateSnapshot,
|
||||
_state?: RouterStateSnapshot,
|
||||
configId?: string
|
||||
): Observable<boolean> {
|
||||
const configurationService = inject(ConfigurationService);
|
||||
const authStateService = inject(AuthStateService);
|
||||
const loginService = inject(LoginService);
|
||||
const autoLoginService = inject(AutoLoginService);
|
||||
const router = inject(Router);
|
||||
const router = injectAbstractType(AbstractRouter);
|
||||
const authOptions: AuthOptions | undefined = route?.data
|
||||
? { customParams: route.data }
|
||||
: undefined;
|
||||
@@ -125,7 +126,7 @@ function checkAuth(
|
||||
configId?: string
|
||||
): Observable<boolean> {
|
||||
return configurationService.getOpenIDConfiguration(configId).pipe(
|
||||
map((configuration) => {
|
||||
switchMap((configuration) => {
|
||||
const isAuthenticated =
|
||||
authStateService.areAuthStorageTokensValid(configuration);
|
||||
|
||||
@@ -136,13 +137,16 @@ function checkAuth(
|
||||
if (!isAuthenticated) {
|
||||
autoLoginService.saveRedirectRoute(configuration, url);
|
||||
if (authOptions) {
|
||||
loginService.login(configuration, authOptions);
|
||||
} else {
|
||||
loginService.login(configuration);
|
||||
return loginService
|
||||
.login(configuration, authOptions)
|
||||
.pipe(switchMap(() => of(isAuthenticated)));
|
||||
}
|
||||
return loginService
|
||||
.login(configuration)
|
||||
.pipe(switchMap(() => of(isAuthenticated)));
|
||||
}
|
||||
|
||||
return isAuthenticated;
|
||||
return of(isAuthenticated);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { Router } from '@angular/router';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { mockProvider } from '../../test/auto-mock';
|
||||
import { TestBed, mockRouterProvider } from '@/testing';
|
||||
import { AbstractRouter } from 'oidc-client-rx/router';
|
||||
import { vi } from 'vitest';
|
||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||
import { mockProvider } from '../testing/mock';
|
||||
import { AutoLoginService } from './auto-login.service';
|
||||
|
||||
describe('AutoLoginService ', () => {
|
||||
let autoLoginService: AutoLoginService;
|
||||
let storagePersistenceService: StoragePersistenceService;
|
||||
let router: Router;
|
||||
let router: AbstractRouter;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [RouterTestingModule],
|
||||
providers: [AutoLoginService, mockProvider(StoragePersistenceService)],
|
||||
imports: [],
|
||||
providers: [
|
||||
mockRouterProvider(),
|
||||
AutoLoginService,
|
||||
mockProvider(StoragePersistenceService),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
router = TestBed.inject(Router);
|
||||
router = TestBed.inject(AbstractRouter);
|
||||
autoLoginService = TestBed.inject(AutoLoginService);
|
||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||
});
|
||||
@@ -29,11 +30,11 @@ describe('AutoLoginService ', () => {
|
||||
|
||||
describe('checkSavedRedirectRouteAndNavigate', () => {
|
||||
it('if not route is saved, router and delete are not called', () => {
|
||||
const deleteSpy = spyOn(storagePersistenceService, 'remove');
|
||||
const routerSpy = spyOn(router, 'navigateByUrl');
|
||||
const readSpy = spyOn(storagePersistenceService, 'read').and.returnValue(
|
||||
null
|
||||
);
|
||||
const deleteSpy = vi.spyOn(storagePersistenceService, 'remove');
|
||||
const routerSpy = vi.spyOn(router, 'navigateByUrl');
|
||||
const readSpy = vi
|
||||
.spyOn(storagePersistenceService, 'read')
|
||||
.mockReturnValue(null);
|
||||
|
||||
autoLoginService.checkSavedRedirectRouteAndNavigate({
|
||||
configId: 'configId1',
|
||||
@@ -41,27 +42,27 @@ describe('AutoLoginService ', () => {
|
||||
|
||||
expect(deleteSpy).not.toHaveBeenCalled();
|
||||
expect(routerSpy).not.toHaveBeenCalled();
|
||||
expect(readSpy).toHaveBeenCalledOnceWith('redirect', {
|
||||
expect(readSpy).toHaveBeenCalledExactlyOnceWith('redirect', {
|
||||
configId: 'configId1',
|
||||
});
|
||||
});
|
||||
|
||||
it('if route is saved, router and delete are called', () => {
|
||||
const deleteSpy = spyOn(storagePersistenceService, 'remove');
|
||||
const routerSpy = spyOn(router, 'navigateByUrl');
|
||||
const readSpy = spyOn(storagePersistenceService, 'read').and.returnValue(
|
||||
'saved-route'
|
||||
);
|
||||
const deleteSpy = vi.spyOn(storagePersistenceService, 'remove');
|
||||
const routerSpy = vi.spyOn(router, 'navigateByUrl');
|
||||
const readSpy = vi
|
||||
.spyOn(storagePersistenceService, 'read')
|
||||
.mockReturnValue('saved-route');
|
||||
|
||||
autoLoginService.checkSavedRedirectRouteAndNavigate({
|
||||
configId: 'configId1',
|
||||
});
|
||||
|
||||
expect(deleteSpy).toHaveBeenCalledOnceWith('redirect', {
|
||||
expect(deleteSpy).toHaveBeenCalledExactlyOnceWith('redirect', {
|
||||
configId: 'configId1',
|
||||
});
|
||||
expect(routerSpy).toHaveBeenCalledOnceWith('saved-route');
|
||||
expect(readSpy).toHaveBeenCalledOnceWith('redirect', {
|
||||
expect(routerSpy).toHaveBeenCalledExactlyOnceWith('saved-route');
|
||||
expect(readSpy).toHaveBeenCalledExactlyOnceWith('redirect', {
|
||||
configId: 'configId1',
|
||||
});
|
||||
});
|
||||
@@ -69,16 +70,20 @@ describe('AutoLoginService ', () => {
|
||||
|
||||
describe('saveRedirectRoute', () => {
|
||||
it('calls storageService with correct params', () => {
|
||||
const writeSpy = spyOn(storagePersistenceService, 'write');
|
||||
const writeSpy = vi.spyOn(storagePersistenceService, 'write');
|
||||
|
||||
autoLoginService.saveRedirectRoute(
|
||||
{ configId: 'configId1' },
|
||||
'some-route'
|
||||
);
|
||||
|
||||
expect(writeSpy).toHaveBeenCalledOnceWith('redirect', 'some-route', {
|
||||
expect(writeSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
'redirect',
|
||||
'some-route',
|
||||
{
|
||||
configId: 'configId1',
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { inject, Injectable } from 'injection-js';
|
||||
import { Router } from '@angular/router';
|
||||
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import { injectAbstractType } from '../injection';
|
||||
import { AbstractRouter } from '../router';
|
||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||
|
||||
const STORAGE_KEY = 'redirect';
|
||||
@@ -9,7 +10,7 @@ const STORAGE_KEY = 'redirect';
|
||||
export class AutoLoginService {
|
||||
private readonly storageService = inject(StoragePersistenceService);
|
||||
|
||||
private readonly router = inject(Router);
|
||||
private readonly router = injectAbstractType(AbstractRouter);
|
||||
|
||||
checkSavedRedirectRouteAndNavigate(config: OpenIdConfiguration | null): void {
|
||||
if (!config) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { mockProvider } from '../../test/auto-mock';
|
||||
import { CallbackContext } from '../flows/callback-context';
|
||||
import { TestBed } from '@/testing';
|
||||
import { Observable, firstValueFrom, of } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import type { CallbackContext } from '../flows/callback-context';
|
||||
import { mockProvider } from '../testing/mock';
|
||||
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
|
||||
import { UrlService } from '../utils/url/url.service';
|
||||
import { CallbackService } from './callback.service';
|
||||
@@ -26,9 +27,6 @@ describe('CallbackService ', () => {
|
||||
mockProvider(CodeFlowCallbackService),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
callbackService = TestBed.inject(CallbackService);
|
||||
flowHelper = TestBed.inject(FlowHelper);
|
||||
implicitFlowCallbackService = TestBed.inject(ImplicitFlowCallbackService);
|
||||
@@ -38,10 +36,13 @@ describe('CallbackService ', () => {
|
||||
|
||||
describe('isCallback', () => {
|
||||
it('calls urlService.isCallbackFromSts with passed url', () => {
|
||||
const urlServiceSpy = spyOn(urlService, 'isCallbackFromSts');
|
||||
const urlServiceSpy = vi.spyOn(urlService, 'isCallbackFromSts');
|
||||
|
||||
callbackService.isCallback('anyUrl');
|
||||
expect(urlServiceSpy).toHaveBeenCalledOnceWith('anyUrl', undefined);
|
||||
expect(urlServiceSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
'anyUrl',
|
||||
undefined
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,93 +53,95 @@ describe('CallbackService ', () => {
|
||||
});
|
||||
|
||||
describe('handleCallbackAndFireEvents', () => {
|
||||
it('calls authorizedCallbackWithCode if current flow is code flow', waitForAsync(() => {
|
||||
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true);
|
||||
const authorizedCallbackWithCodeSpy = spyOn(
|
||||
codeFlowCallbackService,
|
||||
'authenticatedCallbackWithCode'
|
||||
).and.returnValue(of({} as CallbackContext));
|
||||
it('calls authorizedCallbackWithCode if current flow is code flow', async () => {
|
||||
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
|
||||
const authorizedCallbackWithCodeSpy = vi
|
||||
.spyOn(codeFlowCallbackService, 'authenticatedCallbackWithCode')
|
||||
.mockReturnValue(of({} as CallbackContext));
|
||||
|
||||
callbackService
|
||||
.handleCallbackAndFireEvents('anyUrl', { configId: 'configId1' }, [
|
||||
await firstValueFrom(
|
||||
callbackService.handleCallbackAndFireEvents(
|
||||
'anyUrl',
|
||||
{ configId: 'configId1' },
|
||||
])
|
||||
.subscribe(() => {
|
||||
expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledOnceWith(
|
||||
[{ configId: 'configId1' }]
|
||||
)
|
||||
);
|
||||
|
||||
expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
'anyUrl',
|
||||
{ configId: 'configId1' },
|
||||
[{ configId: 'configId1' }]
|
||||
);
|
||||
});
|
||||
}));
|
||||
|
||||
it('calls authorizedImplicitFlowCallback without hash if current flow is implicit flow and callbackurl does not include a hash', waitForAsync(() => {
|
||||
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(false);
|
||||
spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').and.returnValue(true);
|
||||
const authorizedCallbackWithCodeSpy = spyOn(
|
||||
implicitFlowCallbackService,
|
||||
'authenticatedImplicitFlowCallback'
|
||||
).and.returnValue(of({} as CallbackContext));
|
||||
it('calls authorizedImplicitFlowCallback without hash if current flow is implicit flow and callbackurl does not include a hash', async () => {
|
||||
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false);
|
||||
vi.spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').mockReturnValue(
|
||||
true
|
||||
);
|
||||
const authorizedCallbackWithCodeSpy = vi
|
||||
.spyOn(implicitFlowCallbackService, 'authenticatedImplicitFlowCallback')
|
||||
.mockReturnValue(of({} as CallbackContext));
|
||||
|
||||
callbackService
|
||||
.handleCallbackAndFireEvents('anyUrl', { configId: 'configId1' }, [
|
||||
{ configId: 'configId1' },
|
||||
])
|
||||
.subscribe(() => {
|
||||
expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledWith(
|
||||
await firstValueFrom(
|
||||
callbackService.handleCallbackAndFireEvents(
|
||||
'anyUrl',
|
||||
{ configId: 'configId1' },
|
||||
[{ configId: 'configId1' }]
|
||||
)
|
||||
);
|
||||
expect(authorizedCallbackWithCodeSpy.mock.calls).toEqual([
|
||||
[{ configId: 'configId1' }, [{ configId: 'configId1' }]],
|
||||
]);
|
||||
});
|
||||
}));
|
||||
|
||||
it('calls authorizedImplicitFlowCallback with hash if current flow is implicit flow and callbackurl does include a hash', waitForAsync(() => {
|
||||
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(false);
|
||||
spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').and.returnValue(true);
|
||||
const authorizedCallbackWithCodeSpy = spyOn(
|
||||
implicitFlowCallbackService,
|
||||
'authenticatedImplicitFlowCallback'
|
||||
).and.returnValue(of({} as CallbackContext));
|
||||
it('calls authorizedImplicitFlowCallback with hash if current flow is implicit flow and callbackurl does include a hash', async () => {
|
||||
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false);
|
||||
vi.spyOn(flowHelper, 'isCurrentFlowAnyImplicitFlow').mockReturnValue(
|
||||
true
|
||||
);
|
||||
const authorizedCallbackWithCodeSpy = vi
|
||||
.spyOn(implicitFlowCallbackService, 'authenticatedImplicitFlowCallback')
|
||||
.mockReturnValue(of({} as CallbackContext));
|
||||
|
||||
callbackService
|
||||
.handleCallbackAndFireEvents(
|
||||
await firstValueFrom(
|
||||
callbackService.handleCallbackAndFireEvents(
|
||||
'anyUrlWithAHash#some-string',
|
||||
{ configId: 'configId1' },
|
||||
[{ configId: 'configId1' }]
|
||||
)
|
||||
.subscribe(() => {
|
||||
expect(authorizedCallbackWithCodeSpy).toHaveBeenCalledWith(
|
||||
{ configId: 'configId1' },
|
||||
[{ configId: 'configId1' }],
|
||||
'some-string'
|
||||
);
|
||||
});
|
||||
}));
|
||||
|
||||
it('emits callbackinternal no matter which flow it is', waitForAsync(() => {
|
||||
const callbackSpy = spyOn(
|
||||
expect(authorizedCallbackWithCodeSpy.mock.calls).toEqual([
|
||||
[{ configId: 'configId1' }, [{ configId: 'configId1' }], 'some-string'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('emits callbackinternal no matter which flow it is', async () => {
|
||||
const callbackSpy = vi.spyOn(
|
||||
(callbackService as any).stsCallbackInternal$,
|
||||
'next'
|
||||
);
|
||||
|
||||
spyOn(flowHelper, 'isCurrentFlowCodeFlow').and.returnValue(true);
|
||||
const authenticatedCallbackWithCodeSpy = spyOn(
|
||||
codeFlowCallbackService,
|
||||
'authenticatedCallbackWithCode'
|
||||
).and.returnValue(of({} as CallbackContext));
|
||||
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
|
||||
const authenticatedCallbackWithCodeSpy = vi
|
||||
.spyOn(codeFlowCallbackService, 'authenticatedCallbackWithCode')
|
||||
.mockReturnValue(of({} as CallbackContext));
|
||||
|
||||
callbackService
|
||||
.handleCallbackAndFireEvents('anyUrl', { configId: 'configId1' }, [
|
||||
await firstValueFrom(
|
||||
callbackService.handleCallbackAndFireEvents(
|
||||
'anyUrl',
|
||||
{ configId: 'configId1' },
|
||||
])
|
||||
.subscribe(() => {
|
||||
expect(authenticatedCallbackWithCodeSpy).toHaveBeenCalledOnceWith(
|
||||
[{ configId: 'configId1' }]
|
||||
)
|
||||
);
|
||||
|
||||
expect(authenticatedCallbackWithCodeSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
'anyUrl',
|
||||
{ configId: 'configId1' },
|
||||
[{ configId: 'configId1' }]
|
||||
);
|
||||
expect(callbackSpy).toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { inject, Injectable } from 'injection-js';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import { CallbackContext } from '../flows/callback-context';
|
||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import type { CallbackContext } from '../flows/callback-context';
|
||||
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
|
||||
import { UrlService } from '../utils/url/url.service';
|
||||
import { CodeFlowCallbackService } from './code-flow-callback.service';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { Router } from '@angular/router';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { mockProvider } from '../../test/auto-mock';
|
||||
import { CallbackContext } from '../flows/callback-context';
|
||||
import { TestBed, mockRouterProvider } from '@/testing';
|
||||
import { AbstractRouter } from 'oidc-client-rx';
|
||||
import { firstValueFrom, of, throwError } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import type { CallbackContext } from '../flows/callback-context';
|
||||
import { FlowsDataService } from '../flows/flows-data.service';
|
||||
import { FlowsService } from '../flows/flows.service';
|
||||
import { mockProvider } from '../testing/mock';
|
||||
import { CodeFlowCallbackService } from './code-flow-callback.service';
|
||||
import { IntervalService } from './interval.service';
|
||||
|
||||
@@ -14,26 +14,24 @@ describe('CodeFlowCallbackService ', () => {
|
||||
let intervalService: IntervalService;
|
||||
let flowsService: FlowsService;
|
||||
let flowsDataService: FlowsDataService;
|
||||
let router: Router;
|
||||
let router: AbstractRouter;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [RouterTestingModule],
|
||||
imports: [],
|
||||
providers: [
|
||||
mockRouterProvider(),
|
||||
CodeFlowCallbackService,
|
||||
mockProvider(FlowsService),
|
||||
mockProvider(FlowsDataService),
|
||||
mockProvider(IntervalService),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
codeFlowCallbackService = TestBed.inject(CodeFlowCallbackService);
|
||||
intervalService = TestBed.inject(IntervalService);
|
||||
flowsDataService = TestBed.inject(FlowsDataService);
|
||||
flowsService = TestBed.inject(FlowsService);
|
||||
router = TestBed.inject(Router);
|
||||
router = TestBed.inject(AbstractRouter);
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
@@ -42,11 +40,10 @@ describe('CodeFlowCallbackService ', () => {
|
||||
|
||||
describe('authenticatedCallbackWithCode', () => {
|
||||
it('calls flowsService.processCodeFlowCallback with correct url', () => {
|
||||
const spy = spyOn(
|
||||
flowsService,
|
||||
'processCodeFlowCallback'
|
||||
).and.returnValue(of({} as CallbackContext));
|
||||
//spyOn(configurationProvider, 'getOpenIDConfiguration').and.returnValue({ triggerAuthorizationResultEvent: true });
|
||||
const spy = vi
|
||||
.spyOn(flowsService, 'processCodeFlowCallback')
|
||||
.mockReturnValue(of({} as CallbackContext));
|
||||
//spyOn(configurationProvider, 'getOpenIDConfiguration').mockReturnValue({ triggerAuthorizationResultEvent: true });
|
||||
|
||||
const config = {
|
||||
configId: 'configId1',
|
||||
@@ -58,10 +55,12 @@ describe('CodeFlowCallbackService ', () => {
|
||||
config,
|
||||
[config]
|
||||
);
|
||||
expect(spy).toHaveBeenCalledOnceWith('some-url1', config, [config]);
|
||||
expect(spy).toHaveBeenCalledExactlyOnceWith('some-url1', config, [
|
||||
config,
|
||||
]);
|
||||
});
|
||||
|
||||
it('does only call resetCodeFlowInProgress if triggerAuthorizationResultEvent is true and isRenewProcess is true', waitForAsync(() => {
|
||||
it('does only call resetCodeFlowInProgress if triggerAuthorizationResultEvent is true and isRenewProcess is true', async () => {
|
||||
const callbackContext = {
|
||||
code: '',
|
||||
refreshToken: '',
|
||||
@@ -73,27 +72,34 @@ describe('CodeFlowCallbackService ', () => {
|
||||
validationResult: null,
|
||||
existingIdToken: '',
|
||||
};
|
||||
const spy = spyOn(
|
||||
flowsService,
|
||||
'processCodeFlowCallback'
|
||||
).and.returnValue(of(callbackContext));
|
||||
const flowsDataSpy = spyOn(flowsDataService, 'resetCodeFlowInProgress');
|
||||
const routerSpy = spyOn(router, 'navigateByUrl');
|
||||
const spy = vi
|
||||
.spyOn(flowsService, 'processCodeFlowCallback')
|
||||
.mockReturnValue(of(callbackContext));
|
||||
const flowsDataSpy = vi.spyOn(
|
||||
flowsDataService,
|
||||
'resetCodeFlowInProgress'
|
||||
);
|
||||
const routerSpy = vi.spyOn(router, 'navigateByUrl');
|
||||
const config = {
|
||||
configId: 'configId1',
|
||||
triggerAuthorizationResultEvent: true,
|
||||
};
|
||||
|
||||
codeFlowCallbackService
|
||||
.authenticatedCallbackWithCode('some-url2', config, [config])
|
||||
.subscribe(() => {
|
||||
expect(spy).toHaveBeenCalledOnceWith('some-url2', config, [config]);
|
||||
await firstValueFrom(
|
||||
codeFlowCallbackService.authenticatedCallbackWithCode(
|
||||
'some-url2',
|
||||
config,
|
||||
[config]
|
||||
)
|
||||
);
|
||||
expect(spy).toHaveBeenCalledExactlyOnceWith('some-url2', config, [
|
||||
config,
|
||||
]);
|
||||
expect(routerSpy).not.toHaveBeenCalled();
|
||||
expect(flowsDataSpy).toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('calls router and resetCodeFlowInProgress if triggerAuthorizationResultEvent is false and isRenewProcess is false', waitForAsync(() => {
|
||||
it('calls router and resetCodeFlowInProgress if triggerAuthorizationResultEvent is false and isRenewProcess is false', async () => {
|
||||
const callbackContext = {
|
||||
code: '',
|
||||
refreshToken: '',
|
||||
@@ -105,40 +111,47 @@ describe('CodeFlowCallbackService ', () => {
|
||||
validationResult: null,
|
||||
existingIdToken: '',
|
||||
};
|
||||
const spy = spyOn(
|
||||
flowsService,
|
||||
'processCodeFlowCallback'
|
||||
).and.returnValue(of(callbackContext));
|
||||
const flowsDataSpy = spyOn(flowsDataService, 'resetCodeFlowInProgress');
|
||||
const routerSpy = spyOn(router, 'navigateByUrl');
|
||||
const spy = vi
|
||||
.spyOn(flowsService, 'processCodeFlowCallback')
|
||||
.mockReturnValue(of(callbackContext));
|
||||
const flowsDataSpy = vi.spyOn(
|
||||
flowsDataService,
|
||||
'resetCodeFlowInProgress'
|
||||
);
|
||||
const routerSpy = vi.spyOn(router, 'navigateByUrl');
|
||||
const config = {
|
||||
configId: 'configId1',
|
||||
triggerAuthorizationResultEvent: false,
|
||||
postLoginRoute: 'postLoginRoute',
|
||||
};
|
||||
|
||||
codeFlowCallbackService
|
||||
.authenticatedCallbackWithCode('some-url3', config, [config])
|
||||
.subscribe(() => {
|
||||
expect(spy).toHaveBeenCalledOnceWith('some-url3', config, [config]);
|
||||
expect(routerSpy).toHaveBeenCalledOnceWith('postLoginRoute');
|
||||
await firstValueFrom(
|
||||
codeFlowCallbackService.authenticatedCallbackWithCode(
|
||||
'some-url3',
|
||||
config,
|
||||
[config]
|
||||
)
|
||||
);
|
||||
expect(spy).toHaveBeenCalledExactlyOnceWith('some-url3', config, [
|
||||
config,
|
||||
]);
|
||||
expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute');
|
||||
expect(flowsDataSpy).toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('resetSilentRenewRunning, resetCodeFlowInProgress and stopPeriodicallTokenCheck in case of error', waitForAsync(() => {
|
||||
spyOn(flowsService, 'processCodeFlowCallback').and.returnValue(
|
||||
it('resetSilentRenewRunning, resetCodeFlowInProgress and stopPeriodicallTokenCheck in case of error', async () => {
|
||||
vi.spyOn(flowsService, 'processCodeFlowCallback').mockReturnValue(
|
||||
throwError(() => new Error('error'))
|
||||
);
|
||||
const resetSilentRenewRunningSpy = spyOn(
|
||||
const resetSilentRenewRunningSpy = vi.spyOn(
|
||||
flowsDataService,
|
||||
'resetSilentRenewRunning'
|
||||
);
|
||||
const resetCodeFlowInProgressSpy = spyOn(
|
||||
const resetCodeFlowInProgressSpy = vi.spyOn(
|
||||
flowsDataService,
|
||||
'resetCodeFlowInProgress'
|
||||
);
|
||||
const stopPeriodicallTokenCheckSpy = spyOn(
|
||||
const stopPeriodicallTokenCheckSpy = vi.spyOn(
|
||||
intervalService,
|
||||
'stopPeriodicTokenCheck'
|
||||
);
|
||||
@@ -149,33 +162,37 @@ describe('CodeFlowCallbackService ', () => {
|
||||
postLoginRoute: 'postLoginRoute',
|
||||
};
|
||||
|
||||
codeFlowCallbackService
|
||||
.authenticatedCallbackWithCode('some-url4', config, [config])
|
||||
.subscribe({
|
||||
error: (err) => {
|
||||
try {
|
||||
await firstValueFrom(
|
||||
codeFlowCallbackService.authenticatedCallbackWithCode(
|
||||
'some-url4',
|
||||
config,
|
||||
[config]
|
||||
)
|
||||
);
|
||||
} catch (err: any) {
|
||||
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
||||
expect(resetCodeFlowInProgressSpy).toHaveBeenCalled();
|
||||
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
|
||||
expect(err).toBeTruthy();
|
||||
},
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
it(`navigates to unauthorizedRoute in case of error and in case of error and
|
||||
triggerAuthorizationResultEvent is false`, waitForAsync(() => {
|
||||
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||
spyOn(flowsService, 'processCodeFlowCallback').and.returnValue(
|
||||
triggerAuthorizationResultEvent is false`, async () => {
|
||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
||||
vi.spyOn(flowsService, 'processCodeFlowCallback').mockReturnValue(
|
||||
throwError(() => new Error('error'))
|
||||
);
|
||||
const resetSilentRenewRunningSpy = spyOn(
|
||||
const resetSilentRenewRunningSpy = vi.spyOn(
|
||||
flowsDataService,
|
||||
'resetSilentRenewRunning'
|
||||
);
|
||||
const stopPeriodicallTokenCheckSpy = spyOn(
|
||||
const stopPeriodicallTokenCheckSpy = vi.spyOn(
|
||||
intervalService,
|
||||
'stopPeriodicTokenCheck'
|
||||
);
|
||||
const routerSpy = spyOn(router, 'navigateByUrl');
|
||||
const routerSpy = vi.spyOn(router, 'navigateByUrl');
|
||||
|
||||
const config = {
|
||||
configId: 'configId1',
|
||||
@@ -183,16 +200,20 @@ describe('CodeFlowCallbackService ', () => {
|
||||
unauthorizedRoute: 'unauthorizedRoute',
|
||||
};
|
||||
|
||||
codeFlowCallbackService
|
||||
.authenticatedCallbackWithCode('some-url5', config, [config])
|
||||
.subscribe({
|
||||
error: (err) => {
|
||||
try {
|
||||
await firstValueFrom(
|
||||
codeFlowCallbackService.authenticatedCallbackWithCode(
|
||||
'some-url5',
|
||||
config,
|
||||
[config]
|
||||
)
|
||||
);
|
||||
} catch (err: any) {
|
||||
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
||||
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
|
||||
expect(err).toBeTruthy();
|
||||
expect(routerSpy).toHaveBeenCalledOnceWith('unauthorizedRoute');
|
||||
},
|
||||
});
|
||||
}));
|
||||
expect(routerSpy).toHaveBeenCalledExactlyOnceWith('unauthorizedRoute');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { inject, Injectable } from 'injection-js';
|
||||
import { Router } from '@angular/router';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import { type Observable, throwError } from 'rxjs';
|
||||
import { catchError, tap } from 'rxjs/operators';
|
||||
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import { CallbackContext } from '../flows/callback-context';
|
||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import type { CallbackContext } from '../flows/callback-context';
|
||||
import { FlowsDataService } from '../flows/flows-data.service';
|
||||
import { FlowsService } from '../flows/flows.service';
|
||||
import { injectAbstractType } from '../injection';
|
||||
import { AbstractRouter } from '../router';
|
||||
import { IntervalService } from './interval.service';
|
||||
|
||||
@Injectable()
|
||||
export class CodeFlowCallbackService {
|
||||
private readonly flowsService = inject(FlowsService);
|
||||
|
||||
private readonly router = inject(Router);
|
||||
private readonly router = injectAbstractType(AbstractRouter);
|
||||
|
||||
private readonly flowsDataService = inject(FlowsDataService);
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { Router } from '@angular/router';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { mockProvider } from '../../test/auto-mock';
|
||||
import { CallbackContext } from '../flows/callback-context';
|
||||
import { TestBed, mockRouterProvider } from '@/testing';
|
||||
import { AbstractRouter } from 'oidc-client-rx';
|
||||
import { firstValueFrom, of, throwError } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import type { CallbackContext } from '../flows/callback-context';
|
||||
import { FlowsDataService } from '../flows/flows-data.service';
|
||||
import { FlowsService } from '../flows/flows.service';
|
||||
import { mockProvider } from '../testing/mock';
|
||||
import { ImplicitFlowCallbackService } from './implicit-flow-callback.service';
|
||||
import { IntervalService } from './interval.service';
|
||||
|
||||
@@ -14,25 +14,24 @@ describe('ImplicitFlowCallbackService ', () => {
|
||||
let intervalService: IntervalService;
|
||||
let flowsService: FlowsService;
|
||||
let flowsDataService: FlowsDataService;
|
||||
let router: Router;
|
||||
let router: AbstractRouter;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [RouterTestingModule],
|
||||
imports: [],
|
||||
providers: [
|
||||
ImplicitFlowCallbackService,
|
||||
mockRouterProvider(),
|
||||
mockProvider(FlowsService),
|
||||
mockProvider(FlowsDataService),
|
||||
mockProvider(IntervalService),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
implicitFlowCallbackService = TestBed.inject(ImplicitFlowCallbackService);
|
||||
intervalService = TestBed.inject(IntervalService);
|
||||
flowsDataService = TestBed.inject(FlowsDataService);
|
||||
flowsService = TestBed.inject(FlowsService);
|
||||
router = TestBed.inject(Router);
|
||||
router = TestBed.inject(AbstractRouter);
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
@@ -41,10 +40,9 @@ describe('ImplicitFlowCallbackService ', () => {
|
||||
|
||||
describe('authorizedImplicitFlowCallback', () => {
|
||||
it('calls flowsService.processImplicitFlowCallback with hash if given', () => {
|
||||
const spy = spyOn(
|
||||
flowsService,
|
||||
'processImplicitFlowCallback'
|
||||
).and.returnValue(of({} as CallbackContext));
|
||||
const spy = vi
|
||||
.spyOn(flowsService, 'processImplicitFlowCallback')
|
||||
.mockReturnValue(of({} as CallbackContext));
|
||||
const config = {
|
||||
configId: 'configId1',
|
||||
triggerAuthorizationResultEvent: true,
|
||||
@@ -56,10 +54,14 @@ describe('ImplicitFlowCallbackService ', () => {
|
||||
'some-hash'
|
||||
);
|
||||
|
||||
expect(spy).toHaveBeenCalledOnceWith(config, [config], 'some-hash');
|
||||
expect(spy).toHaveBeenCalledExactlyOnceWith(
|
||||
config,
|
||||
[config],
|
||||
'some-hash'
|
||||
);
|
||||
});
|
||||
|
||||
it('does nothing if triggerAuthorizationResultEvent is true and isRenewProcess is true', waitForAsync(() => {
|
||||
it('does nothing if triggerAuthorizationResultEvent is true and isRenewProcess is true', async () => {
|
||||
const callbackContext = {
|
||||
code: '',
|
||||
refreshToken: '',
|
||||
@@ -71,25 +73,31 @@ describe('ImplicitFlowCallbackService ', () => {
|
||||
validationResult: null,
|
||||
existingIdToken: '',
|
||||
};
|
||||
const spy = spyOn(
|
||||
flowsService,
|
||||
'processImplicitFlowCallback'
|
||||
).and.returnValue(of(callbackContext));
|
||||
const routerSpy = spyOn(router, 'navigateByUrl');
|
||||
const spy = vi
|
||||
.spyOn(flowsService, 'processImplicitFlowCallback')
|
||||
.mockReturnValue(of(callbackContext));
|
||||
const routerSpy = vi.spyOn(router, 'navigateByUrl');
|
||||
const config = {
|
||||
configId: 'configId1',
|
||||
triggerAuthorizationResultEvent: true,
|
||||
};
|
||||
|
||||
implicitFlowCallbackService
|
||||
.authenticatedImplicitFlowCallback(config, [config], 'some-hash')
|
||||
.subscribe(() => {
|
||||
expect(spy).toHaveBeenCalledOnceWith(config, [config], 'some-hash');
|
||||
await firstValueFrom(
|
||||
implicitFlowCallbackService.authenticatedImplicitFlowCallback(
|
||||
config,
|
||||
[config],
|
||||
'some-hash'
|
||||
)
|
||||
);
|
||||
expect(spy).toHaveBeenCalledExactlyOnceWith(
|
||||
config,
|
||||
[config],
|
||||
'some-hash'
|
||||
);
|
||||
expect(routerSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('calls router if triggerAuthorizationResultEvent is false and isRenewProcess is false', waitForAsync(() => {
|
||||
it('calls router if triggerAuthorizationResultEvent is false and isRenewProcess is false', async () => {
|
||||
const callbackContext = {
|
||||
code: '',
|
||||
refreshToken: '',
|
||||
@@ -101,34 +109,40 @@ describe('ImplicitFlowCallbackService ', () => {
|
||||
validationResult: null,
|
||||
existingIdToken: '',
|
||||
};
|
||||
const spy = spyOn(
|
||||
flowsService,
|
||||
'processImplicitFlowCallback'
|
||||
).and.returnValue(of(callbackContext));
|
||||
const routerSpy = spyOn(router, 'navigateByUrl');
|
||||
const spy = vi
|
||||
.spyOn(flowsService, 'processImplicitFlowCallback')
|
||||
.mockReturnValue(of(callbackContext));
|
||||
const routerSpy = vi.spyOn(router, 'navigateByUrl');
|
||||
const config = {
|
||||
configId: 'configId1',
|
||||
triggerAuthorizationResultEvent: false,
|
||||
postLoginRoute: 'postLoginRoute',
|
||||
};
|
||||
|
||||
implicitFlowCallbackService
|
||||
.authenticatedImplicitFlowCallback(config, [config], 'some-hash')
|
||||
.subscribe(() => {
|
||||
expect(spy).toHaveBeenCalledOnceWith(config, [config], 'some-hash');
|
||||
expect(routerSpy).toHaveBeenCalledOnceWith('postLoginRoute');
|
||||
await firstValueFrom(
|
||||
implicitFlowCallbackService.authenticatedImplicitFlowCallback(
|
||||
config,
|
||||
[config],
|
||||
'some-hash'
|
||||
)
|
||||
);
|
||||
expect(spy).toHaveBeenCalledExactlyOnceWith(
|
||||
config,
|
||||
[config],
|
||||
'some-hash'
|
||||
);
|
||||
expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute');
|
||||
});
|
||||
}));
|
||||
|
||||
it('resetSilentRenewRunning and stopPeriodicallyTokenCheck in case of error', waitForAsync(() => {
|
||||
spyOn(flowsService, 'processImplicitFlowCallback').and.returnValue(
|
||||
it('resetSilentRenewRunning and stopPeriodicallyTokenCheck in case of error', async () => {
|
||||
vi.spyOn(flowsService, 'processImplicitFlowCallback').mockReturnValue(
|
||||
throwError(() => new Error('error'))
|
||||
);
|
||||
const resetSilentRenewRunningSpy = spyOn(
|
||||
const resetSilentRenewRunningSpy = vi.spyOn(
|
||||
flowsDataService,
|
||||
'resetSilentRenewRunning'
|
||||
);
|
||||
const stopPeriodicallyTokenCheckSpy = spyOn(
|
||||
const stopPeriodicallyTokenCheckSpy = vi.spyOn(
|
||||
intervalService,
|
||||
'stopPeriodicTokenCheck'
|
||||
);
|
||||
@@ -138,48 +152,56 @@ describe('ImplicitFlowCallbackService ', () => {
|
||||
postLoginRoute: 'postLoginRoute',
|
||||
};
|
||||
|
||||
implicitFlowCallbackService
|
||||
.authenticatedImplicitFlowCallback(config, [config], 'some-hash')
|
||||
.subscribe({
|
||||
error: (err) => {
|
||||
try {
|
||||
await firstValueFrom(
|
||||
implicitFlowCallbackService.authenticatedImplicitFlowCallback(
|
||||
config,
|
||||
[config],
|
||||
'some-hash'
|
||||
)
|
||||
);
|
||||
} catch (err: any) {
|
||||
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
||||
expect(stopPeriodicallyTokenCheckSpy).toHaveBeenCalled();
|
||||
expect(err).toBeTruthy();
|
||||
},
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
it(`navigates to unauthorizedRoute in case of error and in case of error and
|
||||
triggerAuthorizationResultEvent is false`, waitForAsync(() => {
|
||||
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||
spyOn(flowsService, 'processImplicitFlowCallback').and.returnValue(
|
||||
triggerAuthorizationResultEvent is false`, async () => {
|
||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
||||
vi.spyOn(flowsService, 'processImplicitFlowCallback').mockReturnValue(
|
||||
throwError(() => new Error('error'))
|
||||
);
|
||||
const resetSilentRenewRunningSpy = spyOn(
|
||||
const resetSilentRenewRunningSpy = vi.spyOn(
|
||||
flowsDataService,
|
||||
'resetSilentRenewRunning'
|
||||
);
|
||||
const stopPeriodicallTokenCheckSpy = spyOn(
|
||||
const stopPeriodicallTokenCheckSpy = vi.spyOn(
|
||||
intervalService,
|
||||
'stopPeriodicTokenCheck'
|
||||
);
|
||||
const routerSpy = spyOn(router, 'navigateByUrl');
|
||||
const routerSpy = vi.spyOn(router, 'navigateByUrl');
|
||||
const config = {
|
||||
configId: 'configId1',
|
||||
triggerAuthorizationResultEvent: false,
|
||||
unauthorizedRoute: 'unauthorizedRoute',
|
||||
};
|
||||
|
||||
implicitFlowCallbackService
|
||||
.authenticatedImplicitFlowCallback(config, [config], 'some-hash')
|
||||
.subscribe({
|
||||
error: (err) => {
|
||||
try {
|
||||
await firstValueFrom(
|
||||
implicitFlowCallbackService.authenticatedImplicitFlowCallback(
|
||||
config,
|
||||
[config],
|
||||
'some-hash'
|
||||
)
|
||||
);
|
||||
} catch (err: any) {
|
||||
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
||||
expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled();
|
||||
expect(err).toBeTruthy();
|
||||
expect(routerSpy).toHaveBeenCalledOnceWith('unauthorizedRoute');
|
||||
},
|
||||
});
|
||||
}));
|
||||
expect(routerSpy).toHaveBeenCalledExactlyOnceWith('unauthorizedRoute');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { inject, Injectable } from 'injection-js';
|
||||
import { Router } from '@angular/router';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import { type Observable, throwError } from 'rxjs';
|
||||
import { catchError, tap } from 'rxjs/operators';
|
||||
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import { CallbackContext } from '../flows/callback-context';
|
||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import type { CallbackContext } from '../flows/callback-context';
|
||||
import { FlowsDataService } from '../flows/flows-data.service';
|
||||
import { FlowsService } from '../flows/flows.service';
|
||||
import { injectAbstractType } from '../injection';
|
||||
import { AbstractRouter } from '../router';
|
||||
import { IntervalService } from './interval.service';
|
||||
|
||||
@Injectable()
|
||||
export class ImplicitFlowCallbackService {
|
||||
private readonly flowsService = inject(FlowsService);
|
||||
|
||||
private readonly router = inject(Router);
|
||||
private readonly router = injectAbstractType(AbstractRouter);
|
||||
|
||||
private readonly flowsDataService = inject(FlowsDataService);
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { TestBed } from '@/testing';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import { IntervalService } from './interval.service';
|
||||
|
||||
describe('IntervalService', () => {
|
||||
let intervalService: IntervalService;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
IntervalService,
|
||||
{
|
||||
provide: Document,
|
||||
useValue: {
|
||||
@@ -18,10 +21,12 @@ describe('IntervalService', () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
intervalService = TestBed.inject(IntervalService);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
intervalService = TestBed.inject(IntervalService);
|
||||
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
@@ -31,7 +36,7 @@ describe('IntervalService', () => {
|
||||
describe('stopPeriodicTokenCheck', () => {
|
||||
it('calls unsubscribe and sets to null', () => {
|
||||
intervalService.runTokenValidationRunning = new Subscription();
|
||||
const spy = spyOn(
|
||||
const spy = vi.spyOn(
|
||||
intervalService.runTokenValidationRunning,
|
||||
'unsubscribe'
|
||||
);
|
||||
@@ -44,7 +49,7 @@ describe('IntervalService', () => {
|
||||
|
||||
it('does nothing if `runTokenValidationRunning` is null', () => {
|
||||
intervalService.runTokenValidationRunning = new Subscription();
|
||||
const spy = spyOn(
|
||||
const spy = vi.spyOn(
|
||||
intervalService.runTokenValidationRunning,
|
||||
'unsubscribe'
|
||||
);
|
||||
@@ -57,20 +62,20 @@ describe('IntervalService', () => {
|
||||
});
|
||||
|
||||
describe('startPeriodicTokenCheck', () => {
|
||||
it('starts check after correct milliseconds', fakeAsync(() => {
|
||||
it('starts check after correct milliseconds', async () => {
|
||||
const periodicCheck = intervalService.startPeriodicTokenCheck(0.5);
|
||||
const spy = jasmine.createSpy();
|
||||
const spy = vi.fn();
|
||||
const sub = periodicCheck.subscribe(() => {
|
||||
spy();
|
||||
});
|
||||
|
||||
tick(500);
|
||||
await vi.advanceTimersByTimeAsync(500);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
|
||||
tick(500);
|
||||
await vi.advanceTimersByTimeAsync(500);
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
|
||||
sub.unsubscribe();
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Injectable, NgZone, inject } from 'injection-js';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { DOCUMENT } from '../../dom';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import { type Observable, type Subscription, interval } from 'rxjs';
|
||||
import { DOCUMENT } from '../dom';
|
||||
|
||||
@Injectable()
|
||||
export class IntervalService {
|
||||
private readonly zone = inject(NgZone);
|
||||
|
||||
private readonly document = inject(DOCUMENT);
|
||||
|
||||
runTokenValidationRunning: Subscription | null = null;
|
||||
@@ -24,19 +22,6 @@ export class IntervalService {
|
||||
startPeriodicTokenCheck(repeatAfterSeconds: number): Observable<unknown> {
|
||||
const millisecondsDelayBetweenTokenCheck = repeatAfterSeconds * 1000;
|
||||
|
||||
return new Observable((subscriber) => {
|
||||
let intervalId: number | undefined;
|
||||
|
||||
this.zone.runOutsideAngular(() => {
|
||||
intervalId = this.document?.defaultView?.setInterval(
|
||||
() => this.zone.run(() => subscriber.next()),
|
||||
millisecondsDelayBetweenTokenCheck
|
||||
);
|
||||
});
|
||||
|
||||
return (): void => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
});
|
||||
return interval(millisecondsDelayBetweenTokenCheck);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { mockProvider } from '../../test/auto-mock';
|
||||
import { TestBed } from '@/testing';
|
||||
import { ReplaySubject, firstValueFrom, of, share, throwError } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import { AuthStateService } from '../auth-state/auth-state.service';
|
||||
import { ConfigurationService } from '../config/config.service';
|
||||
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import { CallbackContext } from '../flows/callback-context';
|
||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import type { CallbackContext } from '../flows/callback-context';
|
||||
import { FlowsDataService } from '../flows/flows-data.service';
|
||||
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
|
||||
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
|
||||
@@ -12,6 +12,7 @@ import { LoggerService } from '../logging/logger.service';
|
||||
import { EventTypes } from '../public-events/event-types';
|
||||
import { PublicEventsService } from '../public-events/public-events.service';
|
||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||
import { mockProvider } from '../testing/mock';
|
||||
import { UserService } from '../user-data/user.service';
|
||||
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
|
||||
import { IntervalService } from './interval.service';
|
||||
@@ -32,9 +33,11 @@ describe('PeriodicallyTokenCheckService', () => {
|
||||
let publicEventsService: PublicEventsService;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
TestBed.configureTestingModule({
|
||||
imports: [],
|
||||
providers: [
|
||||
PeriodicallyTokenCheckService,
|
||||
mockProvider(ResetAuthDataService),
|
||||
FlowHelper,
|
||||
mockProvider(FlowsDataService),
|
||||
@@ -49,9 +52,6 @@ describe('PeriodicallyTokenCheckService', () => {
|
||||
mockProvider(ConfigurationService),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
periodicallyTokenCheckService = TestBed.inject(
|
||||
PeriodicallyTokenCheckService
|
||||
);
|
||||
@@ -68,14 +68,18 @@ describe('PeriodicallyTokenCheckService', () => {
|
||||
publicEventsService = TestBed.inject(PublicEventsService);
|
||||
configurationService = TestBed.inject(ConfigurationService);
|
||||
|
||||
spyOn(intervalService, 'startPeriodicTokenCheck').and.returnValue(of(null));
|
||||
vi.spyOn(intervalService, 'startPeriodicTokenCheck').mockReturnValue(
|
||||
of(null)
|
||||
);
|
||||
});
|
||||
|
||||
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
|
||||
afterEach(() => {
|
||||
if (!!intervalService.runTokenValidationRunning?.unsubscribe) {
|
||||
if (intervalService?.runTokenValidationRunning?.unsubscribe) {
|
||||
intervalService.runTokenValidationRunning.unsubscribe();
|
||||
intervalService.runTokenValidationRunning = null;
|
||||
}
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
@@ -83,164 +87,200 @@ describe('PeriodicallyTokenCheckService', () => {
|
||||
});
|
||||
|
||||
describe('startTokenValidationPeriodically', () => {
|
||||
it('returns if no config has silentrenew enabled', waitForAsync(() => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('returns if no config has silentrenew enabled', async () => {
|
||||
const configs = [
|
||||
{ silentRenew: false, configId: 'configId1' },
|
||||
{ silentRenew: false, configId: 'configId2' },
|
||||
];
|
||||
|
||||
const result =
|
||||
const result = await firstValueFrom(
|
||||
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
||||
configs,
|
||||
configs[0]
|
||||
configs[0]!
|
||||
)
|
||||
);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
}));
|
||||
});
|
||||
|
||||
it('returns if runTokenValidationRunning', waitForAsync(() => {
|
||||
it('returns if runTokenValidationRunning', async () => {
|
||||
const configs = [{ silentRenew: true, configId: 'configId1' }];
|
||||
|
||||
spyOn(intervalService, 'isTokenValidationRunning').and.returnValue(true);
|
||||
vi.spyOn(intervalService, 'isTokenValidationRunning').mockReturnValue(
|
||||
true
|
||||
);
|
||||
|
||||
const result =
|
||||
const result = await firstValueFrom(
|
||||
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
||||
configs,
|
||||
configs[0]
|
||||
configs[0]!
|
||||
)
|
||||
);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
}));
|
||||
});
|
||||
|
||||
it('interval calls resetSilentRenewRunning when current flow is CodeFlowWithRefreshTokens', fakeAsync(() => {
|
||||
it('interval calls resetSilentRenewRunning when current flow is CodeFlowWithRefreshTokens', async () => {
|
||||
const configs = [
|
||||
{ silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 },
|
||||
];
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
periodicallyTokenCheckService as any,
|
||||
'shouldStartPeriodicallyCheckForConfig'
|
||||
).and.returnValue(true);
|
||||
const isCurrentFlowCodeFlowWithRefreshTokensSpy = spyOn(
|
||||
flowHelper,
|
||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||
).and.returnValue(true);
|
||||
const resetSilentRenewRunningSpy = spyOn(
|
||||
).mockReturnValue(true);
|
||||
const isCurrentFlowCodeFlowWithRefreshTokensSpy = vi
|
||||
.spyOn(flowHelper, 'isCurrentFlowCodeFlowWithRefreshTokens')
|
||||
.mockReturnValue(true);
|
||||
const resetSilentRenewRunningSpy = vi.spyOn(
|
||||
flowsDataService,
|
||||
'resetSilentRenewRunning'
|
||||
);
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
refreshSessionRefreshTokenService,
|
||||
'refreshSessionWithRefreshTokens'
|
||||
).and.returnValue(of({} as CallbackContext));
|
||||
spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue(
|
||||
of(configs[0])
|
||||
).mockReturnValue(of({} as CallbackContext));
|
||||
vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue(
|
||||
of(configs[0]!)
|
||||
);
|
||||
|
||||
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
||||
configs,
|
||||
configs[0]
|
||||
configs[0]!
|
||||
);
|
||||
|
||||
tick(1000);
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
|
||||
intervalService.runTokenValidationRunning?.unsubscribe();
|
||||
intervalService.runTokenValidationRunning = null;
|
||||
expect(isCurrentFlowCodeFlowWithRefreshTokensSpy).toHaveBeenCalled();
|
||||
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
|
||||
it('interval calls resetSilentRenewRunning in case of error when current flow is CodeFlowWithRefreshTokens', fakeAsync(() => {
|
||||
it('interval calls resetSilentRenewRunning in case of error when current flow is CodeFlowWithRefreshTokens', async () => {
|
||||
const configs = [
|
||||
{ silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 },
|
||||
];
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
periodicallyTokenCheckService as any,
|
||||
'shouldStartPeriodicallyCheckForConfig'
|
||||
).and.returnValue(true);
|
||||
const resetSilentRenewRunning = spyOn(
|
||||
).mockReturnValue(true);
|
||||
const resetSilentRenewRunning = vi.spyOn(
|
||||
flowsDataService,
|
||||
'resetSilentRenewRunning'
|
||||
);
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
flowHelper,
|
||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||
).and.returnValue(true);
|
||||
spyOn(
|
||||
).mockReturnValue(true);
|
||||
vi.spyOn(
|
||||
refreshSessionRefreshTokenService,
|
||||
'refreshSessionWithRefreshTokens'
|
||||
).and.returnValue(throwError(() => new Error('error')));
|
||||
spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue(
|
||||
of(configs[0])
|
||||
).mockReturnValue(throwError(() => new Error('error')));
|
||||
vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue(
|
||||
of(configs[0]!)
|
||||
);
|
||||
|
||||
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
||||
configs,
|
||||
try {
|
||||
const test$ = periodicallyTokenCheckService
|
||||
.startTokenValidationPeriodically(configs, configs[0]!)
|
||||
.pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(1),
|
||||
resetOnError: false,
|
||||
resetOnComplete: false,
|
||||
resetOnRefCountZero: true,
|
||||
})
|
||||
);
|
||||
|
||||
test$.subscribe();
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
|
||||
await firstValueFrom(test$);
|
||||
expect.fail('should throw errror');
|
||||
} catch {
|
||||
expect(resetSilentRenewRunning).toHaveBeenCalledExactlyOnceWith(
|
||||
configs[0]
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
tick(1000);
|
||||
|
||||
expect(
|
||||
periodicallyTokenCheckService.startTokenValidationPeriodically
|
||||
).toThrowError();
|
||||
expect(resetSilentRenewRunning).toHaveBeenCalledOnceWith(configs[0]);
|
||||
}));
|
||||
|
||||
it('interval throws silent renew failed event with data in case of an error', fakeAsync(() => {
|
||||
it('interval throws silent renew failed event with data in case of an error', async () => {
|
||||
const configs = [
|
||||
{ silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 },
|
||||
];
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
periodicallyTokenCheckService as any,
|
||||
'shouldStartPeriodicallyCheckForConfig'
|
||||
).and.returnValue(true);
|
||||
spyOn(flowsDataService, 'resetSilentRenewRunning');
|
||||
const publicEventsServiceSpy = spyOn(publicEventsService, 'fireEvent');
|
||||
).mockReturnValue(true);
|
||||
vi.spyOn(flowsDataService, 'resetSilentRenewRunning');
|
||||
const publicEventsServiceSpy = vi.spyOn(publicEventsService, 'fireEvent');
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
flowHelper,
|
||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||
).and.returnValue(true);
|
||||
spyOn(
|
||||
).mockReturnValue(true);
|
||||
vi.spyOn(
|
||||
refreshSessionRefreshTokenService,
|
||||
'refreshSessionWithRefreshTokens'
|
||||
).and.returnValue(throwError(() => new Error('error')));
|
||||
spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue(
|
||||
of(configs[0])
|
||||
).mockReturnValue(throwError(() => new Error('error')));
|
||||
vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue(
|
||||
of(configs[0]!)
|
||||
);
|
||||
|
||||
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
||||
configs,
|
||||
configs[0]
|
||||
try {
|
||||
const test$ = periodicallyTokenCheckService
|
||||
.startTokenValidationPeriodically(configs, configs[0]!)
|
||||
.pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(1),
|
||||
resetOnComplete: false,
|
||||
resetOnError: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
|
||||
tick(1000);
|
||||
test$.subscribe();
|
||||
|
||||
expect(
|
||||
periodicallyTokenCheckService.startTokenValidationPeriodically
|
||||
).toThrowError();
|
||||
expect(publicEventsServiceSpy.calls.allArgs()).toEqual([
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
|
||||
await firstValueFrom(test$);
|
||||
} catch {
|
||||
expect(publicEventsServiceSpy.mock.calls).toEqual([
|
||||
[EventTypes.SilentRenewStarted],
|
||||
[EventTypes.SilentRenewFailed, new Error('error')],
|
||||
]);
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
it('calls resetAuthorizationData and returns if no silent renew is configured', fakeAsync(() => {
|
||||
it('calls resetAuthorizationData and returns if no silent renew is configured', async () => {
|
||||
const configs = [
|
||||
{ silentRenew: true, configId: 'configId1', tokenRefreshInSeconds: 1 },
|
||||
];
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
periodicallyTokenCheckService as any,
|
||||
'shouldStartPeriodicallyCheckForConfig'
|
||||
).and.returnValue(true);
|
||||
).mockReturnValue(true);
|
||||
|
||||
const configSpy = spyOn(configurationService, 'getOpenIDConfiguration');
|
||||
const configSpy = vi.spyOn(
|
||||
configurationService,
|
||||
'getOpenIDConfiguration'
|
||||
);
|
||||
const configWithoutSilentRenew = {
|
||||
silentRenew: false,
|
||||
configId: 'configId1',
|
||||
@@ -248,68 +288,70 @@ describe('PeriodicallyTokenCheckService', () => {
|
||||
};
|
||||
const configWithoutSilentRenew$ = of(configWithoutSilentRenew);
|
||||
|
||||
configSpy.and.returnValue(configWithoutSilentRenew$);
|
||||
configSpy.mockReturnValue(configWithoutSilentRenew$);
|
||||
|
||||
const resetAuthorizationDataSpy = spyOn(
|
||||
const resetAuthorizationDataSpy = vi.spyOn(
|
||||
resetAuthDataService,
|
||||
'resetAuthorizationData'
|
||||
);
|
||||
|
||||
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
||||
configs,
|
||||
configs[0]
|
||||
configs[0]!
|
||||
);
|
||||
tick(1000);
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
intervalService.runTokenValidationRunning?.unsubscribe();
|
||||
intervalService.runTokenValidationRunning = null;
|
||||
|
||||
expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1);
|
||||
expect(resetAuthorizationDataSpy).toHaveBeenCalledOnceWith(
|
||||
expect(resetAuthorizationDataSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
configWithoutSilentRenew,
|
||||
configs
|
||||
);
|
||||
}));
|
||||
});
|
||||
|
||||
it('calls refreshSessionWithRefreshTokens if current flow is Code flow with refresh tokens', fakeAsync(() => {
|
||||
spyOn(
|
||||
it('calls refreshSessionWithRefreshTokens if current flow is Code flow with refresh tokens', async () => {
|
||||
vi.spyOn(
|
||||
flowHelper,
|
||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||
).and.returnValue(true);
|
||||
spyOn(
|
||||
).mockReturnValue(true);
|
||||
vi.spyOn(
|
||||
periodicallyTokenCheckService as any,
|
||||
'shouldStartPeriodicallyCheckForConfig'
|
||||
).and.returnValue(true);
|
||||
spyOn(storagePersistenceService, 'read').and.returnValue({});
|
||||
).mockReturnValue(true);
|
||||
vi.spyOn(storagePersistenceService, 'read').mockReturnValue({});
|
||||
const configs = [
|
||||
{ configId: 'configId1', silentRenew: true, tokenRefreshInSeconds: 1 },
|
||||
];
|
||||
|
||||
spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue(
|
||||
vi.spyOn(configurationService, 'getOpenIDConfiguration').mockReturnValue(
|
||||
of(configs[0] as OpenIdConfiguration)
|
||||
);
|
||||
const refreshSessionWithRefreshTokensSpy = spyOn(
|
||||
const refreshSessionWithRefreshTokensSpy = vi
|
||||
.spyOn(
|
||||
refreshSessionRefreshTokenService,
|
||||
'refreshSessionWithRefreshTokens'
|
||||
).and.returnValue(of({} as CallbackContext));
|
||||
)
|
||||
.mockReturnValue(of({} as CallbackContext));
|
||||
|
||||
periodicallyTokenCheckService.startTokenValidationPeriodically(
|
||||
configs,
|
||||
configs[0]
|
||||
configs[0]!
|
||||
);
|
||||
|
||||
tick(1000);
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
|
||||
intervalService.runTokenValidationRunning?.unsubscribe();
|
||||
intervalService.runTokenValidationRunning = null;
|
||||
expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('shouldStartPeriodicallyCheckForConfig', () => {
|
||||
it('returns false when there is no IdToken', () => {
|
||||
spyOn(authStateService, 'getIdToken').and.returnValue('');
|
||||
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||
spyOn(userService, 'getUserDataFromStore').and.returnValue(
|
||||
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('');
|
||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
||||
vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(
|
||||
'some-userdata'
|
||||
);
|
||||
|
||||
@@ -317,13 +359,13 @@ describe('PeriodicallyTokenCheckService', () => {
|
||||
periodicallyTokenCheckService as any
|
||||
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
||||
|
||||
expect(result).toBeFalse();
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns false when silent renew is running', () => {
|
||||
spyOn(authStateService, 'getIdToken').and.returnValue('idToken');
|
||||
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true);
|
||||
spyOn(userService, 'getUserDataFromStore').and.returnValue(
|
||||
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken');
|
||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
|
||||
vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(
|
||||
'some-userdata'
|
||||
);
|
||||
|
||||
@@ -331,14 +373,14 @@ describe('PeriodicallyTokenCheckService', () => {
|
||||
periodicallyTokenCheckService as any
|
||||
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
||||
|
||||
expect(result).toBeFalse();
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns false when code flow is in progress', () => {
|
||||
spyOn(authStateService, 'getIdToken').and.returnValue('idToken');
|
||||
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||
spyOn(flowsDataService, 'isCodeFlowInProgress').and.returnValue(true);
|
||||
spyOn(userService, 'getUserDataFromStore').and.returnValue(
|
||||
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken');
|
||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
||||
vi.spyOn(flowsDataService, 'isCodeFlowInProgress').mockReturnValue(true);
|
||||
vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(
|
||||
'some-userdata'
|
||||
);
|
||||
|
||||
@@ -346,87 +388,87 @@ describe('PeriodicallyTokenCheckService', () => {
|
||||
periodicallyTokenCheckService as any
|
||||
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
||||
|
||||
expect(result).toBeFalse();
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns false when there is no userdata from the store', () => {
|
||||
spyOn(authStateService, 'getIdToken').and.returnValue('idToken');
|
||||
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true);
|
||||
spyOn(userService, 'getUserDataFromStore').and.returnValue(null);
|
||||
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken');
|
||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
|
||||
vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(null);
|
||||
|
||||
const result = (
|
||||
periodicallyTokenCheckService as any
|
||||
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
||||
|
||||
expect(result).toBeFalse();
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns true when there is userDataFromStore, silentrenew is not running and there is an idtoken', () => {
|
||||
spyOn(authStateService, 'getIdToken').and.returnValue('idToken');
|
||||
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||
spyOn(userService, 'getUserDataFromStore').and.returnValue(
|
||||
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken');
|
||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
||||
vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(
|
||||
'some-userdata'
|
||||
);
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
authStateService,
|
||||
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
||||
).and.returnValue(true);
|
||||
spyOn(
|
||||
).mockReturnValue(true);
|
||||
vi.spyOn(
|
||||
authStateService,
|
||||
'hasAccessTokenExpiredIfExpiryExists'
|
||||
).and.returnValue(true);
|
||||
).mockReturnValue(true);
|
||||
|
||||
const result = (
|
||||
periodicallyTokenCheckService as any
|
||||
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
||||
|
||||
expect(result).toBeTrue();
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
|
||||
it('returns false if tokens are not expired', () => {
|
||||
spyOn(authStateService, 'getIdToken').and.returnValue('idToken');
|
||||
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||
spyOn(userService, 'getUserDataFromStore').and.returnValue(
|
||||
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken');
|
||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
||||
vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(
|
||||
'some-userdata'
|
||||
);
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
authStateService,
|
||||
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
||||
).and.returnValue(false);
|
||||
spyOn(
|
||||
).mockReturnValue(false);
|
||||
vi.spyOn(
|
||||
authStateService,
|
||||
'hasAccessTokenExpiredIfExpiryExists'
|
||||
).and.returnValue(false);
|
||||
).mockReturnValue(false);
|
||||
|
||||
const result = (
|
||||
periodicallyTokenCheckService as any
|
||||
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
||||
|
||||
expect(result).toBeFalse();
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns true if tokens are expired', () => {
|
||||
spyOn(authStateService, 'getIdToken').and.returnValue('idToken');
|
||||
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||
spyOn(userService, 'getUserDataFromStore').and.returnValue(
|
||||
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idToken');
|
||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
||||
vi.spyOn(userService, 'getUserDataFromStore').mockReturnValue(
|
||||
'some-userdata'
|
||||
);
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
authStateService,
|
||||
'hasIdTokenExpiredAndRenewCheckIsEnabled'
|
||||
).and.returnValue(true);
|
||||
spyOn(
|
||||
).mockReturnValue(true);
|
||||
vi.spyOn(
|
||||
authStateService,
|
||||
'hasAccessTokenExpiredIfExpiryExists'
|
||||
).and.returnValue(true);
|
||||
).mockReturnValue(true);
|
||||
|
||||
const result = (
|
||||
periodicallyTokenCheckService as any
|
||||
).shouldStartPeriodicallyCheckForConfig({ configId: 'configId1' });
|
||||
|
||||
expect(result).toBeTrue();
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { inject, Injectable } from 'injection-js';
|
||||
import { forkJoin, Observable, of, throwError } from 'rxjs';
|
||||
import { catchError, switchMap } from 'rxjs/operators';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import { type Observable, ReplaySubject, forkJoin, of, throwError } from 'rxjs';
|
||||
import { catchError, map, share, switchMap } from 'rxjs/operators';
|
||||
import { AuthStateService } from '../auth-state/auth-state.service';
|
||||
import { ConfigurationService } from '../config/config.service';
|
||||
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import { CallbackContext } from '../flows/callback-context';
|
||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import type { CallbackContext } from '../flows/callback-context';
|
||||
import { FlowsDataService } from '../flows/flows-data.service';
|
||||
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
|
||||
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
|
||||
@@ -52,16 +52,16 @@ export class PeriodicallyTokenCheckService {
|
||||
startTokenValidationPeriodically(
|
||||
allConfigs: OpenIdConfiguration[],
|
||||
currentConfig: OpenIdConfiguration
|
||||
): void {
|
||||
): Observable<undefined> {
|
||||
const configsWithSilentRenewEnabled =
|
||||
this.getConfigsWithSilentRenewEnabled(allConfigs);
|
||||
|
||||
if (configsWithSilentRenewEnabled.length <= 0) {
|
||||
return;
|
||||
return of(undefined);
|
||||
}
|
||||
|
||||
if (this.intervalService.isTokenValidationRunning()) {
|
||||
return;
|
||||
return of(undefined);
|
||||
}
|
||||
|
||||
const refreshTimeInSeconds = this.getSmallestRefreshTimeFromConfigs(
|
||||
@@ -75,21 +75,27 @@ export class PeriodicallyTokenCheckService {
|
||||
[id: string]: Observable<boolean | CallbackContext | null>;
|
||||
} = {};
|
||||
|
||||
configsWithSilentRenewEnabled.forEach((config) => {
|
||||
for (const config of configsWithSilentRenewEnabled) {
|
||||
const identifier = config.configId as string;
|
||||
const refreshEvent = this.getRefreshEvent(config, allConfigs);
|
||||
|
||||
objectWithConfigIdsAndRefreshEvent[identifier] = refreshEvent;
|
||||
});
|
||||
}
|
||||
|
||||
return forkJoin(objectWithConfigIdsAndRefreshEvent);
|
||||
})
|
||||
);
|
||||
|
||||
this.intervalService.runTokenValidationRunning = periodicallyCheck$
|
||||
.pipe(catchError((error) => throwError(() => new Error(error))))
|
||||
.subscribe({
|
||||
next: (objectWithConfigIds) => {
|
||||
const o$ = periodicallyCheck$.pipe(
|
||||
catchError((error) => {
|
||||
this.loggerService.logError(
|
||||
currentConfig,
|
||||
'silent renew failed!',
|
||||
error
|
||||
);
|
||||
return throwError(() => error);
|
||||
}),
|
||||
map((objectWithConfigIds) => {
|
||||
for (const [configId, _] of Object.entries(objectWithConfigIds)) {
|
||||
this.configurationService
|
||||
.getOpenIDConfiguration(configId)
|
||||
@@ -105,16 +111,20 @@ export class PeriodicallyTokenCheckService {
|
||||
this.flowsDataService.resetSilentRenewRunning(config);
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
error: (error) => {
|
||||
this.loggerService.logError(
|
||||
currentConfig,
|
||||
'silent renew failed!',
|
||||
error
|
||||
}),
|
||||
share({
|
||||
connector: () => new ReplaySubject(1),
|
||||
resetOnError: false,
|
||||
resetOnComplete: false,
|
||||
resetOnRefCountZero: false,
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
this.intervalService.runTokenValidationRunning = o$.subscribe({});
|
||||
|
||||
return o$;
|
||||
}
|
||||
|
||||
private getRefreshEvent(
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { mockProvider } from '../../test/auto-mock';
|
||||
import { CallbackContext } from '../flows/callback-context';
|
||||
import { TestBed } from '@/testing';
|
||||
import { firstValueFrom, of, throwError } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import type { CallbackContext } from '../flows/callback-context';
|
||||
import { FlowsService } from '../flows/flows.service';
|
||||
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
|
||||
import { LoggerService } from '../logging/logger.service';
|
||||
import { mockProvider } from '../testing/mock';
|
||||
import { IntervalService } from './interval.service';
|
||||
import { RefreshSessionRefreshTokenService } from './refresh-session-refresh-token.service';
|
||||
|
||||
@@ -15,6 +16,7 @@ describe('RefreshSessionRefreshTokenService', () => {
|
||||
let flowsService: FlowsService;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
TestBed.configureTestingModule({
|
||||
imports: [],
|
||||
providers: [
|
||||
@@ -25,9 +27,6 @@ describe('RefreshSessionRefreshTokenService', () => {
|
||||
mockProvider(IntervalService),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
flowsService = TestBed.inject(FlowsService);
|
||||
refreshSessionRefreshTokenService = TestBed.inject(
|
||||
RefreshSessionRefreshTokenService
|
||||
@@ -36,66 +35,73 @@ describe('RefreshSessionRefreshTokenService', () => {
|
||||
resetAuthDataService = TestBed.inject(ResetAuthDataService);
|
||||
});
|
||||
|
||||
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(refreshSessionRefreshTokenService).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('refreshSessionWithRefreshTokens', () => {
|
||||
it('calls flowsService.processRefreshToken()', waitForAsync(() => {
|
||||
const spy = spyOn(flowsService, 'processRefreshToken').and.returnValue(
|
||||
of({} as CallbackContext)
|
||||
);
|
||||
it('calls flowsService.processRefreshToken()', async () => {
|
||||
const spy = vi
|
||||
.spyOn(flowsService, 'processRefreshToken')
|
||||
.mockReturnValue(of({} as CallbackContext));
|
||||
|
||||
refreshSessionRefreshTokenService
|
||||
.refreshSessionWithRefreshTokens({ configId: 'configId1' }, [
|
||||
await firstValueFrom(
|
||||
refreshSessionRefreshTokenService.refreshSessionWithRefreshTokens(
|
||||
{ configId: 'configId1' },
|
||||
])
|
||||
.subscribe(() => {
|
||||
[{ configId: 'configId1' }]
|
||||
)
|
||||
);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('resetAuthorizationData in case of error', waitForAsync(() => {
|
||||
spyOn(flowsService, 'processRefreshToken').and.returnValue(
|
||||
it('resetAuthorizationData in case of error', async () => {
|
||||
vi.spyOn(flowsService, 'processRefreshToken').mockReturnValue(
|
||||
throwError(() => new Error('error'))
|
||||
);
|
||||
const resetSilentRenewRunningSpy = spyOn(
|
||||
const resetSilentRenewRunningSpy = vi.spyOn(
|
||||
resetAuthDataService,
|
||||
'resetAuthorizationData'
|
||||
);
|
||||
|
||||
refreshSessionRefreshTokenService
|
||||
.refreshSessionWithRefreshTokens({ configId: 'configId1' }, [
|
||||
try {
|
||||
await firstValueFrom(
|
||||
refreshSessionRefreshTokenService.refreshSessionWithRefreshTokens(
|
||||
{ configId: 'configId1' },
|
||||
])
|
||||
.subscribe({
|
||||
error: (err) => {
|
||||
[{ configId: 'configId1' }]
|
||||
)
|
||||
);
|
||||
} catch (err: any) {
|
||||
expect(resetSilentRenewRunningSpy).toHaveBeenCalled();
|
||||
expect(err).toBeTruthy();
|
||||
},
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
it('finalize with stopPeriodicTokenCheck in case of error', fakeAsync(() => {
|
||||
spyOn(flowsService, 'processRefreshToken').and.returnValue(
|
||||
it('finalize with stopPeriodicTokenCheck in case of error', async () => {
|
||||
vi.spyOn(flowsService, 'processRefreshToken').mockReturnValue(
|
||||
throwError(() => new Error('error'))
|
||||
);
|
||||
const stopPeriodicallyTokenCheckSpy = spyOn(
|
||||
const stopPeriodicallyTokenCheckSpy = vi.spyOn(
|
||||
intervalService,
|
||||
'stopPeriodicTokenCheck'
|
||||
);
|
||||
|
||||
refreshSessionRefreshTokenService
|
||||
.refreshSessionWithRefreshTokens({ configId: 'configId1' }, [
|
||||
try {
|
||||
await firstValueFrom(
|
||||
refreshSessionRefreshTokenService.refreshSessionWithRefreshTokens(
|
||||
{ configId: 'configId1' },
|
||||
])
|
||||
.subscribe({
|
||||
error: (err) => {
|
||||
[{ configId: 'configId1' }]
|
||||
)
|
||||
);
|
||||
} catch (err: any) {
|
||||
expect(err).toBeTruthy();
|
||||
},
|
||||
});
|
||||
tick();
|
||||
}
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
expect(stopPeriodicallyTokenCheckSpy).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { inject, Injectable } from 'injection-js';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import { type Observable, throwError } from 'rxjs';
|
||||
import { catchError, finalize } from 'rxjs/operators';
|
||||
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import { CallbackContext } from '../flows/callback-context';
|
||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import type { CallbackContext } from '../flows/callback-context';
|
||||
import { FlowsService } from '../flows/flows.service';
|
||||
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
|
||||
import { LoggerService } from '../logging/logger.service';
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { delay } from 'rxjs/operators';
|
||||
import { mockProvider } from '../../test/auto-mock';
|
||||
import { TestBed, spyOnProperty } from '@/testing';
|
||||
import {
|
||||
EmptyError,
|
||||
ReplaySubject,
|
||||
firstValueFrom,
|
||||
of,
|
||||
throwError,
|
||||
} from 'rxjs';
|
||||
import { delay, share } from 'rxjs/operators';
|
||||
import { vi } from 'vitest';
|
||||
import { AuthStateService } from '../auth-state/auth-state.service';
|
||||
import { AuthWellKnownService } from '../config/auth-well-known/auth-well-known.service';
|
||||
import { CallbackContext } from '../flows/callback-context';
|
||||
import type { CallbackContext } from '../flows/callback-context';
|
||||
import { FlowsDataService } from '../flows/flows-data.service';
|
||||
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
|
||||
import { SilentRenewService } from '../iframe/silent-renew.service';
|
||||
import { LoggerService } from '../logging/logger.service';
|
||||
import { LoginResponse } from '../login/login-response';
|
||||
import type { LoginResponse } from '../login/login-response';
|
||||
import { PublicEventsService } from '../public-events/public-events.service';
|
||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||
import { mockProvider } from '../testing/mock';
|
||||
import { UserService } from '../user-data/user.service';
|
||||
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
|
||||
import { RefreshSessionRefreshTokenService } from './refresh-session-refresh-token.service';
|
||||
@@ -21,6 +28,7 @@ import {
|
||||
} from './refresh-session.service';
|
||||
|
||||
describe('RefreshSessionService ', () => {
|
||||
vi.useFakeTimers();
|
||||
let refreshSessionService: RefreshSessionService;
|
||||
let flowHelper: FlowHelper;
|
||||
let authStateService: AuthStateService;
|
||||
@@ -49,9 +57,6 @@ describe('RefreshSessionService ', () => {
|
||||
mockProvider(PublicEventsService),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
refreshSessionService = TestBed.inject(RefreshSessionService);
|
||||
flowsDataService = TestBed.inject(FlowsDataService);
|
||||
flowHelper = TestBed.inject(FlowHelper);
|
||||
@@ -65,24 +70,29 @@ describe('RefreshSessionService ', () => {
|
||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||
});
|
||||
|
||||
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(refreshSessionService).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('userForceRefreshSession', () => {
|
||||
it('should persist params refresh when extra custom params given and useRefreshToken is true', waitForAsync(() => {
|
||||
spyOn(
|
||||
it('should persist params refresh when extra custom params given and useRefreshToken is true', async () => {
|
||||
vi.spyOn(
|
||||
flowHelper,
|
||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||
).and.returnValue(true);
|
||||
spyOn(
|
||||
).mockReturnValue(true);
|
||||
vi.spyOn(
|
||||
refreshSessionService as any,
|
||||
'startRefreshSession'
|
||||
).and.returnValue(of(null));
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
).mockReturnValue(of(null));
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
true
|
||||
);
|
||||
const writeSpy = spyOn(storagePersistenceService, 'write');
|
||||
const writeSpy = vi.spyOn(storagePersistenceService, 'write');
|
||||
const allConfigs = [
|
||||
{
|
||||
configId: 'configId1',
|
||||
@@ -93,27 +103,30 @@ describe('RefreshSessionService ', () => {
|
||||
|
||||
const extraCustomParams = { extra: 'custom' };
|
||||
|
||||
refreshSessionService
|
||||
.userForceRefreshSession(allConfigs[0], allConfigs, extraCustomParams)
|
||||
.subscribe(() => {
|
||||
expect(writeSpy).toHaveBeenCalledOnceWith(
|
||||
await firstValueFrom(
|
||||
refreshSessionService.userForceRefreshSession(
|
||||
allConfigs[0]!,
|
||||
allConfigs,
|
||||
extraCustomParams
|
||||
)
|
||||
);
|
||||
expect(writeSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
'storageCustomParamsRefresh',
|
||||
extraCustomParams,
|
||||
allConfigs[0]
|
||||
);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should persist storageCustomParamsAuthRequest when extra custom params given and useRefreshToken is false', waitForAsync(() => {
|
||||
spyOn(
|
||||
it('should persist storageCustomParamsAuthRequest when extra custom params given and useRefreshToken is false', async () => {
|
||||
vi.spyOn(
|
||||
flowHelper,
|
||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||
).and.returnValue(true);
|
||||
spyOn(
|
||||
).mockReturnValue(true);
|
||||
vi.spyOn(
|
||||
refreshSessionService as any,
|
||||
'startRefreshSession'
|
||||
).and.returnValue(of(null));
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
).mockReturnValue(of(null));
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
true
|
||||
);
|
||||
const allConfigs = [
|
||||
@@ -123,31 +136,34 @@ describe('RefreshSessionService ', () => {
|
||||
silentRenewTimeoutInSeconds: 10,
|
||||
},
|
||||
];
|
||||
const writeSpy = spyOn(storagePersistenceService, 'write');
|
||||
const writeSpy = vi.spyOn(storagePersistenceService, 'write');
|
||||
|
||||
const extraCustomParams = { extra: 'custom' };
|
||||
|
||||
refreshSessionService
|
||||
.userForceRefreshSession(allConfigs[0], allConfigs, extraCustomParams)
|
||||
.subscribe(() => {
|
||||
expect(writeSpy).toHaveBeenCalledOnceWith(
|
||||
await firstValueFrom(
|
||||
refreshSessionService.userForceRefreshSession(
|
||||
allConfigs[0]!,
|
||||
allConfigs,
|
||||
extraCustomParams
|
||||
)
|
||||
);
|
||||
expect(writeSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
'storageCustomParamsAuthRequest',
|
||||
extraCustomParams,
|
||||
allConfigs[0]
|
||||
);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should NOT persist customparams if no customparams are given', waitForAsync(() => {
|
||||
spyOn(
|
||||
it('should NOT persist customparams if no customparams are given', async () => {
|
||||
vi.spyOn(
|
||||
flowHelper,
|
||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||
).and.returnValue(true);
|
||||
spyOn(
|
||||
).mockReturnValue(true);
|
||||
vi.spyOn(
|
||||
refreshSessionService as any,
|
||||
'startRefreshSession'
|
||||
).and.returnValue(of(null));
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
).mockReturnValue(of(null));
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
true
|
||||
);
|
||||
const allConfigs = [
|
||||
@@ -157,20 +173,22 @@ describe('RefreshSessionService ', () => {
|
||||
silentRenewTimeoutInSeconds: 10,
|
||||
},
|
||||
];
|
||||
const writeSpy = spyOn(storagePersistenceService, 'write');
|
||||
const writeSpy = vi.spyOn(storagePersistenceService, 'write');
|
||||
|
||||
refreshSessionService
|
||||
.userForceRefreshSession(allConfigs[0], allConfigs)
|
||||
.subscribe(() => {
|
||||
await firstValueFrom(
|
||||
refreshSessionService.userForceRefreshSession(
|
||||
allConfigs[0]!,
|
||||
allConfigs
|
||||
)
|
||||
);
|
||||
expect(writeSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should call resetSilentRenewRunning in case of an error', waitForAsync(() => {
|
||||
spyOn(refreshSessionService, 'forceRefreshSession').and.returnValue(
|
||||
it('should call resetSilentRenewRunning in case of an error', async () => {
|
||||
vi.spyOn(refreshSessionService, 'forceRefreshSession').mockReturnValue(
|
||||
throwError(() => new Error('error'))
|
||||
);
|
||||
spyOn(flowsDataService, 'resetSilentRenewRunning');
|
||||
vi.spyOn(flowsDataService, 'resetSilentRenewRunning');
|
||||
const allConfigs = [
|
||||
{
|
||||
configId: 'configId1',
|
||||
@@ -179,28 +197,29 @@ describe('RefreshSessionService ', () => {
|
||||
},
|
||||
];
|
||||
|
||||
refreshSessionService
|
||||
.userForceRefreshSession(allConfigs[0], allConfigs)
|
||||
.subscribe({
|
||||
next: () => {
|
||||
fail('It should not return any result.');
|
||||
},
|
||||
error: (error) => {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
},
|
||||
complete: () => {
|
||||
try {
|
||||
const result = await firstValueFrom(
|
||||
refreshSessionService.userForceRefreshSession(
|
||||
allConfigs[0]!,
|
||||
allConfigs
|
||||
)
|
||||
);
|
||||
if (result) {
|
||||
expect.fail('It should not return any result.');
|
||||
} else {
|
||||
expect(
|
||||
flowsDataService.resetSilentRenewRunning
|
||||
).toHaveBeenCalledOnceWith(allConfigs[0]);
|
||||
},
|
||||
).toHaveBeenCalledExactlyOnceWith(allConfigs[0]);
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
it('should call resetSilentRenewRunning in case of no error', waitForAsync(() => {
|
||||
spyOn(refreshSessionService, 'forceRefreshSession').and.returnValue(
|
||||
it('should call resetSilentRenewRunning in case of no error', async () => {
|
||||
vi.spyOn(refreshSessionService, 'forceRefreshSession').mockReturnValue(
|
||||
of({} as LoginResponse)
|
||||
);
|
||||
spyOn(flowsDataService, 'resetSilentRenewRunning');
|
||||
vi.spyOn(flowsDataService, 'resetSilentRenewRunning');
|
||||
const allConfigs = [
|
||||
{
|
||||
configId: 'configId1',
|
||||
@@ -209,36 +228,42 @@ describe('RefreshSessionService ', () => {
|
||||
},
|
||||
];
|
||||
|
||||
refreshSessionService
|
||||
.userForceRefreshSession(allConfigs[0], allConfigs)
|
||||
.subscribe({
|
||||
error: () => {
|
||||
fail('It should not return any error.');
|
||||
},
|
||||
complete: () => {
|
||||
try {
|
||||
await firstValueFrom(
|
||||
refreshSessionService.userForceRefreshSession(
|
||||
allConfigs[0]!,
|
||||
allConfigs
|
||||
)
|
||||
);
|
||||
} catch (err: any) {
|
||||
if (err instanceof EmptyError) {
|
||||
expect(
|
||||
flowsDataService.resetSilentRenewRunning
|
||||
).toHaveBeenCalledOnceWith(allConfigs[0]);
|
||||
},
|
||||
).toHaveBeenCalledExactlyOnceWith(allConfigs[0]);
|
||||
} else {
|
||||
expect.fail('It should not return any error.');
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('forceRefreshSession', () => {
|
||||
it('only calls start refresh session and returns idToken and accessToken if auth is true', waitForAsync(() => {
|
||||
spyOn(
|
||||
it('only calls start refresh session and returns idToken and accessToken if auth is true', async () => {
|
||||
vi.spyOn(
|
||||
flowHelper,
|
||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||
).and.returnValue(true);
|
||||
spyOn(
|
||||
).mockReturnValue(true);
|
||||
vi.spyOn(
|
||||
refreshSessionService as any,
|
||||
'startRefreshSession'
|
||||
).and.returnValue(of(null));
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
).mockReturnValue(of(null));
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
true
|
||||
);
|
||||
spyOn(authStateService, 'getIdToken').and.returnValue('id-token');
|
||||
spyOn(authStateService, 'getAccessToken').and.returnValue('access-token');
|
||||
vi.spyOn(authStateService, 'getIdToken').mockReturnValue('id-token');
|
||||
vi.spyOn(authStateService, 'getAccessToken').mockReturnValue(
|
||||
'access-token'
|
||||
);
|
||||
const allConfigs = [
|
||||
{
|
||||
configId: 'configId1',
|
||||
@@ -246,24 +271,23 @@ describe('RefreshSessionService ', () => {
|
||||
},
|
||||
];
|
||||
|
||||
refreshSessionService
|
||||
.forceRefreshSession(allConfigs[0], allConfigs)
|
||||
.subscribe((result) => {
|
||||
const result = await firstValueFrom(
|
||||
refreshSessionService.forceRefreshSession(allConfigs[0]!, allConfigs)
|
||||
);
|
||||
expect(result.idToken).toEqual('id-token');
|
||||
expect(result.accessToken).toEqual('access-token');
|
||||
});
|
||||
}));
|
||||
|
||||
it('only calls start refresh session and returns null if auth is false', waitForAsync(() => {
|
||||
spyOn(
|
||||
it('only calls start refresh session and returns null if auth is false', async () => {
|
||||
vi.spyOn(
|
||||
flowHelper,
|
||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||
).and.returnValue(true);
|
||||
spyOn(
|
||||
).mockReturnValue(true);
|
||||
vi.spyOn(
|
||||
refreshSessionService as any,
|
||||
'startRefreshSession'
|
||||
).and.returnValue(of(null));
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
).mockReturnValue(of(null));
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
false
|
||||
);
|
||||
const allConfigs = [
|
||||
@@ -273,9 +297,9 @@ describe('RefreshSessionService ', () => {
|
||||
},
|
||||
];
|
||||
|
||||
refreshSessionService
|
||||
.forceRefreshSession(allConfigs[0], allConfigs)
|
||||
.subscribe((result) => {
|
||||
const result = await firstValueFrom(
|
||||
refreshSessionService.forceRefreshSession(allConfigs[0]!, allConfigs)
|
||||
);
|
||||
expect(result).toEqual({
|
||||
isAuthenticated: false,
|
||||
errorMessage: '',
|
||||
@@ -285,24 +309,23 @@ describe('RefreshSessionService ', () => {
|
||||
configId: 'configId1',
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('calls start refresh session and waits for completed, returns idtoken and accesstoken if auth is true', waitForAsync(() => {
|
||||
spyOn(
|
||||
it('calls start refresh session and waits for completed, returns idtoken and accesstoken if auth is true', async () => {
|
||||
vi.spyOn(
|
||||
flowHelper,
|
||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||
).and.returnValue(false);
|
||||
spyOn(
|
||||
).mockReturnValue(false);
|
||||
vi.spyOn(
|
||||
refreshSessionService as any,
|
||||
'startRefreshSession'
|
||||
).and.returnValue(of(null));
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
).mockReturnValue(of(null));
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
true
|
||||
);
|
||||
spyOnProperty(
|
||||
silentRenewService,
|
||||
'refreshSessionWithIFrameCompleted$'
|
||||
).and.returnValue(
|
||||
).mockReturnValue(
|
||||
of({
|
||||
authResult: {
|
||||
id_token: 'some-id_token',
|
||||
@@ -317,30 +340,29 @@ describe('RefreshSessionService ', () => {
|
||||
},
|
||||
];
|
||||
|
||||
refreshSessionService
|
||||
.forceRefreshSession(allConfigs[0], allConfigs)
|
||||
.subscribe((result) => {
|
||||
const result = await firstValueFrom(
|
||||
refreshSessionService.forceRefreshSession(allConfigs[0]!, allConfigs)
|
||||
);
|
||||
expect(result.idToken).toBeDefined();
|
||||
expect(result.accessToken).toBeDefined();
|
||||
});
|
||||
}));
|
||||
|
||||
it('calls start refresh session and waits for completed, returns LoginResponse if auth is false', waitForAsync(() => {
|
||||
spyOn(
|
||||
it('calls start refresh session and waits for completed, returns LoginResponse if auth is false', async () => {
|
||||
vi.spyOn(
|
||||
flowHelper,
|
||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||
).and.returnValue(false);
|
||||
spyOn(
|
||||
).mockReturnValue(false);
|
||||
vi.spyOn(
|
||||
refreshSessionService as any,
|
||||
'startRefreshSession'
|
||||
).and.returnValue(of(null));
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
).mockReturnValue(of(null));
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
false
|
||||
);
|
||||
spyOnProperty(
|
||||
silentRenewService,
|
||||
'refreshSessionWithIFrameCompleted$'
|
||||
).and.returnValue(of(null));
|
||||
).mockReturnValue(of(null));
|
||||
const allConfigs = [
|
||||
{
|
||||
configId: 'configId1',
|
||||
@@ -348,9 +370,9 @@ describe('RefreshSessionService ', () => {
|
||||
},
|
||||
];
|
||||
|
||||
refreshSessionService
|
||||
.forceRefreshSession(allConfigs[0], allConfigs)
|
||||
.subscribe((result) => {
|
||||
const result = await firstValueFrom(
|
||||
refreshSessionService.forceRefreshSession(allConfigs[0]!, allConfigs)
|
||||
);
|
||||
expect(result).toEqual({
|
||||
isAuthenticated: false,
|
||||
errorMessage: '',
|
||||
@@ -360,23 +382,24 @@ describe('RefreshSessionService ', () => {
|
||||
configId: 'configId1',
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('occurs timeout error and retry mechanism exhausted max retry count throws error', fakeAsync(() => {
|
||||
spyOn(
|
||||
it('occurs timeout error and retry mechanism exhausted max retry count throws error', async () => {
|
||||
vi.useRealTimers();
|
||||
vi.useFakeTimers();
|
||||
vi.spyOn(
|
||||
flowHelper,
|
||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||
).and.returnValue(false);
|
||||
spyOn(
|
||||
).mockReturnValue(false);
|
||||
vi.spyOn(
|
||||
refreshSessionService as any,
|
||||
'startRefreshSession'
|
||||
).and.returnValue(of(null));
|
||||
).mockReturnValue(of(null));
|
||||
spyOnProperty(
|
||||
silentRenewService,
|
||||
'refreshSessionWithIFrameCompleted$'
|
||||
).and.returnValue(of(null).pipe(delay(11000)));
|
||||
).mockReturnValue(of(null).pipe(delay(11000)));
|
||||
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
false
|
||||
);
|
||||
const allConfigs = [
|
||||
@@ -386,30 +409,44 @@ describe('RefreshSessionService ', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const resetSilentRenewRunningSpy = spyOn(
|
||||
const resetSilentRenewRunningSpy = vi.spyOn(
|
||||
flowsDataService,
|
||||
'resetSilentRenewRunning'
|
||||
);
|
||||
const expectedInvokeCount = MAX_RETRY_ATTEMPTS;
|
||||
|
||||
refreshSessionService
|
||||
.forceRefreshSession(allConfigs[0], allConfigs)
|
||||
.subscribe({
|
||||
next: () => {
|
||||
fail('It should not return any result.');
|
||||
},
|
||||
error: (error) => {
|
||||
try {
|
||||
const o$ = refreshSessionService
|
||||
.forceRefreshSession(allConfigs[0]!, allConfigs)
|
||||
.pipe(
|
||||
share({
|
||||
connector: () => new ReplaySubject(1),
|
||||
resetOnError: false,
|
||||
resetOnComplete: false,
|
||||
resetOnRefCountZero: true,
|
||||
})
|
||||
);
|
||||
|
||||
o$.subscribe();
|
||||
|
||||
await vi.advanceTimersByTimeAsync(
|
||||
allConfigs[0]!.silentRenewTimeoutInSeconds * 10000
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(o$);
|
||||
|
||||
if (result) {
|
||||
expect.fail('It should not return any result.');
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(resetSilentRenewRunningSpy).toHaveBeenCalledTimes(
|
||||
expectedInvokeCount
|
||||
);
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
tick(allConfigs[0].silentRenewTimeoutInSeconds * 10000);
|
||||
}));
|
||||
|
||||
it('occurs unknown error throws it to subscriber', fakeAsync(() => {
|
||||
it('occurs unknown error throws it to subscriber', async () => {
|
||||
const allConfigs = [
|
||||
{
|
||||
configId: 'configId1',
|
||||
@@ -419,43 +456,41 @@ describe('RefreshSessionService ', () => {
|
||||
|
||||
const expectedErrorMessage = 'Test error message';
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
flowHelper,
|
||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||
).and.returnValue(false);
|
||||
).mockReturnValue(false);
|
||||
spyOnProperty(
|
||||
silentRenewService,
|
||||
'refreshSessionWithIFrameCompleted$'
|
||||
).and.returnValue(of(null));
|
||||
spyOn(
|
||||
).mockReturnValue(of(null));
|
||||
vi.spyOn(
|
||||
refreshSessionService as any,
|
||||
'startRefreshSession'
|
||||
).and.returnValue(throwError(() => new Error(expectedErrorMessage)));
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
).mockReturnValue(throwError(() => new Error(expectedErrorMessage)));
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
false
|
||||
);
|
||||
|
||||
const resetSilentRenewRunningSpy = spyOn(
|
||||
const resetSilentRenewRunningSpy = vi.spyOn(
|
||||
flowsDataService,
|
||||
'resetSilentRenewRunning'
|
||||
);
|
||||
|
||||
refreshSessionService
|
||||
.forceRefreshSession(allConfigs[0], allConfigs)
|
||||
.subscribe({
|
||||
next: () => {
|
||||
fail('It should not return any result.');
|
||||
},
|
||||
error: (error) => {
|
||||
try {
|
||||
await firstValueFrom(
|
||||
refreshSessionService.forceRefreshSession(allConfigs[0]!, allConfigs)
|
||||
);
|
||||
expect.fail('It should not return any result.');
|
||||
} catch (error: any) {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error.message).toEqual(`Error: ${expectedErrorMessage}`);
|
||||
expect(resetSilentRenewRunningSpy).not.toHaveBeenCalled();
|
||||
},
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
describe('NOT isCurrentFlowCodeFlowWithRefreshTokens', () => {
|
||||
it('does return null when not authenticated', waitForAsync(() => {
|
||||
it('does return null when not authenticated', async () => {
|
||||
const allConfigs = [
|
||||
{
|
||||
configId: 'configId1',
|
||||
@@ -463,25 +498,25 @@ describe('RefreshSessionService ', () => {
|
||||
},
|
||||
];
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
flowHelper,
|
||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||
).and.returnValue(false);
|
||||
spyOn(
|
||||
).mockReturnValue(false);
|
||||
vi.spyOn(
|
||||
refreshSessionService as any,
|
||||
'startRefreshSession'
|
||||
).and.returnValue(of(null));
|
||||
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(
|
||||
).mockReturnValue(of(null));
|
||||
vi.spyOn(authStateService, 'areAuthStorageTokensValid').mockReturnValue(
|
||||
false
|
||||
);
|
||||
spyOnProperty(
|
||||
silentRenewService,
|
||||
'refreshSessionWithIFrameCompleted$'
|
||||
).and.returnValue(of(null));
|
||||
).mockReturnValue(of(null));
|
||||
|
||||
refreshSessionService
|
||||
.forceRefreshSession(allConfigs[0], allConfigs)
|
||||
.subscribe((result) => {
|
||||
const result = await firstValueFrom(
|
||||
refreshSessionService.forceRefreshSession(allConfigs[0]!, allConfigs)
|
||||
);
|
||||
expect(result).toEqual({
|
||||
isAuthenticated: false,
|
||||
errorMessage: '',
|
||||
@@ -491,9 +526,8 @@ describe('RefreshSessionService ', () => {
|
||||
configId: 'configId1',
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('return value only returns once', waitForAsync(() => {
|
||||
it('return value only returns once', async () => {
|
||||
const allConfigs = [
|
||||
{
|
||||
configId: 'configId1',
|
||||
@@ -501,18 +535,18 @@ describe('RefreshSessionService ', () => {
|
||||
},
|
||||
];
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
flowHelper,
|
||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||
).and.returnValue(false);
|
||||
spyOn(
|
||||
).mockReturnValue(false);
|
||||
vi.spyOn(
|
||||
refreshSessionService as any,
|
||||
'startRefreshSession'
|
||||
).and.returnValue(of(null));
|
||||
).mockReturnValue(of(null));
|
||||
spyOnProperty(
|
||||
silentRenewService,
|
||||
'refreshSessionWithIFrameCompleted$'
|
||||
).and.returnValue(
|
||||
).mockReturnValue(
|
||||
of({
|
||||
authResult: {
|
||||
id_token: 'some-id_token',
|
||||
@@ -520,14 +554,13 @@ describe('RefreshSessionService ', () => {
|
||||
},
|
||||
} as CallbackContext)
|
||||
);
|
||||
const spyInsideMap = spyOn(
|
||||
authStateService,
|
||||
'areAuthStorageTokensValid'
|
||||
).and.returnValue(true);
|
||||
const spyInsideMap = vi
|
||||
.spyOn(authStateService, 'areAuthStorageTokensValid')
|
||||
.mockReturnValue(true);
|
||||
|
||||
refreshSessionService
|
||||
.forceRefreshSession(allConfigs[0], allConfigs)
|
||||
.subscribe((result) => {
|
||||
const result = await firstValueFrom(
|
||||
refreshSessionService.forceRefreshSession(allConfigs[0]!, allConfigs)
|
||||
);
|
||||
expect(result).toEqual({
|
||||
idToken: 'some-id_token',
|
||||
accessToken: 'some-access_token',
|
||||
@@ -537,33 +570,30 @@ describe('RefreshSessionService ', () => {
|
||||
});
|
||||
expect(spyInsideMap).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('startRefreshSession', () => {
|
||||
it('returns null if no auth well known endpoint defined', waitForAsync(() => {
|
||||
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true);
|
||||
it('returns null if no auth well known endpoint defined', async () => {
|
||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
|
||||
|
||||
(refreshSessionService as any)
|
||||
.startRefreshSession()
|
||||
.subscribe((result: any) => {
|
||||
const result = await firstValueFrom(
|
||||
(refreshSessionService as any).startRefreshSession()
|
||||
);
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
}));
|
||||
|
||||
it('returns null if silent renew Is running', waitForAsync(() => {
|
||||
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(true);
|
||||
it('returns null if silent renew Is running', async () => {
|
||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
|
||||
|
||||
(refreshSessionService as any)
|
||||
.startRefreshSession()
|
||||
.subscribe((result: any) => {
|
||||
const result = await firstValueFrom(
|
||||
(refreshSessionService as any).startRefreshSession()
|
||||
);
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
}));
|
||||
|
||||
it('calls `setSilentRenewRunning` when should be executed', waitForAsync(() => {
|
||||
const setSilentRenewRunningSpy = spyOn(
|
||||
it('calls `setSilentRenewRunning` when should be executed', async () => {
|
||||
const setSilentRenewRunningSpy = vi.spyOn(
|
||||
flowsDataService,
|
||||
'setSilentRenewRunning'
|
||||
);
|
||||
@@ -574,30 +604,32 @@ describe('RefreshSessionService ', () => {
|
||||
},
|
||||
];
|
||||
|
||||
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||
spyOn(
|
||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
||||
vi.spyOn(
|
||||
authWellKnownService,
|
||||
'queryAndStoreAuthWellKnownEndPoints'
|
||||
).and.returnValue(of({}));
|
||||
).mockReturnValue(of({}));
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
flowHelper,
|
||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||
).and.returnValue(true);
|
||||
spyOn(
|
||||
).mockReturnValue(true);
|
||||
vi.spyOn(
|
||||
refreshSessionRefreshTokenService,
|
||||
'refreshSessionWithRefreshTokens'
|
||||
).and.returnValue(of({} as CallbackContext));
|
||||
).mockReturnValue(of({} as CallbackContext));
|
||||
|
||||
(refreshSessionService as any)
|
||||
.startRefreshSession(allConfigs[0], allConfigs)
|
||||
.subscribe(() => {
|
||||
await firstValueFrom(
|
||||
(refreshSessionService as any).startRefreshSession(
|
||||
allConfigs[0]!,
|
||||
allConfigs
|
||||
)
|
||||
);
|
||||
expect(setSilentRenewRunningSpy).toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('calls refreshSessionWithRefreshTokens when current flow is codeflow with refresh tokens', waitForAsync(() => {
|
||||
spyOn(flowsDataService, 'setSilentRenewRunning');
|
||||
it('calls refreshSessionWithRefreshTokens when current flow is codeflow with refresh tokens', async () => {
|
||||
vi.spyOn(flowsDataService, 'setSilentRenewRunning');
|
||||
const allConfigs = [
|
||||
{
|
||||
configId: 'configId1',
|
||||
@@ -605,30 +637,34 @@ describe('RefreshSessionService ', () => {
|
||||
},
|
||||
];
|
||||
|
||||
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||
spyOn(
|
||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
||||
vi.spyOn(
|
||||
authWellKnownService,
|
||||
'queryAndStoreAuthWellKnownEndPoints'
|
||||
).and.returnValue(of({}));
|
||||
).mockReturnValue(of({}));
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
flowHelper,
|
||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||
).and.returnValue(true);
|
||||
const refreshSessionWithRefreshTokensSpy = spyOn(
|
||||
).mockReturnValue(true);
|
||||
const refreshSessionWithRefreshTokensSpy = vi
|
||||
.spyOn(
|
||||
refreshSessionRefreshTokenService,
|
||||
'refreshSessionWithRefreshTokens'
|
||||
).and.returnValue(of({} as CallbackContext));
|
||||
)
|
||||
.mockReturnValue(of({} as CallbackContext));
|
||||
|
||||
(refreshSessionService as any)
|
||||
.startRefreshSession(allConfigs[0], allConfigs)
|
||||
.subscribe(() => {
|
||||
await firstValueFrom(
|
||||
(refreshSessionService as any).startRefreshSession(
|
||||
allConfigs[0]!,
|
||||
allConfigs
|
||||
)
|
||||
);
|
||||
expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('calls refreshSessionWithIframe when current flow is NOT codeflow with refresh tokens', waitForAsync(() => {
|
||||
spyOn(flowsDataService, 'setSilentRenewRunning');
|
||||
it('calls refreshSessionWithIframe when current flow is NOT codeflow with refresh tokens', async () => {
|
||||
vi.spyOn(flowsDataService, 'setSilentRenewRunning');
|
||||
const allConfigs = [
|
||||
{
|
||||
configId: 'configId1',
|
||||
@@ -636,32 +672,35 @@ describe('RefreshSessionService ', () => {
|
||||
},
|
||||
];
|
||||
|
||||
spyOn(flowsDataService, 'isSilentRenewRunning').and.returnValue(false);
|
||||
spyOn(
|
||||
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
|
||||
vi.spyOn(
|
||||
authWellKnownService,
|
||||
'queryAndStoreAuthWellKnownEndPoints'
|
||||
).and.returnValue(of({}));
|
||||
).mockReturnValue(of({}));
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
flowHelper,
|
||||
'isCurrentFlowCodeFlowWithRefreshTokens'
|
||||
).and.returnValue(false);
|
||||
const refreshSessionWithRefreshTokensSpy = spyOn(
|
||||
).mockReturnValue(false);
|
||||
const refreshSessionWithRefreshTokensSpy = vi
|
||||
.spyOn(
|
||||
refreshSessionRefreshTokenService,
|
||||
'refreshSessionWithRefreshTokens'
|
||||
).and.returnValue(of({} as CallbackContext));
|
||||
)
|
||||
.mockReturnValue(of({} as CallbackContext));
|
||||
|
||||
const refreshSessionWithIframeSpy = spyOn(
|
||||
refreshSessionIframeService,
|
||||
'refreshSessionWithIframe'
|
||||
).and.returnValue(of(false));
|
||||
const refreshSessionWithIframeSpy = vi
|
||||
.spyOn(refreshSessionIframeService, 'refreshSessionWithIframe')
|
||||
.mockReturnValue(of(false));
|
||||
|
||||
(refreshSessionService as any)
|
||||
.startRefreshSession(allConfigs[0], allConfigs)
|
||||
.subscribe(() => {
|
||||
await firstValueFrom(
|
||||
(refreshSessionService as any).startRefreshSession(
|
||||
allConfigs[0]!,
|
||||
allConfigs
|
||||
)
|
||||
);
|
||||
expect(refreshSessionWithRefreshTokensSpy).not.toHaveBeenCalled();
|
||||
expect(refreshSessionWithIframeSpy).toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { inject, Injectable } from 'injection-js';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import {
|
||||
type Observable,
|
||||
TimeoutError,
|
||||
forkJoin,
|
||||
Observable,
|
||||
of,
|
||||
throwError,
|
||||
TimeoutError,
|
||||
timer,
|
||||
} from 'rxjs';
|
||||
import {
|
||||
@@ -18,13 +18,13 @@ import {
|
||||
} from 'rxjs/operators';
|
||||
import { AuthStateService } from '../auth-state/auth-state.service';
|
||||
import { AuthWellKnownService } from '../config/auth-well-known/auth-well-known.service';
|
||||
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import { CallbackContext } from '../flows/callback-context';
|
||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import type { CallbackContext } from '../flows/callback-context';
|
||||
import { FlowsDataService } from '../flows/flows-data.service';
|
||||
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
|
||||
import { SilentRenewService } from '../iframe/silent-renew.service';
|
||||
import { LoggerService } from '../logging/logger.service';
|
||||
import { LoginResponse } from '../login/login-response';
|
||||
import type { LoginResponse } from '../login/login-response';
|
||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||
import { UserService } from '../user-data/user.service';
|
||||
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { mockProvider } from '../../../test/auto-mock';
|
||||
import { createRetriableStream } from '../../../test/create-retriable-stream.helper';
|
||||
import { TestBed } from '@/testing';
|
||||
import { firstValueFrom, of, throwError } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import { DataService } from '../../api/data.service';
|
||||
import { LoggerService } from '../../logging/logger.service';
|
||||
import { createRetriableStream } from '../../testing/create-retriable-stream.helper';
|
||||
import { mockProvider } from '../../testing/mock';
|
||||
import { AuthWellKnownDataService } from './auth-well-known-data.service';
|
||||
import { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
|
||||
import type { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
|
||||
|
||||
const DUMMY_WELL_KNOWN_DOCUMENT = {
|
||||
issuer: 'https://identity-server.test/realms/main',
|
||||
@@ -38,9 +39,6 @@ describe('AuthWellKnownDataService', () => {
|
||||
mockProvider(LoggerService),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.inject(AuthWellKnownDataService);
|
||||
loggerService = TestBed.inject(LoggerService);
|
||||
dataService = TestBed.inject(DataService);
|
||||
@@ -51,92 +49,94 @@ describe('AuthWellKnownDataService', () => {
|
||||
});
|
||||
|
||||
describe('getWellKnownDocument', () => {
|
||||
it('should add suffix if it does not exist on current URL', waitForAsync(() => {
|
||||
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue(
|
||||
of(null)
|
||||
);
|
||||
it('should add suffix if it does not exist on current URL', async () => {
|
||||
const dataServiceSpy = vi
|
||||
.spyOn(dataService, 'get')
|
||||
.mockReturnValue(of(null));
|
||||
const urlWithoutSuffix = 'myUrl';
|
||||
const urlWithSuffix = `${urlWithoutSuffix}/.well-known/openid-configuration`;
|
||||
|
||||
(service as any)
|
||||
.getWellKnownDocument(urlWithoutSuffix, { configId: 'configId1' })
|
||||
.subscribe(() => {
|
||||
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, {
|
||||
await firstValueFrom(
|
||||
(service as any).getWellKnownDocument(urlWithoutSuffix, {
|
||||
configId: 'configId1',
|
||||
})
|
||||
);
|
||||
expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(urlWithSuffix, {
|
||||
configId: 'configId1',
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not add suffix if it does exist on current url', waitForAsync(() => {
|
||||
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue(
|
||||
of(null)
|
||||
it('should not add suffix if it does exist on current url', async () => {
|
||||
const dataServiceSpy = vi
|
||||
.spyOn(dataService, 'get')
|
||||
.mockReturnValue(of(null));
|
||||
const urlWithSuffix = 'myUrl/.well-known/openid-configuration';
|
||||
|
||||
await firstValueFrom(
|
||||
(service as any).getWellKnownDocument(urlWithSuffix, {
|
||||
configId: 'configId1',
|
||||
})
|
||||
);
|
||||
const urlWithSuffix = `myUrl/.well-known/openid-configuration`;
|
||||
|
||||
(service as any)
|
||||
.getWellKnownDocument(urlWithSuffix, { configId: 'configId1' })
|
||||
.subscribe(() => {
|
||||
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, {
|
||||
expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(urlWithSuffix, {
|
||||
configId: 'configId1',
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not add suffix if it does exist in the middle of current url', waitForAsync(() => {
|
||||
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue(
|
||||
of(null)
|
||||
it('should not add suffix if it does exist in the middle of current url', async () => {
|
||||
const dataServiceSpy = vi
|
||||
.spyOn(dataService, 'get')
|
||||
.mockReturnValue(of(null));
|
||||
const urlWithSuffix =
|
||||
'myUrl/.well-known/openid-configuration/and/some/more/stuff';
|
||||
|
||||
await firstValueFrom(
|
||||
(service as any).getWellKnownDocument(urlWithSuffix, {
|
||||
configId: 'configId1',
|
||||
})
|
||||
);
|
||||
const urlWithSuffix = `myUrl/.well-known/openid-configuration/and/some/more/stuff`;
|
||||
|
||||
(service as any)
|
||||
.getWellKnownDocument(urlWithSuffix, { configId: 'configId1' })
|
||||
.subscribe(() => {
|
||||
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, {
|
||||
expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(urlWithSuffix, {
|
||||
configId: 'configId1',
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('should use the custom suffix provided in the config', waitForAsync(() => {
|
||||
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue(
|
||||
of(null)
|
||||
);
|
||||
const urlWithoutSuffix = `myUrl`;
|
||||
it('should use the custom suffix provided in the config', async () => {
|
||||
const dataServiceSpy = vi
|
||||
.spyOn(dataService, 'get')
|
||||
.mockReturnValue(of(null));
|
||||
const urlWithoutSuffix = 'myUrl';
|
||||
const urlWithSuffix = `${urlWithoutSuffix}/.well-known/test-openid-configuration`;
|
||||
|
||||
(service as any)
|
||||
.getWellKnownDocument(urlWithoutSuffix, {
|
||||
await firstValueFrom(
|
||||
(service as any).getWellKnownDocument(urlWithoutSuffix, {
|
||||
configId: 'configId1',
|
||||
authWellknownUrlSuffix: '/.well-known/test-openid-configuration',
|
||||
})
|
||||
.subscribe(() => {
|
||||
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, {
|
||||
);
|
||||
expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith(urlWithSuffix, {
|
||||
configId: 'configId1',
|
||||
authWellknownUrlSuffix: '/.well-known/test-openid-configuration',
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('should retry once', waitForAsync(() => {
|
||||
spyOn(dataService, 'get').and.returnValue(
|
||||
it('should retry once', async () => {
|
||||
vi.spyOn(dataService, 'get').mockReturnValue(
|
||||
createRetriableStream(
|
||||
throwError(() => new Error('one')),
|
||||
of(DUMMY_WELL_KNOWN_DOCUMENT)
|
||||
)
|
||||
);
|
||||
|
||||
(service as any)
|
||||
.getWellKnownDocument('anyurl', { configId: 'configId1' })
|
||||
.subscribe({
|
||||
next: (res: unknown) => {
|
||||
const res: unknown = await firstValueFrom(
|
||||
(service as any).getWellKnownDocument('anyurl', {
|
||||
configId: 'configId1',
|
||||
})
|
||||
);
|
||||
expect(res).toBeTruthy();
|
||||
expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT);
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
it('should retry twice', waitForAsync(() => {
|
||||
spyOn(dataService, 'get').and.returnValue(
|
||||
it('should retry twice', async () => {
|
||||
vi.spyOn(dataService, 'get').mockReturnValue(
|
||||
createRetriableStream(
|
||||
throwError(() => new Error('one')),
|
||||
throwError(() => new Error('two')),
|
||||
@@ -144,18 +144,17 @@ describe('AuthWellKnownDataService', () => {
|
||||
)
|
||||
);
|
||||
|
||||
(service as any)
|
||||
.getWellKnownDocument('anyurl', { configId: 'configId1' })
|
||||
.subscribe({
|
||||
next: (res: any) => {
|
||||
const res: any = await firstValueFrom(
|
||||
(service as any).getWellKnownDocument('anyurl', {
|
||||
configId: 'configId1',
|
||||
})
|
||||
);
|
||||
expect(res).toBeTruthy();
|
||||
expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT);
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
it('should fail after three tries', waitForAsync(() => {
|
||||
spyOn(dataService, 'get').and.returnValue(
|
||||
it('should fail after three tries', async () => {
|
||||
vi.spyOn(dataService, 'get').mockReturnValue(
|
||||
createRetriableStream(
|
||||
throwError(() => new Error('one')),
|
||||
throwError(() => new Error('two')),
|
||||
@@ -164,55 +163,57 @@ describe('AuthWellKnownDataService', () => {
|
||||
)
|
||||
);
|
||||
|
||||
(service as any).getWellKnownDocument('anyurl', 'configId').subscribe({
|
||||
error: (err: unknown) => {
|
||||
try {
|
||||
await firstValueFrom(
|
||||
(service as any).getWellKnownDocument('anyurl', 'configId')
|
||||
);
|
||||
} catch (err: unknown) {
|
||||
expect(err).toBeTruthy();
|
||||
},
|
||||
}
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('getWellKnownEndPointsForConfig', () => {
|
||||
it('calling internal getWellKnownDocument and maps', waitForAsync(() => {
|
||||
spyOn(dataService, 'get').and.returnValue(of({ jwks_uri: 'jwks_uri' }));
|
||||
it('calling internal getWellKnownDocument and maps', async () => {
|
||||
vi.spyOn(dataService, 'get').mockReturnValue(
|
||||
of({ jwks_uri: 'jwks_uri' })
|
||||
);
|
||||
|
||||
const spy = spyOn(
|
||||
service as any,
|
||||
'getWellKnownDocument'
|
||||
).and.callThrough();
|
||||
const spy = vi.spyOn(service as any, 'getWellKnownDocument');
|
||||
|
||||
service
|
||||
.getWellKnownEndPointsForConfig({
|
||||
const result = await firstValueFrom(
|
||||
service.getWellKnownEndPointsForConfig({
|
||||
configId: 'configId1',
|
||||
authWellknownEndpointUrl: 'any-url',
|
||||
})
|
||||
.subscribe((result) => {
|
||||
);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect((result as any).jwks_uri).toBeUndefined();
|
||||
expect(result.jwksUri).toBe('jwks_uri');
|
||||
});
|
||||
}));
|
||||
|
||||
it('throws error and logs if no authwellknownUrl is given', waitForAsync(() => {
|
||||
const loggerSpy = spyOn(loggerService, 'logError');
|
||||
it('throws error and logs if no authwellknownUrl is given', async () => {
|
||||
const loggerSpy = vi.spyOn(loggerService, 'logError');
|
||||
const config = {
|
||||
configId: 'configId1',
|
||||
authWellknownEndpointUrl: undefined,
|
||||
};
|
||||
|
||||
service.getWellKnownEndPointsForConfig(config).subscribe({
|
||||
error: (error) => {
|
||||
expect(loggerSpy).toHaveBeenCalledOnceWith(
|
||||
try {
|
||||
await firstValueFrom(service.getWellKnownEndPointsForConfig(config));
|
||||
} catch (error: any) {
|
||||
expect(loggerSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
config,
|
||||
'no authWellknownEndpoint given!'
|
||||
);
|
||||
expect(error.message).toEqual('no authWellknownEndpoint given!');
|
||||
},
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
it('should merge the mapped endpoints with the provided endpoints', waitForAsync(() => {
|
||||
spyOn(dataService, 'get').and.returnValue(of(DUMMY_WELL_KNOWN_DOCUMENT));
|
||||
it('should merge the mapped endpoints with the provided endpoints', async () => {
|
||||
vi.spyOn(dataService, 'get').mockReturnValue(
|
||||
of(DUMMY_WELL_KNOWN_DOCUMENT)
|
||||
);
|
||||
|
||||
const expected: AuthWellKnownEndpoints = {
|
||||
endSessionEndpoint: 'config-endSessionEndpoint',
|
||||
@@ -220,8 +221,8 @@ describe('AuthWellKnownDataService', () => {
|
||||
jwksUri: DUMMY_WELL_KNOWN_DOCUMENT.jwks_uri,
|
||||
};
|
||||
|
||||
service
|
||||
.getWellKnownEndPointsForConfig({
|
||||
const result = await firstValueFrom(
|
||||
service.getWellKnownEndPointsForConfig({
|
||||
configId: 'configId1',
|
||||
authWellknownEndpointUrl: 'any-url',
|
||||
authWellknownEndpoints: {
|
||||
@@ -229,9 +230,8 @@ describe('AuthWellKnownDataService', () => {
|
||||
revocationEndpoint: 'config-revocationEndpoint',
|
||||
},
|
||||
})
|
||||
.subscribe((result) => {
|
||||
expect(result).toEqual(jasmine.objectContaining(expected));
|
||||
});
|
||||
}));
|
||||
);
|
||||
expect(result).toEqual(expect.objectContaining(expected));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { inject, Injectable } from 'injection-js';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import { type Observable, throwError } from 'rxjs';
|
||||
import { map, retry } from 'rxjs/operators';
|
||||
import { DataService } from '../../api/data.service';
|
||||
import { LoggerService } from '../../logging/logger.service';
|
||||
import { OpenIdConfiguration } from '../openid-configuration';
|
||||
import { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
|
||||
import type { OpenIdConfiguration } from '../openid-configuration';
|
||||
import type { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
|
||||
|
||||
const WELL_KNOWN_SUFFIX = `/.well-known/openid-configuration`;
|
||||
const WELL_KNOWN_SUFFIX = '/.well-known/openid-configuration';
|
||||
|
||||
@Injectable()
|
||||
export class AuthWellKnownDataService {
|
||||
@@ -42,7 +42,7 @@ export class AuthWellKnownDataService {
|
||||
introspectionEndpoint: wellKnownEndpoints.introspection_endpoint,
|
||||
parEndpoint:
|
||||
wellKnownEndpoints.pushed_authorization_request_endpoint,
|
||||
} as AuthWellKnownEndpoints)
|
||||
}) as AuthWellKnownEndpoints
|
||||
),
|
||||
map((mappedWellKnownEndpoints) => ({
|
||||
...mappedWellKnownEndpoints,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { mockProvider } from '../../../test/auto-mock';
|
||||
import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
|
||||
import { firstValueFrom, of, throwError } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import { EventTypes } from '../../public-events/event-types';
|
||||
import { PublicEventsService } from '../../public-events/public-events.service';
|
||||
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
||||
import { mockProvider } from '../../testing/mock';
|
||||
import { AuthWellKnownDataService } from './auth-well-known-data.service';
|
||||
import { AuthWellKnownService } from './auth-well-known.service';
|
||||
|
||||
@@ -22,9 +23,6 @@ describe('AuthWellKnownService', () => {
|
||||
mockProvider(StoragePersistenceService),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.inject(AuthWellKnownService);
|
||||
dataService = TestBed.inject(AuthWellKnownDataService);
|
||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||
@@ -36,75 +34,75 @@ describe('AuthWellKnownService', () => {
|
||||
});
|
||||
|
||||
describe('getAuthWellKnownEndPoints', () => {
|
||||
it('getAuthWellKnownEndPoints throws an error if not config provided', waitForAsync(() => {
|
||||
service.queryAndStoreAuthWellKnownEndPoints(null).subscribe({
|
||||
error: (error) => {
|
||||
it('getAuthWellKnownEndPoints throws an error if not config provided', async () => {
|
||||
try {
|
||||
await firstValueFrom(service.queryAndStoreAuthWellKnownEndPoints(null));
|
||||
} catch (error) {
|
||||
expect(error).toEqual(
|
||||
new Error(
|
||||
'Please provide a configuration before setting up the module'
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
it('getAuthWellKnownEndPoints calls always dataservice', waitForAsync(() => {
|
||||
const dataServiceSpy = spyOn(
|
||||
dataService,
|
||||
'getWellKnownEndPointsForConfig'
|
||||
).and.returnValue(of({ issuer: 'anything' }));
|
||||
it('getAuthWellKnownEndPoints calls always dataservice', async () => {
|
||||
const dataServiceSpy = vi
|
||||
.spyOn(dataService, 'getWellKnownEndPointsForConfig')
|
||||
.mockReturnValue(of({ issuer: 'anything' }));
|
||||
|
||||
spyOn(storagePersistenceService, 'read')
|
||||
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||
.and.returnValue({ issuer: 'anything' });
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'read'),
|
||||
['authWellKnownEndPoints', { configId: 'configId1' }],
|
||||
() => ({ issuer: 'anything' })
|
||||
);
|
||||
|
||||
service
|
||||
.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
|
||||
.subscribe((result) => {
|
||||
const result = await firstValueFrom(
|
||||
service.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
|
||||
);
|
||||
expect(storagePersistenceService.read).not.toHaveBeenCalled();
|
||||
expect(dataServiceSpy).toHaveBeenCalled();
|
||||
expect(result).toEqual({ issuer: 'anything' });
|
||||
});
|
||||
}));
|
||||
|
||||
it('getAuthWellKnownEndPoints stored the result if http call is made', waitForAsync(() => {
|
||||
const dataServiceSpy = spyOn(
|
||||
dataService,
|
||||
'getWellKnownEndPointsForConfig'
|
||||
).and.returnValue(of({ issuer: 'anything' }));
|
||||
it('getAuthWellKnownEndPoints stored the result if http call is made', async () => {
|
||||
const dataServiceSpy = vi
|
||||
.spyOn(dataService, 'getWellKnownEndPointsForConfig')
|
||||
.mockReturnValue(of({ issuer: 'anything' }));
|
||||
|
||||
spyOn(storagePersistenceService, 'read')
|
||||
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||
.and.returnValue(null);
|
||||
const storeSpy = spyOn(service, 'storeWellKnownEndpoints');
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'read'),
|
||||
['authWellKnownEndPoints', { configId: 'configId1' }],
|
||||
() => null
|
||||
);
|
||||
const storeSpy = vi.spyOn(service, 'storeWellKnownEndpoints');
|
||||
|
||||
service
|
||||
.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
|
||||
.subscribe((result) => {
|
||||
const result = await firstValueFrom(
|
||||
service.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
|
||||
);
|
||||
expect(dataServiceSpy).toHaveBeenCalled();
|
||||
expect(storeSpy).toHaveBeenCalled();
|
||||
expect(result).toEqual({ issuer: 'anything' });
|
||||
});
|
||||
}));
|
||||
|
||||
it('throws `ConfigLoadingFailed` event when error happens from http', waitForAsync(() => {
|
||||
spyOn(dataService, 'getWellKnownEndPointsForConfig').and.returnValue(
|
||||
it('throws `ConfigLoadingFailed` event when error happens from http', async () => {
|
||||
vi.spyOn(dataService, 'getWellKnownEndPointsForConfig').mockReturnValue(
|
||||
throwError(() => new Error('error'))
|
||||
);
|
||||
const publicEventsServiceSpy = spyOn(publicEventsService, 'fireEvent');
|
||||
const publicEventsServiceSpy = vi.spyOn(publicEventsService, 'fireEvent');
|
||||
|
||||
service
|
||||
.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
|
||||
.subscribe({
|
||||
error: (err) => {
|
||||
try {
|
||||
await firstValueFrom(
|
||||
service.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
|
||||
);
|
||||
} catch (err: any) {
|
||||
expect(err).toBeTruthy();
|
||||
expect(publicEventsServiceSpy).toHaveBeenCalledTimes(1);
|
||||
expect(publicEventsServiceSpy).toHaveBeenCalledOnceWith(
|
||||
expect(publicEventsServiceSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
EventTypes.ConfigLoadingFailed,
|
||||
null
|
||||
);
|
||||
},
|
||||
});
|
||||
}));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { inject, Injectable } from 'injection-js';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import { type Observable, throwError } from 'rxjs';
|
||||
import { catchError, tap } from 'rxjs/operators';
|
||||
import { EventTypes } from '../../public-events/event-types';
|
||||
import { PublicEventsService } from '../../public-events/public-events.service';
|
||||
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
||||
import { OpenIdConfiguration } from '../openid-configuration';
|
||||
import type { OpenIdConfiguration } from '../openid-configuration';
|
||||
import { AuthWellKnownDataService } from './auth-well-known-data.service';
|
||||
import { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
|
||||
import type { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
|
||||
|
||||
@Injectable()
|
||||
export class AuthWellKnownService {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { mockAbstractProvider, mockProvider } from '../../test/auto-mock';
|
||||
import { TestBed } from '@/testing';
|
||||
import { firstValueFrom, of } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import { LoggerService } from '../logging/logger.service';
|
||||
import { EventTypes } from '../public-events/event-types';
|
||||
import { PublicEventsService } from '../public-events/public-events.service';
|
||||
import { StoragePersistenceService } from '../storage/storage-persistence.service';
|
||||
import { mockAbstractProvider, mockProvider } from '../testing/mock';
|
||||
import { PlatformProvider } from '../utils/platform-provider/platform.provider';
|
||||
import { AuthWellKnownService } from './auth-well-known/auth-well-known.service';
|
||||
import { ConfigurationService } from './config.service';
|
||||
import { StsConfigLoader, StsConfigStaticLoader } from './loader/config-loader';
|
||||
import { OpenIdConfiguration } from './openid-configuration';
|
||||
import type { OpenIdConfiguration } from './openid-configuration';
|
||||
import { ConfigValidationService } from './validation/config-validation.service';
|
||||
|
||||
describe('Configuration Service', () => {
|
||||
@@ -34,9 +35,6 @@ describe('Configuration Service', () => {
|
||||
mockAbstractProvider(StsConfigLoader, StsConfigStaticLoader),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
configService = TestBed.inject(ConfigurationService);
|
||||
publicEventsService = TestBed.inject(PublicEventsService);
|
||||
authWellKnownService = TestBed.inject(AuthWellKnownService);
|
||||
@@ -88,98 +86,110 @@ describe('Configuration Service', () => {
|
||||
});
|
||||
|
||||
describe('getOpenIDConfiguration', () => {
|
||||
it(`if config is already saved 'loadConfigs' is not called`, waitForAsync(() => {
|
||||
it(`if config is already saved 'loadConfigs' is not called`, async () => {
|
||||
(configService as any).configsInternal = {
|
||||
configId1: { configId: 'configId1' },
|
||||
configId2: { configId: 'configId2' },
|
||||
};
|
||||
const spy = spyOn(configService as any, 'loadConfigs');
|
||||
const spy = vi.spyOn(configService as any, 'loadConfigs');
|
||||
|
||||
configService.getOpenIDConfiguration('configId1').subscribe((config) => {
|
||||
const config = await firstValueFrom(
|
||||
configService.getOpenIDConfiguration('configId1')
|
||||
);
|
||||
expect(config).toBeTruthy();
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it(`if config is NOT already saved 'loadConfigs' is called`, waitForAsync(() => {
|
||||
it(`if config is NOT already saved 'loadConfigs' is called`, async () => {
|
||||
const configs = [{ configId: 'configId1' }, { configId: 'configId2' }];
|
||||
const spy = spyOn(configService as any, 'loadConfigs').and.returnValue(
|
||||
of(configs)
|
||||
const spy = vi
|
||||
.spyOn(configService as any, 'loadConfigs')
|
||||
.mockReturnValue(of(configs));
|
||||
|
||||
vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true);
|
||||
|
||||
const config = await firstValueFrom(
|
||||
configService.getOpenIDConfiguration('configId1')
|
||||
);
|
||||
|
||||
spyOn(configValidationService, 'validateConfig').and.returnValue(true);
|
||||
|
||||
configService.getOpenIDConfiguration('configId1').subscribe((config) => {
|
||||
expect(config).toBeTruthy();
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it(`returns null if config is not valid`, waitForAsync(() => {
|
||||
it('returns null if config is not valid', async () => {
|
||||
const configs = [{ configId: 'configId1' }];
|
||||
|
||||
spyOn(configService as any, 'loadConfigs').and.returnValue(of(configs));
|
||||
spyOn(configValidationService, 'validateConfig').and.returnValue(false);
|
||||
const consoleSpy = spyOn(console, 'warn');
|
||||
vi.spyOn(configService as any, 'loadConfigs').mockReturnValue(
|
||||
of(configs)
|
||||
);
|
||||
vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(
|
||||
false
|
||||
);
|
||||
const consoleSpy = vi.spyOn(console, 'warn');
|
||||
|
||||
configService.getOpenIDConfiguration('configId1').subscribe((config) => {
|
||||
const config = await firstValueFrom(
|
||||
configService.getOpenIDConfiguration('configId1')
|
||||
);
|
||||
expect(config).toBeNull();
|
||||
expect(consoleSpy).toHaveBeenCalledOnceWith(`[oidc-client-rx] No configuration found for config id 'configId1'.`)
|
||||
expect(consoleSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
`[oidc-client-rx] No configuration found for config id 'configId1'.`
|
||||
);
|
||||
});
|
||||
}));
|
||||
|
||||
it(`returns null if configs are stored but not existing ID is passed`, waitForAsync(() => {
|
||||
it('returns null if configs are stored but not existing ID is passed', async () => {
|
||||
(configService as any).configsInternal = {
|
||||
configId1: { configId: 'configId1' },
|
||||
configId2: { configId: 'configId2' },
|
||||
};
|
||||
|
||||
configService
|
||||
.getOpenIDConfiguration('notExisting')
|
||||
.subscribe((config) => {
|
||||
const config = await firstValueFrom(
|
||||
configService.getOpenIDConfiguration('notExisting')
|
||||
);
|
||||
expect(config).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it(`sets authWellKnownEndPoints on config if authWellKnownEndPoints is stored`, waitForAsync(() => {
|
||||
it('sets authWellKnownEndPoints on config if authWellKnownEndPoints is stored', async () => {
|
||||
const configs = [{ configId: 'configId1' }];
|
||||
|
||||
spyOn(configService as any, 'loadConfigs').and.returnValue(of(configs));
|
||||
spyOn(configValidationService, 'validateConfig').and.returnValue(true);
|
||||
const consoleSpy = spyOn(console, 'warn');
|
||||
vi.spyOn(configService as any, 'loadConfigs').mockReturnValue(
|
||||
of(configs)
|
||||
);
|
||||
vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true);
|
||||
const consoleSpy = vi.spyOn(console, 'warn');
|
||||
|
||||
spyOn(storagePersistenceService, 'read').and.returnValue({
|
||||
vi.spyOn(storagePersistenceService, 'read').mockReturnValue({
|
||||
issuer: 'auth-well-known',
|
||||
});
|
||||
|
||||
configService.getOpenIDConfiguration('configId1').subscribe((config) => {
|
||||
const config = await firstValueFrom(
|
||||
configService.getOpenIDConfiguration('configId1')
|
||||
);
|
||||
expect(config?.authWellknownEndpoints).toEqual({
|
||||
issuer: 'auth-well-known',
|
||||
});
|
||||
expect(consoleSpy).not.toHaveBeenCalled()
|
||||
expect(consoleSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it(`fires ConfigLoaded if authWellKnownEndPoints is stored`, waitForAsync(() => {
|
||||
it('fires ConfigLoaded if authWellKnownEndPoints is stored', async () => {
|
||||
const configs = [{ configId: 'configId1' }];
|
||||
|
||||
spyOn(configService as any, 'loadConfigs').and.returnValue(of(configs));
|
||||
spyOn(configValidationService, 'validateConfig').and.returnValue(true);
|
||||
spyOn(storagePersistenceService, 'read').and.returnValue({
|
||||
vi.spyOn(configService as any, 'loadConfigs').mockReturnValue(
|
||||
of(configs)
|
||||
);
|
||||
vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true);
|
||||
vi.spyOn(storagePersistenceService, 'read').mockReturnValue({
|
||||
issuer: 'auth-well-known',
|
||||
});
|
||||
|
||||
const spy = spyOn(publicEventsService, 'fireEvent');
|
||||
const spy = vi.spyOn(publicEventsService, 'fireEvent');
|
||||
|
||||
configService.getOpenIDConfiguration('configId1').subscribe(() => {
|
||||
expect(spy).toHaveBeenCalledOnceWith(
|
||||
await firstValueFrom(configService.getOpenIDConfiguration('configId1'));
|
||||
expect(spy).toHaveBeenCalledExactlyOnceWith(
|
||||
EventTypes.ConfigLoaded,
|
||||
jasmine.anything()
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
}));
|
||||
|
||||
it(`stores, uses and fires event when authwellknownendpoints are passed`, waitForAsync(() => {
|
||||
it('stores, uses and fires event when authwellknownendpoints are passed', async () => {
|
||||
const configs = [
|
||||
{
|
||||
configId: 'configId1',
|
||||
@@ -187,92 +197,96 @@ describe('Configuration Service', () => {
|
||||
},
|
||||
];
|
||||
|
||||
spyOn(configService as any, 'loadConfigs').and.returnValue(of(configs));
|
||||
spyOn(configValidationService, 'validateConfig').and.returnValue(true);
|
||||
spyOn(storagePersistenceService, 'read').and.returnValue(null);
|
||||
vi.spyOn(configService as any, 'loadConfigs').mockReturnValue(
|
||||
of(configs)
|
||||
);
|
||||
vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true);
|
||||
vi.spyOn(storagePersistenceService, 'read').mockReturnValue(null);
|
||||
|
||||
const fireEventSpy = spyOn(publicEventsService, 'fireEvent');
|
||||
const storeWellKnownEndpointsSpy = spyOn(
|
||||
const fireEventSpy = vi.spyOn(publicEventsService, 'fireEvent');
|
||||
const storeWellKnownEndpointsSpy = vi.spyOn(
|
||||
authWellKnownService,
|
||||
'storeWellKnownEndpoints'
|
||||
);
|
||||
|
||||
configService.getOpenIDConfiguration('configId1').subscribe((config) => {
|
||||
expect(config).toBeTruthy();
|
||||
expect(fireEventSpy).toHaveBeenCalledOnceWith(
|
||||
EventTypes.ConfigLoaded,
|
||||
jasmine.anything()
|
||||
const config = await firstValueFrom(
|
||||
configService.getOpenIDConfiguration('configId1')
|
||||
);
|
||||
expect(storeWellKnownEndpointsSpy).toHaveBeenCalledOnceWith(
|
||||
expect(config).toBeTruthy();
|
||||
expect(fireEventSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
EventTypes.ConfigLoaded,
|
||||
expect.anything()
|
||||
);
|
||||
expect(storeWellKnownEndpointsSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
config as OpenIdConfiguration,
|
||||
{
|
||||
issuer: 'auth-well-known',
|
||||
}
|
||||
);
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('getOpenIDConfigurations', () => {
|
||||
it(`returns correct result`, waitForAsync(() => {
|
||||
spyOn(stsConfigLoader, 'loadConfigs').and.returnValue(
|
||||
it('returns correct result', async () => {
|
||||
vi.spyOn(stsConfigLoader, 'loadConfigs').mockReturnValue(
|
||||
of([
|
||||
{ configId: 'configId1' } as OpenIdConfiguration,
|
||||
{ configId: 'configId2' } as OpenIdConfiguration,
|
||||
])
|
||||
);
|
||||
|
||||
spyOn(configValidationService, 'validateConfig').and.returnValue(true);
|
||||
vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true);
|
||||
|
||||
configService.getOpenIDConfigurations('configId1').subscribe((result) => {
|
||||
const result = await firstValueFrom(
|
||||
configService.getOpenIDConfigurations('configId1')
|
||||
);
|
||||
expect(result.allConfigs.length).toEqual(2);
|
||||
expect(result.currentConfig).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
|
||||
it(`created configId when configId is not set`, waitForAsync(() => {
|
||||
spyOn(stsConfigLoader, 'loadConfigs').and.returnValue(
|
||||
it('created configId when configId is not set', async () => {
|
||||
vi.spyOn(stsConfigLoader, 'loadConfigs').mockReturnValue(
|
||||
of([
|
||||
{ clientId: 'clientId1' } as OpenIdConfiguration,
|
||||
{ clientId: 'clientId2' } as OpenIdConfiguration,
|
||||
])
|
||||
);
|
||||
|
||||
spyOn(configValidationService, 'validateConfig').and.returnValue(true);
|
||||
vi.spyOn(configValidationService, 'validateConfig').mockReturnValue(true);
|
||||
|
||||
configService.getOpenIDConfigurations().subscribe((result) => {
|
||||
const result = await firstValueFrom(
|
||||
configService.getOpenIDConfigurations()
|
||||
);
|
||||
expect(result.allConfigs.length).toEqual(2);
|
||||
const allConfigIds = result.allConfigs.map((x) => x.configId);
|
||||
|
||||
expect(allConfigIds).toEqual(['0-clientId1', '1-clientId2']);
|
||||
|
||||
expect(result.currentConfig).toBeTruthy();
|
||||
expect(result.currentConfig?.configId).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
|
||||
it(`returns empty array if config is not valid`, waitForAsync(() => {
|
||||
spyOn(stsConfigLoader, 'loadConfigs').and.returnValue(
|
||||
it('returns empty array if config is not valid', async () => {
|
||||
vi.spyOn(stsConfigLoader, 'loadConfigs').mockReturnValue(
|
||||
of([
|
||||
{ configId: 'configId1' } as OpenIdConfiguration,
|
||||
{ configId: 'configId2' } as OpenIdConfiguration,
|
||||
])
|
||||
);
|
||||
|
||||
spyOn(configValidationService, 'validateConfigs').and.returnValue(false);
|
||||
vi.spyOn(configValidationService, 'validateConfigs').mockReturnValue(
|
||||
false
|
||||
);
|
||||
|
||||
configService
|
||||
.getOpenIDConfigurations()
|
||||
.subscribe(({ allConfigs, currentConfig }) => {
|
||||
const { allConfigs, currentConfig } = await firstValueFrom(
|
||||
configService.getOpenIDConfigurations()
|
||||
);
|
||||
expect(allConfigs).toEqual([]);
|
||||
expect(currentConfig).toBeNull();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('setSpecialCases', () => {
|
||||
it(`should set special cases when current platform is browser`, () => {
|
||||
spyOn(platformProvider, 'isBrowser').and.returnValue(false);
|
||||
it('should set special cases when current platform is browser', () => {
|
||||
vi.spyOn(platformProvider, 'isBrowser').mockReturnValue(false);
|
||||
|
||||
const config = { configId: 'configId1' } as OpenIdConfiguration;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {inject, Injectable, isDevMode} from 'injection-js';
|
||||
import { forkJoin, Observable, of } from 'rxjs';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import { type Observable, forkJoin, of } from 'rxjs';
|
||||
import { concatMap, map } from 'rxjs/operators';
|
||||
import { injectAbstractType } from '../injection/inject';
|
||||
import { LoggerService } from '../logging/logger.service';
|
||||
import { EventTypes } from '../public-events/event-types';
|
||||
import { PublicEventsService } from '../public-events/public-events.service';
|
||||
@@ -9,7 +10,7 @@ import { PlatformProvider } from '../utils/platform-provider/platform.provider';
|
||||
import { AuthWellKnownService } from './auth-well-known/auth-well-known.service';
|
||||
import { DEFAULT_CONFIG } from './default-config';
|
||||
import { StsConfigLoader } from './loader/config-loader';
|
||||
import { OpenIdConfiguration } from './openid-configuration';
|
||||
import type { OpenIdConfiguration } from './openid-configuration';
|
||||
import { ConfigValidationService } from './validation/config-validation.service';
|
||||
|
||||
@Injectable()
|
||||
@@ -26,7 +27,7 @@ export class ConfigurationService {
|
||||
|
||||
private readonly authWellKnownService = inject(AuthWellKnownService);
|
||||
|
||||
private readonly loader = inject(StsConfigLoader);
|
||||
private readonly loader = injectAbstractType(StsConfigLoader);
|
||||
|
||||
private readonly configValidationService = inject(ConfigValidationService);
|
||||
|
||||
@@ -84,11 +85,14 @@ export class ConfigurationService {
|
||||
}
|
||||
|
||||
private getConfig(configId?: string): OpenIdConfiguration | null {
|
||||
if (Boolean(configId)) {
|
||||
if (configId) {
|
||||
const config = this.configsInternal[configId!];
|
||||
|
||||
if(!config && isDevMode()) {
|
||||
console.warn(`[oidc-client-rx] No configuration found for config id '${configId}'.`);
|
||||
if (!config) {
|
||||
// biome-ignore lint/suspicious/noConsole: <explanation>
|
||||
console.warn(
|
||||
`[oidc-client-rx] No configuration found for config id '${configId}'.`
|
||||
);
|
||||
}
|
||||
|
||||
return config || null;
|
||||
@@ -165,7 +169,7 @@ export class ConfigurationService {
|
||||
configuration
|
||||
);
|
||||
|
||||
if (!!alreadyExistingAuthWellKnownEndpoints) {
|
||||
if (alreadyExistingAuthWellKnownEndpoints) {
|
||||
configuration.authWellknownEndpoints =
|
||||
alreadyExistingAuthWellKnownEndpoints;
|
||||
|
||||
@@ -174,7 +178,7 @@ export class ConfigurationService {
|
||||
|
||||
const passedAuthWellKnownEndpoints = configuration.authWellknownEndpoints;
|
||||
|
||||
if (!!passedAuthWellKnownEndpoints) {
|
||||
if (passedAuthWellKnownEndpoints) {
|
||||
this.authWellKnownService.storeWellKnownEndpoints(
|
||||
configuration,
|
||||
passedAuthWellKnownEndpoints
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LogLevel } from '../logging/log-level';
|
||||
import { OpenIdConfiguration } from './openid-configuration';
|
||||
import type { OpenIdConfiguration } from './openid-configuration';
|
||||
|
||||
export const DEFAULT_CONFIG: OpenIdConfiguration = {
|
||||
authority: 'https://please_set',
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { waitForAsync } from '@angular/core/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { OpenIdConfiguration } from '../openid-configuration';
|
||||
import { firstValueFrom, of } from 'rxjs';
|
||||
import type { OpenIdConfiguration } from '../openid-configuration';
|
||||
import { StsConfigHttpLoader, StsConfigStaticLoader } from './config-loader';
|
||||
|
||||
describe('ConfigLoader', () => {
|
||||
describe('StsConfigStaticLoader', () => {
|
||||
describe('loadConfigs', () => {
|
||||
it('returns an array if an array is passed', waitForAsync(() => {
|
||||
it('returns an array if an array is passed', async () => {
|
||||
const toPass = [
|
||||
{ configId: 'configId1' } as OpenIdConfiguration,
|
||||
{ configId: 'configId2' } as OpenIdConfiguration,
|
||||
@@ -16,28 +15,26 @@ describe('ConfigLoader', () => {
|
||||
|
||||
const result$ = loader.loadConfigs();
|
||||
|
||||
result$.subscribe((result) => {
|
||||
expect(Array.isArray(result)).toBeTrue();
|
||||
const result = await firstValueFrom(result$);
|
||||
expect(Array.isArray(result)).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
|
||||
it('returns an array if only one config is passed', waitForAsync(() => {
|
||||
it('returns an array if only one config is passed', async () => {
|
||||
const loader = new StsConfigStaticLoader({
|
||||
configId: 'configId1',
|
||||
} as OpenIdConfiguration);
|
||||
|
||||
const result$ = loader.loadConfigs();
|
||||
|
||||
result$.subscribe((result) => {
|
||||
expect(Array.isArray(result)).toBeTrue();
|
||||
const result = await firstValueFrom(result$);
|
||||
expect(Array.isArray(result)).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('StsConfigHttpLoader', () => {
|
||||
describe('loadConfigs', () => {
|
||||
it('returns an array if an array of observables is passed', waitForAsync(() => {
|
||||
it('returns an array if an array of observables is passed', async () => {
|
||||
const toPass = [
|
||||
of({ configId: 'configId1' } as OpenIdConfiguration),
|
||||
of({ configId: 'configId2' } as OpenIdConfiguration),
|
||||
@@ -46,14 +43,13 @@ describe('ConfigLoader', () => {
|
||||
|
||||
const result$ = loader.loadConfigs();
|
||||
|
||||
result$.subscribe((result) => {
|
||||
expect(Array.isArray(result)).toBeTrue();
|
||||
expect(result[0].configId).toBe('configId1');
|
||||
expect(result[1].configId).toBe('configId2');
|
||||
const result = await firstValueFrom(result$);
|
||||
expect(Array.isArray(result)).toBeTruthy();
|
||||
expect(result[0]!.configId).toBe('configId1');
|
||||
expect(result[1]!.configId).toBe('configId2');
|
||||
});
|
||||
}));
|
||||
|
||||
it('returns an array if an observable with a config array is passed', waitForAsync(() => {
|
||||
it('returns an array if an observable with a config array is passed', async () => {
|
||||
const toPass = of([
|
||||
{ configId: 'configId1' } as OpenIdConfiguration,
|
||||
{ configId: 'configId2' } as OpenIdConfiguration,
|
||||
@@ -62,25 +58,23 @@ describe('ConfigLoader', () => {
|
||||
|
||||
const result$ = loader.loadConfigs();
|
||||
|
||||
result$.subscribe((result) => {
|
||||
expect(Array.isArray(result)).toBeTrue();
|
||||
expect(result[0].configId).toBe('configId1');
|
||||
expect(result[1].configId).toBe('configId2');
|
||||
const result = await firstValueFrom(result$);
|
||||
expect(Array.isArray(result)).toBeTruthy();
|
||||
expect(result[0]!.configId).toBe('configId1');
|
||||
expect(result[1]!.configId).toBe('configId2');
|
||||
});
|
||||
}));
|
||||
|
||||
it('returns an array if only one config is passed', waitForAsync(() => {
|
||||
it('returns an array if only one config is passed', async () => {
|
||||
const loader = new StsConfigHttpLoader(
|
||||
of({ configId: 'configId1' } as OpenIdConfiguration)
|
||||
);
|
||||
|
||||
const result$ = loader.loadConfigs();
|
||||
|
||||
result$.subscribe((result) => {
|
||||
expect(Array.isArray(result)).toBeTrue();
|
||||
expect(result[0].configId).toBe('configId1');
|
||||
});
|
||||
}));
|
||||
const result = await firstValueFrom(result$);
|
||||
expect(Array.isArray(result)).toBeTruthy();
|
||||
expect(result[0]!.configId).toBe('configId1');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Provider } from 'injection-js';
|
||||
import { forkJoin, Observable, of } from 'rxjs';
|
||||
import type { Provider } from '@outposts/injection-js';
|
||||
import { type Observable, forkJoin, of } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { OpenIdConfiguration } from '../openid-configuration';
|
||||
import type { OpenIdConfiguration } from '../openid-configuration';
|
||||
|
||||
export class OpenIdConfigLoader {
|
||||
loader?: Provider;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LogLevel } from '../logging/log-level';
|
||||
import { AuthWellKnownEndpoints } from './auth-well-known/auth-well-known-endpoints';
|
||||
import type { LogLevel } from '../logging/log-level';
|
||||
import type { AuthWellKnownEndpoints } from './auth-well-known/auth-well-known-endpoints';
|
||||
|
||||
export interface OpenIdConfiguration {
|
||||
/**
|
||||
@@ -40,6 +40,12 @@ export interface OpenIdConfiguration {
|
||||
* or if it contains additional audiences not trusted by the Client.
|
||||
*/
|
||||
clientId?: string;
|
||||
/**
|
||||
* @dangerous
|
||||
* @see [client secret is missing](https://github.com/damienbod/angular-auth-oidc-client/issues/399)
|
||||
* The client secret. For some oidc service the must provide this.
|
||||
*/
|
||||
clientSecret?: string;
|
||||
/**
|
||||
* `code`, `id_token token` or `id_token`.
|
||||
* Name of the flow which can be configured.
|
||||
@@ -207,5 +213,5 @@ export interface OpenIdConfiguration {
|
||||
/**
|
||||
* Disable cleaning up the popup when receiving invalid messages
|
||||
*/
|
||||
disableCleaningPopupOnInvalidMessage?: boolean
|
||||
disableCleaningPopupOnInvalidMessage?: boolean;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { mockProvider } from '../../../test/auto-mock';
|
||||
import { TestBed } from '@/testing';
|
||||
import { mockImplementationWhenArgs, spyOnWithOrigin } from '@/testing/spy';
|
||||
import { vi } from 'vitest';
|
||||
import { LogLevel } from '../../logging/log-level';
|
||||
import { LoggerService } from '../../logging/logger.service';
|
||||
import { OpenIdConfiguration } from '../openid-configuration';
|
||||
import { mockProvider } from '../../testing/mock';
|
||||
import type { OpenIdConfiguration } from '../openid-configuration';
|
||||
import { ConfigValidationService } from './config-validation.service';
|
||||
import { allMultipleConfigRules } from './rules';
|
||||
|
||||
@@ -14,6 +16,8 @@ describe('Config Validation Service', () => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [ConfigValidationService, mockProvider(LoggerService)],
|
||||
});
|
||||
configValidationService = TestBed.inject(ConfigValidationService);
|
||||
loggerService = TestBed.inject(LoggerService);
|
||||
});
|
||||
|
||||
const VALID_CONFIG = {
|
||||
@@ -29,11 +33,6 @@ describe('Config Validation Service', () => {
|
||||
logLevel: LogLevel.Debug,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
configValidationService = TestBed.inject(ConfigValidationService);
|
||||
loggerService = TestBed.inject(LoggerService);
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(configValidationService).toBeTruthy();
|
||||
});
|
||||
@@ -42,26 +41,27 @@ describe('Config Validation Service', () => {
|
||||
const config = {};
|
||||
const result = configValidationService.validateConfig(config);
|
||||
|
||||
expect(result).toBeFalse();
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return true for valid config', () => {
|
||||
const result = configValidationService.validateConfig(VALID_CONFIG);
|
||||
|
||||
expect(result).toBeTrue();
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
|
||||
it('calls `logWarning` if one rule has warning level', () => {
|
||||
const loggerWarningSpy = spyOn(loggerService, 'logWarning');
|
||||
const messageTypeSpy = spyOn(
|
||||
configValidationService as any,
|
||||
'getAllMessagesOfType'
|
||||
const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning');
|
||||
const messageTypeSpy = spyOnWithOrigin(
|
||||
configValidationService,
|
||||
'getAllMessagesOfType' as any
|
||||
);
|
||||
|
||||
messageTypeSpy
|
||||
.withArgs('warning', jasmine.any(Array))
|
||||
.and.returnValue(['A warning message']);
|
||||
messageTypeSpy.withArgs('error', jasmine.any(Array)).and.callThrough();
|
||||
mockImplementationWhenArgs(
|
||||
messageTypeSpy,
|
||||
(arg1: any, arg2: any) => arg1 === 'warning' && Array.isArray(arg2),
|
||||
() => ['A warning message']
|
||||
);
|
||||
|
||||
configValidationService.validateConfig(VALID_CONFIG);
|
||||
expect(loggerWarningSpy).toHaveBeenCalled();
|
||||
@@ -72,7 +72,7 @@ describe('Config Validation Service', () => {
|
||||
const config = { ...VALID_CONFIG, clientId: '' } as OpenIdConfiguration;
|
||||
const result = configValidationService.validateConfig(config);
|
||||
|
||||
expect(result).toBeFalse();
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -84,7 +84,7 @@ describe('Config Validation Service', () => {
|
||||
} as OpenIdConfiguration;
|
||||
const result = configValidationService.validateConfig(config);
|
||||
|
||||
expect(result).toBeFalse();
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -93,7 +93,7 @@ describe('Config Validation Service', () => {
|
||||
const config = { ...VALID_CONFIG, redirectUrl: '' };
|
||||
const result = configValidationService.validateConfig(config);
|
||||
|
||||
expect(result).toBeFalse();
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -107,7 +107,7 @@ describe('Config Validation Service', () => {
|
||||
} as OpenIdConfiguration;
|
||||
const result = configValidationService.validateConfig(config);
|
||||
|
||||
expect(result).toBeFalse();
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -120,12 +120,12 @@ describe('Config Validation Service', () => {
|
||||
scopes: 'scope1 scope2 but_no_offline_access',
|
||||
};
|
||||
|
||||
const loggerSpy = spyOn(loggerService, 'logError');
|
||||
const loggerWarningSpy = spyOn(loggerService, 'logWarning');
|
||||
const loggerSpy = vi.spyOn(loggerService, 'logError');
|
||||
const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning');
|
||||
|
||||
const result = configValidationService.validateConfig(config);
|
||||
|
||||
expect(result).toBeTrue();
|
||||
expect(result).toBeTruthy();
|
||||
expect(loggerSpy).not.toHaveBeenCalled();
|
||||
expect(loggerWarningSpy).toHaveBeenCalled();
|
||||
});
|
||||
@@ -146,47 +146,47 @@ describe('Config Validation Service', () => {
|
||||
scopes: 'scope1 scope2 but_no_offline_access',
|
||||
};
|
||||
|
||||
const loggerErrorSpy = spyOn(loggerService, 'logError');
|
||||
const loggerWarningSpy = spyOn(loggerService, 'logWarning');
|
||||
const loggerErrorSpy = vi.spyOn(loggerService, 'logError');
|
||||
const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning');
|
||||
|
||||
const result = configValidationService.validateConfigs([
|
||||
config1,
|
||||
config2,
|
||||
]);
|
||||
|
||||
expect(result).toBeTrue();
|
||||
expect(result).toBeTruthy();
|
||||
expect(loggerErrorSpy).not.toHaveBeenCalled();
|
||||
expect(loggerWarningSpy.calls.argsFor(0)).toEqual([
|
||||
expect(vi.mocked(loggerWarningSpy).mock.calls[0]).toEqual([
|
||||
config1,
|
||||
'You added multiple configs with the same authority, clientId and scope',
|
||||
]);
|
||||
expect(loggerWarningSpy.calls.argsFor(1)).toEqual([
|
||||
expect(vi.mocked(loggerWarningSpy).mock.calls[1]).toEqual([
|
||||
config2,
|
||||
'You added multiple configs with the same authority, clientId and scope',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return false and a better error message when config is not passed as object with config property', () => {
|
||||
const loggerWarningSpy = spyOn(loggerService, 'logWarning');
|
||||
const loggerWarningSpy = vi.spyOn(loggerService, 'logWarning');
|
||||
|
||||
const result = configValidationService.validateConfigs([]);
|
||||
|
||||
expect(result).toBeFalse();
|
||||
expect(result).toBeFalsy();
|
||||
expect(loggerWarningSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateConfigs', () => {
|
||||
it('calls internal method with empty array if something falsy is passed', () => {
|
||||
const spy = spyOn(
|
||||
const spy = vi.spyOn(
|
||||
configValidationService as any,
|
||||
'validateConfigsInternal'
|
||||
).and.callThrough();
|
||||
);
|
||||
|
||||
const result = configValidationService.validateConfigs([]);
|
||||
|
||||
expect(result).toBeFalse();
|
||||
expect(spy).toHaveBeenCalledOnceWith([], allMultipleConfigRules);
|
||||
expect(result).toBeFalsy();
|
||||
expect(spy).toHaveBeenCalledExactlyOnceWith([], allMultipleConfigRules);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { inject, Injectable } from 'injection-js';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import { LoggerService } from '../../logging/logger.service';
|
||||
import { OpenIdConfiguration } from '../openid-configuration';
|
||||
import { Level, RuleValidationResult } from './rule';
|
||||
import type { OpenIdConfiguration } from '../openid-configuration';
|
||||
import type { Level, RuleValidationResult } from './rule';
|
||||
import { allMultipleConfigRules, allRules } from './rules';
|
||||
|
||||
@Injectable()
|
||||
@@ -35,14 +35,14 @@ export class ConfigValidationService {
|
||||
|
||||
let overallErrorCount = 0;
|
||||
|
||||
passedConfigs.forEach((passedConfig) => {
|
||||
for (const passedConfig of passedConfigs) {
|
||||
const errorCount = this.processValidationResultsAndGetErrorCount(
|
||||
allValidationResults,
|
||||
passedConfig
|
||||
);
|
||||
|
||||
overallErrorCount += errorCount;
|
||||
});
|
||||
}
|
||||
|
||||
return overallErrorCount === 0;
|
||||
}
|
||||
@@ -75,17 +75,17 @@ export class ConfigValidationService {
|
||||
const allErrorMessages = this.getAllMessagesOfType('error', allMessages);
|
||||
const allWarnings = this.getAllMessagesOfType('warning', allMessages);
|
||||
|
||||
allErrorMessages.forEach((message) =>
|
||||
this.loggerService.logError(config, message)
|
||||
);
|
||||
allWarnings.forEach((message) =>
|
||||
this.loggerService.logWarning(config, message)
|
||||
);
|
||||
for (const message of allErrorMessages) {
|
||||
this.loggerService.logError(config, message);
|
||||
}
|
||||
for (const message of allWarnings) {
|
||||
this.loggerService.logWarning(config, message);
|
||||
}
|
||||
|
||||
return allErrorMessages.length;
|
||||
}
|
||||
|
||||
private getAllMessagesOfType(
|
||||
protected getAllMessagesOfType(
|
||||
type: Level,
|
||||
results: RuleValidationResult[]
|
||||
): string[] {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OpenIdConfiguration } from '../openid-configuration';
|
||||
import type { OpenIdConfiguration } from '../openid-configuration';
|
||||
|
||||
export interface Rule {
|
||||
validate(passedConfig: OpenIdConfiguration): RuleValidationResult;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { OpenIdConfiguration } from '../../openid-configuration';
|
||||
import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule';
|
||||
import type { OpenIdConfiguration } from '../../openid-configuration';
|
||||
import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule';
|
||||
|
||||
export const ensureAuthority = (
|
||||
passedConfig: OpenIdConfiguration
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { OpenIdConfiguration } from '../../openid-configuration';
|
||||
import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule';
|
||||
import type { OpenIdConfiguration } from '../../openid-configuration';
|
||||
import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule';
|
||||
|
||||
export const ensureClientId = (
|
||||
passedConfig: OpenIdConfiguration
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { OpenIdConfiguration } from '../../openid-configuration';
|
||||
import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule';
|
||||
import type { OpenIdConfiguration } from '../../openid-configuration';
|
||||
import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule';
|
||||
|
||||
const createIdentifierToCheck = (passedConfig: OpenIdConfiguration): string => {
|
||||
if (!passedConfig) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { OpenIdConfiguration } from '../../openid-configuration';
|
||||
import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule';
|
||||
import type { OpenIdConfiguration } from '../../openid-configuration';
|
||||
import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule';
|
||||
|
||||
export const ensureRedirectRule = (
|
||||
passedConfig: OpenIdConfiguration
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { OpenIdConfiguration } from '../../openid-configuration';
|
||||
import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule';
|
||||
import type { OpenIdConfiguration } from '../../openid-configuration';
|
||||
import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule';
|
||||
|
||||
export const ensureSilentRenewUrlWhenNoRefreshTokenUsed = (
|
||||
passedConfig: OpenIdConfiguration
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { OpenIdConfiguration } from '../../openid-configuration';
|
||||
import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule';
|
||||
import type { OpenIdConfiguration } from '../../openid-configuration';
|
||||
import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule';
|
||||
|
||||
export const useOfflineScopeWithSilentRenew = (
|
||||
passedConfig: OpenIdConfiguration
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { InjectionToken } from "injection-js";
|
||||
import { InjectionToken } from '@outposts/injection-js';
|
||||
|
||||
export const DOCUMENT = new InjectionToken<Document>('document');
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { TestBed } from '@/testing';
|
||||
import { CryptoService } from '../utils/crypto/crypto.service';
|
||||
import {
|
||||
JwkExtractor,
|
||||
@@ -93,9 +93,6 @@ describe('JwkExtractor', () => {
|
||||
imports: [],
|
||||
providers: [JwkExtractor, CryptoService],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.inject(JwkExtractor);
|
||||
});
|
||||
|
||||
@@ -105,21 +102,30 @@ describe('JwkExtractor', () => {
|
||||
|
||||
describe('extractJwk', () => {
|
||||
it('throws error if no keys are present in array', () => {
|
||||
expect(() => {
|
||||
try {
|
||||
service.extractJwk([]);
|
||||
}).toThrow(JwkExtractorInvalidArgumentError);
|
||||
expect.fail('should error');
|
||||
} catch (error: any) {
|
||||
expect(error).toBe(JwkExtractorInvalidArgumentError);
|
||||
}
|
||||
});
|
||||
|
||||
it('throws error if spec.kid is present, but no key was matching', () => {
|
||||
expect(() => {
|
||||
try {
|
||||
service.extractJwk(keys, { kid: 'doot' });
|
||||
}).toThrow(JwkExtractorNoMatchingKeysError);
|
||||
expect.fail('should error');
|
||||
} catch (error: any) {
|
||||
expect(error).toBe(JwkExtractorNoMatchingKeysError);
|
||||
}
|
||||
});
|
||||
|
||||
it('throws error if spec.use is present, but no key was matching', () => {
|
||||
expect(() => {
|
||||
try {
|
||||
service.extractJwk(keys, { use: 'blorp' });
|
||||
}).toThrow(JwkExtractorNoMatchingKeysError);
|
||||
expect.fail('should error');
|
||||
} catch (error: any) {
|
||||
expect(error).toBe(JwkExtractorNoMatchingKeysError);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not throw error if no key is matching when throwOnEmpty is false', () => {
|
||||
@@ -129,9 +135,12 @@ describe('JwkExtractor', () => {
|
||||
});
|
||||
|
||||
it('throws error if multiple keys are present, and spec is not present', () => {
|
||||
expect(() => {
|
||||
try {
|
||||
service.extractJwk(keys);
|
||||
}).toThrow(JwkExtractorSeveralMatchingKeysError);
|
||||
expect.fail('should error');
|
||||
} catch (error: any) {
|
||||
expect(error).toBe(JwkExtractorSeveralMatchingKeysError);
|
||||
}
|
||||
});
|
||||
|
||||
it('returns array of keys matching spec.kid', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Injectable } from 'injection-js';
|
||||
import { Injectable } from '@outposts/injection-js';
|
||||
|
||||
@Injectable()
|
||||
export class JwkExtractor {
|
||||
@@ -7,20 +7,20 @@ export class JwkExtractor {
|
||||
spec?: { kid?: string; use?: string; kty?: string },
|
||||
throwOnEmpty = true
|
||||
): JsonWebKey[] {
|
||||
if (0 === keys.length) {
|
||||
if (keys.length === 0) {
|
||||
throw JwkExtractorInvalidArgumentError;
|
||||
}
|
||||
|
||||
const foundKeys = keys
|
||||
.filter((k) => (spec?.kid ? (k as any)['kid'] === spec.kid : true))
|
||||
.filter((k) => (spec?.use ? k['use'] === spec.use : true))
|
||||
.filter((k) => (spec?.kty ? k['kty'] === spec.kty : true));
|
||||
.filter((k) => (spec?.kid ? (k as any).kid === spec.kid : true))
|
||||
.filter((k) => (spec?.use ? k.use === spec.use : true))
|
||||
.filter((k) => (spec?.kty ? k.kty === spec.kty : true));
|
||||
|
||||
if (foundKeys.length === 0 && throwOnEmpty) {
|
||||
throw JwkExtractorNoMatchingKeysError;
|
||||
}
|
||||
|
||||
if (foundKeys.length > 1 && (null === spec || undefined === spec)) {
|
||||
if (foundKeys.length > 1 && (spec === null || undefined === spec)) {
|
||||
throw JwkExtractorSeveralMatchingKeysError;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export class JwkExtractor {
|
||||
}
|
||||
|
||||
function buildErrorName(name: string): string {
|
||||
return JwkExtractor.name + ': ' + name;
|
||||
return `${JwkExtractor.name}: ${name}`;
|
||||
}
|
||||
|
||||
export const JwkExtractorInvalidArgumentError = {
|
||||
|
||||
131
src/features/core.ts
Normal file
131
src/features/core.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import type { HttpFeature } from '@ngify/http';
|
||||
import type { Provider } from '@outposts/injection-js';
|
||||
import { DOCUMENT } from '../dom';
|
||||
import { provideHttpClient } from '../http';
|
||||
import {
|
||||
AbstractRouter,
|
||||
VanillaHistoryRouter,
|
||||
VanillaLocationRouter,
|
||||
} from '../router';
|
||||
import { AbstractSecurityStorage } from '../storage/abstract-security-storage';
|
||||
import { DefaultLocalStorageService } from '../storage/default-localstorage.service';
|
||||
import { DefaultSessionStorageService } from '../storage/default-sessionstorage.service';
|
||||
import { PLATFORM_ID } from '../utils/platform-provider/platform.provider';
|
||||
|
||||
/**
|
||||
* A feature to be used with `provideAuth`.
|
||||
*/
|
||||
export interface AuthFeature {
|
||||
ɵproviders: Provider[];
|
||||
}
|
||||
|
||||
export interface BrowserPlatformFeatureOptions {
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export function withBrowserPlatform({
|
||||
enabled = true,
|
||||
}: BrowserPlatformFeatureOptions = {}): AuthFeature {
|
||||
return {
|
||||
ɵproviders: enabled
|
||||
? [
|
||||
{
|
||||
provide: DOCUMENT,
|
||||
useFactory: () => document,
|
||||
},
|
||||
{
|
||||
provide: PLATFORM_ID,
|
||||
useValue: 'browser',
|
||||
},
|
||||
]
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
export interface HttpClientFeatureOptions {
|
||||
enabled?: boolean;
|
||||
features?: HttpFeature[];
|
||||
}
|
||||
|
||||
export function withHttpClient({
|
||||
features,
|
||||
enabled = true,
|
||||
}: HttpClientFeatureOptions = {}): AuthFeature {
|
||||
return {
|
||||
ɵproviders: enabled ? provideHttpClient(features) : [],
|
||||
};
|
||||
}
|
||||
|
||||
export type SecurityStorageType = 'session-storage' | 'local-storage';
|
||||
|
||||
export interface SecurityStorageFeatureOptions {
|
||||
enabled?: boolean;
|
||||
type?: SecurityStorageType;
|
||||
}
|
||||
|
||||
export function withSecurityStorage({
|
||||
enabled = true,
|
||||
type = 'session-storage',
|
||||
}: SecurityStorageFeatureOptions = {}): AuthFeature {
|
||||
return {
|
||||
ɵproviders: enabled
|
||||
? [
|
||||
type === 'session-storage'
|
||||
? {
|
||||
provide: AbstractSecurityStorage,
|
||||
useClass: DefaultLocalStorageService,
|
||||
}
|
||||
: {
|
||||
provide: AbstractSecurityStorage,
|
||||
useClass: DefaultSessionStorageService,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
export type VanillaRouterType = 'location' | 'history';
|
||||
|
||||
export interface VanillaRouterFeatureOptions {
|
||||
enabled?: boolean;
|
||||
type?: VanillaRouterType;
|
||||
}
|
||||
|
||||
export function withVanillaRouter({
|
||||
enabled = true,
|
||||
type = 'history',
|
||||
}: VanillaRouterFeatureOptions = {}): AuthFeature {
|
||||
return {
|
||||
ɵproviders: enabled
|
||||
? [
|
||||
type === 'location'
|
||||
? {
|
||||
provide: AbstractRouter,
|
||||
useClass: VanillaLocationRouter,
|
||||
}
|
||||
: {
|
||||
provide: AbstractRouter,
|
||||
useClass: VanillaHistoryRouter,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
export interface DefaultFeaturesOptions {
|
||||
browserPlatform?: BrowserPlatformFeatureOptions;
|
||||
securityStorage?: SecurityStorageFeatureOptions;
|
||||
router?: VanillaRouterFeatureOptions;
|
||||
httpClient?: HttpClientFeatureOptions;
|
||||
}
|
||||
|
||||
export function withDefaultFeatures(
|
||||
options: DefaultFeaturesOptions = {}
|
||||
): AuthFeature[] {
|
||||
return [
|
||||
withBrowserPlatform(options.browserPlatform),
|
||||
withSecurityStorage(options.securityStorage),
|
||||
withHttpClient(options.httpClient),
|
||||
withVanillaRouter(options.router),
|
||||
].filter(Boolean) as AuthFeature[];
|
||||
}
|
||||
8
src/features/index.ts
Normal file
8
src/features/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export type * from './core';
|
||||
export * from './core';
|
||||
export {
|
||||
CHECK_AUTH_RESULT_EVENT,
|
||||
withCheckAuthResultEvent,
|
||||
type CheckAuthResultEventType,
|
||||
type WithCheckAuthResultEventProps,
|
||||
} from './with-check-auth-result-event';
|
||||
45
src/features/with-check-auth-result-event.ts
Normal file
45
src/features/with-check-auth-result-event.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { InjectionToken, inject } from '@outposts/injection-js';
|
||||
import { type Observable, filter, shareReplay } from 'rxjs';
|
||||
import { EventTypes } from '../public-events/event-types';
|
||||
import { PublicEventsService } from '../public-events/public-events.service';
|
||||
import type { AuthFeature } from './core';
|
||||
|
||||
export type CheckAuthResultEventType =
|
||||
| { type: EventTypes.CheckingAuthFinished }
|
||||
| {
|
||||
type: EventTypes.CheckingAuthFinishedWithError;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export const CHECK_AUTH_RESULT_EVENT = new InjectionToken<
|
||||
Observable<CheckAuthResultEventType>
|
||||
>('CHECK_AUTH_RESULT_EVENT');
|
||||
|
||||
export interface WithCheckAuthResultEventProps {
|
||||
shareReplayCount?: number;
|
||||
}
|
||||
|
||||
export function withCheckAuthResultEvent({
|
||||
shareReplayCount = 1,
|
||||
}: WithCheckAuthResultEventProps = {}): AuthFeature {
|
||||
return {
|
||||
ɵproviders: [
|
||||
{
|
||||
provide: CHECK_AUTH_RESULT_EVENT,
|
||||
useFactory: () => {
|
||||
const publishEventService = inject(PublicEventsService);
|
||||
|
||||
return publishEventService.registerForEvents().pipe(
|
||||
filter(
|
||||
(e) =>
|
||||
e.type === EventTypes.CheckingAuthFinishedWithError ||
|
||||
e.type === EventTypes.CheckingAuthFinished
|
||||
),
|
||||
shareReplay(shareReplayCount)
|
||||
);
|
||||
},
|
||||
deps: [PublicEventsService],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { JwtKeys } from '../validation/jwtkeys';
|
||||
import { StateValidationResult } from '../validation/state-validation-result';
|
||||
import type { JwtKeys } from '../validation/jwtkeys';
|
||||
import type { StateValidationResult } from '../validation/state-validation-result';
|
||||
|
||||
export interface CallbackContext {
|
||||
code: string;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { mockProvider } from '../../../test/auto-mock';
|
||||
import { createRetriableStream } from '../../../test/create-retriable-stream.helper';
|
||||
import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
|
||||
import { HttpErrorResponse, HttpHeaders } from '@ngify/http';
|
||||
import { firstValueFrom, of, throwError } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import { DataService } from '../../api/data.service';
|
||||
import { LoggerService } from '../../logging/logger.service';
|
||||
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
||||
import { createRetriableStream } from '../../testing/create-retriable-stream.helper';
|
||||
import { mockProvider } from '../../testing/mock';
|
||||
import { UrlService } from '../../utils/url/url.service';
|
||||
import { TokenValidationService } from '../../validation/token-validation.service';
|
||||
import { CallbackContext } from '../callback-context';
|
||||
import type { CallbackContext } from '../callback-context';
|
||||
import { FlowsDataService } from '../flows-data.service';
|
||||
import { CodeFlowCallbackHandlerService } from './code-flow-callback-handler.service';
|
||||
|
||||
@@ -31,9 +32,6 @@ describe('CodeFlowCallbackHandlerService', () => {
|
||||
mockProvider(DataService),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.inject(CodeFlowCallbackHandlerService);
|
||||
dataService = TestBed.inject(DataService);
|
||||
urlService = TestBed.inject(UrlService);
|
||||
@@ -46,42 +44,48 @@ describe('CodeFlowCallbackHandlerService', () => {
|
||||
});
|
||||
|
||||
describe('codeFlowCallback', () => {
|
||||
it('throws error if no state is given', waitForAsync(() => {
|
||||
const getUrlParameterSpy = spyOn(
|
||||
urlService,
|
||||
'getUrlParameter'
|
||||
).and.returnValue('params');
|
||||
it('throws error if no state is given', async () => {
|
||||
const getUrlParameterSpy = vi
|
||||
.spyOn(urlService, 'getUrlParameter')
|
||||
.mockReturnValue('params');
|
||||
|
||||
getUrlParameterSpy.withArgs('test-url', 'state').and.returnValue('');
|
||||
mockImplementationWhenArgsEqual(
|
||||
getUrlParameterSpy,
|
||||
['test-url', 'state'],
|
||||
() => ''
|
||||
);
|
||||
|
||||
service
|
||||
.codeFlowCallback('test-url', { configId: 'configId1' })
|
||||
.subscribe({
|
||||
error: (err) => {
|
||||
try {
|
||||
await firstValueFrom(
|
||||
service.codeFlowCallback('test-url', { configId: 'configId1' })
|
||||
);
|
||||
} catch (err: any) {
|
||||
expect(err).toBeTruthy();
|
||||
},
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
it('throws error if no code is given', waitForAsync(() => {
|
||||
const getUrlParameterSpy = spyOn(
|
||||
urlService,
|
||||
'getUrlParameter'
|
||||
).and.returnValue('params');
|
||||
it('throws error if no code is given', async () => {
|
||||
const getUrlParameterSpy = vi
|
||||
.spyOn(urlService, 'getUrlParameter')
|
||||
.mockReturnValue('params');
|
||||
|
||||
getUrlParameterSpy.withArgs('test-url', 'code').and.returnValue('');
|
||||
mockImplementationWhenArgsEqual(
|
||||
getUrlParameterSpy,
|
||||
['test-url', 'code'],
|
||||
() => ''
|
||||
);
|
||||
|
||||
service
|
||||
.codeFlowCallback('test-url', { configId: 'configId1' })
|
||||
.subscribe({
|
||||
error: (err) => {
|
||||
try {
|
||||
await firstValueFrom(
|
||||
service.codeFlowCallback('test-url', { configId: 'configId1' })
|
||||
);
|
||||
} catch (err: any) {
|
||||
expect(err).toBeTruthy();
|
||||
},
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
it('returns callbackContext if all params are good', waitForAsync(() => {
|
||||
spyOn(urlService, 'getUrlParameter').and.returnValue('params');
|
||||
it('returns callbackContext if all params are good', async () => {
|
||||
vi.spyOn(urlService, 'getUrlParameter').mockReturnValue('params');
|
||||
|
||||
const expectedCallbackContext = {
|
||||
code: 'params',
|
||||
@@ -95,12 +99,11 @@ describe('CodeFlowCallbackHandlerService', () => {
|
||||
existingIdToken: null,
|
||||
} as CallbackContext;
|
||||
|
||||
service
|
||||
.codeFlowCallback('test-url', { configId: 'configId1' })
|
||||
.subscribe((callbackContext) => {
|
||||
const callbackContext = await firstValueFrom(
|
||||
service.codeFlowCallback('test-url', { configId: 'configId1' })
|
||||
);
|
||||
expect(callbackContext).toEqual(expectedCallbackContext);
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('codeFlowCodeRequest ', () => {
|
||||
@@ -112,83 +115,96 @@ describe('CodeFlowCallbackHandlerService', () => {
|
||||
url: 'https://identity-server.test/openid-connect/token',
|
||||
});
|
||||
|
||||
it('throws error if state is not correct', waitForAsync(() => {
|
||||
spyOn(
|
||||
it('throws error if state is not correct', async () => {
|
||||
vi.spyOn(
|
||||
tokenValidationService,
|
||||
'validateStateFromHashCallback'
|
||||
).and.returnValue(false);
|
||||
).mockReturnValue(false);
|
||||
|
||||
service
|
||||
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' })
|
||||
.subscribe({
|
||||
error: (err) => {
|
||||
try {
|
||||
await firstValueFrom(
|
||||
service.codeFlowCodeRequest({} as CallbackContext, {
|
||||
configId: 'configId1',
|
||||
})
|
||||
);
|
||||
} catch (err: any) {
|
||||
expect(err).toBeTruthy();
|
||||
},
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
it('throws error if authWellknownEndpoints is null is given', waitForAsync(() => {
|
||||
spyOn(
|
||||
it('throws error if authWellknownEndpoints is null is given', async () => {
|
||||
vi.spyOn(
|
||||
tokenValidationService,
|
||||
'validateStateFromHashCallback'
|
||||
).and.returnValue(true);
|
||||
spyOn(storagePersistenceService, 'read')
|
||||
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||
.and.returnValue(null);
|
||||
).mockReturnValue(true);
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'read'),
|
||||
['authWellKnownEndPoints', { configId: 'configId1' }],
|
||||
() => null
|
||||
);
|
||||
|
||||
service
|
||||
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' })
|
||||
.subscribe({
|
||||
error: (err) => {
|
||||
try {
|
||||
await firstValueFrom(
|
||||
service.codeFlowCodeRequest({} as CallbackContext, {
|
||||
configId: 'configId1',
|
||||
})
|
||||
);
|
||||
} catch (err: any) {
|
||||
expect(err).toBeTruthy();
|
||||
},
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
it('throws error if tokenendpoint is null is given', waitForAsync(() => {
|
||||
spyOn(
|
||||
it('throws error if tokenendpoint is null is given', async () => {
|
||||
vi.spyOn(
|
||||
tokenValidationService,
|
||||
'validateStateFromHashCallback'
|
||||
).and.returnValue(true);
|
||||
spyOn(storagePersistenceService, 'read')
|
||||
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||
.and.returnValue({ tokenEndpoint: null });
|
||||
).mockReturnValue(true);
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'read'),
|
||||
['authWellKnownEndPoints', { configId: 'configId1' }],
|
||||
() => ({ tokenEndpoint: null })
|
||||
);
|
||||
|
||||
service
|
||||
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' })
|
||||
.subscribe({
|
||||
error: (err) => {
|
||||
try {
|
||||
await firstValueFrom(
|
||||
service.codeFlowCodeRequest({} as CallbackContext, {
|
||||
configId: 'configId1',
|
||||
})
|
||||
);
|
||||
} catch (err: any) {
|
||||
expect(err).toBeTruthy();
|
||||
},
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
it('calls dataService if all params are good', waitForAsync(() => {
|
||||
const postSpy = spyOn(dataService, 'post').and.returnValue(of({}));
|
||||
it('calls dataService if all params are good', async () => {
|
||||
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
|
||||
|
||||
spyOn(storagePersistenceService, 'read')
|
||||
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'read'),
|
||||
['authWellKnownEndPoints', { configId: 'configId1' }],
|
||||
() => ({ tokenEndpoint: 'tokenEndpoint' })
|
||||
);
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
tokenValidationService,
|
||||
'validateStateFromHashCallback'
|
||||
).and.returnValue(true);
|
||||
).mockReturnValue(true);
|
||||
|
||||
service
|
||||
.codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' })
|
||||
.subscribe(() => {
|
||||
expect(postSpy).toHaveBeenCalledOnceWith(
|
||||
await firstValueFrom(
|
||||
service.codeFlowCodeRequest({} as CallbackContext, {
|
||||
configId: 'configId1',
|
||||
})
|
||||
);
|
||||
expect(postSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
'tokenEndpoint',
|
||||
undefined,
|
||||
{ configId: 'configId1' },
|
||||
jasmine.any(HttpHeaders)
|
||||
expect.any(HttpHeaders)
|
||||
);
|
||||
});
|
||||
}));
|
||||
|
||||
it('calls url service with custom token params', waitForAsync(() => {
|
||||
const urlServiceSpy = spyOn(
|
||||
it('calls url service with custom token params', async () => {
|
||||
const urlServiceSpy = vi.spyOn(
|
||||
urlService,
|
||||
'createBodyForCodeFlowCodeRequest'
|
||||
);
|
||||
@@ -197,76 +213,83 @@ describe('CodeFlowCallbackHandlerService', () => {
|
||||
customParamsCodeRequest: { foo: 'bar' },
|
||||
};
|
||||
|
||||
spyOn(storagePersistenceService, 'read')
|
||||
.withArgs('authWellKnownEndPoints', config)
|
||||
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'read'),
|
||||
['authWellKnownEndPoints', config],
|
||||
() => ({ tokenEndpoint: 'tokenEndpoint' })
|
||||
);
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
tokenValidationService,
|
||||
'validateStateFromHashCallback'
|
||||
).and.returnValue(true);
|
||||
).mockReturnValue(true);
|
||||
|
||||
const postSpy = spyOn(dataService, 'post').and.returnValue(of({}));
|
||||
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
|
||||
|
||||
service
|
||||
.codeFlowCodeRequest({ code: 'foo' } as CallbackContext, config)
|
||||
.subscribe(() => {
|
||||
expect(urlServiceSpy).toHaveBeenCalledOnceWith('foo', config, {
|
||||
await firstValueFrom(
|
||||
service.codeFlowCodeRequest({ code: 'foo' } as CallbackContext, config)
|
||||
);
|
||||
expect(urlServiceSpy).toHaveBeenCalledExactlyOnceWith('foo', config, {
|
||||
foo: 'bar',
|
||||
});
|
||||
expect(postSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
}));
|
||||
|
||||
it('calls dataService with correct headers if all params are good', waitForAsync(() => {
|
||||
const postSpy = spyOn(dataService, 'post').and.returnValue(of({}));
|
||||
it('calls dataService with correct headers if all params are good', async () => {
|
||||
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({}));
|
||||
const config = {
|
||||
configId: 'configId1',
|
||||
customParamsCodeRequest: { foo: 'bar' },
|
||||
};
|
||||
|
||||
spyOn(storagePersistenceService, 'read')
|
||||
.withArgs('authWellKnownEndPoints', config)
|
||||
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'read'),
|
||||
['authWellKnownEndPoints', config],
|
||||
() => ({ tokenEndpoint: 'tokenEndpoint' })
|
||||
);
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
tokenValidationService,
|
||||
'validateStateFromHashCallback'
|
||||
).and.returnValue(true);
|
||||
).mockReturnValue(true);
|
||||
|
||||
service
|
||||
.codeFlowCodeRequest({} as CallbackContext, config)
|
||||
.subscribe(() => {
|
||||
const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;
|
||||
|
||||
expect(httpHeaders.has('Content-Type')).toBeTrue();
|
||||
await firstValueFrom(
|
||||
service.codeFlowCodeRequest({} as CallbackContext, config)
|
||||
);
|
||||
const httpHeaders = postSpy.mock.calls.at(-1)?.[3] as HttpHeaders;
|
||||
expect(httpHeaders.has('Content-Type')).toBeTruthy();
|
||||
expect(httpHeaders.get('Content-Type')).toBe(
|
||||
'application/x-www-form-urlencoded'
|
||||
);
|
||||
});
|
||||
}));
|
||||
|
||||
it('returns error in case of http error', waitForAsync(() => {
|
||||
spyOn(dataService, 'post').and.returnValue(throwError(() => HTTP_ERROR));
|
||||
it('returns error in case of http error', async () => {
|
||||
vi.spyOn(dataService, 'post').mockReturnValue(
|
||||
throwError(() => HTTP_ERROR)
|
||||
);
|
||||
const config = {
|
||||
configId: 'configId1',
|
||||
customParamsCodeRequest: { foo: 'bar' },
|
||||
authority: 'authority',
|
||||
};
|
||||
|
||||
spyOn(storagePersistenceService, 'read')
|
||||
.withArgs('authWellKnownEndPoints', config)
|
||||
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'read'),
|
||||
['authWellKnownEndPoints', config],
|
||||
() => ({ tokenEndpoint: 'tokenEndpoint' })
|
||||
);
|
||||
|
||||
service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({
|
||||
error: (err) => {
|
||||
try {
|
||||
await firstValueFrom(
|
||||
service.codeFlowCodeRequest({} as CallbackContext, config)
|
||||
);
|
||||
} catch (err: any) {
|
||||
expect(err).toBeTruthy();
|
||||
},
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
it('retries request in case of no connection http error and succeeds', waitForAsync(() => {
|
||||
const postSpy = spyOn(dataService, 'post').and.returnValue(
|
||||
it('retries request in case of no connection http error and succeeds', async () => {
|
||||
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(
|
||||
createRetriableStream(
|
||||
throwError(() => CONNECTION_ERROR),
|
||||
of({})
|
||||
@@ -278,29 +301,30 @@ describe('CodeFlowCallbackHandlerService', () => {
|
||||
authority: 'authority',
|
||||
};
|
||||
|
||||
spyOn(storagePersistenceService, 'read')
|
||||
.withArgs('authWellKnownEndPoints', config)
|
||||
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'read'),
|
||||
['authWellKnownEndPoints', config],
|
||||
() => ({ tokenEndpoint: 'tokenEndpoint' })
|
||||
);
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
tokenValidationService,
|
||||
'validateStateFromHashCallback'
|
||||
).and.returnValue(true);
|
||||
).mockReturnValue(true);
|
||||
|
||||
service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({
|
||||
next: (res) => {
|
||||
try {
|
||||
const res = await firstValueFrom(
|
||||
service.codeFlowCodeRequest({} as CallbackContext, config)
|
||||
);
|
||||
expect(res).toBeTruthy();
|
||||
expect(postSpy).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
error: (err) => {
|
||||
// fails if there should be a result
|
||||
} catch (err: any) {
|
||||
expect(err).toBeFalsy();
|
||||
},
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
it('retries request in case of no connection http error and fails because of http error afterwards', waitForAsync(() => {
|
||||
const postSpy = spyOn(dataService, 'post').and.returnValue(
|
||||
it('retries request in case of no connection http error and fails because of http error afterwards', async () => {
|
||||
const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(
|
||||
createRetriableStream(
|
||||
throwError(() => CONNECTION_ERROR),
|
||||
throwError(() => HTTP_ERROR)
|
||||
@@ -312,25 +336,26 @@ describe('CodeFlowCallbackHandlerService', () => {
|
||||
authority: 'authority',
|
||||
};
|
||||
|
||||
spyOn(storagePersistenceService, 'read')
|
||||
.withArgs('authWellKnownEndPoints', config)
|
||||
.and.returnValue({ tokenEndpoint: 'tokenEndpoint' });
|
||||
mockImplementationWhenArgsEqual(
|
||||
vi.spyOn(storagePersistenceService, 'read'),
|
||||
['authWellKnownEndPoints', config],
|
||||
() => ({ tokenEndpoint: 'tokenEndpoint' })
|
||||
);
|
||||
|
||||
spyOn(
|
||||
vi.spyOn(
|
||||
tokenValidationService,
|
||||
'validateStateFromHashCallback'
|
||||
).and.returnValue(true);
|
||||
).mockReturnValue(true);
|
||||
|
||||
service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({
|
||||
next: (res) => {
|
||||
// fails if there should be a result
|
||||
try {
|
||||
const res = await firstValueFrom(
|
||||
service.codeFlowCodeRequest({} as CallbackContext, config)
|
||||
);
|
||||
expect(res).toBeFalsy();
|
||||
},
|
||||
error: (err) => {
|
||||
} catch (err: any) {
|
||||
expect(err).toBeTruthy();
|
||||
expect(postSpy).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
});
|
||||
}));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user