Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/yaml/tenant.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ databases:
identifier:
active: false # Set to true when email.unique is false
profile_required: false
# custom_password_hash: # (Early Availability) Reference action by name
# action_id: "MyPasswordHashAction"
# hash_algorithm: "bcrypt"

connections:
- name: "myad-waad"
Expand Down
119 changes: 96 additions & 23 deletions src/tools/auth0/handlers/databases.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { Management } from 'auth0';
import DefaultAPIHandler, { order } from './default';
import constants from '../../constants';
import { filterExcluded, getEnabledClients } from '../../utils';
import {
filterExcluded,
getEnabledClients,
convertActionNameToId,
convertActionIdToName,
} from '../../utils';
import { CalculatedChanges, Assets, Asset } from '../../../types';
import { paginate } from '../client';
import log from '../../../logger';
Expand All @@ -11,6 +16,7 @@ import {
processConnectionEnabledClients,
} from './connections';
import { Client } from './clients';
import { Action } from './actions';

export const schema = {
type: 'array',
Expand Down Expand Up @@ -138,6 +144,12 @@ export const schema = {
},
},
},
custom_password_hash: {
type: 'object',
properties: {
action_id: { type: 'string' },
},
},
},
},
},
Expand All @@ -158,6 +170,24 @@ export default class DatabaseHandler extends DefaultAPIHandler {
return super.objString({ name: db.name, id: db.id });
}

getFormattedOptions(options, actions: Action[] = []) {
try {
const formattedOptions = { ...options };

// Handle custom_password_hash.action_id conversion
if (options?.custom_password_hash?.action_id) {
formattedOptions.custom_password_hash = {
...options.custom_password_hash,
action_id: convertActionNameToId(options.custom_password_hash.action_id, actions),
};
}

return formattedOptions;
} catch (e) {
return {};
}
}

async validate(assets: Assets): Promise<void> {
const { databases } = assets;

Expand Down Expand Up @@ -269,24 +299,55 @@ export default class DatabaseHandler extends DefaultAPIHandler {
async getType() {
if (this.existing) return this.existing;

const connections = await paginate<Connection>(this.client.connections.list, {
strategy: [Management.ConnectionStrategyEnum.Auth0],
checkpoint: true,
});
// Fetch connections and actions concurrently
const [connections, actions] = await Promise.all([
paginate<Connection>(this.client.connections.list, {
strategy: [Management.ConnectionStrategyEnum.Auth0],
checkpoint: true,
}),
paginate<Action>(this.client.actions.list, {
paginate: true,
include_totals: true,
}),
]);

const dbConnectionsWithEnabledClients = await Promise.all(
connections.map(async (con) => {
if (!con?.id) return con;

const enabledClients = await getConnectionEnabledClients(this.client, con.id);
const connection = { ...con };

if (enabledClients && enabledClients?.length) {
return { ...con, enabled_clients: enabledClients };
connection.enabled_clients = enabledClients;
}
return con;

return connection;
})
);

// Convert action ID back to action name for export
const dbConnectionsWithActionNames = dbConnectionsWithEnabledClients.map((connection) => {
if (connection.options && 'custom_password_hash' in connection.options) {
const customPasswordHash = (connection.options as any)?.custom_password_hash;
if (customPasswordHash?.action_id) {
return {
...connection,
options: {
...connection.options,
custom_password_hash: {
...customPasswordHash,
action_id: convertActionIdToName(customPasswordHash.action_id, actions),
},
},
};
}
}
return connection;
});

// If options option is empty for all connection, log the missing options scope.
const isOptionExists = dbConnectionsWithEnabledClients.every(
const isOptionExists = dbConnectionsWithActionNames.every(
(c) => c.options && Object.keys(c.options).length > 0
);
if (!isOptionExists) {
Expand All @@ -295,7 +356,7 @@ export default class DatabaseHandler extends DefaultAPIHandler {
);
}

this.existing = dbConnectionsWithEnabledClients;
this.existing = dbConnectionsWithActionNames;

return this.existing;
}
Expand All @@ -313,25 +374,37 @@ export default class DatabaseHandler extends DefaultAPIHandler {
};

// Convert enabled_clients by name to the id
// Fetch clients, connections, and actions concurrently
const [clients, existingDatabasesConnections, actions] = await Promise.all([
paginate<Client>(this.client.clients.list, {
paginate: true,
}),
paginate<Connection>(this.client.connections.list, {
strategy: [Management.ConnectionStrategyEnum.Auth0],
checkpoint: true,
include_totals: true,
}),
paginate<Action>(this.client.actions.list, {
paginate: true,
include_totals: true,
}),
]);

const clients = await paginate<Client>(this.client.clients.list, {
paginate: true,
});

const existingDatabasesConnections = await paginate<Connection>(this.client.connections.list, {
strategy: [Management.ConnectionStrategyEnum.Auth0],
checkpoint: true,
include_totals: true,
});
const formatted = databases.map((db) => {
const { options, ...rest } = db;
const formattedOptions = this.getFormattedOptions(options, actions);
const formattedDb: any = { ...rest, options: formattedOptions };

if (db.enabled_clients) {
return {
...db,
enabled_clients: getEnabledClients(assets, db, existingDatabasesConnections, clients),
};
formattedDb.enabled_clients = getEnabledClients(
assets,
db,
existingDatabasesConnections,
clients
);
}

return db;
return formattedDb;
});

return super.calcChanges({ ...assets, databases: formatted });
Expand Down
10 changes: 10 additions & 0 deletions src/tools/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ export function convertClientNameToId(name: string, clients: Asset[]): string {
return (found && found.client_id) || name;
}

export function convertActionNameToId(name: string, actions: Asset[]): string {
const found = actions.find((a) => a.name === name);
return (found && found.id) || name;
}

export function convertActionIdToName(id: string, actions: Asset[]): string {
const found = actions.find((a) => a.id === id);
return (found && found.name) || id;
}

export function convertClientNamesToIds(names: string[], clients: Asset[]): string[] {
const resolvedNames = names.map((name) => ({ name, resolved: false }));
const result = clients.reduce((acc: string[], client): string[] => {
Expand Down
Loading