@gregoriusrippenstein/node-red-contrib-nodedev 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,124 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('OTPGenerate',{
3
+ category: 'nodedev',
4
+ color: '#e5e4ef',
5
+ defaults: {
6
+ name: {value:""},
7
+
8
+ otptype: {value:"totp"},
9
+ secret: { value: "OTP_SECRET"},
10
+ secretType: { value: "env"},
11
+ issuer: { value: "" },
12
+ label: { value: "" },
13
+ digits: { value: 6 },
14
+ period: { value: 30 },
15
+ algorithm: { value: "SHA1" },
16
+ counter: { value: 0 },
17
+
18
+ property: { value:"payload" },
19
+ propertyType: { value:"msg" }
20
+ },
21
+ inputs:1,
22
+ outputs:1,
23
+ icon: "font-awesome/fa-user-secret",
24
+
25
+ label: function() {
26
+ return (this.name || this._def.paletteLabel);
27
+ },
28
+
29
+ labelStyle: function() {
30
+ return this.name?"node_label_italic":"";
31
+ },
32
+
33
+ oneditprepare: function() {
34
+ let that = this;
35
+
36
+ $("#node-input-secret").typedInput({
37
+ types:["env", "msg", "global", "cred"],
38
+ typeField: "#node-input-secretType"
39
+ });
40
+
41
+ $("#node-input-property").typedInput({
42
+ default:'msg',
43
+ types:['msg', 'flow', 'global'],
44
+ typeField: "#node-input-propertyType"
45
+ });
46
+
47
+ $("#node-input-property").typedInput('value', that.property || that._def.defaults.property.value || 'payload');
48
+ $("#node-input-property").typedInput('type', that.propertyType || that._def.defaults.propertyType.value || 'msg');
49
+ }
50
+ });
51
+ </script>
52
+
53
+ <script type="text/html" data-template-name="OTPGenerate">
54
+ <div class="form-row">
55
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
56
+ <input type="text" id="node-input-name" placeholder="Name">
57
+ </div>
58
+
59
+
60
+ <div class="form-row">
61
+ <label for="node-input-otptype"><i class="fa fa-tag"></i> Type</label>
62
+ <select id="node-input-otptype">
63
+ <option value="totp">Time-Based (TOTP)</option>
64
+ <option value="hotp">HMAC-based (HOTP)</option>
65
+ </select>
66
+ </div>
67
+
68
+ <div class="form-row" style="margin-left: 10px; margin-top: 20px;">
69
+ <label for="node-input-secret">
70
+ <i class="fa fa-key"></i>
71
+ Secret
72
+ </label>
73
+ <input type="text" id="node-input-secret">
74
+ <input type="hidden" id="node-input-secretType">
75
+ </div>
76
+
77
+ <div class="form-row">
78
+ <label for="node-input-algorithm"><i class="fa fa-tag"></i> Algorithm</label>
79
+ <select id="node-input-algorithm">
80
+ <option value="SHA1">SHA1</option>
81
+ <option value="SHA256">SHA256</option>
82
+ <option value="SHA512">SHA512</option>
83
+ </select>
84
+ </div>
85
+
86
+ <div class="form-row">
87
+ <label for="node-input-issuer"><i class="fa fa-tag"></i> Issuer</label>
88
+ <input type="text" id="node-input-issuer" placeholder="Issuer">
89
+ </div>
90
+
91
+ <div class="form-row">
92
+ <label for="node-input-label"><i class="fa fa-tag"></i> Label</label>
93
+ <input type="text" id="node-input-label" placeholder="Label">
94
+ </div>
95
+
96
+ <div class="form-row">
97
+ <label for="node-input-digits"><i class="fa fa-tag"></i> Digits</label>
98
+ <input type="text" id="node-input-digits" placeholder="Digits">
99
+ </div>
100
+
101
+ <div class="form-row">
102
+ <label for="node-input-period"><i class="fa fa-tag"></i> Period</label>
103
+ <input type="text" id="node-input-period" placeholder="Period">
104
+ </div>
105
+
106
+ <div class="form-row">
107
+ <label for="node-input-counter"><i class="fa fa-tag"></i> Counter</label>
108
+ <input type="text" id="node-input-counter" placeholder="Counter">
109
+ </div>
110
+
111
+ <hr/>
112
+
113
+ <div class="form-row">
114
+ <label for="node-input-property">Property to set</label>
115
+ <input type="text" id="node-input-property"/>
116
+ <input type="hidden" id="node-input-propertyType"/>
117
+ </div>
118
+ </script>
119
+
120
+ <script type="text/html" data-help-name="OTPGenerate">
121
+ <p>One Time Password (HOTP/TOTP) generator</p>
122
+
123
+ Based off the <a href="https://flows.nodered.org/node/node-red-contrib-otpauth">OTPAuth node</a>
124
+ </script>
@@ -0,0 +1,66 @@
1
+ const OTPAuth = require('otpauth');
2
+
3
+ module.exports = function (RED) {
4
+ function OTPGeneratorNode(config) {
5
+ RED.nodes.createNode(this, config);
6
+
7
+ let node = this;
8
+ let cfg = config
9
+
10
+ node.on('input', function (msg, send, done) {
11
+ RED.util.evaluateNodeProperty(cfg.secret, cfg.secretType, node, msg, (err, result) => {
12
+ if (err) {
13
+ node.status({ fill: "red", shape: "dot", text: "Failed" });
14
+ node.error("error occurred", { ...msg, _err: err })
15
+ } else {
16
+ let opts = undefined
17
+
18
+ if (typeof msg.payload !== "object") {
19
+ opts = {
20
+ issuer: cfg.issuer,
21
+ label: cfg.label,
22
+ digits: parseInt(cfg.digits),
23
+ secret: msg.payload || result,
24
+ algorithm: cfg.algorithm,
25
+ };
26
+ } else {
27
+ opts = { ...msg.payload }
28
+
29
+ opts.issuer = opts.issuer || cfg.issuer
30
+ opts.label = opts.label || cfg.label
31
+ opts.digits = opts.digits || parseInt(cfg.digits)
32
+ opts.secret = opts.secret || result
33
+ opts.algorithm = opts.algorithm || cfg.algorithm
34
+ }
35
+
36
+ let otp = undefined
37
+
38
+ if (cfg.otptype == 'totp') {
39
+ opts.period = parseInt((typeof msg.payload == "object" && msg.payload.period) || cfg.period)
40
+ otp = new OTPAuth.TOTP(opts)
41
+ } else if (cfg.otptype == 'hotp') {
42
+ opts.counter = parseInt((typeof msg.payload == "object" && msg.payload.counter) || cfg.counter)
43
+ otp = new OTPAuth.HOTP(opts)
44
+ } else {
45
+ return node.error("unknown type: " + cfg.otptype, msg)
46
+ }
47
+
48
+ let otpvalue = otp.generate();
49
+
50
+ if (cfg.propertyType === 'flow' || cfg.propertyType === 'global') {
51
+ node.context()[cfg.propertyType].set(cfg.property, otpvalue, () => {
52
+ send(msg);
53
+ done()
54
+ })
55
+ return;
56
+ } else {
57
+ RED.util.setMessageProperty(msg, cfg.property || 'payload', otpvalue, true);
58
+ send(msg);
59
+ }
60
+ }
61
+ })
62
+ });
63
+ }
64
+
65
+ RED.nodes.registerType('OTPGenerate', OTPGeneratorNode);
66
+ }
@@ -5,7 +5,17 @@
5
5
  hasUsers: false,
