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:
Kristoffer
2023-08-02 16:44:07 +02:00
committed by GitHub
parent aea5b84c41
commit 8f8ce1e02b
12 changed files with 284 additions and 97 deletions

47
.github/workflows/docker-release.yml vendored Normal file
View 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 }}

View File

@@ -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

View File

@@ -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

View File

@@ -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
View File

@@ -9,6 +9,9 @@ extensions/
uploads/ uploads/
.env .env
# Redis files
./*.rdb
# Code editor files # Code editor files
.vscode .vscode

View File

@@ -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)

View File

@@ -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
View File

@@ -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

View File

@@ -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
View 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);
}

View File

@@ -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',

View File

@@ -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;
} }