feat(JWT Node): New node (#9005)

Co-authored-by: Giulio Andreini <andreini@netseven.it>
This commit is contained in:
Michael Kret 2024-04-10 13:16:48 +03:00 committed by GitHub
parent 9403657e46
commit 0a9f6b3de8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 1088 additions and 6 deletions

View file

@ -71,8 +71,7 @@ export class jwtAuth implements ICredentialType {
displayName: 'Key Type',
name: 'keyType',
type: 'options',
description:
'Choose either the secret passphrase for PEM encoded public keys for RSA and ECDSA',
description: 'Choose either the secret passphrase or PEM encoded public keys',
options: [
{
name: 'Passphrase',

View file

@ -0,0 +1,19 @@
{
"node": "n8n-nodes-base.jwt",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Development"],
"alias": ["Token", "Key", "JSON", "Payload", "Sign", "Verify", "Decode"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/credentials/jwt"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.jwt/"
}
]
}
}

View file

@ -0,0 +1,457 @@
import type {
IDataObject,
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import jwt from 'jsonwebtoken';
import { formatPrivateKey } from '../../utils/utilities';
import { parseJsonParameter } from '../Set/v2/helpers/utils';
const prettifyOperation = (operation: string) => {
if (operation === 'sign') {
return 'Sign a JWT';
}
if (operation === 'decode') {
return 'Decode a JWT';
}
if (operation === 'verify') {
return 'Verify a JWT';
}
return operation;
};
const getToken = (ctx: IExecuteFunctions, itemIndex = 0) => {
const token = ctx.getNodeParameter('token', itemIndex) as string;
if (!token) {
throw new NodeOperationError(ctx.getNode(), 'The JWT token was not provided', {
itemIndex,
description: "Be sure to add a valid JWT token to the 'Token' parameter",
});
}
return token;
};
export class Jwt implements INodeType {
description: INodeTypeDescription = {
displayName: 'JWT',
name: 'jwt',
icon: 'file:jwt.svg',
group: ['transform'],
version: 1,
description: 'JWT',
subtitle: `={{(${prettifyOperation})($parameter.operation)}}`,
defaults: {
name: 'JWT',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
// eslint-disable-next-line n8n-nodes-base/node-class-description-credentials-name-unsuffixed
name: 'jwtAuth',
required: true,
},
],
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Decode',
value: 'decode',
action: 'Decode a JWT',
},
{
name: 'Sign',
value: 'sign',
action: 'Sign a JWT',
},
{
name: 'Verify',
value: 'verify',
action: 'Verify a JWT',
},
],
default: 'sign',
},
{
displayName: 'Use JSON to Build Payload',
name: 'useJson',
type: 'boolean',
default: false,
description: 'Whether to use JSON to build the claims',
displayOptions: {
show: {
operation: ['sign'],
},
},
},
{
displayName: 'Payload Claims',
name: 'claims',
type: 'collection',
placeholder: 'Add Claim',
default: {},
options: [
{
displayName: 'Audience',
name: 'audience',
type: 'string',
placeholder: 'e.g. https://example.com',
default: '',
description: 'Identifies the recipients that the JWT is intended for',
},
{
displayName: 'Expires In',
name: 'expiresIn',
type: 'number',
placeholder: 'e.g. 3600',
default: 3600,
description: 'The lifetime of the token in seconds',
typeOptions: {
minValue: 0,
},
},
{
displayName: 'Issuer',
name: 'issuer',
type: 'string',
placeholder: 'e.g. https://example.com',
default: '',
description: 'Identifies the principal that issued the JWT',
},
{
displayName: 'JWT ID',
name: 'jwtid',
type: 'string',
placeholder: 'e.g. 123456',
default: '',
description: 'Unique identifier for the JWT',
},
{
displayName: 'Not Before',
name: 'notBefore',
type: 'number',
default: 0,
description: 'The time before which the JWT must not be accepted for processing',
typeOptions: {
minValue: 0,
},
},
{
displayName: 'Subject',
name: 'subject',
type: 'string',
default: '',
description: 'Identifies the principal that is the subject of the JWT',
},
],
displayOptions: {
show: {
operation: ['sign'],
useJson: [false],
},
},
},
{
displayName: 'Payload Claims (JSON)',
name: 'claimsJson',
type: 'json',
description: 'Claims to add to the token in JSON format',
default: '{\n "my_field_1": "value 1",\n "my_field_2": "value 2"\n}\n',
validateType: 'object',
ignoreValidationDuringExecution: true,
typeOptions: {
rows: 5,
},
displayOptions: {
show: {
operation: ['sign'],
useJson: [true],
},
},
},
{
displayName: 'Token',
name: 'token',
type: 'string',
typeOptions: { password: true },
required: true,
validateType: 'jwt',
default: '',
description: 'The token to verify or decode',
displayOptions: {
show: {
operation: ['verify', 'decode'],
},
},
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Return Additional Info',
name: 'complete',
type: 'boolean',
default: false,
description:
'Whether to return the complete decoded token with information about the header and signature or just the payload',
displayOptions: {
show: {
'/operation': ['verify', 'decode'],
},
},
},
{
displayName: 'Ignore Expiration',
name: 'ignoreExpiration',
type: 'boolean',
default: false,
description: 'Whether to ignore the expiration of the token',
displayOptions: {
show: {
'/operation': ['verify'],
},
},
},
{
displayName: 'Ignore Not Before Claim',
name: 'ignoreNotBefore',
type: 'boolean',
default: false,
description: 'Whether to ignore the not before claim of the token',
displayOptions: {
show: {
'/operation': ['verify'],
},
},
},
{
displayName: 'Clock Tolerance',
name: 'clockTolerance',
type: 'number',
default: 0,
description:
'Number of seconds to tolerate when checking the nbf and exp claims, to deal with small clock differences among different servers',
typeOptions: {
minValue: 0,
},
displayOptions: {
show: {
'/operation': ['verify'],
},
},
},
{
displayName: 'Override Algorithm',
name: 'algorithm',
type: 'options',
options: [
{
name: 'ES256',
value: 'ES256',
},
{
name: 'ES384',
value: 'ES384',
},
{
name: 'ES512',
value: 'ES512',
},
{
name: 'HS256',
value: 'HS256',
},
{
name: 'HS384',
value: 'HS384',
},
{
name: 'HS512',
value: 'HS512',
},
{
name: 'PS256',
value: 'PS256',
},
{
name: 'PS384',
value: 'PS384',
},
{
name: 'PS512',
value: 'PS512',
},
{
name: 'RS256',
value: 'RS256',
},
{
name: 'RS384',
value: 'RS384',
},
{
name: 'RS512',
value: 'RS512',
},
],
default: 'HS256',
description:
'The algorithm to use for signing or verifying the token, overrides algorithm in credentials',
displayOptions: {
show: {
'/operation': ['sign', 'verify'],
},
},
},
],
},
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
const operation = this.getNodeParameter('operation', 0);
const credentials = (await this.getCredentials('jwtAuth')) as {
keyType: 'passphrase' | 'pemKey';
publicKey: string;
privateKey: string;
secret: string;
algorithm: jwt.Algorithm;
};
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
const options = this.getNodeParameter('options', itemIndex, {}) as {
algorithm?: jwt.Algorithm;
complete?: boolean;
ignoreExpiration?: boolean;
ignoreNotBefore?: boolean;
clockTolerance?: number;
};
try {
if (operation === 'sign') {
const useJson = this.getNodeParameter('useJson', itemIndex) as boolean;
let payload: IDataObject = {};
if (useJson) {
payload = parseJsonParameter(
this.getNodeParameter('claimsJson', itemIndex) as IDataObject,
this.getNode(),
itemIndex,
);
} else {
payload = this.getNodeParameter('claims', itemIndex) as IDataObject;
}
let secretOrPrivateKey;
if (credentials.keyType === 'passphrase') {
secretOrPrivateKey = credentials.secret;
} else {
secretOrPrivateKey = formatPrivateKey(credentials.privateKey);
}
const token = jwt.sign(payload, secretOrPrivateKey, {
algorithm: options.algorithm ?? credentials.algorithm,
});
returnData.push({
json: { token },
pairedItem: itemIndex,
});
}
if (operation === 'verify') {
const token = getToken(this, itemIndex);
let secretOrPublicKey;
if (credentials.keyType === 'passphrase') {
secretOrPublicKey = credentials.secret;
} else {
secretOrPublicKey = formatPrivateKey(credentials.publicKey, true);
}
const { ignoreExpiration, ignoreNotBefore, clockTolerance, complete } = options;
const data = jwt.verify(token, secretOrPublicKey, {
algorithms: [options.algorithm ?? credentials.algorithm],
ignoreExpiration,
ignoreNotBefore,
clockTolerance,
complete,
});
const json = options.complete && data ? (data as IDataObject) : { payload: data };
returnData.push({
json,
pairedItem: itemIndex,
});
}
if (operation === 'decode') {
const token = getToken(this, itemIndex);
const data = jwt.decode(token, { complete: options.complete });
const json = options.complete && data ? (data as IDataObject) : { payload: data };
returnData.push({
json,
pairedItem: itemIndex,
});
}
} catch (error) {
if (error.message === 'invalid signature') {
error = new NodeOperationError(this.getNode(), "The JWT token can't be verified", {
itemIndex,
description:
'Be sure that the provided JWT token is correctly encoded and matches the selected credentials',
});
}
if (this.continueOnFail()) {
returnData.push({
json: this.getInputData(itemIndex)[0].json,
error,
pairedItem: itemIndex,
});
continue;
}
if (error.context) {
error.context.itemIndex = itemIndex;
throw error;
}
throw new NodeOperationError(this.getNode(), error, {
itemIndex,
});
}
}
return [returnData];
}
}