6
6
  defaults: {
7
7
  pkgname: { value: "node-red-dashboard" },
8
- pkgversion: { value: "latest" }
8
+ pkgversion: { value: "latest" },
9
+ /* otp stuff */
10
+ otptype: { value: "totp" },
11
+ algorithm: { value: "SHA1"},
12
+ secret: { value: "OTP_SECRET"},
13
+ secretType: { value: "env"},
14
+ issuer: { value: ""},
15
+ otplabel: { value: ""},
16
+ digits: { value: 6 },
17
+ counter: { value: 0 },
18
+ period: { value: 30 }
9
19
  },
10
20
  paletteLabel: 'PackageImporter',
11
21
  label: function () {
@@ -80,6 +80,53 @@ module.exports = function (RED) {
80
80
  }
81
81
  }
82
82
 
83
+ RED.httpAdmin.post("/NodeDevOtpGenerator",
84
+ RED.auth.needsPermission("nodedev.write"),
85
+ (req, res) => {
86
+ try {
87
+ if ( req.body ) {
88
+ var msg = req.body;
89
+ var cfgnode = req.body.cfgnode;
90
+ var node = RED.nodes.getNode(cfgnode.id)
91
+
92
+ RED.util.evaluateNodeProperty(cfgnode.secret, cfgnode.secretType, node, msg, (err, result) => {
93
+ if (err || (result || "").trim() == "") {
94
+ res.sendStatus(404);
95
+ } else {
96
+ const OTPAuth = require('otpauth');
97
+ let otp = undefined
98
+
99
+ let opts = {
100
+ issuer: cfgnode.issuer,
101
+ label: cfgnode.otplabel,
102
+ digits: parseInt(cfgnode.digits),
103
+ secret: result,
104
+ algorithm: cfgnode.algorithm,
105
+ };
106
+
107
+ if (cfgnode.otptype == 'totp') {
108
+ opts.period = parseInt(cfgnode.period)
109
+ otp = new OTPAuth.TOTP(opts)
110
+ } else if (cfg.otptype == 'hotp') {
111
+ opts.counter = parseInt(cfgnode.counter)
112
+ otp = new OTPAuth.HOTP(opts)
113
+ } else {
114
+ return res.send("unknown otp type").status(404)
115
+ }
116
+
117
+ res.send({data:{otp:otp.generate()}}).status(200)
118
+ }
119
+ })
120
+ } else {
121
+ res.sendStatus(406);
122
+ }
123
+ } catch (ex) {
124
+ console.error("ERROR", ex)
125
+ res.sendStatus(500);
126
+ }
127
+ }
128
+ )
129
+
83
130
  RED.httpAdmin.post("/NodeFactorySidebarCfg",
84
131
  RED.auth.needsPermission("nodedev.write"),
85
132
  (req, res) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name" : "@gregoriusrippenstein/node-red-contrib-nodedev",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "dependencies": {
5
5
  "pako": "^2.1.0",
6
6
  "tar-stream": "^3.1.6",
@@ -8,7 +8,8 @@
8
8
  "libnpmpublish": "^9.0.0",
9
9
  "streamx": "^2.15.5",
10
10
  "got": "^13",
11
- "pacote": "^17.0.4"
11
+ "pacote": "^17.0.4",
12
+ "otpauth": "^5.0.8"
12
13
  },
