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

suculent / thinx-device-api / #252646768

01 Nov 2025 03:31PM UTC coverage: 46.298% (-25.7%) from 71.971%
#252646768

push

suculent
testing upgraded mqtt package

1123 of 3470 branches covered (32.36%)

Branch coverage included in aggregate %.

5324 of 10455 relevant lines covered (50.92%)

4.07 hits per line

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

16.47
/lib/router.github.js
1
// /api/v2/oauth/github
2

3
let RSAKey = require("../lib/thinx/rsakey.js");
1✔
4
let rsakey = new RSAKey();
1✔
5
let GitHub = require("../lib/thinx/github.js");
1✔
6

7
let envi = require("../_envi.json");
1✔
8
let owner = envi.oid;
1✔
9

10
const Globals = require("./thinx/globals");
1✔
11
const prefix = Globals.prefix();
1✔
12
const Database = require("../lib/thinx/database.js");
1✔
13
let db_uri = new Database().uri();
1✔
14
let userlib = require("nano")(db_uri).use(prefix + "managed_users"); // lgtm [js/unused-local-variable]
1✔
15

16
const github_ocfg = Globals.github_ocfg();
1✔
17
const https = require('https');
1✔
18
const sha256 = require("sha256");
1✔
19

20
const app_config = Globals.app_config();
1✔
21

22
let AuditLog = require("../lib/thinx/audit"); let alog = new AuditLog();
1✔
23

24
const Util = require("./thinx/util");
1✔
25

26
//
27
// OAuth2 for GitHub
28
//
29