View file

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="200px"
height="200px"
viewBox="0 0 200 200"
version="1.1"
id="svg10"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<!-- Generator: Sketch 3.3.2 (12043) - http://www.bohemiancoding.com/sketch -->
<title
id="title1">Group</title>
<desc
id="desc1">Created with Sketch.</desc>
<defs
id="defs1">
<filter
style="color-interpolation-filters:sRGB"
id="filter11"
x="-0.18346753"
y="-0.076523014"
width="1.3669351"
height="1.153046">
<feGaussianBlur
stdDeviation="1.2230926"
id="feGaussianBlur11" />
</filter>
<filter
style="color-interpolation-filters:sRGB"
id="filter12"
x="-0.18346753"
y="-0.076523014"
width="1.3669351"
height="1.153046">
<feGaussianBlur
stdDeviation="1.2230926"
id="feGaussianBlur12" />
</filter>
</defs>
<g
id="layer1" />
<g
transform="translate(57.626456,48.948311)"
id="Shape-6">
<g
id="g12"
transform="matrix(1.8791546,0,0,1.8791546,-51.584186,-42.862227)">
<path
d="M 42.223544,27.435493 V -0.303311 H 58.223226 V 27.435493 L 50.223385,38.05668 Z"
fill="#ffffff"
id="path11"
style="fill:#ececec;stroke-width:1.04876;filter:url(#filter12)" />
<path
d="M 57.5,26.9 V 0 h -15 V 26.9 L 50,37.2 Z"
fill="#ffffff"
id="path1-7" />
<path
d="M 42.223544,72.517876 V 100.25668 H 58.223226 V 72.517876 L 50.223385,61.896689 Z"
fill="#ffffff"
id="path10"
style="fill:#ececec;stroke-width:1.04876;filter:url(#filter11)" />
<path
d="m 42.623319,73.156936 v 26.900004 h 15 V 73.156936 l -7.5,-10.3 z"
fill="#ffffff"
id="path2-5" />
<path
d="M 57.5,73.1 73.3,94.9 85.5,86 69.6,64.3 57.5,60.3 Z"
fill="#00f2e6"
id="path3-3" />
<path
d="M 42.5,26.9 26.7,5.1 14.5,14 l 15.9,21.7 12.1,4 z"
fill="#00f2e6"
id="path4-5" />
<path
d="M 30.4,35.7 4.8,27.4 0.1,41.7 25.7,50 37.9,46.1 Z"
fill="#00b9f1"
id="path5-6" />
<path
d="M 62.1,53.9 69.6,64.3 95.2,72.6 99.9,58.3 74.3,50 Z"
fill="#00b9f1"
id="path6-2" />
<path
d="M 74.3,50 99.9,41.7 95.2,27.4 69.6,35.7 62.1,46.1 Z"
fill="#d63aff"
id="path7-9" />
<path
d="M 25.7,50 0.1,58.3 4.8,72.6 30.4,64.3 37.9,53.9 Z"
fill="#d63aff"
id="path8-1" />
<path
d="M 30.4,64.3 14.5,86 26.7,94.9 42.5,73.1 V 60.3 Z"
fill="#fb015b"
id="path9-2" />
<path
d="M 69.6,35.7 85.5,14 73.3,5.1 57.5,26.9 v 12.8 z"
fill="#fb015b"
id="path10-7" />
</g>
</g>
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:title>Group</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,4 @@
import { testWorkflows, getWorkflowFilenames } from '@test/nodes/Helpers';
const workflows = getWorkflowFilenames(__dirname);
describe('Test Jwt Node', () => testWorkflows(workflows));