13
14
 
14
15
  "keywords": [
@@ -38,6 +39,7 @@
38
39
  "npmtarball": "nodes/30-npm-tarball.js",
39
40
  "noderedinstall": "nodes/40-node-red-install.js",
40
41
  "npmpublish": "nodes/50-npm-publish.js",
42
+ "otpgenerator": "nodes/60-otp-generator.js",
41
43
  "nodefactorycfg": "nodes/node-factory-sidebar-cfg.js"
42
44
  }
43
45
  },
@@ -2,6 +2,35 @@
2
2
  (function() {
3
3
  var globalYourConfigNode = null;
4
4
 
5
+ function doGenerateOTP(cfgnode, cb) {
6
+ ensureYourConfigNodeExists();
7
+
8
+ $.ajax({
9
+ url: "NodeDevOtpGenerator",
10
+ type: "POST",
11
+ contentType: "application/json; charset=utf-8",
12
+
13
+ data: JSON.stringify({
14
+ cfgnode: cfgnode
15
+ }),
16
+
17
+ success: function (resp) {
18
+ $('#node-input-nodedev-sidebar-otp').val(resp.data.otp)
19
+ cb(resp)
20
+ },
21
+
22
+ error: function (jqXHR, textStatus, errorThrown) {
23
+ RED.notify("Generating failed: " + textStatus, {
24
+ type: "error",
25
+ id: "NodeFactorySidebar",
26
+ timeout: 2000
27
+ });
28
+ console.log(textStatus,errorThrown )
29
+ }
30
+ });
31
+
32
+ }
33
+
5
34
  function doSubmission(pkgname,pkgversion) {
6
35
  ensureYourConfigNodeExists();
7
36
 
@@ -73,16 +102,25 @@
73
102
  let typ = RED.nodes.getType("NodeFactorySidebarCfg");
74
103
 
75
104
  globalYourConfigNode = {
76
- id: RED.nodes.id(), // on the server side, this is called RED.util.generateId()
77
- _def: typ,
78
- type: "NodeFactorySidebarCfg",
79
- hasUsers: false,
80
- users: [],
81
- name: "NodeFactorySidebar",
82
- label: function() { return this.name || "NodeFactorySidebar"},
83
- /* values and data defined by this config node */
84
- pkgname: typ.defaults.pkgname.value,
85
- pkgversion: typ.defaults.pkgversion.value
105
+ id: RED.nodes.id(), // on the server side, this is called RED.util.generateId()
106
+ _def: typ,
107
+ type: "NodeFactorySidebarCfg",
108
+ hasUsers: false,
109
+ users: [],
110
+ name: "NodeFactorySidebar",
111
+ label: function() { return this.name || "NodeFactorySidebar"},
112
+ /* values and data defined by this config node */
113
+ pkgname: typ.defaults.pkgname.value,
114
+ pkgversion: typ.defaults.pkgversion.value,
115
+ otptype: typ.defaults.otptype.value,
116
+ algorithm: typ.defaults.algorithm.value,
117
+ secret: typ.defaults.secret.value,
118
+ secretType: typ.defaults.secretType.value,
119
+ issuer: typ.defaults.issuer.value,
120
+ otplabel: typ.defaults.otplabel.value,
121
+ digits: typ.defaults.digits.value,
122
+ counter: typ.defaults.counter.value,
123
+ period: typ.defaults.period.value
86
124
  }
87
125
 
88
126
  // Add the new config node to the collection of Node-RED nodes
@@ -104,8 +142,8 @@
104
142
  // --> more details: https://nodered.org/docs/api/ui/sidebar/
105
143
  RED.sidebar.addTab({
106
144
  id: "NodeFactorySidebar",
107
- label: "Pkg Import", // short name for the tab
108
- name: "Package Import", // long name for the menu
145
+ label: "Node Dev", // short name for the tab
146
+ name: "Node Dev", // long name for the menu
109
147
  content: content,
110
148
  closeable: true,
111
149
  // disableOnEdit: true,
@@ -113,8 +151,34 @@
113
151
  iconClass: "fa fa-industry" // your fontawesome icon
114
152
  });
115
153
 
154
+ var tabs = RED.tabs.create({
155
+ id: 'func-nodedev-pkgimporter-tabs',
156
+ onchange: function(tab) {
157
+ $('#func-nodedev-pkgimporter-tabs-content').children().hide();
158
+ $('#' + tab.id).show();
159
+ }
160
+ });
161
+
162
+ // Add first tab, pkg importer
163
+ tabs.addTab({
164
+ id: 'func-nodedev-pkgimporter-tab-pkgimport',
165
+ iconClass: 'fa fa-industry',
166
+ label: 'PkgImporter'
167
+ });
168
+
169
+ // Add first tab, otp
170
+ tabs.addTab({
171
+ id: 'func-nodedev-pkgimporter-tab-otpgenerator',
172
+ iconClass: 'fa fa-user-secret',
173
+ label: 'OTP Generator'
174
+ });
175
+
116
176
  ensureYourConfigNodeExists();
117
177
 
178
+ /*
179
+ ************ Package importer handlers
180
+ */
181
+
118
182
  var doSomething = (e) => {
119
183
  if (e) { e.preventDefault(); }
120
184
  doSubmission()
@@ -145,6 +209,123 @@
145
209
  }
146
210
  })
147
211
  $("#node-input-nodefactorysidebar-package-name").val(globalYourConfigNode.pkgname);
212
+
213
+ /*
214
+ ************* OTP generator handlers
215
+ */
216
+
217
+ $("#node-input-nodedev-sidebar-otp-secret").typedInput({
218
+ types:["env", "msg", "global", "cred"],
219
+ typeField: "#node-input-nodedev-sidebar-otp-secretType"
220
+ });
221
+
222
+ $("#node-input-nodedev-sidebar-otp-secret").typedInput( 'value', globalYourConfigNode.secret || globalYourConfigNode._def.defaults.secret.value );
223
+ $("#node-input-nodedev-sidebar-otp-secret").typedInput( 'type', globalYourConfigNode.secretType || globalYourConfigNode._def.defaults.secretType.value);
224
+
225
+
226
+ $("#node-input-nodedev-sidebar-otp-secret").on('change', () => {
227
+ ensureYourConfigNodeExists();
228
+
229
+ let val = $("#node-input-nodedev-sidebar-otp-secret").typedInput('value');
230
+ let typ = $("#node-input-nodedev-sidebar-otp-secret").typedInput('type');
231
+
232
+ if ( val != globalYourConfigNode.secret ) {
233
+ globalYourConfigNode.secret = val;
234
+ RED.nodes.dirty(true);
235
+ }
236
+
237
+ if ( typ != globalYourConfigNode.secretType ) {
238
+ globalYourConfigNode.secretType = typ;
239
+ RED.nodes.dirty(true);
240
+ }
241
+ })
242
+
243
+ $('#node-input-nodedev-sidebar-otp-otptype').val(globalYourConfigNode.otptype || globalYourConfigNode._def.defaults.otptype.value)
244
+ $('#node-input-nodedev-sidebar-otp-algorithm').val(globalYourConfigNode.algorithm || globalYourConfigNode._def.defaults.algorithm.value)
245
+ $('#node-input-nodedev-sidebar-otp-issuer').val(globalYourConfigNode.issuer || globalYourConfigNode._def.defaults.issuer.value)
246
+ $('#node-input-nodedev-sidebar-otp-label').val(globalYourConfigNode.otplabel || globalYourConfigNode._def.defaults.otplabel.value)
247
+ $('#node-input-nodedev-sidebar-otp-digits').val(globalYourConfigNode.digits || globalYourConfigNode._def.defaults.digits.value)
248
+ $('#node-input-nodedev-sidebar-otp-period').val(globalYourConfigNode.period || globalYourConfigNode._def.defaults.period.value)
249
+ $('#node-input-nodedev-sidebar-otp-counter').val(globalYourConfigNode.counter || globalYourConfigNode._def.defaults.counter.value)
250
+
251
+ $('#node-input-nodedev-sidebar-otp-otptype').on('change', () => {
252
+ ensureYourConfigNodeExists();
253
+ let val = $('#node-input-nodedev-sidebar-otp-otptype').val()
254
+
255
+ if ( val != globalYourConfigNode.otptype) {
256
+ globalYourConfigNode.otptype = val
257
+ RED.nodes.dirty(true);
258
+ }
259
+ })
260
+
261
+ $('#node-input-nodedev-sidebar-otp-algorithm').on('change', () => {
262
+ ensureYourConfigNodeExists();
263
+ let val = $('#node-input-nodedev-sidebar-otp-algorithm').val()
264
+
265
+ if ( val != globalYourConfigNode.algorithm) {
266
+ globalYourConfigNode.algorithm = val
267
+ RED.nodes.dirty(true);
268
+ }
269
+ })
270
+
271
+ $('#node-input-nodedev-sidebar-otp-issuer').on('change', () => {
272
+ ensureYourConfigNodeExists();
273
+ let val = $('#node-input-nodedev-sidebar-otp-issuer').val()
274
+
275
+ if ( val != globalYourConfigNode.issuer) {
276
+ globalYourConfigNode.issuer = val
277
+ RED.nodes.dirty(true);
278
+ }
279
+ })
280
+
281
+ $('#node-input-nodedev-sidebar-otp-label').on('change', () => {
282
+ ensureYourConfigNodeExists();
283
+ let val = $('#node-input-nodedev-sidebar-otp-label').val()
284
+
285
+ if ( val != globalYourConfigNode.otplabel) {
286
+ globalYourConfigNode.otplabel = val
287
+ RED.nodes.dirty(true);
288
+ }
289
+ })
290
+
291
+ $('#node-input-nodedev-sidebar-otp-digits').on('change', () => {
292
+ ensureYourConfigNodeExists();
293
+ let val = $('#node-input-nodedev-sidebar-otp-digits').val()
294
+
295
+ if ( val != globalYourConfigNode.digits) {
296
+ globalYourConfigNode.digits = val
297
+ RED.nodes.dirty(true);
298
+ }
299
+ })
300
+
301
+ $('#node-input-nodedev-sidebar-otp-period').on('change', () => {
302
+ ensureYourConfigNodeExists();
303
+ let val = $('#node-input-nodedev-sidebar-otp-period').val()
304
+
305
+ if ( val != globalYourConfigNode.period) {
306
+ globalYourConfigNode.period = val
307
+ RED.nodes.dirty(true);
308
+ }
309
+ })
310
+
311
+ $('#node-input-nodedev-sidebar-otp-counter').on('change', () => {
312
+ ensureYourConfigNodeExists();
313
+ let val = $('#node-input-nodedev-sidebar-otp-counter').val()
314
+
315
+ if ( val != globalYourConfigNode.counter) {
316
+ globalYourConfigNode.counter = val
317
+ RED.nodes.dirty(true);
318
+ }
319
+ })
320
+
321
+ $('#nodedev-sidebar-otp-generate-btn').on('click', e => {
322
+ if ( e) { e.preventDefault()}
323
+ ensureYourConfigNodeExists();
324
+
325
+ doGenerateOTP(globalYourConfigNode, (resp) => {
326
+ $('#node-input-nodedev-sidebar-otp').val(resp.data.otp)
327
+ })
328
+ })
148
329
  };
