diff --git a/.c8rc.json b/.c8rc.json deleted file mode 100644 index 635483d7e..000000000 --- a/.c8rc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "src": "lib", - "include": "lib", - "exclude": "lib/router", - "reporter": [] -} diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index d5b1eb494..89cec0811 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -34,7 +34,10 @@ body: required: true - type: checkboxes attributes: + label: Required options: + - label: I have provided a gist or a public repo which can be cloned, installed and ran locally in order to reproduce the bug in the textarea above. + required: true - label: I have searched the issues tracker and discussions for similar topics and couldn't find anything related. required: true - label: I have searched the [FAQ](https://github.com/panva/node-oidc-provider/blob/v7.x/docs/README.md#faq) and couldn't find anything related. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index a525de907..1c9301bf3 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,21 +1,14 @@ blank_issues_enabled: false contact_links: + - name: Support the project + url: https://github.com/sponsors/panva + about: + To make sure you get your questions answered - name: ❓ Question url: https://github.com/panva/node-oidc-provider/discussions/894 about: - Have a question about using oidc-provider? Head over to the discussions "Q&A" Category + Have a question about using oidc-provider? Support the project and then head over to the discussions "Q&A" Category - name: 💡 Feature proposal url: https://github.com/panva/node-oidc-provider/discussions/893 about: Have a proposal for a new feature? Head over to the discussions "Ideas" Category - - name: Support the project - url: https://github.com/sponsors/panva - about: - Are you asking your nth question? Relying on oidc-provider for critical operations? Consider - supporting the project so that it may continue being maintained. - - name: Report a security vulnerability - url: https://en.wikipedia.org/wiki/Responsible_disclosure - about: - Do not disclose vulnerabilities via issues or discussions. Reach out to the project team - via e.g. email, we'll work together on patching the vulnerability and follow some form of - Responsible disclosure once fixed. Thank you. diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 417f06091..000000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: "Code scanning - action" - -on: - pull_request: - paths-ignore: - - '**.md' - schedule: - - cron: '0 14 * * 4' - -jobs: - CodeQL-Build: - - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can checkout the head. - fetch-depth: 2 - - # If this run was triggered by a pull request event, then checkout - # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - # Override language selection by uncommenting this and choosing your languages - # with: - # languages: go, javascript, csharp, python, cpp, java - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..52ec50138 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,74 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main" ] + schedule: + - cron: '55 11 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/label-sponsors.yml b/.github/workflows/label-sponsors.yml deleted file mode 100644 index 1e18063b6..000000000 --- a/.github/workflows/label-sponsors.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Label sponsors -on: - issues: - types: [opened] -jobs: - build: - name: is-sponsor-label - runs-on: ubuntu-latest - steps: - - uses: JasonEtco/is-sponsor-label-action@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 44399fc30..3fea04fcf 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -2,16 +2,17 @@ name: 'Lock threads' on: schedule: - - cron: '0 9 * * *' + - cron: '55 11 * * 1' jobs: lock: + continue-on-error: true runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v2 + - uses: dessant/lock-threads@e460dfeb36e731f3aeb214be6b0c9a9d9a67eda6 with: github-token: ${{ github.token }} - issue-lock-inactive-days: '90' + issue-inactive-days: '90' issue-lock-reason: '' - pr-lock-inactive-days: '90' + pr-inactive-days: '90' pr-lock-reason: '' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a3894184d..24599fda9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,107 +1,92 @@ -name: Continuous Integration +name: Test on: push: - paths-ignore: - - '**.md' pull_request: - paths-ignore: - - '**.md' schedule: - - cron: 0 9 * * 1-5 + - cron: '55 11 * * 1' + workflow_dispatch: + inputs: + conformance-version: + description: 'Conformance Suite Version (commit hash)' + required: false jobs: - lint: - name: Lint + audit: + name: NPM Audit (Production) continue-on-error: true runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@master + uses: actions/checkout@v3 - name: Setup node - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: - node-version: 12 - - name: Store node version variable - id: node - run: | - echo "::set-output name=version::$(node -v)" - - name: Cache node_modules - uses: actions/cache@v2 - id: node_modules - with: - path: node_modules - key: ${{ runner.os }}-node_modules-${{ hashFiles('**/package.json') }}-${{ steps.node.outputs.version }} - - name: Install dependencies - run: npx panva/npm-install-retry - if: ${{ steps.node_modules.outputs.cache-hit != 'true' }} - - run: npm run lint + node-version: lts/hydrogen # 18 + cache: 'npm' + check-latest: true + - run: npm clean-install + - run: npm upgrade + - run: npm audit --production test: name: Node Tests - runs-on: ${{ matrix.os }} - continue-on-error: ${{ matrix.experimental || false }} + runs-on: ubuntu-latest + continue-on-error: ${{ !startsWith(matrix.node-version, 'lts') }} strategy: fail-fast: false matrix: node-version: - - 12.19.0 - - 12 - - 14.15.0 - - 14 - os: - - ubuntu-latest - - windows-latest - include: - - experimental: true - node-version: '>=15' - os: ubuntu-latest + - lts/erbium # 12 + - lts/fermium # 14 + - lts/gallium # 16 + - lts/hydrogen # 18 + - current steps: - name: Checkout - uses: actions/checkout@master + uses: actions/checkout@v3 - name: Setup node - uses: actions/setup-node@v2-beta - with: - node-version: ${{ matrix.node-version }} - - name: Store node version variable id: node - run: | - echo "::set-output name=version::$(node -v)" - - name: Cache node_modules - uses: actions/cache@v2 - id: node_modules + uses: actions/setup-node@v3 with: - path: node_modules - key: ${{ runner.os }}-node_modules-${{ hashFiles('**/package.json') }}-${{ steps.node.outputs.version }} - - name: Install dependencies - run: npx panva/npm-install-retry - if: ${{ steps.node_modules.outputs.cache-hit != 'true' }} + node-version: ${{ matrix.node-version }} + cache: 'npm' + check-latest: true + - run: npm install --global npm@8 + if: ${{ startsWith(steps.node.outputs.node-version, 'v12') || startsWith(steps.node.outputs.node-version, 'v14') }} + - run: npm clean-install - run: npm run ci - - uses: actions/upload-artifact@v2 - if: ${{ matrix.node-version == 14 && matrix.os == 'ubuntu-latest' }} - with: - if-no-files-found: warn - retention-days: 1 - name: coverage-data - path: coverage/ build-conformance-suite: runs-on: ubuntu-latest - env: - VERSION: release-v4.1.19 + outputs: + cache-key: ${{ steps.cache-key.outputs.value }} steps: - name: Checkout - uses: actions/checkout@master + uses: actions/checkout@v3 + - name: Set Conformance Suite Version from GitLab + if: ${{ !github.event.inputs.conformance-version }} + run: | + export VERSION=($(curl --silent "https://gitlab.com/api/v4/projects/4175605/releases" | jq -r '.[0].tag_name')) + echo "VERSION=$VERSION" >> $GITHUB_ENV + - name: Set Conformance Suite Version from Workflow Dispatch + if: ${{ github.event.inputs.conformance-version }} + run: | + echo "VERSION=${{ github.event.inputs.conformance-version }}" >> $GITHUB_ENV + - id: cache-key + run: echo "value=suite-${{ hashFiles('.github/workflows/test.yml') }}-${{ env.VERSION }}" >> $GITHUB_OUTPUT - name: Load Cached Conformance Suite Build - uses: actions/cache@v2 + uses: actions/cache@v3 id: cache with: path: ./conformance-suite - key: suite-${{ hashFiles('**/test.yml') }} + key: ${{ steps.cache-key.outputs.value }} - name: Conformance Suite Checkout if: ${{ steps.cache.outputs.cache-hit != 'true' }} - run: git clone --depth 1 --single-branch --branch $VERSION https://gitlab.com/openid/conformance-suite.git + run: git clone https://gitlab.com/openid/conformance-suite.git + - run: git reset --hard ${{ env.VERSION }} + working-directory: ./conformance-suite - name: Conformance Suite Build working-directory: ./conformance-suite if: ${{ steps.cache.outputs.cache-hit != 'true' }} @@ -115,6 +100,7 @@ jobs: conformance-suite: runs-on: ubuntu-latest needs: + - test - build-conformance-suite env: SUITE_BASE_URL: https://localhost.emobix.co.uk:8443 @@ -185,49 +171,49 @@ jobs: client_auth_type: private_key_jwt fapi_profile: plain_fapi fapi_response_mode: plain_response - configuration: ./certification/fapi/pkjwt.json + configuration: ./certification/fapi/plan.json - plan: fapi-rw-id2-test-plan fapi_auth_request_method: pushed client_auth_type: private_key_jwt fapi_profile: plain_fapi fapi_response_mode: plain_response - configuration: ./certification/fapi/pkjwt.json + configuration: ./certification/fapi/plan.json - plan: fapi-rw-id2-test-plan fapi_auth_request_method: by_value client_auth_type: private_key_jwt fapi_profile: plain_fapi fapi_response_mode: jarm - configuration: ./certification/fapi/pkjwt.json + configuration: ./certification/fapi/plan.json - plan: fapi-rw-id2-test-plan fapi_auth_request_method: pushed client_auth_type: private_key_jwt fapi_profile: plain_fapi fapi_response_mode: jarm - configuration: ./certification/fapi/pkjwt.json + configuration: ./certification/fapi/plan.json - plan: fapi-rw-id2-test-plan fapi_auth_request_method: by_value client_auth_type: mtls fapi_profile: plain_fapi fapi_response_mode: plain_response - configuration: ./certification/fapi/mtls.json + configuration: ./certification/fapi/plan.json - plan: fapi-rw-id2-test-plan fapi_auth_request_method: pushed client_auth_type: mtls fapi_profile: plain_fapi fapi_response_mode: plain_response - configuration: ./certification/fapi/mtls.json + configuration: ./certification/fapi/plan.json - plan: fapi-rw-id2-test-plan fapi_auth_request_method: by_value client_auth_type: mtls fapi_profile: plain_fapi fapi_response_mode: jarm - configuration: ./certification/fapi/mtls.json + configuration: ./certification/fapi/plan.json - plan: fapi-rw-id2-test-plan fapi_auth_request_method: pushed client_auth_type: mtls fapi_profile: plain_fapi fapi_response_mode: jarm - configuration: ./certification/fapi/mtls.json + configuration: ./certification/fapi/plan.json # FAPI 1.0 Advanced (Final) - plan: fapi1-advanced-final-test-plan @@ -235,49 +221,49 @@ jobs: client_auth_type: private_key_jwt fapi_profile: plain_fapi fapi_response_mode: plain_response - configuration: ./certification/fapi/pkjwt.json + configuration: ./certification/fapi/plan.json - plan: fapi1-advanced-final-test-plan fapi_auth_request_method: pushed client_auth_type: private_key_jwt fapi_profile: plain_fapi fapi_response_mode: plain_response - configuration: ./certification/fapi/pkjwt.json + configuration: ./certification/fapi/plan.json - plan: fapi1-advanced-final-test-plan fapi_auth_request_method: by_value client_auth_type: private_key_jwt fapi_profile: plain_fapi fapi_response_mode: jarm - configuration: ./certification/fapi/pkjwt.json + configuration: ./certification/fapi/plan.json - plan: fapi1-advanced-final-test-plan fapi_auth_request_method: pushed client_auth_type: private_key_jwt fapi_profile: plain_fapi fapi_response_mode: jarm - configuration: ./certification/fapi/pkjwt.json + configuration: ./certification/fapi/plan.json - plan: fapi1-advanced-final-test-plan fapi_auth_request_method: by_value client_auth_type: mtls fapi_profile: plain_fapi fapi_response_mode: plain_response - configuration: ./certification/fapi/mtls.json + configuration: ./certification/fapi/plan.json - plan: fapi1-advanced-final-test-plan fapi_auth_request_method: pushed client_auth_type: mtls fapi_profile: plain_fapi fapi_response_mode: plain_response - configuration: ./certification/fapi/mtls.json + configuration: ./certification/fapi/plan.json - plan: fapi1-advanced-final-test-plan fapi_auth_request_method: by_value client_auth_type: mtls fapi_profile: plain_fapi fapi_response_mode: jarm - configuration: ./certification/fapi/mtls.json + configuration: ./certification/fapi/plan.json - plan: fapi1-advanced-final-test-plan fapi_auth_request_method: pushed client_auth_type: mtls fapi_profile: plain_fapi fapi_response_mode: jarm - configuration: ./certification/fapi/mtls.json + configuration: ./certification/fapi/plan.json # FAPI RW-CIBA-ID1 - plan: fapi-ciba-id1-test-plan @@ -285,25 +271,25 @@ jobs: fapi_profile: plain_fapi ciba_mode: poll client_registration: dynamic_client - configuration: ./certification/fapi/pkjwt.json + configuration: ./certification/fapi/plan.json - plan: fapi-ciba-id1-test-plan client_auth_type: private_key_jwt fapi_profile: plain_fapi ciba_mode: ping client_registration: dynamic_client - configuration: ./certification/fapi/pkjwt.json + configuration: ./certification/fapi/plan.json - plan: fapi-ciba-id1-test-plan client_auth_type: mtls fapi_profile: plain_fapi ciba_mode: poll client_registration: dynamic_client - configuration: ./certification/fapi/mtls.json + configuration: ./certification/fapi/plan.json - plan: fapi-ciba-id1-test-plan client_auth_type: mtls fapi_profile: plain_fapi ciba_mode: ping client_registration: dynamic_client - configuration: ./certification/fapi/mtls.json + configuration: ./certification/fapi/plan.json # Extensive - plan: oidcc-test-plan @@ -434,55 +420,45 @@ jobs: skip: oidcc-server-rotate-keys steps: - name: Checkout - uses: actions/checkout@master + uses: actions/checkout@v3 - name: Setup node - uses: actions/setup-node@v2-beta - with: - node-version: 14 - - name: Store node version variable - id: node - run: | - echo "::set-output name=version::$(node -v)" - - name: Cache node_modules - uses: actions/cache@v2 - id: node_modules + uses: actions/setup-node@v3 with: - path: node_modules - key: ${{ runner.os }}-node_modules-${{ hashFiles('**/package.json') }}-${{ steps.node.outputs.version }} - - name: Install dependencies - run: npx panva/npm-install-retry - if: ${{ steps.node_modules.outputs.cache-hit != 'true' }} + node-version: lts/hydrogen # 18 + cache: 'npm' + check-latest: true + - run: npm clean-install - name: Run oidc-provider (OIDC) run: npx c8 node certification/oidc/docker & - if: ${{ startsWith(matrix.setup.plan, 'oidcc-') }} + if: ${{ startsWith(matrix.setup.plan, 'oidcc') }} env: PORT: 3000 ISSUER: https://172.17.0.1:3000 NODE_TLS_REJECT_UNAUTHORIZED: 0 - - name: Run oidc-provider (FAPI 1.0 R/W - ID2) - run: npx c8 node certification/fapi & - if: ${{ startsWith(matrix.setup.plan, 'fapi-') }} - env: - ISSUER: https://172.17.0.1:3000 - PORT: 3000 - PROFILE: '1.0 ID2' - NODE_OPTIONS: --tls-cipher-list="ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384" - NODE_TLS_REJECT_UNAUTHORIZED: 0 - - name: Run oidc-provider (FAPI 1.0 Advanced - Final) + - name: Run oidc-provider (FAPI) run: npx c8 node certification/fapi & - if: ${{ startsWith(matrix.setup.plan, 'fapi1-') }} + if: ${{ startsWith(matrix.setup.plan, 'fapi') }} env: ISSUER: https://172.17.0.1:3000 - PROFILE: '1.0 Final' PORT: 3000 - NODE_OPTIONS: --tls-cipher-list="ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384" + NODE_OPTIONS: --tls-cipher-list="DHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384" NODE_TLS_REJECT_UNAUTHORIZED: 0 + - name: Set Conformance Suite Version + run: | + export VERSION=($(curl --silent "https://gitlab.com/api/v4/projects/4175605/releases" | jq -r '.[0].tag_name')) + echo "VERSION=$VERSION" >> $GITHUB_ENV - name: Load Cached Conformance Suite Build - uses: actions/cache@v2 + uses: actions/cache@v3 id: cache with: path: ./conformance-suite - key: suite-${{ hashFiles('**/test.yml') }} + key: ${{ needs.build-conformance-suite.outputs.cache-key }} + - name: Abort if Conformance Suite isn't cached + if: ${{ steps.cache.outputs.cache-hit != 'true' }} + uses: actions/github-script@v6 + with: + script: | + core.setFailed('Conformance Suite cache hit failed') - name: Run Conformance Suite working-directory: ./conformance-suite run: | @@ -491,16 +467,14 @@ jobs: - name: Adjust configuration files for CI run: | sed -i -e 's/op.panva.cz/172.17.0.1:3000/g' certification/oidc/plan.json - sed -i -e 's/mtls.fapi.panva.cz/172.17.0.1:3000/g' certification/fapi/mtls.json - sed -i -e 's/fapi.panva.cz/172.17.0.1:3000/g' certification/fapi/mtls.json - sed -i -e 's/mtls.fapi.panva.cz/172.17.0.1:3000/g' certification/fapi/pkjwt.json - sed -i -e 's/fapi.panva.cz/172.17.0.1:3000/g' certification/fapi/pkjwt.json + sed -i -e 's/mtls.fapi.panva.cz/172.17.0.1:3000/g' certification/fapi/plan.json + sed -i -e 's/fapi.panva.cz/172.17.0.1:3000/g' certification/fapi/plan.json - name: Run the plan run: npx mocha --delay --timeout 0 --retries 0 certification/runner env: NODE_TLS_REJECT_UNAUTHORIZED: 0 - name: Upload test artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: path: export-*.zip name: failed certification html results @@ -512,48 +486,6 @@ jobs: killall -SIGINT node docker-compose -f docker-compose-dev.yml down sudo rm -rf mongo - - uses: actions/upload-artifact@v2 - with: - if-no-files-found: warn - retention-days: 1 - name: coverage-data - path: coverage/ - - coverage: - runs-on: ubuntu-latest - needs: - - test - - conformance-suite - steps: - - name: Checkout - uses: actions/checkout@master - - name: Setup node - uses: actions/setup-node@v2-beta - with: - node-version: 14 - - name: Store node version variable - id: node - run: | - echo "::set-output name=version::$(node -v)" - - name: Cache node_modules - uses: actions/cache@v2 - id: node_modules - with: - path: node_modules - key: ${{ runner.os }}-node_modules-${{ hashFiles('**/package.json') }}-${{ steps.node.outputs.version }} - - name: Install dependencies - run: npx panva/npm-install-retry - if: ${{ steps.node_modules.outputs.cache-hit != 'true' }} - - uses: actions/download-artifact@v2 - with: - name: coverage-data - path: coverage/ - - run: npx c8 report --reporter=lcov --reporter=text-summary - - uses: actions/upload-artifact@v2 - with: - if-no-files-found: warn - name: coverage-report - path: coverage/lcov-report/ deploy: if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'panva/node-oidc-provider' }} @@ -569,7 +501,7 @@ jobs: - powerful-cove-31049 steps: - name: Checkout - uses: actions/checkout@master + uses: actions/checkout@v3 - uses: actions/setup-ruby@v1 with: ruby-version: 2.x diff --git a/.gitignore b/.gitignore index 8dcd6d12b..9dd3af683 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,6 @@ build/Release node_modules yarn.lock npm-shrinkwrap.json -package-lock.json # Optional npm cache directory .npm diff --git a/.versionrc.json b/.versionrc.json index 5ed66449e..b5007d2e7 100644 --- a/.versionrc.json +++ b/.versionrc.json @@ -9,7 +9,7 @@ }, { "type": "fix", - "section": "Bug Fixes" + "section": "Fixes" }, { "type": "chore", diff --git a/CHANGELOG.md b/CHANGELOG.md index 77db84673..b2df6d379 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,187 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [7.14.3](https://github.com/panva/node-oidc-provider/compare/v7.14.2...v7.14.3) (2022-12-01) + + +### Fixes + +* memory adapter grant references for intended models ([357ced3](https://github.com/panva/node-oidc-provider/commit/357ced3924b71819be144f184861a8c78c27fde6)) + +## [7.14.2](https://github.com/panva/node-oidc-provider/compare/v7.14.1...v7.14.2) (2022-11-30) + + +### Fixes + +* build client symmetric keys from all client signing alg properties ([a26f87d](https://github.com/panva/node-oidc-provider/commit/a26f87d74fcc38e48da2814b9e41e84f7e64e784)) + +## [7.14.1](https://github.com/panva/node-oidc-provider/compare/v7.14.0...v7.14.1) (2022-11-22) + + +### Fixes + +* url encode client_id returned in registration responses ([500dfeb](https://github.com/panva/node-oidc-provider/commit/500dfeb763bd8ea445ad3c79be8e543ba34af275)) + +## [7.14.0](https://github.com/panva/node-oidc-provider/compare/v7.13.0...v7.14.0) (2022-11-09) + + +### Features + +* graduate jwtResponseModes (JARM) feature as stable ([7b878cd](https://github.com/panva/node-oidc-provider/commit/7b878cd195da05024a45c0e347546f0d4ee862c5)) + +## [7.13.0](https://github.com/panva/node-oidc-provider/compare/v7.12.0...v7.13.0) (2022-10-26) + + +### Features + +* enable v18 LTS in package.json ([e423b4d](https://github.com/panva/node-oidc-provider/commit/e423b4d87a6caf490d993e8ae4e2b428504f7d8b)) + +## [7.12.0](https://github.com/panva/node-oidc-provider/compare/v7.11.5...v7.12.0) (2022-09-13) + + +### Features + +* graduate backchannelLogout feature as stable ([617e260](https://github.com/panva/node-oidc-provider/commit/617e2602d0e862fdbdec4d501555097596378d96)) + + +### Fixes + +* ignore instead of throw on unverified post_logout_redirect_uri ([04b1096](https://github.com/panva/node-oidc-provider/commit/04b1096ac1507cab15a680ebde91cc33f6d636eb)) + +## [7.11.5](https://github.com/panva/node-oidc-provider/compare/v7.11.4...v7.11.5) (2022-07-16) + + +### Fixes + +* **PAR:** set additional stored PAR object properties on plain requests ([1be15fa](https://github.com/panva/node-oidc-provider/commit/1be15faed0a704bf5a2c34121d8131e00f23c734)) +* **PAR:** skip stored PAR object alg validation when it's being used ([406caa4](https://github.com/panva/node-oidc-provider/commit/406caa48f0c5929666e43a7b76af9a393c4631d2)) + +## [7.11.4](https://github.com/panva/node-oidc-provider/compare/v7.11.3...v7.11.4) (2022-07-04) + + +### Fixes + +* arrow & static class methods as adapter factories ([#1197](https://github.com/panva/node-oidc-provider/issues/1197)) ([cee552f](https://github.com/panva/node-oidc-provider/commit/cee552f0403863d9a1b8495b93098cc706f7f625)) + +## [7.11.3](https://github.com/panva/node-oidc-provider/compare/v7.11.2...v7.11.3) (2022-06-13) + + +### Fixes + +* httpOptions helper ([#1194](https://github.com/panva/node-oidc-provider/issues/1194)) ([80fe961](https://github.com/panva/node-oidc-provider/commit/80fe9619cd679b5988820e5389b027a4b0a24ca2)) + +## [7.11.2](https://github.com/panva/node-oidc-provider/compare/v7.11.1...v7.11.2) (2022-05-16) + + +### Fixes + +* updated `signed` to `trusted` in the Interaction model ([#1192](https://github.com/panva/node-oidc-provider/issues/1192)) ([eb91aea](https://github.com/panva/node-oidc-provider/commit/eb91aea9c3a50284e71f43ce2fe7f31ebdd83bb5)) + +## [7.11.1](https://github.com/panva/node-oidc-provider/compare/v7.11.0...v7.11.1) (2022-04-25) + + +### Fixes + +* client schema invalidation code not set ([edf22fb](https://github.com/panva/node-oidc-provider/commit/edf22fba1ca8fb21e528c6755f74ae48a9d6704b)) + +## [7.11.0](https://github.com/panva/node-oidc-provider/compare/v7.10.6...v7.11.0) (2022-04-20) + + +### Features + +* allow native app callbacks in client post_logout_redirect_uris ([3fca22b](https://github.com/panva/node-oidc-provider/commit/3fca22bb5dc8dc529c08c596109988a35e110f74)) +* bump backchannelLogout to draft-07 ([95611d9](https://github.com/panva/node-oidc-provider/commit/95611d9978c1f5c10ae9dbe1d49186983a3b01df)) +* graduate issAuthResp feature as stable and enable by default ([e774f60](https://github.com/panva/node-oidc-provider/commit/e774f606ea63271c971aad3d7b5730d0f16c0f52)) + + +### Fixes + +* ensure jwt replay detection takes clockTolerance into account ([f167233](https://github.com/panva/node-oidc-provider/commit/f1672336750b8a23ad0c0fa9f3bb2f91ad61397d)) + +## [7.10.6](https://github.com/panva/node-oidc-provider/compare/v7.10.5...v7.10.6) (2022-01-07) + + +### Fixes + +* substr > slice change in mountPath should have been substring ([adc0d63](https://github.com/panva/node-oidc-provider/commit/adc0d63193c131d7a45f83e4195360a62ef2c522)) + +## [7.10.5](https://github.com/panva/node-oidc-provider/compare/v7.10.4...v7.10.5) (2022-01-04) + + +### Fixes + +* **resourceIndicators:** await the result of useGrantedResource ([#1173](https://github.com/panva/node-oidc-provider/issues/1173)) ([64a8028](https://github.com/panva/node-oidc-provider/commit/64a802884dc4b990847cda4eacb3dba619ae379d)) + +## [7.10.4](https://github.com/panva/node-oidc-provider/compare/v7.10.3...v7.10.4) (2021-12-05) + + +### Fixes + +* add iss to error responses when issAuthResp is enabled ([05ac3a8](https://github.com/panva/node-oidc-provider/commit/05ac3a8cc51f18d33e17982b81f1996e6a327e8c)) + +## [7.10.3](https://github.com/panva/node-oidc-provider/compare/v7.10.2...v7.10.3) (2021-12-04) + + +### Fixes + +* expose invalid_dpop_proof error code and set it to 401 on userinfo ([2628d7e](https://github.com/panva/node-oidc-provider/commit/2628d7e4b81d22a3972e8f82c94b9ec4dd9835d4)) + +## [7.10.2](https://github.com/panva/node-oidc-provider/compare/v7.10.1...v7.10.2) (2021-11-28) + + +### Fixes + +* use paseto configuration from `getResourceServerInfo` ([#1150](https://github.com/panva/node-oidc-provider/issues/1150)) ([02c821d](https://github.com/panva/node-oidc-provider/commit/02c821d7f16c6421d30ffc449366d4d79d951830)) + +## [7.10.1](https://github.com/panva/node-oidc-provider/compare/v7.10.0...v7.10.1) (2021-11-16) + + +### Fixes + +* clearly mark that multiple pop mechanisms are not allowed ([49eed4c](https://github.com/panva/node-oidc-provider/commit/49eed4c20b28ef95e7a1a6315783dd3956b8c84a)) + +## [7.10.0](https://github.com/panva/node-oidc-provider/compare/v7.9.0...v7.10.0) (2021-11-04) + + +### Features + +* duplicate iss and aud as JWE Header Parameters ([b26ea44](https://github.com/panva/node-oidc-provider/commit/b26ea4465b3e45b8e63e69bd08c5de525494dea8)) + +## [7.9.0](https://github.com/panva/node-oidc-provider/compare/v7.8.1...v7.9.0) (2021-10-26) + + +### Features + +* add LTS Gallium as a supported runtime version ([19b4d0d](https://github.com/panva/node-oidc-provider/commit/19b4d0daa4ca1e05acd2b5651545251fe937ff39)) + +## [7.8.1](https://github.com/panva/node-oidc-provider/compare/v7.8.0...v7.8.1) (2021-10-12) + + +### Bug Fixes + +* use insufficient_scope instead of invalid_scope at userinfo_endpoint ([ba8a8f0](https://github.com/panva/node-oidc-provider/commit/ba8a8f0188c9a73a0ab0f8b974bea49feb2a87a6)) + +## [7.8.0](https://github.com/panva/node-oidc-provider/compare/v7.7.0...v7.8.0) (2021-09-15) + + +### Features + +* OAuth 2.0 Pushed Authorization Requests (PAR) is now a stable feature ([3c54d8d](https://github.com/panva/node-oidc-provider/commit/3c54d8ddb85d72fc9432c283b3bea417a895afca)) + +## [7.7.0](https://github.com/panva/node-oidc-provider/compare/v7.6.0...v7.7.0) (2021-09-02) + + +### Features + +* CIBA Core 1.0 is now a stable feature ([cc8bc0d](https://github.com/panva/node-oidc-provider/commit/cc8bc0d651e8111a144cb3eeaf7f61600dd074f2)) + +## [7.6.0](https://github.com/panva/node-oidc-provider/compare/v7.5.4...v7.6.0) (2021-08-03) + + +### Features + +* support v3.local, v3.public, and v4.public paseto access tokens format ([aca5813](https://github.com/panva/node-oidc-provider/commit/aca5813a5b7e669f30894102ad925b1aec3f3467)) + ## [7.5.4](https://github.com/panva/node-oidc-provider/compare/v7.5.3...v7.5.4) (2021-07-21) diff --git a/README.md b/README.md index 0b9542afb..82f303b04 100644 --- a/README.md +++ b/README.md @@ -14,19 +14,15 @@ additional features and standards implemented. ## Implemented specs & features -The following specifications are implemented by oidc-provider. Note that not all features are -enabled by default, check the configuration section on how to enable them. +The following specifications are implemented by oidc-provider: + +_Note that not all features are enabled by default, check the configuration section on how to enable them._ - [RFC6749 - OAuth 2.0][oauth2] & [OpenID Connect Core 1.0][core] - - Authorization (Authorization Code Flow, Implicit Flow, Hybrid Flow) - - UserInfo Endpoint and ID Tokens including Signing and Encryption - - Passing a Request Object by Value or Reference including Signing and Encryption - - Public and Pairwise Subject Identifier Types - - Offline Access / Refresh Token Grant - - Client Credentials Grant - - Client Authentication incl. client_secret_jwt and private_key_jwt methods - [OpenID Connect Discovery 1.0][discovery] - [OpenID Connect Dynamic Client Registration 1.0][registration] and [RFC7591 - OAuth 2.0 Dynamic Client Registration Protocol][oauth2-registration] +- [OpenID Connect RP-Initiated Logout 1.0][rpinitiated-logout] +- [OpenID Connect Back-Channel Logout 1.0][backchannel-logout] - [OAuth 2.0 Form Post Response Mode][form-post] - [RFC7009 - OAuth 2.0 Token Revocation][revocation] - [RFC7592 - OAuth 2.0 Dynamic Client Registration Management Protocol][registration-management] @@ -36,21 +32,24 @@ enabled by default, check the configuration section on how to enable them. - [RFC8628 - OAuth 2.0 Device Authorization Grant (Device Flow)][device-flow] - [RFC8705 - OAuth 2.0 Mutual TLS Client Authentication and Certificate Bound Access Tokens (MTLS)][mtls] - [RFC8707 - OAuth 2.0 Resource Indicators][resource-indicators] -- [JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens][jwt-at] +- [RFC9101 - OAuth 2.0 JWT-Secured Authorization Request (JAR)][jar] +- [RFC9126 - OAuth 2.0 Pushed Authorization Requests (PAR)][par] +- [RFC9207 - OAuth 2.0 Authorization Server Issuer Identifier in Authorization Response][iss-auth-resp] - [Financial-grade API Security Profile 1.0 - Part 2: Advanced (FAPI)][fapi] -- [Financial-grade API - Part 2: Read and Write API Security Profile (FAPI) - Implementer's Draft 02][fapi-id2] +- [JWT Secured Authorization Response Mode for OAuth 2.0 (JARM)][jarm] +- [OpenID Connect Client Initiated Backchannel Authentication Flow - Core 1.0 (CIBA)][ciba] + +Supported Access Token formats: + +- Opaque +- [JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens][jwt-at] +- [Platform-Agnostic Security Tokens (PASETO)][paseto-at] + +The following draft specifications are implemented by oidc-provider: -The following draft specifications are implemented by oidc-provider. - [JWT Response for OAuth Token Introspection - draft 10][jwt-introspection] -- [JWT Secured Authorization Response Mode for OAuth 2.0 (JARM) - Implementer's Draft 01][jarm] - [Financial-grade API: Client Initiated Backchannel Authentication Profile (FAPI-CIBA) - Implementer's Draft 01][fapi-ciba] -- [OAuth 2.0 Authorization Server Issuer Identifier in Authorization Response - draft 01][iss-auth-resp] - [OAuth 2.0 Demonstration of Proof-of-Possession at the Application Layer (DPoP) - draft 03][dpop] -- [OAuth 2.0 JWT Secured Authorization Request (JAR) - draft 33][jar] -- [OAuth 2.0 Pushed Authorization Requests (PAR) - draft 08][par] -- [OpenID Connect Back-Channel Logout 1.0 - draft 06][backchannel-logout] -- [OpenID Connect Client Initiated Backchannel Authentication Flow - Core 1.0 (CIBA) - draft-03][ciba] -- [OpenID Connect RP-Initiated Logout 1.0 - draft 01][rpinitiated-logout] Updates to draft specification versions are released as MINOR library versions, if you utilize these specification implementations consider using the tilde `~` operator in your @@ -66,16 +65,16 @@ conforms to the following profiles of the OpenID Connect™ protocol - Basic OP, Implicit OP, Hybrid OP, Config OP, Dynamic OP, Form Post OP, 3rd Party-Init OP - Back-Channel OP, RP-Initiated OP - FAPI 1.0 Advanced Final (w/ Private Key JWT, MTLS, JARM, PAR) -- FAPI 1.0 Second Implementer’s Draft (w/ Private Key JWT, MTLS, PAR) +- FAPI 1.0 Second Implementer's Draft (w/ Private Key JWT, MTLS, PAR) - FAPI-CIBA OP (w/ Private Key JWT, MTLS, Ping mode, Poll mode) ## Sponsor -[auth0-logo][sponsor-auth0] If you want to quickly add OpenID Connect authentication to Node.js apps, feel free to check out Auth0's Node.js SDK and free plan at [auth0.com/developers][sponsor-auth0].

