├── .editorconfig ├── .eslintrc.json ├── .github └── workflows │ ├── ci.yml │ ├── publish-next.yml │ ├── publish.yml │ └── release.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── angular.json ├── jest.config.js ├── package-lock.json ├── package.json ├── projects ├── angular-sdk │ ├── .eslintrc.json │ ├── README.md │ ├── ng-package.json │ ├── src │ │ ├── environment.ts │ │ ├── lib │ │ │ ├── components │ │ │ │ ├── access-key-management │ │ │ │ │ ├── access-key-management.component.spec.ts │ │ │ │ │ └── access-key-management.component.ts │ │ │ │ ├── audit-management │ │ │ │ │ ├── audit-management.component.spec.ts │ │ │ │ │ └── audit-management.component.ts │ │ │ │ ├── descope │ │ │ │ │ ├── descope.component.spec.ts │ │ │ │ │ └── descope.component.ts │ │ │ │ ├── role-management │ │ │ │ │ ├── role-management.component.spec.ts │ │ │ │ │ └── role-management.component.ts │ │ │ │ ├── sign-in-flow │ │ │ │ │ ├── sign-in-flow.component.html │ │ │ │ │ ├── sign-in-flow.component.spec.ts │ │ │ │ │ └── sign-in-flow.component.ts │ │ │ │ ├── sign-up-flow │ │ │ │ │ ├── sign-up-flow.component.html │ │ │ │ │ ├── sign-up-flow.component.spec.ts │ │ │ │ │ └── sign-up-flow.component.ts │ │ │ │ ├── sign-up-or-in-flow │ │ │ │ │ ├── sign-up-or-in-flow.component.html │ │ │ │ │ ├── sign-up-or-in-flow.component.spec.ts │ │ │ │ │ └── sign-up-or-in-flow.component.ts │ │ │ │ ├── user-management │ │ │ │ │ ├── user-management.component.spec.ts │ │ │ │ │ └── user-management.component.ts │ │ │ │ └── user-profile │ │ │ │ │ ├── user-profile.component.spec.ts │ │ │ │ │ └── user-profile.component.ts │ │ │ ├── descope-auth.module.ts │ │ │ ├── services │ │ │ │ ├── descope-auth.guard.spec.ts │ │ │ │ ├── descope-auth.guard.ts │ │ │ │ ├── descope-auth.service.spec.ts │ │ │ │ ├── descope-auth.service.ts │ │ │ │ ├── descope.interceptor.spec.ts │ │ │ │ └── descope.interceptor.ts │ │ │ ├── types │ │ │ │ └── types.ts │ │ │ └── utils │ │ │ │ ├── constants.ts │ │ │ │ ├── helpers.spec.ts │ │ │ │ └── helpers.ts │ │ └── public-api.ts │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json └── demo-app │ ├── .eslintrc.json │ ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.html │ │ ├── app.component.scss │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── home │ │ │ ├── home.component.html │ │ │ ├── home.component.scss │ │ │ ├── home.component.spec.ts │ │ │ └── home.component.ts │ │ ├── interceptor │ │ │ └── auth.interceptor.ts │ │ ├── login │ │ │ ├── login.component.html │ │ │ ├── login.component.spec.ts │ │ │ └── login.component.ts │ │ ├── manage-access-keys │ │ │ ├── manage-access-keys.component.html │ │ │ ├── manage-access-keys.component.spec.ts │ │ │ └── manage-access-keys.component.ts │ │ ├── manage-audit │ │ │ ├── manage-audit.component.html │ │ │ ├── manage-audit.component.spec.ts │ │ │ └── manage-audit.component.ts │ │ ├── manage-roles │ │ │ ├── manage-roles.component.html │ │ │ ├── manage-roles.component.spec.ts │ │ │ └── manage-roles.component.ts │ │ ├── manage-users │ │ │ ├── manage-users.component.html │ │ │ ├── manage-users.component.spec.ts │ │ │ └── manage-users.component.ts │ │ ├── my-user-profile │ │ │ ├── my-user-profile.component.html │ │ │ ├── my-user-profile.component.spec.ts │ │ │ ├── my-user-profile.component.ts │ │ │ └── my-user-profile.scss │ │ └── protected │ │ │ ├── protected.component.html │ │ │ ├── protected.component.scss │ │ │ ├── protected.component.spec.ts │ │ │ └── protected.component.ts │ ├── assets │ │ └── .gitkeep │ ├── environments │ │ ├── conifg.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ └── styles.scss │ ├── tsconfig.app.json │ └── tsconfig.spec.json ├── renovate.json ├── scripts ├── gitleaks │ ├── .gitleaks.toml │ └── gitleaks.sh └── setversion │ └── setversion.js ├── setup-jest.ts ├── thirdPartyLicenseCollector_linux_amd64 └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["projects/**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:@angular-eslint/recommended", 11 | "plugin:@angular-eslint/template/process-inline-templates" 12 | ], 13 | "rules": { 14 | "@angular-eslint/directive-selector": [ 15 | "error", 16 | { 17 | "type": "attribute", 18 | "prefix": "lib", 19 | "style": "camelCase" 20 | } 21 | ], 22 | "@angular-eslint/component-selector": [ 23 | "error", 24 | { 25 | "type": "element", 26 | "style": "kebab-case" 27 | } 28 | ], 29 | "@typescript-eslint/no-explicit-any": "off" 30 | } 31 | }, 32 | { 33 | "files": ["*.html"], 34 | "extends": [ 35 | "plugin:@angular-eslint/template/recommended", 36 | "plugin:@angular-eslint/template/accessibility" 37 | ], 38 | "rules": {} 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: 🎛️ CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | env: 10 | NODE_VERSION: 20 11 | 12 | jobs: 13 | build: 14 | name: 👷 Build 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | - uses: actions/setup-node@v4 20 | with: 21 | node-version: ${{ env.NODE_VERSION }} 22 | registry-url: https://npm.pkg.github.com/ 23 | - name: Install dependencies 24 | run: npm ci 25 | - name: Building Lib 26 | run: npm run build:lib 27 | - name: Building Demo App 28 | run: npm run build:app 29 | 30 | lint: 31 | name: 🪥 Lint 32 | runs-on: ubuntu-latest 33 | steps: 34 | - name: Checkout code 35 | uses: actions/checkout@v4 36 | - uses: actions/setup-node@v4 37 | with: 38 | node-version: ${{ env.NODE_VERSION }} 39 | registry-url: https://npm.pkg.github.com/ 40 | - name: Install dependencies 41 | run: npm ci 42 | - name: Linting 43 | run: npm run lint 44 | - name: Format check 45 | run: npm run format-check 46 | 47 | gitleaks: 48 | name: 🔒 Run Git leaks 49 | runs-on: ubuntu-latest 50 | steps: 51 | - name: Checkout code 52 | uses: actions/checkout@v4 53 | - uses: actions/setup-node@v4 54 | with: 55 | node-version: ${{ env.NODE_VERSION }} 56 | - name: Install dependencies 57 | run: npm ci 58 | - name: Gitleaks 59 | run: npm run leaks 60 | shell: bash 61 | 62 | testing: 63 | name: 👔 Test 64 | runs-on: ubuntu-latest 65 | steps: 66 | - name: Checkout code 67 | uses: actions/checkout@v4 68 | - uses: actions/setup-node@v4 69 | with: 70 | node-version: ${{ env.NODE_VERSION }} 71 | registry-url: https://npm.pkg.github.com/ 72 | - name: Install dependencies 73 | run: npm ci 74 | - name: Testing 75 | run: npm run test 76 | - name: Coverage check 77 | uses: devmasx/coverage-check-action@v1.2.0 78 | with: 79 | type: lcov 80 | min_coverage: 71.19 81 | result_path: coverage/lcov.info 82 | token: ${{ github.token }} 83 | 84 | license-validation: 85 | name: 🪪 License Validation 86 | runs-on: ubuntu-latest 87 | steps: 88 | - name: Checkout code 89 | uses: actions/checkout@v4 90 | - uses: actions/setup-node@v4 91 | with: 92 | node-version: ${{ env.NODE_VERSION }} 93 | registry-url: https://npm.pkg.github.com/ 94 | - name: Install dependencies 95 | run: npm ci 96 | - name: License validation 97 | shell: bash 98 | run: ./thirdPartyLicenseCollector_linux_amd64 -npm-project . 99 | -------------------------------------------------------------------------------- /.github/workflows/publish-next.yml: -------------------------------------------------------------------------------- 1 | name: 📢 Publish Next 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - init # TODO remove this 8 | 9 | env: 10 | NODE_VERSION: 20 11 | 12 | jobs: 13 | publish: 14 | name: 📢 Publish Next 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/setup-node@v4 20 | with: 21 | node-version: ${{ env.NODE_VERSION }} 22 | registry-url: https://registry.npmjs.org/ 23 | - name: Get Short SHA 24 | run: echo "SHORT_SHA=`echo ${{ github.sha }} | cut -c1-8`" >> $GITHUB_ENV 25 | - name: Get Next Version 26 | run: | 27 | current_date=$(date +'%Y%m%d') 28 | echo "NEW_VERSION=0.0.0-next-${SHORT_SHA}-${current_date}" >> $GITHUB_ENV 29 | - name: Use Next Version 30 | run: | 31 | git config --global user.email "info@descope.com" 32 | git config --global user.name "Descope" 33 | yarn version --new-version ${NEW_VERSION} 34 | cat package.json 35 | - name: Install dependencies 36 | run: npm ci 37 | - name: Build Library 38 | run: npm run build:ci 39 | - name: Publish to NPM Package Registry 40 | run: | 41 | cd dist 42 | npm publish --access=public --tag next 43 | env: 44 | NODE_AUTH_TOKEN: ${{ secrets.CI_NPM_REGISTRY }} 45 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: 📢 publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'release/**' 7 | 8 | env: 9 | NODE_VERSION: 20 10 | 11 | jobs: 12 | publish: 13 | name: 📢 Publish 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: ${{ env.NODE_VERSION }} 21 | registry-url: https://registry.npmjs.org/ 22 | - name: Install dependencies 23 | run: npm ci 24 | - name: Build Library 25 | run: npm run build:ci 26 | - name: Publish to NPM Package Registry 27 | run: | 28 | cd dist 29 | npm publish --access=public 30 | env: 31 | CI: true 32 | NODE_AUTH_TOKEN: ${{ secrets.CI_NPM_REGISTRY }} 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: 👊 Bump Version & Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | bump-version: 10 | name: 👊 Bump Version & Release 11 | runs-on: ubuntu-latest 12 | # this workflow will run only if commit message ends with "RELEASE" 13 | if: "contains(github.event.head_commit.message, 'RELEASE')" 14 | steps: 15 | - name: Checkout source code 16 | uses: actions/checkout@v4 17 | with: 18 | persist-credentials: false 19 | ref: ${{ github.ref }} 20 | - name: Get token 21 | id: get_token 22 | uses: tibdex/github-app-token@v1 23 | with: 24 | private_key: ${{ secrets.RELEASE_APP_PEM }} 25 | app_id: ${{ secrets.RELEASE_APP_ID }} 26 | 27 | - name: Automated Version Bump 28 | id: version-bump 29 | uses: phips28/gh-action-bump-version@master 30 | with: 31 | tag-prefix: release/v 32 | major-wording: 'MAJOR' 33 | minor-wording: 'MINOR' 34 | env: 35 | GITHUB_TOKEN: ${{ steps.get_token.outputs.token }} 36 | - name: cat angular-sdk package.json 37 | run: cat ./package.json 38 | - name: Output Step 39 | env: 40 | NEW_TAG: ${{ steps.version-bump.outputs.newTag }} 41 | run: echo "new tag $NEW_TAG" 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | 44 | # env files 45 | environment.development.ts 46 | 47 | # angular sdk package json, we use the top level package.json instead 48 | projects/angular-sdk/package.json 49 | 50 | .nx 51 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run format-lint 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": true, 4 | "semi": true, 5 | "singleQuote": true, 6 | "trailingComma": "none", 7 | "printWidth": 80 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["setversion"], 3 | "workbench.colorCustomizations": { 4 | "activityBar.background": "#29310A", 5 | "titleBar.activeBackground": "#39450E", 6 | "titleBar.activeForeground": "#F9FCEF" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Descope 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IMPORTANT NOTE: This repository is _DEPRECATED_. 2 | 3 | Descope Angular SDK has been moved to the [descope-js](https://github.com/descope/descope-js/tree/main/packages/sdks/angular-sdk) repoistory. 4 | 5 | --- 6 | 7 | # Descope SDK for Angular 8 | 9 | The Descope SDK for Angular provides convenient access to the Descope for an application written on top of Angular. You can read more on the [Descope Website](https://descope.com). 10 | 11 | ## Requirements 12 | 13 | - The SDK supports Angular version 16 and above. 14 | - A Descope `Project ID` is required for using the SDK. Find it on the [project page in the Descope Console](https://app.descope.com/settings/project). 15 | 16 | ## Installing the SDK 17 | 18 | Install the package with: 19 | 20 | ```bash 21 | npm i --save @descope/angular-sdk 22 | ``` 23 | 24 | Add Descope type definitions to your `tsconfig.ts` 25 | 26 | ``` 27 | "compilerOptions": { 28 | "typeRoots": ["./node_modules/@descope"], 29 | 30 | } 31 | ``` 32 | 33 | ## Usage 34 | 35 | ### NgModule - Import `DescopeAuthModule` to your application 36 | 37 | `app.module.ts` 38 | 39 | ```ts 40 | import { NgModule } from '@angular/core'; 41 | import { BrowserModule } from '@angular/platform-browser'; 42 | 43 | import { AppComponent } from './app.component'; 44 | import { DescopeAuthModule } from '@descope/angular-sdk'; 45 | 46 | @NgModule({ 47 | declarations: [AppComponent], 48 | imports: [ 49 | BrowserModule, 50 | DescopeAuthModule.forRoot({ 51 | projectId: '' 52 | }) 53 | ], 54 | bootstrap: [AppComponent] 55 | }) 56 | export class AppModule {} 57 | ``` 58 | 59 | ### Standalone Mode - Configure Descope SDK for your application 60 | 61 | `main.ts` 62 | 63 | ```ts 64 | import { bootstrapApplication } from '@angular/platform-browser'; 65 | import { AppComponent } from './app/app.component'; 66 | import { DescopeAuthConfig } from '@descope/angular-sdk'; 67 | 68 | bootstrapApplication(AppComponent, { 69 | providers: [ 70 | { provide: DescopeAuthConfig, useValue: { projectId: '' } } 71 | ] 72 | }).catch((err) => console.error(err)); 73 | ``` 74 | 75 | ### Use Descope to render specific flow 76 | 77 | You can use **default flows** or **provide flow id** directly to the descope component 78 | 79 | #### 1. Default flows 80 | 81 | `app.component.html` 82 | 83 | ```angular2html 84 | 89 | 90 |
95 | Loading... 96 |
97 | ``` 98 | 99 | `app.component.ts` 100 | 101 | ```ts 102 | import { Component } from '@angular/core'; 103 | 104 | @Component({ 105 | selector: 'app-root', 106 | templateUrl: './app.component.html' 107 | }) 108 | export class AppComponent { 109 | // Optionally, you can show/hide loading indication until the flow page is ready 110 | // See usage in onReady() method and the html template 111 | isLoading = true; 112 | 113 | onSuccess(e: CustomEvent) { 114 | console.log('SUCCESSFULLY LOGGED IN', e.detail); 115 | } 116 | 117 | onError(e: CustomEvent) { 118 | console.log('ERROR FROM LOG IN FLOW', e.detail); 119 | } 120 | 121 | onReady() { 122 | this.isLoading = false; 123 | } 124 | } 125 | ``` 126 | 127 | #### 2. Provide flow id 128 | 129 | ```angular2html 130 | 145 | 146 | Redirect URL for OAuth and SSO (will be used when redirecting back from the OAuth provider / IdP), or for "Magic Link" and "Enchanted Link" (will be used as a link in the message sent to the the user) 147 | redirectUrl= 148 | 149 | telemetryKey= 150 | 151 | autoFocus can be true, false or "skipFirstScreen". Default is true. 152 | - true: automatically focus on the first input of each screen 153 | - false: do not automatically focus on screen's inputs 154 | - "skipFirstScreen": automatically focus on the first input of each screen, except first screen 155 | autoFocus="skipFirstScreen" 156 | 157 | errorTransformer is a function that receives an error object and returns a string. The returned string will be displayed to the user. 158 | NOTE: errorTransformer is not required. If not provided, the error object will be displayed as is. 159 | Example: 160 | errorTransformer = (error: { text: string; type: string }): string => { 161 | const translationMap: { [key: string]: string } = { 162 | SAMLStartFailed: 'Failed to start SAML flow' 163 | }; 164 | return translationMap[error.type] || error.text; 165 | }; 166 | ... 167 | errorTransformer={errorTransformer} 168 | 169 | form is an object the initial form context that is used in screens inputs in the flow execution. 170 | Used to inject predefined input values on flow start such as custom inputs, custom attributes and other inputs. 171 | Keys passed can be accessed in flows actions, conditions and screens prefixed with "form.". 172 | NOTE: form is not required. If not provided, 'form' context key will be empty before user input. 173 | Example: 174 | form={{ email: "predefinedname@domain.com", firstName: "test", "customAttribute.test": "aaaa", "myCustomInput": 12 }} 175 | 176 | client is an object the initial client context in the flow execution. 177 | Keys passed can be accessed in flows actions and conditions prefixed with "client.". 178 | NOTE: client is not required. If not provided, context key will be empty. 179 | Example: 180 | client={{ version: "1.2.0" }} 181 | 182 | logger is an object describing how to log info, warn and errors. 183 | NOTE: logger is not required. If not provided, the logs will be printed to the console. 184 | Example: 185 | const logger = { 186 | info: (title: string, description: string, state: any) => { 187 | console.log(title, description, JSON.stringify(state)); 188 | }, 189 | warn: (title: string, description: string) => { 190 | console.warn(title); 191 | }, 192 | error: (title: string, description: string) => { 193 | console.error('OH NOO'); 194 | }, 195 | } 196 | ... 197 | logger={logger}--> 198 | > 199 | ``` 200 | 201 | #### Standalone Mode 202 | 203 | All components in the sdk are standalone, so you can use them by directly importing them to your components. 204 | 205 | ### Use the `DescopeAuthService` and its exposed fields (`descopeSdk`, `session$`, `user$`) to access authentication state, user details and utilities 206 | 207 | This can be helpful to implement application-specific logic. Examples: 208 | 209 | - Render different components if current session is authenticated 210 | - Render user's content 211 | - Logout button 212 | 213 | `app.component.html` 214 | 215 | ```angular2html 216 |

You are not logged in

217 | 218 |

User: {{userName}}

219 | ``` 220 | 221 | `app.component.ts` 222 | 223 | ```ts 224 | import { Component, OnInit } from '@angular/core'; 225 | import { DescopeAuthService } from '@descope/angular-sdk'; 226 | 227 | @Component({ 228 | selector: 'app-home', 229 | templateUrl: './app.component.html', 230 | styleUrls: ['./app.component.scss'] 231 | }) 232 | export class AppComponent implements OnInit { 233 | isAuthenticated: boolean = false; 234 | userName: string = ''; 235 | 236 | constructor(private authService: DescopeAuthService) {} 237 | 238 | ngOnInit() { 239 | this.authService.session$.subscribe((session) => { 240 | this.isAuthenticated = session.isAuthenticated; 241 | }); 242 | this.authService.user$.subscribe((descopeUser) => { 243 | if (descopeUser.user) { 244 | this.userName = descopeUser.user.name ?? ''; 245 | } 246 | }); 247 | } 248 | 249 | logout() { 250 | this.authService.descopeSdk.logout(); 251 | } 252 | } 253 | ``` 254 | 255 | ### Session Refresh 256 | 257 | `DescopeAuthService` provides `refreshSession` and `refreshUser` methods that triggers a single request to the Descope backend to attempt to refresh the session or user. You can use them whenever you want to refresh the session/user. For example you can use `APP_INITIALIZER` provider to attempt to refresh session and user on each page refresh: 258 | 259 | `app.module.ts` 260 | 261 | ```ts 262 | import { APP_INITIALIZER, NgModule } from '@angular/core'; 263 | import { BrowserModule } from '@angular/platform-browser'; 264 | import { AppComponent } from './app.component'; 265 | import { DescopeAuthModule, DescopeAuthService } from '@descope/angular-sdk'; 266 | import { zip } from 'rxjs'; 267 | 268 | export function initializeApp(authService: DescopeAuthService) { 269 | return () => zip([authService.refreshSession(), authService.refreshUser()]); 270 | } 271 | 272 | @NgModule({ 273 | declarations: [AppComponent], 274 | imports: [ 275 | BrowserModule, 276 | DescopeAuthModule.forRoot({ 277 | projectId: '' 278 | }) 279 | ], 280 | providers: [ 281 | { 282 | provide: APP_INITIALIZER, 283 | useFactory: initializeApp, 284 | deps: [DescopeAuthService], 285 | multi: true 286 | } 287 | ], 288 | bootstrap: [AppComponent] 289 | }) 290 | export class AppModule {} 291 | ``` 292 | 293 | #### Standalone Mode Note: 294 | 295 | You can use the same approach with `APP_INITIALIZER` in standalone mode, by adding it to `providers` array of the application. 296 | 297 | ### Descope Interceptor 298 | 299 | You can also use `DescopeInterceptor` to attempt to refresh session on each HTTP request that gets `401` or `403` response: 300 | 301 | `app.module.ts` 302 | 303 | ```ts 304 | import { NgModule } from '@angular/core'; 305 | import { BrowserModule } from '@angular/platform-browser'; 306 | import { AppComponent } from './app.component'; 307 | import { 308 | HttpClientModule, 309 | provideHttpClient, 310 | withInterceptors 311 | } from '@angular/common/http'; 312 | import { DescopeAuthModule, descopeInterceptor } from '@descope/angular-sdk'; 313 | 314 | @NgModule({ 315 | declarations: [AppComponent], 316 | imports: [ 317 | BrowserModule, 318 | HttpClientModule, 319 | DescopeAuthModule.forRoot({ 320 | projectId: '', 321 | pathsToIntercept: ['/protectedPath'] 322 | }) 323 | ], 324 | providers: [provideHttpClient(withInterceptors([descopeInterceptor]))], 325 | bootstrap: [AppComponent] 326 | }) 327 | export class AppModule {} 328 | ``` 329 | 330 | `DescopeInterceptor`: 331 | 332 | - is configured for requests that urls contain one of `pathsToIntercept`. If not provided it will be used for all requests. 333 | - attaches session token as `Authorization` header in `Bearer ` format 334 | - if requests get response with `401` or `403` it automatically attempts to refresh session 335 | - if refresh attempt is successful, it automatically retries original request, otherwise it fails with original error 336 | 337 | **For more SDK usage examples refer to [docs](https://docs.descope.com/build/guides/client_sdks/)** 338 | 339 | ### Session token server validation (pass session token to server API) 340 | 341 | When developing a full-stack application, it is common to have private server API which requires a valid session token: 342 | 343 | ![session-token-validation-diagram](https://docs.descope.com/static/SessionValidation-cf7b2d5d26594f96421d894273a713d8.png) 344 | 345 | Note: Descope also provides server-side SDKs in various languages (NodeJS, Go, Python, etc). Descope's server SDKs have out-of-the-box session validation API that supports the options described bellow. To read more about session validation, Read [this section](https://docs.descope.com/build/guides/gettingstarted/#session-validation) in Descope documentation. 346 | 347 | You can securely communicate with your backend either by using `DescopeInterceptor` or manually adding token to your requests (ie. by using `DescopeAuthService.getSessionToken()` helper function) 348 | 349 | ### Helper Functions 350 | 351 | You can also use the following helper methods on `DescopeAuthService` to assist with various actions managing your JWT. 352 | 353 | - `getSessionToken()` - Get current session token. 354 | - `getRefreshToken()` - Get current refresh token. 355 | - `isAuthenticated()` - Returns boolean whether user is authenticated 356 | - `refreshSession` - Force a refresh on current session token using an existing valid refresh token. 357 | - `refreshUser` - Force a refresh on current user using an existing valid refresh token. 358 | - `isSessionTokenExpired(token = getSessionToken())` - Check whether the current session token is expired. Provide a session token if is not persisted. 359 | - `isRefreshTokenExpired(token = getRefreshToken())` - Check whether the current refresh token is expired. Provide a refresh token if is not persisted. 360 | - `getJwtRoles(token = getSessionToken(), tenant = '')` - Get current roles from an existing session token. Provide tenant id for specific tenant roles. 361 | - `getJwtPermissions(token = getSessionToken(), tenant = '')` - Fet current permissions from an existing session token. Provide tenant id for specific tenant permissions. 362 | 363 | ### Refresh token lifecycle 364 | 365 | Descope SDK is automatically refreshes the session token when it is about to expire. This is done in the background using the refresh token, without any additional configuration. 366 | 367 | If the Descope project settings are configured to manage tokens in cookies. 368 | you must also configure a custom domain, and set it as the `baseUrl` in `DescopeAuthModule`. 369 | 370 | ### Descope Guard 371 | 372 | `angular-sdk` provides a convenient route guard that prevents from accessing given route for users that are not authenticated: 373 | 374 | ```ts 375 | import { NgModule } from '@angular/core'; 376 | import { RouterModule, Routes } from '@angular/router'; 377 | import { HomeComponent } from './home/home.component'; 378 | import { ProtectedComponent } from './protected/protected.component'; 379 | import { descopeAuthGuard } from '@descope/angular-sdk'; 380 | import { LoginComponent } from './login/login.component'; 381 | 382 | const routes: Routes = [ 383 | { 384 | path: 'step-up', 385 | component: ProtectedComponent, 386 | canActivate: [descopeAuthGuard], 387 | data: { descopeFallbackUrl: '/' } 388 | }, 389 | { path: 'login', component: LoginComponent }, 390 | { path: '**', component: HomeComponent } 391 | ]; 392 | 393 | @NgModule({ 394 | imports: [RouterModule.forRoot(routes, { enableTracing: false })], 395 | exports: [RouterModule] 396 | }) 397 | export class AppRoutingModule {} 398 | ``` 399 | 400 | If not authenticated user tries to access protected route they will be redirected to `descopeFallbackUrl` 401 | 402 | ### Token Persistence 403 | 404 | Descope stores two tokens: the session token and the refresh token. 405 | 406 | - The refresh token is either stored in local storage or an `httpOnly` cookie. This is configurable in the Descope console. 407 | - The session token is stored in either local storage or a JS cookie. This behavior is configurable via the `sessionTokenViaCookie` prop in the `DescopeAuthModule` module. 408 | 409 | However, for security reasons, you may choose not to store tokens in the browser. In this case, you can pass `persistTokens: false` to the `DescopeAuthModule` module. This prevents the SDK from storing the tokens in the browser. 410 | 411 | Notes: 412 | 413 | - You must configure the refresh token to be stored in an `httpOnly` cookie in the Descope console. Otherwise, the refresh token will not be stored, and when the page is refreshed, the user will be logged out. 414 | - You can still retrieve the session token using the `session` observable of `DescopeAuthService`. 415 | 416 | ### Last User Persistence 417 | 418 | Descope stores the last user information in local storage. If you wish to disable this feature, you can pass `storeLastAuthenticatedUser: false` to the `DescopeAuthModule` module. Please note that some features related to the last authenticated user may not function as expected if this behavior is disabled. 419 | 420 | ### Widgets 421 | 422 | Widgets are components that allow you to expose management features for tenant-based implementation. In certain scenarios, your customers may require the capability to perform managerial actions independently, alleviating the necessity to contact you. Widgets serve as a feature enabling you to delegate these capabilities to your customers in a modular manner. 423 | 424 | Important Note: 425 | 426 | - For the user to be able to use the widget, they need to be assigned the `Tenant Admin` Role. 427 | 428 | #### User Management 429 | 430 | The `UserManagement` widget will let you embed a user table in your site to view and take action. 431 | 432 | The widget lets you: 433 | 434 | - Create a new user 435 | - Edit an existing user 436 | - Activate / disable an existing user 437 | - Reset an existing user's password 438 | - Remove an existing user's passkey 439 | - Delete an existing user 440 | 441 | Note: 442 | 443 | - Custom fields also appear in the table. 444 | 445 | ###### Usage 446 | 447 | ```html 448 | 449 | ``` 450 | 451 | Example: 452 | [Manage Users](./projects/demo-app/src/app/manage-users/manage-users.component.html) 453 | 454 | #### Role Management 455 | 456 | The `RoleManagement` widget will let you embed a role table in your site to view and take action. 457 | 458 | The widget lets you: 459 | 460 | - Create a new role 461 | - Change an existing role's fields 462 | - Delete an existing role 463 | 464 | Note: 465 | 466 | - The `Editable` field is determined by the user's access to the role - meaning that project-level roles are not editable by tenant level users. 467 | - You need to pre-define the permissions that the user can use, which are not editable in the widget. 468 | 469 | ###### Usage 470 | 471 | ```html 472 | 473 | ``` 474 | 475 | Example: 476 | [Manage Roles](./projects/demo-app/src/app/manage-roles/manage-roles.component.html) 477 | 478 | #### AccessKeyManagement 479 | 480 | The `AccessKeyManagement` widget will let you embed an access key table in your site to view and take action. 481 | 482 | The widget lets you: 483 | 484 | - Create a new access key 485 | - Activate / deactivate an existing access key 486 | - Delete an exising access key 487 | 488 | ###### Usage 489 | 490 | ```html 491 | 492 | 496 | 497 | 498 | 502 | ``` 503 | 504 | Example: 505 | [Manage Access Keys](./projects/demo-app/src/app/manage-access-keys/manage-access-keys.component.html) 506 | 507 | #### AuditManagement 508 | 509 | The `AuditManagement` widget will let you embed an audit table in your site. 510 | 511 | ###### Usage 512 | 513 | ```html 514 | 515 | ``` 516 | 517 | Example: 518 | [Manage Audit](./projects/demo-app/src/app/manage-audit/manage-audit.component.html) 519 | 520 | #### UserProfile 521 | 522 | The `UserProfile` widget lets you embed a user profile component in your app and let the logged in user update his profile. 523 | 524 | The widget lets you: 525 | 526 | - Update user profile picture 527 | - Update user personal information 528 | - Update authentication methods 529 | - Logout 530 | 531 | ###### Usage 532 | 533 | ```angular2html 534 | 537 | ``` 538 | 539 | Example: 540 | [My User Profile](./projects/demo-app/src/app/my-user-profile/my-user-profile.component.html) 541 | 542 | ## Code Example 543 | 544 | You can find an example angular app in the [examples folder](./projects/demo-app). 545 | 546 | ### Setup 547 | 548 | To run the examples, create `environment.development.ts` file in `environments` folder. 549 | 550 | ```ts 551 | import { Env } from './conifg'; 552 | 553 | export const environment: Env = { 554 | descopeProjectId: '' 555 | }; 556 | ``` 557 | 558 | Find your Project ID in the [Descope console](https://app.descope.com/settings/project). 559 | 560 | ### Run Example 561 | 562 | Run the following command in the root of the project to build and run the example: 563 | 564 | ```bash 565 | npm i && npm start 566 | ``` 567 | 568 | ### Example Optional Env Variables 569 | 570 | See the following table for customization environment variables for the example app: 571 | 572 | | Env Variable | Description | Default value | 573 | | -------------------- | ------------------------------------------------------------------------------------------------------------- | ----------------- | 574 | | descopeFlowId | Which flow ID to use in the login page | **sign-up-or-in** | 575 | | descopeBaseUrl | Custom Descope base URL | None | 576 | | descopeBaseStaticUrl | Custom Descope base static URL | None | 577 | | descopeTheme | Flow theme | None | 578 | | descopeLocale | Flow locale | Browser's locale | 579 | | descopeRedirectUrl | Flow redirect URL for OAuth/SSO/Magic Link/Enchanted Link | None | 580 | | descopeTenantId | Flow tenant ID for SSO/SAML | None | 581 | | descopeDebugMode | **"true"** - Enable debugger
**"false"** - Disable flow debugger | None | 582 | | descopeStepUpFlowId | Step up flow ID to show to logged in user (via button). e.g. "step-up". Button will be hidden if not provided | None | 583 | | descopeTelemetryKey | **String** - Telemetry public key provided by Descope Inc | None | 584 | | descopeBackendUrl | Url to your test backend app in case you want to test e2e | None | 585 | 586 | Example `environment.development.ts` file: 587 | 588 | ```ts 589 | import { Env } from './conifg'; 590 | 591 | export const environment: Env = { 592 | descopeProjectId: '', 593 | descopeBaseUrl: '', 594 | descopeBaseStaticUrl: '', 595 | descopeFlowId: 'sign-in', 596 | descopeDebugMode: false, 597 | descopeTheme: 'os', 598 | descopeLocale: 'en_US', 599 | descopeRedirectUrl: '', 600 | descopeTelemetryKey: '', 601 | descopeStepUpFlowId: 'step-up', 602 | descopeBackendUrl: 'http://localhost:8080/protected' 603 | }; 604 | ``` 605 | 606 | ## Troubleshooting 607 | 608 | If you encounter warning during build of your application: 609 | 610 | ``` 611 | ▲ [WARNING] Module 'lodash.get' used by 'node_modules/@descope/web-component/node_modules/@descope/core-js-sdk/dist/index.esm.js' is not ESM 612 | ``` 613 | 614 | add `lodash.get` to allowed CommonJS dependencies in `angular.json` 615 | 616 | ```json 617 | "architect": { 618 | "build": { 619 | "builder": "@angular-devkit/build-angular:browser", 620 | "options": { 621 | "allowedCommonJsDependencies": ["lodash.get"], 622 | 623 | } 624 | 625 | } 626 | 627 | } 628 | ``` 629 | 630 | ## FAQ 631 | 632 | ### I updated the user in my backend, but the user / session token are not updated in the frontend 633 | 634 | The Descope SDK caches the user and session token in the frontend. If you update the user in your backend (using Descope Management SDK/API for example), you can call `me` / `refresh` from `descopeSdk` member of `DescopeAuthService` to refresh the user and session token. Example: 635 | 636 | ```ts 637 | import { DescopeAuthService } from '@descope/angular-sdk'; 638 | 639 | export class MyComponent { 640 | // ... 641 | constructor(private authService: DescopeAuthService) {} 642 | 643 | handleUpdateUser() { 644 | myBackendUpdateUser().then(() => { 645 | this.authService.descopeSdk.me(); 646 | // or 647 | this.authService.descopeSdk.refresh(); 648 | }); 649 | } 650 | } 651 | ``` 652 | 653 | ## Learn More 654 | 655 | To learn more please see the [Descope Documentation and API reference page](https://docs.descope.com/). 656 | 657 | ## Contact Us 658 | 659 | If you need help you can email [Descope Support](mailto:support@descope.com) 660 | 661 | ## License 662 | 663 | The Descope SDK for Angular is licensed for use under the terms and conditions of the [MIT license Agreement](./LICENSE). 664 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular-sdk": { 7 | "projectType": "library", 8 | "root": "projects/angular-sdk", 9 | "sourceRoot": "projects/angular-sdk/src", 10 | "prefix": "lib", 11 | "architect": { 12 | "build": { 13 | "builder": "@angular-devkit/build-angular:ng-packagr", 14 | "options": { 15 | "project": "projects/angular-sdk/ng-package.json" 16 | }, 17 | "configurations": { 18 | "production": { 19 | "tsConfig": "projects/angular-sdk/tsconfig.lib.prod.json" 20 | }, 21 | "development": { 22 | "tsConfig": "projects/angular-sdk/tsconfig.lib.json" 23 | } 24 | }, 25 | "defaultConfiguration": "production" 26 | }, 27 | "test": { 28 | "builder": "@angular-devkit/build-angular:karma", 29 | "options": { 30 | "tsConfig": "projects/angular-sdk/tsconfig.spec.json", 31 | "polyfills": ["zone.js", "zone.js/testing"] 32 | } 33 | }, 34 | "lint": { 35 | "builder": "@angular-eslint/builder:lint", 36 | "options": { 37 | "lintFilePatterns": [ 38 | "projects/angular-sdk/**/*.ts", 39 | "projects/angular-sdk/**/*.html" 40 | ] 41 | } 42 | } 43 | } 44 | }, 45 | "demo-app": { 46 | "projectType": "application", 47 | "schematics": { 48 | "@schematics/angular:component": { 49 | "style": "scss" 50 | } 51 | }, 52 | "root": "projects/demo-app", 53 | "sourceRoot": "projects/demo-app/src", 54 | "prefix": "app", 55 | "architect": { 56 | "build": { 57 | "builder": "@angular-devkit/build-angular:browser", 58 | "options": { 59 | "allowedCommonJsDependencies": ["lodash.get"], 60 | "outputPath": "dist/demo-app", 61 | "index": "projects/demo-app/src/index.html", 62 | "main": "projects/demo-app/src/main.ts", 63 | "polyfills": ["zone.js"], 64 | "tsConfig": "projects/demo-app/tsconfig.app.json", 65 | "inlineStyleLanguage": "scss", 66 | "assets": [ 67 | "projects/demo-app/src/favicon.ico", 68 | "projects/demo-app/src/assets" 69 | ], 70 | "styles": ["projects/demo-app/src/styles.scss"], 71 | "scripts": [] 72 | }, 73 | "configurations": { 74 | "production": { 75 | "budgets": [ 76 | { 77 | "type": "initial", 78 | "maximumWarning": "500kb", 79 | "maximumError": "1mb" 80 | }, 81 | { 82 | "type": "anyComponentStyle", 83 | "maximumWarning": "2kb", 84 | "maximumError": "4kb" 85 | } 86 | ], 87 | "outputHashing": "all" 88 | }, 89 | "development": { 90 | "buildOptimizer": false, 91 | "optimization": false, 92 | "vendorChunk": true, 93 | "extractLicenses": false, 94 | "sourceMap": true, 95 | "namedChunks": true, 96 | "fileReplacements": [ 97 | { 98 | "replace": "projects/demo-app/src/environments/environment.ts", 99 | "with": "projects/demo-app/src/environments/environment.development.ts" 100 | } 101 | ] 102 | } 103 | }, 104 | "defaultConfiguration": "production" 105 | }, 106 | "serve": { 107 | "builder": "@angular-devkit/build-angular:dev-server", 108 | "configurations": { 109 | "production": { 110 | "browserTarget": "demo-app:build:production" 111 | }, 112 | "development": { 113 | "browserTarget": "demo-app:build:development" 114 | } 115 | }, 116 | "defaultConfiguration": "development" 117 | }, 118 | "extract-i18n": { 119 | "builder": "@angular-devkit/build-angular:extract-i18n", 120 | "options": { 121 | "browserTarget": "demo-app:build" 122 | } 123 | }, 124 | "test": { 125 | "builder": "@angular-devkit/build-angular:karma", 126 | "options": { 127 | "polyfills": ["zone.js", "zone.js/testing"], 128 | "tsConfig": "projects/demo-app/tsconfig.spec.json", 129 | "inlineStyleLanguage": "scss", 130 | "assets": [ 131 | "projects/demo-app/src/favicon.ico", 132 | "projects/demo-app/src/assets" 133 | ], 134 | "styles": ["projects/demo-app/src/styles.scss"], 135 | "scripts": [] 136 | } 137 | }, 138 | "lint": { 139 | "builder": "@angular-eslint/builder:lint", 140 | "options": { 141 | "lintFilePatterns": [ 142 | "projects/demo-app/**/*.ts", 143 | "projects/demo-app/**/*.html" 144 | ] 145 | } 146 | } 147 | } 148 | } 149 | }, 150 | "cli": { 151 | "schematicCollections": ["@angular-eslint/schematics"], 152 | "analytics": false 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'jest-preset-angular', 3 | setupFilesAfterEnv: ['/setup-jest.ts'], 4 | roots: ['/projects/angular-sdk', '/projects/demo-app'], 5 | collectCoverage: true, 6 | coverageDirectory: 'coverage', 7 | collectCoverageFrom: ['projects/**/*.{js,ts}'], 8 | transform: { 9 | '^.+\\.(ts|js|html)$': [ 10 | 'jest-preset-angular', 11 | { 12 | tsconfig: './projects/angular-sdk/tsconfig.spec.json', 13 | stringifyContentPathRegex: '\\.(html|svg)$' 14 | } 15 | ] 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@descope/angular-sdk", 3 | "version": "0.5.10", 4 | "peerDependencies": { 5 | "@angular/common": ">=16.0.0", 6 | "@angular/core": ">=16.0.0" 7 | }, 8 | "license": "MIT", 9 | "engines": { 10 | "node": "^16.14.0 || >=18.10.0", 11 | "npm": ">= 8.1.0" 12 | }, 13 | "scripts": { 14 | "ng": "ng", 15 | "start": "ng serve", 16 | "prepare": "husky install", 17 | "format-lint": "pretty-quick --staged --ignore-path .gitignore && lint-staged", 18 | "prebuild:ci": "node scripts/setversion/setversion.js", 19 | "build:ci": "npm run build:lib", 20 | "build:lib": "cp package.json ./projects/angular-sdk && ng build angular-sdk", 21 | "build:app": "ng build demo-app", 22 | "watch": "ng build --watch --configuration development", 23 | "test": "jest --config jest.config.js", 24 | "lint": "ng lint", 25 | "format": "prettier . -w --ignore-path .gitignore", 26 | "format-check": "prettier . --check --ignore-path .gitignore", 27 | "prepublishOnly": "npm run build:ci", 28 | "leaks": "bash ./scripts/gitleaks/gitleaks.sh" 29 | }, 30 | "lint-staged": { 31 | "+(src|test|examples)/**/*.{js,ts,html}": [ 32 | "npm run lint" 33 | ] 34 | }, 35 | "dependencies": { 36 | "@descope/access-key-management-widget": "0.1.113", 37 | "@descope/audit-management-widget": "0.1.76", 38 | "@descope/role-management-widget": "0.1.111", 39 | "@descope/user-management-widget": "0.4.114", 40 | "@descope/user-profile-widget": "0.0.91", 41 | "@descope/web-component": "3.20.2", 42 | "tslib": "^2.3.0" 43 | }, 44 | "optionalDependencies": { 45 | "@descope/web-js-sdk": ">=1" 46 | }, 47 | "devDependencies": { 48 | "@angular-devkit/build-angular": "^16.2.6", 49 | "@angular-eslint/builder": "16.3.1", 50 | "@angular-eslint/eslint-plugin": "16.3.1", 51 | "@angular-eslint/eslint-plugin-template": "16.3.1", 52 | "@angular-eslint/schematics": "16.3.1", 53 | "@angular-eslint/template-parser": "16.3.1", 54 | "@angular/animations": "^16.2.9", 55 | "@angular/cli": "^16.2.6", 56 | "@angular/common": "^16.2.9", 57 | "@angular/compiler": "^16.2.9", 58 | "@angular/compiler-cli": "^16.2.9", 59 | "@angular/core": "^16.2.9", 60 | "@angular/forms": "^16.2.9", 61 | "@angular/platform-browser": "^16.2.9", 62 | "@angular/platform-browser-dynamic": "^16.2.9", 63 | "@angular/router": "^16.2.9", 64 | "@types/jest": "^29.5.5", 65 | "@typescript-eslint/eslint-plugin": "6.12.0", 66 | "@typescript-eslint/parser": "6.12.0", 67 | "eslint": "^8.51.0", 68 | "eslint-plugin-prettier": "^4.2.1", 69 | "husky": "^8.0.3", 70 | "jest": "^29.7.0", 71 | "jest-preset-angular": "^13.1.2", 72 | "lint-staged": "^15.2.0", 73 | "ng-mocks": "^14.11.0", 74 | "ng-packagr": "^16.2.3", 75 | "prettier": "2.8.8", 76 | "pretty-quick": "^3.1.3", 77 | "rxjs": "~7.8.1", 78 | "tslib": "^2.6.2", 79 | "typescript": "4.9.3", 80 | "zone.js": "~0.13.0" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /projects/angular-sdk/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "rules": { 8 | "@angular-eslint/directive-selector": [ 9 | "error", 10 | { 11 | "type": "attribute", 12 | "prefix": "lib", 13 | "style": "camelCase" 14 | } 15 | ], 16 | "@angular-eslint/component-selector": [ 17 | "error", 18 | { 19 | "type": "element", 20 | "prefix": "", 21 | "style": "kebab-case" 22 | } 23 | ], 24 | "@angular-eslint/no-output-native": "off" 25 | } 26 | }, 27 | { 28 | "files": ["*.html"], 29 | "rules": {} 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /projects/angular-sdk/README.md: -------------------------------------------------------------------------------- 1 | # AngularSdk 2 | 3 | This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.2.0. 4 | 5 | ## Code scaffolding 6 | 7 | Run `ng generate component component-name --project angular-sdk` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project angular-sdk`. 8 | 9 | > Note: Don't forget to add `--project angular-sdk` or else it will be added to the default project in your `angular.json` file. 10 | 11 | ## Build 12 | 13 | Run `ng build angular-sdk` to build the project. The build artifacts will be stored in the `dist/` directory. 14 | 15 | ## Running unit tests 16 | 17 | Run `ng test angular-sdk` to execute the unit tests via [Karma](https://karma-runner.github.io). 18 | 19 | ## Further help 20 | 21 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 22 | -------------------------------------------------------------------------------- /projects/angular-sdk/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | }, 7 | "allowedNonPeerDependencies": [ 8 | "@descope/web-component", 9 | "@descope/user-management-widget", 10 | "@descope/role-management-widget", 11 | "@descope/access-key-management-widget", 12 | "@descope/audit-management-widget", 13 | "@descope/user-profile-widget" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/environment.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | buildVersion: '0.0.0' // Placeholder, replaced by pre-build script (setversion.js) 3 | }; 4 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/access-key-management/access-key-management.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { AccessKeyManagementComponent } from './access-key-management.component'; 3 | import createSdk from '@descope/web-js-sdk'; 4 | import { DescopeAuthConfig } from '../../types/types'; 5 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 6 | import mocked = jest.mocked; 7 | 8 | jest.mock('@descope/web-js-sdk'); 9 | //Mock DescopeAccessKeyManagementWidget 10 | jest.mock('@descope/access-key-management-widget', () => { 11 | return jest.fn(() => { 12 | // Create a mock DOM element 13 | return document.createElement('descope-access-key-management-widget'); 14 | }); 15 | }); 16 | 17 | describe('DescopeAccessKeyManagementComponent', () => { 18 | let component: AccessKeyManagementComponent; 19 | let fixture: ComponentFixture; 20 | let mockedCreateSdk: jest.Mock; 21 | const onSessionTokenChangeSpy = jest.fn(); 22 | const onAccessKeyChangeSpy = jest.fn(); 23 | const afterRequestHooksSpy = jest.fn(); 24 | const mockConfig: DescopeAuthConfig = { 25 | projectId: 'someProject' 26 | }; 27 | 28 | beforeEach(() => { 29 | mockedCreateSdk = mocked(createSdk); 30 | 31 | mockedCreateSdk.mockReturnValue({ 32 | onSessionTokenChange: onSessionTokenChangeSpy, 33 | onAccessKeyChange: onAccessKeyChangeSpy, 34 | httpClient: { 35 | hooks: { 36 | afterRequest: afterRequestHooksSpy 37 | } 38 | } 39 | }); 40 | 41 | TestBed.configureTestingModule({ 42 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 43 | providers: [ 44 | DescopeAuthConfig, 45 | { provide: DescopeAuthConfig, useValue: mockConfig } 46 | ] 47 | }); 48 | 49 | fixture = TestBed.createComponent(AccessKeyManagementComponent); 50 | component = fixture.componentInstance; 51 | component.projectId = '123'; 52 | component.tenant = 'tenant-1'; 53 | component.widgetId = 'widget-1'; 54 | component.logger = { 55 | info: jest.fn(), 56 | error: jest.fn(), 57 | warn: jest.fn(), 58 | debug: jest.fn() 59 | }; 60 | fixture.detectChanges(); 61 | }); 62 | 63 | it('should create', () => { 64 | expect(component).toBeTruthy(); 65 | const html: HTMLElement = fixture.nativeElement; 66 | const webComponentHtml = html.querySelector( 67 | 'descope-access-key-management-widget' 68 | ); 69 | expect(webComponentHtml).toBeDefined(); 70 | }); 71 | 72 | it('should correctly setup attributes based on inputs', () => { 73 | const html: HTMLElement = fixture.nativeElement; 74 | const webComponentHtml = html.querySelector( 75 | 'descope-access-key-management-widget' 76 | )!; 77 | expect(webComponentHtml.getAttribute('project-id')).toStrictEqual('123'); 78 | expect(webComponentHtml.getAttribute('tenant')).toStrictEqual('tenant-1'); 79 | expect(webComponentHtml.getAttribute('widget-id')).toStrictEqual( 80 | 'widget-1' 81 | ); 82 | expect(webComponentHtml.getAttribute('logger')).toBeDefined(); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/access-key-management/access-key-management.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, Input, OnChanges, OnInit } from '@angular/core'; 2 | import DescopeAccessKeyManagementWidget from '@descope/access-key-management-widget'; 3 | import { ILogger } from '@descope/web-component'; 4 | import { DescopeAuthConfig } from '../../types/types'; 5 | 6 | @Component({ 7 | selector: 'access-key-management[tenant]', 8 | standalone: true, 9 | template: '' 10 | }) 11 | export class AccessKeyManagementComponent implements OnInit, OnChanges { 12 | projectId: string; 13 | baseUrl?: string; 14 | baseStaticUrl?: string; 15 | @Input() tenant: string; 16 | @Input() widgetId: string; 17 | 18 | @Input() theme: 'light' | 'dark' | 'os'; 19 | @Input() debug: boolean; 20 | @Input() logger: ILogger; 21 | 22 | private readonly webComponent = new DescopeAccessKeyManagementWidget(); 23 | 24 | constructor( 25 | private elementRef: ElementRef, 26 | descopeConfig: DescopeAuthConfig 27 | ) { 28 | this.projectId = descopeConfig.projectId; 29 | this.baseUrl = descopeConfig.baseUrl; 30 | this.baseStaticUrl = descopeConfig.baseStaticUrl; 31 | } 32 | 33 | ngOnInit() { 34 | this.setupWebComponent(); 35 | this.elementRef.nativeElement.appendChild(this.webComponent); 36 | } 37 | 38 | ngOnChanges(): void { 39 | this.setupWebComponent(); 40 | } 41 | 42 | private setupWebComponent() { 43 | this.webComponent.setAttribute('project-id', this.projectId); 44 | this.webComponent.setAttribute('tenant', this.tenant); 45 | this.webComponent.setAttribute('widget-id', this.widgetId); 46 | if (this.baseUrl) { 47 | this.webComponent.setAttribute('base-url', this.baseUrl); 48 | } 49 | if (this.baseStaticUrl) { 50 | this.webComponent.setAttribute('base-static-url', this.baseStaticUrl); 51 | } 52 | if (this.theme) { 53 | this.webComponent.setAttribute('theme', this.theme); 54 | } 55 | if (this.debug) { 56 | this.webComponent.setAttribute('debug', this.debug.toString()); 57 | } 58 | 59 | if (this.logger) { 60 | (this.webComponent as any).logger = this.logger; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/audit-management/audit-management.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { AuditManagementComponent } from './audit-management.component'; 3 | import createSdk from '@descope/web-js-sdk'; 4 | import { DescopeAuthConfig } from '../../types/types'; 5 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 6 | import mocked = jest.mocked; 7 | 8 | jest.mock('@descope/web-js-sdk'); 9 | //Mock DescopeAuditManagementWidget 10 | jest.mock('@descope/audit-management-widget', () => { 11 | return jest.fn(() => { 12 | // Create a mock DOM element 13 | return document.createElement('descope-audit-management-widget'); 14 | }); 15 | }); 16 | 17 | describe('DescopeAuditManagementComponent', () => { 18 | let component: AuditManagementComponent; 19 | let fixture: ComponentFixture; 20 | let mockedCreateSdk: jest.Mock; 21 | const onSessionTokenChangeSpy = jest.fn(); 22 | const onAuditChangeSpy = jest.fn(); 23 | const afterRequestHooksSpy = jest.fn(); 24 | const mockConfig: DescopeAuthConfig = { 25 | projectId: 'someProject' 26 | }; 27 | 28 | beforeEach(() => { 29 | mockedCreateSdk = mocked(createSdk); 30 | 31 | mockedCreateSdk.mockReturnValue({ 32 | onSessionTokenChange: onSessionTokenChangeSpy, 33 | onAuditChange: onAuditChangeSpy, 34 | httpClient: { 35 | hooks: { 36 | afterRequest: afterRequestHooksSpy 37 | } 38 | } 39 | }); 40 | 41 | TestBed.configureTestingModule({ 42 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 43 | providers: [ 44 | DescopeAuthConfig, 45 | { provide: DescopeAuthConfig, useValue: mockConfig } 46 | ] 47 | }); 48 | 49 | fixture = TestBed.createComponent(AuditManagementComponent); 50 | component = fixture.componentInstance; 51 | component.projectId = '123'; 52 | component.tenant = 'tenant-1'; 53 | component.widgetId = 'widget-1'; 54 | component.logger = { 55 | info: jest.fn(), 56 | error: jest.fn(), 57 | warn: jest.fn(), 58 | debug: jest.fn() 59 | }; 60 | fixture.detectChanges(); 61 | }); 62 | 63 | it('should create', () => { 64 | expect(component).toBeTruthy(); 65 | const html: HTMLElement = fixture.nativeElement; 66 | const webComponentHtml = html.querySelector( 67 | 'descope-audit-management-widget' 68 | ); 69 | expect(webComponentHtml).toBeDefined(); 70 | }); 71 | 72 | it('should correctly setup attributes based on inputs', () => { 73 | const html: HTMLElement = fixture.nativeElement; 74 | const webComponentHtml = html.querySelector( 75 | 'descope-audit-management-widget' 76 | )!; 77 | expect(webComponentHtml.getAttribute('project-id')).toStrictEqual('123'); 78 | expect(webComponentHtml.getAttribute('tenant')).toStrictEqual('tenant-1'); 79 | expect(webComponentHtml.getAttribute('widget-id')).toStrictEqual( 80 | 'widget-1' 81 | ); 82 | expect(webComponentHtml.getAttribute('logger')).toBeDefined(); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/audit-management/audit-management.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, Input, OnChanges, OnInit } from '@angular/core'; 2 | import DescopeAuditManagementWidget from '@descope/audit-management-widget'; 3 | import { ILogger } from '@descope/web-component'; 4 | import { DescopeAuthConfig } from '../../types/types'; 5 | 6 | @Component({ 7 | selector: 'audit-management[tenant]', 8 | standalone: true, 9 | template: '' 10 | }) 11 | export class AuditManagementComponent implements OnInit, OnChanges { 12 | projectId: string; 13 | baseUrl?: string; 14 | baseStaticUrl?: string; 15 | @Input() tenant: string; 16 | @Input() widgetId: string; 17 | 18 | @Input() theme: 'light' | 'dark' | 'os'; 19 | @Input() debug: boolean; 20 | @Input() logger: ILogger; 21 | 22 | private readonly webComponent = new DescopeAuditManagementWidget(); 23 | 24 | constructor( 25 | private elementRef: ElementRef, 26 | descopeConfig: DescopeAuthConfig 27 | ) { 28 | this.projectId = descopeConfig.projectId; 29 | this.baseUrl = descopeConfig.baseUrl; 30 | this.baseStaticUrl = descopeConfig.baseStaticUrl; 31 | } 32 | 33 | ngOnInit() { 34 | this.setupWebComponent(); 35 | this.elementRef.nativeElement.appendChild(this.webComponent); 36 | } 37 | 38 | ngOnChanges(): void { 39 | this.setupWebComponent(); 40 | } 41 | 42 | private setupWebComponent() { 43 | this.webComponent.setAttribute('project-id', this.projectId); 44 | this.webComponent.setAttribute('tenant', this.tenant); 45 | this.webComponent.setAttribute('widget-id', this.widgetId); 46 | if (this.baseUrl) { 47 | this.webComponent.setAttribute('base-url', this.baseUrl); 48 | } 49 | if (this.baseStaticUrl) { 50 | this.webComponent.setAttribute('base-static-url', this.baseStaticUrl); 51 | } 52 | if (this.theme) { 53 | this.webComponent.setAttribute('theme', this.theme); 54 | } 55 | if (this.debug) { 56 | this.webComponent.setAttribute('debug', this.debug.toString()); 57 | } 58 | 59 | if (this.logger) { 60 | (this.webComponent as any).logger = this.logger; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/descope/descope.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { default as DescopeWC } from '@descope/web-component'; 3 | import { DescopeComponent } from './descope.component'; 4 | import createSdk from '@descope/web-js-sdk'; 5 | import { DescopeAuthConfig } from '../../types/types'; 6 | import { spyOn } from 'jest-mock'; 7 | import { CUSTOM_ELEMENTS_SCHEMA, EventEmitter } from '@angular/core'; 8 | import mocked = jest.mocked; 9 | 10 | jest.mock('@descope/web-js-sdk'); 11 | //Mock DescopeWebComponent 12 | jest.mock('@descope/web-component', () => { 13 | return jest.fn(() => { 14 | // Create a mock DOM element 15 | return document.createElement('descope-wc'); 16 | }); 17 | }); 18 | 19 | describe('DescopeComponent', () => { 20 | let component: DescopeComponent; 21 | let fixture: ComponentFixture; 22 | let mockedCreateSdk: jest.Mock; 23 | const onSessionTokenChangeSpy = jest.fn(); 24 | const onUserChangeSpy = jest.fn(); 25 | const afterRequestHooksSpy = jest.fn(); 26 | const mockConfig: DescopeAuthConfig = { 27 | projectId: 'someProject' 28 | }; 29 | 30 | beforeEach(() => { 31 | mockedCreateSdk = mocked(createSdk); 32 | 33 | mockedCreateSdk.mockReturnValue({ 34 | onSessionTokenChange: onSessionTokenChangeSpy, 35 | onUserChange: onUserChangeSpy, 36 | httpClient: { 37 | hooks: { 38 | afterRequest: afterRequestHooksSpy 39 | } 40 | } 41 | }); 42 | 43 | TestBed.configureTestingModule({ 44 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 45 | providers: [ 46 | DescopeAuthConfig, 47 | { provide: DescopeAuthConfig, useValue: mockConfig } 48 | ] 49 | }); 50 | 51 | fixture = TestBed.createComponent(DescopeComponent); 52 | component = fixture.componentInstance; 53 | component.projectId = '123'; 54 | component.flowId = 'sign-in'; 55 | component.locale = 'en-US'; 56 | component.success = new EventEmitter(); 57 | component.error = new EventEmitter(); 58 | component.logger = { 59 | info: jest.fn(), 60 | error: jest.fn(), 61 | warn: jest.fn(), 62 | debug: jest.fn() 63 | }; 64 | component.errorTransformer = jest.fn(); 65 | component.client = {}; 66 | component.form = {}; 67 | component.storeLastAuthenticatedUser = true; 68 | fixture.detectChanges(); 69 | }); 70 | 71 | it('should create', () => { 72 | expect(component).toBeTruthy(); 73 | const html: HTMLElement = fixture.nativeElement; 74 | const webComponentHtml = html.querySelector('descope-wc'); 75 | expect(webComponentHtml).toBeDefined(); 76 | 77 | expect(DescopeWC.sdkConfigOverrides).toEqual({ 78 | baseHeaders: { 79 | 'x-descope-sdk-name': 'angular', 80 | 'x-descope-sdk-version': '0.0.0' 81 | }, 82 | persistTokens: false, 83 | hooks: {} 84 | }); 85 | }); 86 | 87 | it('should correctly setup attributes based on inputs', () => { 88 | const html: HTMLElement = fixture.nativeElement; 89 | const webComponentHtml = html.querySelector('descope-wc')!; 90 | expect(webComponentHtml.getAttribute('project-id')).toStrictEqual('123'); 91 | expect(webComponentHtml.getAttribute('flow-id')).toStrictEqual('sign-in'); 92 | expect(webComponentHtml.getAttribute('locale')).toStrictEqual('en-US'); 93 | expect(webComponentHtml.getAttribute('logger')).toBeDefined(); 94 | expect(webComponentHtml.getAttribute('error-transformer')).toBeDefined(); 95 | expect(webComponentHtml.getAttribute('redirect-url')).toBeNull(); 96 | expect( 97 | webComponentHtml?.getAttribute('store-last-authenticated-user') 98 | ).toEqual('true'); 99 | }); 100 | 101 | it('should emit success when web component emits success', () => { 102 | const html: HTMLElement = fixture.nativeElement; 103 | const webComponentHtml = html.querySelector('descope-wc')!; 104 | 105 | const event = { 106 | detail: { user: { name: 'user1' }, sessionJwt: 'session1' } 107 | }; 108 | component.success.subscribe((e) => { 109 | expect(afterRequestHooksSpy).toHaveBeenCalled(); 110 | expect(e.detail).toHaveBeenCalledWith(event.detail); 111 | }); 112 | webComponentHtml.dispatchEvent(new CustomEvent('success', event)); 113 | }); 114 | 115 | it('should emit error when web component emits error', () => { 116 | const html: HTMLElement = fixture.nativeElement; 117 | const webComponentHtml = html.querySelector('descope-wc')!; 118 | 119 | const event = { 120 | detail: { 121 | errorCode: 'someError', 122 | errorDescription: 'someErrorDescription' 123 | } 124 | }; 125 | component.error.subscribe((e) => { 126 | expect(e.detail).toEqual(event.detail); 127 | }); 128 | webComponentHtml.dispatchEvent(new CustomEvent('error', event)); 129 | }); 130 | 131 | it('should emit ready when web component emits ready', () => { 132 | const spy = spyOn(component.ready, 'emit'); 133 | 134 | const html: HTMLElement = fixture.nativeElement; 135 | const webComponentHtml = html.querySelector('descope-wc')!; 136 | 137 | webComponentHtml.dispatchEvent(new CustomEvent('ready', {})); 138 | 139 | expect(spy).toHaveBeenCalled(); 140 | }); 141 | }); 142 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/descope/descope.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | ElementRef, 4 | EventEmitter, 5 | Input, 6 | OnChanges, 7 | OnInit, 8 | Output 9 | } from '@angular/core'; 10 | import DescopeWebComponent from '@descope/web-component'; 11 | import DescopeWc, { ILogger } from '@descope/web-component'; 12 | import { DescopeAuthService } from '../../services/descope-auth.service'; 13 | import { from } from 'rxjs'; 14 | import { baseHeaders } from '../../utils/constants'; 15 | import { DescopeAuthConfig } from '../../types/types'; 16 | 17 | @Component({ 18 | selector: 'descope[flowId]', 19 | standalone: true, 20 | template: '' 21 | }) 22 | export class DescopeComponent implements OnInit, OnChanges { 23 | projectId: string; 24 | baseUrl?: string; 25 | baseStaticUrl?: string; 26 | storeLastAuthenticatedUser?: boolean; 27 | @Input() flowId: string; 28 | 29 | @Input() locale: string; 30 | @Input() theme: 'light' | 'dark' | 'os'; 31 | @Input() tenant: string; 32 | @Input() telemetryKey: string; 33 | @Input() redirectUrl: string; 34 | @Input() autoFocus: true | false | 'skipFirstScreen'; 35 | 36 | @Input() debug: boolean; 37 | @Input() errorTransformer: (error: { text: string; type: string }) => string; 38 | @Input() client: Record; 39 | @Input() form: Record; 40 | @Input() logger: ILogger; 41 | 42 | @Output() success: EventEmitter = 43 | new EventEmitter(); 44 | @Output() error: EventEmitter = new EventEmitter(); 45 | @Output() ready: EventEmitter = new EventEmitter(); 46 | 47 | private readonly webComponent: DescopeWebComponent = 48 | new DescopeWebComponent(); 49 | 50 | constructor( 51 | private elementRef: ElementRef, 52 | private authService: DescopeAuthService, 53 | descopeConfig: DescopeAuthConfig 54 | ) { 55 | this.projectId = descopeConfig.projectId; 56 | this.baseUrl = descopeConfig.baseUrl; 57 | this.baseStaticUrl = descopeConfig.baseStaticUrl; 58 | this.storeLastAuthenticatedUser = descopeConfig.storeLastAuthenticatedUser; 59 | } 60 | 61 | ngOnInit() { 62 | const sdk = this.authService.descopeSdk; // Capture the class context in a variable 63 | DescopeWc.sdkConfigOverrides = { 64 | // Overrides the web-component's base headers to indicate usage via the React SDK 65 | baseHeaders, 66 | // Disables token persistence within the web-component to delegate token management 67 | // to the global SDK hooks. This ensures token handling aligns with the SDK's configuration, 68 | // and web-component requests leverage the global SDK's beforeRequest hooks for consistency 69 | persistTokens: false, 70 | hooks: { 71 | get beforeRequest() { 72 | // Retrieves the beforeRequest hook from the global SDK, which is initialized 73 | // within the AuthProvider using the desired configuration. This approach ensures 74 | // the web-component utilizes the same beforeRequest hooks as the global SDK 75 | return sdk.httpClient.hooks?.beforeRequest; 76 | }, 77 | set beforeRequest(_) { 78 | // The empty setter prevents runtime errors when attempts are made to assign a value to 'beforeRequest'. 79 | // JavaScript objects default to having both getters and setters 80 | } 81 | } 82 | }; 83 | this.setupWebComponent(); 84 | this.elementRef.nativeElement.appendChild(this.webComponent); 85 | } 86 | 87 | ngOnChanges(): void { 88 | this.setupWebComponent(); 89 | } 90 | 91 | private setupWebComponent() { 92 | this.webComponent.setAttribute('project-id', this.projectId); 93 | this.webComponent.setAttribute('flow-id', this.flowId); 94 | 95 | if (this.baseUrl) { 96 | this.webComponent.setAttribute('base-url', this.baseUrl); 97 | } 98 | if (this.baseStaticUrl) { 99 | this.webComponent.setAttribute('base-static-url', this.baseStaticUrl); 100 | } 101 | if (this.storeLastAuthenticatedUser) { 102 | this.webComponent.setAttribute( 103 | 'store-last-authenticated-user', 104 | this.storeLastAuthenticatedUser.toString() 105 | ); 106 | } 107 | if (this.locale) { 108 | this.webComponent.setAttribute('locale', this.locale); 109 | } 110 | if (this.theme) { 111 | this.webComponent.setAttribute('theme', this.theme); 112 | } 113 | if (this.tenant) { 114 | this.webComponent.setAttribute('tenant', this.tenant); 115 | } 116 | if (this.telemetryKey) { 117 | this.webComponent.setAttribute('telemetryKey', this.telemetryKey); 118 | } 119 | if (this.redirectUrl) { 120 | this.webComponent.setAttribute('redirect-url', this.redirectUrl); 121 | } 122 | if (this.autoFocus) { 123 | this.webComponent.setAttribute('auto-focus', this.autoFocus.toString()); 124 | } 125 | if (this.debug) { 126 | this.webComponent.setAttribute('debug', this.debug.toString()); 127 | } 128 | 129 | if (this.errorTransformer) { 130 | this.webComponent.errorTransformer = this.errorTransformer; 131 | } 132 | 133 | if (this.client) { 134 | this.webComponent.setAttribute('client', JSON.stringify(this.client)); 135 | } 136 | 137 | if (this.form) { 138 | this.webComponent.setAttribute('form', JSON.stringify(this.form)); 139 | } 140 | 141 | if (this.logger) { 142 | this.webComponent.logger = this.logger; 143 | } 144 | 145 | this.webComponent.addEventListener('success', (e: Event) => { 146 | from( 147 | this.authService.descopeSdk.httpClient.hooks?.afterRequest!( 148 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 149 | {} as any, 150 | new Response(JSON.stringify((e as CustomEvent).detail)) 151 | ) as Promise 152 | ).subscribe(() => { 153 | if (this.success) { 154 | this.success?.emit(e as CustomEvent); 155 | } 156 | }); 157 | }); 158 | 159 | if (this.error) { 160 | this.webComponent.addEventListener('error', (e: Event) => { 161 | this.error?.emit(e as CustomEvent); 162 | }); 163 | } 164 | 165 | if (this.ready) { 166 | this.webComponent.addEventListener('ready', () => { 167 | this.ready?.emit(); 168 | }); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/role-management/role-management.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { RoleManagementComponent } from './role-management.component'; 3 | import createSdk from '@descope/web-js-sdk'; 4 | import { DescopeAuthConfig } from '../../types/types'; 5 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 6 | import mocked = jest.mocked; 7 | 8 | jest.mock('@descope/web-js-sdk'); 9 | //Mock DescopeRoleManagementWidget 10 | jest.mock('@descope/role-management-widget', () => { 11 | return jest.fn(() => { 12 | // Create a mock DOM element 13 | return document.createElement('descope-role-management-widget'); 14 | }); 15 | }); 16 | 17 | describe('DescopeRoleManagementComponent', () => { 18 | let component: RoleManagementComponent; 19 | let fixture: ComponentFixture; 20 | let mockedCreateSdk: jest.Mock; 21 | const onSessionTokenChangeSpy = jest.fn(); 22 | const onRoleChangeSpy = jest.fn(); 23 | const afterRequestHooksSpy = jest.fn(); 24 | const mockConfig: DescopeAuthConfig = { 25 | projectId: 'someProject' 26 | }; 27 | 28 | beforeEach(() => { 29 | mockedCreateSdk = mocked(createSdk); 30 | 31 | mockedCreateSdk.mockReturnValue({ 32 | onSessionTokenChange: onSessionTokenChangeSpy, 33 | onRoleChange: onRoleChangeSpy, 34 | httpClient: { 35 | hooks: { 36 | afterRequest: afterRequestHooksSpy 37 | } 38 | } 39 | }); 40 | 41 | TestBed.configureTestingModule({ 42 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 43 | providers: [ 44 | DescopeAuthConfig, 45 | { provide: DescopeAuthConfig, useValue: mockConfig } 46 | ] 47 | }); 48 | 49 | fixture = TestBed.createComponent(RoleManagementComponent); 50 | component = fixture.componentInstance; 51 | component.projectId = '123'; 52 | component.tenant = 'tenant-1'; 53 | component.widgetId = 'widget-1'; 54 | component.logger = { 55 | info: jest.fn(), 56 | error: jest.fn(), 57 | warn: jest.fn(), 58 | debug: jest.fn() 59 | }; 60 | fixture.detectChanges(); 61 | }); 62 | 63 | it('should create', () => { 64 | expect(component).toBeTruthy(); 65 | const html: HTMLElement = fixture.nativeElement; 66 | const webComponentHtml = html.querySelector( 67 | 'descope-role-management-widget' 68 | ); 69 | expect(webComponentHtml).toBeDefined(); 70 | }); 71 | 72 | it('should correctly setup attributes based on inputs', () => { 73 | const html: HTMLElement = fixture.nativeElement; 74 | const webComponentHtml = html.querySelector( 75 | 'descope-role-management-widget' 76 | )!; 77 | expect(webComponentHtml.getAttribute('project-id')).toStrictEqual('123'); 78 | expect(webComponentHtml.getAttribute('tenant')).toStrictEqual('tenant-1'); 79 | expect(webComponentHtml.getAttribute('widget-id')).toStrictEqual( 80 | 'widget-1' 81 | ); 82 | expect(webComponentHtml.getAttribute('logger')).toBeDefined(); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/role-management/role-management.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, Input, OnChanges, OnInit } from '@angular/core'; 2 | import DescopeRoleManagementWidget from '@descope/role-management-widget'; 3 | import { ILogger } from '@descope/web-component'; 4 | import { DescopeAuthConfig } from '../../types/types'; 5 | 6 | @Component({ 7 | selector: 'role-management[tenant]', 8 | standalone: true, 9 | template: '' 10 | }) 11 | export class RoleManagementComponent implements OnInit, OnChanges { 12 | projectId: string; 13 | baseUrl?: string; 14 | baseStaticUrl?: string; 15 | @Input() tenant: string; 16 | @Input() widgetId: string; 17 | 18 | @Input() theme: 'light' | 'dark' | 'os'; 19 | @Input() debug: boolean; 20 | @Input() logger: ILogger; 21 | 22 | private readonly webComponent = new DescopeRoleManagementWidget(); 23 | 24 | constructor( 25 | private elementRef: ElementRef, 26 | descopeConfig: DescopeAuthConfig 27 | ) { 28 | this.projectId = descopeConfig.projectId; 29 | this.baseUrl = descopeConfig.baseUrl; 30 | this.baseStaticUrl = descopeConfig.baseStaticUrl; 31 | } 32 | 33 | ngOnInit() { 34 | this.setupWebComponent(); 35 | this.elementRef.nativeElement.appendChild(this.webComponent); 36 | } 37 | 38 | ngOnChanges(): void { 39 | this.setupWebComponent(); 40 | } 41 | 42 | private setupWebComponent() { 43 | this.webComponent.setAttribute('project-id', this.projectId); 44 | this.webComponent.setAttribute('tenant', this.tenant); 45 | this.webComponent.setAttribute('widget-id', this.widgetId); 46 | if (this.baseUrl) { 47 | this.webComponent.setAttribute('base-url', this.baseUrl); 48 | } 49 | if (this.baseStaticUrl) { 50 | this.webComponent.setAttribute('base-static-url', this.baseStaticUrl); 51 | } 52 | if (this.theme) { 53 | this.webComponent.setAttribute('theme', this.theme); 54 | } 55 | if (this.debug) { 56 | this.webComponent.setAttribute('debug', this.debug.toString()); 57 | } 58 | 59 | if (this.logger) { 60 | (this.webComponent as any).logger = this.logger; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/sign-in-flow/sign-in-flow.component.html: -------------------------------------------------------------------------------- 1 | 17 | 18 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/sign-in-flow/sign-in-flow.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { SignInFlowComponent } from './sign-in-flow.component'; 3 | import { ngMocks } from 'ng-mocks'; 4 | import { DescopeComponent } from '../descope/descope.component'; 5 | import { DescopeAuthConfig } from '../../types/types'; 6 | import createSdk from '@descope/web-js-sdk'; 7 | import mocked = jest.mocked; 8 | 9 | jest.mock('@descope/web-js-sdk'); 10 | jest.mock('@descope/web-component', () => { 11 | return jest.fn(() => { 12 | // Create a mock DOM element 13 | return document.createElement('descope-wc'); 14 | }); 15 | }); 16 | 17 | describe('SignInFlowComponent', () => { 18 | let component: SignInFlowComponent; 19 | let fixture: ComponentFixture; 20 | let mockedCreateSdk: jest.Mock; 21 | 22 | beforeEach(() => { 23 | mockedCreateSdk = mocked(createSdk); 24 | 25 | mockedCreateSdk.mockReturnValue({ 26 | onSessionTokenChange: jest.fn(), 27 | onUserChange: jest.fn() 28 | }); 29 | 30 | TestBed.configureTestingModule({ 31 | providers: [ 32 | DescopeAuthConfig, 33 | { 34 | provide: DescopeAuthConfig, 35 | useValue: { 36 | projectId: 'someProject' 37 | } 38 | } 39 | ] 40 | }); 41 | 42 | fixture = TestBed.createComponent(SignInFlowComponent); 43 | component = fixture.componentInstance; 44 | fixture.detectChanges(); 45 | }); 46 | 47 | it('should create and be correctly configured', () => { 48 | expect(component).toBeTruthy(); 49 | const mockComponent = 50 | ngMocks.find('[flowId=sign-in]').componentInstance; 51 | expect(mockComponent.flowId).toStrictEqual('sign-in'); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/sign-in-flow/sign-in-flow.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | import { ILogger } from '@descope/web-component'; 3 | import { DescopeComponent } from '../descope/descope.component'; 4 | import { DescopeAuthConfig } from '../../types/types'; 5 | 6 | @Component({ 7 | selector: 'descope-sign-in-flow', 8 | standalone: true, 9 | imports: [DescopeComponent], 10 | templateUrl: './sign-in-flow.component.html' 11 | }) 12 | export class SignInFlowComponent { 13 | projectId: string; 14 | 15 | @Input() locale: string; 16 | @Input() theme: 'light' | 'dark' | 'os'; 17 | @Input() tenant: string; 18 | @Input() telemetryKey: string; 19 | @Input() redirectUrl: string; 20 | @Input() autoFocus: true | false | 'skipFirstScreen'; 21 | 22 | @Input() debug: boolean; 23 | @Input() errorTransformer: (error: { text: string; type: string }) => string; 24 | @Input() client: Record; 25 | @Input() form: Record; 26 | @Input() logger: ILogger; 27 | 28 | @Output() success: EventEmitter = 29 | new EventEmitter(); 30 | @Output() error: EventEmitter = new EventEmitter(); 31 | 32 | constructor(descopeConfig: DescopeAuthConfig) { 33 | this.projectId = descopeConfig.projectId; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/sign-up-flow/sign-up-flow.component.html: -------------------------------------------------------------------------------- 1 | 17 | 18 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/sign-up-flow/sign-up-flow.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { SignUpFlowComponent } from './sign-up-flow.component'; 3 | import { DescopeComponent } from '../descope/descope.component'; 4 | import { ngMocks } from 'ng-mocks'; 5 | import createSdk from '@descope/web-js-sdk'; 6 | import { DescopeAuthConfig } from '../../types/types'; 7 | import mocked = jest.mocked; 8 | 9 | jest.mock('@descope/web-js-sdk'); 10 | jest.mock('@descope/web-component', () => { 11 | return jest.fn(() => { 12 | // Create a mock DOM element 13 | return document.createElement('descope-wc'); 14 | }); 15 | }); 16 | describe('SignUpFlowComponent', () => { 17 | let component: SignUpFlowComponent; 18 | let fixture: ComponentFixture; 19 | let mockedCreateSdk: jest.Mock; 20 | 21 | beforeEach(() => { 22 | mockedCreateSdk = mocked(createSdk); 23 | 24 | mockedCreateSdk.mockReturnValue({ 25 | onSessionTokenChange: jest.fn(), 26 | onUserChange: jest.fn() 27 | }); 28 | 29 | TestBed.configureTestingModule({ 30 | providers: [ 31 | DescopeAuthConfig, 32 | { 33 | provide: DescopeAuthConfig, 34 | useValue: { 35 | projectId: 'someProject' 36 | } 37 | } 38 | ] 39 | }); 40 | fixture = TestBed.createComponent(SignUpFlowComponent); 41 | component = fixture.componentInstance; 42 | fixture.detectChanges(); 43 | }); 44 | 45 | it('should create and be correctly configured', () => { 46 | expect(component).toBeTruthy(); 47 | const mockComponent = 48 | ngMocks.find('[flowId=sign-up]').componentInstance; 49 | expect(mockComponent.flowId).toStrictEqual('sign-up'); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/sign-up-flow/sign-up-flow.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | import { ILogger } from '@descope/web-component'; 3 | import { DescopeComponent } from '../descope/descope.component'; 4 | import { DescopeAuthConfig } from '../../types/types'; 5 | 6 | @Component({ 7 | selector: 'descope-sign-up-flow', 8 | standalone: true, 9 | imports: [DescopeComponent], 10 | templateUrl: './sign-up-flow.component.html' 11 | }) 12 | export class SignUpFlowComponent { 13 | projectId: string; 14 | 15 | @Input() locale: string; 16 | @Input() theme: 'light' | 'dark' | 'os'; 17 | @Input() tenant: string; 18 | @Input() telemetryKey: string; 19 | @Input() redirectUrl: string; 20 | @Input() autoFocus: true | false | 'skipFirstScreen'; 21 | 22 | @Input() debug: boolean; 23 | @Input() errorTransformer: (error: { text: string; type: string }) => string; 24 | @Input() client: Record; 25 | @Input() form: Record; 26 | @Input() logger: ILogger; 27 | 28 | @Output() success: EventEmitter = 29 | new EventEmitter(); 30 | @Output() error: EventEmitter = new EventEmitter(); 31 | 32 | constructor(descopeConfig: DescopeAuthConfig) { 33 | this.projectId = descopeConfig.projectId; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/sign-up-or-in-flow/sign-up-or-in-flow.component.html: -------------------------------------------------------------------------------- 1 | 17 | 18 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/sign-up-or-in-flow/sign-up-or-in-flow.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { SignUpOrInFlowComponent } from './sign-up-or-in-flow.component'; 3 | import { DescopeComponent } from '../descope/descope.component'; 4 | import { ngMocks } from 'ng-mocks'; 5 | import createSdk from '@descope/web-js-sdk'; 6 | import { DescopeAuthConfig } from '../../types/types'; 7 | import mocked = jest.mocked; 8 | 9 | jest.mock('@descope/web-js-sdk'); 10 | jest.mock('@descope/web-component', () => { 11 | return jest.fn(() => { 12 | // Create a mock DOM element 13 | return document.createElement('descope-wc'); 14 | }); 15 | }); 16 | describe('SignUpOrInFlowComponent', () => { 17 | let component: SignUpOrInFlowComponent; 18 | let fixture: ComponentFixture; 19 | let mockedCreateSdk: jest.Mock; 20 | 21 | beforeEach(() => { 22 | mockedCreateSdk = mocked(createSdk); 23 | 24 | mockedCreateSdk.mockReturnValue({ 25 | onSessionTokenChange: jest.fn(), 26 | onUserChange: jest.fn() 27 | }); 28 | 29 | TestBed.configureTestingModule({ 30 | providers: [ 31 | DescopeAuthConfig, 32 | { 33 | provide: DescopeAuthConfig, 34 | useValue: { 35 | projectId: 'someProject' 36 | } 37 | } 38 | ] 39 | }); 40 | 41 | fixture = TestBed.createComponent(SignUpOrInFlowComponent); 42 | component = fixture.componentInstance; 43 | fixture.detectChanges(); 44 | }); 45 | 46 | it('should create and be correctly configured', () => { 47 | expect(component).toBeTruthy(); 48 | const mockComponent = ngMocks.find( 49 | '[flowId=sign-up-or-in]' 50 | ).componentInstance; 51 | expect(mockComponent.flowId).toStrictEqual('sign-up-or-in'); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/sign-up-or-in-flow/sign-up-or-in-flow.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | import { ILogger } from '@descope/web-component'; 3 | import { DescopeComponent } from '../descope/descope.component'; 4 | import { DescopeAuthConfig } from '../../types/types'; 5 | 6 | @Component({ 7 | selector: 'descope-sign-up-or-in-flow', 8 | standalone: true, 9 | imports: [DescopeComponent], 10 | templateUrl: './sign-up-or-in-flow.component.html' 11 | }) 12 | export class SignUpOrInFlowComponent { 13 | projectId: string; 14 | 15 | @Input() locale: string; 16 | @Input() theme: 'light' | 'dark' | 'os'; 17 | @Input() tenant: string; 18 | @Input() telemetryKey: string; 19 | @Input() redirectUrl: string; 20 | @Input() autoFocus: true | false | 'skipFirstScreen'; 21 | 22 | @Input() debug: boolean; 23 | @Input() errorTransformer: (error: { text: string; type: string }) => string; 24 | @Input() client: Record; 25 | @Input() form: Record; 26 | @Input() logger: ILogger; 27 | 28 | @Output() success: EventEmitter = 29 | new EventEmitter(); 30 | @Output() error: EventEmitter = new EventEmitter(); 31 | 32 | constructor(descopeConfig: DescopeAuthConfig) { 33 | this.projectId = descopeConfig.projectId; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/user-management/user-management.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { UserManagementComponent } from './user-management.component'; 3 | import createSdk from '@descope/web-js-sdk'; 4 | import { DescopeAuthConfig } from '../../types/types'; 5 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 6 | import mocked = jest.mocked; 7 | 8 | jest.mock('@descope/web-js-sdk'); 9 | //Mock DescopeUserManagementWidget 10 | jest.mock('@descope/user-management-widget', () => { 11 | return jest.fn(() => { 12 | // Create a mock DOM element 13 | return document.createElement('descope-user-management-widget'); 14 | }); 15 | }); 16 | 17 | describe('DescopeUserManagementComponent', () => { 18 | let component: UserManagementComponent; 19 | let fixture: ComponentFixture; 20 | let mockedCreateSdk: jest.Mock; 21 | const onSessionTokenChangeSpy = jest.fn(); 22 | const onUserChangeSpy = jest.fn(); 23 | const afterRequestHooksSpy = jest.fn(); 24 | const mockConfig: DescopeAuthConfig = { 25 | projectId: 'someProject' 26 | }; 27 | 28 | beforeEach(() => { 29 | mockedCreateSdk = mocked(createSdk); 30 | 31 | mockedCreateSdk.mockReturnValue({ 32 | onSessionTokenChange: onSessionTokenChangeSpy, 33 | onUserChange: onUserChangeSpy, 34 | httpClient: { 35 | hooks: { 36 | afterRequest: afterRequestHooksSpy 37 | } 38 | } 39 | }); 40 | 41 | TestBed.configureTestingModule({ 42 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 43 | providers: [ 44 | DescopeAuthConfig, 45 | { provide: DescopeAuthConfig, useValue: mockConfig } 46 | ] 47 | }); 48 | 49 | fixture = TestBed.createComponent(UserManagementComponent); 50 | component = fixture.componentInstance; 51 | component.projectId = '123'; 52 | component.tenant = 'tenant-1'; 53 | component.widgetId = 'widget-1'; 54 | component.logger = { 55 | info: jest.fn(), 56 | error: jest.fn(), 57 | warn: jest.fn(), 58 | debug: jest.fn() 59 | }; 60 | fixture.detectChanges(); 61 | }); 62 | 63 | it('should create', () => { 64 | expect(component).toBeTruthy(); 65 | const html: HTMLElement = fixture.nativeElement; 66 | const webComponentHtml = html.querySelector( 67 | 'descope-user-management-widget' 68 | ); 69 | expect(webComponentHtml).toBeDefined(); 70 | }); 71 | 72 | it('should correctly setup attributes based on inputs', () => { 73 | const html: HTMLElement = fixture.nativeElement; 74 | const webComponentHtml = html.querySelector( 75 | 'descope-user-management-widget' 76 | )!; 77 | expect(webComponentHtml.getAttribute('project-id')).toStrictEqual('123'); 78 | expect(webComponentHtml.getAttribute('tenant')).toStrictEqual('tenant-1'); 79 | expect(webComponentHtml.getAttribute('widget-id')).toStrictEqual( 80 | 'widget-1' 81 | ); 82 | expect(webComponentHtml.getAttribute('logger')).toBeDefined(); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/user-management/user-management.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, Input, OnChanges, OnInit } from '@angular/core'; 2 | import DescopeUserManagementWidget from '@descope/user-management-widget'; 3 | import { ILogger } from '@descope/web-component'; 4 | import { DescopeAuthConfig } from '../../types/types'; 5 | 6 | @Component({ 7 | selector: 'user-management[tenant]', 8 | standalone: true, 9 | template: '' 10 | }) 11 | export class UserManagementComponent implements OnInit, OnChanges { 12 | projectId: string; 13 | baseUrl?: string; 14 | baseStaticUrl?: string; 15 | @Input() tenant: string; 16 | @Input() widgetId: string; 17 | 18 | @Input() theme: 'light' | 'dark' | 'os'; 19 | @Input() debug: boolean; 20 | @Input() logger: ILogger; 21 | 22 | private readonly webComponent = new DescopeUserManagementWidget(); 23 | 24 | constructor( 25 | private elementRef: ElementRef, 26 | descopeConfig: DescopeAuthConfig 27 | ) { 28 | this.projectId = descopeConfig.projectId; 29 | this.baseUrl = descopeConfig.baseUrl; 30 | this.baseStaticUrl = descopeConfig.baseStaticUrl; 31 | } 32 | 33 | ngOnInit() { 34 | this.setupWebComponent(); 35 | this.elementRef.nativeElement.appendChild(this.webComponent); 36 | } 37 | 38 | ngOnChanges(): void { 39 | this.setupWebComponent(); 40 | } 41 | 42 | private setupWebComponent() { 43 | this.webComponent.setAttribute('project-id', this.projectId); 44 | this.webComponent.setAttribute('tenant', this.tenant); 45 | this.webComponent.setAttribute('widget-id', this.widgetId); 46 | 47 | if (this.baseUrl) { 48 | this.webComponent.setAttribute('base-url', this.baseUrl); 49 | } 50 | if (this.baseStaticUrl) { 51 | this.webComponent.setAttribute('base-static-url', this.baseStaticUrl); 52 | } 53 | if (this.theme) { 54 | this.webComponent.setAttribute('theme', this.theme); 55 | } 56 | if (this.debug) { 57 | this.webComponent.setAttribute('debug', this.debug.toString()); 58 | } 59 | 60 | if (this.logger) { 61 | (this.webComponent as any).logger = this.logger; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/user-profile/user-profile.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { UserProfileComponent } from './user-profile.component'; 3 | import createSdk from '@descope/web-js-sdk'; 4 | import { DescopeAuthConfig } from '../../types/types'; 5 | import { CUSTOM_ELEMENTS_SCHEMA, EventEmitter } from '@angular/core'; 6 | import mocked = jest.mocked; 7 | 8 | jest.mock('@descope/web-js-sdk'); 9 | //Mock DescopeUserProfileWidget 10 | jest.mock('@descope/user-profile-widget', () => { 11 | return jest.fn(() => { 12 | // Create a mock DOM element 13 | return document.createElement('descope-user-profile-widget'); 14 | }); 15 | }); 16 | 17 | describe('DescopeUserProfileComponent', () => { 18 | let component: UserProfileComponent; 19 | let fixture: ComponentFixture; 20 | let mockedCreateSdk: jest.Mock; 21 | const onSessionTokenChangeSpy = jest.fn(); 22 | const onAuditChangeSpy = jest.fn(); 23 | const afterRequestHooksSpy = jest.fn(); 24 | const mockConfig: DescopeAuthConfig = { 25 | projectId: 'someProject' 26 | }; 27 | 28 | beforeEach(() => { 29 | mockedCreateSdk = mocked(createSdk); 30 | 31 | mockedCreateSdk.mockReturnValue({ 32 | onSessionTokenChange: onSessionTokenChangeSpy, 33 | onAuditChange: onAuditChangeSpy, 34 | httpClient: { 35 | hooks: { 36 | afterRequest: afterRequestHooksSpy 37 | } 38 | } 39 | }); 40 | 41 | TestBed.configureTestingModule({ 42 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 43 | providers: [ 44 | DescopeAuthConfig, 45 | { provide: DescopeAuthConfig, useValue: mockConfig } 46 | ] 47 | }); 48 | 49 | fixture = TestBed.createComponent(UserProfileComponent); 50 | component = fixture.componentInstance; 51 | component.projectId = '123'; 52 | component.widgetId = 'widget-1'; 53 | component.logout = new EventEmitter(); 54 | component.logger = { 55 | info: jest.fn(), 56 | error: jest.fn(), 57 | warn: jest.fn(), 58 | debug: jest.fn() 59 | }; 60 | fixture.detectChanges(); 61 | }); 62 | 63 | it('should create', () => { 64 | expect(component).toBeTruthy(); 65 | const html: HTMLElement = fixture.nativeElement; 66 | const webComponentHtml = html.querySelector('descope-user-profile-widget'); 67 | expect(webComponentHtml).toBeDefined(); 68 | }); 69 | 70 | it('should correctly setup attributes based on inputs', () => { 71 | const html: HTMLElement = fixture.nativeElement; 72 | const webComponentHtml = html.querySelector('descope-user-profile-widget')!; 73 | expect(webComponentHtml.getAttribute('project-id')).toStrictEqual('123'); 74 | expect(webComponentHtml.getAttribute('widget-id')).toStrictEqual( 75 | 'widget-1' 76 | ); 77 | expect(webComponentHtml.getAttribute('logger')).toBeDefined(); 78 | }); 79 | 80 | it('should emit logout when web component emits logout', () => { 81 | const html: HTMLElement = fixture.nativeElement; 82 | const webComponentHtml = html.querySelector('descope-user-profile-widget')!; 83 | 84 | const event = { 85 | detail: 'logout' 86 | }; 87 | component.logout.subscribe((e) => { 88 | expect(afterRequestHooksSpy).toHaveBeenCalled(); 89 | expect(e.detail).toHaveBeenCalledWith(event.detail); 90 | }); 91 | webComponentHtml.dispatchEvent(new CustomEvent('logout', event)); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/components/user-profile/user-profile.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | ElementRef, 4 | EventEmitter, 5 | Input, 6 | OnChanges, 7 | OnInit, 8 | Output 9 | } from '@angular/core'; 10 | import DescopeUserProfileWidget from '@descope/user-profile-widget'; 11 | import { ILogger } from '@descope/web-component'; 12 | import { DescopeAuthConfig } from '../../types/types'; 13 | 14 | @Component({ 15 | selector: 'user-profile', 16 | standalone: true, 17 | template: '' 18 | }) 19 | export class UserProfileComponent implements OnInit, OnChanges { 20 | projectId: string; 21 | baseUrl?: string; 22 | baseStaticUrl?: string; 23 | @Input() widgetId: string; 24 | 25 | @Input() theme: 'light' | 'dark' | 'os'; 26 | @Input() debug: boolean; 27 | @Input() logger: ILogger; 28 | 29 | @Output() logout: EventEmitter = new EventEmitter(); 30 | 31 | private readonly webComponent = new DescopeUserProfileWidget(); 32 | 33 | constructor( 34 | private elementRef: ElementRef, 35 | descopeConfig: DescopeAuthConfig 36 | ) { 37 | this.projectId = descopeConfig.projectId; 38 | this.baseUrl = descopeConfig.baseUrl; 39 | this.baseStaticUrl = descopeConfig.baseStaticUrl; 40 | } 41 | 42 | ngOnInit() { 43 | this.setupWebComponent(); 44 | this.elementRef.nativeElement.appendChild(this.webComponent); 45 | } 46 | 47 | ngOnChanges(): void { 48 | this.setupWebComponent(); 49 | } 50 | 51 | private setupWebComponent() { 52 | this.webComponent.setAttribute('project-id', this.projectId); 53 | this.webComponent.setAttribute('widget-id', this.widgetId); 54 | if (this.baseUrl) { 55 | this.webComponent.setAttribute('base-url', this.baseUrl); 56 | } 57 | if (this.baseStaticUrl) { 58 | this.webComponent.setAttribute('base-static-url', this.baseStaticUrl); 59 | } 60 | if (this.theme) { 61 | this.webComponent.setAttribute('theme', this.theme); 62 | } 63 | if (this.debug) { 64 | this.webComponent.setAttribute('debug', this.debug.toString()); 65 | } 66 | 67 | if (this.logger) { 68 | (this.webComponent as any).logger = this.logger; 69 | } 70 | 71 | if (this.logout) { 72 | this.webComponent.addEventListener('logout', (e: Event) => { 73 | this.logout?.emit(e as CustomEvent); 74 | }); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/descope-auth.module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CUSTOM_ELEMENTS_SCHEMA, 3 | ModuleWithProviders, 4 | NgModule, 5 | Optional, 6 | SkipSelf 7 | } from '@angular/core'; 8 | import { DescopeComponent } from './components/descope/descope.component'; 9 | import { SignInFlowComponent } from './components/sign-in-flow/sign-in-flow.component'; 10 | import { SignUpFlowComponent } from './components/sign-up-flow/sign-up-flow.component'; 11 | import { SignUpOrInFlowComponent } from './components/sign-up-or-in-flow/sign-up-or-in-flow.component'; 12 | import { UserManagementComponent } from './components/user-management/user-management.component'; 13 | import { RoleManagementComponent } from './components/role-management/role-management.component'; 14 | import { AccessKeyManagementComponent } from './components/access-key-management/access-key-management.component'; 15 | import { AuditManagementComponent } from './components/audit-management/audit-management.component'; 16 | import { UserProfileComponent } from './components/user-profile/user-profile.component'; 17 | import { DescopeAuthConfig } from './types/types'; 18 | 19 | @NgModule({ 20 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 21 | imports: [ 22 | DescopeComponent, 23 | SignInFlowComponent, 24 | SignUpFlowComponent, 25 | SignUpOrInFlowComponent, 26 | UserManagementComponent, 27 | RoleManagementComponent, 28 | AccessKeyManagementComponent, 29 | AuditManagementComponent, 30 | UserProfileComponent 31 | ], 32 | exports: [ 33 | DescopeComponent, 34 | SignInFlowComponent, 35 | SignUpFlowComponent, 36 | SignUpOrInFlowComponent, 37 | UserManagementComponent, 38 | RoleManagementComponent, 39 | AccessKeyManagementComponent, 40 | AuditManagementComponent, 41 | UserProfileComponent 42 | ] 43 | }) 44 | export class DescopeAuthModule { 45 | constructor(@Optional() @SkipSelf() parentModule?: DescopeAuthModule) { 46 | if (parentModule) { 47 | throw new Error( 48 | 'DescopeAuthModule is already loaded. Import it only once' 49 | ); 50 | } 51 | } 52 | 53 | static forRoot( 54 | config?: DescopeAuthConfig 55 | ): ModuleWithProviders { 56 | return { 57 | ngModule: DescopeAuthModule, 58 | providers: [{ provide: DescopeAuthConfig, useValue: config }] 59 | }; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/services/descope-auth.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { delay, Observable } from 'rxjs'; 2 | import { ActivatedRoute, Router } from '@angular/router'; 3 | import { descopeAuthGuard } from './descope-auth.guard'; 4 | import { fakeAsync, TestBed, tick } from '@angular/core/testing'; 5 | import { RouterTestingModule } from '@angular/router/testing'; 6 | import { DescopeAuthService } from './descope-auth.service'; 7 | 8 | describe('descopeAuthGuard', () => { 9 | const authServiceMock = { 10 | isAuthenticated: jest.fn() 11 | }; 12 | 13 | const routerMock = { 14 | navigate: jest.fn() 15 | }; 16 | 17 | beforeEach(async () => { 18 | await TestBed.configureTestingModule({ 19 | imports: [RouterTestingModule], 20 | providers: [ 21 | { 22 | provide: Router, 23 | useValue: routerMock 24 | }, 25 | { 26 | provide: DescopeAuthService, 27 | useValue: authServiceMock 28 | }, 29 | { 30 | provide: ActivatedRoute, 31 | useValue: { 32 | snapshot: { 33 | data: { 34 | descopeFallbackUrl: '/fallback' 35 | } 36 | } 37 | } 38 | } 39 | ] 40 | }); 41 | }); 42 | 43 | it('return true if authenticated', fakeAsync(() => { 44 | const activatedRoute = TestBed.inject(ActivatedRoute); 45 | authServiceMock.isAuthenticated.mockReturnValue(true); 46 | const guardResponse = TestBed.runInInjectionContext(() => { 47 | return descopeAuthGuard(activatedRoute.snapshot) as Observable; 48 | }); 49 | 50 | let guardOutput = null; 51 | guardResponse 52 | .pipe(delay(100)) 53 | .subscribe((response) => (guardOutput = response)); 54 | tick(100); 55 | 56 | expect(guardOutput).toBeTruthy(); 57 | })); 58 | 59 | it('navigate to fallbackUrl when not auth', fakeAsync(() => { 60 | const activatedRoute = TestBed.inject(ActivatedRoute); 61 | authServiceMock.isAuthenticated.mockReturnValue(false); 62 | routerMock.navigate.mockReturnValue([]); 63 | const guardResponse = TestBed.runInInjectionContext(() => { 64 | return descopeAuthGuard(activatedRoute.snapshot) as Observable; 65 | }); 66 | 67 | let guardOutput = null; 68 | guardResponse 69 | .pipe(delay(100)) 70 | .subscribe((response) => (guardOutput = response)); 71 | tick(100); 72 | 73 | expect(guardOutput).toBeFalsy(); 74 | expect(routerMock.navigate).toHaveBeenCalledWith(['/fallback']); 75 | })); 76 | }); 77 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/services/descope-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { inject } from '@angular/core'; 2 | 3 | import { DescopeAuthService } from './descope-auth.service'; 4 | import { ActivatedRouteSnapshot, Router } from '@angular/router'; 5 | import { from, of } from 'rxjs'; 6 | 7 | export const descopeAuthGuard = (route: ActivatedRouteSnapshot) => { 8 | const authService = inject(DescopeAuthService); 9 | const router = inject(Router); 10 | const fallbackUrl = route.data['descopeFallbackUrl']; 11 | const isAuthenticated = authService.isAuthenticated(); 12 | if (!isAuthenticated && !!fallbackUrl) { 13 | return from(router.navigate([fallbackUrl])); 14 | } 15 | return of(isAuthenticated); 16 | }; 17 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/services/descope-auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { DescopeAuthService } from './descope-auth.service'; 4 | import createSdk from '@descope/web-js-sdk'; 5 | import mocked = jest.mocked; 6 | import { DescopeAuthConfig } from '../types/types'; 7 | import { of, take, toArray } from 'rxjs'; 8 | 9 | jest.mock('@descope/web-js-sdk'); 10 | 11 | describe('DescopeAuthService', () => { 12 | let service: DescopeAuthService; 13 | let mockedCreateSdk: jest.Mock; 14 | let windowSpy: jest.SpyInstance; 15 | const onSessionTokenChangeSpy = jest.fn(); 16 | const onUserChangeSpy = jest.fn(); 17 | const getSessionTokenSpy = jest.fn(); 18 | const getRefreshTokenSpy = jest.fn(); 19 | const isJwtExpiredSpy = jest.fn(); 20 | const getJwtPermissionsSpy = jest.fn(); 21 | const getJwtRolesSpy = jest.fn(); 22 | const meSpy = jest.fn(); 23 | const refreshSpy = jest.fn(); 24 | const mockConfig: DescopeAuthConfig = { 25 | projectId: 'someProject' 26 | }; 27 | 28 | beforeEach(() => { 29 | mockedCreateSdk = mocked(createSdk); 30 | windowSpy = jest.spyOn(window, 'window', 'get'); 31 | 32 | mockedCreateSdk.mockReturnValue({ 33 | onSessionTokenChange: onSessionTokenChangeSpy, 34 | onUserChange: onUserChangeSpy, 35 | getSessionToken: getSessionTokenSpy, 36 | getRefreshToken: getRefreshTokenSpy, 37 | isJwtExpired: isJwtExpiredSpy, 38 | getJwtPermissions: getJwtPermissionsSpy, 39 | getJwtRoles: getJwtRolesSpy, 40 | me: meSpy, 41 | refresh: refreshSpy 42 | }); 43 | 44 | onSessionTokenChangeSpy.mockImplementation((fn) => fn()); 45 | onUserChangeSpy.mockImplementation((fn) => fn()); 46 | 47 | TestBed.configureTestingModule({ 48 | providers: [ 49 | DescopeAuthConfig, 50 | { provide: DescopeAuthConfig, useValue: mockConfig } 51 | ] 52 | }); 53 | service = TestBed.inject(DescopeAuthService); 54 | }); 55 | 56 | afterEach(() => { 57 | getSessionTokenSpy.mockReset(); 58 | getRefreshTokenSpy.mockReset(); 59 | isJwtExpiredSpy.mockReset(); 60 | getJwtPermissionsSpy.mockReset(); 61 | getJwtRolesSpy.mockReset(); 62 | }); 63 | 64 | it('should be created', () => { 65 | expect(service).toBeTruthy(); 66 | expect(mockedCreateSdk).toHaveBeenCalledWith( 67 | expect.objectContaining(mockConfig) 68 | ); 69 | expect(onSessionTokenChangeSpy).toHaveBeenCalled(); 70 | expect(onUserChangeSpy).toHaveBeenCalled(); 71 | }); 72 | 73 | describe('getSessionToken', () => { 74 | it('should call getSessionToken from sdk', () => { 75 | const token = 'abcd'; 76 | getSessionTokenSpy.mockReturnValueOnce(token); 77 | const result = service.getSessionToken(); 78 | expect(getSessionTokenSpy).toHaveBeenCalled(); 79 | expect(result).toStrictEqual(token); 80 | }); 81 | 82 | it('should warn when using getSessionToken in non browser environment', () => { 83 | const warnSpy = jest.spyOn(console, 'warn'); 84 | windowSpy.mockImplementationOnce(() => undefined); 85 | 86 | service.getSessionToken(); 87 | 88 | expect(warnSpy).toHaveBeenCalledWith( 89 | 'Get session token is not supported in SSR' 90 | ); 91 | expect(getSessionTokenSpy).not.toHaveBeenCalled(); 92 | }); 93 | }); 94 | 95 | describe('getRefreshToken', () => { 96 | it('should call getRefreshToken from sdk', () => { 97 | const token = 'abcd'; 98 | getRefreshTokenSpy.mockReturnValueOnce(token); 99 | const result = service.getRefreshToken(); 100 | expect(getRefreshTokenSpy).toHaveBeenCalled(); 101 | expect(result).toStrictEqual(token); 102 | }); 103 | 104 | it('should warn when using getRefreshToken in non browser environment', () => { 105 | const warnSpy = jest.spyOn(console, 'warn'); 106 | windowSpy.mockImplementationOnce(() => undefined); 107 | 108 | service.getRefreshToken(); 109 | 110 | expect(warnSpy).toHaveBeenCalledWith( 111 | 'Get refresh token is not supported in SSR' 112 | ); 113 | expect(getRefreshTokenSpy).not.toHaveBeenCalled(); 114 | }); 115 | }); 116 | 117 | describe('isSessionTokenExpired', () => { 118 | it('should call isSessionTokenExpired from sdk', () => { 119 | const token = 'abcd'; 120 | getSessionTokenSpy.mockReturnValueOnce(token); 121 | service.isSessionTokenExpired(); 122 | expect(getSessionTokenSpy).toHaveBeenCalled(); 123 | expect(isJwtExpiredSpy).toHaveBeenCalledWith(token); 124 | }); 125 | 126 | it('should warn when using isSessionTokenExpired in non browser environment', () => { 127 | const warnSpy = jest.spyOn(console, 'warn'); 128 | windowSpy.mockImplementationOnce(() => undefined); 129 | 130 | service.isSessionTokenExpired('some token'); 131 | expect(warnSpy).toHaveBeenCalledWith( 132 | 'isSessionTokenExpired is not supported in SSR' 133 | ); 134 | expect(isJwtExpiredSpy).not.toHaveBeenCalled(); 135 | }); 136 | }); 137 | 138 | describe('isRefreshTokenExpired', () => { 139 | it('should call isRefreshTokenExpired from sdk', () => { 140 | const token = 'abcd'; 141 | getRefreshTokenSpy.mockReturnValueOnce(token); 142 | service.isRefreshTokenExpired(); 143 | expect(getRefreshTokenSpy).toHaveBeenCalled(); 144 | expect(isJwtExpiredSpy).toHaveBeenCalledWith(token); 145 | }); 146 | 147 | it('should warn when using isRefreshTokenExpired in non browser environment', () => { 148 | const warnSpy = jest.spyOn(console, 'warn'); 149 | windowSpy.mockImplementationOnce(() => undefined); 150 | 151 | service.isRefreshTokenExpired('some token'); 152 | expect(warnSpy).toHaveBeenCalledWith( 153 | 'isRefreshTokenExpired is not supported in SSR' 154 | ); 155 | expect(isJwtExpiredSpy).not.toHaveBeenCalled(); 156 | }); 157 | }); 158 | 159 | describe('getJwtPermissions', () => { 160 | it('should return permissions for token from sdk', () => { 161 | const permissions = ['edit']; 162 | getJwtPermissionsSpy.mockReturnValueOnce(permissions); 163 | const result = service.getJwtPermissions('token'); 164 | expect(getJwtPermissionsSpy).toHaveBeenCalledWith('token', undefined); 165 | expect(result).toStrictEqual(permissions); 166 | }); 167 | 168 | it('should return empty array and log error when there is no token', () => { 169 | const errorSpy = jest.spyOn(console, 'error'); 170 | getSessionTokenSpy.mockReturnValueOnce(null); 171 | const result = service.getJwtPermissions(); 172 | expect(errorSpy).toHaveBeenCalledWith( 173 | 'Could not get JWT Permissions - not authenticated' 174 | ); 175 | expect(getJwtPermissionsSpy).not.toHaveBeenCalled(); 176 | expect(result).toStrictEqual([]); 177 | }); 178 | }); 179 | 180 | describe('getJwtRoles', () => { 181 | it('should return roles for token from sdk', () => { 182 | const roles = ['admin']; 183 | getJwtRolesSpy.mockReturnValueOnce(roles); 184 | const result = service.getJwtRoles('token'); 185 | expect(getJwtRolesSpy).toHaveBeenCalledWith('token', undefined); 186 | expect(result).toStrictEqual(roles); 187 | }); 188 | 189 | it('should return empty array and log error when there is no token', () => { 190 | const errorSpy = jest.spyOn(console, 'error'); 191 | getSessionTokenSpy.mockReturnValueOnce(null); 192 | const result = service.getJwtRoles(); 193 | expect(errorSpy).toHaveBeenCalledWith( 194 | 'Could not get JWT Roles - not authenticated' 195 | ); 196 | expect(getJwtRolesSpy).not.toHaveBeenCalled(); 197 | expect(result).toStrictEqual([]); 198 | }); 199 | }); 200 | 201 | describe('refreshSession', () => { 202 | it('correctly handle descopeSession stream when session is successfully refreshed', (done: jest.DoneCallback) => { 203 | refreshSpy.mockReturnValueOnce( 204 | of({ ok: true, data: { sessionJwt: 'newToken' } }) 205 | ); 206 | // Taking 4 values from stream: first is initial value, next 3 are the result of refreshSession 207 | service.session$.pipe(take(4), toArray()).subscribe({ 208 | next: (result) => { 209 | expect(result.slice(1)).toStrictEqual([ 210 | { 211 | isAuthenticated: false, 212 | isSessionLoading: true, 213 | sessionToken: undefined 214 | }, 215 | { 216 | isAuthenticated: true, 217 | isSessionLoading: true, 218 | sessionToken: 'newToken' 219 | }, 220 | { 221 | isAuthenticated: true, 222 | isSessionLoading: false, 223 | sessionToken: 'newToken' 224 | } 225 | ]); 226 | expect(service.isAuthenticated()).toBeTruthy(); 227 | done(); 228 | }, 229 | error: (err) => { 230 | done.fail(err); 231 | } 232 | }); 233 | service.refreshSession().subscribe(); 234 | }); 235 | 236 | it('correctly handle descopeSession stream when refresh session failed', (done: jest.DoneCallback) => { 237 | refreshSpy.mockReturnValueOnce( 238 | of({ ok: false, data: { sessionJwt: 'newToken' } }) 239 | ); 240 | // Taking 4 values from stream: first is initial value, next 3 are the result of refreshSession 241 | service.session$.pipe(take(4), toArray()).subscribe({ 242 | next: (result) => { 243 | expect(result.slice(1)).toStrictEqual([ 244 | { 245 | isAuthenticated: false, 246 | isSessionLoading: true, 247 | sessionToken: undefined 248 | }, 249 | { 250 | isAuthenticated: false, 251 | isSessionLoading: true, 252 | sessionToken: '' 253 | }, 254 | { 255 | isAuthenticated: false, 256 | isSessionLoading: false, 257 | sessionToken: '' 258 | } 259 | ]); 260 | expect(service.isAuthenticated()).toBeFalsy(); 261 | done(); 262 | }, 263 | error: (err) => { 264 | done.fail(err); 265 | } 266 | }); 267 | service.refreshSession().subscribe(); 268 | }); 269 | }); 270 | 271 | describe('refreshUser', () => { 272 | it('correctly handle descopeUser stream when user is successfully refreshed', (done: jest.DoneCallback) => { 273 | meSpy.mockReturnValueOnce(of({ ok: true, data: { name: 'test' } })); 274 | // Taking 4 values from stream: first is initial value, next 3 are the result of refreshUser 275 | service.user$.pipe(take(4), toArray()).subscribe({ 276 | next: (result) => { 277 | expect(result.slice(1)).toStrictEqual([ 278 | { isUserLoading: true, user: undefined }, 279 | { isUserLoading: true, user: { name: 'test' } }, 280 | { isUserLoading: false, user: { name: 'test' } } 281 | ]); 282 | done(); 283 | }, 284 | error: (err) => { 285 | done.fail(err); 286 | } 287 | }); 288 | service.refreshUser().subscribe(); 289 | }); 290 | 291 | it('correctly handle descopeUser stream when refresh session failed', (done: jest.DoneCallback) => { 292 | meSpy.mockReturnValueOnce(of({ ok: false })); 293 | // Taking 3 values from stream: first is initial value, next 2 are the result of refreshUser 294 | service.user$.pipe(take(3), toArray()).subscribe({ 295 | next: (result) => { 296 | expect(result.slice(1)).toStrictEqual([ 297 | { isUserLoading: true, user: undefined }, 298 | { isUserLoading: false, user: undefined } 299 | ]); 300 | done(); 301 | }, 302 | error: (err) => { 303 | done.fail(err); 304 | } 305 | }); 306 | service.refreshUser().subscribe(); 307 | }); 308 | }); 309 | }); 310 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/services/descope-auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import type { UserResponse } from '@descope/web-js-sdk'; 3 | import createSdk from '@descope/web-js-sdk'; 4 | import { BehaviorSubject, finalize, Observable, tap } from 'rxjs'; 5 | import { observabilify, Observablefied } from '../utils/helpers'; 6 | import { baseHeaders, isBrowser } from '../utils/constants'; 7 | import { DescopeAuthConfig } from '../types/types'; 8 | 9 | type DescopeSDK = ReturnType; 10 | type AngularDescopeSDK = Observablefied; 11 | 12 | export interface DescopeSession { 13 | isAuthenticated: boolean; 14 | isSessionLoading: boolean; 15 | sessionToken: string | null; 16 | } 17 | 18 | export type DescopeUser = { user?: UserResponse; isUserLoading: boolean }; 19 | 20 | @Injectable({ 21 | providedIn: 'root' 22 | }) 23 | export class DescopeAuthService { 24 | public descopeSdk: AngularDescopeSDK; 25 | private readonly sessionSubject: BehaviorSubject; 26 | private readonly userSubject: BehaviorSubject; 27 | readonly session$: Observable; 28 | readonly user$: Observable; 29 | 30 | constructor(config: DescopeAuthConfig) { 31 | this.descopeSdk = observabilify( 32 | createSdk({ 33 | persistTokens: isBrowser() as true, 34 | ...config, 35 | storeLastAuthenticatedUser: isBrowser() as true, 36 | autoRefresh: isBrowser() as true, 37 | baseHeaders 38 | }) 39 | ); 40 | 41 | this.sessionSubject = new BehaviorSubject({ 42 | isAuthenticated: false, 43 | isSessionLoading: false, 44 | sessionToken: '' 45 | }); 46 | this.session$ = this.sessionSubject.asObservable(); 47 | this.userSubject = new BehaviorSubject({ 48 | isUserLoading: false 49 | }); 50 | this.user$ = this.userSubject.asObservable(); 51 | this.descopeSdk.onSessionTokenChange(this.setSession.bind(this)); 52 | this.descopeSdk.onUserChange(this.setUser.bind(this)); 53 | } 54 | 55 | refreshSession() { 56 | const beforeRefreshSession = this.sessionSubject.value; 57 | this.sessionSubject.next({ 58 | ...beforeRefreshSession, 59 | isSessionLoading: true 60 | }); 61 | return this.descopeSdk.refresh().pipe( 62 | tap((data) => { 63 | const afterRequestSession = this.sessionSubject.value; 64 | if (data.ok && data.data) { 65 | this.sessionSubject.next({ 66 | ...afterRequestSession, 67 | sessionToken: data.data.sessionJwt, 68 | isAuthenticated: !!data.data.sessionJwt 69 | }); 70 | } else { 71 | this.sessionSubject.next({ 72 | ...afterRequestSession, 73 | sessionToken: '', 74 | isAuthenticated: false 75 | }); 76 | } 77 | }), 78 | finalize(() => { 79 | const afterRefreshSession = this.sessionSubject.value; 80 | this.sessionSubject.next({ 81 | ...afterRefreshSession, 82 | isSessionLoading: false 83 | }); 84 | }) 85 | ); 86 | } 87 | 88 | refreshUser() { 89 | const beforeRefreshUser = this.userSubject.value; 90 | this.userSubject.next({ 91 | ...beforeRefreshUser, 92 | isUserLoading: true 93 | }); 94 | return this.descopeSdk.me().pipe( 95 | tap((data) => { 96 | const afterRequestUser = this.userSubject.value; 97 | if (data.data) { 98 | this.userSubject.next({ 99 | ...afterRequestUser, 100 | user: { 101 | ...data.data 102 | } 103 | }); 104 | } 105 | }), 106 | finalize(() => { 107 | const afterRefreshUser = this.userSubject.value; 108 | this.userSubject.next({ 109 | ...afterRefreshUser, 110 | isUserLoading: false 111 | }); 112 | }) 113 | ); 114 | } 115 | 116 | getSessionToken() { 117 | if (isBrowser()) { 118 | return ( 119 | this.descopeSdk as AngularDescopeSDK & { 120 | getSessionToken: () => string | null; 121 | } 122 | ).getSessionToken(); 123 | } 124 | console.warn('Get session token is not supported in SSR'); 125 | return ''; 126 | } 127 | 128 | getRefreshToken() { 129 | if (isBrowser()) { 130 | return ( 131 | this.descopeSdk as AngularDescopeSDK & { 132 | getRefreshToken: () => string | null; 133 | } 134 | ).getRefreshToken(); 135 | } 136 | this.descopeSdk.getJwtPermissions; 137 | console.warn('Get refresh token is not supported in SSR'); 138 | return ''; 139 | } 140 | 141 | isSessionTokenExpired(token = this.getSessionToken()) { 142 | if (isBrowser()) { 143 | return this.descopeSdk.isJwtExpired(token ?? ''); 144 | } 145 | console.warn('isSessionTokenExpired is not supported in SSR'); 146 | return true; 147 | } 148 | 149 | isRefreshTokenExpired(token = this.getRefreshToken()) { 150 | if (isBrowser()) { 151 | return ( 152 | this.descopeSdk as AngularDescopeSDK & { 153 | isJwtExpired: (token: string) => boolean | null; 154 | } 155 | ).isJwtExpired(token ?? ''); 156 | } 157 | console.warn('isRefreshTokenExpired is not supported in SSR'); 158 | return true; 159 | } 160 | 161 | getJwtPermissions(token = this.getSessionToken(), tenant?: string) { 162 | if (token === null) { 163 | console.error('Could not get JWT Permissions - not authenticated'); 164 | return []; 165 | } 166 | return this.descopeSdk.getJwtPermissions(token, tenant); 167 | } 168 | 169 | getJwtRoles(token = this.getSessionToken(), tenant?: string) { 170 | if (token === null) { 171 | console.error('Could not get JWT Roles - not authenticated'); 172 | return []; 173 | } 174 | return this.descopeSdk.getJwtRoles(token, tenant); 175 | } 176 | 177 | isAuthenticated() { 178 | return this.sessionSubject.value.isAuthenticated; 179 | } 180 | 181 | private setSession(sessionToken: string | null) { 182 | const currentSession = this.sessionSubject.value; 183 | this.sessionSubject.next({ 184 | sessionToken, 185 | isAuthenticated: !!sessionToken, 186 | isSessionLoading: currentSession.isSessionLoading 187 | }); 188 | } 189 | 190 | private setUser(user: UserResponse) { 191 | const currentUser = this.userSubject.value; 192 | this.userSubject.next({ 193 | isUserLoading: currentUser.isUserLoading, 194 | user 195 | }); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/services/descope.interceptor.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { 3 | HttpTestingController, 4 | provideHttpClientTesting 5 | } from '@angular/common/http/testing'; 6 | import { 7 | HttpClient, 8 | provideHttpClient, 9 | withInterceptors 10 | } from '@angular/common/http'; 11 | import { of } from 'rxjs'; 12 | import { DescopeAuthService } from './descope-auth.service'; 13 | import { DescopeAuthConfig } from '../types/types'; 14 | import createSdk from '@descope/web-js-sdk'; 15 | import { descopeInterceptor } from './descope.interceptor'; 16 | import mocked = jest.mocked; 17 | 18 | jest.mock('@descope/web-js-sdk'); 19 | 20 | describe('DescopeInterceptor', () => { 21 | let authService: DescopeAuthService; 22 | let httpTestingController: HttpTestingController; 23 | let httpClient: HttpClient; 24 | let mockedCreateSdk: jest.Mock; 25 | 26 | beforeEach(() => { 27 | mockedCreateSdk = mocked(createSdk); 28 | mockedCreateSdk.mockReturnValue({ 29 | onSessionTokenChange: jest.fn(), 30 | onUserChange: jest.fn() 31 | }); 32 | 33 | TestBed.configureTestingModule({ 34 | providers: [ 35 | DescopeAuthService, 36 | { 37 | provide: DescopeAuthConfig, 38 | useValue: { pathsToIntercept: ['/api'], projectId: 'test' } 39 | }, 40 | provideHttpClient(withInterceptors([descopeInterceptor])), 41 | provideHttpClientTesting() 42 | ] 43 | }); 44 | 45 | authService = TestBed.inject(DescopeAuthService); 46 | httpTestingController = TestBed.inject(HttpTestingController); 47 | httpClient = TestBed.inject(HttpClient); 48 | }); 49 | 50 | afterEach(() => { 51 | httpTestingController.verify(); 52 | }); 53 | 54 | it('should intercept requests for specified paths', () => { 55 | jest.spyOn(authService, 'getSessionToken').mockReturnValue('fakeToken'); 56 | 57 | httpClient.get('/api/data').subscribe(); 58 | httpClient.get('/other').subscribe(); 59 | 60 | const req1 = httpTestingController.expectOne('/api/data'); 61 | const req2 = httpTestingController.expectOne('/other'); 62 | 63 | expect(req1.request.headers.get('Authorization')).toEqual( 64 | 'Bearer fakeToken' 65 | ); 66 | expect(req2.request.headers.get('Authorization')).toEqual(null); 67 | req1.flush({}); 68 | req2.flush({}); 69 | }); 70 | 71 | it('should refresh token and retry request on 401 or 403 error', () => { 72 | jest.spyOn(authService, 'getSessionToken').mockReturnValue(null); 73 | const refreshSessionSpy = jest 74 | .spyOn(authService, 'refreshSession') 75 | .mockReturnValue(of({ ok: true, data: { sessionJwt: 'newToken' } })); 76 | 77 | httpClient.get('/api/data').subscribe(); 78 | 79 | const req = httpTestingController.expectOne('/api/data'); 80 | 81 | expect(req.request.headers.get('Authorization')).toEqual('Bearer newToken'); 82 | expect(refreshSessionSpy).toHaveBeenCalled(); 83 | req.flush({}, { status: 401, statusText: 'Not authorized' }); 84 | }); 85 | 86 | it('should throw an error if refreshing the session fails', () => { 87 | jest.spyOn(authService, 'getSessionToken').mockReturnValue(null); 88 | jest 89 | .spyOn(authService, 'refreshSession') 90 | .mockReturnValue(of({ ok: false, data: undefined })); 91 | 92 | httpClient.get('/api/data').subscribe({ 93 | next: () => {}, 94 | error: (error) => { 95 | expect(error.message).toEqual('Could not refresh session!'); 96 | }, 97 | complete: () => {} 98 | }); 99 | 100 | httpTestingController.expectNone('/api/data'); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/services/descope.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { inject } from '@angular/core'; 2 | import { 3 | HttpErrorResponse, 4 | HttpHandlerFn, 5 | HttpInterceptorFn, 6 | HttpRequest 7 | } from '@angular/common/http'; 8 | import { throwError } from 'rxjs'; 9 | import { catchError, switchMap } from 'rxjs/operators'; 10 | import { DescopeAuthService } from './descope-auth.service'; 11 | import { DescopeAuthConfig } from '../types/types'; 12 | 13 | export const descopeInterceptor: HttpInterceptorFn = (request, next) => { 14 | const config = inject(DescopeAuthConfig); 15 | const authService = inject(DescopeAuthService); 16 | 17 | function refreshAndRetry( 18 | request: HttpRequest, 19 | next: HttpHandlerFn, 20 | error?: HttpErrorResponse 21 | ) { 22 | return authService.refreshSession().pipe( 23 | switchMap((refreshed) => { 24 | if (refreshed.ok && refreshed.data) { 25 | const requestWithRefreshedToken = addTokenToRequest( 26 | request, 27 | refreshed.data?.sessionJwt 28 | ); 29 | return next(requestWithRefreshedToken); 30 | } else { 31 | return throwError( 32 | () => error ?? new Error('Could not refresh session!') 33 | ); 34 | } 35 | }) 36 | ); 37 | } 38 | 39 | function shouldIntercept(request: HttpRequest): boolean { 40 | return ( 41 | (config.pathsToIntercept?.length === 0 || 42 | config.pathsToIntercept?.some((path) => request.url.includes(path))) ?? 43 | true 44 | ); 45 | } 46 | 47 | function addTokenToRequest( 48 | request: HttpRequest, 49 | token: string 50 | ): HttpRequest { 51 | return request.clone({ 52 | setHeaders: { 53 | Authorization: `Bearer ${token}` 54 | } 55 | }); 56 | } 57 | 58 | if (shouldIntercept(request)) { 59 | const token = authService.getSessionToken(); 60 | if (!token) { 61 | return refreshAndRetry(request, next); 62 | } 63 | const requestWithToken = addTokenToRequest(request, token); 64 | return next(requestWithToken).pipe( 65 | catchError((error: HttpErrorResponse) => { 66 | if (error.status === 401 || error.status === 403) { 67 | return refreshAndRetry(request, next, error); 68 | } else { 69 | return throwError(() => error); 70 | } 71 | }) 72 | ); 73 | } else { 74 | return next(request); 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/types/types.ts: -------------------------------------------------------------------------------- 1 | import { ILogger } from '@descope/web-component'; 2 | 3 | export class DescopeAuthConfig { 4 | projectId = ''; 5 | baseUrl?: string; 6 | baseStaticUrl?: string; 7 | // If true, tokens will be stored on local storage 8 | persistTokens?: boolean; 9 | sessionTokenViaCookie?: boolean; 10 | // If true, last authenticated user will be stored on local storage and can accessed with getUser function 11 | storeLastAuthenticatedUser?: boolean; 12 | pathsToIntercept?: string[]; 13 | } 14 | 15 | export type { ILogger }; 16 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/utils/constants.ts: -------------------------------------------------------------------------------- 1 | import { environment } from '../../environment'; 2 | 3 | export const baseHeaders = { 4 | 'x-descope-sdk-name': 'angular', 5 | 'x-descope-sdk-version': environment.buildVersion 6 | }; 7 | 8 | export const isBrowser = () => typeof window !== 'undefined'; 9 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/utils/helpers.spec.ts: -------------------------------------------------------------------------------- 1 | import { observabilify, Observablefied } from './helpers'; 2 | import { lastValueFrom, Observable } from 'rxjs'; 3 | 4 | describe('Helpers', () => { 5 | describe('Observabilify', () => { 6 | it('should not affect simple object', () => { 7 | //GIVEN 8 | const obj = { 9 | field1: 'string', 10 | field2: 123, 11 | nested: { 12 | field1: 'string', 13 | field2: 123 14 | } 15 | }; 16 | type TestType = typeof obj; 17 | 18 | //WHEN 19 | const result: Observablefied = observabilify(obj); 20 | 21 | //THEN 22 | expect(result).toStrictEqual(obj); 23 | }); 24 | 25 | it('should not affect simple object with non async functions', () => { 26 | //GIVEN 27 | const obj = { 28 | field1: 'string', 29 | field2: 123, 30 | fn: (arg1: string, arg2: number): string => { 31 | return arg1 + arg2.toString(); 32 | }, 33 | nested: { 34 | fn2: (arg: string): string => { 35 | return arg; 36 | }, 37 | field1: 'string', 38 | field2: 123 39 | } 40 | }; 41 | type TestType = typeof obj; 42 | const expected1 = obj.fn('Test', 1); 43 | const expected2 = obj.nested.fn2('Test'); 44 | 45 | //WHEN 46 | const transformed: Observablefied = 47 | observabilify(obj); 48 | const actual1 = transformed.fn('Test', 1); 49 | const actual2 = transformed.nested.fn2('Test'); 50 | 51 | //THEN 52 | expect(expected1).toStrictEqual(actual1); 53 | expect(expected2).toStrictEqual(actual2); 54 | }); 55 | 56 | it('should transform async functions', async () => { 57 | //GIVEN 58 | const obj = { 59 | field1: 'string', 60 | field2: 123, 61 | fn: (arg1: string, arg2: number): string => { 62 | return arg1 + arg2.toString(); 63 | }, 64 | asyncFn: (arg1: string, arg2: number): Promise => { 65 | return Promise.resolve(arg1 + arg2.toString()); 66 | }, 67 | nested: { 68 | fn2: (arg: string) => { 69 | return arg; 70 | }, 71 | asyncFn: (arg: string): Promise => { 72 | return Promise.resolve(arg); 73 | }, 74 | field1: 'string', 75 | field2: 123 76 | } 77 | }; 78 | type TestType = typeof obj; 79 | const expected1 = obj.fn('Test', 1); 80 | const expected2 = obj.nested.fn2('Test'); 81 | const expected3 = await obj.asyncFn('Test', 1); 82 | const expected4 = await obj.nested.asyncFn('Test'); 83 | 84 | //WHEN 85 | const transformed: Observablefied = 86 | observabilify(obj); 87 | const actual1 = transformed.fn('Test', 1); 88 | const actual2 = transformed.nested.fn2('Test'); 89 | const actual3Async = transformed.asyncFn('Test', 1); 90 | const actual3 = await lastValueFrom(actual3Async); 91 | const actual4Async = transformed.nested.asyncFn('Test'); 92 | const actual4 = await lastValueFrom(actual4Async); 93 | 94 | //THEN 95 | expect(expected1).toStrictEqual(actual1); 96 | expect(expected2).toStrictEqual(actual2); 97 | expect(actual3Async).toBeInstanceOf(Observable); 98 | expect(actual4Async).toBeInstanceOf(Observable); 99 | expect(expected3).toStrictEqual(actual3); 100 | expect(expected4).toStrictEqual(actual4); 101 | }); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/lib/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | import { from, Observable } from 'rxjs'; 2 | 3 | export type Observablefied = { 4 | [K in keyof T]: T[K] extends (...args: infer Args) => Promise 5 | ? (...args: Args) => Observable 6 | : T[K] extends (...args: infer Args) => infer R 7 | ? (...args: Args) => R 8 | : T[K] extends object 9 | ? Observablefied 10 | : T[K]; 11 | }; 12 | 13 | export function observabilify(value: T): Observablefied { 14 | /* eslint-disable @typescript-eslint/no-explicit-any */ 15 | const observableValue: any = {}; 16 | 17 | for (const key in value) { 18 | if (typeof value[key] === 'function') { 19 | const fn = value[key] as (...args: unknown[]) => unknown; 20 | observableValue[key] = (...args: unknown[]) => { 21 | const fnResult = fn(...args); 22 | if (fnResult instanceof Promise) { 23 | return from(fnResult); 24 | } else { 25 | return fnResult; 26 | } 27 | }; 28 | } else if (typeof value[key] === 'object' && value[key] !== null) { 29 | observableValue[key] = observabilify(value[key]); 30 | } else { 31 | observableValue[key] = value[key]; 32 | } 33 | } 34 | 35 | return observableValue as Observablefied; 36 | } 37 | -------------------------------------------------------------------------------- /projects/angular-sdk/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of angular-sdk 3 | */ 4 | 5 | export * from './lib/services/descope-auth.service'; 6 | export * from './lib/services/descope-auth.guard'; 7 | export * from './lib/services/descope.interceptor'; 8 | export * from './lib/descope-auth.module'; 9 | export * from './lib/components/descope/descope.component'; 10 | export * from './lib/components/sign-in-flow/sign-in-flow.component'; 11 | export * from './lib/components/sign-up-flow/sign-up-flow.component'; 12 | export * from './lib/components/sign-up-or-in-flow/sign-up-or-in-flow.component'; 13 | export * from './lib/components/user-management/user-management.component'; 14 | export * from './lib/components/role-management/role-management.component'; 15 | export * from './lib/components/access-key-management/access-key-management.component'; 16 | export * from './lib/components/audit-management/audit-management.component'; 17 | export * from './lib/components/user-profile/user-profile.component'; 18 | export * from './lib/types/types'; 19 | -------------------------------------------------------------------------------- /projects/angular-sdk/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/lib", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [] 10 | }, 11 | "exclude": ["**/*.spec.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /projects/angular-sdk/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /projects/angular-sdk/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/spec", 6 | "types": ["jest"], 7 | "esModuleInterop": true, 8 | "emitDecoratorMetadata": true 9 | }, 10 | "include": ["**/*.spec.ts", "**/*.d.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /projects/demo-app/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "rules": { 8 | "@angular-eslint/directive-selector": [ 9 | "error", 10 | { 11 | "type": "attribute", 12 | "prefix": "app", 13 | "style": "camelCase" 14 | } 15 | ], 16 | "@angular-eslint/component-selector": [ 17 | "error", 18 | { 19 | "type": "element", 20 | "prefix": "app", 21 | "style": "kebab-case" 22 | } 23 | ] 24 | } 25 | }, 26 | { 27 | "files": ["*.html"], 28 | "rules": {} 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { HomeComponent } from './home/home.component'; 4 | import { ProtectedComponent } from './protected/protected.component'; 5 | import { descopeAuthGuard } from '../../../angular-sdk/src/lib/services/descope-auth.guard'; 6 | import { LoginComponent } from './login/login.component'; 7 | import { ManageUsersComponent } from './manage-users/manage-users.component'; 8 | import { ManageRolesComponent } from './manage-roles/manage-roles.component'; 9 | import { ManageAccessKeysComponent } from './manage-access-keys/manage-access-keys.component'; 10 | import { ManageAuditComponent } from './manage-audit/manage-audit.component'; 11 | import { MyUserProfileComponent } from './my-user-profile/my-user-profile.component'; 12 | 13 | const routes: Routes = [ 14 | { 15 | path: 'step-up', 16 | component: ProtectedComponent, 17 | canActivate: [descopeAuthGuard], 18 | data: { descopeFallbackUrl: '/' } 19 | }, 20 | { path: 'login', component: LoginComponent }, 21 | { path: 'manage-users', component: ManageUsersComponent }, 22 | { path: 'manage-roles', component: ManageRolesComponent }, 23 | { path: 'manage-access-keys', component: ManageAccessKeysComponent }, 24 | { path: 'manage-audit', component: ManageAuditComponent }, 25 | { path: 'my-user-profile', component: MyUserProfileComponent }, 26 | { path: '**', component: HomeComponent } 27 | ]; 28 | 29 | @NgModule({ 30 | imports: [RouterModule.forRoot(routes, { enableTracing: false })], 31 | exports: [RouterModule] 32 | }) 33 | export class AppRoutingModule {} 34 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | height: 100vh; 3 | position: relative; 4 | } 5 | main { 6 | border-radius: 10px; 7 | margin: auto; 8 | border: 1px solid lightgray; 9 | padding: 20px; 10 | max-width: 500px; 11 | box-shadow: 13px 13px 20px #cbced1, -13px -13px 20px #fff; 12 | background: #ecf0f3; 13 | position: relative; 14 | top: 50%; 15 | transform: translateY(-50%); 16 | } 17 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | import createSdk from '@descope/web-js-sdk'; 5 | import mocked = jest.mocked; 6 | import { DescopeAuthConfig } from '../../../angular-sdk/src/lib/types/types'; 7 | 8 | jest.mock('@descope/web-js-sdk'); 9 | 10 | describe('AppComponent', () => { 11 | let mockedCreateSdk: jest.Mock; 12 | const onSessionTokenChangeSpy = jest.fn(); 13 | const onUserChangeSpy = jest.fn(); 14 | 15 | beforeEach(() => { 16 | mockedCreateSdk = mocked(createSdk); 17 | mockedCreateSdk.mockReturnValue({ 18 | onSessionTokenChange: onSessionTokenChangeSpy, 19 | onUserChange: onUserChangeSpy 20 | }); 21 | 22 | TestBed.configureTestingModule({ 23 | imports: [RouterTestingModule], 24 | providers: [ 25 | DescopeAuthConfig, 26 | { provide: DescopeAuthConfig, useValue: { projectId: 'test' } } 27 | ], 28 | declarations: [AppComponent] 29 | }); 30 | }); 31 | 32 | it('should create the app', () => { 33 | const fixture = TestBed.createComponent(AppComponent); 34 | const app = fixture.componentInstance; 35 | expect(app).toBeTruthy(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss', './my-user-profile/my-user-profile.scss'] 7 | }) 8 | export class AppComponent {} 9 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { APP_INITIALIZER, NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | 4 | import { AppRoutingModule } from './app-routing.module'; 5 | import { AppComponent } from './app.component'; 6 | import { DescopeAuthModule } from '../../../angular-sdk/src/lib/descope-auth.module'; 7 | import { HomeComponent } from './home/home.component'; 8 | import { ProtectedComponent } from './protected/protected.component'; 9 | import { environment } from '../environments/environment'; 10 | import { DescopeAuthService } from 'projects/angular-sdk/src/public-api'; 11 | import { zip } from 'rxjs'; 12 | import { LoginComponent } from './login/login.component'; 13 | import { ManageUsersComponent } from './manage-users/manage-users.component'; 14 | import { ManageRolesComponent } from './manage-roles/manage-roles.component'; 15 | import { ManageAccessKeysComponent } from './manage-access-keys/manage-access-keys.component'; 16 | import { ManageAuditComponent } from './manage-audit/manage-audit.component'; 17 | import { MyUserProfileComponent } from './my-user-profile/my-user-profile.component'; 18 | import { 19 | HttpClientModule, 20 | provideHttpClient, 21 | withInterceptors 22 | } from '@angular/common/http'; 23 | import { descopeInterceptor } from '../../../angular-sdk/src/lib/services/descope.interceptor'; 24 | 25 | export function initializeApp(authService: DescopeAuthService) { 26 | return () => zip([authService.refreshSession(), authService.refreshUser()]); 27 | } 28 | 29 | @NgModule({ 30 | declarations: [ 31 | AppComponent, 32 | HomeComponent, 33 | ProtectedComponent, 34 | LoginComponent, 35 | ManageUsersComponent, 36 | ManageRolesComponent, 37 | ManageAccessKeysComponent, 38 | ManageAuditComponent, 39 | MyUserProfileComponent 40 | ], 41 | imports: [ 42 | BrowserModule, 43 | AppRoutingModule, 44 | HttpClientModule, 45 | DescopeAuthModule.forRoot({ 46 | projectId: environment.descopeProjectId, 47 | baseUrl: environment.descopeBaseUrl || '', 48 | baseStaticUrl: environment.descopeBaseStaticUrl || '', 49 | sessionTokenViaCookie: true 50 | }) 51 | ], 52 | providers: [ 53 | { 54 | provide: APP_INITIALIZER, 55 | useFactory: initializeApp, 56 | deps: [DescopeAuthService], 57 | multi: true 58 | }, 59 | provideHttpClient(withInterceptors([descopeInterceptor])) 60 | ], 61 | bootstrap: [AppComponent] 62 | }) 63 | export class AppModule {} 64 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 |

ANGULAR SDK DEMO APP

2 | 3 | 4 |

Hello {{ userName }}

5 |
6 | Roles: 7 | 8 | {{ roles }} 9 | 10 | N/A 11 |
12 | 19 | 22 | 23 | 24 | 27 | 28 | 31 | 32 |
33 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/home/home.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | flex-direction: column; 6 | } 7 | .action-button { 8 | border: 1px solid #00ace1; 9 | background-color: white; 10 | color: #00ace1; 11 | margin-bottom: 1rem; 12 | &:first-of-type { 13 | margin-top: 1rem; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | import createSdk from '@descope/web-js-sdk'; 5 | import mocked = jest.mocked; 6 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 7 | import { DescopeAuthConfig } from '../../../../angular-sdk/src/lib/types/types'; 8 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 9 | 10 | jest.mock('@descope/web-js-sdk'); 11 | 12 | describe('HomeComponent', () => { 13 | let component: HomeComponent; 14 | let fixture: ComponentFixture; 15 | 16 | let mockedCreateSdk: jest.Mock; 17 | const onSessionTokenChangeSpy = jest.fn(); 18 | const onUserChangeSpy = jest.fn(); 19 | 20 | beforeEach(() => { 21 | mockedCreateSdk = mocked(createSdk); 22 | mockedCreateSdk.mockReturnValue({ 23 | onSessionTokenChange: onSessionTokenChangeSpy, 24 | onUserChange: onUserChangeSpy 25 | }); 26 | 27 | TestBed.configureTestingModule({ 28 | imports: [HttpClientTestingModule], 29 | schemas: [NO_ERRORS_SCHEMA], 30 | declarations: [HomeComponent], 31 | providers: [ 32 | DescopeAuthConfig, 33 | { provide: DescopeAuthConfig, useValue: { projectId: 'test' } } 34 | ] 35 | }); 36 | fixture = TestBed.createComponent(HomeComponent); 37 | component = fixture.componentInstance; 38 | fixture.detectChanges(); 39 | }); 40 | 41 | it('should create', () => { 42 | expect(component).toBeTruthy(); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { DescopeAuthService } from '../../../../angular-sdk/src/lib/services/descope-auth.service'; 3 | import { Router } from '@angular/router'; 4 | import { environment } from '../../environments/environment'; 5 | import { HttpClient } from '@angular/common/http'; 6 | 7 | @Component({ 8 | selector: 'app-home', 9 | templateUrl: './home.component.html', 10 | styleUrls: ['./home.component.scss'] 11 | }) 12 | export class HomeComponent implements OnInit { 13 | projectId: string = environment.descopeProjectId; 14 | isAuthenticated: boolean = false; 15 | roles: string[] = []; 16 | userName: string = ''; 17 | stepUpConfigured = (environment.descopeStepUpFlowId ?? '').length > 0; 18 | backendUrl = environment.backendUrl ?? ''; 19 | 20 | constructor( 21 | private router: Router, 22 | private httpClient: HttpClient, 23 | private authService: DescopeAuthService 24 | ) {} 25 | 26 | ngOnInit() { 27 | this.authService.session$.subscribe((session) => { 28 | this.isAuthenticated = session.isAuthenticated; 29 | if (session.sessionToken) { 30 | this.roles = this.authService.getJwtRoles(session.sessionToken); 31 | } 32 | }); 33 | this.authService.user$.subscribe((descopeUser) => { 34 | if (descopeUser.user) { 35 | this.userName = descopeUser.user.name ?? ''; 36 | } 37 | }); 38 | } 39 | 40 | login() { 41 | this.router.navigate(['/login']).catch((err) => console.error(err)); 42 | } 43 | 44 | logout() { 45 | this.authService.descopeSdk.logout(); 46 | } 47 | 48 | fetchData() { 49 | if (this.backendUrl) { 50 | this.httpClient 51 | .get(this.backendUrl, { responseType: 'text' }) 52 | .subscribe((data) => alert(data)); 53 | } else { 54 | console.warn('Please setup backendUrl in your environment'); 55 | } 56 | } 57 | 58 | stepUp() { 59 | this.router.navigate(['/step-up']).catch((err) => console.error(err)); 60 | } 61 | 62 | manageUsers() { 63 | this.router.navigate(['/manage-users']).catch((err) => console.error(err)); 64 | } 65 | 66 | manageRoles() { 67 | this.router.navigate(['/manage-roles']).catch((err) => console.error(err)); 68 | } 69 | 70 | manageAccessKeys() { 71 | this.router 72 | .navigate(['/manage-access-keys']) 73 | .catch((err) => console.error(err)); 74 | } 75 | 76 | manageAudit() { 77 | this.router.navigate(['/manage-audit']).catch((err) => console.error(err)); 78 | } 79 | 80 | myUserProfile() { 81 | this.router 82 | .navigate(['/my-user-profile']) 83 | .catch((err) => console.error(err)); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/interceptor/auth.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HttpHandlerFn, 3 | HttpInterceptorFn, 4 | HttpRequest 5 | } from '@angular/common/http'; 6 | import { inject } from '@angular/core'; 7 | import { DescopeAuthService } from '../../../../angular-sdk/src/lib/services/descope-auth.service'; 8 | 9 | export const authenticationInterceptor: HttpInterceptorFn = ( 10 | req: HttpRequest, 11 | next: HttpHandlerFn 12 | ) => { 13 | const authService = inject(DescopeAuthService); 14 | const sessionToken = authService.getSessionToken(); 15 | const modifiedReq = req.clone({ 16 | headers: req.headers.set('Authorization', `Bearer ${sessionToken}`) 17 | }); 18 | 19 | return next(modifiedReq); 20 | }; 21 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/login/login.component.html: -------------------------------------------------------------------------------- 1 | 14 |
19 | Loading... 20 |
21 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/login/login.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LoginComponent } from './login.component'; 4 | import createSdk from '@descope/web-js-sdk'; 5 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 6 | import { DescopeAuthConfig } from '../../../../angular-sdk/src/lib/types/types'; 7 | import mocked = jest.mocked; 8 | 9 | jest.mock('@descope/web-js-sdk'); 10 | 11 | describe('LoginComponent', () => { 12 | let component: LoginComponent; 13 | let fixture: ComponentFixture; 14 | 15 | let mockedCreateSdk: jest.Mock; 16 | const onSessionTokenChangeSpy = jest.fn(); 17 | const onUserChangeSpy = jest.fn(); 18 | 19 | beforeEach(() => { 20 | mockedCreateSdk = mocked(createSdk); 21 | mockedCreateSdk.mockReturnValue({ 22 | onSessionTokenChange: onSessionTokenChangeSpy, 23 | onUserChange: onUserChangeSpy 24 | }); 25 | 26 | TestBed.configureTestingModule({ 27 | schemas: [NO_ERRORS_SCHEMA], 28 | declarations: [LoginComponent], 29 | providers: [ 30 | DescopeAuthConfig, 31 | { provide: DescopeAuthConfig, useValue: { projectId: 'test' } } 32 | ] 33 | }); 34 | fixture = TestBed.createComponent(LoginComponent); 35 | component = fixture.componentInstance; 36 | fixture.detectChanges(); 37 | }); 38 | 39 | it('should create', () => { 40 | expect(component).toBeTruthy(); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { environment } from '../../environments/environment'; 3 | import { Router } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-login', 7 | templateUrl: './login.component.html' 8 | }) 9 | export class LoginComponent { 10 | flowId = environment.descopeFlowId ?? 'sign-up-or-in'; 11 | theme = (environment.descopeTheme as 'light' | 'dark' | 'os') ?? 'os'; 12 | telemetryKey = environment.descopeTelemetryKey ?? ''; 13 | debugMode = environment.descopeDebugMode ?? false; 14 | tenantId = environment.descopeTenantId ?? ''; 15 | locale = environment.descopeLocale ?? ''; 16 | redirectUrl = environment.descopeRedirectUrl ?? ''; 17 | 18 | isLoading = true; 19 | 20 | constructor(private router: Router) {} 21 | 22 | errorTransformer = (error: { text: string; type: string }): string => { 23 | const translationMap: { [key: string]: string } = { 24 | SAMLStartFailed: 'Failed to start SAML flow' 25 | }; 26 | return translationMap[error.type] || error.text; 27 | }; 28 | 29 | onSuccess(e: CustomEvent) { 30 | console.log('SUCCESSFULLY LOGGED IN FROM WEB COMPONENT', e.detail); 31 | this.router.navigate(['/']).catch((err) => console.error(err)); 32 | } 33 | 34 | onError(e: CustomEvent) { 35 | console.log('ERROR FROM LOG IN FLOW FROM WEB COMPONENT', e); 36 | } 37 | 38 | onReady() { 39 | this.isLoading = false; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/manage-access-keys/manage-access-keys.component.html: -------------------------------------------------------------------------------- 1 | 7 | 13 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/manage-access-keys/manage-access-keys.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { ManageAccessKeysComponent } from './manage-access-keys.component'; 3 | import createSdk from '@descope/web-js-sdk'; 4 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 5 | import { DescopeAuthConfig } from '../../../../angular-sdk/src/lib/types/types'; 6 | import mocked = jest.mocked; 7 | 8 | jest.mock('@descope/web-js-sdk'); 9 | 10 | describe('ManageAccessKeysComponent', () => { 11 | let component: ManageAccessKeysComponent; 12 | let fixture: ComponentFixture; 13 | 14 | let mockedCreateSdk: jest.Mock; 15 | 16 | beforeEach(() => { 17 | mockedCreateSdk = mocked(createSdk); 18 | mockedCreateSdk.mockReturnValue({}); 19 | 20 | TestBed.configureTestingModule({ 21 | schemas: [NO_ERRORS_SCHEMA], 22 | declarations: [ManageAccessKeysComponent], 23 | providers: [ 24 | DescopeAuthConfig, 25 | { provide: DescopeAuthConfig, useValue: { projectId: 'test' } } 26 | ] 27 | }); 28 | fixture = TestBed.createComponent(ManageAccessKeysComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/manage-access-keys/manage-access-keys.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { environment } from '../../environments/environment'; 3 | import { Router } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-manage-access-keys', 7 | templateUrl: './manage-access-keys.component.html' 8 | }) 9 | export class ManageAccessKeysComponent { 10 | theme = (environment.descopeTheme as 'light' | 'dark' | 'os') ?? 'os'; 11 | debugMode = environment.descopeDebugMode ?? false; 12 | tenant = environment.descopeTenantId ?? ''; 13 | 14 | constructor(private _: Router) {} 15 | } 16 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/manage-audit/manage-audit.component.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/manage-audit/manage-audit.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { ManageAuditComponent } from './manage-audit.component'; 3 | import createSdk from '@descope/web-js-sdk'; 4 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 5 | import { DescopeAuthConfig } from '../../../../angular-sdk/src/lib/types/types'; 6 | import mocked = jest.mocked; 7 | 8 | jest.mock('@descope/web-js-sdk'); 9 | 10 | describe('ManageAuditComponent', () => { 11 | let component: ManageAuditComponent; 12 | let fixture: ComponentFixture; 13 | 14 | let mockedCreateSdk: jest.Mock; 15 | 16 | beforeEach(() => { 17 | mockedCreateSdk = mocked(createSdk); 18 | mockedCreateSdk.mockReturnValue({}); 19 | 20 | TestBed.configureTestingModule({ 21 | schemas: [NO_ERRORS_SCHEMA], 22 | declarations: [ManageAuditComponent], 23 | providers: [ 24 | DescopeAuthConfig, 25 | { provide: DescopeAuthConfig, useValue: { projectId: 'test' } } 26 | ] 27 | }); 28 | fixture = TestBed.createComponent(ManageAuditComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/manage-audit/manage-audit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { environment } from '../../environments/environment'; 3 | import { Router } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-manage-audit', 7 | templateUrl: './manage-audit.component.html' 8 | }) 9 | export class ManageAuditComponent { 10 | theme = (environment.descopeTheme as 'light' | 'dark' | 'os') ?? 'os'; 11 | debugMode = environment.descopeDebugMode ?? false; 12 | tenant = environment.descopeTenantId ?? ''; 13 | 14 | constructor(private _: Router) {} 15 | } 16 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/manage-roles/manage-roles.component.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/manage-roles/manage-roles.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { ManageRolesComponent } from './manage-roles.component'; 3 | import createSdk from '@descope/web-js-sdk'; 4 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 5 | import { DescopeAuthConfig } from '../../../../angular-sdk/src/lib/types/types'; 6 | import mocked = jest.mocked; 7 | 8 | jest.mock('@descope/web-js-sdk'); 9 | 10 | describe('ManageRolesComponent', () => { 11 | let component: ManageRolesComponent; 12 | let fixture: ComponentFixture; 13 | 14 | let mockedCreateSdk: jest.Mock; 15 | 16 | beforeEach(() => { 17 | mockedCreateSdk = mocked(createSdk); 18 | mockedCreateSdk.mockReturnValue({}); 19 | 20 | TestBed.configureTestingModule({ 21 | schemas: [NO_ERRORS_SCHEMA], 22 | declarations: [ManageRolesComponent], 23 | providers: [ 24 | DescopeAuthConfig, 25 | { provide: DescopeAuthConfig, useValue: { projectId: 'test' } } 26 | ] 27 | }); 28 | fixture = TestBed.createComponent(ManageRolesComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/manage-roles/manage-roles.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { environment } from '../../environments/environment'; 3 | import { Router } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-manage-roles', 7 | templateUrl: './manage-roles.component.html' 8 | }) 9 | export class ManageRolesComponent { 10 | theme = (environment.descopeTheme as 'light' | 'dark' | 'os') ?? 'os'; 11 | debugMode = environment.descopeDebugMode ?? false; 12 | tenant = environment.descopeTenantId ?? ''; 13 | 14 | constructor(private _: Router) {} 15 | } 16 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/manage-users/manage-users.component.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/manage-users/manage-users.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { ManageUsersComponent } from './manage-users.component'; 3 | import createSdk from '@descope/web-js-sdk'; 4 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 5 | import { DescopeAuthConfig } from '../../../../angular-sdk/src/lib/types/types'; 6 | import mocked = jest.mocked; 7 | 8 | jest.mock('@descope/web-js-sdk'); 9 | 10 | describe('ManageUsersComponent', () => { 11 | let component: ManageUsersComponent; 12 | let fixture: ComponentFixture; 13 | 14 | let mockedCreateSdk: jest.Mock; 15 | 16 | beforeEach(() => { 17 | mockedCreateSdk = mocked(createSdk); 18 | mockedCreateSdk.mockReturnValue({}); 19 | 20 | TestBed.configureTestingModule({ 21 | schemas: [NO_ERRORS_SCHEMA], 22 | declarations: [ManageUsersComponent], 23 | providers: [ 24 | DescopeAuthConfig, 25 | { provide: DescopeAuthConfig, useValue: { projectId: 'test' } } 26 | ] 27 | }); 28 | fixture = TestBed.createComponent(ManageUsersComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/manage-users/manage-users.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { environment } from '../../environments/environment'; 3 | import { Router } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-manage-users', 7 | templateUrl: './manage-users.component.html' 8 | }) 9 | export class ManageUsersComponent { 10 | theme = (environment.descopeTheme as 'light' | 'dark' | 'os') ?? 'os'; 11 | debugMode = environment.descopeDebugMode ?? false; 12 | tenant = environment.descopeTenantId ?? ''; 13 | 14 | constructor(private _: Router) {} 15 | } 16 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/my-user-profile/my-user-profile.component.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/my-user-profile/my-user-profile.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { MyUserProfileComponent } from './my-user-profile.component'; 3 | import createSdk from '@descope/web-js-sdk'; 4 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 5 | import { DescopeAuthConfig } from '../../../../angular-sdk/src/lib/types/types'; 6 | import mocked = jest.mocked; 7 | 8 | jest.mock('@descope/web-js-sdk'); 9 | 10 | describe('MyUserProfileComponent', () => { 11 | let component: MyUserProfileComponent; 12 | let fixture: ComponentFixture; 13 | 14 | let mockedCreateSdk: jest.Mock; 15 | 16 | beforeEach(() => { 17 | mockedCreateSdk = mocked(createSdk); 18 | mockedCreateSdk.mockReturnValue({}); 19 | 20 | TestBed.configureTestingModule({ 21 | schemas: [NO_ERRORS_SCHEMA], 22 | declarations: [MyUserProfileComponent], 23 | providers: [ 24 | DescopeAuthConfig, 25 | { provide: DescopeAuthConfig, useValue: { projectId: 'test' } } 26 | ] 27 | }); 28 | fixture = TestBed.createComponent(MyUserProfileComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/my-user-profile/my-user-profile.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { environment } from '../../environments/environment'; 3 | import { Router } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-my-user-profile', 7 | templateUrl: './my-user-profile.component.html', 8 | styleUrls: ['./my-user-profile.scss'] 9 | }) 10 | export class MyUserProfileComponent { 11 | theme = (environment.descopeTheme as 'light' | 'dark' | 'os') ?? 'os'; 12 | debugMode = environment.descopeDebugMode ?? false; 13 | 14 | onLogout(e: CustomEvent) { 15 | console.log('SUCCESSFULLY LOGGED IN FROM WEB COMPONENT', e.detail); 16 | this.router.navigate(['/login']).catch((err) => console.error(err)); 17 | } 18 | 19 | constructor(private router: Router) {} 20 | } 21 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/my-user-profile/my-user-profile.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | height: 100vh; 3 | position: relative; 4 | } 5 | main { 6 | border-radius: 10px; 7 | margin: auto; 8 | border: 1px solid lightgray; 9 | padding: 20px; 10 | max-width: 700px; 11 | box-shadow: 13px 13px 20px #cbced1, -13px -13px 20px #fff; 12 | background: #ecf0f3; 13 | position: relative; 14 | top: 50%; 15 | transform: translateY(-50%); 16 | } 17 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/protected/protected.component.html: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 |

STEP UP SUCCESS

17 | 18 |
19 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/protected/protected.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | height: 100%; 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | flex-direction: column; 7 | gap: 20px; 8 | } 9 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/protected/protected.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProtectedComponent } from './protected.component'; 4 | import createSdk from '@descope/web-js-sdk'; 5 | import mocked = jest.mocked; 6 | import { DescopeAuthConfig } from '../../../../angular-sdk/src/lib/types/types'; 7 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 8 | 9 | jest.mock('@descope/web-js-sdk'); 10 | 11 | describe('ProtectedComponent', () => { 12 | let component: ProtectedComponent; 13 | let fixture: ComponentFixture; 14 | 15 | let mockedCreateSdk: jest.Mock; 16 | const onSessionTokenChangeSpy = jest.fn(); 17 | const onUserChangeSpy = jest.fn(); 18 | 19 | beforeEach(() => { 20 | mockedCreateSdk = mocked(createSdk); 21 | mockedCreateSdk.mockReturnValue({ 22 | onSessionTokenChange: onSessionTokenChangeSpy, 23 | onUserChange: onUserChangeSpy 24 | }); 25 | 26 | TestBed.configureTestingModule({ 27 | declarations: [ProtectedComponent], 28 | schemas: [NO_ERRORS_SCHEMA], 29 | providers: [ 30 | DescopeAuthConfig, 31 | { provide: DescopeAuthConfig, useValue: { projectId: 'test' } } 32 | ] 33 | }); 34 | fixture = TestBed.createComponent(ProtectedComponent); 35 | component = fixture.componentInstance; 36 | fixture.detectChanges(); 37 | }); 38 | 39 | it('should create', () => { 40 | expect(component).toBeTruthy(); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /projects/demo-app/src/app/protected/protected.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { environment } from '../../environments/environment'; 4 | 5 | @Component({ 6 | selector: 'app-protected', 7 | templateUrl: './protected.component.html', 8 | styleUrls: ['./protected.component.scss'] 9 | }) 10 | export class ProtectedComponent { 11 | flowId = environment.descopeStepUpFlowId ?? 'sign-up-or-in'; 12 | theme = (environment.descopeTheme as 'light' | 'dark' | 'os') ?? 'os'; 13 | telemetryKey = environment.descopeTelemetryKey ?? ''; 14 | debugMode = environment.descopeDebugMode ?? false; 15 | tenantId = environment.descopeTenantId ?? ''; 16 | locale = environment.descopeLocale ?? ''; 17 | redirectUrl = environment.descopeRedirectUrl ?? ''; 18 | 19 | stepUpSuccess = false; 20 | constructor(private router: Router) {} 21 | 22 | errorTransformer = (error: { text: string; type: string }): string => { 23 | const translationMap: { [key: string]: string } = { 24 | SAMLStartFailed: 'Failed to start SAML flow' 25 | }; 26 | return translationMap[error.type] || error.text; 27 | }; 28 | 29 | onSuccess(e: CustomEvent) { 30 | console.log('SUCCESSFULLY DONE IN PROTECTED ROUTE FLOW', e.detail); 31 | this.stepUpSuccess = true; 32 | } 33 | 34 | onError(e: CustomEvent) { 35 | console.log('ERROR FROM PROTECTED ROUTE FLOW', e); 36 | } 37 | 38 | goBack() { 39 | this.router.navigate(['/']).catch((err) => console.error(err)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /projects/demo-app/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/descope/angular-sdk/82470644b17fc3a542135c8faf239d6825ff124c/projects/demo-app/src/assets/.gitkeep -------------------------------------------------------------------------------- /projects/demo-app/src/environments/conifg.ts: -------------------------------------------------------------------------------- 1 | export interface Env { 2 | descopeProjectId: string; 3 | descopeBaseUrl?: string; 4 | descopeBaseStaticUrl?: string; 5 | descopeFlowId?: string; 6 | descopeDebugMode?: false; 7 | descopeTheme?: string; 8 | descopeLocale?: string; 9 | descopeRedirectUrl?: string; 10 | descopeTenantId?: string; 11 | descopeTelemetryKey?: string; 12 | descopeStepUpFlowId?: string; 13 | backendUrl?: string; 14 | } 15 | -------------------------------------------------------------------------------- /projects/demo-app/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | import { Env } from './conifg'; 2 | 3 | /** 4 | * Create environment.development.ts file and copy content of this file to it. 5 | * Fill the env vars according to your needs 6 | */ 7 | export const environment: Env = { 8 | descopeProjectId: '', 9 | descopeBaseUrl: '', 10 | descopeBaseStaticUrl: '', 11 | descopeFlowId: '', 12 | descopeDebugMode: false, 13 | descopeTheme: '', 14 | descopeLocale: '', 15 | descopeRedirectUrl: '', 16 | descopeTenantId: '', 17 | descopeTelemetryKey: '', 18 | descopeStepUpFlowId: '', 19 | backendUrl: '' 20 | }; 21 | -------------------------------------------------------------------------------- /projects/demo-app/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/descope/angular-sdk/82470644b17fc3a542135c8faf239d6825ff124c/projects/demo-app/src/favicon.ico -------------------------------------------------------------------------------- /projects/demo-app/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DemoApp 6 | 7 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /projects/demo-app/src/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { AppModule } from './app/app.module'; 4 | 5 | platformBrowserDynamic() 6 | .bootstrapModule(AppModule) 7 | .catch((err) => console.error(err)); 8 | -------------------------------------------------------------------------------- /projects/demo-app/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | body { 4 | height: 100vh; 5 | width: 100vw; 6 | margin: 0; 7 | font-family: 'Roboto', sans-serif; 8 | } 9 | 10 | button { 11 | width: 200px; 12 | height: 40px; 13 | border: none; 14 | background-color: #00ace1; 15 | color: white; 16 | cursor: pointer; 17 | 18 | &:hover { 19 | background-color: #047293; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /projects/demo-app/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/app", 6 | "target": "ES2022", 7 | "types": [] 8 | }, 9 | "files": ["src/main.ts"], 10 | "include": ["src/**/*.d.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /projects/demo-app/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/spec", 6 | "types": ["jest"], 7 | "module": "CommonJs" 8 | }, 9 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["local>descope/renovate-config"], 4 | "packageRules": [ 5 | { 6 | "matchDepNames": [ 7 | "prettier", 8 | "@angular-devkit/build-angular", 9 | "ng-packagr", 10 | "typescript", 11 | "@angular/*", 12 | "@angular-eslint/*" 13 | ], 14 | "enabled": false 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /scripts/gitleaks/.gitleaks.toml: -------------------------------------------------------------------------------- 1 | title = "gitleaks config" 2 | 3 | [[rules]] 4 | id = "gitlab-pat" 5 | description = "GitLab Personal Access Token" 6 | regex = '''glpat-[0-9a-zA-Z\-\_]{20}''' 7 | keywords = ["glpat"] 8 | 9 | [[rules]] 10 | id = "aws-access-token" 11 | description = "AWS" 12 | regex = '''(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}''' 13 | keywords = [ 14 | "AKIA", 15 | "AGPA", 16 | "AIDA", 17 | "AROA", 18 | "AIPA", 19 | "ANPA", 20 | "ANVA", 21 | "ASIA", 22 | ] 23 | 24 | [[rules]] 25 | id = "PKCS8-PK" 26 | description = "PKCS8 private key" 27 | regex = '''-----BEGIN PRIVATE KEY-----''' 28 | keywords = ["BEGIN PRIVATE"] 29 | 30 | [[rules]] 31 | id = "RSA-PK" 32 | description = "RSA private key" 33 | regex = '''-----BEGIN RSA PRIVATE KEY-----''' 34 | keywords = ["BEGIN RSA"] 35 | 36 | [[rules]] 37 | id = "OPENSSH-PK" 38 | description = "SSH private key" 39 | regex = '''-----BEGIN OPENSSH PRIVATE KEY-----''' 40 | keywords = ["BEGIN OPENSSH"] 41 | 42 | [[rules]] 43 | id = "PGP-PK" 44 | description = "PGP private key" 45 | regex = '''-----BEGIN PGP PRIVATE KEY BLOCK-----''' 46 | keywords = ["BEGIN PGP"] 47 | 48 | [[rules]] 49 | id = "github-pat" 50 | description = "GitHub Personal Access Token" 51 | regex = '''ghp_[0-9a-zA-Z]{36}''' 52 | keywords = ["ghp_"] 53 | 54 | [[rules]] 55 | id = "github-oauth" 56 | description = "GitHub OAuth Access Token" 57 | regex = '''gho_[0-9a-zA-Z]{36}''' 58 | keywords = ["gho_"] 59 | 60 | 61 | [[rules]] 62 | id = "SSH-DSA-PK" 63 | description = "SSH (DSA) private key" 64 | regex = '''-----BEGIN DSA PRIVATE KEY-----''' 65 | keywords = ["BEGIN DSA"] 66 | 67 | [[rules]] 68 | id = "SSH-EC-PK" 69 | description = "SSH (EC) private key" 70 | regex = '''-----BEGIN EC PRIVATE KEY-----''' 71 | keywords = ["BEGIN EC"] 72 | 73 | 74 | [[rules]] 75 | id = "github-app-token" 76 | description = "GitHub App Token" 77 | regex = '''(ghu|ghs)_[0-9a-zA-Z]{36}''' 78 | keywords = [ 79 | "ghu_", 80 | "ghs_" 81 | ] 82 | 83 | [[rules]] 84 | id = "github-refresh-token" 85 | description = "GitHub Refresh Token" 86 | regex = '''ghr_[0-9a-zA-Z]{76}''' 87 | keywords = ["ghr_"] 88 | 89 | [[rules]] 90 | id = "shopify-shared-secret" 91 | description = "Shopify shared secret" 92 | regex = '''shpss_[a-fA-F0-9]{32}''' 93 | keywords = ["shpss_"] 94 | 95 | [[rules]] 96 | id = "shopify-access-token" 97 | description = "Shopify access token" 98 | regex = '''shpat_[a-fA-F0-9]{32}''' 99 | keywords = ["shpat_"] 100 | 101 | [[rules]] 102 | id = "shopify-custom-access-token" 103 | description = "Shopify custom app access token" 104 | regex = '''shpca_[a-fA-F0-9]{32}''' 105 | keywords = ["shpca_"] 106 | 107 | [[rules]] 108 | id = "shopify-private-app-access-token" 109 | description = "Shopify private app access token" 110 | regex = '''shppa_[a-fA-F0-9]{32}''' 111 | keywords = ["shppa_"] 112 | 113 | [[rules]] 114 | id = "slack-access-token" 115 | description = "Slack token" 116 | regex = '''xox[baprs]-([0-9a-zA-Z]{10,48})?''' 117 | keywords = [ 118 | "xoxb", 119 | "xoxa", 120 | "xoxp", 121 | "xoxr", 122 | "xoxs" 123 | ] 124 | 125 | [[rules]] 126 | id = "stripe-access-token" 127 | description = "Stripe" 128 | regex = '''(?i)(sk|pk)_(test|live)_[0-9a-z]{10,32}''' 129 | keywords = [ 130 | "sk_test", 131 | "pk_test", 132 | "sk_live", 133 | "pk_live" 134 | ] 135 | 136 | [[rules]] 137 | id = "pypi-upload-token" 138 | description = "PyPI upload token" 139 | regex = '''pypi-AgEIcHlwaS5vcmc[A-Za-z0-9\-_]{50,1000}''' 140 | keywords = ["pypi-AgEIcHlwaS5vcmc"] 141 | 142 | [[rules]] 143 | id = "gcp-service-account" 144 | description = "Google (GCP) Service-account" 145 | regex = '''\"type\": \"service_account\"''' 146 | keywords = ["\"type\": \"service_account\""] 147 | 148 | [[rules]] 149 | id = "heroku-api-key" 150 | description = "Heroku API Key" 151 | regex = ''' (?i)(heroku[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})['\"]''' 152 | secretGroup = 3 153 | keywords = ["heroku"] 154 | 155 | [[rules]] 156 | id = "slack-web-hook" 157 | description = "Slack Webhook" 158 | regex = '''https://hooks.slack.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8,12}/[a-zA-Z0-9_]{24}''' 159 | keywords = ["https://hooks.slack.com/services/"] 160 | 161 | [[rules]] 162 | id = "twilio-api-key" 163 | description = "Twilio API Key" 164 | regex = '''SK[0-9a-fA-F]{32}''' 165 | keywords = ["twilio"] 166 | 167 | [[rules]] 168 | id = "age-secret-key" 169 | description = "Age secret key" 170 | regex = '''AGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]{58}''' 171 | keywords = ["AGE-SECRET-KEY-1"] 172 | 173 | [[rules]] 174 | id = "facebook-token" 175 | description = "Facebook token" 176 | regex = '''(?i)(facebook[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-f0-9]{32})['\"]''' 177 | secretGroup = 3 178 | keywords = ["facebook"] 179 | 180 | [[rules]] 181 | id = "twitter-token" 182 | description = "Twitter token" 183 | regex = '''(?i)(twitter[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-f0-9]{35,44})['\"]''' 184 | secretGroup = 3 185 | keywords = ["twitter"] 186 | 187 | [[rules]] 188 | id = "adobe-client-id" 189 | description = "Adobe Client ID (Oauth Web)" 190 | regex = '''(?i)(adobe[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-f0-9]{32})['\"]''' 191 | secretGroup = 3 192 | keywords = ["adobe"] 193 | 194 | [[rules]] 195 | id = "adobe-client-secret" 196 | description = "Adobe Client Secret" 197 | regex = '''(p8e-)(?i)[a-z0-9]{32}''' 198 | keywords = ["p8e-"] 199 | 200 | [[rules]] 201 | id = "alibaba-access-key-id" 202 | description = "Alibaba AccessKey ID" 203 | regex = '''(LTAI)(?i)[a-z0-9]{20}''' 204 | keywords = ["LTAI"] 205 | 206 | [[rules]] 207 | id = "alibaba-secret-key" 208 | description = "Alibaba Secret Key" 209 | regex = '''(?i)(alibaba[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-z0-9]{30})['\"]''' 210 | secretGroup = 3 211 | keywords = ["alibaba"] 212 | 213 | [[rules]] 214 | id = "asana-client-id" 215 | description = "Asana Client ID" 216 | regex = '''(?i)(asana[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([0-9]{16})['\"]''' 217 | secretGroup = 3 218 | keywords = ["asana"] 219 | 220 | [[rules]] 221 | id = "asana-client-secret" 222 | description = "Asana Client Secret" 223 | regex = '''(?i)(asana[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-z0-9]{32})['\"]''' 224 | secretGroup = 3 225 | keywords = ["asana"] 226 | 227 | [[rules]] 228 | id = "atlassian-api-token" 229 | description = "Atlassian API token" 230 | regex = '''(?i)(atlassian[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-z0-9]{24})['\"]''' 231 | secretGroup = 3 232 | keywords = ["atlassian"] 233 | 234 | [[rules]] 235 | id = "bitbucket-client-id" 236 | description = "Bitbucket client ID" 237 | regex = '''(?i)(bitbucket[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-z0-9]{32})['\"]''' 238 | secretGroup = 3 239 | keywords = ["bitbucket"] 240 | 241 | [[rules]] 242 | id = "bitbucket-client-secret" 243 | description = "Bitbucket client secret" 244 | regex = '''(?i)(bitbucket[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-z0-9_\-]{64})['\"]''' 245 | secretGroup = 3 246 | keywords = ["bitbucket"] 247 | 248 | [[rules]] 249 | id = "beamer-api-token" 250 | description = "Beamer API token" 251 | regex = '''(?i)(beamer[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](b_[a-z0-9=_\-]{44})['\"]''' 252 | secretGroup = 3 253 | keywords = ["beamer"] 254 | 255 | [[rules]] 256 | id = "clojars-api-token" 257 | description = "Clojars API token" 258 | regex = '''(CLOJARS_)(?i)[a-z0-9]{60}''' 259 | keywords = ["clojars"] 260 | 261 | [[rules]] 262 | id = "contentful-delivery-api-token" 263 | description = "Contentful delivery API token" 264 | regex = '''(?i)(contentful[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-z0-9\-=_]{43})['\"]''' 265 | secretGroup = 3 266 | keywords = ["contentful"] 267 | 268 | [[rules]] 269 | id = "databricks-api-token" 270 | description = "Databricks API token" 271 | regex = '''dapi[a-h0-9]{32}''' 272 | keywords = ["dapi"] 273 | 274 | [[rules]] 275 | id = "discord-api-token" 276 | description = "Discord API key" 277 | regex = '''(?i)(discord[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-h0-9]{64})['\"]''' 278 | secretGroup = 3 279 | keywords = ["discord"] 280 | 281 | [[rules]] 282 | id = "discord-client-id" 283 | description = "Discord client ID" 284 | regex = '''(?i)(discord[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([0-9]{18})['\"]''' 285 | secretGroup = 3 286 | keywords = ["discord"] 287 | 288 | [[rules]] 289 | id = "discord-client-secret" 290 | description = "Discord client secret" 291 | regex = '''(?i)(discord[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-z0-9=_\-]{32})['\"]''' 292 | secretGroup = 3 293 | keywords = ["discord"] 294 | 295 | [[rules]] 296 | id = "doppler-api-token" 297 | description = "Doppler API token" 298 | regex = '''['\"](dp\.pt\.)(?i)[a-z0-9]{43}['\"]''' 299 | keywords = ["doppler"] 300 | 301 | [[rules]] 302 | id = "dropbox-api-secret" 303 | description = "Dropbox API secret/key" 304 | regex = '''(?i)(dropbox[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-z0-9]{15})['\"]''' 305 | keywords = ["dropbox"] 306 | 307 | [[rules]] 308 | id = "dropbox--api-key" 309 | description = "Dropbox API secret/key" 310 | regex = '''(?i)(dropbox[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-z0-9]{15})['\"]''' 311 | keywords = ["dropbox"] 312 | 313 | [[rules]] 314 | id = "dropbox-short-lived-api-token" 315 | description = "Dropbox short lived API token" 316 | regex = '''(?i)(dropbox[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](sl\.[a-z0-9\-=_]{135})['\"]''' 317 | keywords = ["dropbox"] 318 | 319 | [[rules]] 320 | id = "dropbox-long-lived-api-token" 321 | description = "Dropbox long lived API token" 322 | regex = '''(?i)(dropbox[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"][a-z0-9]{11}(AAAAAAAAAA)[a-z0-9\-_=]{43}['\"]''' 323 | keywords = ["dropbox"] 324 | 325 | [[rules]] 326 | id = "duffel-api-token" 327 | description = "Duffel API token" 328 | regex = '''['\"]duffel_(test|live)_(?i)[a-z0-9_-]{43}['\"]''' 329 | keywords = ["duffel"] 330 | 331 | [[rules]] 332 | id = "dynatrace-api-token" 333 | description = "Dynatrace API token" 334 | regex = '''['\"]dt0c01\.(?i)[a-z0-9]{24}\.[a-z0-9]{64}['\"]''' 335 | keywords = ["dynatrace"] 336 | 337 | [[rules]] 338 | id = "easypost-api-token" 339 | description = "EasyPost API token" 340 | regex = '''['\"]EZAK(?i)[a-z0-9]{54}['\"]''' 341 | keywords = ["EZAK"] 342 | 343 | [[rules]] 344 | id = "easypost-test-api-token" 345 | description = "EasyPost test API token" 346 | regex = '''['\"]EZTK(?i)[a-z0-9]{54}['\"]''' 347 | keywords = ["EZTK"] 348 | 349 | [[rules]] 350 | id = "fastly-api-token" 351 | description = "Fastly API token" 352 | regex = '''(?i)(fastly[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-z0-9\-=_]{32})['\"]''' 353 | secretGroup = 3 354 | keywords = ["fastly"] 355 | 356 | [[rules]] 357 | id = "finicity-client-secret" 358 | description = "Finicity client secret" 359 | regex = '''(?i)(finicity[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-z0-9]{20})['\"]''' 360 | secretGroup = 3 361 | keywords = ["finicity"] 362 | 363 | [[rules]] 364 | id = "finicity-api-token" 365 | description = "Finicity API token" 366 | regex = '''(?i)(finicity[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-f0-9]{32})['\"]''' 367 | secretGroup = 3 368 | keywords = ["finicity"] 369 | 370 | [[rules]] 371 | id = "flutterwave-public-key" 372 | description = "Flutterwave public key" 373 | regex = '''FLWPUBK_TEST-(?i)[a-h0-9]{32}-X''' 374 | keywords = ["FLWPUBK_TEST"] 375 | 376 | [[rules]] 377 | id = "flutterwave-secret-key" 378 | description = "Flutterwave secret key" 379 | regex = '''FLWSECK_TEST-(?i)[a-h0-9]{32}-X''' 380 | keywords = ["FLWSECK_TEST"] 381 | 382 | [[rules]] 383 | id = "flutterwave-enc-key" 384 | description = "Flutterwave encrypted key" 385 | regex = '''FLWSECK_TEST[a-h0-9]{12}''' 386 | keywords = ["FLWSECK_TEST"] 387 | 388 | [[rules]] 389 | id = "frameio-api-token" 390 | description = "Frame.io API token" 391 | regex = '''fio-u-(?i)[a-z0-9\-_=]{64}''' 392 | keywords = ["fio-u-"] 393 | 394 | [[rules]] 395 | id = "gocardless-api-token" 396 | description = "GoCardless API token" 397 | regex = '''['\"]live_(?i)[a-z0-9\-_=]{40}['\"]''' 398 | keywords = ["live_"] 399 | 400 | [[rules]] 401 | id = "hashicorp-tf-api-token" 402 | description = "HashiCorp Terraform user/org API token" 403 | regex = '''['\"](?i)[a-z0-9]{14}\.atlasv1\.[a-z0-9\-_=]{60,70}['\"]''' 404 | keywords = ["atlasv1"] 405 | 406 | [[rules]] 407 | id = "hubspot-api-token" 408 | description = "HubSpot API token" 409 | regex = '''(?i)(hubspot[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-h0-9]{8}-[a-h0-9]{4}-[a-h0-9]{4}-[a-h0-9]{4}-[a-h0-9]{12})['\"]''' 410 | secretGroup = 3 411 | keywords = ["hubspot"] 412 | 413 | [[rules]] 414 | id = "intercom-api-token" 415 | description = "Intercom API token" 416 | regex = '''(?i)(intercom[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-z0-9=_]{60})['\"]''' 417 | secretGroup = 3 418 | keywords = ["intercom"] 419 | 420 | [[rules]] 421 | id = "intercom-client-secret" 422 | description = "Intercom client secret/ID" 423 | regex = '''(?i)(intercom[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-h0-9]{8}-[a-h0-9]{4}-[a-h0-9]{4}-[a-h0-9]{4}-[a-h0-9]{12})['\"]''' 424 | secretGroup = 3 425 | keywords = ["intercom"] 426 | 427 | [[rules]] 428 | id = "ionic-api-token" 429 | description = "Ionic API token" 430 | regex = '''(?i)(ionic[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](ion_[a-z0-9]{42})['\"]''' 431 | keywords = ["ionic"] 432 | 433 | [[rules]] 434 | id = "linear-api-token" 435 | description = "Linear API token" 436 | regex = '''lin_api_(?i)[a-z0-9]{40}''' 437 | keywords = ["lin_api_"] 438 | 439 | [[rules]] 440 | id = "linear-client-secret" 441 | description = "Linear client secret/ID" 442 | regex = '''(?i)(linear[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-f0-9]{32})['\"]''' 443 | secretGroup = 3 444 | keywords = ["linear"] 445 | 446 | [[rules]] 447 | id = "lob-api-key" 448 | description = "Lob API Key" 449 | regex = '''(?i)(lob[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]((live|test)_[a-f0-9]{35})['\"]''' 450 | secretGroup = 3 451 | keywords = ["lob"] 452 | 453 | [[rules]] 454 | id = "lob-pub-api-key" 455 | description = "Lob Publishable API Key" 456 | regex = '''(?i)(lob[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]((test|live)_pub_[a-f0-9]{31})['\"]''' 457 | secretGroup = 3 458 | keywords = [ 459 | "test_pub", 460 | "live_pub", 461 | "_pub" 462 | ] 463 | 464 | [[rules]] 465 | id = "mailchimp-api-key" 466 | description = "Mailchimp API key" 467 | regex = '''(?i)(mailchimp[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-f0-9]{32}-us20)['\"]''' 468 | secretGroup = 3 469 | keywords = ["mailchimp"] 470 | 471 | [[rules]] 472 | id = "mailgun-private-api-token" 473 | description = "Mailgun private API token" 474 | regex = '''(?i)(mailgun[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](key-[a-f0-9]{32})['\"]''' 475 | secretGroup = 3 476 | keywords = [ 477 | "mailgun", 478 | "key-" 479 | ] 480 | 481 | [[rules]] 482 | id = "mailgun-pub-key" 483 | description = "Mailgun public validation key" 484 | regex = '''(?i)(mailgun[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"](pubkey-[a-f0-9]{32})['\"]''' 485 | secretGroup = 3 486 | keywords = [ 487 | "mailgun", 488 | "pubkey-" 489 | ] 490 | 491 | [[rules]] 492 | id = "mailgun-signing-key" 493 | description = "Mailgun webhook signing key" 494 | regex = '''(?i)(mailgun[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-h0-9]{32}-[a-h0-9]{8}-[a-h0-9]{8})['\"]''' 495 | secretGroup = 3 496 | keywords = ["mailgun"] 497 | 498 | [[rules]] 499 | id = "mapbox-api-token" 500 | description = "Mapbox API token" 501 | regex = '''(?i)(pk\.[a-z0-9]{60}\.[a-z0-9]{22})''' 502 | keywords = ["mapbox"] 503 | 504 | [[rules]] 505 | id = "messagebird-api-token" 506 | description = "MessageBird API token" 507 | regex = '''(?i)(messagebird[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-z0-9]{25})['\"]''' 508 | secretGroup = 3 509 | keywords = [ 510 | "messagebird", 511 | "message_bird", 512 | "message-bird" 513 | ] 514 | 515 | [[rules]] 516 | id = "messagebird-client-id" 517 | description = "MessageBird API client ID" 518 | regex = '''(?i)(messagebird[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-h0-9]{8}-[a-h0-9]{4}-[a-h0-9]{4}-[a-h0-9]{4}-[a-h0-9]{12})['\"]''' 519 | secretGroup = 3 520 | keywords = [ 521 | "messagebird", 522 | "message_bird", 523 | "message-bird" 524 | ] 525 | 526 | [[rules]] 527 | id = "new-relic-user-api-key" 528 | description = "New Relic user API Key" 529 | regex = '''['\"](NRAK-[A-Z0-9]{27})['\"]''' 530 | keywords = ["NRAK-"] 531 | 532 | [[rules]] 533 | id = "new-relic-user-api-id" 534 | description = "New Relic user API ID" 535 | regex = '''(?i)(newrelic[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([A-Z0-9]{64})['\"]''' 536 | secretGroup = 3 537 | keywords = ["newrelic"] 538 | 539 | [[rules]] 540 | id = "new-relic-browser-api-token" 541 | description = "New Relic ingest browser API token" 542 | regex = '''['\"](NRJS-[a-f0-9]{19})['\"]''' 543 | keywords = ["NRJS-"] 544 | 545 | [[rules]] 546 | id = "npm-access-token" 547 | description = "npm access token" 548 | regex = '''['\"](npm_(?i)[a-z0-9]{36})['\"]''' 549 | keywords = ["npm_"] 550 | 551 | [[rules]] 552 | id = "planetscale-password" 553 | description = "PlanetScale password" 554 | regex = '''pscale_pw_(?i)[a-z0-9\-_\.]{43}''' 555 | keywords = ["pscale_pw_"] 556 | 557 | [[rules]] 558 | id = "planetscale-api-token" 559 | description = "PlanetScale API token" 560 | regex = '''pscale_tkn_(?i)[a-z0-9\-_\.]{43}''' 561 | keywords = ["pscale_tkn_"] 562 | 563 | [[rules]] 564 | id = "postman-api-token" 565 | description = "Postman API token" 566 | regex = '''PMAK-(?i)[a-f0-9]{24}\-[a-f0-9]{34}''' 567 | keywords = ["PMAK-"] 568 | 569 | [[rules]] 570 | id = "pulumi-api-token" 571 | description = "Pulumi API token" 572 | regex = '''pul-[a-f0-9]{40}''' 573 | keywords = ["pul-"] 574 | 575 | [[rules]] 576 | id = "rubygems-api-token" 577 | description = "Rubygem API token" 578 | regex = '''rubygems_[a-f0-9]{48}''' 579 | keywords = ["rubygems_"] 580 | 581 | [[rules]] 582 | id = "sendgrid-api-token" 583 | description = "SendGrid API token" 584 | regex = '''SG\.(?i)[a-z0-9_\-\.]{66}''' 585 | keywords = ["sendgrid"] 586 | 587 | [[rules]] 588 | id = "sendinblue-api-token" 589 | description = "Sendinblue API token" 590 | regex = '''xkeysib-[a-f0-9]{64}\-(?i)[a-z0-9]{16}''' 591 | keywords = ["xkeysib-"] 592 | 593 | [[rules]] 594 | id = "shippo-api-token" 595 | description = "Shippo API token" 596 | regex = '''shippo_(live|test)_[a-f0-9]{40}''' 597 | keywords = ["shippo_"] 598 | 599 | [[rules]] 600 | id = "linkedin-client-secret" 601 | description = "LinkedIn Client secret" 602 | regex = '''(?i)(linkedin[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-z]{16})['\"]''' 603 | secretGroup = 3 604 | keywords = ["linkedin"] 605 | 606 | [[rules]] 607 | id = "linkedin-client-id" 608 | description = "LinkedIn Client ID" 609 | regex = '''(?i)(linkedin[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-z0-9]{14})['\"]''' 610 | secretGroup = 3 611 | keywords = ["linkedin"] 612 | 613 | [[rules]] 614 | id = "twitch-api-token" 615 | description = "Twitch API token" 616 | regex = '''(?i)(twitch[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-z0-9]{30})['\"]''' 617 | secretGroup = 3 618 | keywords = ["twitch"] 619 | 620 | [[rules]] 621 | id = "typeform-api-token" 622 | description = "Typeform API token" 623 | regex = '''(?i)(typeform[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}(tfp_[a-z0-9\-_\.=]{59})''' 624 | secretGroup = 3 625 | keywords = ["tpf_"] 626 | 627 | [[rules]] 628 | id = "generic-api-key" 629 | description = "Generic API Key" 630 | regex = '''(?i)((key|api[^Version]|token|secret|password|auth)[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([0-9a-zA-Z\-_=]{8,64})['\"]''' 631 | entropy = 3.7 632 | secretGroup = 4 633 | keywords = [ 634 | "key", 635 | "api", 636 | "token", 637 | "secret", 638 | "password", 639 | "auth", 640 | ] 641 | 642 | [allowlist] 643 | description = "global allow lists" 644 | regexes = [ 645 | '''219-09-9999''', 646 | '''078-05-1120''', 647 | '''(9[0-9]{2}|666)-\d{2}-\d{4}''', 648 | ] 649 | paths = [ 650 | '''gitleaks.toml''', 651 | '''(.*?)(jpg|gif|doc|pdf|bin|svg|socket|js|ts|json)$''', 652 | "node_modules/", 653 | ] 654 | -------------------------------------------------------------------------------- /scripts/gitleaks/gitleaks.sh: -------------------------------------------------------------------------------- 1 | # Run detect-secrets 2 | lint_find_secrets() { 3 | echo "- Running secrets check" 4 | SECRETS_SUPPORTED_VERSION="8.8.11" 5 | INSTALLED_SECRETS_VERSION="$(gitleaks version)" 6 | if [[ $INSTALLED_SECRETS_VERSION != *"$SECRETS_SUPPORTED_VERSION"* ]]; then 7 | echo "Installing gitleaks $(uname -s)_$(arch) for the first time..." 8 | FILE=`curl --header "$headers" -s https://api.github.com/repos/zricethezav/gitleaks/releases/tags/v${SECRETS_SUPPORTED_VERSION} | jq -r "first(.assets[].name | select(test(\"$(uname -s)_$(arch)\"; \"i\") or test(\"$(uname -s)_x64\"; \"i\")))"` 9 | if [ -z "$FILE" ] 10 | then 11 | echo "Using redirect URL" 12 | URL_REDIRECT=`curl --header "$headers" -s https://api.github.com/repos/zricethezav/gitleaks/releases/tags/v${SECRETS_SUPPORTED_VERSION} | jq -r ".url"` 13 | FILE=`curl --header "$headers" -s ${URL_REDIRECT} | jq -r "first(.assets[].name | select(test(\"$(uname -s)_$(arch)\"; \"i\") or test(\"$(uname -s)_x64\"; \"i\")))"` 14 | fi 15 | TMPDIR=$(mktemp -d) 16 | curl -o ${TMPDIR}/${FILE} -JL https://github.com/zricethezav/gitleaks/releases/download/v${SECRETS_SUPPORTED_VERSION}/${FILE} 17 | tar zxv -C /usr/local/bin -f ${TMPDIR}/${FILE} gitleaks 18 | rm ${TMPDIR}/${FILE} 19 | echo "Done installing gitleaks" 20 | fi 21 | echo " - Finding leaks in git log" 22 | gitleaks detect -v --redact -c scripts/gitleaks/.gitleaks.toml 23 | if [ $? -ne 0 ]; then 24 | exit 1 25 | fi 26 | echo " - Finding leaks in local repo" 27 | gitleaks detect --no-git -v --redact -c scripts/gitleaks/.gitleaks.toml 28 | if [ $? -ne 0 ]; then 29 | exit 1 30 | fi 31 | echo "- Secrets check passed sucessfully!" 32 | } 33 | 34 | lint_find_secrets 35 | -------------------------------------------------------------------------------- /scripts/setversion/setversion.js: -------------------------------------------------------------------------------- 1 | const { writeFile } = require('fs'); 2 | const { version } = require('./../../package.json'); 3 | const envFile = `export const environment = { 4 | buildVersion: '${version}' 5 | }; 6 | `; 7 | 8 | console.log( 9 | `Writing version ${version} to projects/angular-sdk/src/environment.ts` 10 | ); 11 | 12 | writeFile('./projects/angular-sdk/src/environment.ts', envFile, function (err) { 13 | if (err) { 14 | console.error(err); 15 | process.exit(1); 16 | } 17 | console.log(`Environment file updated with version: ${version}`); 18 | }); 19 | 20 | console.log('Writing version done!'); 21 | -------------------------------------------------------------------------------- /setup-jest.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | -------------------------------------------------------------------------------- /thirdPartyLicenseCollector_linux_amd64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/descope/angular-sdk/82470644b17fc3a542135c8faf239d6825ff124c/thirdPartyLicenseCollector_linux_amd64 -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "paths": { 6 | "angular-sdk": ["dist/angular-sdk"] 7 | }, 8 | "typeRoots": ["./node_modules/@types", "./node_modules/@descope"], 9 | "baseUrl": "./", 10 | "outDir": "./dist/out-tsc", 11 | "forceConsistentCasingInFileNames": true, 12 | "strictPropertyInitialization": false, 13 | "strict": true, 14 | "noImplicitOverride": true, 15 | "noPropertyAccessFromIndexSignature": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "sourceMap": true, 19 | "declaration": false, 20 | "downlevelIteration": true, 21 | "experimentalDecorators": true, 22 | "moduleResolution": "node", 23 | "importHelpers": true, 24 | "target": "es2017", 25 | "module": "ES2022", 26 | "useDefineForClassFields": false, 27 | "lib": ["es2017", "dom"], 28 | "skipLibCheck": true 29 | }, 30 | "angularCompilerOptions": { 31 | "enableI18nLegacyMessageIdFormat": false, 32 | "strictInjectionParameters": true, 33 | "strictInputAccessModifiers": true, 34 | "strictTemplates": true 35 | } 36 | } 37 | --------------------------------------------------------------------------------