@@ -12,28 +12,30 @@ const InvalidJWKKeys = createError('INVALID_JWKS_ISSUER_ERROR', 'No signing keys
|
||||
|
||||
|
||||
export interface AuthProvider {
|
||||
label: string;
|
||||
name: string;
|
||||
driver: string;
|
||||
icon?: string;
|
||||
client_id: string;
|
||||
client_secret?: string;
|
||||
trusted: boolean;
|
||||
jwks_url?: string;
|
||||
jwks_keys?: string;
|
||||
issuer_url?: string;
|
||||
|
||||
admin_key?: string;
|
||||
app_key?: string;
|
||||
role_key?: string;
|
||||
JWKSClient?: JwksClient;
|
||||
use_database?: boolean;
|
||||
label: string;
|
||||
name: string;
|
||||
driver: string;
|
||||
icon?: string;
|
||||
client_id: string;
|
||||
client_secret?: string;
|
||||
trusted: boolean;
|
||||
jwks_url?: string;
|
||||
jwks_keys?: string;
|
||||
issuer_url?: string;
|
||||
|
||||
admin_key?: string;
|
||||
app_key?: string;
|
||||
role_key?: string;
|
||||
JWKSClient?: JwksClient;
|
||||
use_database?: boolean;
|
||||
|
||||
initial_role?: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function getAuthProviders(): Promise<AuthProvider[]> {
|
||||
console.log("calling auth providers")
|
||||
console.log("calling auth providers _")
|
||||
return new Promise((resolve, reject) => {
|
||||
const authProviders: AuthProvider[] = toArray(env['AUTH_PROVIDERS'])
|
||||
.filter((provider) => provider && env[`AUTH_${provider.toUpperCase()}_DRIVER`] === ('openid' || 'oauth2'))
|
||||
@@ -52,6 +54,8 @@ export async function getAuthProviders(): Promise<AuthProvider[]> {
|
||||
client_id: env[`AUTH_${provider.toUpperCase()}_CLIENT_ID`],
|
||||
client_secret: env[`AUTH_${provider.toUpperCase()}_CLIENT_SECRET`],
|
||||
use_database: env[`AUTH_${provider.toUpperCase()}_JWT_USEDB`],
|
||||
|
||||
initial_role: env[`AUTH_${provider.toUpperCase()}_INITIAL_ROLE`]
|
||||
}));
|
||||
|
||||
|
||||
@@ -78,6 +82,7 @@ export async function getAuthProviders(): Promise<AuthProvider[]> {
|
||||
}
|
||||
|
||||
Promise.all(promises).then((values) => {
|
||||
console.log("resolved auth providers", values)
|
||||
resolve(values);
|
||||
}).catch((error) => {
|
||||
reject(error);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Accountability } from '@directus/types';
|
||||
import { getAuthProviders } from './authProvider/get-auth-providers.js';
|
||||
|
||||
import { verify_token } from './verify-token.js';
|
||||
import { CacheEnabled, CacheGet, CacheSet } from './cache.js';
|
||||
import type { Knex } from 'knex';
|
||||
import type { Accountability } from "@directus/types";
|
||||
import { getAuthProviders } from "./authProvider/get-auth-providers.js";
|
||||
|
||||
import { verify_token } from "./verify-token.js";
|
||||
import { CacheEnabled, CacheGet, CacheSet } from "./cache.js";
|
||||
import type { Knex } from "knex";
|
||||
import * as uuid from "uuid";
|
||||
|
||||
const authProviders = await getAuthProviders();
|
||||
|
||||
@@ -14,111 +14,157 @@ const NoValidKeysError = createError('INVALID_JWKS_ISSUER_ERROR', 'could not ret
|
||||
const NoAuthProvidersError = createError('INVALID_JWKS_ISSUER_ERROR', 'No auth providers in the list', 500);
|
||||
*/
|
||||
|
||||
const getUser = async (
|
||||
database: Knex,
|
||||
externalIdentifier: string | undefined,
|
||||
provider: string
|
||||
) => {
|
||||
return database
|
||||
.select(
|
||||
"directus_users.id",
|
||||
"directus_users.role",
|
||||
"directus_policies.admin_access"
|
||||
)
|
||||
.from("directus_users")
|
||||
.leftJoin("directus_roles", "directus_users.role", "directus_roles.id")
|
||||
.leftJoin("directus_access", "directus_users.role", "directus_access.role")
|
||||
.leftJoin(
|
||||
"directus_policies",
|
||||
"directus_access.policy",
|
||||
"directus_policies.id"
|
||||
)
|
||||
.where({
|
||||
"directus_users.external_identifier": externalIdentifier,
|
||||
"directus_users.provider": provider,
|
||||
})
|
||||
.first();
|
||||
};
|
||||
|
||||
const insertUser = async (database: Knex, user: Record<string, any>) => {
|
||||
return database("directus_users").insert(user).returning("*");
|
||||
};
|
||||
|
||||
// TODO: optimize this function, reduce the amount of loops
|
||||
|
||||
|
||||
export async function getAccountabilityForToken(
|
||||
token: string | null,
|
||||
iss: string[] | string | undefined,
|
||||
accountability: Accountability | null,
|
||||
database: Knex
|
||||
token: string | null,
|
||||
iss: string[] | string | undefined,
|
||||
accountability: Accountability | null,
|
||||
database: Knex
|
||||
): Promise<Accountability> {
|
||||
if (accountability == null) {
|
||||
accountability = {
|
||||
user: null,
|
||||
role: null,
|
||||
admin: false,
|
||||
app: false,
|
||||
};
|
||||
}
|
||||
console.log(
|
||||
"getAccountabilityForToken called with token",
|
||||
token,
|
||||
"and iss",
|
||||
iss,
|
||||
"and accountability",
|
||||
accountability
|
||||
);
|
||||
if (accountability == null) {
|
||||
accountability = {
|
||||
user: null,
|
||||
role: null,
|
||||
admin: false,
|
||||
app: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (token == null || iss == null) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (token == null || iss == null) {
|
||||
return accountability;
|
||||
}
|
||||
|
||||
const provider = providers[0];
|
||||
const providers = authProviders.filter(
|
||||
(provider) =>
|
||||
provider.issuer_url && provider.issuer_url.includes(iss.toString())
|
||||
);
|
||||
|
||||
|
||||
if (providers.length === 0) return accountability;
|
||||
if (providers.length > 1) {
|
||||
return accountability;
|
||||
}
|
||||
|
||||
try {
|
||||
const provider = providers[0];
|
||||
|
||||
|
||||
const result = await verify_token(provider, token)
|
||||
try {
|
||||
const result = await verify_token(provider, token);
|
||||
|
||||
|
||||
|
||||
if(provider.use_database) { // use database to get user
|
||||
// TODO: Add caching to this function
|
||||
if (CacheEnabled() && result.sub) {
|
||||
|
||||
const cachedAccountability = await CacheGet(result.sub);
|
||||
if (cachedAccountability) {
|
||||
return cachedAccountability;
|
||||
}
|
||||
}
|
||||
if (provider.use_database) {
|
||||
// use database to get user
|
||||
// TODO: Add caching to this function
|
||||
if (CacheEnabled() && result.sub) {
|
||||
const cachedAccountability = await CacheGet(result.sub);
|
||||
if (cachedAccountability) {
|
||||
return cachedAccountability;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
try {
|
||||
let user = await getUser(database, result.sub, provider.name);
|
||||
|
||||
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;
|
||||
console.debug("User found in database:", user);
|
||||
|
||||
if (CacheEnabled() && result.sub) {
|
||||
CacheSet(result.sub, accountability);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
const role = provider.initial_role
|
||||
user = await insertUser(database, {
|
||||
id: uuid.v4(),
|
||||
role: role,
|
||||
provider: provider.name,
|
||||
external_identifier: result.sub,
|
||||
});
|
||||
console.debug("Inserted new user:", user);
|
||||
}
|
||||
|
||||
return accountability;
|
||||
}
|
||||
if (user) {
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
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(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;
|
||||
if (CacheEnabled() && result.sub) {
|
||||
CacheSet(result.sub, accountability);
|
||||
}
|
||||
|
||||
}
|
||||
console.log(
|
||||
"Returning accountability from database:",
|
||||
accountability
|
||||
);
|
||||
|
||||
return accountability;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error getting user from database:", error);
|
||||
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;
|
||||
// accountability.role = "d737d4bd-ae35-4a68-a907-e913bcdfcc53";
|
||||
// accountability.admin = true;
|
||||
// accountability.app = true;
|
||||
} catch (error) {
|
||||
return accountability;
|
||||
}
|
||||
|
||||
return accountability;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ export default defineHook<HookConfig>(({ filter }) => {
|
||||
// get all configuration
|
||||
|
||||
filter('authenticate', (defaultAccountability: Accountability, event, context: EventContext) => {
|
||||
console.log("authenticate hook called");
|
||||
const req = <Request>event['req'];
|
||||
if(!req.token) return defaultAccountability;
|
||||
|
||||
@@ -20,6 +21,7 @@ export default defineHook<HookConfig>(({ filter }) => {
|
||||
|
||||
|
||||
const decodedToken = jwt.decode(req.token);
|
||||
console.log("decoded token", decodedToken);
|
||||
|
||||
if(typeof decodedToken === 'string' || decodedToken == null) 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
|
||||
@@ -30,6 +32,7 @@ export default defineHook<HookConfig>(({ filter }) => {
|
||||
return getAccountabilityForToken(req.token, decodedToken?.iss, context.accountability, context.database)
|
||||
});
|
||||
|
||||
|
||||
/*filter('auth.jwt', (status, user, provider) => {
|
||||
|
||||
})*/
|
||||
|
||||
Reference in New Issue
Block a user