Modified temporaryPassword and filters for User, and 'options' to additionalFields' for Group

This commit is contained in:
Adina Totorean 2025-01-08 16:09:20 +02:00
parent abdd1521c0
commit 2d5eb7ce24
4 changed files with 104 additions and 189 deletions

View file

@ -14,13 +14,27 @@ import type {
} from 'n8n-workflow';
import { ApplicationError, jsonParse, NodeApiError, NodeOperationError } from 'n8n-workflow';
export async function presendTest(
this: IExecuteSingleFunctions,
requestOptions: IHttpRequestOptions,
): Promise<IHttpRequestOptions> {
console.log('requestOptions', requestOptions);
return requestOptions;
}
type UserPool = {
Arn: string;
CreationDate: number;
DeletionProtection: string;
Domain: string;
EstimatedNumberOfUsers: number;
Id: string;
LastModifiedDate: number;
MfaConfiguration: string;
Name: string;
};
type User = {
Enabled: boolean;
UserAttributes?: Array<{ Name: string; Value: string }>;
Attributes?: Array<{ Name: string; Value: string }>;
UserCreateDate: number;
UserLastModifiedDate: number;
UserStatus: string;
Username: string;
};
/*
* Helper function which stringifies the body before sending the request.
@ -36,33 +50,17 @@ export async function presendStringifyBody(
return requestOptions;
}
export async function presendFilter(
export async function presendFilters(
this: IExecuteSingleFunctions,
requestOptions: IHttpRequestOptions,
): Promise<IHttpRequestOptions> {
const additionalFields = this.getNodeParameter('additionalFields', {}) as IDataObject;
const filterAttribute = additionalFields.filters as string;
let filterType = additionalFields.filterType as string;
const filterValue = additionalFields.filterValue as string;
const filters = this.getNodeParameter('filters') as IDataObject;
const hasAnyFilter = filterAttribute || filterType || filterValue;
if (filters.length === 0) return requestOptions;
if (!hasAnyFilter) {
return requestOptions;
}
if (hasAnyFilter && (!filterAttribute || !filterType || !filterValue)) {
throw new NodeOperationError(
this.getNode(),
'If filtering is used, please provide Filter Attribute, Filter Type, and Filter Value.',
);
}
const filterTypeMapping: { [key: string]: string } = {
exactMatch: '=',
startsWith: '^=',
};
filterType = filterTypeMapping[filterType] || filterType;
const filterToSend = filters.filter as IDataObject;
const filterAttribute = filterToSend?.attribute as string;
const filterValue = filterToSend?.value as string;
let body: IDataObject;
if (typeof requestOptions.body === 'string') {
@ -72,12 +70,17 @@ export async function presendFilter(
throw new NodeOperationError(this.getNode(), 'Failed to parse requestOptions body');
}
} else {
body = requestOptions.body as IDataObject;
body = (requestOptions.body as IDataObject) || {};
}
let filter = '';
if (filterAttribute && filterValue) {
filter = `"${filterAttribute}"^="${filterValue}"`;
}
requestOptions.body = JSON.stringify({
...body,
Filter: `${filterAttribute} ${filterType} "${filterValue}"`,
Filter: filter,
});
return requestOptions;
@ -121,7 +124,6 @@ export async function presendVerifyPath(
return requestOptions;
}
/* Helper function to process attributes in UserAttributes */
export async function processAttributes(
this: IExecuteSingleFunctions,
requestOptions: IHttpRequestOptions,
@ -156,7 +158,6 @@ export async function processAttributes(
return requestOptions;
}
/* Helper function to handle pagination */
const possibleRootProperties = ['Users', 'Groups'];
export async function handlePagination(
this: IExecutePaginationFunctions,
@ -207,7 +208,6 @@ export async function handlePagination(
return aggregatedResult.map((item) => ({ json: item }));
}
/* Helper functions to handle errors */
export async function handleErrorPostReceive(
this: IExecuteSingleFunctions,
data: INodeExecutionData[],
@ -337,7 +337,6 @@ export async function handleErrorPostReceive(
return data;
}
/* Helper function used in listSearch methods */
export async function awsRequest(
this: ILoadOptionsFunctions | IPollFunctions | IExecuteSingleFunctions,
opts: IHttpRequestOptions,
@ -542,6 +541,20 @@ export async function searchGroups(
return { results, paginationToken: responseData.NextToken };
}
export function mapUserAttributes(userAttributes: Array<{ Name: string; Value: string }>): {
[key: string]: string;
} {
return userAttributes?.reduce(
(acc, { Name, Value }) => {
if (Name !== 'sub') {
acc[Name] = Value;
}
return acc;
},
{} as { [key: string]: string },
);
}
export async function simplifyData(
this: IExecuteSingleFunctions,
items: INodeExecutionData[],
@ -549,42 +562,6 @@ export async function simplifyData(
): Promise<INodeExecutionData[]> {
const simple = this.getNodeParameter('simple') as boolean;
type UserPool = {
Arn: string;
CreationDate: number;
DeletionProtection: string;
Domain: string;
EstimatedNumberOfUsers: number;
Id: string;
LastModifiedDate: number;
MfaConfiguration: string;
Name: string;
};
type User = {
Enabled: boolean;
UserAttributes?: Array<{ Name: string; Value: string }>;
Attributes?: Array<{ Name: string; Value: string }>;
UserCreateDate: number;
UserLastModifiedDate: number;
UserStatus: string;
Username: string;
};
function mapUserAttributes(userAttributes: Array<{ Name: string; Value: string }>): {
[key: string]: string;
} {
return userAttributes?.reduce(
(acc, { Name, Value }) => {
if (Name !== 'sub') {
acc[Name] = Value;
}
return acc;
},
{} as { [key: string]: string },
);
}
if (!simple) {
return items;
}
@ -643,12 +620,12 @@ export async function simplifyData(
users.forEach((user) => {
const userAttributes = user.Attributes ? mapUserAttributes(user.Attributes) : {};
processedUsers.push({
Enabled: userData.Enabled,
Enabled: user.Enabled,
...Object.fromEntries(Object.entries(userAttributes).slice(0, 6)),
UserCreateDate: userData.UserCreateDate,
UserLastModifiedDate: userData.UserLastModifiedDate,
UserStatus: userData.UserStatus,
Username: userData.Username,
UserCreateDate: user.UserCreateDate,
UserLastModifiedDate: user.UserLastModifiedDate,
UserStatus: user.UserStatus,
Username: user.Username,
});
});
return {
@ -793,31 +770,3 @@ export async function processUsersForGroups(
json: { ...item.json, Groups: processedGroups },
}));
}
//Check if needed
export async function fetchUserPoolConfig(
this: IExecuteSingleFunctions,
requestOptions: IHttpRequestOptions,
): Promise<IHttpRequestOptions> {
const userPoolIdRaw = this.getNodeParameter('userPoolId') as IDataObject;
const userPoolId = userPoolIdRaw.value as string;
if (!userPoolId) {
throw new ApplicationError('User Pool ID is required');
}
const opts: IHttpRequestOptions = {
url: '',
method: 'POST',
body: JSON.stringify({
UserPoolId: userPoolId,
}),
headers: {
'X-Amz-Target': 'AWSCognitoIdentityProviderService.DescribeUserPool',
},
};
const responseData: IDataObject = await awsRequest.call(this, opts);
return requestOptions;
}

View file

@ -34,7 +34,15 @@ export const groupOperations: INodeProperties[] = [
ignoreHttpStatusErrors: true,
},
output: {
postReceive: [handleErrorPostReceive],
postReceive: [
handleErrorPostReceive,
{
type: 'rootProperty',
properties: {
property: 'Group',
},
},
],
},
},
action: 'Create group',
@ -215,8 +223,8 @@ const createFields: INodeProperties[] = [
validateType: 'string',
},
{
displayName: 'Options',
name: 'options',
displayName: 'Additional Fields',
name: 'additionalFields',
default: {},
displayOptions: {
show: {

View file

@ -1,11 +1,9 @@
import type { INodeProperties } from 'n8n-workflow';
import {
fetchUserPoolConfig,
handleErrorPostReceive,
handlePagination,
presendFilter,
presendTest,
presendFilters,
processAttributes,
simplifyData,
} from '../GenericFunctions';
@ -55,9 +53,6 @@ export const userOperations: INodeProperties[] = [
description: 'Create a new user',
action: 'Create user',
routing: {
send: {
preSend: [presendTest, fetchUserPoolConfig],
},
request: {
method: 'POST',
headers: {
@ -121,7 +116,7 @@ export const userOperations: INodeProperties[] = [
routing: {
send: {
paginate: true,
preSend: [presendFilter],
preSend: [presendFilters],
},
operations: { pagination: handlePagination },
request: {
@ -173,7 +168,6 @@ export const userOperations: INodeProperties[] = [
description: 'Update a user',
action: 'Update user',
routing: {
send: { preSend: [presendTest] },
request: {
method: 'POST',
headers: {
@ -395,39 +389,11 @@ const createFields: INodeProperties[] = [
{
displayName: 'Temporary Password',
name: 'temporaryPasswordOptions',
type: 'options',
default: 'generatePassword',
description: 'Choose to set a password manually or one will be automatically generated',
options: [
{
name: 'Set a Password',
value: 'setPassword',
},
{
name: 'Generate a Password',
value: 'generatePassword',
},
],
routing: {
send: {
property: 'TemporaryPassword',
type: 'body',
},
},
},
{
displayName: 'Password',
name: 'password',
type: 'string',
typeOptions: { password: true },
default: '',
placeholder: 'Enter a temporary password',
description: "The user's temporary password",
displayOptions: {
show: {
temporaryPasswordOptions: ['setPassword'],
},
},
description:
"The user's temporary password that will be valid only once. If not set, Amazon Cognito will automatically generate one for you.",
routing: {
send: {
property: 'TemporaryPassword',
@ -641,11 +607,11 @@ const getAllFields: INodeProperties[] = [
description: 'Whether to return a simplified version of the response instead of the raw data',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayName: 'Filters',
name: 'filters',
type: 'fixedCollection',
placeholder: 'Add Filter',
default: [],
displayOptions: {
show: {
resource: ['user'],
@ -654,43 +620,37 @@ const getAllFields: INodeProperties[] = [
},
options: [
{
displayName: 'Filters',
name: 'filters',
type: 'options',
default: 'username',
hint: 'Make sure to select an attribute, type, and provide a value before submitting.',
description: 'The attribute to search for',
options: [
{ name: 'Cognito User Status', value: 'cognito:user_status' },
{ name: 'Email', value: 'email' },
{ name: 'Family Name', value: 'family_name' },
{ name: 'Given Name', value: 'given_name' },
{ name: 'Name', value: 'name' },
{ name: 'Phone Number', value: 'phone_number' },
{ name: 'Preferred Username', value: 'preferred_username' },
{ name: 'Status (Enabled)', value: 'status' },
{ name: 'Sub', value: 'sub' },
{ name: 'Username', value: 'username' },
displayName: 'Filter',
name: 'filter',
values: [
{
displayName: 'Attribute',
name: 'attribute',
type: 'options',
default: 'email',
description: 'The attribute to search for',
options: [
{ name: 'Cognito User Status', value: 'cognito:user_status' },
{ name: 'Email', value: 'email' },
{ name: 'Family Name', value: 'family_name' },
{ name: 'Given Name', value: 'given_name' },
{ name: 'Name', value: 'name' },
{ name: 'Phone Number', value: 'phone_number' },
{ name: 'Preferred Username', value: 'preferred_username' },
{ name: 'Status (Enabled)', value: 'status' },
{ name: 'Sub', value: 'sub' },
{ name: 'Username', value: 'username' },
],
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'The value of the attribute to search for',
},
],
},
{
displayName: 'Filter Type',
name: 'filterType',
type: 'options',
default: 'exactMatch',
description: 'The matching strategy of the filter',
options: [
{ name: 'Exact Match', value: 'exactMatch' },
{ name: 'Starts With', value: 'startsWith' },
],
},
{
displayName: 'Filter Value',
name: 'filterValue',
type: 'string',
default: '',
description: 'The value of the attribute to search for',
},
],
},
];

View file

@ -1,5 +1,6 @@
import type { INodeProperties } from 'n8n-workflow';
import { presendTest, simplifyData } from '../GenericFunctions';
import { simplifyData } from '../GenericFunctions';
export const userPoolOperations: INodeProperties[] = [
{
@ -14,9 +15,6 @@ export const userPoolOperations: INodeProperties[] = [
value: 'get',
action: 'Describe the configuration of a user pool',
routing: {
send: {
preSend: [presendTest],
},
request: {
method: 'POST',
headers: {