add actions
This commit is contained in:
@@ -9,6 +9,7 @@ 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 InvalidJWKSUrl = createError('INVALID_JWKS_ISSUER_ERROR', 'Could not retrieve any valid keys from JWKS_URL', 500);
|
||||
|
||||
|
||||
export interface AuthProvider {
|
||||
label: string;
|
||||
name: string;
|
||||
@@ -32,7 +33,7 @@ export interface AuthProvider {
|
||||
|
||||
export async function getAuthProviders(): Promise<AuthProvider[]> {
|
||||
console.log("calling auth providers")
|
||||
return new Promise(async (resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const authProviders: AuthProvider[] = toArray(env['AUTH_PROVIDERS'])
|
||||
.filter((provider) => provider && env[`AUTH_${provider.toUpperCase()}_DRIVER`] === ('openid' || 'oauth2'))
|
||||
.map((provider) => ({
|
||||
@@ -57,7 +58,7 @@ export async function getAuthProviders(): Promise<AuthProvider[]> {
|
||||
|
||||
|
||||
|
||||
var promises = [];
|
||||
const promises = [];
|
||||
|
||||
for (const authProvider of authProviders) {
|
||||
switch (authProvider.driver) {
|
||||
@@ -84,65 +85,56 @@ export async function getAuthProviders(): Promise<AuthProvider[]> {
|
||||
});
|
||||
}
|
||||
|
||||
function getJWKS(provider: AuthProvider): Promise<AuthProvider>{
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if(provider.jwks_keys != null && provider.issuer_url == null && provider.jwks_url == null) {
|
||||
|
||||
const jwksClient = new JwksClient({
|
||||
getKeysInterceptor: () => {
|
||||
return JSON.parse(provider.jwks_keys);
|
||||
},
|
||||
jwksUri: ''
|
||||
})
|
||||
|
||||
|
||||
provider.JWKSClient = jwksClient;
|
||||
|
||||
resolve(provider);
|
||||
return;
|
||||
}
|
||||
|
||||
if(provider.issuer_url && !provider.jwks_url) {
|
||||
//try to discover with openid
|
||||
try {
|
||||
const issuer = await Issuer.discover(provider.issuer_url);
|
||||
if(issuer.metadata.jwks_uri != null) {
|
||||
provider.jwks_url = issuer.metadata.jwks_uri;
|
||||
}
|
||||
} catch (error) {
|
||||
//throw new InvalidJWKIssuerMetadata();
|
||||
reject("Could not discover JWKS_URL from openid metadata")
|
||||
}
|
||||
}
|
||||
|
||||
if(!provider.jwks_url) {
|
||||
reject("No JWKS_URL or JWKS_KEYS and could not discover JWKS_URL from openid metadata")
|
||||
return;
|
||||
}
|
||||
|
||||
async function getJWKS(provider: AuthProvider) {
|
||||
if(provider.jwks_keys !== undefined && provider.issuer_url == null && provider.jwks_url == null) {
|
||||
const jwks_keys = JSON.parse(provider.jwks_keys);
|
||||
const jwksClient = new JwksClient({
|
||||
jwksUri: provider.jwks_url,
|
||||
cache: true,
|
||||
cacheMaxAge: 36000000, // 10 hours
|
||||
cacheMaxEntries: 10,
|
||||
timeout: 30000, // 30 seconds
|
||||
});
|
||||
|
||||
// try to get the keys
|
||||
try {
|
||||
const keys = await jwksClient.getSigningKeys()
|
||||
if (keys.length == 0) {
|
||||
reject("Could not retrieve any valid keys from JWKS_URL")
|
||||
}
|
||||
} catch (error) {
|
||||
throw new InvalidJWKSUrl();
|
||||
}
|
||||
getKeysInterceptor: () => {
|
||||
return jwks_keys;
|
||||
},
|
||||
jwksUri: ''
|
||||
})
|
||||
|
||||
|
||||
provider.JWKSClient = jwksClient;
|
||||
|
||||
resolve(provider);
|
||||
}
|
||||
|
||||
})
|
||||
if(provider.issuer_url && !provider.jwks_url) {
|
||||
//try to discover with openid
|
||||
const issuer = await Issuer.discover(provider.issuer_url);
|
||||
if(issuer.metadata.jwks_uri != null) {
|
||||
provider.jwks_url = issuer.metadata.jwks_uri;
|
||||
}
|
||||
}
|
||||
|
||||
if (provider.jwks_url == null) throw new InvalidJWKIssuerMetadata();
|
||||
|
||||
const jwksClient = await getJWKSClient(provider.jwks_url);
|
||||
|
||||
provider.JWKSClient = jwksClient;
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
async function getJWKSClient(url: string) {
|
||||
const jwksClient = new JwksClient({
|
||||
jwksUri: url,
|
||||
cache: true,
|
||||
cacheMaxAge: 36000000, // 10 hours
|
||||
cacheMaxEntries: 10,
|
||||
timeout: 30000, // 30 seconds
|
||||
});
|
||||
|
||||
// try to get the keys
|
||||
try {
|
||||
const keys = await jwksClient.getSigningKeys()
|
||||
if (keys.length == 0) {
|
||||
throw new InvalidJWKSUrl();
|
||||
}
|
||||
} catch (error) {
|
||||
throw new InvalidJWKSUrl();
|
||||
}
|
||||
|
||||
return jwksClient;
|
||||
}
|
||||
@@ -10,6 +10,28 @@ import { requireYAML } from '../require-yaml.js';
|
||||
const allowedEnvironmentVars = [
|
||||
// general
|
||||
'CONFIG_PATH',
|
||||
// cache
|
||||
'CACHE_ENABLED',
|
||||
'CACHE_TTL',
|
||||
'CACHE_CONTROL_S_MAXAGE',
|
||||
'CACHE_AUTO_PURGE',
|
||||
'CACHE_AUTO_PURGE_IGNORE_LIST',
|
||||
'CACHE_SYSTEM_TTL',
|
||||
'CACHE_SCHEMA',
|
||||
'CACHE_PERMISSIONS',
|
||||
'CACHE_NAMESPACE',
|
||||
'CACHE_STORE',
|
||||
'CACHE_STATUS_HEADER',
|
||||
'CACHE_VALUE_MAX_SIZE',
|
||||
'CACHE_SKIP_ALLOWED',
|
||||
'CACHE_HEALTHCHECK_THRESHOLD',
|
||||
// redis
|
||||
'REDIS',
|
||||
'REDIS_HOST',
|
||||
'REDIS_PORT',
|
||||
'REDIS_USERNAME',
|
||||
'REDIS_PASSWORD',
|
||||
'REDIS_JWT_DB',
|
||||
// auth
|
||||
'AUTH_PROVIDERS',
|
||||
'AUTH_.+_DRIVER',
|
||||
@@ -29,19 +51,7 @@ const allowedEnvironmentVars = [
|
||||
'AUTH_.+_LABEL',
|
||||
'AUTH_.+_PARAMS',
|
||||
'AUTH_.+_ISSUER_URL',
|
||||
'AUTH_.+_AUTH_REQUIRE_VERIFIED_EMAIL',
|
||||
'AUTH_.+_CLIENT_URL',
|
||||
'AUTH_.+_BIND_DN',
|
||||
'AUTH_.+_BIND_PASSWORD',
|
||||
'AUTH_.+_USER_DN',
|
||||
'AUTH_.+_USER_ATTRIBUTE',
|
||||
'AUTH_.+_USER_SCOPE',
|
||||
'AUTH_.+_MAIL_ATTRIBUTE',
|
||||
'AUTH_.+_FIRST_NAME_ATTRIBUTE',
|
||||
'AUTH_.+_LAST_NAME_ATTRIBUTE',
|
||||
'AUTH_.+_GROUP_DN',
|
||||
'AUTH_.+_GROUP_ATTRIBUTE',
|
||||
'AUTH_.+_GROUP_SCOPE',
|
||||
'AUTH_.+_TRUSTED',
|
||||
'AUTH_.+_JWKS_URL',
|
||||
'AUTH_.+_JWKS_KEYS',
|
||||
@@ -49,8 +59,6 @@ const allowedEnvironmentVars = [
|
||||
'AUTH_.+_JWT_ADMIN_KEY',
|
||||
'AUTH_.+_JWT_APP_KEY',
|
||||
'AUTH_.+_JWT_USEDB',
|
||||
'AUTH_.+_IDP.+',
|
||||
'AUTH_.+_SP.+',
|
||||
].map((name) => new RegExp(`^${name}$`));
|
||||
|
||||
const acceptedEnvTypes = ['string', 'number', 'regex', 'array', 'json'];
|
||||
@@ -97,7 +105,7 @@ export function refreshEnv(): void {
|
||||
|
||||
|
||||
|
||||
function toBoolean(value: any): boolean {
|
||||
function toBoolean(value: string | boolean | number): boolean {
|
||||
return value === 'true' || value === true || value === '1' || value === 1;
|
||||
}
|
||||
|
||||
@@ -110,6 +118,7 @@ function processConfiguration() {
|
||||
const fileExt = path.extname(configPath).toLowerCase();
|
||||
|
||||
if (fileExt === '.js') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const module = require(configPath);
|
||||
const exported = module.default || module;
|
||||
|
||||
@@ -161,8 +170,8 @@ function getEnvironmentValueWithPrefix(envArray: Array<string>): Array<string |
|
||||
}
|
||||
|
||||
function getEnvironmentValueByType(envVariableString: string) {
|
||||
const variableType = getVariableType(envVariableString)!;
|
||||
const envVariableValue = getEnvVariableValue(envVariableString, variableType)!;
|
||||
const variableType = getVariableType(envVariableString) ?? false;
|
||||
const envVariableValue = getEnvVariableValue(envVariableString, variableType) ?? false;
|
||||
|
||||
switch (variableType) {
|
||||
case 'number':
|
||||
@@ -285,7 +294,7 @@ function processValues(env: Record<string, any>) {
|
||||
return env;
|
||||
}
|
||||
|
||||
function tryJSON(value: any) {
|
||||
function tryJSON(value: string) {
|
||||
try {
|
||||
return parseJSON(value);
|
||||
} catch {
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import type { Accountability } from '@directus/types';
|
||||
import type { JwtHeader, VerifyCallback} from 'jsonwebtoken';
|
||||
import {JsonWebTokenError} from 'jsonwebtoken';
|
||||
import { getAuthProviders } from './authProvider/get-auth-providers.js';
|
||||
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 = 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
|
||||
|
||||
@@ -38,7 +35,7 @@ export async function getAccountabilityForToken(
|
||||
return accountability
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const providers = authProviders.filter((provider) => provider && iss.includes(provider.client_id));
|
||||
if(providers.length === 0) return accountability;
|
||||
if(providers.length > 1) {
|
||||
@@ -48,7 +45,7 @@ export async function getAccountabilityForToken(
|
||||
|
||||
const provider = providers[0];
|
||||
|
||||
let promises = [];
|
||||
|
||||
|
||||
verify_token(provider, token).then(async (result) => {
|
||||
if(accountability) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { JwksClient } from "jwks-rsa";
|
||||
import type { AuthProvider } from "./authProvider/get-auth-providers.js";
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
|
||||
13
src/index.ts
13
src/index.ts
@@ -1,21 +1,14 @@
|
||||
import { defineHook } from '@directus/extensions-sdk';
|
||||
import { createError } from '@directus/errors';
|
||||
|
||||
import { getAccountabilityForToken } from './external-jwt/get-accountability-for-token';
|
||||
import type { Request } from 'express';
|
||||
import type { Accountability } from '@directus/types';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
|
||||
const InvalidTokenError = createError('INVALID_TOKEN_ERROR', 'Could not validate external JWT token', 500);
|
||||
|
||||
|
||||
export default defineHook(({ filter }) => {
|
||||
|
||||
// get all configuration
|
||||
|
||||
filter('authenticate', (defaultAccountability, event, context) => {
|
||||
let req = <Request>event['req'];
|
||||
const req = <Request>event['req'];
|
||||
if(!req.token) return defaultAccountability;
|
||||
|
||||
if(!context.database) {
|
||||
@@ -32,9 +25,9 @@ export default defineHook(({ filter }) => {
|
||||
return getAccountabilityForToken(req.token, decodedToken?.iss, context.accountability, context.database)
|
||||
});
|
||||
|
||||
filter('auth.jwt', (status, user, provider) => {
|
||||
/*filter('auth.jwt', (status, user, provider) => {
|
||||
|
||||
})
|
||||
})*/
|
||||
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user