Add schemas for ftp and sftp

This commit is contained in:
Elias Meire 2024-08-29 16:31:17 +02:00
parent 6f15aa6a88
commit 2d40b299df
No known key found for this signature in database
8 changed files with 104 additions and 120 deletions

View file

@ -1,4 +1,14 @@
import type { ICredentialType, INodeProperties } from 'n8n-workflow'; import type { ICredentialType } from 'n8n-workflow';
import { CredentialSchema, type InferCredentialSchema } from '../utils/CredentialSchema';
const ftpCredentialSchema = CredentialSchema.create({
host: CredentialSchema.string({ label: 'Host', placeholder: 'localhost' }),
port: CredentialSchema.number({ label: 'Port', default: 21 }),
username: CredentialSchema.string({ label: 'Username', optional: true }),
password: CredentialSchema.password({ optional: true }),
});
export type FtpCredentialSchema = InferCredentialSchema<typeof ftpCredentialSchema>;
export class Ftp implements ICredentialType { export class Ftp implements ICredentialType {
name = 'ftp'; name = 'ftp';
@ -7,36 +17,5 @@ export class Ftp implements ICredentialType {
documentationUrl = 'ftp'; documentationUrl = 'ftp';
properties: INodeProperties[] = [ properties = ftpCredentialSchema.toNodeProperties();
{
displayName: 'Host',
name: 'host',
required: true,
type: 'string',
default: '',
placeholder: 'localhost',
},
{
displayName: 'Port',
name: 'port',
required: true,
type: 'number',
default: 21,
},
{
displayName: 'Username',
name: 'username',
type: 'string',
default: '',
},
{
displayName: 'Password',
name: 'password',
type: 'string',
typeOptions: {
password: true,
},
default: '',
},
];
} }

View file

@ -1,4 +1,23 @@
import type { ICredentialType, INodeProperties } from 'n8n-workflow'; import type { ICredentialType } from 'n8n-workflow';
import { CredentialSchema, type InferCredentialSchema } from '../utils/CredentialSchema';
const sftpCredentialSchema = CredentialSchema.create({
host: CredentialSchema.string({ label: 'Host', placeholder: 'localhost' }),
port: CredentialSchema.number({ label: 'Port', default: 22 }),
username: CredentialSchema.string({ label: 'Username' }),
password: CredentialSchema.password(),
privateKey: CredentialSchema.password({
label: 'Private Key',
description:
'String that contains a private key for either key-based or hostbased user authentication (OpenSSH format)',
}),
passphrase: CredentialSchema.password({
label: 'Passphrase',
description: 'For an encrypted private key, this is the passphrase used to decrypt it',
}),
});
export type SftpCredentialSchema = InferCredentialSchema<typeof sftpCredentialSchema>;
export class Sftp implements ICredentialType { export class Sftp implements ICredentialType {
name = 'sftp'; name = 'sftp';
@ -7,55 +26,5 @@ export class Sftp implements ICredentialType {
documentationUrl = 'ftp'; documentationUrl = 'ftp';
properties: INodeProperties[] = [ properties = sftpCredentialSchema.toNodeProperties();
{
displayName: 'Host',
name: 'host',
required: true,
type: 'string',
default: '',
},
{
displayName: 'Port',
name: 'port',
required: true,
type: 'number',
default: 22,
},
{
displayName: 'Username',
name: 'username',
required: true,
type: 'string',
default: '',
},
{
displayName: 'Password',
name: 'password',
type: 'string',
typeOptions: {
password: true,
},
default: '',
},
{
displayName: 'Private Key',
name: 'privateKey',
type: 'string',
typeOptions: { password: true },
default: '',
description:
'String that contains a private key for either key-based or hostbased user authentication (OpenSSH format)',
},
{
displayName: 'Passphrase',
name: 'passphrase',
typeOptions: {
password: true,
},
type: 'string',
default: '',
description: 'For an encrypted private key, this is the passphrase used to decrypt it',
},
];
} }

View file

@ -7,7 +7,6 @@ import ftpClient from 'promise-ftp';
import sftpClient from 'ssh2-sftp-client'; import sftpClient from 'ssh2-sftp-client';
import { BINARY_ENCODING, NodeApiError } from 'n8n-workflow'; import { BINARY_ENCODING, NodeApiError } from 'n8n-workflow';
import type { import type {
ICredentialDataDecryptedObject,
ICredentialsDecrypted, ICredentialsDecrypted,
ICredentialTestFunctions, ICredentialTestFunctions,
IDataObject, IDataObject,
@ -18,6 +17,8 @@ import type {
INodeTypeDescription, INodeTypeDescription,
JsonObject, JsonObject,
} from 'n8n-workflow'; } from 'n8n-workflow';
import type { FtpCredentialSchema } from '@credentials/Ftp.credentials';
import type { SftpCredentialSchema } from '@credentials/Sftp.credentials';
import { formatPrivateKey, generatePairedItemData } from '@utils/utilities'; import { formatPrivateKey, generatePairedItemData } from '@utils/utilities';
interface ReturnFtpItem { interface ReturnFtpItem {
@ -439,14 +440,14 @@ export class Ftp implements INodeType {
this: ICredentialTestFunctions, this: ICredentialTestFunctions,
credential: ICredentialsDecrypted, credential: ICredentialsDecrypted,
): Promise<INodeCredentialTestResult> { ): Promise<INodeCredentialTestResult> {
const credentials = credential.data as ICredentialDataDecryptedObject; const credentials = credential.data as FtpCredentialSchema;
const ftp = new ftpClient(); const ftp = new ftpClient();
try { try {
await ftp.connect({ await ftp.connect({
host: credentials.host as string, host: credentials.host,
port: credentials.port as number, port: credentials.port,
user: credentials.username as string, user: credentials.username,
password: credentials.password as string, password: credentials.password,
}); });
} catch (error) { } catch (error) {
await ftp.end(); await ftp.end();
@ -465,24 +466,24 @@ export class Ftp implements INodeType {
this: ICredentialTestFunctions, this: ICredentialTestFunctions,
credential: ICredentialsDecrypted, credential: ICredentialsDecrypted,
): Promise<INodeCredentialTestResult> { ): Promise<INodeCredentialTestResult> {
const credentials = credential.data as ICredentialDataDecryptedObject; const credentials = credential.data as SftpCredentialSchema;
const sftp = new sftpClient(); const sftp = new sftpClient();
try { try {
if (credentials.privateKey) { if (credentials.privateKey) {
await sftp.connect({ await sftp.connect({
host: credentials.host as string, host: credentials.host,
port: credentials.port as number, port: credentials.port,
username: credentials.username as string, username: credentials.username,
password: (credentials.password as string) || undefined, password: credentials.password || undefined,
privateKey: formatPrivateKey(credentials.privateKey as string), privateKey: formatPrivateKey(credentials.privateKey),
passphrase: credentials.passphrase as string | undefined, passphrase: credentials.passphrase,
}); });
} else { } else {
await sftp.connect({ await sftp.connect({
host: credentials.host as string, host: credentials.host,
port: credentials.port as number, port: credentials.port,
username: credentials.username as string, username: credentials.username,
password: credentials.password as string, password: credentials.password,
}); });
} }
} catch (error) { } catch (error) {
@ -516,30 +517,30 @@ export class Ftp implements INodeType {
try { try {
if (protocol === 'sftp') { if (protocol === 'sftp') {
sftp = new sftpClient(); sftp = new sftpClient();
if (credentials.privateKey) { if ('privateKey' in credentials && credentials.privateKey) {
await sftp.connect({ await sftp.connect({
host: credentials.host as string, host: credentials.host,
port: credentials.port as number, port: credentials.port,
username: credentials.username as string, username: credentials.username,
password: (credentials.password as string) || undefined, password: credentials.password || undefined,
privateKey: formatPrivateKey(credentials.privateKey as string), privateKey: formatPrivateKey(credentials.privateKey),
passphrase: credentials.passphrase as string | undefined, passphrase: credentials.passphrase,
}); });
} else { } else {
await sftp.connect({ await sftp.connect({
host: credentials.host as string, host: credentials.host,
port: credentials.port as number, port: credentials.port,
username: credentials.username as string, username: credentials.username,
password: credentials.password as string, password: credentials.password,
}); });
} }
} else { } else {
ftp = new ftpClient(); ftp = new ftpClient();
await ftp.connect({ await ftp.connect({
host: credentials.host as string, host: credentials.host,
port: credentials.port as number, port: credentials.port,
user: credentials.username as string, user: credentials.username,
password: credentials.password as string, password: credentials.password,
}); });
} }
} catch (error) { } catch (error) {

View file

@ -9,7 +9,7 @@ import type {
JsonObject, JsonObject,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow'; import { NodeApiError } from 'n8n-workflow';
import type { StrapiApiCredential } from '../../credentials/StrapiApi.credentials'; import type { StrapiApiCredential } from '@credentials/StrapiApi.credentials';
export const removeTrailingSlash = (url: string) => { export const removeTrailingSlash = (url: string) => {
if (url.endsWith('/')) { if (url.endsWith('/')) {

View file

@ -11,7 +11,6 @@ import type {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow';
import type { StrapiApiCredential } from '../../credentials/StrapiApi.credentials';
import { import {
getToken, getToken,
removeTrailingSlash, removeTrailingSlash,
@ -21,6 +20,7 @@ import {
} from './GenericFunctions'; } from './GenericFunctions';
import { entryFields, entryOperations } from './EntryDescription'; import { entryFields, entryOperations } from './EntryDescription';
import type { StrapiApiCredential } from '@credentials/StrapiApi.credentials';
export class Strapi implements INodeType { export class Strapi implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {

View file

@ -4,7 +4,9 @@
"lib": ["dom", "es2020", "es2022.error"], "lib": ["dom", "es2020", "es2022.error"],
"paths": { "paths": {
"@test/*": ["./test/*"], "@test/*": ["./test/*"],
"@utils/*": ["./utils/*"] "@utils/*": ["./utils/*"],
"@nodes/*": ["./nodes/*"],
"@credentials/*": ["./credentials/*"]
}, },
"tsBuildInfoFile": "dist/typecheck.tsbuildinfo", "tsBuildInfoFile": "dist/typecheck.tsbuildinfo",
// TODO: remove all options below this line // TODO: remove all options below this line

View file

@ -1,9 +1,13 @@
import type { FtpCredentialSchema } from './credentials/Ftp.credentials';
import type { SftpCredentialSchema } from './credentials/Sftp.credentials';
import type { StrapiApiCredential } from './credentials/StrapiApi.credentials'; import type { StrapiApiCredential } from './credentials/StrapiApi.credentials';
import type { StrapiTokenApiCredential } from './credentials/StrapiTokenApi.credentials'; import type { StrapiTokenApiCredential } from './credentials/StrapiTokenApi.credentials';
type CredentialSchemaMap = { type CredentialSchemaMap = {
strapiApi: StrapiApiCredential; strapiApi: StrapiApiCredential;
strapiTokenApi: StrapiTokenApiCredential; strapiTokenApi: StrapiTokenApiCredential;
ftp: FtpCredentialSchema;
sftp: SftpCredentialSchema;
}; };
declare module 'n8n-workflow' { declare module 'n8n-workflow' {

View file

@ -103,6 +103,26 @@ class CredentialSchemaString<
} }
} }
class CredentialSchemaNumber<
S extends ZodType,
M extends NumberMetadata,
> extends CredentialSchemaProperty<M, S> {
constructor(
public metadata: M,
schema: S,
) {
super(metadata, schema);
}
toNodeProperties(name: string): INodeProperties {
return removeUndefinedProperties({
...super.toNodeProperties(name),
type: 'number',
default: this.metadata.default,
});
}
}
class CredentialSchemaOptions< class CredentialSchemaOptions<
V extends string, V extends string,
S extends ZodType, S extends ZodType,
@ -157,6 +177,11 @@ type StringMetadata = BaseMetadata &
default: string; default: string;
}>; }>;
type NumberMetadata = BaseMetadata &
Partial<{
default: number;
}>;
type Option<V extends string> = { type Option<V extends string> = {
label: string; label: string;
value: V; value: V;
@ -210,6 +235,10 @@ export const CredentialSchema = {
string<M extends StringMetadata>(options: M) { string<M extends StringMetadata>(options: M) {
return new CredentialSchemaString(options, z.string()); return new CredentialSchemaString(options, z.string());
}, },
// eslint-disable-next-line id-denylist
number<M extends NumberMetadata>(options: M) {
return new CredentialSchemaNumber(options, z.number());
},
url(options: Optional<StringMetadata, 'label'> = {}) { url(options: Optional<StringMetadata, 'label'> = {}) {
return new CredentialSchemaString({ label: 'URL', ...options }, z.string().url()); return new CredentialSchemaString({ label: 'URL', ...options }, z.string().url());
}, },