View file

@ -0,0 +1,412 @@
{
"name": "My workflow 31",
"nodes": [
{
"parameters": {},
"id": "fcc3e9dc-90c9-4b26-9b44-e661e0ebf658",
"name": "When clicking \"Test workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
780,
380
]
},
{
"parameters": {
"claims": {
"audience": "test",
"issuer": "test",
"jwtid": "123",
"subject": "test"
},
"options": {}
},
"id": "6b1ba38f-60d8-482a-a7d4-4fee7054334b",
"name": "JWT",
"type": "n8n-nodes-base.jwt",
"typeVersion": 1,
"position": [
1000,
380
],
"credentials": {
"jwtAuth": {
"id": "AosB7WdhmVA3be8t",
"name": "JWT Auth test"
}
}
},
{
"parameters": {
"operation": "decode",
"token": "={{ $json.token }}",
"options": {}
},
"id": "d07b9335-29a0-41bc-a6c4-0232d94a0559",
"name": "JWT1",
"type": "n8n-nodes-base.jwt",
"typeVersion": 1,
"position": [
1260,
220
],
"credentials": {
"jwtAuth": {
"id": "AosB7WdhmVA3be8t",
"name": "JWT Auth test"
}
}
},
{
"parameters": {
"operation": "verify",
"token": "={{ $json.token }}",
"options": {
"complete": true,
"ignoreExpiration": true,
"ignoreNotBefore": true
}
},
"id": "b6228805-f0c1-479d-bbec-cefcde7298e3",
"name": "JWT2",
"type": "n8n-nodes-base.jwt",
"typeVersion": 1,
"position": [
1280,
440
],
"credentials": {
"jwtAuth": {
"id": "AosB7WdhmVA3be8t",
"name": "JWT Auth test"
}
}
},
{
"parameters": {
"useJson": true,
"options": {}
},
"id": "56ede43f-6771-4341-8c17-ba4b9185711a",
"name": "JWT3",
"type": "n8n-nodes-base.jwt",
"typeVersion": 1,
"position": [
1000,
640
],
"credentials": {
"jwtAuth": {
"id": "G45TOKX5kBEraTr1",
"name": "JWT Auth test PEM"
}
}
},
{
"parameters": {
"operation": "decode",
"token": "={{ $json.token }}",
"options": {}
},
"id": "1b57af10-710f-4b86-9020-e1fc43a222cd",
"name": "JWT4",
"type": "n8n-nodes-base.jwt",
"typeVersion": 1,
"position": [
1280,
640
],
"credentials": {
"jwtAuth": {
"id": "G45TOKX5kBEraTr1",
"name": "JWT Auth test PEM"
}
}
},
{
"parameters": {
"operation": "verify",
"token": "={{ $json.token }}",
"options": {}
},
"id": "9a658330-ecc3-4007-9524-534ad15a3a40",
"name": "JWT5",
"type": "n8n-nodes-base.jwt",
"typeVersion": 1,
"position": [
1280,
860
],
"credentials": {
"jwtAuth": {
"id": "G45TOKX5kBEraTr1",
"name": "JWT Auth test PEM"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "1289f607-d46f-45f5-953a-1492f3b50bbd",
"name": "payload.audience",
"value": "={{ $json.payload.audience }}",
"type": "string"
},
{
"id": "e32ae71b-62ca-45e5-8351-2fd9ab7451ef",
"name": "payload.jwtid",
"value": "={{ $json.payload.jwtid }}",
"type": "string"
}
]
},
"options": {}
},
"id": "51a6c4b9-ebae-4ffa-a870-9915c3304cd5",
"name": "Edit Fields",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [
1500,
220
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "3ff845d5-9c2c-4744-bc33-a7318b4741fc",
"name": "payload.audience",
"value": "={{ $json.payload.audience }}",
"type": "string"
},
{
"id": "d1206579-634e-472a-9190-a176cf2477a1",
"name": "payload.jwtid",
"value": "={{ $json.payload.jwtid }}",
"type": "string"
}
]
},
"options": {}
},
"id": "9e97ea34-ec32-4935-b116-e40815567e1c",
"name": "Edit Fields1",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [
1520,
440
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "46ed5c03-d41a-4387-988e-2ed3821f32d4",
"name": "payload.my_field_1",
"value": "={{ $json.payload.my_field_1 }}",
"type": "string"
},
{
"id": "0007786e-3f93-4146-8e10-32ab8e088b81",
"name": "payload.my_field_2",
"value": "={{ $json.payload.my_field_2 }}",
"type": "string"
}
]
},
"options": {}
},
"id": "f205e447-2a98-48ef-bba5-12e9aecab90e",
"name": "Edit Fields2",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [
1520,
860
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "fe90f817-1b89-409c-992c-e42070fc67bf",
"name": "payload.my_field_1",
"value": "={{ $json.payload.my_field_1 }}",
"type": "string"
},
{
"id": "1b80e8b3-3004-4959-8c6c-0d36176579ea",
"name": "payload.my_field_2",
"value": "={{ $json.payload.my_field_2 }}",
"type": "string"
}
]
},
"options": {}
},
"id": "0788f03e-0c1c-4e21-a408-9441930cd82f",
"name": "Edit Fields3",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [
1520,
640
]
}
],
"pinData": {
"Edit Fields": [
{
"json": {
"payload": {
"audience": "test",
"jwtid": "123"
}
}
}
],
"Edit Fields1": [
{
"json": {
"payload": {
"audience": "test",
"jwtid": "123"
}
}
}
],
"Edit Fields3": [
{
"json": {
"payload": {
"my_field_1": "value 1",
"my_field_2": "value 2"
}
}
}
],
"Edit Fields2": [
{
"json": {
"payload": {
"my_field_1": "value 1",
"my_field_2": "value 2"
}
}
}
]
},
"connections": {
"When clicking \"Test workflow\"": {
"main": [
[
{
"node": "JWT",
"type": "main",
"index": 0
},
{
"node": "JWT3",
"type": "main",
"index": 0
}
]
]
},
"JWT": {
"main": [
[
{
"node": "JWT1",
"type": "main",
"index": 0
},
{
"node": "JWT2",
"type": "main",
"index": 0
}
]
]
},
"JWT3": {
"main": [
[
{
"node": "JWT4",
"type": "main",
"index": 0
},
{
"node": "JWT5",
"type": "main",
"index": 0
}
]
]
},
"JWT1": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
},
"JWT2": {
"main": [
[
{
"node": "Edit Fields1",
"type": "main",
"index": 0
}
]
]
},
"JWT4": {
"main": [
[
{
"node": "Edit Fields3",
"type": "main",
"index": 0
}
]
]
},
"JWT5": {
"main": [
[
{
"node": "Edit Fields2",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "f70ffef1-3c5d-4991-a3aa-b4141b93e4cb",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
},
"id": "H0sZEXDuE7VIP5vz",
"tags": []
}

View file

@ -287,7 +287,7 @@ export class Webhook extends Node {
if (expectedAuth.keyType === 'passphrase') {
secretOrPublicKey = expectedAuth.secret;
} else {
secretOrPublicKey = formatPrivateKey(expectedAuth.publicKey);
secretOrPublicKey = formatPrivateKey(expectedAuth.publicKey, true);
}
try {

View file

@ -574,6 +574,7 @@
"dist/nodes/Jira/Jira.node.js",
"dist/nodes/Jira/JiraTrigger.node.js",
"dist/nodes/JotForm/JotFormTrigger.node.js",
"dist/nodes/Jwt/Jwt.node.js",
"dist/nodes/Kafka/Kafka.node.js",
"dist/nodes/Kafka/KafkaTrigger.node.js",
"dist/nodes/Keap/Keap.node.js",

View file

@ -6,6 +6,51 @@ export const FAKE_CREDENTIALS_DATA = {
[JSON.stringify({ id: '20', name: 'Airtable account' })]: {
apiKey: 'key456',
},
[JSON.stringify({ id: 'G45TOKX5kBEraTr1', name: 'JWT Auth test PEM' })]: {
keyType: 'pemKey',
privateKey: `
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCfw0m1K+M1/6Tw
CvLYDv0gmxa+reEdKBfT0/hfkkjFWqbMRo0f4CQ3PwrOavS+80PDy6nVL21BmGev
w1bF7KXmqOzr+yKOUJ8A4u6vXUQKzVSFBqb2YZmZL1s7va9aaO6pVANTbmYHpTjh
SBnBrXgidVOXNX1c+IG+OZZMiTFWg4lJTE9rvMbLh4o5FPwdZlA1rLAux4KXVNNr
mE7T5/tsuikR06KMJS6V6YR4PZmTsy/3D2clADXDCtbUdEe0eBYoUmRLMhRL6umq
h98Dsm5ZG+YB2dn0ThR/g7DPVwvmnrK2S5e4hpqFYxQ8V8pGx7dQLsc/utbvsn32
dctGivkFAgMBAAECggEABDB0QdZtRlC2S/8VgBnghFbcwVJA6WlQOqM/y43D77zh
S9D7yV6wxGwYRfJjCZDKcZtpECiGtmYfLeoy38zFSueaEtQKg23UxYqt1JZe/uOE
eFqEzUgg5XXq8AWY0AeZXoJP9gOalE++TpX76uq4EDtAXmIuL95qVIkhCk+8pfaR
avLcejnyYGSJAG1J9pXHNChXXDVPd7PrIa20A44osvusifVMlcIYM3qkv167ULzX
4nu2hZwlNxGKtpVPldFY/qu5S7SdLo/2BQinrMSSKRSFihA4Uuod8GK0+UwjE4gO
TD15bjqIcadlAYV6bn34sHnMU9hjhPB5NyXiINYdsQKBgQDNu0XFjYyAgwORaZYs
jVTJg+fQ9wM7xnlfxXCVb9eoOGF0blW9VjIEz8lLjmPlPFFVS+EPN0andHHqH4t5
SQZVZxgNMcodWs8BJTVZUkXa+IljHXT1Vkb2zvtH96ADzs3c43+tNpmKhjG3XK1U
rL/v8feU31nwQb7imOmYmzbHCQKBgQDGzJ/pRLByB24W6FRfHIItP81vVg5ckCXi
sIhzHUdUmTdVbdAxeS6IW2oAc/IuksvmiZMLYsm+sIFFePJQrBsoD41R5VsFcJqE
o5x0DUzbOzqaV4HPOHDniibudyryZKnBvkXlCjyCv4iPKaFhH4T1aB+wdK4pJPo2
fyABs2lFHQKBgQDHz6IFK+QREK3PdhA/twhpK65jWvUOAkbxyE3/JX/7xr6IGX02
hdfQqoqj0midRMbto+OzJol9q+/TZs3MfysimR1X+0qE1iSExUGaPfjQomC1He/x
M9l6bi7Jh+wmpp10cpQXhBb93jW9E8rYmWtVPNmsAn1UhlZBuCfwapd6GQKBgATM
f7ezzsaR41huN0ssdv/8oErluucFG8UDGegddtFV+X34bqQjFrp36nEkW15AcOeZ
vpDxy4js3dH9f2vvG6C172VgsffJphE5mdc7UvWf0mRTZHDKHf+Y2CO9gK3lPCvP
GgTTYG6PjQ5XpOuhRSZfYxRxXJrlp5yVKQKhgBMJAoGBAMc6ktd0iqHAYCW3d9QP
e618RiMlVIYZIUlLWAUQWQSf3linqMjo1rCbbI/lSxE216XwI/VBX50gg/Oy3aUl
CibHHk2aKGlxVxe0Huv5gcjbZgVh1EMi4oxh4600IrWRH1Uz5AleXnheNiappKnA
lOMhy99LXMlAOL7qOBnZHgrm
-----END PRIVATE KEY-----
`,
publicKey: `
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn8NJtSvjNf+k8Ary2A79
IJsWvq3hHSgX09P4X5JIxVqmzEaNH+AkNz8Kzmr0vvNDw8up1S9tQZhnr8NWxeyl
5qjs6/sijlCfAOLur11ECs1UhQam9mGZmS9bO72vWmjuqVQDU25mB6U44UgZwa14
InVTlzV9XPiBvjmWTIkxVoOJSUxPa7zGy4eKORT8HWZQNaywLseCl1TTa5hO0+f7
bLopEdOijCUulemEeD2Zk7Mv9w9nJQA1wwrW1HRHtHgWKFJkSzIUS+rpqoffA7Ju
WRvmAdnZ9E4Uf4Owz1cL5p6ytkuXuIaahWMUPFfKRse3UC7HP7rW77J99nXLRor5
BQIDAQAB
-----END PUBLIC KEY-----
`,
algorithm: 'RS256',
},
airtableApi: {
apiKey: 'key123',
},
@ -45,4 +90,9 @@ export const FAKE_CREDENTIALS_DATA = {
refresh_token: 'REFRESHTOKEN',
},
},
jwtAuth: {
keyType: 'passphrase',
secret: 'baz',
algorithm: 'HS256',
},
} as const;

View file

@ -223,14 +223,17 @@ export const keysToLowercase = <T>(headers: T) => {
* @param privateKey - The private key to format.
* @returns The formatted private key.
*/
export function formatPrivateKey(privateKey: string): string {
export function formatPrivateKey(privateKey: string, keyIsPublic = false): string {
let regex = /(PRIVATE KEY|CERTIFICATE)/;
if (keyIsPublic) {
regex = /(PUBLIC KEY)/;
}
if (!privateKey || /\n/.test(privateKey)) {
return privateKey;
}
let formattedPrivateKey = '';
const parts = privateKey.split('-----').filter((item) => item !== '');
parts.forEach((part) => {
const regex = /(PRIVATE KEY|CERTIFICATE)/;
if (regex.test(part)) {
formattedPrivateKey += `-----${part}-----`;
} else {

View file

@ -2341,7 +2341,8 @@ export type FieldType =
| 'array'
| 'object'
| 'options'
| 'url';
| 'url'
| 'jwt';
export type ValidationResult = {
valid: boolean;

View file

@ -158,6 +158,20 @@ export const tryToParseUrl = (value: unknown): string => {
return String(value);
};
export const tryToParseJwt = (value: unknown): string => {
const error = new ApplicationError(`The value "${String(value)}" is not a valid JWT token.`, {
extra: { value },
});
if (!value) throw error;
const jwtPattern = /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_.+/=]*$/;
if (!jwtPattern.test(String(value))) throw error;
return String(value);
};
type ValidateFieldTypeOptions = Partial<{
valueOptions: INodePropertyOptions[];
strict: boolean;
@ -279,6 +293,16 @@ export const validateFieldType = (
return { valid: false, errorMessage: defaultErrorMessage };
}
}
case 'jwt': {
try {
return { valid: true, newValue: tryToParseJwt(value) };
} catch (e) {
return {
valid: false,
errorMessage: 'Value is not a valid JWT token',
};
}
}
default: {
return { valid: true, newValue: value };
}