149
330
 
150
331
  RED.events.on('runtime-state', initialiseConfigNodeOnce);
@@ -153,40 +334,109 @@
153
334
 
154
335
  <!-- The html for the right sidebar plugin screen -->
155
336
  <script type="text/x-red" data-template-name="NodeFactorySidebar">
156
- <div class="form-row" style="margin-left: 10px; margin-top: 30px;">
157
- Import <a href="https://npmjs.com" target=_blank>npmJs.com</a> packages as a flow. packages
158
- are imported as a collection of PkgFile nodes. This can help to learn from other
159
- peoples experiences and code.
160
- </div>
161
-
162
- <div class="form-row" style="margin-left: 10px;">
163
- Enter package details here:
164
- </div>
165
-
166
- <div class="form-row" style="margin-left: 10px;">
167
- <label for="node-input-nodefactorysidebar-package-name">
168
- <i class="fa fa-tag"></i>
169
- Name
170
- </label>
171
- <input type="text" id="node-input-nodefactorysidebar-package-name"
337
+ <div class="form-row func-nodedev-pkgimporter-tabs-row">
338
+ <ul style="min-width: 600px; margin-bottom: 20px;" id="func-nodedev-pkgimporter-tabs"></ul>
339
+ </div>
340
+
341
+ <div id="func-nodedev-pkgimporter-tabs-content" style="min-height: calc(100% - 95px);">
342
+
343
+ <div id="func-nodedev-pkgimporter-tab-pkgimport" style="display:none">
344
+
345
+ <div class="form-row" style="margin-left: 10px; margin-top: 30px;">
346
+ Import <a href="https://npmjs.com" target=_blank>npmJs.com</a> packages as a flow. packages
347
+ are imported as a collection of PkgFile nodes. This can help to learn from other
348
+ peoples experiences and code.
349
+ </div>
350
+
351
+ <div class="form-row" style="margin-left: 10px;">
352
+ Enter package details here:
353
+ </div>
354
+
355
+ <div class="form-row" style="margin-left: 10px;">
356
+ <label for="node-input-nodefactorysidebar-package-name">
357
+ <i class="fa fa-tag"></i>
358
+ Name
359
+ </label>
360
+ <input type="text" id="node-input-nodefactorysidebar-package-name"
172
361
  placeholder="node-red-dashboard">
