node-red-contrib-jsonwebtoken
Version:
This node allows you to sign and validate JSON Web Token (JWT)
567 lines (518 loc) • 23.6 kB
HTML
<script type="text/html" data-template-name="jwt verify">
<div class="form-row">
<label><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label><i class="fa fa-cog"></i> Mode</label>
<input type="text" id="node-input-mode">
</div>
<div class="form-row" id="node-input-secret-div">
<label><i class="fa fa-user-secret"></i> Secret</label>
<input type="text" id="node-input-secret">
<input type="hidden" id="node-input-secretType">
</div>
<div class="form-row" id="node-input-publicKey-div">
<label><i class="fa fa-file"></i> Public Key</label>
<input type="text" id="node-input-publicKey">
<input type="hidden" id="node-input-publicKeyType">
</div>
<div class="form-row" id="node-input-autoDetectjwkid-div">
<label><i class="fa fa-magic"></i> Auto detect JWK KID</label>
<input type="text" id="node-input-autoDetectjwkid">
<input type="hidden" id="node-input-autoDetectjwkidType">
</div>
<div class="form-row" id="node-input-jwkid-div">
<label><i class="fa fa-id-badge"></i> JWK KID</label>
<input type="text" id="node-input-jwkid">
<input type="hidden" id="node-input-jwkidType">
</div>
<div class="form-row" id="node-input-jwkurl-div">
<label><i class="fa fa-link"></i> JWK URL</label>
<input type="text" id="node-input-jwkurl">
<input type="hidden" id="node-input-jwkurlType">
</div>
<div class="form-row">
<label><i class="fa fa-map-signs"></i> Token origin</label>
<input type="text" id="node-input-token">
</div>
<div class="form-row">
<label><i class="fa fa-arrow-right"></i> Output data</label>
<input type="text" id="node-input-output">
<input type="hidden" id="node-input-outputType">
</div>
<div class="form-row" id="node-input-algorithms-div">
<label><i class="fa fa-key"></i> Algorithms</label>
<input type="text" id="node-input-algorithms">
</div>
<div class="form-row">
<label><i class="fa fa-hourglass-end"></i> Ignore Expiration</label>
<input type="text" id="node-input-ignoreExpiration">
<input type="hidden" id="node-input-ignoreExpirationType">
</div>
<div class="form-row">
<label><i class="fa fa-calendar"></i> Ignore "NotBefore"</label>
<input type="text" id="node-input-ignoreNotBefore">
<input type="hidden" id="node-input-ignoreNotBeforeType">
</div>
<div class="form-row">
<label><i class="fa fa-users"></i> Validate Audience</label>
<input type="text" id="node-input-audience">
<input type="hidden" id="node-input-audienceType">
</div>
<div class="form-row">
<label><i class="fa fa-columns"></i> Validate Issuer</label>
<input type="text" id="node-input-issuer">
<input type="hidden" id="node-input-issuerType">
</div>
<div class="form-row">
<label><i class="fa fa-columns"></i> Validate Max Age</label>
<input type="text" id="node-input-maxAge">
<input type="hidden" id="node-input-maxAgeType">
</div>
<div class="form-row" id="node-input-order-div">
<label><i class="fa fa-check-circle"></i> Validate Claims</label>
<ol id="node-input-validators"></ol>
</div>
</script>
<script type="text/javascript">
(function() {
let validators = [
{value:"required",label:"Required"},
{value:"email",label:"Email"},
{value:"equal",label:"Equal"},
{value:"equality",label:"Equal to property"},
{value:"pattern",label:"Regular Expression"},
{value:"maxlength",label:"Maximum Length"},
{value:"minlength",label:"Minimum Length"},
{value:"type",label:"Type"},
{value:"url",label:"URL"},
{value:"date",label:"Date"},
{value:"inclusion",label:"In"},
{value:"exclusion",label:"Not In"},
{value:"ipv4",label:"IP v4"},
{value:"ipv6",label:"IP v6"},
{value:"hostname",label:"Hostname"},
{value:"json",label:"JSON"},
{value:"maximum_number",label:"Maximum number"},
{value:"minimum_number",label:"Minimum number"},
{value:"maximum_items",label:"Maximum Items"},
{value:"minimum_items",label:"Minimum Items"},
{value:"uuid",label:"UUID"},
{value:"any_of",label:"Any Of"},
];
let dataType = [
{value:"array",label:"Array"},
{value:"integer",label:"Integer"},
{value:"number",label:"Number"},
{value:"string",label:"String"},
{value:"boolean",label:"Boolean"},
{value:"object",label:"Object"}
];
function insertConstraint(container, data){
container.css({
overflow: 'hidden',
whiteSpace: 'nowrap',
display: "flex",
"align-items":"center"
});
let inputRows = $('<div></div>',{style:"flex-grow:1"}).appendTo(container);
let column1 = $('<div></div>',{style:"display: inline-block;width:25%;padding: 0px 1px"}).appendTo(inputRows);
let column2 = $('<div/>',{style:"display: inline-block;width:25%;padding: 0px 1px"}).appendTo(inputRows);
let column3 = $('<div/>',{style:"display: inline-block;width:25%;padding: 0px 1px"}).appendTo(inputRows);
let column4 = $('<div/>',{style:"display: inline-block;width:23%;padding: 0px 1px"}).appendTo(inputRows);
let inputProperty = $('<input type="text" class="node-input-column-property" style="width:100%;">').appendTo(column1);
if(data.property)
inputProperty.val(data.property);
let selectValidator = $('<select/>',{class:"node-input-column-validator",style:"width:100%;"}).appendTo(column2);
validators.forEach(item=>{
selectValidator.append($("<option></option>").val(item.value).text(item.label));
})
if(data.validator)
selectValidator.val(data.validator);
$(selectValidator).on('change', function(event) {
createValueField(column3, selectValidator.val(), '')
})
createValueField(column3, data.validator || 'required', data.value, data.typeValue)
let inputError = $('<input type="text" class="node-input-column-error" style="width:100%">').appendTo(column4);
if(data.error)
inputError.val(data.error);
}
function createValueField(container, type, value, typeValue){
let inputValue = null
let inputType = null
let types = null
let disabled = false
$(container).empty();
switch (type) {
case 'required':{
types = ['str']
disabled = true
}break;
case 'date':{
types = ['str']
disabled = true
}break;
case 'email':{
types = ['str']
disabled = true
}break;
case 'equality':{
types = ['str']
}break;
case 'equal':{
types = ['str','num','json']
}break;
case 'maxlength':{
types = ['num']
}break;
case 'minlength':{
types = ['num']
}break;
case 'type':{
inputValue = $('<input type="text" class="node-input-column-value">').css('width','100%').appendTo(container);
$(inputValue).typedInput({type:"string", types:[{
value: "string",
options: dataType
}]})
$(inputValue).typedInput('value', value)
}break;
case 'url':{
types = ['str']
disabled = true
}break;
case 'ipv4':{
types = ['str']
disabled = true
}break;
case 'ipv6':{
types = ['str']
disabled = true
}break;
case 'inclusion':{
types = ['json']
}break;
case 'exclusion':{
types = ['json']
}break;
case 'hostname':{
types = ['str']
disabled = true
}break;
case 'json':{
types = ['str']
disabled = true
}break;
case 'pattern':{
types = ['str']
}break;
case 'maximum_number':{
types = ['num']
}break;
case 'minimum_number':{
types = ['num']
}break;
case 'maximum_items':{
types = ['num']
}break;
case 'minimum_items':{
types = ['num']
}break;
case 'uuid':{
types = ['str']
disabled = true
}break;
case 'any_of':{
types = ['json']
}break;
}
if(type != 'type'){
inputValue = $('<input type="text" class="node-input-column-value">').css('width','100%').appendTo(container);
inputType = $('<input type="hidden" class="node-input-column-type">').appendTo(container);
$(inputValue).typedInput({
type: typeValue,
types: types,
typeField: inputType
})
$(inputValue).typedInput('value', value)
$(inputValue).typedInput('type', typeValue || 'str')
}
if(disabled)
$(inputValue).typedInput('disable')
}
function getConstraints() {
let result = []
try {
let validatorItems = $("#node-input-validators").editableList('items');
validatorItems.each(function(i) {
const property = $(this).find(".node-input-column-property").val()
const validator = $(this).find(".node-input-column-validator").val()
const value = $(this).find(".node-input-column-value").val()
const typeValue = $(this).find(".node-input-column-type").val()
const error = $(this).find(".node-input-column-error").val()
result.push({property, validator, value, error, typeValue})
});
} catch(e){
console.log('Error: ', e);
}
return result
}
RED.nodes.registerType('jwt verify',{
category: 'security',
color: '#90CAF9',
defaults: {
name: { value: "" },
algorithms: { value: "" },
mode: { value: "" },
secret: { value:"", validate:function(v) {
const mode = $("#node-input-mode").typedInput('value')
if( mode == 'secret' && !v)
return false
return true
}},
secretType: { value: "str" },
publicKey: { value:"", validate:function(v) {
const mode = $("#node-input-mode").typedInput('value')
if( mode == 'public-key' && !v)
return false
return true
} },
publicKeyType: { value: "str" },
autoDetectjwkid: { value: false },
autoDetectjwkidType: { value: 'bool' },
jwkid: { value: "" },
jwkidType: { value: "str" },
jwkurl: { value: "" },
jwkurlType: { value: "str" },
ignoreExpiration: { value: false },
ignoreExpirationType: { value: 'bool' },
ignoreNotBefore: { value: false },
ignoreNotBeforeType: { value: 'bool' },
audience: { value: "" },
audienceType: { value: "str" },
issuer: { value: "" },
issuerType: { value: "str" },
token: { value: "" },
maxAge: { value: "" },
maxAgeType: { value: "num" },
constraints: { value: [] },
output: { value: "payload", validate:function(v) {
return v || false
}},
outputType: { value: "msg" },
},
inputs: 1,
outputs: 1,
outputLabels: ['output'],
icon: "font-awesome/fa-unlock",
label: function() {
return this.name || 'jwt verify';
},
oneditprepare: function () {
let _this = this
$('#node-input-mode').typedInput({
types: [
{
value: 'secret',
options: [
{ value: "secret", label: "Secret"},
{ value: "public-key", label: "Public Key"},
{ value: "jwtid", label: "JWKS"}
]
}
]
})
$("#node-input-mode").on('change', function(event) {
const mode = $("#node-input-mode").typedInput('value')
hideAllModes()
switch (mode) {
case 'secret':{
$("#node-input-secret-div").show()
createAlgorithmField(algorithmHMAC, _this.algorithms )
$("#node-input-algorithms-div").show()
}break;
case 'public-key':{
$("#node-input-publicKey-div").show()
createAlgorithmField(algorithmRSA, _this.algorithms)
$("#node-input-algorithms-div").show()
}break;
case 'jwtid':{
$("#node-input-jwkurl-div").show()
$("#node-input-autoDetectjwkid-div").show()
createAlgorithmField(algorithmAll, _this.algorithms )
const autoDetect = $("#node-input-autoDetectjwkid").typedInput('value')
if(autoDetect === "true"){
$("#node-input-jwkid-div").hide()
$("#node-input-algorithms-div").show()
}else{
$("#node-input-jwkid-div").show()
$("#node-input-algorithms-div").hide()
}
}break;
}
})
$("#node-input-autoDetectjwkid").on('change', function(event) {
const autoDetect = $("#node-input-autoDetectjwkid").typedInput('value')
const mode = $("#node-input-mode").typedInput('value')
if(autoDetect === "true" && mode == 'jwtid'){
$("#node-input-jwkid-div").hide()
$("#node-input-algorithms-div").show()
}else if(autoDetect === "false" && mode == 'jwtid'){
$("#node-input-jwkid-div").show()
$("#node-input-algorithms-div").hide()
} else {
$("#node-input-jwkid-div").hide()
$("#node-input-algorithms-div").hide()
}
})
$('#node-input-secret').css({ width: '330px'}).typedInput({
type: 'msg',
types:['str', 'msg', 'flow','global', 'env'],
typeField: '#node-input-secretType'
})
$("#node-input-secret").on('change', function(event, type, value) {
if(type == 'str')
$("#node-input-secret").typedInput("type", "password");
else $("#node-input-secret").typedInput("type", "text");
} );
$('#node-input-publicKey').css({ width: '330px'}).typedInput({
type: 'bin',
types:['bin', 'msg', 'flow','global', 'env'],
typeField: '#node-input-publicKeyType'
})
$('#node-input-autoDetectjwkid').typedInput({
type: 'bool',
value: false,
types:['bool'],
typeField: '#node-input-autoDetectjwkidType'
})
$('#node-input-jwkid').css({ width: '330px'}).typedInput({
type: 'msg',
types:['str', 'msg', 'flow','global'],
typeField: '#node-input-jwkidType'
})
$('#node-input-jwkurl').css({ width: '330px'}).typedInput({
type: 'msg',
types:['str', 'msg', 'flow','global'],
typeField: '#node-input-jwkurlType'
})
$('#node-input-ignoreExpiration').typedInput({
type: 'bool',
value: false,
types:['bool'],
typeField: '#node-input-ignoreExpirationType'
})
$('#node-input-ignoreNotBefore').typedInput({
type: 'bool',
value: false,
types:['bool'],
typeField: '#node-input-ignoreNotBeforeType'
})
$('#node-input-audience').typedInput({
type: 'msg',
types:['str', 'msg', 'flow','global', 'env'],
typeField: '#node-input-audienceType'
})
$('#node-input-issuer').typedInput({
type: 'msg',
types:['str', 'msg', 'flow','global', 'env'],
typeField: '#node-input-issuerType'
})
$('#node-input-token').typedInput({
types: [
{
value: 'payload',
options: [
{ value: "payload", label: "msg.payload"},
{ value: "token", label: "msg.token"},
{ value: "bearer_authorization_header", label: "Authorization Header (Bearer)"},
{ value: "query_params", label: "Query Params (access_token)"}
]
}
]
})
$('#node-input-maxAge').typedInput({
type: 'num',
value: '',
types:['num', 'msg', 'env', 'flow', 'global'],
typeField: '#node-input-maxAgeType'
})
if(! _this.mode){
hideAllModes()
$("#node-input-secret-div").show()
$("#node-input-algorithms-div").show()
createAlgorithmField(algorithmHMAC, '')
}
$("#node-input-validators").css('min-height','200px').editableList({
removable: true,
sortable: true,
header: $('<div style="display:flex"></div>').append($.parseHTML("<div style='width:25%;display: inline; padding-left:15px'>Property</div><div style='width:25%;display: inline;'>Constraint</div><div style='width:25%;display: inline;'>Value</div><div style='width:25%;display: inline;'>Error Message</div>")),
addItem: function(container, index, data) {
insertConstraint(container, data)
}
})
if(Array.isArray(this.constraints)){
this.constraints.forEach(x=>{
$("#node-input-validators").editableList('addItem', x);
})
}
function hideAllModes() {
$("#node-input-algorithms-div").hide()
$("#node-input-secret-div").hide()
$("#node-input-publicKey-div").hide()
$("#node-input-jwkid-div").hide()
$("#node-input-autoDetectjwkid-div").hide()
$("#node-input-jwkurl-div").hide()
}
function createAlgorithmField(options, value) {
$("#node-input-algorithms").remove();
$('<input>', {
type: 'text',
id: 'node-input-algorithms'
}).appendTo($("#node-input-algorithms-div"));
$("#node-input-algorithms").css({ width: '330px'}).typedInput({types:[{
multiple: true,
options: options
}]})
$('#node-input-algorithms').typedInput('value', value)
}
$('#node-input-output').typedInput({
type: 'msg',
value: 'payload',
types:['msg'],
typeField: '#node-input-outputType'
})
},
oneditsave: function() {
let _this = this
const constraints = getConstraints()
this.constraints = constraints
}
});
})();
</script>
<script type="text/markdown" data-help-name="jwt verify">
This node allows verifying the authenticity of a JSON Web Token (JWT).
### Inputs
: payload (string) : the payload of the message to publish.
### Outputs
1. Standard output
: payload (object) : the standard output of the command.
2. Standard error
: payload (object) : the standard error of the command.
### Form fields
* **Mode**: Mode to use for verifying the token.
* **Private Key**: In case of selecting the private key mode, the source of the key must be specified. The input variable must be of type buffer, for any of the selected cases, whether it comes from the message payload or environment variables.
* **Secret**: In the case of signing the token with a secret key, the source of the key must be specified, which can be a string, input message, or environment variable.
* **Auto detect JWK KID**: If true, it will automatically detect the JWK KID from the JWT kid header. Warning: this can be a security risk if the expected algorithms aren't selected. Make sure to not combine symmetric and asymmetric algorithms, as this can lead to security vulnerabilities.
* **Token origin**: Source of the token.
* **Algorithms**: Optional, if not specified a defaults will be used based on the type of key provided
* **Ignore Expiration**: The default value is false; if true, it bypasses the token's expiration date.
* **Ignore "NotBefore"**: The default value is false; if true, it bypasses the validation that the token is validated before the specified date.
* *Validate Audience**: Validates that the audience is correct.
* **Validate Issuer**: Validates that the token issuer is correct.
* **Validate Max Age**: The "max age" in a JSON Web Token (JWT) refers to the maximum time a token can be considered valid from the moment it was issued. The value is expressed in seconds
* **Validate Claims**: Allows validating the different claims of the token, ideal for validating the scopes, roles, and users with access to some API resources.
### References
- [Json Web Token](https://www.npmjs.com/package/jsonwebtoken)
</script>