• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

suculent / thinx-device-api / #252646244

29 Mar 2022 10:54PM UTC coverage: 1.758% (-0.1%) from 1.895%
#252646244

push

web-flow
Merge pull request #342 from suculent/thinx-class

trying to switch base script into a class

2 of 607 branches covered (0.33%)

Branch coverage included in aggregate %.

0 of 12 new or added lines in 2 files covered. (0.0%)

76 existing lines in 3 files now uncovered.

36 of 1554 relevant lines covered (2.32%)

0.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.53
/lib/thinx/apikey.js
1
/** This THiNX Device Management API module is responsible for managing API Keys.
2
        This is the new version that will use Redis only. */
3

4
var Globals = require("./globals.js");
1✔
5
var AuditLog = require("./audit");
×
6
var sha256 = require("sha256");
×
7

8
module.exports = class APIKey {
×
9

10
        constructor() {
11
                let options = Globals.redis_options();
×
12
                this.client = require("redis").createClient(options);
×
13
                this.alog = new AuditLog();
×
14
                this.prefix = Globals.prefix();
×
15
                console.log("✅ [info] Loaded module: API Keys");
×
16
        }
17

18
        /**
19
         * No magic. Anyone can invent API Key, but it must be assigned to valid owner.
20
         * @return {string} full-blown API Key (no hashing so far)
21
         */
22

23
        create_key(owner_id) {
24
                return sha256(this.prefix + owner_id + new Date().toString());
×
25
        }
26

27
        save_apikeys(owner_id, api_key_array, callback) {
NEW
28
                this.client.set("ak:" + owner_id, JSON.stringify(api_key_array), (/* nerr */) => {
×
29
                        this.client.save();
×
UNCOV
30
                        callback(true, api_key_array); // warning, leaks all API Keys!?
×
31
                });
32
        }
33

34
        /**
35
         * Create new API Key for owner
36
         * @param {string} owner_id - owner_id
37
         * @param {string} apikey_alias - requested API key alias
38
         * @param {function} callback (err, apikey) - async return callback, returns new API Key...
39
         * @return {string} api_key - full blown API Key once, no hashes...
40
         */
41

42
        create(owner_id, apikey_alias, callback) {
UNCOV
43
                var new_api_key = this.create_key(owner_id);
×
NEW
44
                if (typeof (new_api_key) === "undefined") console.log("☣️ [error] API Key generator error. Check test_ prefix for new_api_key.");
×
NEW
45
                if (typeof (owner_id) === "undefined") console.log("☣️ [error] API Key generator error. Check owner_id");
×
46
                var api_key_object = {
×
47
                        "key": new_api_key,
48
                        "hash": sha256(new_api_key),
49
                        "alias": apikey_alias
50
                };
51
                // Fetch owner keys from redis
UNCOV
52
                this.client.get("ak:" + owner_id, (cerr, json_keys) => {
×
53
                        // Create new owner object if nothing found and return
54
                        if (cerr || json_keys === null) {
×
55
                                this.save_apikeys(owner_id, [api_key_object], callback);
×
56
                        } else {
57
                                // Update existing key with new data
UNCOV
58
                                var api_keys = JSON.parse(json_keys) || [];
×
UNCOV
59
                                api_keys.push(api_key_object);
×
UNCOV
60
                                this.save_apikeys(owner_id, api_keys, callback);
×
61
                        }
62
                });
63
        }
64

65
        log_invalid_key(apikey, is_http, owner, callback) {
66
                this.alog.log(owner, "Attempt to use invalid API Key: " + apikey, "error");
×
67
                if (typeof (callback) === "undefined") return;
×
UNCOV
68
                if (typeof (is_http) === "undefined" || is_http === false) {
×
UNCOV
69
                        callback(true); // can be safely ignored, because we use MQTT authentication mechanism here (if properly configured)
×
70
                } else {
UNCOV
71
                        console.log(`⚠️ [warning] Invalid API key request with owner ${owner} and key ${apikey}`);
×
72
                        callback(false, "owner_found_but_no_key"); // no key found
×
73
                }
74
        }
75

76
        key_in_keys(apikey, json_keys) {
77
                var keys = JSON.parse(json_keys);
×
UNCOV
78
                for (var ki in keys) {
×
UNCOV
79
                        let value = keys[ki].key;
×
80
                        if (value.indexOf(apikey) !== -1) {
×
81
                                console.log("🔨 [debug] API Key found by Key.");
×
82
                                return true; // valid key found, early exit
×
83
                        }
84
                }
UNCOV
85
                for (var ha in keys) {
×
UNCOV
86
                        let value = keys[ha].hash;
×
87
                        if (value.indexOf(apikey) !== -1) {
×
88
                                console.log("🔨 [debug] API Key found by Hash.");
×
UNCOV
89
                                return true; // valid hash found, early exit
×
90
                        }
91
                }
UNCOV
92
                console.log(`⚠️ [warning] APIKey '${apikey}' not found.`);
×
UNCOV
93
                return false;
×
94
        }
95

96
        /**
97
         * Verify API Key (should return only boolean if valid)
98
         * @param {string} owner - owner_id (may be optional but speeds things up... will be owner id!)
99
         * @param {apikey} apikey - apikey
100
         * @param {is_http} is_http - used for callback...
101
         * @param {function} callback (result, apikey) - async return callback, returns true or false and error
102
         */
103

104
        verify(owner, apikey, is_http, callback) {
105
                // Test stack only, does not verify this api_key from envi.json
UNCOV
106
                if (process.env.ENVIRONMENT === "test") {
×
UNCOV
107
                        if (apikey.indexOf("a6d548c60da8307394d19894a246c9e9eec6c841b8ad54968e047ce0a1687b94") !== -1) {
×
108
                                callback(true, null);
×
UNCOV
109
                                return;
×
110
                        }
111
                }
112

113
                // Fetch owner keys from redis
UNCOV
114
                this.client.get("ak:" + owner, (err, json_keys) => {
×
115

116
                        // Check API Key against stored objects
UNCOV
117
                        if ((typeof (json_keys) !== "undefined") && (json_keys !== null)) {
×
NEW
118
                                if (this.key_in_keys(apikey, json_keys)) {
×
UNCOV
119
                                        callback(true);
×
120
                                } else {
121
                                        console.log(`⚠️ [warning] API Key '${apikey}' not found!`);
×
UNCOV
122
                                        this.log_invalid_key(apikey, is_http, owner, callback);
×
123
                                }
UNCOV
124
                                return;
×
125
                        }
126

UNCOV
127
                        if (err === null) err = "apikey_not_found";
×
128

UNCOV
129
                        callback(false, err);
×
130
                });
131
        }
132

133
        /**
134
         * Revoke API Key
135
         * @param {string} owner - owner_id (may be optional but speeds things up... will be owner id!)
136
         * @param {string} apikey_hashes -
137
         * @param {function} callback - async return callback, returns true or false and error
138
         */
139

140
        revoke(owner, apikey_hashes, callback) {
141

UNCOV
142
                let key_id = "ak:" + owner;
×
143

144
                // Fetch owner keys from redis
145
                this.client.get(key_id, (rerr, json_keys) => {
×
146

147
                        console.log("[debug] loaded keys before revocation", json_keys);
×
148

149
                        // Check API Key against stored objects
150
                        if ((typeof (json_keys) === "undefined") || (json_keys === null)) {
×
151
                                console.log("[APIKey:revoke:error]:" + rerr + " revoking " + key_id);
×
152
                                callback(false, "owner_not_found");
×
UNCOV
153
                                return;
×
154
                        }
155

UNCOV
156
                        var new_keys = [];
×
UNCOV
157
                        var deleted_keys = [];
×
158
                        var keys = JSON.parse(json_keys);
×
159

160
                        for (var ki in keys) {
×
UNCOV
161
                                var key_hash = keys[ki].hash;
×
162
                                // Evaluate existing key_hash in deletes and remove...
163
                                // First successful result should be sufficient.
164
                                var deleted = false;
×
165

UNCOV
166
                                for (var apikey_hash_index in apikey_hashes) {
×
167
                                        // Skip revoked key(s)
168
                                        if (key_hash === apikey_hashes[apikey_hash_index]) {
×
169
                                                deleted = true;
×
UNCOV
170
                                                deleted_keys.push(key_hash);
×
171
                                        }
172
                                }
173
                                // In case none of the deletes is valid, keep this key.
UNCOV
174
                                if (deleted === false) {
×
175
                                        new_keys.push(keys[ki]);
×
176
                                }
177
                        }
178

UNCOV
179
                        console.log("[debug] saving new keys after revocation", new_keys);
×
180

181
                        this.client.set(key_id, JSON.stringify(new_keys), (err, reply) => {
×
UNCOV
182
                                if (err) {
×
UNCOV
183
                                        console.log("[debug] apikey set error", err, reply);
×
UNCOV
184
                                        callback(false, null);
×
185
                                } else {
UNCOV
186
                                        this.client.save();
×
UNCOV
187
                                        callback(true, deleted_keys);
×
188
                                }
189
                        });
190
                });
191
        }
192

193
        /**
194
         * List API Keys for owner
195
         * @param {string} owner - 'owner' id
196
         * @param {function} callback (err, body) - async return callback
197
         */
198

199
        list(owner, callback) {
200
                // Fetch owner keys from redis
201
                this.client.get("ak:" + owner, (err, json_keys) => {
×
202
                        var exportedKeys = [];
×
203
                        if ((err === null) && (typeof (json_keys) !== "undefined") && (json_keys !== null)) {
×
204
                                var api_keys = JSON.parse(json_keys);
×
205
                                var keys = Object.keys(api_keys);
×
UNCOV
206
                                for (var index in keys) {
×
UNCOV
207
                                        var keyname = keys[index];
×
208
                                        var keydata = api_keys[keyname];
×
UNCOV
209
                                        var key = "**************************************";
×
UNCOV
210
                                        if (typeof (keydata.key) !== "undefined") {
×
UNCOV
211
                                                key = keydata.key; // should be masked but the builder fails to fetch keys for building
×
212
                                                //key = "******************************" + keydata.key.substring(30);
213
                                        }
214
                                        var info = {
×
215
                                                name: "******************************" + key.substring(30),
216
                                                key: key, // warning; cleartext key!!!
217
                                                hash: sha256(keydata.key),
218
                                                alias: keydata.alias
219
                                        };
UNCOV
220
                                        exportedKeys.push(info);
×
221
                                }
222
                        }
UNCOV
223
                        callback(exportedKeys);
×
224
                });
225
        }
226

227
        // used by mqtt
228

229
        get_first_apikey(owner, callback) {
230
                this.list(owner, (json_keys) => {
×
231
                        if (json_keys == []) {
×
232
                                console.log("API Key list failed. " + json_keys);
×
233
                                callback(false, "messenger_has_no_api_keys");
×
UNCOV
234
                                return;
×
235
                        }
NEW
236
                        var api_key = (typeof (json_keys[0]) !== "undefined") ? json_keys[0] : null;
×
NEW
237
                        console.log("[debug] first fetched json_keys for (!!!)", { owner }, { json_keys }, { api_key });
×
UNCOV
238
                        if (api_key === null) {
×
UNCOV
239
                                callback(false, null);
×
240
                        } else {
UNCOV
241
                                callback(true, api_key.key);
×
242
                        }
243
                });
244
        }
245

246
        // used by builder
247
        get_last_apikey(owner, callback) {
248
                this.list(owner, (json_keys) => {
×
249
                        if (json_keys == []) {
×
250
                                console.log("API Key list failed. " + json_keys);
×
251
                                callback(false, "owner_has_no_api_keys");
×
252
                                return;
×
253
                        }
254
                        var last_key_hash = owner.last_key_hash;
×
255
                        var api_key = null;
×
UNCOV
256
                        for (var key in json_keys) {
×
257
                                var kdata = json_keys[key];
×
UNCOV
258
                                if ((typeof (kdata) !== "undefined") && (kdata !== null)) {
×
UNCOV
259
                                        if (sha256(kdata.hash) == last_key_hash) {
×
UNCOV
260
                                                api_key = kdata.name;
×
261
                                                break;
×
262
                                        } else {
263
                                                api_key = kdata.name; // pick valid key automatically if not the selected one
×
264
                                        }
265
                                }
266
                        }
UNCOV
267
                        if (api_key === null) {
×
UNCOV
268
                                console.log("Build requires API Key result.");
×
UNCOV
269
                                callback(false, "build_requires_api_key");
×
UNCOV
270
                                return;
×
271
                        }
UNCOV
272
                        callback(true, api_key);
×
273
                });
274
        }
275

276
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc