mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-02 07:01:30 -08:00
feat(JWT Node): New node (#9005)
Co-authored-by: Giulio Andreini <andreini@netseven.it>
This commit is contained in:
parent
9403657e46
commit
0a9f6b3de8
|
@ -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',
|
||||
|
|
19
packages/nodes-base/nodes/Jwt/Jwt.node.json
Normal file
19
packages/nodes-base/nodes/Jwt/Jwt.node.json
Normal 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/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
457
packages/nodes-base/nodes/Jwt/Jwt.node.ts
Normal file
457
packages/nodes-base/nodes/Jwt/Jwt.node.ts
Normal 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];
|
||||
}
|
||||
}
|
112
packages/nodes-base/nodes/Jwt/jwt.svg
Normal file
112
packages/nodes-base/nodes/Jwt/jwt.svg
Normal 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 |
4
packages/nodes-base/nodes/Jwt/test/Jwt.node.test.ts
Normal file
4
packages/nodes-base/nodes/Jwt/test/Jwt.node.test.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
import { testWorkflows, getWorkflowFilenames } from '@test/nodes/Helpers';
|
||||
const workflows = getWorkflowFilenames(__dirname);
|
||||
|
||||
describe('Test Jwt Node', () => testWorkflows(workflows));
|
412
packages/nodes-base/nodes/Jwt/test/jwt.workflow.json
Normal file
412
packages/nodes-base/nodes/Jwt/test/jwt.workflow.json
Normal 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": []
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -2341,7 +2341,8 @@ export type FieldType =
|
|||
| 'array'
|
||||
| 'object'
|
||||
| 'options'
|
||||
| 'url';
|
||||
| 'url'
|
||||
| 'jwt';
|
||||
|
||||
export type ValidationResult = {
|
||||
valid: boolean;
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue