feat: add cache (#2)
* ci: remove main workflow ci: add pr workflow to branch next * docs: correct readme ci: add docker build on release ci: add lint and test to release flow * feat: add cache for both memory and redis refactor: cleanup getAccountability nested promise refactor: import path for get-auth-providers.ts docs: document cache options ci: add redis file to gitignore * ci: use test:coverage for testing to update pr --------- Co-authored-by: Krise <krise86@users.noreply.github.com>
This commit is contained in:
47
.github/workflows/docker-release.yml
vendored
Normal file
47
.github/workflows/docker-release.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
name: Create and publish a Docker image
|
||||||
|
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- published
|
||||||
|
- prereleased
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push-image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.release.tag_name }}
|
||||||
|
|
||||||
|
- name: Log in to the Container registry
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v3
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
27
.github/workflows/main.yml
vendored
27
.github/workflows/main.yml
vendored
@@ -1,27 +0,0 @@
|
|||||||
name: Merge to Main
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
# The type of runner that the job will run on
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node: [ 18, 20 ]
|
|
||||||
|
|
||||||
name: Node ${{ matrix.node }} Release
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: pnpm/action-setup@v2
|
|
||||||
with:
|
|
||||||
version: 8
|
|
||||||
- name: Use Node.js ${{ matrix.node }}
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node }}
|
|
||||||
cache: 'pnpm'
|
|
||||||
- run: pnpm install
|
|
||||||
- run: pnpm lint
|
|
||||||
- run: pnpm test
|
|
||||||
8
.github/workflows/pr.yml
vendored
8
.github/workflows/pr.yml
vendored
@@ -2,7 +2,9 @@ name: Pull Request
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
branches:
|
||||||
|
- main
|
||||||
|
- next
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
@@ -27,8 +29,10 @@ jobs:
|
|||||||
name: install dependencies
|
name: install dependencies
|
||||||
- run: pnpm lint
|
- run: pnpm lint
|
||||||
name: linting
|
name: linting
|
||||||
- run: pnpm test
|
- run: pnpm test:coverage
|
||||||
name: testing
|
name: testing
|
||||||
|
- run: pnpm build
|
||||||
|
name: build package
|
||||||
- name: 'Report Coverage'
|
- name: 'Report Coverage'
|
||||||
if: always() # Also generate the report if tests are failing
|
if: always() # Also generate the report if tests are failing
|
||||||
uses: davelosert/vitest-coverage-report-action@v2
|
uses: davelosert/vitest-coverage-report-action@v2
|
||||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -34,6 +34,10 @@ jobs:
|
|||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
- run: pnpm lint
|
||||||
|
name: linting
|
||||||
|
- run: pnpm test
|
||||||
|
name: testing
|
||||||
- name: Verify the integrity of provenance attestations and registry signatures for installed dependencies
|
- name: Verify the integrity of provenance attestations and registry signatures for installed dependencies
|
||||||
run: pnpm audit signatures
|
run: pnpm audit signatures
|
||||||
- name: Release
|
- name: Release
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -9,6 +9,9 @@ extensions/
|
|||||||
uploads/
|
uploads/
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
# Redis files
|
||||||
|
./*.rdb
|
||||||
|
|
||||||
# Code editor files
|
# Code editor files
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -5,9 +5,15 @@ The plugin expects to resolve the following new configuration option
|
|||||||
|
|
||||||
The provider must issues Access tokens as JWT since this is used for verification right now. Might add support for general tokens later.
|
The provider must issues Access tokens as JWT since this is used for verification right now. Might add support for general tokens later.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
all configuration options listed here are an extension to directus default config.
|
||||||
|
|
||||||
|
| ENV Variable | Supported values | Description |
|
||||||
|AUTH_PROVIDER_TRUSTED| True | Must be true for the provider to be considered as trusted. Note, do not trust public providers as these can generate tokens that you cannot control.
|
|------------------------------|-------------------|--------------|
|
||||||
|AUTH_PROVIDER_JWT_ROLE_KEY | String | What key in the JWT payload contains the role
|
| AUTH_PROVIDER_TRUSTED | True/False | Must be true for the provider to be considered as trusted. Note, do not trust public providers as these can generate tokens that you cannot control.
|
||||||
|AUTH_PROVIDER_JWT_ADMIN_KEY | Boolean | What key in the JWT payload contains a bool to grant admin rights
|
| AUTH_PROVIDER_JWT_ROLE_KEY | String | What key in the JWT payload contains the role |
|
||||||
|AUTH_PROVIDER_JWT_APP_KEY | Boolean | What key in the JWT payload contains a bool to allow app access
|
| AUTH_PROVIDER_JWT_ADMIN_KEY | String | What key in the JWT payload contains a bool to grant admin rights |
|
||||||
|
| AUTH_PROVIDER_JWT_APP_KEY | String | What key in the JWT payload contains a bool to allow app access
|
||||||
|
| AUTH_PROVIDER_JWT_USEDB | Bool | If enabled/true the plugin will resolve the user and roles from the directus database using the token. For OIDC the sub is used. Should not be used without a Redis Cache enabled.
|
||||||
|
| CACHE_JWT_NAMESPACE | String | What namespace to use in cache store.
|
||||||
|
| CACHE_JWT_TTL | Number | Time to live for the cached user entry, default 5000 (5 seconds)
|
||||||
|
|||||||
@@ -103,8 +103,10 @@
|
|||||||
"vitest": "^0.34.1"
|
"vitest": "^0.34.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@keyv/redis": "^2.7.0",
|
||||||
"jsonwebtoken": "^9.0.1",
|
"jsonwebtoken": "^9.0.1",
|
||||||
"jwks-rsa": "^3.0.1",
|
"jwks-rsa": "^3.0.1",
|
||||||
|
"keyv": "^4.5.3",
|
||||||
"openid-client": "^5.4.3"
|
"openid-client": "^5.4.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
80
pnpm-lock.yaml
generated
80
pnpm-lock.yaml
generated
@@ -5,12 +5,18 @@ settings:
|
|||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@keyv/redis':
|
||||||
|
specifier: ^2.7.0
|
||||||
|
version: 2.7.0
|
||||||
jsonwebtoken:
|
jsonwebtoken:
|
||||||
specifier: ^9.0.1
|
specifier: ^9.0.1
|
||||||
version: 9.0.1
|
version: 9.0.1
|
||||||
jwks-rsa:
|
jwks-rsa:
|
||||||
specifier: ^3.0.1
|
specifier: ^3.0.1
|
||||||
version: 3.0.1
|
version: 3.0.1
|
||||||
|
keyv:
|
||||||
|
specifier: ^4.5.3
|
||||||
|
version: 4.5.3
|
||||||
openid-client:
|
openid-client:
|
||||||
specifier: ^5.4.3
|
specifier: ^5.4.3
|
||||||
version: 5.4.3
|
version: 5.4.3
|
||||||
@@ -892,6 +898,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
|
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@ioredis/commands@1.2.0:
|
||||||
|
resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@istanbuljs/load-nyc-config@1.1.0:
|
/@istanbuljs/load-nyc-config@1.1.0:
|
||||||
resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==}
|
resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -997,6 +1007,15 @@ packages:
|
|||||||
'@jridgewell/sourcemap-codec': 1.4.15
|
'@jridgewell/sourcemap-codec': 1.4.15
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@keyv/redis@2.7.0:
|
||||||
|
resolution: {integrity: sha512-GYqCT+iEP93+gVVPzhW4kmkr/9KTmwb88wkglX6aUMSP50JIhUhNF/yXH0aQTZRPsWfPKO10NJjUZzEh7YW6yw==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
dependencies:
|
||||||
|
ioredis: 5.3.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@mapbox/node-pre-gyp@1.0.11:
|
/@mapbox/node-pre-gyp@1.0.11:
|
||||||
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
|
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -2303,6 +2322,11 @@ packages:
|
|||||||
engines: {node: '>=0.8'}
|
engines: {node: '>=0.8'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/cluster-key-slot@1.1.2:
|
||||||
|
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/color-convert@1.9.3:
|
/color-convert@1.9.3:
|
||||||
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2624,6 +2648,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
|
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/denque@2.1.0:
|
||||||
|
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
|
||||||
|
engines: {node: '>=0.10'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/depd@2.0.0:
|
/depd@2.0.0:
|
||||||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -3856,6 +3885,23 @@ packages:
|
|||||||
engines: {node: '>= 0.10'}
|
engines: {node: '>= 0.10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/ioredis@5.3.2:
|
||||||
|
resolution: {integrity: sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==}
|
||||||
|
engines: {node: '>=12.22.0'}
|
||||||
|
dependencies:
|
||||||
|
'@ioredis/commands': 1.2.0
|
||||||
|
cluster-key-slot: 1.1.2
|
||||||
|
debug: 4.3.4(supports-color@8.1.1)
|
||||||
|
denque: 2.1.0
|
||||||
|
lodash.defaults: 4.2.0
|
||||||
|
lodash.isarguments: 3.1.0
|
||||||
|
redis-errors: 1.2.0
|
||||||
|
redis-parser: 3.0.0
|
||||||
|
standard-as-callback: 2.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
/ip@2.0.0:
|
/ip@2.0.0:
|
||||||
resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==}
|
resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
@@ -4241,6 +4287,10 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/json-buffer@3.0.1:
|
||||||
|
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/json-parse-even-better-errors@2.3.1:
|
/json-parse-even-better-errors@2.3.1:
|
||||||
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
|
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -4318,6 +4368,12 @@ packages:
|
|||||||
safe-buffer: 5.2.1
|
safe-buffer: 5.2.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/keyv@4.5.3:
|
||||||
|
resolution: {integrity: sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==}
|
||||||
|
dependencies:
|
||||||
|
json-buffer: 3.0.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/knex@2.5.1(sqlite3@5.1.6):
|
/knex@2.5.1(sqlite3@5.1.6):
|
||||||
resolution: {integrity: sha512-z78DgGKUr4SE/6cm7ku+jHvFT0X97aERh/f0MUKAKgFnwCYBEW4TFBqtHWFYiJFid7fMrtpZ/gxJthvz5mEByA==}
|
resolution: {integrity: sha512-z78DgGKUr4SE/6cm7ku+jHvFT0X97aERh/f0MUKAKgFnwCYBEW4TFBqtHWFYiJFid7fMrtpZ/gxJthvz5mEByA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -4413,10 +4469,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
|
resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/lodash.defaults@4.2.0:
|
||||||
|
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/lodash.flattendeep@4.4.0:
|
/lodash.flattendeep@4.4.0:
|
||||||
resolution: {integrity: sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==}
|
resolution: {integrity: sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/lodash.isarguments@3.1.0:
|
||||||
|
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/lodash.memoize@4.1.2:
|
/lodash.memoize@4.1.2:
|
||||||
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
|
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -5644,6 +5708,18 @@ packages:
|
|||||||
resolve: 1.22.2
|
resolve: 1.22.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/redis-errors@1.2.0:
|
||||||
|
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/redis-parser@3.0.0:
|
||||||
|
resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
dependencies:
|
||||||
|
redis-errors: 1.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/regenerator-runtime@0.13.11:
|
/regenerator-runtime@0.13.11:
|
||||||
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
|
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -6027,6 +6103,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/standard-as-callback@2.1.0:
|
||||||
|
resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/std-env@3.3.3:
|
/std-env@3.3.3:
|
||||||
resolution: {integrity: sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==}
|
resolution: {integrity: sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {JwksClient} from 'jwks-rsa';
|
|||||||
|
|
||||||
import { Issuer } from 'openid-client';
|
import { Issuer } from 'openid-client';
|
||||||
|
|
||||||
import env from '../config/config.js';
|
import env from '../config/config';
|
||||||
import { createError } from '@directus/errors';
|
import { createError } from '@directus/errors';
|
||||||
|
|
||||||
const InvalidJWKIssuerMetadata = createError('INVALID_JWKS_ISSUER_ERROR', 'No JWKS_URL or JWKS_KEYS and could not discover JWKS_URL from openid metadata', 500);
|
const InvalidJWKIssuerMetadata = createError('INVALID_JWKS_ISSUER_ERROR', 'No JWKS_URL or JWKS_KEYS and could not discover JWKS_URL from openid metadata', 500);
|
||||||
|
|||||||
54
src/external-jwt/cache.ts
Normal file
54
src/external-jwt/cache.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import {default as Keyv, Store} from 'keyv';
|
||||||
|
import env from './config/config';
|
||||||
|
import {default as KeyvRedis} from '@keyv/redis';
|
||||||
|
// check if redis is defined
|
||||||
|
|
||||||
|
const cache: Keyv | null = getCache();
|
||||||
|
|
||||||
|
function getCache(): Keyv | null {
|
||||||
|
if(env['CACHE_ENABLED'] !== true) return null;
|
||||||
|
|
||||||
|
// check namespace
|
||||||
|
let namespace = env['CACHE_JWT_NAMESPACE'];
|
||||||
|
if(namespace == null || namespace === '') {
|
||||||
|
namespace = 'exjwt';
|
||||||
|
}
|
||||||
|
|
||||||
|
let ttl = env['CACHE_JWT_TTL'];
|
||||||
|
if (ttl == null || ttl === '') {
|
||||||
|
ttl = 5000
|
||||||
|
}
|
||||||
|
|
||||||
|
let uri = '';
|
||||||
|
let store: Store<string | undefined> | undefined = undefined;
|
||||||
|
if(env['CACHE_STORE'] === 'redis') {
|
||||||
|
uri = env['REDIS']
|
||||||
|
|
||||||
|
if(uri == null || uri === '') {
|
||||||
|
uri = `redis://${env['REDIS_USERNAME']}:${env['REDIS_PASSWORD']}@${env['REDIS_HOST']}:${env['REDIS_PORT']}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
store = new KeyvRedis(uri);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Keyv(uri, {
|
||||||
|
namespace: namespace,
|
||||||
|
ttl,
|
||||||
|
store: store
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CacheEnabled(): boolean {
|
||||||
|
return cache !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function CacheSet(key: string, value: any) {
|
||||||
|
if(cache === null) return false;
|
||||||
|
return cache.set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function CacheGet(key: string) {
|
||||||
|
if(cache === null) return null;
|
||||||
|
return cache.get(key);
|
||||||
|
}
|
||||||
@@ -25,13 +25,14 @@ const allowedEnvironmentVars = [
|
|||||||
'CACHE_VALUE_MAX_SIZE',
|
'CACHE_VALUE_MAX_SIZE',
|
||||||
'CACHE_SKIP_ALLOWED',
|
'CACHE_SKIP_ALLOWED',
|
||||||
'CACHE_HEALTHCHECK_THRESHOLD',
|
'CACHE_HEALTHCHECK_THRESHOLD',
|
||||||
|
// Externl JWT Cache
|
||||||
|
'CACHE_JWT_NAMESPACE',
|
||||||
// redis
|
// redis
|
||||||
'REDIS',
|
'REDIS',
|
||||||
'REDIS_HOST',
|
'REDIS_HOST',
|
||||||
'REDIS_PORT',
|
'REDIS_PORT',
|
||||||
'REDIS_USERNAME',
|
'REDIS_USERNAME',
|
||||||
'REDIS_PASSWORD',
|
'REDIS_PASSWORD',
|
||||||
'REDIS_JWT_DB',
|
|
||||||
// auth
|
// auth
|
||||||
'AUTH_PROVIDERS',
|
'AUTH_PROVIDERS',
|
||||||
'AUTH_.+_DRIVER',
|
'AUTH_.+_DRIVER',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { Accountability } from '@directus/types';
|
|||||||
import { getAuthProviders } from './authProvider/get-auth-providers.js';
|
import { getAuthProviders } from './authProvider/get-auth-providers.js';
|
||||||
import type { Knex } from 'knex';
|
import type { Knex } from 'knex';
|
||||||
import { verify_token } from './verify-token.js';
|
import { verify_token } from './verify-token.js';
|
||||||
|
import { CacheEnabled, CacheGet, CacheSet } from './cache.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ export async function getAccountabilityForToken(
|
|||||||
accountability: Accountability | null,
|
accountability: Accountability | null,
|
||||||
database: Knex
|
database: Knex
|
||||||
): Promise<Accountability> {
|
): Promise<Accountability> {
|
||||||
if (!accountability) {
|
if (accountability == null) {
|
||||||
accountability = {
|
accountability = {
|
||||||
user: null,
|
user: null,
|
||||||
role: null,
|
role: null,
|
||||||
@@ -32,80 +33,92 @@ export async function getAccountabilityForToken(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (token == null || iss == null) {
|
if (token == null || iss == null) {
|
||||||
|
|
||||||
return accountability
|
return accountability
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const providers = authProviders.filter((provider) => provider.issuer_url && iss.includes(provider.issuer_url));
|
||||||
|
|
||||||
|
if(providers.length === 0) return accountability;
|
||||||
|
if(providers.length > 1) {
|
||||||
|
return accountability;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
const provider = providers[0];
|
||||||
const providers = authProviders.filter((provider) => provider && iss.includes(provider.client_id));
|
|
||||||
if(providers.length === 0) return accountability;
|
|
||||||
if(providers.length > 1) {
|
|
||||||
console.log("to many matching providers");
|
|
||||||
return accountability;
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = providers[0];
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
|
||||||
|
const result = await verify_token(provider, token)
|
||||||
|
|
||||||
verify_token(provider, token).then(async (result) => {
|
|
||||||
if(accountability) {
|
|
||||||
// check if role key is set else try role key
|
if(provider.use_database) { // use database to get user
|
||||||
|
// TODO: Add caching to this function
|
||||||
if(provider.role_key != null) {
|
if (CacheEnabled() && result.sub) {
|
||||||
accountability.role = typeof result[provider.role_key] === 'string' ? result[provider.role_key] : result[provider.role_key][0];
|
|
||||||
} else {
|
|
||||||
if (result.role) {
|
|
||||||
accountability.role = result.role;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(provider.use_database) { // use database to get user
|
|
||||||
// TODO: Add caching to this function
|
|
||||||
|
|
||||||
const user = await database
|
|
||||||
.select('directus_users.id', 'directus_users.role', 'directus_roles.admin_access', 'directus_roles.app_access')
|
|
||||||
.from('directus_users')
|
|
||||||
.leftJoin('directus_roles', 'directus_users.role', 'directus_roles.id')
|
|
||||||
.where({
|
|
||||||
'directus_users.external_identifier': result.sub,
|
|
||||||
'directus_users.provider': provider.name,
|
|
||||||
})
|
|
||||||
.first();
|
|
||||||
|
|
||||||
if(!user) {
|
|
||||||
reject("invalid user credentials");
|
|
||||||
}
|
|
||||||
|
|
||||||
accountability.user = user.id;
|
|
||||||
accountability.role = user.role;
|
|
||||||
accountability.admin = user.admin_access === true || user.admin_access == 1;
|
|
||||||
accountability.app = user.app_access === true || user.app_access == 1;
|
|
||||||
} else {
|
|
||||||
if(provider.admin_key != null) {
|
|
||||||
accountability.admin = result[provider.admin_key];
|
|
||||||
}
|
|
||||||
if(provider.app_key != null) {
|
|
||||||
accountability.app = result[provider.app_key];
|
|
||||||
}
|
|
||||||
accountability.user = result.sub;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(accountability);
|
const cachedAccountability = await CacheGet(result.sub);
|
||||||
|
if (cachedAccountability) {
|
||||||
resolve(accountability);
|
return cachedAccountability;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reject("no accountability");
|
const user = await database
|
||||||
|
.select('directus_users.id', 'directus_users.role', 'directus_roles.admin_access', 'directus_roles.app_access')
|
||||||
|
.from('directus_users')
|
||||||
|
.leftJoin('directus_roles', 'directus_users.role', 'directus_roles.id')
|
||||||
|
.where({
|
||||||
|
'directus_users.external_identifier': result.sub,
|
||||||
|
'directus_users.provider': provider.name,
|
||||||
|
})
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if(!user) {
|
||||||
|
return accountability;
|
||||||
|
}
|
||||||
|
|
||||||
|
accountability.user = user.id;
|
||||||
|
accountability.role = user.role;
|
||||||
|
accountability.admin = user.admin_access === true || user.admin_access == 1;
|
||||||
|
accountability.app = user.app_access === true || user.app_access == 1;
|
||||||
|
|
||||||
|
if (CacheEnabled() && result.sub) {
|
||||||
|
CacheSet(result.sub, accountability);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
})
|
return accountability;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if role key is set else try role key
|
||||||
|
if(provider.role_key != null) {
|
||||||
|
if(typeof result[provider.role_key] === 'string') {
|
||||||
|
accountability.role = result[provider.role_key];
|
||||||
|
}
|
||||||
|
if(typeof result[provider.role_key] === 'object') {
|
||||||
|
accountability.role = ''
|
||||||
|
}
|
||||||
|
if(result[provider.role_key].instanceOf(Array)) {
|
||||||
|
accountability.role = result[provider.role_key][0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
});
|
if(provider.admin_key != null) {
|
||||||
|
accountability.admin = result[provider.admin_key];
|
||||||
|
}
|
||||||
|
if(provider.app_key != null) {
|
||||||
|
accountability.app = result[provider.app_key];
|
||||||
|
}
|
||||||
|
accountability.user = result.sub;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return accountability;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return accountability;
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user