173
- </div>
174
-
175
- <div class="form-row" style="margin-left: 10px;">
176
- <label for="node-input-nodefactorysidebar-package-version">
177
- <i class="fa fa-tag"></i>
178
- Version
179
- </label>
180
- <input type="text" id="node-input-nodefactorysidebar-package-version"
362
+ </div>
363
+
364
+ <div class="form-row" style="margin-left: 10px;">
365
+ <label for="node-input-nodefactorysidebar-package-version">
366
+ <i class="fa fa-tag"></i>
367
+ Version
368
+ </label>
369
+ <input type="text" id="node-input-nodefactorysidebar-package-version"
181
370
  placeholder="3.5.0">
182
- </div>
371
+ </div>
183
372
 
184
- <div class="form-row">
185
- <div style="position: relative; height: 100%; margin: 15px;">
186
- <button id="node-input-nodefactorysidebar-generate-btn"
373
+ <div class="form-row">
374
+ <div style="position: relative; height: 100%; margin: 15px;">
375
+ <button id="node-input-nodefactorysidebar-generate-btn"
187
376
  class="red-ui-button"
188
377
  style="width: 100%; margin-top: 30px">Import Package</button>
189
- </div>
190
- </div>
191
-
378
+ </div>
379
+ </div>
380
+
381
+ </div>
382
+
383
+ <div id="func-nodedev-pkgimporter-tab-otpgenerator" style="display:none">
384
+
385
+ <div class="form-row" style="margin-left: 10px;">
386
+ <label for="node-input-nodedev-sidebar-otp-otptype"><i class="fa fa-tag"></i> Type</label>
387
+ <select id="node-input-nodedev-sidebar-otp-otptype">
388
+ <option value="totp">Time-Based (TOTP)</option>
389
+ <option value="hotp">HMAC-based (HOTP)</option>
390
+ </select>
391
+ </div>
392
+
393
+ <div class="form-row" style="margin-left: 10px;">
394
+ <label for="node-input-nodedev-sidebar-otp-secret">
395
+ <i class="fa fa-key"></i>
396
+ Secret
397
+ </label>
398
+ <input type="text" id="node-input-nodedev-sidebar-otp-secret">
399
+ <input type="hidden" id="node-input-nodedev-sidebar-otp-secretType">
400
+ </div>
401
+
402
+ <div class="form-row" style="margin-left: 10px;">
403
+ <label for="node-input-nodedev-sidebar-otp-algorithm"><i class="fa fa-tag"></i> Algorithm</label>
404
+ <select id="node-input-nodedev-sidebar-otp-algorithm">
405
+ <option value="SHA1">SHA1</option>
406
+ <option value="SHA256">SHA256</option>
407
+ <option value="SHA512">SHA512</option>
408
+ </select>
409
+ </div>
410
+
411
+ <div class="form-row" style="margin-left: 10px;">
412
+ <label for="node-input-nodedev-sidebar-otp-issuer"><i class="fa fa-tag"></i> Issuer</label>
413
+ <input type="text" id="node-input-nodedev-sidebar-otp-issuer" placeholder="Issuer">
414
+ </div>
415
+
416
+ <div class="form-row" style="margin-left: 10px;">
417
+ <label for="node-input-nodedev-sidebar-otp-label"><i class="fa fa-tag"></i> Label</label>
418
+ <input type="text" id="node-input-nodedev-sidebar-otp-label" placeholder="Label">
419
+ </div>
420
+
421
+ <div class="form-row" style="margin-left: 10px;">
422
+ <label for="node-input-nodedev-sidebar-otp-digits"><i class="fa fa-tag"></i> Digits</label>
423
+ <input type="text" id="node-input-nodedev-sidebar-otp-digits" placeholder="Digits">
424
+ </div>
425
+
426
+ <div class="form-row" style="margin-left: 10px;">
427
+ <label for="node-input-nodedev-sidebar-otp-period"><i class="fa fa-tag"></i> Period</label>
428
+ <input type="text" id="node-input-nodedev-sidebar-otp-period" placeholder="Period">
429
+ </div>
430
+
431
+ <div class="form-row" style="margin-left: 10px;">
432
+ <label for="node-input-nodedev-sidebar-otp-counter"><i class="fa fa-tag"></i> Counter</label>
433
+ <input type="text" id="node-input-nodedev-sidebar-otp-counter" placeholder="Counter">
434
+ </div>
435
+
436
+ <div class="form-row" style="margin-left: 10px;">
437
+ <button id="nodedev-sidebar-otp-generate-btn" class="button">Generate</button>
438
+ <input type="text" id="node-input-nodedev-sidebar-otp" placeholder="OTP...">
439
+ </div>
440
+ </div>
441
+ </div>
192
442
  </script>