add actions

This commit is contained in:
Kristoffer
2023-08-01 14:42:30 +00:00
parent e44a5bb527
commit 4dc8fbdce6
20 changed files with 52405 additions and 631 deletions

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
import type { JwksClient } from "jwks-rsa";
import type { AuthProvider } from "./authProvider/get-auth-providers.js";
import jwt from 'jsonwebtoken';

View File

@@ -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) => {
})
})*/
});