diff --git a/packages/nodes-base/credentials/BitwardenApi.credentials.ts b/packages/nodes-base/credentials/BitwardenApi.credentials.ts new file mode 100644 index 0000000000..93a3f3f1d6 --- /dev/null +++ b/packages/nodes-base/credentials/BitwardenApi.credentials.ts @@ -0,0 +1,56 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +// https://bitwarden.com/help/article/public-api/#authentication + +export class BitwardenApi implements ICredentialType { + name = 'bitwardenApi'; + displayName = 'Bitwarden API'; + documentationUrl = 'bitwarden'; + properties = [ + { + displayName: 'Client ID', + name: 'clientId', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Client Secret', + name: 'clientSecret', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Environment', + name: 'environment', + type: 'options' as NodePropertyTypes, + default: 'cloudHosted', + options: [ + { + name: 'Cloud-hosted', + value: 'cloudHosted', + }, + { + name: 'Self-hosted', + value: 'selfHosted', + }, + ], + }, + { + displayName: 'Self-hosted domain', + name: 'domain', + type: 'string' as NodePropertyTypes, + default: '', + placeholder: 'https://www.mydomain.com', + displayOptions: { + show: { + environment: [ + 'selfHosted', + ], + }, + }, + }, + ]; +} diff --git a/packages/nodes-base/nodes/Bitwarden/Bitwarden.node.ts b/packages/nodes-base/nodes/Bitwarden/Bitwarden.node.ts new file mode 100644 index 0000000000..7b08eda740 --- /dev/null +++ b/packages/nodes-base/nodes/Bitwarden/Bitwarden.node.ts @@ -0,0 +1,500 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + bitwardenApiRequest as tokenlessBitwardenApiRequest, + getAccessToken, + handleGetAll as tokenlessHandleGetAll, + loadResource, +} from './GenericFunctions'; + +import { + collectionFields, + collectionOperations, + CollectionUpdateFields, +} from './descriptions/CollectionDescription'; + +import { + eventFields, + eventOperations, +} from './descriptions/EventDescription'; + +import { + GroupCreationAdditionalFields, + groupFields, + groupOperations, + GroupUpdateFields, +} from './descriptions/GroupDescription'; + +import { + MemberCreationAdditionalFields, + memberFields, + memberOperations, + MemberUpdateFields, +} from './descriptions/MemberDescription'; + +import { + isEmpty, + partialRight, +} from 'lodash'; + +export class Bitwarden implements INodeType { + description: INodeTypeDescription = { + displayName: 'Bitwarden', + name: 'bitwarden', + icon: 'file:bitwarden.svg', + group: ['transform'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume the Bitwarden API', + defaults: { + name: 'Bitwarden', + color: '#175DDC', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'bitwardenApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Collection', + value: 'collection', + }, + { + name: 'Event', + value: 'event', + }, + { + name: 'Group', + value: 'group', + }, + { + name: 'Member', + value: 'member', + }, + ], + default: 'collection', + description: 'Resource to consume', + }, + ...collectionOperations, + ...collectionFields, + ...eventOperations, + ...eventFields, + ...groupOperations, + ...groupFields, + ...memberOperations, + ...memberFields, + ], + }; + + methods = { + loadOptions: { + async getGroups(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'groups'); + }, + + async getCollections(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'collections'); + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + let responseData; + const returnData: IDataObject[] = []; + + const token = await getAccessToken.call(this); + const bitwardenApiRequest = partialRight(tokenlessBitwardenApiRequest, token); + const handleGetAll = partialRight(tokenlessHandleGetAll, token); + + for (let i = 0; i < items.length; i++) { + + if (resource === 'collection') { + + // ********************************************************************* + // collection + // ********************************************************************* + + if (operation === 'delete') { + + // ---------------------------------- + // collection: delete + // ---------------------------------- + + const id = this.getNodeParameter('collectionId', i); + const endpoint = `/public/collections/${id}`; + responseData = await bitwardenApiRequest.call(this, 'DELETE', endpoint, {}, {}); + responseData = { success: true }; + + } else if (operation === 'get') { + + // ---------------------------------- + // collection: get + // ---------------------------------- + + const id = this.getNodeParameter('collectionId', i); + const endpoint = `/public/collections/${id}`; + responseData = await bitwardenApiRequest.call(this, 'GET', endpoint, {}, {}); + + } else if (operation === 'getAll') { + + // ---------------------------------- + // collection: getAll + // ---------------------------------- + + const endpoint = '/public/collections'; + responseData = await handleGetAll.call(this, i, 'GET', endpoint, {}, {}); + + } else if (operation === 'update') { + + // ---------------------------------- + // collection: update + // ---------------------------------- + + const updateFields = this.getNodeParameter('updateFields', i) as CollectionUpdateFields; + + if (isEmpty(updateFields)) { + throw new Error(`Please enter at least one field to update for the ${resource}.`); + } + + const { groups, externalId } = updateFields; + + const body = {} as IDataObject; + + if (groups) { + body.groups = groups.map((groupId) => ({ + id: groupId, + ReadOnly: false, + })); + } + + if (externalId) { + body.externalId = externalId; + } + + const id = this.getNodeParameter('collectionId', i); + const endpoint = `/public/collections/${id}`; + responseData = await bitwardenApiRequest.call(this, 'PUT', endpoint, {}, body); + + } + + } else if (resource === 'event') { + + // ********************************************************************* + // event + // ********************************************************************* + + if (operation === 'getAll') { + + // ---------------------------------- + // event: getAll + // ---------------------------------- + + const filters = this.getNodeParameter('filters', i) as IDataObject; + const qs = isEmpty(filters) ? {} : filters; + const endpoint = '/public/events'; + responseData = await handleGetAll.call(this, i, 'GET', endpoint, qs, {}); + + } + + } else if (resource === 'group') { + + // ********************************************************************* + // group + // ********************************************************************* + + if (operation === 'create') { + + // ---------------------------------- + // group: create + // ---------------------------------- + + const body = { + name: this.getNodeParameter('name', i), + AccessAll: this.getNodeParameter('accessAll', i), + } as IDataObject; + + const { + collections, + externalId, + } = this.getNodeParameter('additionalFields', i) as GroupCreationAdditionalFields; + + if (collections) { + body.collections = collections.map((collectionId) => ({ + id: collectionId, + ReadOnly: false, + })); + } + + if (externalId) { + body.externalId = externalId; + } + + const endpoint = '/public/groups'; + responseData = await bitwardenApiRequest.call(this, 'POST', endpoint, {}, body); + + } else if (operation === 'delete') { + + // ---------------------------------- + // group: delete + // ---------------------------------- + + const id = this.getNodeParameter('groupId', i); + const endpoint = `/public/groups/${id}`; + responseData = await bitwardenApiRequest.call(this, 'DELETE', endpoint, {}, {}); + responseData = { success: true }; + + } else if (operation === 'get') { + + // ---------------------------------- + // group: get + // ---------------------------------- + + const id = this.getNodeParameter('groupId', i); + const endpoint = `/public/groups/${id}`; + responseData = await bitwardenApiRequest.call(this, 'GET', endpoint, {}, {}); + + } else if (operation === 'getAll') { + + // ---------------------------------- + // group: getAll + // ---------------------------------- + + const endpoint = '/public/groups'; + responseData = await handleGetAll.call(this, i, 'GET', endpoint, {}, {}); + + } else if (operation === 'getMembers') { + + // ---------------------------------- + // group: getMembers + // ---------------------------------- + + const id = this.getNodeParameter('groupId', i); + const endpoint = `/public/groups/${id}/member-ids`; + responseData = await bitwardenApiRequest.call(this, 'GET', endpoint, {}, {}); + responseData = responseData.map((memberId: string) => ({ memberId })); + + } else if (operation === 'update') { + + // ---------------------------------- + // group: update + // ---------------------------------- + + const body = {} as IDataObject; + + const updateFields = this.getNodeParameter('updateFields', i) as GroupUpdateFields; + + if (isEmpty(updateFields)) { + throw new Error(`Please enter at least one field to update for the ${resource}.`); + } + + const { name, collections, externalId, accessAll } = updateFields; + + if (collections) { + body.collections = collections.map((collectionId) => ({ + id: collectionId, + ReadOnly: false, + })); + } + + if (name) { + body.name = name; + } + + if (externalId) { + body.externalId = externalId; + } + + if (accessAll !== undefined) { + body.AccessAll = accessAll; + } + + const id = this.getNodeParameter('groupId', i); + const endpoint = `/public/groups/${id}`; + responseData = await bitwardenApiRequest.call(this, 'PUT', endpoint, {}, body); + + } else if (operation === 'updateMembers') { + + // ---------------------------------- + // group: updateMembers + // ---------------------------------- + + const memberIds = this.getNodeParameter('memberIds', i) as string; + + const body = { + memberIds: memberIds.includes(',') ? memberIds.split(',') : [memberIds], + }; + + const groupId = this.getNodeParameter('groupId', i); + const endpoint = `/public/groups/${groupId}/member-ids`; + responseData = await bitwardenApiRequest.call(this, 'PUT', endpoint, {}, body); + responseData = { success: true }; + } + + } else if (resource === 'member') { + + // ********************************************************************* + // member + // ********************************************************************* + + if (operation === 'create') { + + // ---------------------------------- + // member: create + // ---------------------------------- + + const body = { + email: this.getNodeParameter('email', i), + type: this.getNodeParameter('type', i), + AccessAll: this.getNodeParameter('accessAll', i), + } as IDataObject; + + const { + collections, + externalId, + } = this.getNodeParameter('additionalFields', i) as MemberCreationAdditionalFields; + + if (collections) { + body.collections = collections.map((collectionId) => ({ + id: collectionId, + ReadOnly: false, + })); + } + + if (externalId) { + body.externalId = externalId; + } + + const endpoint = '/public/members/'; + responseData = await bitwardenApiRequest.call(this, 'POST', endpoint, {}, body); + + } else if (operation === 'delete') { + + // ---------------------------------- + // member: delete + // ---------------------------------- + + const id = this.getNodeParameter('memberId', i); + const endpoint = `/public/members/${id}`; + responseData = await bitwardenApiRequest.call(this, 'DELETE', endpoint, {}, {}); + responseData = { success: true }; + + } else if (operation === 'get') { + + // ---------------------------------- + // member: get + // ---------------------------------- + + const id = this.getNodeParameter('memberId', i); + const endpoint = `/public/members/${id}`; + responseData = await bitwardenApiRequest.call(this, 'GET', endpoint, {}, {}); + + } else if (operation === 'getAll') { + + // ---------------------------------- + // member: getAll + // ---------------------------------- + + const endpoint = '/public/members'; + responseData = await handleGetAll.call(this, i, 'GET', endpoint, {}, {}); + + } else if (operation === 'getGroups') { + + // ---------------------------------- + // member: getGroups + // ---------------------------------- + + const id = this.getNodeParameter('memberId', i); + const endpoint = `/public/members/${id}/group-ids`; + responseData = await bitwardenApiRequest.call(this, 'GET', endpoint, {}, {}); + responseData = responseData.map((groupId: string) => ({ groupId })); + + } else if (operation === 'update') { + + // ---------------------------------- + // member: update + // ---------------------------------- + + const body = {} as IDataObject; + + const updateFields = this.getNodeParameter('updateFields', i) as MemberUpdateFields; + + if (isEmpty(updateFields)) { + throw new Error(`Please enter at least one field to update for the ${resource}.`); + } + + const { accessAll, collections, externalId, type } = updateFields; + + if (accessAll !== undefined) { + body.AccessAll = accessAll; + } + + if (collections) { + body.collections = collections.map((collectionId) => ({ + id: collectionId, + ReadOnly: false, + })); + } + + if (externalId) { + body.externalId = externalId; + } + + if (type !== undefined) { + body.Type = type; + } + + const id = this.getNodeParameter('memberId', i); + const endpoint = `/public/members/${id}`; + responseData = await bitwardenApiRequest.call(this, 'PUT', endpoint, {}, body); + + } else if (operation === 'updateGroups') { + + // ---------------------------------- + // member: updateGroups + // ---------------------------------- + + const groupIds = this.getNodeParameter('groupIds', i) as string; + + const body = { + groupIds: groupIds.includes(',') ? groupIds.split(',') : [groupIds], + }; + + const memberId = this.getNodeParameter('memberId', i); + const endpoint = `/public/members/${memberId}/group-ids`; + responseData = await bitwardenApiRequest.call(this, 'PUT', endpoint, {}, body); + responseData = { success: true }; + } + } + + Array.isArray(responseData) + ? returnData.push(...responseData) + : returnData.push(responseData); + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Bitwarden/GenericFunctions.ts b/packages/nodes-base/nodes/Bitwarden/GenericFunctions.ts new file mode 100644 index 0000000000..5e1c3e90c6 --- /dev/null +++ b/packages/nodes-base/nodes/Bitwarden/GenericFunctions.ts @@ -0,0 +1,168 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodePropertyOptions, +} from 'n8n-workflow'; + +import { + OptionsWithUri, +} from 'request'; + +/** + * Make an authenticated API request to Bitwarden. + */ +export async function bitwardenApiRequest( + this: IExecuteFunctions | ILoadOptionsFunctions, + method: string, + endpoint: string, + qs: IDataObject, + body: IDataObject, + token: string, +): Promise { // tslint:disable-line:no-any + + const options: OptionsWithUri = { + headers: { + 'user-agent': 'n8n', + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + method, + qs, + body, + uri: `${getBaseUrl.call(this)}${endpoint}`, + json: true, + }; + + if (!Object.keys(body).length) { + delete options.body; + } + + if (!Object.keys(qs).length) { + delete options.qs; + } + + try { + return await this.helpers.request!(options); + } catch (error) { + + if (error.statusCode === 404) { + throw new Error('Bitwarden error response [404]: Not found'); + } + + if (error?.response?.body?.Message) { + const message = error?.response?.body?.Message; + throw new Error(`Bitwarden error response [${error.statusCode}]: ${message}`); + } + //TODO handle Errors array + throw error; + } +} + +/** + * Retrieve the access token needed for every API request to Bitwarden. + */ +export async function getAccessToken( + this: IExecuteFunctions | ILoadOptionsFunctions, +): Promise { // tslint:disable-line:no-any + + const credentials = this.getCredentials('bitwardenApi') as IDataObject; + + const options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + method: 'POST', + form: { + client_id: credentials.clientId, + client_secret: credentials.clientSecret, + grant_type: 'client_credentials', + scope: 'api.organization', + deviceName: 'n8n', + deviceType: 2, // https://github.com/bitwarden/server/blob/master/src/Core/Enums/DeviceType.cs + deviceIdentifier: 'n8n', + }, + uri: getTokenUrl.call(this), + json: true, + }; + + try { + const { access_token } = await this.helpers.request!(options); + return access_token; + } catch (error) { + throw error; + } +} + +/** + * Supplement a `getAll` operation with `returnAll` and `limit` parameters. + */ +export async function handleGetAll( + this: IExecuteFunctions, + i: number, + method: string, + endpoint: string, + qs: IDataObject, + body: IDataObject, + token: string, +) { + const responseData = await bitwardenApiRequest.call(this, method, endpoint, qs, body, token); + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + if (returnAll) { + return responseData.data; + } else { + const limit = this.getNodeParameter('limit', i) as number; + return responseData.data.slice(0, limit); + } +} + +/** + * Return the access token URL based on the user's environment. + */ +function getTokenUrl(this: IExecuteFunctions | ILoadOptionsFunctions) { + const { environment, domain } = this.getCredentials('bitwardenApi') as IDataObject; + + return environment === 'cloudHosted' + ? 'https://identity.bitwarden.com/connect/token' + : `${domain}/identity/connect/token`; + +} + +/** + * Return the base API URL based on the user's environment. + */ +function getBaseUrl(this: IExecuteFunctions | ILoadOptionsFunctions) { + const { environment, domain } = this.getCredentials('bitwardenApi') as IDataObject; + + return environment === 'cloudHosted' + ? 'https://api.bitwarden.com' + : `${domain}/api`; + +} + +/** + * Load a resource so that it can be selected by name from a dropdown. + */ +export async function loadResource( + this: ILoadOptionsFunctions, + resource: string, +) { + const returnData: INodePropertyOptions[] = []; + const token = await getAccessToken.call(this); + const endpoint = `/public/${resource}`; + + const { data } = await bitwardenApiRequest.call(this, 'GET', endpoint, {}, {}, token); + + data.forEach(({ id, name }: { id: string, name: string }) => { + returnData.push({ + name: name || id, + value: id, + }); + }); + + return returnData; +} diff --git a/packages/nodes-base/nodes/Bitwarden/bitwarden.svg b/packages/nodes-base/nodes/Bitwarden/bitwarden.svg new file mode 100644 index 0000000000..d5d8ddadaa --- /dev/null +++ b/packages/nodes-base/nodes/Bitwarden/bitwarden.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/nodes-base/nodes/Bitwarden/descriptions/CollectionDescription.ts b/packages/nodes-base/nodes/Bitwarden/descriptions/CollectionDescription.ts new file mode 100644 index 0000000000..aa54701481 --- /dev/null +++ b/packages/nodes-base/nodes/Bitwarden/descriptions/CollectionDescription.ts @@ -0,0 +1,152 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const collectionOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Delete', + value: 'delete', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Update', + value: 'update', + }, + ], + displayOptions: { + show: { + resource: [ + 'collection', + ], + }, + }, + }, +] as INodeProperties[]; + +export const collectionFields = [ + // ---------------------------------- + // collection: shared + // ---------------------------------- + { + displayName: 'Collection ID', + name: 'collectionId', + type: 'string', + required: true, + description: 'The identifier of the collection.', + default: '', + placeholder: '5e59c8c7-e05a-4d17-8e85-acc301343926', + displayOptions: { + show: { + resource: [ + 'collection', + ], + operation: [ + 'delete', + 'get', + 'update', + ], + }, + }, + }, + + // ---------------------------------- + // collection: getAll + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Return all available results for the query.', + displayOptions: { + show: { + resource: [ + 'collection', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 10, + description: 'Number of results to return for the query.', + displayOptions: { + show: { + resource: [ + 'collection', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + + // ---------------------------------- + // collection: update + // ---------------------------------- + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + required: true, + options: [ + { + displayName: 'Group', + name: 'groups', + type: 'multiOptions', + description: 'The group to assign this collection to.', + default: [], + typeOptions: { + loadOptionsMethod: 'getGroups', + }, + }, + { + displayName: 'External ID', + name: 'externalId', + type: 'string', + description: 'The external identifier to set to this collection.', + default: '', + }, + ], + displayOptions: { + show: { + resource: [ + 'collection', + ], + operation: [ + 'update', + ], + }, + }, + }, +] as INodeProperties[]; + +export interface CollectionUpdateFields { + groups: string[]; + externalId: string; +} diff --git a/packages/nodes-base/nodes/Bitwarden/descriptions/EventDescription.ts b/packages/nodes-base/nodes/Bitwarden/descriptions/EventDescription.ts new file mode 100644 index 0000000000..3c4278775c --- /dev/null +++ b/packages/nodes-base/nodes/Bitwarden/descriptions/EventDescription.ts @@ -0,0 +1,119 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const eventOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Get All', + value: 'getAll', + }, + ], + displayOptions: { + show: { + resource: [ + 'event', + ], + }, + }, + }, +] as INodeProperties[]; + + +export const eventFields = [ + // ---------------------------------- + // event: getAll + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Return all available results for the query.', + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 10, + description: 'Number of results to return for the query.', + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + options: [ + { + displayName: 'Acting User ID', + name: 'actingUserId', + type: 'string', + default: '', + description: 'The unique identifier of the acting user.', + placeholder: '4a59c8c7-e05a-4d17-8e85-acc301343926', + }, + { + displayName: 'End Date', + name: 'end', + type: 'dateTime', + default: '', + description: 'The end date for the search.', + }, + { + displayName: 'Item ID', + name: 'itemID', + type: 'string', + default: '', + description: 'The unique identifier of the item that the event describes.', + placeholder: '5e59c8c7-e05a-4d17-8e85-acc301343926', + }, + { + displayName: 'Start Date', + name: 'start', + type: 'dateTime', + default: '', + description: 'The start date for the search.', + }, + ], + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'getAll', + ], + }, + }, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Bitwarden/descriptions/GroupDescription.ts b/packages/nodes-base/nodes/Bitwarden/descriptions/GroupDescription.ts new file mode 100644 index 0000000000..aa6b4f07b5 --- /dev/null +++ b/packages/nodes-base/nodes/Bitwarden/descriptions/GroupDescription.ts @@ -0,0 +1,281 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const groupOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Create', + value: 'create', + }, + { + name: 'Delete', + value: 'delete', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Get Members', + value: 'getMembers', + }, + { + name: 'Update', + value: 'update', + }, + { + name: 'Update Members', + value: 'updateMembers', + }, + ], + displayOptions: { + show: { + resource: [ + 'group', + ], + }, + }, + }, +] as INodeProperties[]; + +export const groupFields = [ + // ---------------------------------- + // group: shared + // ---------------------------------- + { + displayName: 'Group ID', + name: 'groupId', + type: 'string', + required: true, + description: 'The identifier of the group.', + default: '', + placeholder: '5e59c8c7-e05a-4d17-8e85-acc301343926', + displayOptions: { + show: { + resource: [ + 'group', + ], + operation: [ + 'delete', + 'get', + 'getMembers', + 'update', + 'updateMembers', + ], + }, + }, + }, + + // ---------------------------------- + // group: getAll + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Return all available results for the query.', + displayOptions: { + show: { + resource: [ + 'group', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 10, + description: 'Number of results to return for the query.', + displayOptions: { + show: { + resource: [ + 'group', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + + // ---------------------------------- + // group: create + // ---------------------------------- + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + required: true, + description: 'The name of the group to create.', + displayOptions: { + show: { + resource: [ + 'group', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Access All', + name: 'accessAll', + type: 'boolean', + default: false, + description: 'Allow this group to access all collections within the organization, instead of only its associated collections.
If set to true, this option overrides any collection assignments.', + displayOptions: { + show: { + resource: [ + 'group', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Collections', + name: 'collections', + type: 'multiOptions', + description: 'The collections to assign to this group.', + default: [], + typeOptions: { + loadOptionsMethod: 'getCollections', + }, + }, + { + displayName: 'External ID', + name: 'externalId', + type: 'string', + description: 'The external identifier to set to this group.', + default: '', + }, + ], + displayOptions: { + show: { + resource: [ + 'group', + ], + operation: [ + 'create', + ], + }, + }, + }, + + // ---------------------------------- + // group: update + // ---------------------------------- + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + options: [ + + { + displayName: 'Access All', + name: 'accessAll', + type: 'boolean', + default: false, + }, + { + displayName: 'Collections', + name: 'collections', + type: 'multiOptions', + description: 'The collections to assign to this group.', + default: [], + typeOptions: { + loadOptionsMethod: 'getCollections', + }, + }, + { + displayName: 'External ID', + name: 'externalId', + type: 'string', + description: 'The external identifier to set to this group.', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'The name of the group to update.', + }, + ], + displayOptions: { + show: { + resource: [ + 'group', + ], + operation: [ + 'update', + ], + }, + }, + }, + + // ---------------------------------- + // group: updateMembers + // ---------------------------------- + { + displayName: 'Member IDs', + name: 'memberIds', + type: 'string', + default: '', + description: 'Comma-separated list of IDs of members to set in a group.', + displayOptions: { + show: { + resource: [ + 'group', + ], + operation: [ + 'updateMembers', + ], + }, + }, + }, +] as INodeProperties[]; + +type GroupSchema = { + name: string; + collections: string[]; + accessAll: boolean; + externalId: string; +}; + +export type GroupUpdateFields = GroupSchema; + +export type GroupCreationAdditionalFields = Omit; diff --git a/packages/nodes-base/nodes/Bitwarden/descriptions/MemberDescription.ts b/packages/nodes-base/nodes/Bitwarden/descriptions/MemberDescription.ts new file mode 100644 index 0000000000..884475af77 --- /dev/null +++ b/packages/nodes-base/nodes/Bitwarden/descriptions/MemberDescription.ts @@ -0,0 +1,327 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const memberOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Create', + value: 'create', + }, + { + name: 'Delete', + value: 'delete', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Get Groups', + value: 'getGroups', + }, + { + name: 'Update', + value: 'update', + }, + { + name: 'Update Groups', + value: 'updateGroups', + }, + ], + displayOptions: { + show: { + resource: [ + 'member', + ], + }, + }, + }, +] as INodeProperties[]; + +export const memberFields = [ + // ---------------------------------- + // member: shared + // ---------------------------------- + { + displayName: 'Member ID', + name: 'memberId', + type: 'string', + required: true, + description: 'The identifier of the member.', + default: '', + placeholder: '5e59c8c7-e05a-4d17-8e85-acc301343926', + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'delete', + 'get', + 'getGroups', + 'update', + 'updateGroups', + ], + }, + }, + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + default: 2, + required: true, + options: [ + { + name: 'Owner', + value: 0, + }, + { + name: 'Admin', + value: 1, + }, + { + name: 'User', + value: 2, + }, + { + name: 'Manager', + value: 3, + }, + ], + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'create', + ], + }, + }, + }, + // ---------------------------------- + // member: getAll + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Return all available results for the query.', + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 10, + description: 'Number of results to return for the query.', + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + // ---------------------------------- + // member: create + // ---------------------------------- + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + description: 'The email of the member to update.', + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Access All', + name: 'accessAll', + type: 'boolean', + default: false, + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Collections', + name: 'collections', + type: 'multiOptions', + description: 'The collections to assign to this member.', + default: [], + typeOptions: { + loadOptionsMethod: 'getCollections', + }, + }, + { + displayName: 'External ID', + name: 'externalId', + type: 'string', + description: 'The external identifier to set to this member.', + default: '', + }, + ], + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'create', + ], + }, + }, + }, + // ---------------------------------- + // member: update + // ---------------------------------- + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Type', + name: 'type', + type: 'options', + default: {}, + options: [ + { + name: 'Owner', + value: 0, + }, + { + name: 'Admin', + value: 1, + }, + { + name: 'User', + value: 2, + }, + { + name: 'Manager', + value: 3, + }, + ], + }, + { + displayName: 'Collections', + name: 'collections', + type: 'multiOptions', + description: 'The collections to assign to this member.', + default: [], + typeOptions: { + loadOptionsMethod: 'getCollections', + }, + }, + { + displayName: 'External ID', + name: 'externalId', + type: 'string', + description: 'The external identifier to set to this member.', + default: '', + }, + { + displayName: 'Access All', + name: 'accessAll', + type: 'boolean', + default: false, + }, + ], + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'update', + ], + }, + }, + }, + // ---------------------------------- + // member: updateGroups + // ---------------------------------- + { + displayName: 'Group IDs', + name: 'groupIds', + type: 'string', + default: '', + description: 'Comma-separated list of IDs of groups to set for a member.', + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'updateGroups', + ], + }, + }, + }, +] as INodeProperties[]; + +type MemberSchema = { + email: string; + collections: string[]; + type: number; + accessAll: boolean; + externalId: string; +}; + +export type MemberUpdateFields = Omit; + +export type MemberCreationAdditionalFields = Omit; diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 099e8e468e..d1d13c629b 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -43,6 +43,7 @@ "dist/credentials/BitbucketApi.credentials.js", "dist/credentials/BitlyApi.credentials.js", "dist/credentials/BitlyOAuth2Api.credentials.js", + "dist/credentials/BitwardenApi.credentials.js", "dist/credentials/BoxOAuth2Api.credentials.js", "dist/credentials/BrandfetchApi.credentials.js", "dist/credentials/ChargebeeApi.credentials.js", @@ -278,6 +279,7 @@ "dist/nodes/Beeminder/Beeminder.node.js", "dist/nodes/Bitbucket/BitbucketTrigger.node.js", "dist/nodes/Bitly/Bitly.node.js", + "dist/nodes/Bitwarden/Bitwarden.node.js", "dist/nodes/Box/Box.node.js", "dist/nodes/Box/BoxTrigger.node.js", "dist/nodes/Brandfetch/Brandfetch.node.js",