30
module.exports = function (app) {
1✔
31

32
    /*
33
    * OAuth 2 with GitHub
34
    */
35

36
    let user = app.owner;
2✔
37

38
    let githubOAuth;
39

40
    if (typeof (process.env.GITHUB_CLIENT_SECRET) !== "undefined" && process.env.GITHUB_CLIENT_SECRET !== null) {
2!
41
        try {
2✔
42
            let specs = {
2✔
43
                githubClient: process.env.GITHUB_CLIENT_ID,
44
                githubSecret: process.env.GITHUB_CLIENT_SECRET,
45
                baseURL: github_ocfg.base_url, // should be rather gotten from global config!
46
                loginURI: '/api/oauth/github',
47
                callbackURI: '/api/oauth/github/callback',
48
                scope: 'user'
49
            };
50
            githubOAuth = require('./thinx/oauth-github.js')(specs);
2✔
51
        } catch (e) {
52
            console.log(`[debug] [oauth] [github] github_ocfg init error: ${e}`);
×
53
        }
54
    }
55

56
    function validateGithubUser(response, token, userWrapper) {
57

58
        let owner_id = userWrapper.owner; // must not be nil
×
59

60
        // Check user and make note on user login
61
        userlib.get(userWrapper.owner, (error, udoc) => {
×
62

63
            // Error case covers creating new user/managing deleted account
64
            if (error) {
×
65

66
                if (error.toString().indexOf("Error: deleted") !== -1) {
×
67
                    console.log("🔨 [debug] [oauth] [check] user document deleted");
×
68
                    response.redirect(
×
69
                        app_config.public_url + '/error.html?success=failed&title=Sorry&reason=' +
70
                        encodeURI('Account document deleted.')
71
                    );
72
                    return;
×
73
                }
74

75
                // May exist, but be deleted. Can be cleared using Filtered Replication Handler "del"
76
                if (typeof (udoc) !== "undefined" && udoc !== null) {
×
77
                    if ((typeof (udoc.deleted) !== "undefined") && udoc.deleted === true) {
×
78
                        console.log("🔨 [debug] [oauth] [check] user account marked as deleted");
×
79
                        response.redirect(
×
80
                            app_config.public_url + '/error.html?success=failed&title=Sorry&reason=' +
81
                            encodeURI('Account deleted.')
82
                        );
83
                        return;
×
84
                    }
85
                } else {
86
                    console.log("No such owner, should create one...");
×
87
                }
88

89
                // No such owner, create without activation.
90
                this.user.create(userWrapper, false, response, (/* res, success, status*/) => {
×
91

92
                    console.log("[OID:" + owner_id + "] [NEW_SESSION] [oauth] 2485:");
×
93

94
                    alog.log(owner_id, "OAuth User created. ");
×
95

96
                    app.redis_client.v4.set(token, JSON.stringify(userWrapper));
×
97
                    app.redis_client.v4.expire(token, 30);
×
98

99
                    const courl = app_config.public_url + "/auth.html?t=" + token + "&g=true"; // require GDPR consent
×
100

101
                    console.log("Redirecting to login (2)", courl);
×
102
                    response.redirect(courl); // for successful login, this must be a response to /oauth/<idp>/callback 
×
103
                });
104
                return;
×
105
            }
106

107
            console.log(`ℹ️ [info] Calling trackUserLogin on GtHub Auth Callback...`);
×
108
            user.trackUserLogin(owner_id);
×
109

110
            console.log("validateGithubUser", { token }, { userWrapper });
×
111
            app.redis_client.v4.set(token, JSON.stringify(userWrapper));
×
112
            app.redis_client.v4.expire(token, 3600);
×
113

114
            let gdpr = false;
×
115
            if (typeof (udoc) !== "undefined" && udoc !== null) {
×
116
                if (typeof (udoc.info) !== "undefined") {
×
117
                    if (typeof (udoc.gdpr_consent) !== "undefined" && udoc.gdpr_consent === true) {
×
118
                        gdpr = true;
×
119
                    }
120
                }
121
            }
122

123
            const ourl = app_config.public_url + "/auth.html?t=" + token + "&g=" + gdpr; // require GDPR consent
×
124
            response.redirect(ourl);
×
125
        }); // userlib.get
126
    }
127

128
    function githubLogin(access_token, hdata, res, original_response) {
129
        let token = "ghat:" + access_token;
×
130
        let owner_id, given_name, family_name, email;
131
    
132
        if ((typeof (hdata.name) !== "undefined") && hdata.name !== null) {
×
133
            let in_name_array = hdata.name.split(" ");
×
134
            given_name = in_name_array[0];
×
135
            family_name = in_name_array[in_name_array.count - 1];
×
136
        } else {
137
            family_name = hdata.login;
×
138
            given_name = hdata.login;
×
139
            console.log("🔨 [debug] [github] [token] Warning: no name in GitHub access token response, using login: ", { hdata }); // logs personal data in case the user has no name!
×
140
        }
141
        email = hdata.email || hdata.login;
×
142
    
143
        try {
×
144
            owner_id = sha256(prefix + email);
×
145
        } catch (e) {
146
            console.log("☣️ [error] [github] [token] error parsing e-mail: " + e + " email: " + email);
×
147
            return res.redirect(app_config.public_url + '/error.html?success=failed&title=Sorry&reason=Missing%20e-mail.');
×
148
        }
149
        validateGithubUser(original_response, token, {
×
150
            first_name: given_name,
151
            last_name: family_name,
152
            email: email,
153
            owner: owner_id,
154
            username: owner_id
155
        });
156
    }
157

158
    function secureGithubCallbacks(original_response, callback) {
159

160
        if (typeof (githubOAuth) === "undefined") {
×
161
            console.log("[critical] [githubOAuth] undefined on secure! attempting to fix...");
×
162

163
            try {
×
164
                let specs = {
×
165
                    githubClient: process.env.GITHUB_CLIENT_ID,
166
                    githubSecret: process.env.GITHUB_CLIENT_SECRET,
167
                    baseURL: github_ocfg.base_url, // should be rather gotten from global config!
168
                    loginURI: '/api/oauth/github',
169
                    callbackURI: '/api/oauth/github/callback',
170
                    scope: 'user'
171
                };
172
                githubOAuth = require('./thinx/oauth-github.js')(specs);
×
173

174
            } catch (e) {
175
                console.log(`[debug] [oauth] [github] github_ocfg init error: ${e}`);
×
176
            }
177
        }
178

179
        // configure callbacks for Emitter events
180

181
        githubOAuth.on('error', (err) => {
×
182
            console.error('[debug] [oauth] [github] there was a login error', err);
×
183
            if (process.env.ENVIRONMENT == "test")
×
184
                if (typeof (original_response) !== "undefined") original_response.end("test-ok");
×
185
        });
186

187
        githubOAuth.on('token', (access_token, /* resp, _res, req */) => {
×
188

189
            if (!Util.isDefined(access_token)) {
×
190
                original_response.status(401).end();
×
191
                console.log("[github] oauth_response missing (test or intrusion)");
×
192
                return;
×
193
            }
194

195
            if ((!access_token) || (access_token.indexOf("bad_verification") !== -1)) {
×
196
                console.log("🔨 [debug] [github] [token] No token, exiting.");
×
197
                original_response.status(401).end();
×
198
                return;
×
199
            }
200

201
            const requestOptions = {
×
202
                host: 'api.github.com',
203
                port: 443,
204
                path: '/user',
205
                headers: {
206
                    'User-Agent': 'THiNX', // Application name from GitHub / Settings / Developer Settings 
207
                    'Authorization': 'token ' + access_token, 
208
                    'Accept': 'application/vnd.github+json' 
209
                } 
210
            };
211

212
            https.get(requestOptions, (res) => {
×
213
                let data = '';
×
214
                res.on('data', (chunk) => {
×
215
                    data += chunk;
×
216
                });
217
                res.on('end', () => {
×
218
                    githubLogin(access_token, JSON.parse(data), res, original_response);
×
219
                });
220
            });
221
        });
222

223
        callback(); // async completes the secureGithubCallbacks()
×
224
    }
225

226
    // Initial page redirecting to OAuth2 provider
227
    app.get('/api/oauth/github', function (req, res) {
2✔
228
        if (typeof (req.session) !== "undefined") {
×
229
            console.log("🔨 [debug] GET /api/oauth/github will destroy old session...");
×
230
            req.session.destroy();
×
231
        }
232
        if (typeof (githubOAuth) !== "undefined") {
×
233
            console.log("🔨 [debug] GET /api/oauth/github calling githubOAuth.login");
×
234
            githubOAuth.login(req, res);
×
235
        } else {
236
            res.status(400).end();
×
237
        }
238
    });
239

240
    // Callback service parsing the authorization token and asking for the access token
241
    app.get('/api/oauth/github/callback', function (req, res) {
2✔
242
        // save original response to callbacks in this code path... when callback is called, response is used to reply (except for error)
243
        secureGithubCallbacks(res, () => {
×
244
            githubOAuth.callback(req, res, (err) => {
×
245
                console.log("[spec] GitHub OAuth result", err);
×
246
            });
247
        });
248
    });
249

250
    app.post('/api/github/token', (req, res) => {
2✔
251
        let token = req.body.token;
×
252
        rsakey.list(owner, (success, list) => {
×
253
            console.log("RSAKey list success:", success);
×
254
            if (list.length == 0) return console.log("No RSA Keys to add from", list);
×
255
            let key = list[0];
×
256
            let pubkey = key.pubkey;
×
257
            console.log("GitHub adding pub key:", pubkey);
×
258
            GitHub.addPublicKey(token, pubkey, (result) => {
×
259
                res.status(200).end(result);
×
260
            });
261
        });
262
    });
263
};
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