+[auth0-logo][sponsor-auth0] If you want to quickly add OpenID Connect authentication to Node.js apps, feel free to check out Auth0's Node.js SDK and free plan. [Create an Auth0 account; it's free!][sponsor-auth0]

## Support -If you or your business use oidc-provider, or you need help using/upgrading the module, please consider becoming a [sponsor][support-sponsor] so I can continue maintaining it and adding new features carefree. The only way to guarantee you get feedback from the author & sole maintainer of this module is to support the package through GitHub Sponsors. I make it a best effort to try and answer newcomers regardless of being a supporter or not, but if you're asking your n-th question and don't get an answer it's because I'm out of handouts and spare time to give. +If you or your business use oidc-provider, or you need help using/upgrading the module, please consider becoming a [sponsor][support-sponsor] so I can continue maintaining it and adding new features carefree. The only way to guarantee you get feedback from the author & sole maintainer of this module is to support the package through GitHub Sponsors. ## Get started You may check the [example folder](/example) or follow a [step by step example][example-repo] to see @@ -92,25 +91,18 @@ various ways to fit a variety of uses. See the [documentation](/docs/README.md). ```js const { Provider } = require('oidc-provider'); const configuration = { - // ... see available options /docs + // ... see /docs for available configuration clients: [{ client_id: 'foo', client_secret: 'bar', redirect_uris: ['http://lvh.me:8080/cb'], - // + other client properties + // ... other client properties }], }; const oidc = new Provider('http://localhost:3000', configuration); -// express/nodejs style application callback (req, res, next) for use with express apps, see /examples/express.js -oidc.callback() - -// koa application for use with koa apps, see /examples/koa.js -oidc.app - -// or just expose a server standalone, see /examples/standalone.js -const server = oidc.listen(3000, () => { +oidc.listen(3000, () => { console.log('oidc-provider listening on port 3000, check http://localhost:3000/.well-known/openid-configuration'); }); ``` @@ -121,10 +113,8 @@ Collection of useful configurations use cases are available over at [recipes](/r ## Events -Your oidc-provider instance is an event emitter, using event handlers you can hook into the various -actions and i.e. emit metrics or that react to specific triggers. In some scenarios you can even -change the defined behavior. -See the list of available emitted [event names](/docs/events.md) and their description. +oidc-provider instances are event emitters, using event handlers you can hook into the various +actions and i.e. emit metrics that react to specific triggers. See the list of available emitted [event names](/docs/events.md) and their description. [npm-url]: https://www.npmjs.com/package/oidc-provider @@ -132,32 +122,32 @@ See the list of available emitted [event names](/docs/events.md) and their descr [openid-connect]: https://openid.net/connect/ [core]: https://openid.net/specs/openid-connect-core-1_0.html [discovery]: https://openid.net/specs/openid-connect-discovery-1_0.html -[oauth2-registration]: https://tools.ietf.org/html/rfc7591 +[oauth2-registration]: https://www.rfc-editor.org/rfc/rfc7591.html [registration]: https://openid.net/specs/openid-connect-registration-1_0.html [form-post]: https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html -[oauth2]: https://tools.ietf.org/html/rfc6749 -[oauth2-bearer]: https://tools.ietf.org/html/rfc6750 -[revocation]: https://tools.ietf.org/html/rfc7009 -[introspection]: https://tools.ietf.org/html/rfc7662 -[pkce]: https://tools.ietf.org/html/rfc7636 +[oauth2]: https://www.rfc-editor.org/rfc/rfc6749.html +[oauth2-bearer]: https://www.rfc-editor.org/rfc/rfc6750.html +[revocation]: https://www.rfc-editor.org/rfc/rfc7009.html +[introspection]: https://www.rfc-editor.org/rfc/rfc7662.html +[pkce]: https://www.rfc-editor.org/rfc/rfc7636.html [example-repo]: https://github.com/panva/node-oidc-provider-example -[backchannel-logout]: https://openid.net/specs/openid-connect-backchannel-1_0-06.html -[registration-management]: https://tools.ietf.org/html/rfc7592 -[oauth-native-apps]: https://tools.ietf.org/html/rfc8252 -[jar]: https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-33 -[device-flow]: https://tools.ietf.org/html/rfc8628 +[backchannel-logout]: https://openid.net/specs/openid-connect-backchannel-1_0-final.html +[registration-management]: https://www.rfc-editor.org/rfc/rfc7592.html +[oauth-native-apps]: https://www.rfc-editor.org/rfc/rfc8252.html +[jar]: https://www.rfc-editor.org/rfc/rfc9101.html +[device-flow]: https://www.rfc-editor.org/rfc/rfc8628.html [jwt-introspection]: https://tools.ietf.org/html/draft-ietf-oauth-jwt-introspection-response-10 -[sponsor-auth0]: https://auth0.com/developers?utm_source=GHsponsor&utm_medium=GHsponsor&utm_campaign=oidc-provider&utm_content=auth -[mtls]: https://tools.ietf.org/html/rfc8705 +[sponsor-auth0]: https://a0.to/try-auth0 +[mtls]: https://www.rfc-editor.org/rfc/rfc8705.html [dpop]: https://tools.ietf.org/html/draft-ietf-oauth-dpop-03 -[resource-indicators]: https://tools.ietf.org/html/rfc8707 -[jarm]: https://openid.net/specs/openid-financial-api-jarm-ID1.html -[jwt-at]: https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-11 +[resource-indicators]: https://www.rfc-editor.org/rfc/rfc8707.html +[jarm]: https://openid.net/specs/oauth-v2-jarm.html +[jwt-at]: https://www.rfc-editor.org/rfc/rfc9068.html +[paseto-at]: https://paseto.io [support-sponsor]: https://github.com/sponsors/panva -[par]: https://tools.ietf.org/html/draft-ietf-oauth-par-08 -[rpinitiated-logout]: https://openid.net/specs/openid-connect-rpinitiated-1_0-01.html -[iss-auth-resp]: https://tools.ietf.org/html/draft-ietf-oauth-iss-auth-resp-01 -[fapi-id2]: https://openid.net/specs/openid-financial-api-part-2-ID2.html +[par]: https://www.rfc-editor.org/rfc/rfc9126.html +[rpinitiated-logout]: https://openid.net/specs/openid-connect-rpinitiated-1_0-final.html +[iss-auth-resp]: https://www.rfc-editor.org/rfc/rfc9207.html [fapi]: https://openid.net/specs/openid-financial-api-part-2-1_0.html -[ciba]: https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0-03.html +[ciba]: https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0-final.html [fapi-ciba]: https://openid.net/specs/openid-financial-api-ciba-ID1.html diff --git a/certification/fapi/index.js b/certification/fapi/index.js index 4845f0cbd..7f4cb401d 100644 --- a/certification/fapi/index.js +++ b/certification/fapi/index.js @@ -2,15 +2,17 @@ const { readFileSync } = require('fs'); const path = require('path'); -const { randomBytes } = require('crypto'); +const { randomBytes, randomUUID } = require('crypto'); const https = require('https'); const { promisify } = require('util'); +const { URL } = require('url'); const jose = require('jose2'); const helmet = require('helmet'); -const pem = require('https-pem'); +const selfsigned = require('selfsigned').generate(); const { Provider, errors } = require('../../lib'); // require('oidc-provider'); +const MemoryAdapter = require('../../lib/adapters/memory_adapter'); const OFFICIAL_CERTIFICATION = 'https://www.certification.openid.net'; const { PORT = 3000, ISSUER = `http://localhost:${PORT}`, SUITE_BASE_URL = OFFICIAL_CERTIFICATION } = process.env; @@ -20,30 +22,125 @@ const tokenEndpointAuthMethods = ['private_key_jwt', 'self_signed_tls_client_aut const normalize = (cert) => cert.toString().replace(/(?:-----(?:BEGIN|END) CERTIFICATE-----|\s)/g, ''); -const JWK_PKJWTONE = jose.JWK.asKey(readFileSync(path.join(__dirname, 'pkjwtone.key')), { alg: 'PS256', use: 'sig' }).toJWK(); -const JWK_PKJWTTWO = jose.JWK.asKey(readFileSync(path.join(__dirname, 'pkjwttwo.key')), { alg: 'PS256', use: 'sig' }).toJWK(); -const JWK_MTLSONE = jose.JWK.asKey(readFileSync(path.join(__dirname, 'mtlsone.key')), { x5c: [normalize(readFileSync(path.join(__dirname, 'mtlsone.crt')))], alg: 'PS256', use: 'sig' }).toJWK(); -const JWK_MTLSTWO = jose.JWK.asKey(readFileSync(path.join(__dirname, 'mtlstwo.key')), { x5c: [normalize(readFileSync(path.join(__dirname, 'mtlstwo.crt')))], alg: 'PS256', use: 'sig' }).toJWK(); - -const aliases = [ - 'oidc-provider', - 'oidc-provider-by_value-mtls-plain_fapi-jarm', - 'oidc-provider-by_value-mtls-plain_fapi-plain_response', - 'oidc-provider-by_value-private_key_jwt-plain_fapi-jarm', - 'oidc-provider-by_value-private_key_jwt-plain_fapi-plain_response', - 'oidc-provider-pushed-mtls-plain_fapi-jarm', - 'oidc-provider-pushed-mtls-plain_fapi-plain_response', - 'oidc-provider-pushed-private_key_jwt-plain_fapi-jarm', - 'oidc-provider-pushed-private_key_jwt-plain_fapi-plain_response', -]; - -const REDIRECT_URIS = aliases.map((alias) => [`${SUITE_BASE_URL}/test/a/${alias}/callback`, `${SUITE_BASE_URL}/test/a/${alias}/callback?dummy1=lorem&dummy2=ipsum`]).flat(Infinity); +const JWK_ONE = jose.JWK.asKey(readFileSync(path.join(__dirname, 'one.key')), { x5c: [normalize(readFileSync(path.join(__dirname, 'one.crt')))], alg: 'PS256', use: 'sig' }).toJWK(); +const JWK_TWO = jose.JWK.asKey(readFileSync(path.join(__dirname, 'two.key')), { x5c: [normalize(readFileSync(path.join(__dirname, 'two.crt')))], alg: 'PS256', use: 'sig' }).toJWK(); + +function jwk(metadata, key) { + return { + ...metadata, + jwks: { keys: [key] }, + }; +} + +function pkjwt(metadata, key) { + return jwk({ + ...metadata, + token_endpoint_auth_method: 'private_key_jwt', + }, key); +} + +function mtlsAuth(metadata, key) { + return jwk({ + ...metadata, + token_endpoint_auth_method: 'self_signed_tls_client_auth', + }, key); +} + +function mtlsPoP(metadata) { + return { + ...metadata, + tls_client_certificate_bound_access_tokens: true, + }; +} + +function jar(metadata) { + return { + ...metadata, + require_signed_request_object: true, + }; +} + +function fapi1(metadata) { + return mtlsPoP(jar({ + ...metadata, + default_acr_values: ['urn:mace:incommon:iap:silver'], + grant_types: ['implicit', 'authorization_code', 'refresh_token'], + response_types: ['code', 'code id_token'], + redirect_uris: ['https://rp.example.com/cb'], + })); +} + +const adapter = (name) => { + if (name === 'Client') { + const memory = new MemoryAdapter(name); + const orig = MemoryAdapter.prototype.find; + memory.find = async function find(id) { + const [version, ...rest] = id.split('-'); + + let metadata = { + cacheBuster: randomUUID(), + }; + + if (version === '1.0') { + const [tag, clientAuth, num, ...empty] = rest; + if (empty.length !== 0) { + return orig.call(this, id); + } + metadata = fapi1(metadata); + + switch (tag) { + case 'final': + metadata.profile = '1.0 Final'; + break; + case 'id2': + metadata.profile = '1.0 ID2'; + break; + default: + return orig.call(this, id); + } + + let key; + switch (num) { + case 'one': + key = JWK_ONE; + break; + case 'two': + key = JWK_TWO; + break; + default: + return orig.call(this, id); + } + + switch (clientAuth) { + case 'mtls': + metadata = mtlsAuth(metadata, key); + break; + case 'pkjwt': + metadata = pkjwt(metadata, key); + break; + default: + return orig.call(this, id); + } + + metadata.client_id = id; + return metadata; + } + + return orig.call(this, id); + }; + + return memory; + } + + return new MemoryAdapter(name); +}; const fapi = new Provider(ISSUER, { acrValues: ['urn:mace:incommon:iap:silver'], routes: { userinfo: '/accounts', }, + adapter, jwks: { keys: [ { @@ -62,49 +159,10 @@ const fapi = new Provider(ISSUER, { ], }, scopes: ['openid', 'offline_access'], - clients: [ - { - client_id: 'pkjwt-one', - redirect_uris: REDIRECT_URIS, - token_endpoint_auth_method: 'private_key_jwt', - jwks: { - keys: [JWK_PKJWTONE], - }, - }, - { - client_id: 'pkjwt-two', - redirect_uris: REDIRECT_URIS, - token_endpoint_auth_method: 'private_key_jwt', - jwks: { - keys: [JWK_PKJWTTWO], - }, - }, - { - client_id: 'mtls-one', - redirect_uris: REDIRECT_URIS, - token_endpoint_auth_method: 'self_signed_tls_client_auth', - jwks: { - keys: [JWK_MTLSONE], - }, - }, - { - client_id: 'mtls-two', - redirect_uris: REDIRECT_URIS, - token_endpoint_auth_method: 'self_signed_tls_client_auth', - jwks: { - keys: [JWK_MTLSTWO], - }, - }, - ], clientDefaults: { - default_acr_values: ['urn:mace:incommon:iap:silver'], authorization_signed_response_alg: 'PS256', - grant_types: ['implicit', 'authorization_code', 'refresh_token'], - response_types: ['code', 'code id_token'], id_token_signed_response_alg: 'PS256', request_object_signing_alg: 'PS256', - tls_client_certificate_bound_access_tokens: true, - token_endpoint_auth_method: 'private_key_jwt', }, clockTolerance: 5, features: { @@ -122,7 +180,16 @@ const fapi = new Provider(ISSUER, { registrationManagement: { enabled: true }, fapi: { enabled: true, - profile: process.env.PROFILE ? process.env.PROFILE : '1.0 Final', + profile(ctx, client) { + if (!client?.profile) { + if (client.grantTypes.includes('urn:openid:params:grant-type:ciba')) { + return '1.0 Final'; + } + throw new Error('could not determine FAPI profile'); + } + + return client.profile; + }, }, mTLS: { enabled: true, @@ -130,7 +197,7 @@ const fapi = new Provider(ISSUER, { selfSignedTlsClientAuth: true, getCertificate(ctx) { if (SUITE_BASE_URL === OFFICIAL_CERTIFICATION) { - return unescape(ctx.get('x-ssl-client-cert').replace(/\+/g, ' ')); + return ctx.get('client-certificate'); } const peerCertificate = ctx.socket.getPeerCertificate(); @@ -145,7 +212,7 @@ const fapi = new Provider(ISSUER, { requestObjects: { request: true, requestUri: false, - requireSignedRequestObject: true, + requireSignedRequestObject: false, mode: 'strict', }, }, @@ -158,6 +225,9 @@ const fapi = new Provider(ISSUER, { tokenEndpointAuthSigningAlgValues: ALGS, userinfoSigningAlgValues: ALGS, }, + extraClientMetadata: { + properties: ['profile'], + }, pkce: { required: () => false, }, @@ -172,6 +242,19 @@ Object.defineProperty(fapi.OIDCContext.prototype, 'clientJwtAuthExpectedAudience }, }); +Object.defineProperty(fapi.Client.prototype, 'redirectUriAllowed', { + value(url) { + let parsed; + try { + parsed = new URL(url); + } catch (err) { + return false; + } + + return parsed.origin === SUITE_BASE_URL && parsed.pathname.endsWith('/callback') && (parsed.search === '' || parsed.search === '?dummy1=lorem&dummy2=ipsum'); + }, +}); + const orig = fapi.interactionResult; fapi.interactionResult = function patchedInteractionResult(...args) { if (args[2] && args[2].login) { @@ -183,7 +266,14 @@ fapi.interactionResult = function patchedInteractionResult(...args) { function uuid(e){return e?(e^randomBytes(1)[0]%16>>e/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,uuid)} // eslint-disable-line -const pHelmet = promisify(helmet()); +const directives = helmet.contentSecurityPolicy.getDefaultDirectives(); +delete directives['form-action']; +const pHelmet = promisify(helmet({ + contentSecurityPolicy: { + useDefaults: false, + directives, + }, +})); fapi.use(async (ctx, next) => { if (ctx.path === '/ciba-sim') { @@ -270,7 +360,8 @@ if (SUITE_BASE_URL === OFFICIAL_CERTIFICATION) { const server = https.createServer({ requestCert: true, rejectUnauthorized: false, - ...pem, + key: selfsigned.private, + cert: selfsigned.cert, }, fapi.callback()); server.listen(PORT, () => { diff --git a/certification/fapi/mtlsone.crt b/certification/fapi/one.crt similarity index 100% rename from certification/fapi/mtlsone.crt rename to certification/fapi/one.crt diff --git a/certification/fapi/mtlsone.key b/certification/fapi/one.key similarity index 100% rename from certification/fapi/mtlsone.key rename to certification/fapi/one.key diff --git a/certification/fapi/pkjwt.json b/certification/fapi/pkjwt.json deleted file mode 100644 index 18709753b..000000000 --- a/certification/fapi/pkjwt.json +++ /dev/null @@ -1,220 +0,0 @@ -{ - "alias": "oidc-provider", - "automated_ciba_approval_url": "https://fapi.panva.cz/ciba-sim?authReqId={auth_req_id}&action={action}", - "server": { - "discoveryUrl": "https://fapi.panva.cz/.well-known/openid-configuration" - }, - "client": { - "client_id": "pkjwt-one", - "client_name": "pkjwt-one", - "scope": "openid offline_access", - "jwks": { - "keys": [ - { - "e": "AQAB", - "n": "sUQ6a7yX-qCAIgqYl_pzn2yK5RsPb5zjxMG1v2bvlvf6l6LyvJkxEr4dWLAbn9WAV72GuyMkvWfVi13fu4cYl2vdkIFBt0JGT40QxkMUp0izHs4RiLK1GTrwJ2qX7H67EaNNWFeE9Yqh3sIRyQgHqQf6L9rZFWBSED-M3OaiwH-zdwrMzjQH6wCEjmuyTFiNLO2QI6Yr7dDl1rPjWvN9d8pHHWxkRMAnQrL5_mfvOD_j1Tr5blXYTMHHpThOHVM2Ibe4_5YDmPaRXFMgQrPjz6mlUa9d1EL7CuxLd19S3A_6XEMB2juo0RRCfaHK7ZORPJKa36qrVZVhXK3Geuqorw", - "d": "dxzWeLBYGwOgNb-S-4RCDxz7U6lUPPZaIkrbmkpLsdDdZOkMXGg_jk2LIJ3tYgAvZkWm87ZQqKjN2ADzJmpHvu-vCLuh8ccpwaiTXfWTOjjii0-Cfq0-fT6aQpIglbwubVKi1Tqxz-AglrMnCkNICm-e0GsotXFskxhwybp8IAZP__Up1pg-G9Dg_Timtepw55HjO4xDhzY70zV2NqSDEIvKOleyIZj4JP5kCkwP4_FJw_KynXwlxKvCshtFC3U2IEWWUaUQmM8Yy1Hz2x3TqImLQTWs3EMm6oRuhS0Y4tg9VlzJqnetdd6Ulh-DFzSB37KnBZS1qvnGGG4Cri9IkQ", - "p": "5SLM2g4Uv3Q1-PGqwKPQ8h8Onp674FTxxYAHCh8mivgCGx7uIjVaOxCKvCimi8NCgtON0a1QdGY-BT3NsewJUvaniWyb5BZo-kpdkSzXCvQpWuWT_iSorgEgl4anJ59JZH_QW7wtjRnF8jWnw-_nkNv4HIIVd7fdKKCkpGi1Drk", - "q": "xgyjgfZdlfpne27vdlxi5VGmNnBnLRAe_a7Wgo6JdmKPMPa1qugxVM5tUhoYjUuUpHxi8gDSxb0-N_kIqTu7zp2Ly9iB8wQIyyYmdxN7J_B5bSn5rfTcu_Uz-EuYVEGfj0hk5_aNQc0y02Di1L4QrnMNRGBo3jWCCRZrjqyHfqc", - "dp": "tc9sHeUoX1V1cedHpn0VUNiFwCSRTIn6IMzaSRS4f3IUMbLUHv6Ybt9MRco3hBRV1PrJv8K2YPWzZnNIoFF6gILIIsmz1EJX36lcHtIme0GLAt3BFNm_ofmxA6pLPawtDvo_uFpTBm-Z2frq-BSGeDGh5_Tr1cdlS1RT70RJzbk", - "dq": "FXlVWUgfSZ3HDqkuqcTGrFq4DPsPFOHEmnkUpT9TRFTXddWqSQe4IZvoWpidxORHD7a0-8x_DhXA40zLVZ42dOa8O7QUEweC9JQEY7DnD6ORZvbALc55CKBDrE52C9y5sk2FM2mWU2YudqDwt2SMZn3vGFTjygQ_P0EBFI08e80", - "qi": "nmJaonUO_d62824V6YmWuEX7imXdgHKRi-tY4IUDJbrm7lKEfcn_xazqilECh1xm7O8b4bj0th3JrRcs1Al0sWP1FwVHjzzmg5oqq26PvHjmtVIHn3cXGT6AmY8-eUPkYgPBc61Ej58Usazm1iuRIe-wNIBeL244kFTQK7zJfnE", - "kty": "RSA", - "kid": "EUsMLFwXz5zMhkJoo1lcnIM2pApLc3kc_2WV8YKYC3M", - "alg": "PS256", - "use": "sig" - } - ] - }, - "hint_type": "login_hint", - "hint_value": "panva" - }, - "mtls": { - "cert": "-----BEGIN CERTIFICATE-----\nMIIC4DCCAcgCCQDuBF1vmG5mlDANBgkqhkiG9w0BAQsFADAyMQswCQYDVQQGEwJD\nWjEPMA0GA1UEBwwGUHJhZ3VlMRIwEAYDVQQDDAlwa210bHNvbmUwHhcNMTkwNjE4\nMTIzMTA2WhcNMjAwNjE3MTIzMTA2WjAyMQswCQYDVQQGEwJDWjEPMA0GA1UEBwwG\nUHJhZ3VlMRIwEAYDVQQDDAlwa210bHNvbmUwggEiMA0GCSqGSIb3DQEBAQUAA4IB\nDwAwggEKAoIBAQDEEnW885Hp+2Q7l+KCtKPOwfPIVOLKshgygWIAXC8z5TKnA1N9\nqbB2BvpDpWUKdXrYuBzWcNH/PHwrJvX42AHGeXCZJDSXzuRH934/fjMQHTFJquoP\n4rziUlRJfT+pwJcuvgxgGLI5xgzNqD7gZZp/9LVm5OdXU1poQviUel+hwV5eiT1r\n1fOe5LOiXkLwp3kBLlqGrtRPFIIa+20qkvnFh5ZcnRmOmm2vcAnI7OaNc2rSLHVb\nvkFuY8mMEx8rtthq0dQyyy1Ucudi3cLCI2x8Px0qQFUqWH4LgNaj7VZjlU1NPE8L\njsSPLasZsMsn0wt22fo+v5bJbaZ3N3QQqM0VAgMBAAEwDQYJKoZIhvcNAQELBQAD\nggEBAFGKYDieCWZ63Fx9jMhtlPlHUgkR6bmKqGwvZuVAe9Zz+sHvbVtTk/4AEOjS\nozksxf070O1PnK3zY0SuZynhKJnTaFouN45iMnnNQS6XMKd9Tm5WpSRbxfaOeuIZ\nybvOmNy0nuxkvqcE5fXIyr9bDCO9WEArQIQqjGJ93zKJpV2nT9Q7heTK430z7Hp3\n+XxwGXoKsLW/jebr3ryWTMEv8ouEbXeCz2OH6Oup8UIwXDyjYxwhwS5FAcRQdh4K\nnhHOLGYVAuVR3wPewtrTioYznFdfwtDHGd9fZVxrXPlVqCksj0CTnPf7UgXtjm2h\nTfkwHHtW2BegWR/q3+q9gs7uehc=\n-----END CERTIFICATE-----\n", - "key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDEEnW885Hp+2Q7\nl+KCtKPOwfPIVOLKshgygWIAXC8z5TKnA1N9qbB2BvpDpWUKdXrYuBzWcNH/PHwr\nJvX42AHGeXCZJDSXzuRH934/fjMQHTFJquoP4rziUlRJfT+pwJcuvgxgGLI5xgzN\nqD7gZZp/9LVm5OdXU1poQviUel+hwV5eiT1r1fOe5LOiXkLwp3kBLlqGrtRPFIIa\n+20qkvnFh5ZcnRmOmm2vcAnI7OaNc2rSLHVbvkFuY8mMEx8rtthq0dQyyy1Ucudi\n3cLCI2x8Px0qQFUqWH4LgNaj7VZjlU1NPE8LjsSPLasZsMsn0wt22fo+v5bJbaZ3\nN3QQqM0VAgMBAAECggEBAKrgF6Hrd9+9yhWxgaM9gIDhQO73I4tY+IOThHAh5rVI\nawNof6vFZdcGr7aiftNFnSEgG2m7exgAg4or8zPCNJHfJgUgq4Eduo8JnwoAlsnV\nVy4HeOMNTGXFMFW3hPMQt/DxieF5xGFbO69DkECJ68LV5f3dQcw2BVVWAEON/qf9\nUhgEnx79OdiCYzyoHjxaHoXk9cVTUtXwmU0lphRFT14W2Py0KZ5vA8JEefoZl/qE\no42iR0KB4eDj/LiduWAMKDLN/u0Dmq3WxLvw7LHnlqENTEY56Lw1sbMQlmvl0zCD\n68BeMz+Mvlm7VRSCxR+3uX6Nli3eb2EsYziZIlt00iUCgYEA4ZNvp0u6vXQAf7IG\nESuhu3u4cD6K8iJ6BT+O64yHJzc17e9lt+p89GasTtkgeEH/lr+XxScVBYndi/8v\naqsA47bkIoWVvcXq7C4jkv0bPelpaI/KPGfoZC1etR102+dS1ZufxzwSo64drw+H\nPuwaIkXvmpK4MBI9jEYoOHh9KR8CgYEA3oRU/iCWvKOGHlR9BmcBysVGmkvBwj8X\nLTJGfxJkm3M9utLSg0uNo7vW0Zm88KdzdeaRBt6ltZAG3hBL0mHNwC0hYRv5fzO7\nyczWFLPXbUNVqC5OLI+wTL7+ikx040lL9IRRtgub47+9IvxxaGaIRzsFwa+15D7T\nCE7K8BydH0sCgYEAivnkC2VL2tcyS3op5MBF95Vk77qIrl9xX/RloFfHGPEaB8q7\nl5EfhRAQzs9VAuJejsjhv7SxbeUfmtYQp55NgP44FdDJjc73SqWugyvvcbhxmdsl\nFQxLkBSnydwpGCav0Sz9RqmLLk7iuO1PPQQHoeAGm+wTEILcaqT6uLf7HK8CgYBO\nlloWLphOI0q454oIetTNMoNO9zaFThb3ZWw0cOCLblX853xl1oc9rpeeCzgJnnpO\nx5Gs5XGNAEMMpqDAur4aA1Zon6KsZC8MhIWPZjzNYByee0wsvMq9MC9h1MLrivWC\ndEEPlGYIN62q75F2F9BFp/jOgSoyZGXP51QRHWn4pQKBgCEy7GsS3sAj3GiTmCkV\nvR3gKSlSKmJULmI+8EoT6tzYhMvyqoCHPtll2DHzBeqA6Il2DKz+gFbKln2+LrPU\nrb2rgK9e8qdZH9X36Ws8u9YA/VasGQRUFIAcFcNWoBBX79nBQ/89zrPubaN2Rh9B\nKuUEGLKAzDf1JIzseUr8jdvw\n-----END PRIVATE KEY-----\n" - }, - "client2": { - "client_id": "pkjwt-two", - "client_name": "pkjwt-two", - "scope": "openid offline_access", - "jwks": { - "keys": [ - { - "e": "AQAB", - "n": "5xcUf9SqYSEQHDyQft6iabfz5WvuZyjFLTj9x_R8ARdBDyVE42vkWnf6jOZoeMZ_WjtYGLF0nwnuEVBoCVxP4Nu1UUQq8OTigmk2f4IyPT-79pvW9b7EGQDdTsBIrrb2AKktwDJe16uK64kDNO0Ay3U_nShIPgMVbmGd_K6__OwwhhrLwKv_OSbvU8ZlPI24jy1Yxfq175F_1ZQBRjm0jAMf6PBt8cgxQclgbpP-7OBUQHBzpfudD1U9W6V8hqKKjDldlI9Zg6xBa8MpC6JuQdgwkcugWdwyG69p2kq_pyl735C8yj9Bif7Vok1d4E81cSh8Jy1wGNfAmlz8ncJSRw", - "d": "ttJDnX4Z7Q3DORORHU31H74wbmidC2_tzoEwBWkeUZ6cEediQKmy1v_kcGQ4cpRXXINmhuKdaNTqSzhZlk45w6MOJ2TOtBXgPVwPiYDvThWGFuvjfDeX3FAaxZrJ5a9Jn-w4Db3L2sQT-5NBvjViU5RG54Ze4PB_m_wOiUGXEfgonv64APPBkodIz_26lJ-r4447tcdMkDw4p6xTP-y33sPB4ZOhQ5ezE4FzQlpq0K98ysKLwo8wvhQ8stxqL46avBnflgDhTsb-1I54aU3ROjThU5tkeAkudzg4lFKdd4WmVneVMD-FJ2wrkjwztqHSBCP8G9IAXBdXRxRzdJIOAQ", - "p": "_WOUrDYt1uay8XtnaNgutctzdI6XJaf5XrGv6wE_ZoVbqEKWTnBbVfqbwkWf58CeH32tt3hUjBFicG-XAlYI8TW5Lf6H78Fmt2y3fdowLzzWLwNDoLWo1_rOGxneL-vYM1MekZyUN-uDARdEeOL9yRPFKOZm7eWSUc_Sdlu9yuc", - "q": "6XitRdF0tFu7O7S1rQZZjqhrza53f-Ox1vCYY_Hy4Tm_1fMc7GV0VCHoQwO3ZJPlPEE0JahnfpQkhHGj67GGO40lZYEXdBwNxtktp1zdmEokbsWmacz57SVPg4pzgS9kketf-wdUbT5VgBmNWS9MPt7rKiyFbmuPcHU99fWEsaE", - "dp": "e_5s1FC24cCGtFCU6-NOCDwExXa5U_38s2_0C-XSZpK_pXjgIIYuy8YUzl5Pv5KsTfCsP2msxdYD-80_ci8ztQV7FpzFXHehkgSrTfSlO5hjnyHTyCLc-sOKdAyWg5C_fW4hOVQL28ltk-0U3qsFUY5RHpCQsb1zeoFeFfkSyOU", - "dq": "2tyhsh2UBa8oaeQRm02kjrMbvAidRWoxwIhykt6xDKmSSAJLTuAcmPHgRVIqjUKHVmDZfaPMwUAmq3HMdJpKd3DtaaYGUnYqBAp7XbUUljqKxLzML8pTUBf13h3gAW5oHNJFe5F3d6FDjX5mnwBTvWxDj5mEy-pQ4N9HYlbyOYE", - "qi": "rbxxLBMPxBVpoyNfpjYtXEuem0HHvemHiGklhCbJO_N3vRu8lEarlZ_IPLrRmq7he0cNHcd4j_yhge1-0RR0LzJ4l_Wg-B1Jc0fKJrSItp8pmjmaVHbp_ToYqVlJh_AfU5tru86zSCvVycV79BvkvLl6IusDAuRK8eD1h7dU59g", - "kty": "RSA", - "kid": "NriyGITqpt6QdRBXz6k_qjup6vO_81Namq05CX4hij0", - "alg": "PS256", - "use": "sig" - } - ] - }, - "acr_value": "urn:mace:incommon:iap:silver" - }, - "mtls2": { - "cert": "-----BEGIN CERTIFICATE-----\nMIIC4DCCAcgCCQDO8JBSH914NDANBgkqhkiG9w0BAQsFADAyMQswCQYDVQQGEwJD\nWjEPMA0GA1UEBwwGUHJhZ3VlMRIwEAYDVQQDDAlwa210bHN0d28wHhcNMTkwNjE4\nMTIzMjAxWhcNMjAwNjE3MTIzMjAxWjAyMQswCQYDVQQGEwJDWjEPMA0GA1UEBwwG\nUHJhZ3VlMRIwEAYDVQQDDAlwa210bHN0d28wggEiMA0GCSqGSIb3DQEBAQUAA4IB\nDwAwggEKAoIBAQDhqVAaMsvnCETzDtKwfKxZC1jwIOhIyUp8xp+2oN+pJwtqP0Up\nkLlTV7MD94HZSL3n3f9hsG6appRQGGAJ2ThOw1N9zlAr7Sk9YH6Gtu3bYSDvS6wa\nKjVoxGrrmLfyuoEbv3PDqMWuOjE3MT/G1nwUBgIEKYAr8hizY8dUE0Z2qWvKFZJj\n6etjCXEppjXuwlSusHWw/tj/ePMMxMAJMPPhzJeh6AL7iUKBisJysPuaWrS9ntdP\nxv9PS40sv6cZT4woxmE6tpTCkAxabXqA25SgJOyKOjnvg+BPNlrucLqHw3ErWrxY\nTL99cHqhexO6K4FaspW3+1kuWd3fY4Cm+zkTAgMBAAEwDQYJKoZIhvcNAQELBQAD\nggEBALsB6MGWke5vS1TB3Z+NJkC29bEIb3XGC9WaxRovH0jqaaua2AfAF7VZzUyW\nS/+r6hvWOtqUVy7YF1ThnEJXuXJG9ra2B2+F5RYNCtrVj6Bi+zDTSJ4IvQfrF0XB\nKwwOdRu7VJpAxvweA/3woKl6Cjfy20ZupPH9mxr1R78BMKgEtdFsiLwbB7MOdDbT\nLsrUcEcupXv+gZek22upQKrAk/XFP067KIqKmCEhDidxhP251SloUaruv9cHEx0a\nDKol9eR465FAiBLvg2N7qJHCKlWdn99SgN4Y3kINsuFR7Tj4QIJZNubOjV0YeOgn\nAWzRJlZD89KZAQgjj4Z215QeLxA=\n-----END CERTIFICATE-----\n", - "key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDhqVAaMsvnCETz\nDtKwfKxZC1jwIOhIyUp8xp+2oN+pJwtqP0UpkLlTV7MD94HZSL3n3f9hsG6appRQ\nGGAJ2ThOw1N9zlAr7Sk9YH6Gtu3bYSDvS6waKjVoxGrrmLfyuoEbv3PDqMWuOjE3\nMT/G1nwUBgIEKYAr8hizY8dUE0Z2qWvKFZJj6etjCXEppjXuwlSusHWw/tj/ePMM\nxMAJMPPhzJeh6AL7iUKBisJysPuaWrS9ntdPxv9PS40sv6cZT4woxmE6tpTCkAxa\nbXqA25SgJOyKOjnvg+BPNlrucLqHw3ErWrxYTL99cHqhexO6K4FaspW3+1kuWd3f\nY4Cm+zkTAgMBAAECggEASlRt2SNQPJphs7n3NkDquC3frsD4IjmDepQmOY7F/T6q\nKOYMTwOnt8cUZUMal0q5mu1p2HC4DeK+yZ8tyzCstmzqTG8vwzhMNDZblt4cfP2C\nTrAbUUcD7q18FnxoYuCB9HTcmd82tgve+DIVstYlaqcL9PkCifcloblFB/GNbToE\ndiHqRNbHvVZLMYGYsShkiyFVL61h5zkFgQCBv+kR5u3AWGGGtlhZ3dvU7+i0P7n4\ndKseQyyd9v/QcW4iW8b3f6izn4plQNcPbb4o0iAdhpnjYxCOwxc8TGZvHul/RYnl\nFGuiKUXSV/r/t/dEppsKM4moJ8n6qzsoZf6czR5NwQKBgQD+y6nJs9sNEuIBa2Jj\nEZIYhVXjVRO9XkusqN3aoKyU48X+Jg4/XDJMU6NRaMEdappo8Jgykrg0h2cNS4JD\nkrw3PwVohaR1SSwNclg7cVlH50HO6WTIrA1lAT7VXBPsxiUiKHr3nNph22chHDRw\n3qhhbluF4Nh0TXdLNwsrdcYyoQKBgQDiumS2EnCOT5uvEpW4MjFRVeLBsCt1tAuf\nVRcF2XngyfgLme0zLnQGh8ZqrHHpZmgZQBM3APnDtJ/UqzEIwg5suvrE1U2lScjv\nrpVpXr1ZiIV0Hc3wEGLcvgmZvWEtcjJfZYhNMdjbDlQo5jAPqkoJrkWV2oCubmyU\nkKma199DMwKBgAKZ16Dceib3A2GaVAXI3yHq8oaAjtQHC2S20JTzwO9AJ/xBLTIO\nYeEPlYI2PIptVSgvFI6nmsPGghHLrIe+DrfNp+N6QcSEu7NjcG0i6hNm0/Alx8aY\nowZd7eNFrlpjZ2ui2CaA7mXDVJks7YgdbcCY3MxQEEWXqNkWtcF60UwhAoGBAI6R\nWmbK7Y/vKxxJeW/bz/svIGle19Upo+1K2jFJcUQSfDD/V5JJcZfxpKjLSs3TIT5P\ndkWuDWAsohxekXTKYbupT6qZ3jtDTGC6zST29+Xm3NQJMcf05dWcgfj0hrjHCDnI\nZI71+0Czn+Qf6rTPBcNUnFkAjs4gjZJV7PB7Md7VAoGAZ/CBJI5NGWkGZjkLM4fq\nq8U2hFM9Y5IHsn9Y8vvst3+mCqcFhTS7TcYUY00qdiFXAJjiwhfGFOoc0q+AWlui\nUxB+b6FWRu73XhqkKxgBehE9i3QCrcitMufSkXKydLonfh7/PeUpB63LmMjtyLdM\nlGAIorVbtD1xAn2WaYuEb5Q=\n-----END PRIVATE KEY-----\n \n" - }, - "resource": { - "resourceUrl": "https://mtls.fapi.panva.cz/accounts", - "institution_id": "xxxxx" - }, - "browser": [ - { - "match": "https://fapi.panva.cz/auth*", - "tasks": [ - { - "task": "Login", - "optional": true, - "match": "https://fapi.panva.cz/interaction*", - "commands": [ - [ - "text", - "name", - "login", - "foo", - "optional" - ], - [ - "text", - "name", - "password", - "bar", - "optional" - ], - [ - "click", - "class", - "login-submit" - ] - ] - }, - { - "task": "Consent", - "optional": true, - "match": "https://fapi.panva.cz/interaction*", - "commands": [ - [ - "click", - "class", - "login-submit" - ] - ] - }, - { - "task": "Verify Complete", - "match": "https://*/test/a/*/callback*", - "commands": [ - [ - "wait", - "id", - "submission_complete", - 10 - ] - ] - } - ] - } - ], - "override": { - "fapi-rw-id2-ensure-redirect-uri-in-authorization-request": { - "browser": [ - { - "comment": "expect an immediate error page", - "match": "https://fapi.panva.cz/auth*", - "tasks": [ - { - "task": "Expect redirect uri mismatch error page", - "match": "https://fapi.panva.cz/auth*", - "commands": [ - [ - "wait", - "xpath", - "//*", - 10, - "oops! something went wrong", - "update-image-placeholder" - ] - ] - } - ] - } - ] - }, - "fapi-rw-id2-ensure-registered-redirect-uri": { - "browser": [ - { - "comment": "expect an immediate error page", - "match": "https://fapi.panva.cz/auth*", - "tasks": [ - { - "task": "Expect redirect uri mismatch error page", - "match": "https://fapi.panva.cz/auth*", - "commands": [ - [ - "wait", - "xpath", - "//*", - 10, - "oops! something went wrong", - "update-image-placeholder" - ] - ] - } - ] - } - ] - }, - "fapi-rw-id2-ensure-request-object-without-redirect-uri-fails": { - "browser": [ - { - "comment": "expect an immediate error page", - "match": "https://fapi.panva.cz/auth*", - "tasks": [ - { - "task": "Expect redirect_uri missing error page", - "match": "https://fapi.panva.cz/auth*", - "commands": [ - [ - "wait", - "xpath", - "//*", - 10, - "oops! something went wrong", - "update-image-placeholder" - ] - ] - } - ] - } - ] - }, - "fapi-rw-id2-user-rejects-authentication": { - "browser": [ - { - "comment": "Rejects interaction", - "match": "https://fapi.panva.cz/auth*", - "tasks": [ - { - "task": "Clicks cancel", - "match": "https://fapi.panva.cz/interaction*", - "commands": [ - [ - "click", - "xpath", - "//*/a" - ] - ] - } - ] - } - ] - } - } -} diff --git a/certification/fapi/pkjwtone.key b/certification/fapi/pkjwtone.key deleted file mode 100644 index 7b6e6875f..000000000 --- a/certification/fapi/pkjwtone.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCxRDprvJf6oIAi -CpiX+nOfbIrlGw9vnOPEwbW/Zu+W9/qXovK8mTESvh1YsBuf1YBXvYa7IyS9Z9WL -Xd+7hxiXa92QgUG3QkZPjRDGQxSnSLMezhGIsrUZOvAnapfsfrsRo01YV4T1iqHe -whHJCAepB/ov2tkVYFIQP4zc5qLAf7N3CszONAfrAISOa7JMWI0s7ZAjpivt0OXW -s+Na8313ykcdbGREwCdCsvn+Z+84P+PVOvluVdhMwcelOE4dUzYht7j/lgOY9pFc -UyBCs+PPqaVRr13UQvsK7Et3X1LcD/pcQwHaO6jRFEJ9ocrtk5E8kprfqqtVlWFc -rcZ66qivAgMBAAECggEAdxzWeLBYGwOgNb+S+4RCDxz7U6lUPPZaIkrbmkpLsdDd -ZOkMXGg/jk2LIJ3tYgAvZkWm87ZQqKjN2ADzJmpHvu+vCLuh8ccpwaiTXfWTOjji -i0+Cfq0+fT6aQpIglbwubVKi1Tqxz+AglrMnCkNICm+e0GsotXFskxhwybp8IAZP -//Up1pg+G9Dg/Timtepw55HjO4xDhzY70zV2NqSDEIvKOleyIZj4JP5kCkwP4/FJ -w/KynXwlxKvCshtFC3U2IEWWUaUQmM8Yy1Hz2x3TqImLQTWs3EMm6oRuhS0Y4tg9 -VlzJqnetdd6Ulh+DFzSB37KnBZS1qvnGGG4Cri9IkQKBgQDlIszaDhS/dDX48arA -o9DyHw6enrvgVPHFgAcKHyaK+AIbHu4iNVo7EIq8KKaLw0KC043RrVB0Zj4FPc2x -7AlS9qeJbJvkFmj6Sl2RLNcK9Cla5ZP+JKiuASCXhqcnn0lkf9BbvC2NGcXyNafD -7+eQ2/gcghV3t90ooKSkaLUOuQKBgQDGDKOB9l2V+md7bu92XGLlUaY2cGctEB79 -rtaCjol2Yo8w9rWq6DFUzm1SGhiNS5SkfGLyANLFvT43+QipO7vOnYvL2IHzBAjL -JiZ3E3sn8HltKfmt9Ny79TP4S5hUQZ+PSGTn9o1BzTLTYOLUvhCucw1EYGjeNYIJ -FmuOrId+pwKBgQC1z2wd5ShfVXVx50emfRVQ2IXAJJFMifogzNpJFLh/chQxstQe -/phu30xFyjeEFFXU+sm/wrZg9bNmc0igUXqAgsgiybPUQlffqVwe0iZ7QYsC3cEU -2b+h+bEDqks9rC0O+j+4WlMGb5nZ+ur4FIZ4MaHn9OvVx2VLVFPvREnNuQKBgBV5 -VVlIH0mdxw6pLqnExqxauAz7DxThxJp5FKU/U0RU13XVqkkHuCGb6FqYncTkRw+2 -tPvMfw4VwONMy1WeNnTmvDu0FBMHgvSUBGOw5w+jkWb2wC3OeQigQ6xOdgvcubJN -hTNpllNmLnag8LdkjGZ97xhU48oEPz9BARSNPHvNAoGBAJ5iWqJ1Dv3etvNuFemJ -lrhF+4pl3YBykYvrWOCFAyW65u5ShH3J/8Ws6opRAodcZuzvG+G49LYdya0XLNQJ -dLFj9RcFR4885oOaKqtuj7x45rVSB593Fxk+gJmPPnlD5GIDwXOtRI+fFLGs5tYr -kSHvsDSAXi9uOJBU0Cu8yX5x ------END PRIVATE KEY----- diff --git a/certification/fapi/pkjwttwo.key b/certification/fapi/pkjwttwo.key deleted file mode 100644 index aac8213fd..000000000 --- a/certification/fapi/pkjwttwo.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDnFxR/1KphIRAc -PJB+3qJpt/Pla+5nKMUtOP3H9HwBF0EPJUTja+Rad/qM5mh4xn9aO1gYsXSfCe4R -UGgJXE/g27VRRCrw5OKCaTZ/gjI9P7v2m9b1vsQZAN1OwEiutvYAqS3AMl7Xq4rr -iQM07QDLdT+dKEg+AxVuYZ38rr/87DCGGsvAq/85Ju9TxmU8jbiPLVjF+rXvkX/V -lAFGObSMAx/o8G3xyDFByWBuk/7s4FRAcHOl+50PVT1bpXyGooqMOV2Uj1mDrEFr -wykLom5B2DCRy6BZ3DIbr2naSr+nKXvfkLzKP0GJ/tWiTV3gTzVxKHwnLXAY18Ca -XPydwlJHAgMBAAECggEBALbSQ51+Ge0NwzkTkR1N9R++MG5onQtv7c6BMAVpHlGe -nBHnYkCpstb/5HBkOHKUV1yDZobinWjU6ks4WZZOOcOjDidkzrQV4D1cD4mA704V -hhbr43w3l9xQGsWayeWvSZ/sOA29y9rEE/uTQb41YlOURueGXuDwf5v8DolBlxH4 -KJ7+uADzwZKHSM/9upSfq+OOO7XHTJA8OKesUz/st97DweGToUOXsxOBc0JaatCv -fMrCi8KPML4UPLLcai+OmrwZ35YA4U7G/tSOeGlN0To04VObZHgJLnc4OJRSnXeF -plZ3lTA/hSdsK5I8M7ah0gQj/BvSAFwXV0cUc3SSDgECgYEA/WOUrDYt1uay8Xtn -aNgutctzdI6XJaf5XrGv6wE/ZoVbqEKWTnBbVfqbwkWf58CeH32tt3hUjBFicG+X -AlYI8TW5Lf6H78Fmt2y3fdowLzzWLwNDoLWo1/rOGxneL+vYM1MekZyUN+uDARdE -eOL9yRPFKOZm7eWSUc/Sdlu9yucCgYEA6XitRdF0tFu7O7S1rQZZjqhrza53f+Ox -1vCYY/Hy4Tm/1fMc7GV0VCHoQwO3ZJPlPEE0JahnfpQkhHGj67GGO40lZYEXdBwN -xtktp1zdmEokbsWmacz57SVPg4pzgS9kketf+wdUbT5VgBmNWS9MPt7rKiyFbmuP -cHU99fWEsaECgYB7/mzUULbhwIa0UJTr404IPATFdrlT/fyzb/QL5dJmkr+leOAg -hi7LxhTOXk+/kqxN8Kw/aazF1gP7zT9yLzO1BXsWnMVcd6GSBKtN9KU7mGOfIdPI -Itz6w4p0DJaDkL99biE5VAvbyW2T7RTeqwVRjlEekJCxvXN6gV4V+RLI5QKBgQDa -3KGyHZQFryhp5BGbTaSOsxu8CJ1FajHAiHKS3rEMqZJIAktO4ByY8eBFUiqNQodW -YNl9o8zBQCarccx0mkp3cO1ppgZSdioECntdtRSWOorEvMwvylNQF/XeHeABbmgc -0kV7kXd3oUONfmafAFO9bEOPmYTL6lDg30diVvI5gQKBgQCtvHEsEw/EFWmjI1+m -Ni1cS56bQce96YeIaSWEJsk783e9G7yURquVn8g8utGaruF7Rw0dx3iP/KGB7X7R -FHQvMniX9aD4HUlzR8omtIi2nymaOZpUdun9OhipWUmH8B9Tm2u7zrNIK9XJxXv0 -G+S8uXoi6wMC5Erx4PWHt1Tn2A== ------END PRIVATE KEY----- diff --git a/certification/fapi/mtls.json b/certification/fapi/plan.json similarity index 95% rename from certification/fapi/mtls.json rename to certification/fapi/plan.json index b003188c2..45e20ab4a 100644 --- a/certification/fapi/mtls.json +++ b/certification/fapi/plan.json @@ -5,8 +5,7 @@ "discoveryUrl": "https://fapi.panva.cz/.well-known/openid-configuration" }, "client": { - "client_id": "mtls-one", - "client_name": "mtls-one", + "client_name": "one", "scope": "openid offline_access", "jwks": { "keys": [ @@ -38,8 +37,7 @@ "key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDEEnW885Hp+2Q7\nl+KCtKPOwfPIVOLKshgygWIAXC8z5TKnA1N9qbB2BvpDpWUKdXrYuBzWcNH/PHwr\nJvX42AHGeXCZJDSXzuRH934/fjMQHTFJquoP4rziUlRJfT+pwJcuvgxgGLI5xgzN\nqD7gZZp/9LVm5OdXU1poQviUel+hwV5eiT1r1fOe5LOiXkLwp3kBLlqGrtRPFIIa\n+20qkvnFh5ZcnRmOmm2vcAnI7OaNc2rSLHVbvkFuY8mMEx8rtthq0dQyyy1Ucudi\n3cLCI2x8Px0qQFUqWH4LgNaj7VZjlU1NPE8LjsSPLasZsMsn0wt22fo+v5bJbaZ3\nN3QQqM0VAgMBAAECggEBAKrgF6Hrd9+9yhWxgaM9gIDhQO73I4tY+IOThHAh5rVI\nawNof6vFZdcGr7aiftNFnSEgG2m7exgAg4or8zPCNJHfJgUgq4Eduo8JnwoAlsnV\nVy4HeOMNTGXFMFW3hPMQt/DxieF5xGFbO69DkECJ68LV5f3dQcw2BVVWAEON/qf9\nUhgEnx79OdiCYzyoHjxaHoXk9cVTUtXwmU0lphRFT14W2Py0KZ5vA8JEefoZl/qE\no42iR0KB4eDj/LiduWAMKDLN/u0Dmq3WxLvw7LHnlqENTEY56Lw1sbMQlmvl0zCD\n68BeMz+Mvlm7VRSCxR+3uX6Nli3eb2EsYziZIlt00iUCgYEA4ZNvp0u6vXQAf7IG\nESuhu3u4cD6K8iJ6BT+O64yHJzc17e9lt+p89GasTtkgeEH/lr+XxScVBYndi/8v\naqsA47bkIoWVvcXq7C4jkv0bPelpaI/KPGfoZC1etR102+dS1ZufxzwSo64drw+H\nPuwaIkXvmpK4MBI9jEYoOHh9KR8CgYEA3oRU/iCWvKOGHlR9BmcBysVGmkvBwj8X\nLTJGfxJkm3M9utLSg0uNo7vW0Zm88KdzdeaRBt6ltZAG3hBL0mHNwC0hYRv5fzO7\nyczWFLPXbUNVqC5OLI+wTL7+ikx040lL9IRRtgub47+9IvxxaGaIRzsFwa+15D7T\nCE7K8BydH0sCgYEAivnkC2VL2tcyS3op5MBF95Vk77qIrl9xX/RloFfHGPEaB8q7\nl5EfhRAQzs9VAuJejsjhv7SxbeUfmtYQp55NgP44FdDJjc73SqWugyvvcbhxmdsl\nFQxLkBSnydwpGCav0Sz9RqmLLk7iuO1PPQQHoeAGm+wTEILcaqT6uLf7HK8CgYBO\nlloWLphOI0q454oIetTNMoNO9zaFThb3ZWw0cOCLblX853xl1oc9rpeeCzgJnnpO\nx5Gs5XGNAEMMpqDAur4aA1Zon6KsZC8MhIWPZjzNYByee0wsvMq9MC9h1MLrivWC\ndEEPlGYIN62q75F2F9BFp/jOgSoyZGXP51QRHWn4pQKBgCEy7GsS3sAj3GiTmCkV\nvR3gKSlSKmJULmI+8EoT6tzYhMvyqoCHPtll2DHzBeqA6Il2DKz+gFbKln2+LrPU\nrb2rgK9e8qdZH9X36Ws8u9YA/VasGQRUFIAcFcNWoBBX79nBQ/89zrPubaN2Rh9B\nKuUEGLKAzDf1JIzseUr8jdvw\n-----END PRIVATE KEY-----\n" }, "client2": { - "client_id": "mtls-two", - "client_name": "mtls-two", + "client_name": "two", "scope": "openid offline_access", "jwks": { "keys": [ @@ -131,7 +129,7 @@ } ], "override": { - "fapi-rw-id2-ensure-redirect-uri-in-authorization-request": { + "REPLACEME-ensure-redirect-uri-in-authorization-request": { "browser": [ { "comment": "expect an immediate error page", @@ -155,7 +153,7 @@ } ] }, - "fapi-rw-id2-ensure-registered-redirect-uri": { + "REPLACEME-ensure-registered-redirect-uri": { "browser": [ { "comment": "expect an immediate error page", @@ -179,7 +177,7 @@ } ] }, - "fapi-rw-id2-ensure-request-object-without-redirect-uri-fails": { + "REPLACEME-ensure-request-object-without-redirect-uri-fails": { "browser": [ { "comment": "expect an immediate error page", @@ -203,7 +201,7 @@ } ] }, - "fapi-rw-id2-user-rejects-authentication": { + "REPLACEME-user-rejects-authentication": { "browser": [ { "comment": "Rejects interaction", @@ -219,6 +217,18 @@ "//*/a" ] ] + }, + { + "task": "Verify Complete", + "match": "https://*/test/a/*/callback*", + "commands": [ + [ + "wait", + "id", + "submission_complete", + 10 + ] + ] } ] } diff --git a/certification/fapi/mtlstwo.crt b/certification/fapi/two.crt similarity index 100% rename from certification/fapi/mtlstwo.crt rename to certification/fapi/two.crt diff --git a/certification/fapi/mtlstwo.key b/certification/fapi/two.key similarity index 100% rename from certification/fapi/mtlstwo.key rename to certification/fapi/two.key diff --git a/certification/oidc/configuration.js b/certification/oidc/configuration.js index beb098054..d61e6e197 100644 --- a/certification/oidc/configuration.js +++ b/certification/oidc/configuration.js @@ -2,7 +2,6 @@ const crypto = require('crypto'); const pkg = require('../../package.json'); const enabledJWA = JSON.parse(JSON.stringify(require('../../lib/consts/jwa'))); -const { InvalidClientMetadata } = require('../../lib/helpers/errors'); function filterOutNone(conf, prop) { // eslint-disable-next-line no-param-reassign @@ -22,16 +21,6 @@ const tokenEndpointAuthMethods = [ ]; module.exports = { - clients: [ - { - client_id: 'dpop-heroku', - token_endpoint_auth_method: 'none', - scope: 'openid offline_access', - grant_types: ['authorization_code', 'refresh_token'], - response_types: ['code'], - redirect_uris: ['https://murmuring-journey-60982.herokuapp.com/cb'], - }, - ], interactions: { url(ctx, interaction) { return `/interaction/${interaction.uid}`; @@ -56,13 +45,6 @@ module.exports = { profile: ['birthdate', 'family_name', 'gender', 'given_name', 'locale', 'middle_name', 'name', 'nickname', 'picture', 'preferred_username', 'profile', 'updated_at', 'website', 'zoneinfo'], }, - clientBasedCORS(ctx, origin, client) { - if (client.clientId === 'dpop-heroku' && origin === 'https://murmuring-journey-60982.herokuapp.com') { - return true; - } - - return false; - }, features: { backchannelLogout: { enabled: true }, devInteractions: { enabled: false }, @@ -71,16 +53,7 @@ module.exports = { certificateBoundAccessTokens: true, selfSignedTlsClientAuth: true, getCertificate(ctx) { - return unescape(ctx.get('x-ssl-client-cert').replace(/\+/g, ' ')); - }, - certificateAuthorized(ctx) { - return ctx.get('x-ssl-client-verify') === 'SUCCESS'; - }, - certificateSubjectMatches(ctx, property, expected) { - if (property !== 'tls_client_auth_subject_dn') { - throw new InvalidClientMetadata(`${property} is not supported by this deployment`); - } - return ctx.get('x-ssl-client-s-dn') === expected; + return ctx.get('client-certificate'); }, }, claimsParameter: { enabled: true }, diff --git a/certification/oidc/docker.js b/certification/oidc/docker.js index 08ffecd85..6751ea6e6 100644 --- a/certification/oidc/docker.js +++ b/certification/oidc/docker.js @@ -3,8 +3,8 @@ const path = require('path'); const https = require('https'); -const pem = require('https-pem'); -const render = require('koa-ejs'); +const selfsigned = require('selfsigned').generate(); +const render = require('@koa/ejs'); const { Provider } = require('../../lib'); // require('oidc-provider'); const Account = require('../../example/support/account'); @@ -39,7 +39,10 @@ render(provider.app, { root: path.join(__dirname, '..', '..', 'example', 'views'), }); provider.use(routes(provider).routes()); -const server = https.createServer(pem, provider.callback()); +const server = https.createServer({ + key: selfsigned.private, + cert: selfsigned.cert, +}, provider.callback()); server.listen(PORT, () => { console.log(`application is listening on port ${PORT}, check its /.well-known/openid-configuration`); process.on('SIGINT', () => { diff --git a/certification/oidc/index.js b/certification/oidc/index.js index 6fe0d48a7..6eb0f70c5 100644 --- a/certification/oidc/index.js +++ b/certification/oidc/index.js @@ -4,7 +4,7 @@ const { promisify } = require('util'); const path = require('path'); const crypto = require('crypto'); -const render = require('koa-ejs'); +const render = require('@koa/ejs'); const helmet = require('helmet'); const { Provider } = require('../../lib'); // require('oidc-provider'); @@ -54,12 +54,13 @@ let server; return interactionFinished.call(provider, ...args); }; + const directives = helmet.contentSecurityPolicy.getDefaultDirectives(); + delete directives['form-action']; + directives['script-src'] = ["'self'", (req, res) => `'nonce-${res.locals.cspNonce}'`]; const pHelmet = promisify(helmet({ contentSecurityPolicy: { - directives: { - ...helmet.contentSecurityPolicy.getDefaultDirectives(), - 'script-src': ["'self'", (req, res) => `'nonce-${res.locals.cspNonce}'`], - }, + useDefaults: false, + directives, }, })); @@ -105,7 +106,7 @@ let server; break; } case 'device_authorization': { - if (ctx.stats === 200) { + if (ctx.status === 200) { ctx.body.verification_uri = ctx.body.verification_uri.replace('https://mtls.', 'https://'); ctx.body.verification_uri_complete = ctx.body.verification_uri_complete.replace('https://mtls.', 'https://'); } diff --git a/certification/oidc/plan.json b/certification/oidc/plan.json index 2fadf36c4..24eea2a47 100644 --- a/certification/oidc/plan.json +++ b/certification/oidc/plan.json @@ -778,25 +778,36 @@ ] }, { - "comment": "expect an immediate error page", - "match": "https://op.panva.cz/session/end*", - "tasks": [ - { - "task": "Expect error page", - "match": "https://op.panva.cz/session/end*", - "commands": [ - [ - "wait", - "xpath", - "//*", - 10, - "post_logout_redirect_uri can only be used in combination with id_token_hint", - "update-image-placeholder" - ] - ] - } - ] - } + "comment": "wait for the logout success", + "match": "https://op.panva.cz/session/end*", + "tasks": [ + { + "task": "Choose logout option", + "match": "https://op.panva.cz/session/end*", + "commands": [ + [ + "click", + "css", + "button[autofocus] " + ] + ] + }, + { + "task": "Expect success page", + "match": "https://op.panva.cz/session/end/success", + "commands": [ + [ + "wait", + "xpath", + "//*", + 10, + "Sign-out Success", + "update-image-placeholder" + ] + ] + } + ] + } ] }, "oidcc-rp-initiated-logout-no-params" : { diff --git a/certification/runner/api.js b/certification/runner/api.js index 6f9b910d7..12a854095 100644 --- a/certification/runner/api.js +++ b/certification/runner/api.js @@ -1,10 +1,14 @@ /* eslint-disable no-await-in-loop */ const { strict: assert } = require('assert'); const { createWriteStream } = require('fs'); +const stream = require('stream'); +const { promisify } = require('util'); const Got = require('got'); const ms = require('ms'); +const pipeline = promisify(stream.pipeline); + const debug = require('./debug'); const FINISHED = new Set(['FINISHED']); @@ -27,7 +31,10 @@ class API { timeout: 10000, }); - const { stream } = Got.extend({ + this.get = get; + this.post = post; + + this.stream = Got.extend({ prefixUrl: baseUrl, throwHttpErrors: false, followRedirect: false, @@ -36,11 +43,7 @@ class API { 'content-type': 'application/json', }, retry: 0, - }); - - this.get = get; - this.stream = stream; - this.post = post; + }).stream; } async getAllTestModules() { @@ -109,19 +112,14 @@ class API { async downloadArtifact({ planId } = {}) { assert(planId, 'argument property "planId" missing'); - await new Promise((resolve) => { - const download = this.stream(`api/plan/exporthtml/${planId}`, { + const filename = `export-${planId}.zip`; + return pipeline( + this.stream(`api/plan/exporthtml/${planId}`, { headers: { accept: 'application/zip' }, responseType: 'buffer', - }); - - const filename = `export-${planId}.zip`; - download.pipe(createWriteStream(filename)); - download.on('close', () => { - console.log(`Logs in ${filename}.`); // eslint-disable-line no-console - resolve(); - }); - }); + }), + createWriteStream(filename), + ); } async waitForState({ moduleId, timeout = ms('4m') } = {}) { diff --git a/certification/runner/index.js b/certification/runner/index.js index f1f006861..2bcec1d66 100644 --- a/certification/runner/index.js +++ b/certification/runner/index.js @@ -1,11 +1,9 @@ /* eslint-env mocha */ -/* eslint-disable no-bitwise, func-names, no-console, no-restricted-syntax, no-await-in-loop */ +/* eslint-disable no-bitwise, func-names, no-console, no-restricted-syntax, no-await-in-loop, no-multi-assign, max-len */ const { strict: assert } = require('assert'); const fs = require('fs'); -const parallel = require('mocha.parallel'); - const debug = require('./debug'); const API = require('./api'); @@ -28,12 +26,31 @@ const configuration = JSON.parse(fs.readFileSync(CONFIGURATION)); const runner = new API({ baseUrl: SUITE_BASE_URL, bearerToken: SUITE_ACCESS_TOKEN }); if ('alias' in configuration) { - configuration.alias = `${configuration.alias}-${Object.values(VARIANT).join('-')}`; + configuration.alias = `${configuration.alias}-${Object.values(VARIANT).sort().join('-')}`; +} + +let override; +// eslint-disable-next-line default-case +switch (PLAN_NAME) { + case 'fapi1-advanced-final-test-plan': { + override = 'fapi1-advanced-final'; + const auth = VARIANT.client_auth_type === 'mtls' ? 'mtls' : 'pkjwt'; + configuration.client.client_id = `1.0-final-${auth}-one`; + configuration.client2.client_id = `1.0-final-${auth}-two`; + break; + } + case 'fapi-rw-id2-test-plan': { + override = 'fapi-rw-id2'; + const auth = VARIANT.client_auth_type === 'mtls' ? 'mtls' : 'pkjwt'; + configuration.client.client_id = `1.0-id2-${auth}-one`; + configuration.client2.client_id = `1.0-id2-${auth}-two`; + break; + } } -if (PLAN_NAME === 'fapi1-advanced-final-test-plan') { +if (override) { configuration.override = Object.entries(configuration.override).reduce((acc, [key, value]) => { - acc[key.replace('fapi-rw-id2', 'fapi1-advanced-final')] = value; + acc[key.replace('REPLACEME', override)] = value; return acc; }, {}); } @@ -53,45 +70,32 @@ runner.createTestPlan({ debug('%s/plan-detail.html?plan=%s', SUITE_BASE_URL, PLAN_ID); debug('modules to test %O', MODULES); - if (fs.existsSync('.failed')) { - fs.unlinkSync('.failed'); - } - + let download = false; describe(PLAN_NAME, () => { after(() => { - if (fs.existsSync('.failed')) { - fs.unlinkSync('.failed'); - process.exitCode |= 1; - return runner.downloadArtifact({ planId: PLAN_ID }); + if (download) { + runner.downloadArtifact({ planId: PLAN_ID }); } - return undefined; }); - parallel('', () => { - const skips = SKIP ? SKIP.split(',') : []; - for (const { testModule, variant } of MODULES) { - const test = skips.includes(testModule) ? it.skip : it; - test(`${testModule}, ${JSON.stringify(variant)}`, async () => { - debug('\n\nRunning test module: %s', testModule); - const { id: moduleId } = await runner.createTestFromPlan({ - plan: PLAN_ID, test: testModule, variant, - }); - debug('Created test module, new id: %s', moduleId); - debug('%s/log-detail.html?log=%s', SUITE_BASE_URL, moduleId); - try { - await runner.waitForState({ moduleId }); - } catch (err) { - fs.writeFileSync('.failed', Buffer.alloc(0)); - throw err; - } - }); + afterEach(function () { + if (this.currentTest.state === 'failed') { + download = true; } }); - if (configuration.alias) { - parallel.limit(1); - } else { - parallel.limit(10); + const skips = SKIP ? SKIP.split(',') : []; + for (const { testModule, variant } of MODULES) { + const test = skips.includes(testModule) ? it.skip : it; + test(`${testModule}, ${JSON.stringify(variant)}`, async () => { + debug('\n\nRunning test module: %s', testModule); + const { id: moduleId } = await runner.createTestFromPlan({ + plan: PLAN_ID, test: testModule, variant, + }); + debug('Created test module, new id: %s', moduleId); + debug('%s/log-detail.html?log=%s', SUITE_BASE_URL, moduleId); + await runner.waitForState({ moduleId }); + }); } }); diff --git a/docs/README.md b/docs/README.md index ada1fc702..ffb3c0975 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,7 +10,7 @@ is a good starting point to get an idea of what you should provide. ## Support -If you or your business use oidc-provider, or you need help using/upgrading the module, please consider becoming a [sponsor][support-sponsor] so I can continue maintaining it and adding new features carefree. The only way to guarantee you get feedback from the author & sole maintainer of this module is to support the package through GitHub Sponsors. I make it a best effort to try and answer newcomers regardless of being a supporter or not, but if you're asking your n-th question and don't get an answer it's because I'm out of handouts and spare time to give. +If you or your business use oidc-provider, or you need help using/upgrading the module, please consider becoming a [sponsor][support-sponsor] so I can continue maintaining it and adding new features carefree. The only way to guarantee you get feedback from the author & sole maintainer of this module is to support the package through GitHub Sponsors.
@@ -194,8 +194,9 @@ router.post('/interaction/:uid', async (ctx, next) => { ## Custom Grant Types oidc-provider comes with the basic grants implemented, but you can register your own grant types, -for example to implement an [OAuth 2.0 Token Exchange](https://tools.ietf.org/html/rfc8693). You can -check the standard grant factories [here](/lib/actions/grants). +for example to implement an +[OAuth 2.0 Token Exchange](https://www.rfc-editor.org/rfc/rfc8693.html). You can check the standard +grant factories [here](/lib/actions/grants). ```js const parameters = [ @@ -299,12 +300,12 @@ connectApp.use('/oidc', oidc.callback()); ### to a `fastify` application ```js -// assumes fastify ^3.0.0 -await app.register(require('fastify-express')); +// assumes fastify ^4.0.0 +const fastify = new Fastify(); +await fastify.register(require('@fastify/middie')); // or -// await app.register(require('middie')); - -fastifyApp.use('/oidc', oidc.callback()); +// await app.register(require('@fastify/express')); +fastify.use('/oidc', oidc.callback()); ``` ### to a `hapi` application @@ -429,7 +430,6 @@ location / { - [encryption](#featuresencryption) - [fapi](#featuresfapi) - [introspection](#featuresintrospection) - - [issAuthResp](#featuresissauthresp) - [jwtIntrospection](#featuresjwtintrospection) - [jwtResponseModes](#featuresjwtresponsemodes) - [jwtUserinfo](#featuresjwtuserinfo) @@ -560,13 +560,16 @@ async function findAccount(ctx, sub, token) { ### jwks -JSON Web Key Set used by the provider for signing and decryption. The object must be in [JWK Set format](https://tools.ietf.org/html/rfc7517#section-5). All provided keys must be private keys. - - -_**recommendation**_: Be sure to follow best practices for distributing private keying material and secrets for your respective target deployment environment. Supported key types are: +JSON Web Key Set used by the provider for signing and decryption. The object must be in [JWK Set format](https://www.rfc-editor.org/rfc/rfc7517.html#section-5). All provided keys must be private keys. + Supported key types are: - RSA - OKP (Ed25519, Ed448, X25519, X448 sub types) - - EC (P-256, secp256k1, P-384, and P-521 curves) Provider key rotation** - The following action order is recommended when rotating signing keys on a distributed deployment with rolling reloads in place. + - EC (P-256, secp256k1, P-384, and P-521 curves) + + +_**recommendation**_: Be sure to follow best practices for distributing private keying material and secrets for your respective target deployment environment. + +_**recommendation**_: The following action order is recommended when rotating signing keys on a distributed deployment with rolling reloads in place. 1. push new keys at the very end of the "keys" array in your JWKS, this means the keys will become available for verification should they be encountered but not yet used for signing 2. reload all your processes 3. move your new key to the very front of the "keys" array in your JWKS, this means the key will be used for signing after reload @@ -621,25 +624,21 @@ new Provider('http://localhost:3000', { ### features.backchannelLogout -[Back-Channel Logout 1.0 - draft 06](https://openid.net/specs/openid-connect-backchannel-1_0-06.html) +[Back-Channel Logout 1.0](https://openid.net/specs/openid-connect-backchannel-1_0-final.html) -Enables Back-Channel Logout features. - - -_**recommendation**_: Updates to draft specification versions are released as MINOR library versions, if you utilize these specification implementations consider using the tilde `~` operator in your package.json since breaking changes may be introduced as part of these version updates. Alternatively, [acknowledge](#features) the version and be notified of breaking changes as part of your CI. +Enables Back-Channel Logout features. _**default value**_: ```js { - ack: undefined, enabled: false } ``` ### features.ciba -[OpenID Connect Client Initiated Backchannel Authentication Flow - Core 1.0 - draft-03](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0-03.html) +[OpenID Connect Client Initiated Backchannel Authentication Flow - Core 1.0](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0-final.html) Enables Core CIBA Flow, when combined with `features.fapi` enables [Financial-grade API: Client Initiated Backchannel Authentication Profile - Implementer's Draft 01](https://openid.net/specs/openid-financial-api-ciba-ID1.html) as well. @@ -648,7 +647,6 @@ Enables Core CIBA Flow, when combined with `features.fapi` enables [Financial-gr _**default value**_: ```js { - ack: undefined, deliveryModes: [ 'poll' ], @@ -685,7 +683,9 @@ _**default value**_: Helper function used to process the login_hint parameter and return the accountId value to use for processsing the request. -_**recommendation**_: Use `throw Provider.errors.InvalidRequest('validation error message')` when login_hint is invalid. Use `return undefined` or when you can't determine the accountId from the login_hint. +_**recommendation**_: Use `throw Provider.errors.InvalidRequest('validation error message')` when login_hint is invalid. + +_**recommendation**_: Use `return undefined` or when you can't determine the accountId from the login_hint. _**default value**_: @@ -702,7 +702,11 @@ async function processLoginHint(ctx, loginHint) { Helper function used to process the login_hint_token parameter and return the accountId value to use for processsing the request. -_**recommendation**_: Use `throw Provider.errors.ExpiredLoginHintToken('validation error message')` when login_hint_token is expired. Use `throw Provider.errors.InvalidRequest('validation error message')` when login_hint_token is invalid. Use `return undefined` or when you can't determine the accountId from the login_hint. +_**recommendation**_: Use `throw Provider.errors.ExpiredLoginHintToken('validation error message')` when login_hint_token is expired. + +_**recommendation**_: Use `throw Provider.errors.InvalidRequest('validation error message')` when login_hint_token is invalid. + +_**recommendation**_: Use `return undefined` or when you can't determine the accountId from the login_hint. _**default value**_: @@ -756,7 +760,9 @@ await provider.backchannelResult(...); Helper function used to process the binding_message parameter and throw if its not following the authorization server's policy. -_**recommendation**_: Use `throw Provider.errors.InvalidBindingMessage('validation error message')` when the binding_message is invalid. Use `return undefined` when a binding_message isn't required and wasn't provided. +_**recommendation**_: Use `throw Provider.errors.InvalidBindingMessage('validation error message')` when the binding_message is invalid. + +_**recommendation**_: Use `return undefined` when a binding_message isn't required and wasn't provided. _**default value**_: @@ -775,7 +781,9 @@ async function validateBindingMessage(ctx, bindingMessage) { Helper function used to process the request_context parameter and throw if its not following the authorization server's policy. -_**recommendation**_: Use `throw Provider.errors.InvalidRequest('validation error message')` when the request_context is required by policy and missing or invalid. Use `return undefined` when a request_context isn't required and wasn't provided. +_**recommendation**_: Use `throw Provider.errors.InvalidRequest('validation error message')` when the request_context is required by policy and missing or invalid. + +_**recommendation**_: Use `return undefined` when a request_context isn't required and wasn't provided. _**default value**_: @@ -792,7 +800,11 @@ async function validateRequestContext(ctx, requestContext) { Helper function used to verify the user_code parameter value is present when required and verify its value. -_**recommendation**_: Use `throw Provider.errors.MissingUserCode('validation error message')` when user_code should have been provided but wasn't. Use `throw Provider.errors.InvalidUserCode('validation error message')` when the provided user_code is invalid. Use `return undefined` when no user_code was provided and isn't required. +_**recommendation**_: Use `throw Provider.errors.MissingUserCode('validation error message')` when user_code should have been provided but wasn't. + +_**recommendation**_: Use `throw Provider.errors.InvalidUserCode('validation error message')` when the provided user_code is invalid. + +_**recommendation**_: Use `return undefined` when no user_code was provided and isn't required. _**default value**_: @@ -824,7 +836,7 @@ _**default value**_: ### features.clientCredentials -[RFC6749](https://tools.ietf.org/html/rfc6749#section-1.3.4) - Client Credentials +[RFC6749](https://www.rfc-editor.org/rfc/rfc6749.html#section-1.3.4) - Client Credentials Enables `grant_type=client_credentials` to be used on the token endpoint. @@ -870,7 +882,7 @@ _**default value**_: ### features.deviceFlow -[RFC8628](https://tools.ietf.org/html/rfc8628) - OAuth 2.0 Device Authorization Grant (Device Flow) +[RFC8628](https://www.rfc-editor.org/rfc/rfc8628.html) - OAuth 2.0 Device Authorization Grant (Device Flow) Enables Device Authorization Grant @@ -1110,7 +1122,7 @@ _**default value**_: ### features.introspection -[RFC7662](https://tools.ietf.org/html/rfc7662) - OAuth 2.0 Token Introspection +[RFC7662](https://www.rfc-editor.org/rfc/rfc7662.html) - OAuth 2.0 Token Introspection Enables Token Introspection for: - opaque access tokens @@ -1146,24 +1158,6 @@ async function introspectionAllowedPolicy(ctx, client, token) { -### features.issAuthResp - -[draft-ietf-oauth-iss-auth-resp-01](https://tools.ietf.org/html/draft-ietf-oauth-iss-auth-resp-01) - OAuth 2.0 Authorization Server Issuer Identifier in Authorization Response - -Enables `iss` authorization response parameter for responses without existing countermeasures against mix-up attacks. - - -_**recommendation**_: Updates to draft specification versions are released as MINOR library versions, if you utilize these specification implementations consider using the tilde `~` operator in your package.json since breaking changes may be introduced as part of these version updates. Alternatively, [acknowledge](#features) the version and be notified of breaking changes as part of your CI. - - -_**default value**_: -```js -{ - ack: undefined, - enabled: false -} -``` - ### features.jwtIntrospection [draft-ietf-oauth-jwt-introspection-response-10](https://tools.ietf.org/html/draft-ietf-oauth-jwt-introspection-response-10) - JWT Response for OAuth Token Introspection @@ -1184,18 +1178,14 @@ _**default value**_: ### features.jwtResponseModes -[openid-financial-api-jarm-ID1](https://openid.net/specs/openid-financial-api-jarm-ID1.html) - JWT Secured Authorization Response Mode (JARM) +[JWT Secured Authorization Response Mode (JARM)](https://openid.net/specs/oauth-v2-jarm.html) -Enables JWT Secured Authorization Responses - - -_**recommendation**_: Updates to draft specification versions are released as MINOR library versions, if you utilize these specification implementations consider using the tilde `~` operator in your package.json since breaking changes may be introduced as part of these version updates. Alternatively, [acknowledge](#features) the version and be notified of breaking changes as part of your CI. +Enables JWT Secured Authorization Responses _**default value**_: ```js { - ack: undefined, enabled: false } ``` @@ -1216,7 +1206,7 @@ _**default value**_: ### features.mTLS -[RFC8705](https://tools.ietf.org/html/rfc8705) - OAuth 2.0 Mutual TLS Client Authentication and Certificate Bound Access Tokens (MTLS) +[RFC8705](https://www.rfc-editor.org/rfc/rfc8705.html) - OAuth 2.0 Mutual TLS Client Authentication and Certificate Bound Access Tokens (MTLS) Enables specific features from the Mutual TLS specification. The three main features have their own specific setting in this feature's configuration object and you must provide functions for resolving some of the functions which are deployment-specific. @@ -1352,18 +1342,14 @@ false ### features.pushedAuthorizationRequests -[draft-ietf-oauth-par-08](https://tools.ietf.org/html/draft-ietf-oauth-par-08) - OAuth 2.0 Pushed Authorization Requests (PAR) - -Enables the use of `pushed_authorization_request_endpoint` defined by the Pushed Authorization Requests draft. - +[RFC9126](https://www.rfc-editor.org/rfc/rfc9126.html) - OAuth 2.0 Pushed Authorization Requests (PAR) -_**recommendation**_: Updates to draft specification versions are released as MINOR library versions, if you utilize these specification implementations consider using the tilde `~` operator in your package.json since breaking changes may be introduced as part of these version updates. Alternatively, [acknowledge](#features) the version and be notified of breaking changes as part of your CI. +Enables the use of `pushed_authorization_request_endpoint` defined by the Pushed Authorization Requests RFC. _**default value**_: ```js { - ack: undefined, enabled: false, requirePushedAuthorizationRequests: false } @@ -1441,8 +1427,8 @@ new (provider.InitialAccessToken)({}).save().then(console.log); #### issueRegistrationAccessToken Boolean or a function used to decide whether a registration access token will be issued or not. Supported values are - - `false` registration access tokens is issued - - `true` registration access tokens is not issued + - `true` registration access tokens is issued + - `false` registration access tokens is not issued - function returning true/false, true when token should be issued, false when it shouldn't @@ -1467,7 +1453,9 @@ async issueRegistrationAccessToken(ctx) { define registration and registration management policies applied to client properties. Policies are sync/async functions that are assigned to an Initial Access Token that run before the regular client property validations are run. Multiple policies may be assigned to an Initial Access Token and by default the same policies will transfer over to the Registration Access Token. A policy may throw / reject and it may modify the properties object. -_**recommendation**_: referenced policies must always be present when encountered on a token, an AssertionError will be thrown inside the request context if it is not, resulting in a 500 Server Error. the same policies will be assigned to the Registration Access Token after a successful validation. If you wish to assign different policies to the Registration Access Token +_**recommendation**_: referenced policies must always be present when encountered on a token, an AssertionError will be thrown inside the request context if it is not, resulting in a 500 Server Error. + +_**recommendation**_: the same policies will be assigned to the Registration Access Token after a successful validation. If you wish to assign different policies to the Registration Access Token ```js // inside your final ran policy ctx.oidc.entities.RegistrationAccessToken.policies = ['update-policy']; @@ -1533,7 +1521,7 @@ async function secretFactory(ctx) { ### features.registrationManagement -[OAuth 2.0 Dynamic Client Registration Management Protocol](https://tools.ietf.org/html/rfc7592) +[OAuth 2.0 Dynamic Client Registration Management Protocol](https://www.rfc-editor.org/rfc/rfc7592.html) Enables Update and Delete features described in the RFC @@ -1584,7 +1572,7 @@ false ### features.requestObjects -[Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#RequestObject) and [JWT Secured Authorization Request (JAR)](https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-33) - Request Object +[Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#RequestObject) and [JWT Secured Authorization Request (JAR)](https://www.rfc-editor.org/rfc/rfc9101.html) - Request Object Enables the use and validations of the `request` and/or `request_uri` parameters. @@ -1661,7 +1649,7 @@ true ### features.resourceIndicators -[RFC8707](https://tools.ietf.org/html/rfc8707) - Resource Indicators for OAuth 2.0 +[RFC8707](https://www.rfc-editor.org/rfc/rfc8707.html) - Resource Indicators for OAuth 2.0 Enables the use of `resource` parameter for the authorization and token endpoints to enable issuing Access Tokens for Resource Servers (APIs). - Multiple resource parameters may be present during Authorization Code Flow, Device Authorization Grant, and Backchannel Authentication Requests, but only a single audience for an Access Token is permitted. @@ -1724,11 +1712,8 @@ async function getResourceServerInfo(ctx, resourceIndicator, client) { throw new errors.InvalidTarget(); } ``` -
(Click to expand) Resource Server (API) with two scopes, an expected audience value, an Access Token TTL
- - -and a JWT Access Token Format. - +
(Click to expand) Resource Server (API) with two scopes, an expected audience value, an Access Token TTL and a JWT Access Token Format. +
```js { @@ -1818,8 +1803,9 @@ and a JWT Access Token Format. } } // PASETO Access Token Format (when accessTokenFormat is 'paseto') + // Note: v2.local and v4.local are NOT supported paseto?: { - version: 1 | 2, + version: 1 | 2 | 3 | 4, purpose: 'local' | 'public', key?: crypto.KeyObject, // required when purpose is 'local' kid?: string, // OPTIONAL `kid` to aid in signing key selection or to put in the footer for 'local' @@ -1833,7 +1819,9 @@ and a JWT Access Token Format. Function used to determine if an already granted resource indicator should be used without being explicitly requested by the client during the Token Endpoint request. -_**recommendation**_: Use `return true` when it's allowed for a client skip providing the "resource" parameter at the Token Endpoint. Use `return false` (default) when it's required for a client to explitly provide a "resource" parameter at the Token Endpoint or when other indication dictates an Access Token for the UserInfo Endpoint should returned. +_**recommendation**_: Use `return true` when it's allowed for a client skip providing the "resource" parameter at the Token Endpoint. + +_**recommendation**_: Use `return false` (default) when it's required for a client to explitly provide a "resource" parameter at the Token Endpoint or when other indication dictates an Access Token for the UserInfo Endpoint should returned. _**default value**_: @@ -1850,7 +1838,7 @@ async function useGrantedResource(ctx, model) { ### features.revocation -[RFC7009](https://tools.ietf.org/html/rfc7009) - OAuth 2.0 Token Revocation +[RFC7009](https://www.rfc-editor.org/rfc/rfc7009.html) - OAuth 2.0 Token Revocation Enables Token Revocation for: - opaque access tokens @@ -1867,7 +1855,7 @@ _**default value**_: ### features.rpInitiatedLogout -[RP-Initiated Logout 1.0](https://openid.net/specs/openid-connect-rpinitiated-1_0-01.html) +[RP-Initiated Logout 1.0](https://openid.net/specs/openid-connect-rpinitiated-1_0-final.html) Enables RP-Initiated Logout features @@ -2325,7 +2313,7 @@ _**default value**_: } ```
-
(Click to expand) To push a payload and a footer to a PASETO structured access token +
(Click to expand) To push a payload, a footer, and use an implicit assertion with a PASETO structured access token
```js @@ -2334,6 +2322,7 @@ _**default value**_: paseto(ctx, token, structuredToken) { structuredToken.payload.foo = 'bar'; structuredToken.footer = { foo: 'bar' }; + structuredToken.assertion = 'foo'; // v3 and v4 tokens only } } } @@ -2715,7 +2704,7 @@ async function pairwiseIdentifier(ctx, accountId, client) { ### pkce -[RFC7636 - Proof Key for Code Exchange (PKCE)](https://tools.ietf.org/html/rfc7636) +[RFC7636 - Proof Key for Code Exchange (PKCE)](https://www.rfc-editor.org/rfc/rfc7636.html) PKCE configuration such as available methods and policy check on required use of PKCE @@ -2949,7 +2938,9 @@ _**default value**_: description: Expirations for various token and session types. The value can be a number (in seconds) or a synchronous function that dynamically returns value based on the context. -_**recommendation**_: Do not set token TTLs longer then they absolutely have to be, the shorter the TTL, the better. Rather than setting crazy high Refresh Token TTL look into `rotateRefreshToken` configuration option which is set up in way that when refresh tokens are regularly used they will have their TTL refreshed (via rotation). This is inline with the [OAuth 2.0 Security Best Current Practice](https://tools.ietf.org/html/draft-ietf-oauth-security-topics-13) +_**recommendation**_: Do not set token TTLs longer then they absolutely have to be, the shorter the TTL, the better. + +_**recommendation**_: Rather than setting crazy high Refresh Token TTL look into `rotateRefreshToken` configuration option which is set up in way that when refresh tokens are regularly used they will have their TTL refreshed (via rotation). This is inline with the [OAuth 2.0 Security Best Current Practice](https://tools.ietf.org/html/draft-ietf-oauth-security-topics-13) _**default value**_: @@ -3019,7 +3010,7 @@ Configure `ttl` for a given token type with a function like so, this must return Fine-tune the algorithms your provider will support by declaring algorithm values for each respective JWA use -_**recommendation**_: Only allow JWA algs that are necessary. The current defaults are based on recommendations from the [JWA specification](https://tools.ietf.org/html/rfc7518) + enables RSASSA-PSS based on current guidance in FAPI. "none" JWT algs are disabled by default but available if you need them. +_**recommendation**_: Only allow JWA algs that are necessary. The current defaults are based on recommendations from the [JWA specification](https://www.rfc-editor.org/rfc/rfc7518.html) + enables RSASSA-PSS based on current guidance in FAPI. "none" JWT algs are disabled by default but available if you need them. ### enabledJWA.authorizationEncryptionAlgValues @@ -3564,8 +3555,8 @@ be additionally formencoded. A proper way of submitting `client_id` and `client_secret` using `client_secret_basic` is `Authorization: base64(formEncode(client_id):formEncode(client_secret))` as per -https://tools.ietf.org/html/rfc6749#section-2.3.1 incl. -https://tools.ietf.org/html/rfc6749#appendix-B +https://www.rfc-editor.org/rfc/rfc6749.html#section-2.3.1 incl. +https://www.rfc-editor.org/rfc/rfc6749.html#appendix-B Example: diff --git a/docs/events.md b/docs/events.md index 42266489e..be3f7c770 100644 --- a/docs/events.md +++ b/docs/events.md @@ -1,6 +1,7 @@ # Events -Your oidc-provider instance is an event emitter, `this` is always the instance. In events where +Your oidc-provider instance is an event emitter, in the event handlers `this` is always the +Provider instance. In events where `ctx` (request context) is passed to the listener `ctx.oidc` [OIDCContext](/lib/helpers/oidc_context.js) holds additional details like recognized parameters, loaded client or session. diff --git a/docs/update-configuration.js b/docs/update-configuration.js index d44df1f70..723954406 100644 --- a/docs/update-configuration.js +++ b/docs/update-configuration.js @@ -123,15 +123,15 @@ const props = [ if (nextIsOption) { nextIsOption = false; - option = blocks[strLine.substring(2)] = new Block(); // eslint-disable-line no-multi-assign + option = blocks[strLine.slice(2)] = new Block(); // eslint-disable-line no-multi-assign return; } const next = props.find((prop) => { if ( prop.startsWith('@') - ? strLine.substring(2, 2 + prop.length) === prop - : strLine.substring(2, 2 + prop.length + 1) === `${prop}:` + ? strLine.slice(2, 2 + prop.length) === prop + : strLine.slice(2, 2 + prop.length + 1) === `${prop}:` ) { let override; if (prop === 'example' && option.example) { @@ -140,6 +140,12 @@ const props = [ .map((e) => parseInt(e.slice(-1), 10) || 0)); override = `example${i + 1}`; } + if (prop === 'recommendation' && option.recommendation) { + const i = Math.max(...Object.keys(option) + .filter((p) => p.startsWith('recommendation')) + .map((e) => parseInt(e.slice(-1), 10) || 0)); + override = `recommendation${i + 1}`; + } option.active = override || prop; option.write(line.slice(prop.length + 4)); return true; @@ -253,10 +259,8 @@ const props = [ append(`${capitalizeSentences(section.description.join(' '))} \n\n`); } - ['recommendation'].forEach((option) => { - if (section[option]) { - append(`_**${option}**_: ${section[option].join(' ')} \n\n`); - } + Object.keys(section).filter((x) => x.startsWith('recommendation')).forEach((prop) => { + append(`_**recommendation**_: ${section[prop].join(' ')} \n\n`); }); if (!('@nodefault' in section)) { diff --git a/example/adapters/mongodb.js b/example/adapters/mongodb.js index e81d4feff..79584892b 100644 --- a/example/adapters/mongodb.js +++ b/example/adapters/mongodb.js @@ -1,6 +1,6 @@ /* eslint-disable max-classes-per-file */ -// npm i mongodb@^3.0.0 +// npm i mongodb@^4.3.0 const { MongoClient } = require('mongodb'); // eslint-disable-line import/no-unresolved const snakeCase = require('lodash/snakeCase'); @@ -126,9 +126,7 @@ class MongoAdapter { // This is not part of the required or supported API, all initialization should happen before // you pass the adapter to `new Provider` static async connect() { - const connection = await MongoClient.connect(process.env.MONGODB_URI, { - useNewUrlParser: true, - }); + const connection = await MongoClient.connect(process.env.MONGODB_URI); DB = connection.db(connection.s.options.dbName); } } diff --git a/example/express.js b/example/express.js index 69abe484c..5aaeca7c7 100644 --- a/example/express.js +++ b/example/express.js @@ -16,7 +16,15 @@ const { PORT = 3000, ISSUER = `http://localhost:${PORT}` } = process.env; configuration.findAccount = Account.findAccount; const app = express(); -app.use(helmet()); + +const directives = helmet.contentSecurityPolicy.getDefaultDirectives(); +delete directives['form-action']; +app.use(helmet({ + contentSecurityPolicy: { + useDefaults: false, + directives, + }, +})); app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); diff --git a/example/koa.js b/example/koa.js index d56738e33..e96397a7b 100644 --- a/example/koa.js +++ b/example/koa.js @@ -1,10 +1,11 @@ /* eslint-disable no-console */ const path = require('path'); +const { promisify } = require('util'); const Koa = require('koa'); -const render = require('koa-ejs'); -const helmet = require('koa-helmet'); // eslint-disable-line import/no-unresolved +const render = require('@koa/ejs'); +const helmet = require('helmet'); const mount = require('koa-mount'); const { Provider } = require('../lib'); // require('oidc-provider'); @@ -17,7 +18,24 @@ const { PORT = 3000, ISSUER = `http://localhost:${PORT}` } = process.env; configuration.findAccount = Account.findAccount; const app = new Koa(); -app.use(helmet()); + +const directives = helmet.contentSecurityPolicy.getDefaultDirectives(); +delete directives['form-action']; +const pHelmet = promisify(helmet({ + contentSecurityPolicy: { + useDefaults: false, + directives, + }, +})); + +app.use(async (ctx, next) => { + const origSecure = ctx.req.secure; + ctx.req.secure = ctx.request.secure; + await pHelmet(ctx.req, ctx.res); + ctx.req.secure = origSecure; + return next(); +}); + render(app, { cache: false, viewExt: 'ejs', diff --git a/example/routes/express.js b/example/routes/express.js index c6ce4ea1d..0ca9ab14a 100644 --- a/example/routes/express.js +++ b/example/routes/express.js @@ -39,8 +39,7 @@ module.exports = (app, provider) => { }); function setNoCache(req, res, next) { - res.set('Pragma', 'no-cache'); - res.set('Cache-Control', 'no-cache, no-store'); + res.set('cache-control', 'no-store'); next(); } diff --git a/example/routes/koa.js b/example/routes/koa.js index afa736688..01d0d1a53 100644 --- a/example/routes/koa.js +++ b/example/routes/koa.js @@ -26,8 +26,7 @@ module.exports = (provider) => { const { constructor: { errors: { SessionNotFound } } } = provider; router.use(async (ctx, next) => { - ctx.set('Pragma', 'no-cache'); - ctx.set('Cache-Control', 'no-cache, no-store'); + ctx.set('cache-control', 'no-store'); try { await next(); } catch (err) { diff --git a/example/standalone.js b/example/standalone.js index 0dfaad0da..575878295 100644 --- a/example/standalone.js +++ b/example/standalone.js @@ -1,9 +1,10 @@ /* eslint-disable no-console */ const path = require('path'); +const { promisify } = require('util'); -const render = require('koa-ejs'); -const helmet = require('koa-helmet'); // eslint-disable-line import/no-unresolved +const render = require('@koa/ejs'); +const helmet = require('helmet'); const { Provider } = require('../lib'); // require('oidc-provider'); @@ -27,7 +28,22 @@ let server; const provider = new Provider(ISSUER, { adapter, ...configuration }); - provider.use(helmet()); + const directives = helmet.contentSecurityPolicy.getDefaultDirectives(); + delete directives['form-action']; + const pHelmet = promisify(helmet({ + contentSecurityPolicy: { + useDefaults: false, + directives, + }, + })); + + provider.use(async (ctx, next) => { + const origSecure = ctx.req.secure; + ctx.req.secure = ctx.request.secure; + await pHelmet(ctx.req, ctx.res); + ctx.req.secure = origSecure; + return next(); + }); if (prod) { provider.proxy = true; diff --git a/example/views/repost.ejs b/example/views/repost.ejs index ff592c2c3..96767dde4 100644 --- a/example/views/repost.ejs +++ b/example/views/repost.ejs @@ -6,7 +6,7 @@