second commit
This commit is contained in:
13
README.md
Normal file
13
README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# External JWT Plugin for Directus
|
||||||
|
## This plugin serves as a way to make Directus trust externally signed JWT tokens from an OIDC or OAuth2 provider.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|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_JWT_ADMIN_KEY | Boolean | What key in the JWT payload contains a bool to grant admin rights
|
||||||
|
|AUTH_PROVIDER_JWT_APP_KEY | Boolean | What key in the JWT payload contains a bool to allow app access
|
||||||
@@ -19,7 +19,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "directus-extension build",
|
"build": "directus-extension build",
|
||||||
"dev": "directus-extension build -w --no-minify",
|
"dev": "directus-extension build -w --no-minify",
|
||||||
"link": "directus-extension link"
|
"link": "directus-extension link",
|
||||||
|
"directus": "npx directus start"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@directus/errors": "^0.0.2",
|
"@directus/errors": "^0.0.2",
|
||||||
@@ -38,6 +39,7 @@
|
|||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
|
"knex": "^2.5.1",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"typescript": "^5.1.6"
|
"typescript": "^5.1.6"
|
||||||
},
|
},
|
||||||
|
|||||||
2951
pnpm-lock.yaml
generated
Normal file
2951
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,29 +3,38 @@ import {JwksClient} from 'jwks-rsa';
|
|||||||
|
|
||||||
import { Issuer } from 'openid-client';
|
import { Issuer } from 'openid-client';
|
||||||
|
|
||||||
import env from './config';
|
import env from '../config/config.js';
|
||||||
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);
|
||||||
const InvalidJWKSUrl = createError('INVALID_JWKS_ISSUER_ERROR', 'Could not retrieve any valid keys from JWKS_URL', 500);
|
const InvalidJWKSUrl = createError('INVALID_JWKS_ISSUER_ERROR', 'Could not retrieve any valid keys from JWKS_URL', 500);
|
||||||
|
|
||||||
interface AuthProvider {
|
export interface AuthProvider {
|
||||||
label: string;
|
label: string;
|
||||||
name: string;
|
name: string;
|
||||||
driver: string;
|
driver: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
client_id: string;
|
||||||
|
client_secret?: string;
|
||||||
trusted: boolean;
|
trusted: boolean;
|
||||||
jwks_url?: string;
|
jwks_url?: string;
|
||||||
jwks_keys?: string;
|
jwks_keys?: string;
|
||||||
issuer_url?: string;
|
issuer_url?: string;
|
||||||
|
|
||||||
|
admin_key?: string;
|
||||||
|
app_key?: string;
|
||||||
|
role_key?: string;
|
||||||
|
JWKSClient?: JwksClient;
|
||||||
|
use_database?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function getAuthProviders(): Promise<JwksClient[]> {
|
export async function getAuthProviders(): Promise<AuthProvider[]> {
|
||||||
|
console.log("calling auth providers")
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
const authProviders = toArray(env['AUTH_PROVIDERS'])
|
const authProviders: AuthProvider[] = toArray(env['AUTH_PROVIDERS'])
|
||||||
.filter((provider) => provider && env[`AUTH_${provider.toUpperCase()}_DRIVER`])
|
.filter((provider) => provider && env[`AUTH_${provider.toUpperCase()}_DRIVER`] === ('openid' || 'oauth2'))
|
||||||
.map((provider) => ({
|
.map((provider) => ({
|
||||||
name: provider,
|
name: provider,
|
||||||
label: env[`AUTH_${provider.toUpperCase()}_LABEL`],
|
label: env[`AUTH_${provider.toUpperCase()}_LABEL`],
|
||||||
@@ -35,6 +44,12 @@ export async function getAuthProviders(): Promise<JwksClient[]> {
|
|||||||
jwks_url: env[`AUTH_${provider.toUpperCase()}_JWKS_URL`],
|
jwks_url: env[`AUTH_${provider.toUpperCase()}_JWKS_URL`],
|
||||||
jwks_keys: env[`AUTH_${provider.toUpperCase()}_JWKS_KEYS`],
|
jwks_keys: env[`AUTH_${provider.toUpperCase()}_JWKS_KEYS`],
|
||||||
issuer_url: env[`AUTH_${provider.toUpperCase()}_ISSUER_URL`],
|
issuer_url: env[`AUTH_${provider.toUpperCase()}_ISSUER_URL`],
|
||||||
|
admin_key: env[`AUTH_${provider.toUpperCase()}_JWT_ADMIN_KEY`],
|
||||||
|
app_key: env[`AUTH_${provider.toUpperCase()}_JWT_APP_KEY`],
|
||||||
|
role_key: env[`AUTH_${provider.toUpperCase()}_JWT_ROLE_KEY`],
|
||||||
|
client_id: env[`AUTH_${provider.toUpperCase()}_CLIENT_ID`],
|
||||||
|
client_secret: env[`AUTH_${provider.toUpperCase()}_CLIENT_SECRET`],
|
||||||
|
use_database: env[`AUTH_${provider.toUpperCase()}_JWT_USEDB`],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
@@ -47,12 +62,15 @@ export async function getAuthProviders(): Promise<JwksClient[]> {
|
|||||||
for (const authProvider of authProviders) {
|
for (const authProvider of authProviders) {
|
||||||
switch (authProvider.driver) {
|
switch (authProvider.driver) {
|
||||||
case 'openid':
|
case 'openid':
|
||||||
if (!authProvider.trusted || (!authProvider.issuer_url && !authProvider.jwks_url && !authProvider.jwks_keys)) break;
|
|
||||||
promises.push(getJWKS(authProvider.issuer_url, authProvider.jwks_url, authProvider.jwks_keys));
|
if (!authProvider.trusted || (authProvider.issuer_url == null && authProvider.jwks_url == null && authProvider.jwks_keys == null)) break;
|
||||||
|
//promises.push(getJWKS(authProvider.issuer_url, authProvider.jwks_url, authProvider.jwks_keys));
|
||||||
|
promises.push(getJWKS(authProvider));
|
||||||
break;
|
break;
|
||||||
case 'oauth2':
|
case 'oauth2':
|
||||||
if (!authProvider.trusted || (!authProvider.issuer_url && !authProvider.jwks_url && !authProvider.jwks_keys)) break;
|
if (!authProvider.trusted || (authProvider.issuer_url == null && authProvider.jwks_url == null && authProvider.jwks_keys == null)) break;
|
||||||
promises.push(getJWKS(authProvider.issuer_url, authProvider.jwks_url, authProvider.jwks_keys));
|
//promises.push(getJWKS(authProvider.issuer_url, authProvider.jwks_url, authProvider.jwks_keys));
|
||||||
|
promises.push(getJWKS(authProvider));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,26 +84,30 @@ export async function getAuthProviders(): Promise<JwksClient[]> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getJWKS(issuer_url: string, jwks_url: string, jwks_keys: string): Promise<JwksClient>{
|
function getJWKS(provider: AuthProvider): Promise<AuthProvider>{
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
if(jwks_keys && !issuer_url && !jwks_url) {
|
if(provider.jwks_keys != null && provider.issuer_url == null && provider.jwks_url == null) {
|
||||||
|
|
||||||
const jwksClient = new JwksClient({
|
const jwksClient = new JwksClient({
|
||||||
getKeysInterceptor: () => {
|
getKeysInterceptor: () => {
|
||||||
return JSON.parse(jwks_keys);
|
return JSON.parse(provider.jwks_keys);
|
||||||
},
|
},
|
||||||
jwksUri: ''
|
jwksUri: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
resolve(jwksClient);
|
provider.JWKSClient = jwksClient;
|
||||||
|
|
||||||
|
resolve(provider);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(issuer_url && !jwks_url) {
|
if(provider.issuer_url && !provider.jwks_url) {
|
||||||
//try to discover with openid
|
//try to discover with openid
|
||||||
try {
|
try {
|
||||||
const issuer = await Issuer.discover(issuer_url);
|
const issuer = await Issuer.discover(provider.issuer_url);
|
||||||
if(issuer.metadata.jwks_uri != null) {
|
if(issuer.metadata.jwks_uri != null) {
|
||||||
jwks_url = issuer.metadata.jwks_uri;
|
provider.jwks_url = issuer.metadata.jwks_uri;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//throw new InvalidJWKIssuerMetadata();
|
//throw new InvalidJWKIssuerMetadata();
|
||||||
@@ -93,8 +115,13 @@ function getJWKS(issuer_url: string, jwks_url: string, jwks_keys: string): Promi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!provider.jwks_url) {
|
||||||
|
reject("No JWKS_URL or JWKS_KEYS and could not discover JWKS_URL from openid metadata")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const jwksClient = new JwksClient({
|
const jwksClient = new JwksClient({
|
||||||
jwksUri: jwks_url,
|
jwksUri: provider.jwks_url,
|
||||||
cache: true,
|
cache: true,
|
||||||
cacheMaxAge: 36000000, // 10 hours
|
cacheMaxAge: 36000000, // 10 hours
|
||||||
cacheMaxEntries: 10,
|
cacheMaxEntries: 10,
|
||||||
@@ -110,8 +137,10 @@ function getJWKS(issuer_url: string, jwks_url: string, jwks_keys: string): Promi
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new InvalidJWKSUrl();
|
throw new InvalidJWKSUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
provider.JWKSClient = jwksClient;
|
||||||
|
|
||||||
resolve(jwksClient);
|
resolve(provider);
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -3,7 +3,7 @@ import dotenv from 'dotenv';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { clone, toNumber, toString } from 'lodash-es';
|
import { clone, toNumber, toString } from 'lodash-es';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { requireYAML } from './require-yaml';
|
import { requireYAML } from '../require-yaml.js';
|
||||||
|
|
||||||
|
|
||||||
// keeping this here for now to prevent a circular import to constants.ts
|
// keeping this here for now to prevent a circular import to constants.ts
|
||||||
@@ -45,6 +45,10 @@ const allowedEnvironmentVars = [
|
|||||||
'AUTH_.+_TRUSTED',
|
'AUTH_.+_TRUSTED',
|
||||||
'AUTH_.+_JWKS_URL',
|
'AUTH_.+_JWKS_URL',
|
||||||
'AUTH_.+_JWKS_KEYS',
|
'AUTH_.+_JWKS_KEYS',
|
||||||
|
'AUTH_.+_JWT_ROLE_KEY',
|
||||||
|
'AUTH_.+_JWT_ADMIN_KEY',
|
||||||
|
'AUTH_.+_JWT_APP_KEY',
|
||||||
|
'AUTH_.+_JWT_USEDB',
|
||||||
'AUTH_.+_IDP.+',
|
'AUTH_.+_IDP.+',
|
||||||
'AUTH_.+_SP.+',
|
'AUTH_.+_SP.+',
|
||||||
].map((name) => new RegExp(`^${name}$`));
|
].map((name) => new RegExp(`^${name}$`));
|
||||||
@@ -1,35 +1,29 @@
|
|||||||
import type { Accountability } from '@directus/types';
|
import type { Accountability } from '@directus/types';
|
||||||
import type { JwtHeader, VerifyCallback} from 'jsonwebtoken';
|
import type { JwtHeader, VerifyCallback} from 'jsonwebtoken';
|
||||||
import {JsonWebTokenError} from 'jsonwebtoken';
|
import {JsonWebTokenError} from 'jsonwebtoken';
|
||||||
import { getAuthProviders } from './get-auth-providers';
|
import { getAuthProviders } from './authProvider/get-auth-providers.js';
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
|
import type { Knex } from 'knex';
|
||||||
|
import { createError } from '@directus/errors';
|
||||||
|
import { verify_token } from './verify-token.js';
|
||||||
|
import { forEach } from 'lodash-es';
|
||||||
|
|
||||||
|
|
||||||
const authProviders = getAuthProviders();
|
const authProviders = await getAuthProviders();
|
||||||
|
|
||||||
|
const MissingJWTHeaderError = createError('INVALID_JWKS_ISSUER_ERROR', 'No header in JWT Token', 500);
|
||||||
|
const NoValidKeysError = createError('INVALID_JWKS_ISSUER_ERROR', 'could not retrieve any valid keys with key id(kid)', 500);
|
||||||
|
const NoAuthProvidersError = createError('INVALID_JWKS_ISSUER_ERROR', 'No auth providers in the list', 500);
|
||||||
|
|
||||||
|
|
||||||
// TODO: optimize this function, reduce the amount of loops
|
// TODO: optimize this function, reduce the amount of loops
|
||||||
async function getKey(header: JwtHeader | undefined, callback: VerifyCallback<string>) {
|
|
||||||
for (const authProvider of (await authProviders)) {
|
|
||||||
if(!header) return new JsonWebTokenError("No header found")
|
|
||||||
authProvider.getSigningKey(header.kid, function (err, key) {
|
|
||||||
if (err) {
|
|
||||||
return new JsonWebTokenError("Could not retrieve any valid keys with key id(kid)");
|
|
||||||
}
|
|
||||||
if(key == null) return new JsonWebTokenError("No valid key found");
|
|
||||||
|
|
||||||
|
|
||||||
const signingKey = key.getPublicKey();
|
|
||||||
return callback(null, signingKey);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JsonWebTokenError("No auth provider in list");
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAccountabilityForToken(
|
export async function getAccountabilityForToken(
|
||||||
token?: string | null,
|
token: string | null,
|
||||||
accountability?: Accountability
|
iss: string[] | string | undefined,
|
||||||
|
accountability: Accountability | null,
|
||||||
|
database: Knex
|
||||||
): Promise<Accountability> {
|
): Promise<Accountability> {
|
||||||
if (!accountability) {
|
if (!accountability) {
|
||||||
accountability = {
|
accountability = {
|
||||||
@@ -40,32 +34,81 @@ export async function getAccountabilityForToken(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token) {
|
if (token == null || iss == null) {
|
||||||
const decodedToken = jwt.decode(token);
|
return accountability
|
||||||
if(typeof decodedToken === 'string') return accountability; // if token is not a jwt, let directus handle it
|
}
|
||||||
if(decodedToken?.iss == 'directus') return accountability; // if token issued by directus, let directus handle it
|
|
||||||
|
|
||||||
|
|
||||||
jwt.verify(token, getKey,{
|
return new Promise(async (resolve, reject) => {
|
||||||
}, function(err, decoded) {
|
const providers = authProviders.filter((provider) => provider && iss.includes(provider.client_id));
|
||||||
if (err) {
|
if(providers.length === 0) return accountability;
|
||||||
console.log(err)
|
if(providers.length > 1) {
|
||||||
return accountability;
|
console.log("to many matching providers");
|
||||||
|
return accountability;
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = providers[0];
|
||||||
|
|
||||||
|
let promises = [];
|
||||||
|
|
||||||
|
verify_token(provider, token).then(async (result) => {
|
||||||
|
if(accountability) {
|
||||||
|
// check if role key is set else try role key
|
||||||
|
|
||||||
|
if(provider.role_key != null) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
resolve(accountability);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(decoded)
|
reject("no accountability");
|
||||||
|
|
||||||
// We have a valid token, validate user against database.
|
|
||||||
// We must also check against the correct provider.
|
|
||||||
|
|
||||||
// TODO: add cache support
|
|
||||||
// TODO: add database check
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return accountability;
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return accountability;
|
|
||||||
}
|
}
|
||||||
31
src/external-jwt/verify-token.ts
Normal file
31
src/external-jwt/verify-token.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import type { JwksClient } from "jwks-rsa";
|
||||||
|
import type { AuthProvider } from "./authProvider/get-auth-providers.js";
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
|
||||||
|
export function verify_token(provider: AuthProvider, token: string): Promise<jwt.JwtPayload> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (provider.JWKSClient === undefined){
|
||||||
|
return reject('JWKSClient not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
jwt.verify(
|
||||||
|
token,
|
||||||
|
(header, callback) => {
|
||||||
|
provider.JWKSClient?.getSigningKey(header.kid, (err, key) => {
|
||||||
|
const signingKey = key?.getPublicKey();
|
||||||
|
callback(err, signingKey);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
complete: false,
|
||||||
|
},
|
||||||
|
(err, decoded) => {
|
||||||
|
if (err || decoded === undefined || typeof decoded === 'string') {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
return resolve(decoded);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
18
src/index.ts
18
src/index.ts
@@ -4,6 +4,7 @@ import { createError } from '@directus/errors';
|
|||||||
import { getAccountabilityForToken } from './external-jwt/get-accountability-for-token';
|
import { getAccountabilityForToken } from './external-jwt/get-accountability-for-token';
|
||||||
import type { Request } from 'express';
|
import type { Request } from 'express';
|
||||||
import type { Accountability } from '@directus/types';
|
import type { Accountability } from '@directus/types';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
|
||||||
|
|
||||||
const InvalidTokenError = createError('INVALID_TOKEN_ERROR', 'Could not validate external JWT token', 500);
|
const InvalidTokenError = createError('INVALID_TOKEN_ERROR', 'Could not validate external JWT token', 500);
|
||||||
@@ -13,13 +14,22 @@ export default defineHook(({ filter }) => {
|
|||||||
|
|
||||||
// get all configuration
|
// get all configuration
|
||||||
|
|
||||||
filter('authenticate', (accountability, event, context) => {
|
filter('authenticate', (defaultAccountability, event, context) => {
|
||||||
let req = <Request>event['req'];
|
let req = <Request>event['req'];
|
||||||
let account = <Accountability>accountability;
|
if(!req.token) return defaultAccountability;
|
||||||
|
|
||||||
if(!req.token) return accountability;
|
if(!context.database) {
|
||||||
|
return defaultAccountability
|
||||||
|
}
|
||||||
|
|
||||||
return getAccountabilityForToken(req.token, account)
|
const decodedToken = jwt.decode(req.token);
|
||||||
|
if(typeof decodedToken === 'string') return defaultAccountability; // if token is not a jwt, let directus handle it
|
||||||
|
if(decodedToken?.iss == 'directus') return defaultAccountability; // if token issued by directus, let directus handle it
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return getAccountabilityForToken(req.token, decodedToken?.iss, context.accountability, context.database)
|
||||||
});
|
});
|
||||||
|
|
||||||
filter('auth.jwt', (status, user, provider) => {
|
filter('auth.jwt', (status, user, provider) => {
|
||||||
|
|||||||
@@ -1,27 +1,16 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["es2020"],
|
||||||
|
"module": "es2022",
|
||||||
|
"preserveConstEnums": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"sourceMap": true,
|
||||||
"esModuleInterop": true,
|
"target": "es2022",
|
||||||
"noImplicitAny": true,
|
"types": ["node"],
|
||||||
"noImplicitThis": true,
|
"outDir": "dist"
|
||||||
"noImplicitReturns": true,
|
},
|
||||||
"noUnusedLocals": true,
|
"include": ["src/**/*"],
|
||||||
"noUncheckedIndexedAccess": true,
|
"exclude": ["node_modules"]
|
||||||
"noUnusedParameters": true,
|
|
||||||
"alwaysStrict": true,
|
|
||||||
"strictNullChecks": true,
|
|
||||||
"strictFunctionTypes": true,
|
|
||||||
"strictBindCallApply": true,
|
|
||||||
"strictPropertyInitialization": true,
|
|
||||||
"resolveJsonModule": false,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"rootDir": "./src"
|
|
||||||
},
|
|
||||||
"include": ["./src/**/*.ts"],
|
|
||||||
"extends": "@directus/tsconfig/node18-esm"
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user