UNPKG

node-hue-api

Version:
93 lines (92 loc) 4.5 kB
import * as https from 'https'; import { Api } from '../Api'; import * as httpClient from './HttpClientFetch'; import { ApiError } from '../../ApiError'; import { Transport } from './Transport'; import { getSSLCertificate } from './sslCertificate'; import { cleanHostname, getHttpsUrl } from './urlUtil'; const DEBUG = /node-hue-api/.test(process.env.NODE_DEBUG || ''); const INITIAL_HTTPS_AGENT = new https.Agent({ rejectUnauthorized: false }); export class LocalBootstrap { /** * Create a Local Network Bootstrap for connecting to the Hue Bridge. The connection is ALWAYS over TLS/HTTPS. * * @param {String} hostname The hostname or ip address of the hue bridge on the local network. * @param {number=} port The port number for the connections, defaults to 443 and should not need to be specified in the majority of use cases. */ constructor(hostname, rateLimits, port) { this.baseUrl = getHttpsUrl(hostname, port || 443); this.hostname = cleanHostname(hostname); this.rateLimits = rateLimits; } /** * Connects to the Hue Bridge using the local network. * * The connection will perform checks on the Hue Bridge TLS Certificate to verify it is correct before sending any * sensitive information. * * @param {String=} username The username to use when connecting, can be null, but will severely limit the endpoints that you can call/access * @param {String=} clientkey The clientkey for the user, used by the entertainment API, can be null * @param {Number=} timeout The timeout for requests sent to the Hue Bridge. If not set will default to 20 seconds. * @returns {Promise<Api>} The API for interacting with the hue bridge. */ connect(username, clientkey, timeout) { const self = this, hostname = self.hostname, baseUrl = self.baseUrl.href; return httpClient.request({ method: 'GET', url: `${baseUrl}api/config`, json: true, httpsAgent: INITIAL_HTTPS_AGENT, timeout: getTimeout(timeout), }).then(res => { const bridgeId = res.data.bridgeid.toLowerCase(); return getSSLCertificate(hostname) .then((cert) => { const subjectCn = cert.subject.CN.toLowerCase(); if (DEBUG) { console.log('Bridge Certificate:\n' + ` subject: ${JSON.stringify(cert.subject)}\n` + ` issuer: ${JSON.stringify(cert.subject)}\n` + ` valid from: ${cert.valid_from}\n` + ` valid to: ${cert.valid_to}\n` + ` serial number: ${cert.serialNumber}\n`); console.log(`Performing validation of bridgeId "${bridgeId}" against certificate subject "${subjectCn}"; matched? ${subjectCn === bridgeId}`); } if (subjectCn === bridgeId) { return new https.Agent({ keepAlive: true, keepAliveMsecs: 10000, maxSockets: 50, // timeout: getTimeout(timeout), //node-fetch appears to ignore this rejectUnauthorized: false, // ca: cert.pemEncoded //TODO there are still issues here, as the certificate being self signed is failing somewhere deeper in TLS code }); } else { throw new ApiError('The hue bridge certificate does not match the expected issuer'); } }).catch(error => { throw new ApiError(error); }) .then(agent => { const apiBaseUrl = `${baseUrl}api`, fetchConfig = { baseURL: apiBaseUrl, httpsAgent: agent, timeout: getTimeout(timeout) }, transport = new Transport(httpClient.create(fetchConfig), this.rateLimits.transportRateLimit, username), config = { remote: false, baseUrl: apiBaseUrl, bridgeName: this.hostname, clientKey: clientkey, username: username, }; return new Api(config, transport, this.rateLimits); }); }); } } function getTimeout(timeout) { return timeout || 20000; }