node-red-contrib-ipgeolocation 1.0.0

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,214 @@
1
+ <!-- ============================================================
2
+ ipgeo-security.html
3
+ Single-IP threat/security lookup node — editor template + help
4
+ ============================================================ -->
5
+
6
+ <script type="text/javascript">
7
+ (function () {
8
+ 'use strict';
9
+
10
+ RED.nodes.registerType('ipgeo-security', {
11
+ category: 'IPGeolocation',
12
+ color: '#d08a5d',
13
+ icon: 'font-awesome/fa-shield',
14
+ paletteLabel: 'ipgeo security',
15
+
16
+ defaults: {
17
+ name: { value: '' },
18
+ server: { value: '', type: 'ipgeo-config', required: true },
19
+ ipType: { value: 'str' },
20
+ ipValue: { value: '' },
21
+ fields: { value: '' },
22
+ excludes: { value: '' },
23
+ outputProp: { value: 'payload' },
24
+ outputType: { value: 'msg' },
25
+ twoOutputs: { value: true },
26
+ },
27
+
28
+ inputs: 1,
29
+ outputs: 2, // [success, error]
30
+
31
+ outputLabels: function (idx) {
32
+ return idx === 0 ? 'success' : 'error';
33
+ },
34
+
35
+ label: function () {
36
+ return this.name || 'ipgeo security';
37
+ },
38
+
39
+ labelStyle: function () {
40
+ return this.name ? 'node_label_italic' : '';
41
+ },
42
+
43
+ oneditprepare: function () {
44
+ const node = this;
45
+
46
+ // ── IP input — typed input widget ──────────────────────────────────
47
+ $('#node-input-ipValue').typedInput({
48
+ default: 'str',
49
+ typeField: '#node-input-ipType',
50
+ types: ['msg', 'flow', 'global', 'str'],
51
+ });
52
+
53
+ // ── Output property typed input ────────────────────────────────────
54
+ $('#node-input-outputProp').typedInput({
55
+ default: 'msg',
56
+ typeField: '#node-input-outputType',
57
+ types: ['msg', 'flow', 'global'],
58
+ });
59
+
60
+ // ── Two outputs checkbox ───────────────────────────────────────────
61
+ $('#node-input-twoOutputs').prop('checked', node.twoOutputs !== false);
62
+ },
63
+
64
+ oneditsave: function () {
65
+ this.twoOutputs = $('#node-input-twoOutputs').prop('checked');
66
+ },
67
+ });
68
+ }());
69
+ </script>
70
+
71
+ <!-- ── Edit template ── -->
72
+ <script type="text/html" data-template-name="ipgeo-security">
73
+ <!-- Name -->
74
+ <div class="form-row">
75
+ <label for="node-input-name"><i class="fa fa-tag"></i>&nbsp;Name</label>
76
+ <input type="text" id="node-input-name" placeholder="ipgeo security" style="width:70%" />
77
+ </div>
78
+
79
+ <!-- Config node (shared with ipgeo-lookup) -->
80
+ <div class="form-row">
81
+ <label for="node-input-server"><i class="fa fa-key"></i>&nbsp;API Config</label>
82
+ <input type="text" id="node-input-server" style="width:70%" />
83
+ <input type="hidden" id="node-input-ipType" />
84
+ </div>
85
+
86
+ <hr />
87
+
88
+ <!-- IP address input -->
89
+ <div class="form-row">
90
+ <label for="node-input-ipValue"><i class="fa fa-globe"></i>&nbsp;IP Address</label>
91
+ <input type="text" id="node-input-ipValue" placeholder="Leave empty to detect caller IP" style="width:70%" />
92
+ <div style="margin-top:4px;font-size:0.82em;color:#888;">
93
+ IPv4 or IPv6 only — <strong>domain names are not supported</strong> by this
94
+ endpoint and will return an error. Leave empty to auto-detect the
95
+ caller's IP. You can also point to a msg/flow/global property.
96
+ </div>
97
+ </div>
98
+
99
+ <hr />
100
+
101
+ <!-- Fields & Excludes -->
102
+ <div class="form-row">
103
+ <label for="node-input-fields"><i class="fa fa-filter"></i>&nbsp;Fields</label>
104
+ <input type="text" id="node-input-fields" placeholder="e.g. security.threat_score,security.is_vpn"
105
+ style="width:70%" />
106
+ </div>
107
+ <div class="form-row">
108
+ <label for="node-input-excludes"><i class="fa fa-minus-square"></i>&nbsp;Excludes</label>
109
+ <input type="text" id="node-input-excludes" placeholder="e.g. security.is_tor,security.is_cloud_provider"
110
+ style="width:70%" />
111
+ </div>
112
+
113
+ <hr />
114
+
115
+ <!-- Output property -->
116
+ <div class="form-row">
117
+ <label for="node-input-outputProp"><i class="fa fa-sign-out"></i>&nbsp;Output to</label>
118
+ <input type="text" id="node-input-outputProp" style="width:70%" />
119
+ <input type="hidden" id="node-input-outputType" />
120
+ </div>
121
+
122
+ <!-- Two outputs toggle -->
123
+ <div class="form-row">
124
+ <label><i class="fa fa-random"></i>&nbsp;Outputs</label>
125
+ <input type="checkbox" id="node-input-twoOutputs" style="width:auto;margin-right:6px" />
126
+ <label for="node-input-twoOutputs" style="display:inline;font-weight:normal">
127
+ Use second output for errors (instead of throwing to Catch node)
128
+ </label>
129
+ </div>
130
+ </script>
131
+
132
+ <!-- ── Help panel ── -->
133
+ <script type="text/html" data-help-name="ipgeo-security">
134
+ <p>
135
+ Returns threat/risk flags for a single IPv4 or IPv6 address via
136
+ <strong>IPGeolocation.io v3 Security API</strong>: VPN, proxy,
137
+ residential proxy, Tor, relay, bot, spam, known-attacker, and
138
+ cloud-provider signals, with confidence scores and provider names
139
+ where available.
140
+ </p>
141
+
142
+ <h3>Inputs</h3>
143
+ <dl class="message-properties">
144
+ <dt>payload <span class="property-type">any</span></dt>
145
+ <dd>Passed through unchanged.</dd>
146
+ </dl>
147
+
148
+ <h3>Outputs</h3>
149
+ <ol class="node-ports">
150
+ <li>
151
+ <strong>Success</strong>
152
+ <dl class="message-properties">
153
+ <dt>payload <span class="property-type">object</span></dt>
154
+ <dd>
155
+ <code>{ ip, security }</code> where <code>security</code> contains
156
+ <code>threat_score</code>, <code>is_tor</code>, <code>is_proxy</code>,
157
+ <code>proxy_provider_names</code>, <code>proxy_confidence_score</code>,
158
+ <code>proxy_last_seen</code>, <code>is_residential_proxy</code>,
159
+ <code>is_vpn</code>, <code>vpn_provider_names</code>,
160
+ <code>vpn_confidence_score</code>, <code>vpn_last_seen</code>,
161
+ <code>is_relay</code>, <code>relay_provider_name</code>,
162
+ <code>is_anonymous</code>, <code>is_known_attacker</code>,
163
+ <code>is_bot</code>, <code>is_spam</code>,
164
+ <code>is_cloud_provider</code>, <code>cloud_provider_name</code>.
165
+ </dd>
166
+ <dt class="optional">ipgeo_credits <span class="property-type">number</span></dt>
167
+ <dd>Credits consumed by this request (flat 2 credits per valid IP).</dd>
168
+ </dl>
169
+ </li>
170
+ <li>
171
+ <strong>Error</strong> (when <em>Use second output</em> is checked)
172
+ <dl class="message-properties">
173
+ <dt>error <span class="property-type">object</span></dt>
174
+ <dd>
175
+ <code>{ code, message, status, body }</code> — structured error object.
176
+ Error codes include <code>AUTH_FAILED</code>, <code>RATE_LIMITED</code>,
177
+ <code>TIMEOUT</code>, <code>BAD_REQUEST</code>, etc.
178
+ </dd>
179
+ </dl>
180
+ </li>
181
+ </ol>
182
+
183
+ <h3>Details</h3>
184
+ <ul>
185
+ <li>Leave <em>IP Address</em> empty to look up the caller's IP address.</li>
186
+ <li>
187
+ <strong>Domain names are not accepted</strong> — only IPv4/IPv6
188
+ addresses. Passing a domain returns a 401 from the API.
189
+ </li>
190
+ <li>Every successful lookup costs a flat <strong>2 credits</strong>, regardless of fields/excludes used.</li>
191
+ <li>
192
+ <em>Fields</em> restricts the response to named fields
193
+ (e.g. <code>security.threat_score</code>).
194
+ </li>
195
+ <li>
196
+ <em>Excludes</em> removes fields from the <code>security</code> object
197
+ (e.g. <code>security.is_tor,security.is_cloud_provider</code>). The
198
+ top-level <code>ip</code> field is always present and cannot be excluded.
199
+ </li>
200
+ <li>
201
+ Bogon/private/malformed IPs are rejected with HTTP 423 and are not billed.
202
+ </li>
203
+ <li>Failed requests are retried up to 3 times with exponential back-off.</li>
204
+ </ul>
205
+
206
+ <h3>References</h3>
207
+ <ul>
208
+ <li>
209
+ <a href="https://ipgeolocation.io/documentation/ip-security-api.html" target="_blank">
210
+ IP Security API documentation
211
+ </a>
212
+ </li>
213
+ </ul>
214
+ </script>
@@ -0,0 +1,150 @@
1
+ /**
2
+ * ipgeo-security.js
3
+ *
4
+ * Node-RED node: "ipgeo security"
5
+ *
6
+ * Performs a single-IP threat/security lookup via the IPGeolocation.io
7
+ * /v3/security endpoint (GET).
8
+ *
9
+ * Features
10
+ * ─────────
11
+ * • IPv4 or IPv6 input, or empty (caller IP auto-detect)
12
+ * NOTE: unlike /v3/ipgeo, the security endpoint does NOT accept domain
13
+ * names — passing one returns a 401 from the API.
14
+ * • `fields` and `excludes` for bandwidth optimisation (dot-notation,
15
+ * e.g. security.threat_score / security.is_tor)
16
+ * • IP can be sourced from node config OR msg property (dynamic)
17
+ * • Two outputs: [success, error] — error output is optional
18
+ * • Exposes credits consumed on msg.ipgeo_credits (flat 2 credits/lookup)
19
+ * • Full retry + exponential backoff via shared api-client
20
+ *
21
+ * Shares the same `ipgeo-config` config node (API key) as ipgeo-lookup.
22
+ */
23
+
24
+ 'use strict';
25
+
26
+ const { request, IPGeoError } = require('./lib/api-client');
27
+ const {
28
+ setStatus,
29
+ resolveValue,
30
+ setOutputProperty,
31
+ handleNodeError,
32
+ buildCommonParams,
33
+ } = require('./lib/node-helpers');
34
+
35
+ module.exports = function (RED) {
36
+
37
+ function IPGeoSecurityNode(config) {
38
+ RED.nodes.createNode(this, config);
39
+
40
+ // ── Config node (API key) ─────────────────────────────────────────────
41
+ const configNode = RED.nodes.getNode(config.server);
42
+
43
+ // ── Node configuration ────────────────────────────────────────────────
44
+ this.name = config.name;
45
+ this.ipType = config.ipType || 'str'; // 'str' | 'msg' | 'flow' | 'global'
46
+ this.ipValue = config.ipValue || ''; // value or msg/flow/global prop path
47
+ this.fields = config.fields || '';
48
+ this.excludes = config.excludes || '';
49
+ this.outputProp = config.outputProp || 'payload'; // output property
50
+ this.outputType = config.outputType || 'msg';
51
+ this.twoOutputs = config.twoOutputs !== false; // default true
52
+
53
+ const node = this;
54
+
55
+ // ── Pre-flight: warn if no config node ───────────────────────────────
56
+ if (!configNode) {
57
+ setStatus(node, 'NO_KEY');
58
+ node.warn('No IPGeolocation.io config node selected — configure an API key first.');
59
+ } else {
60
+ setStatus(node, 'IDLE');
61
+ }
62
+
63
+ // ── Input handler ─────────────────────────────────────────────────────
64
+ node.on('input', async function (msg, send, done) {
65
+ // Node-RED 0.x compat
66
+ send = send || function () { node.send.apply(node, arguments); };
67
+
68
+ // ── Validate API key availability ─────────────────────────────────
69
+ const apiKey = configNode && configNode.credentials
70
+ ? configNode.credentials.apiKey
71
+ : null;
72
+
73
+ if (!apiKey) {
74
+ setStatus(node, 'NO_KEY');
75
+ const err = new IPGeoError('No API key configured', 'NO_API_KEY');
76
+ handleNodeError(node, err, msg, done, send, node.twoOutputs);
77
+ return;
78
+ }
79
+
80
+ // ── Resolve IP from msg/flow/global or static config ──────────────
81
+ let ip = '';
82
+ if (node.ipType === 'msg') {
83
+ ip = resolveValue(RED, node, msg,
84
+ { value: node.ipValue, type: 'msg' }) || '';
85
+ } else if (node.ipType === 'flow') {
86
+ ip = resolveValue(RED, node, msg,
87
+ { value: node.ipValue, type: 'flow' }) || '';
88
+ } else if (node.ipType === 'global') {
89
+ ip = resolveValue(RED, node, msg,
90
+ { value: node.ipValue, type: 'global' }) || '';
91
+ } else {
92
+ // 'str' — static value from node config; can be empty for caller-IP
93
+ ip = (node.ipValue || '').trim();
94
+ }
95
+
96
+ ip = String(ip).trim();
97
+
98
+ // ── Build query parameters ────────────────────────────────────────
99
+ // Security endpoint has no `lang` or `include` params — just
100
+ // apiKey / ip / fields / excludes.
101
+ const params = buildCommonParams(apiKey, {
102
+ fields: node.fields,
103
+ excludes: node.excludes,
104
+ });
105
+
106
+ if (ip) params.ip = ip;
107
+
108
+ // ── Set working status ────────────────────────────────────────────
109
+ setStatus(node, 'WORKING');
110
+
111
+ // ── API call ──────────────────────────────────────────────────────
112
+ try {
113
+ const { data, credits } = await request({
114
+ method: 'GET',
115
+ path: '/security',
116
+ params,
117
+ });
118
+
119
+ // ── Success ────────────────────────────────────────────────────
120
+ const score = data.security?.threat_score;
121
+ setStatus(node, 'OK',
122
+ score !== undefined ? `threat ${score}` : (data.ip || 'ok'));
123
+
124
+ // Write result to configured output property
125
+ setOutputProperty(RED, node, msg,
126
+ { value: node.outputProp, type: node.outputType }, data);
127
+
128
+ // Expose credits consumed (informational) — flat 2 credits/lookup
129
+ if (credits !== null) msg.ipgeo_credits = credits;
130
+
131
+ // Clear error property if previously set
132
+ delete msg.error;
133
+
134
+ send(node.twoOutputs ? [msg, null] : msg);
135
+ if (done) done();
136
+
137
+ } catch (err) {
138
+ handleNodeError(node, err, msg, done, send, node.twoOutputs);
139
+ }
140
+ });
141
+
142
+ // ── Clean up on node removal/redeploy ─────────────────────────────────
143
+ node.on('close', function () {
144
+ setStatus(node, 'IDLE');
145
+ });
146
+ }
147
+
148
+ RED.nodes.registerType('ipgeo-security', IPGeoSecurityNode);
149
+
150
+ };
@@ -0,0 +1,201 @@
1
+ <!-- ============================================================
2
+ ipgeo-timezone.html
3
+ Timezone lookup node with 7-mode editor
4
+ ============================================================ -->
5
+
6
+ <script type="text/javascript">
7
+ (function () {
8
+ 'use strict';
9
+
10
+ RED.nodes.registerType('ipgeo-timezone', {
11
+ category: 'IPGeolocation',
12
+ color: '#5da0d0',
13
+ icon: 'font-awesome/fa-clock-o',
14
+ paletteLabel:'ipgeo timezone',
15
+
16
+ defaults: {
17
+ name: { value: '' },
18
+ server: { value: '', type: 'ipgeo-config', required: true },
19
+ mode: { value: 'tz' },
20
+ inputType: { value: 'str' },
21
+ inputValue: { value: '' },
22
+ latType: { value: 'str' },
23
+ latValue: { value: '' },
24
+ lngType: { value: 'str' },
25
+ lngValue: { value: '' },
26
+ outputProp: { value: 'payload' },
27
+ outputType: { value: 'msg' },
28
+ twoOutputs: { value: true },
29
+ },
30
+
31
+ inputs: 1,
32
+ outputs: 2,
33
+ outputLabels: function (idx) { return idx === 0 ? 'success' : 'error'; },
34
+
35
+ label: function () {
36
+ return this.name ||
37
+ 'ipgeo timezone (' + (this.mode || 'tz') + ')';
38
+ },
39
+
40
+ oneditprepare: function () {
41
+ const node = this;
42
+
43
+ // ── Mode selector — show/hide input rows ───────────────────────────
44
+ function updateModeRows() {
45
+ const mode = $('#node-input-mode').val();
46
+ $('#ipgeo-tz-single-row').toggle(mode !== 'coords');
47
+ $('#ipgeo-tz-coords-rows').toggle(mode === 'coords');
48
+
49
+ // Update placeholder text based on mode
50
+ const placeholders = {
51
+ tz: 'e.g. America/New_York',
52
+ ip: 'e.g. 8.8.8.8',
53
+ location: 'e.g. New York, USA',
54
+ iata: 'e.g. JFK',
55
+ icao: 'e.g. KJFK',
56
+ unlocode: 'e.g. USNYC',
57
+ };
58
+ const p = placeholders[mode] || '';
59
+ $('#node-input-inputValue').attr('placeholder', p);
60
+ }
61
+
62
+ $('#node-input-mode').val(node.mode || 'tz').on('change', updateModeRows);
63
+
64
+ // ── Single-value typed input ───────────────────────────────────────
65
+ $('#node-input-inputValue').typedInput({
66
+ default: 'str',
67
+ typeField: '#node-input-inputType',
68
+ types: ['msg', 'flow', 'global', 'str'],
69
+ });
70
+
71
+ // ── Coords typed inputs ────────────────────────────────────────────
72
+ $('#node-input-latValue').typedInput({
73
+ default: 'str',
74
+ typeField: '#node-input-latType',
75
+ types: ['msg', 'flow', 'global', 'str', 'num'],
76
+ });
77
+ $('#node-input-lngValue').typedInput({
78
+ default: 'str',
79
+ typeField: '#node-input-lngType',
80
+ types: ['msg', 'flow', 'global', 'str', 'num'],
81
+ });
82
+
83
+ // ── Output property ────────────────────────────────────────────────
84
+ $('#node-input-outputProp').typedInput({
85
+ default: 'msg',
86
+ typeField: '#node-input-outputType',
87
+ types: ['msg', 'flow', 'global'],
88
+ });
89
+
90
+ $('#node-input-twoOutputs').prop('checked', node.twoOutputs !== false);
91
+
92
+ updateModeRows();
93
+ },
94
+
95
+ oneditsave: function () {
96
+ this.twoOutputs = $('#node-input-twoOutputs').prop('checked');
97
+ },
98
+ });
99
+ }());
100
+ </script>
101
+
102
+ <!-- ── Edit template ── -->
103
+ <script type="text/html" data-template-name="ipgeo-timezone">
104
+ <div class="form-row">
105
+ <label for="node-input-name"><i class="fa fa-tag"></i>&nbsp;Name</label>
106
+ <input type="text" id="node-input-name" placeholder="ipgeo timezone" style="width:70%"/>
107
+ </div>
108
+ <div class="form-row">
109
+ <label for="node-input-server"><i class="fa fa-key"></i>&nbsp;API Config</label>
110
+ <input type="text" id="node-input-server" style="width:70%"/>
111
+ </div>
112
+ <hr/>
113
+
114
+ <!-- Lookup mode selector -->
115
+ <div class="form-row">
116
+ <label for="node-input-mode"><i class="fa fa-search"></i>&nbsp;Lookup by</label>
117
+ <select id="node-input-mode" style="width:70%">
118
+ <option value="tz">Timezone Name (tz)</option>
119
+ <option value="ip">IP Address (ip)</option>
120
+ <option value="location">Address / City (location)</option>
121
+ <option value="coords">Coordinates (lat + long)</option>
122
+ <option value="iata">IATA Airport Code</option>
123
+ <option value="icao">ICAO Airport Code</option>
124
+ <option value="unlocode">UN/LOCODE</option>
125
+ </select>
126
+ </div>
127
+
128
+ <!-- Single value row (all modes except coords) -->
129
+ <div id="ipgeo-tz-single-row" class="form-row">
130
+ <label for="node-input-inputValue"><i class="fa fa-pencil"></i>&nbsp;Value</label>
131
+ <input type="text" id="node-input-inputValue" style="width:70%"/>
132
+ <input type="hidden" id="node-input-inputType"/>
133
+ </div>
134
+
135
+ <!-- Coordinates rows -->
136
+ <div id="ipgeo-tz-coords-rows">
137
+ <div class="form-row">
138
+ <label for="node-input-latValue"><i class="fa fa-arrows-v"></i>&nbsp;Latitude</label>
139
+ <input type="text" id="node-input-latValue" placeholder="e.g. 40.7128" style="width:70%"/>
140
+ <input type="hidden" id="node-input-latType"/>
141
+ </div>
142
+ <div class="form-row">
143
+ <label for="node-input-lngValue"><i class="fa fa-arrows-h"></i>&nbsp;Longitude</label>
144
+ <input type="text" id="node-input-lngValue" placeholder="e.g. -74.0060" style="width:70%"/>
145
+ <input type="hidden" id="node-input-lngType"/>
146
+ </div>
147
+ </div>
148
+
149
+ <hr/>
150
+ <div class="form-row">
151
+ <label for="node-input-outputProp"><i class="fa fa-sign-out"></i>&nbsp;Output to</label>
152
+ <input type="text" id="node-input-outputProp" style="width:70%"/>
153
+ <input type="hidden" id="node-input-outputType"/>
154
+ </div>
155
+ <div class="form-row">
156
+ <label><i class="fa fa-random"></i>&nbsp;Outputs</label>
157
+ <input type="checkbox" id="node-input-twoOutputs" style="width:auto;margin-right:6px"/>
158
+ <label for="node-input-twoOutputs" style="display:inline;font-weight:normal">
159
+ Use second output for errors
160
+ </label>
161
+ </div>
162
+ </script>
163
+
164
+ <!-- ── Help panel ── -->
165
+ <script type="text/html" data-help-name="ipgeo-timezone">
166
+ <p>
167
+ Retrieves current date, time, and timezone information from the
168
+ <strong>IPGeolocation.io Timezone API</strong>.
169
+ </p>
170
+
171
+ <h3>Lookup Modes</h3>
172
+ <ul>
173
+ <li><strong>Timezone Name</strong> — IANA name, e.g. <code>America/New_York</code></li>
174
+ <li><strong>IP Address</strong> — any IPv4 or IPv6 address</li>
175
+ <li><strong>Address / City</strong> — free text, e.g. <code>Paris, France</code></li>
176
+ <li><strong>Coordinates</strong> — decimal lat &amp; long</li>
177
+ <li><strong>IATA</strong> — airport code, e.g. <code>LHR</code></li>
178
+ <li><strong>ICAO</strong> — airport code, e.g. <code>EGLL</code></li>
179
+ <li><strong>UN/LOCODE</strong> — e.g. <code>GBLON</code></li>
180
+ </ul>
181
+
182
+ <h3>Output</h3>
183
+ <dl class="message-properties">
184
+ <dt>payload <span class="property-type">object</span></dt>
185
+ <dd>
186
+ Contains a <code>time_zone</code> object with fields:
187
+ <code>name</code>, <code>offset</code>, <code>date_time</code>,
188
+ <code>current_time</code>, <code>is_dst</code>, <code>dst_start</code>,
189
+ <code>dst_end</code>, and more.
190
+ </dd>
191
+ </dl>
192
+
193
+ <h3>References</h3>
194
+ <ul>
195
+ <li>
196
+ <a href="https://ipgeolocation.io/documentation/timezone-api.html" target="_blank">
197
+ Timezone API documentation
198
+ </a>
199
+ </li>
200
+ </ul>
201
+ </script>