daikin-controller-cloud
Advanced tools
Comparing version 0.1.3 to 0.2.0
@@ -7,2 +7,5 @@ /** | ||
* | ||
* For an example on how to use the automatic Username/Password Login | ||
* please refer to tokensaver.js | ||
* | ||
* The tokens will be stored in a tokenset.json file in the example | ||
@@ -9,0 +12,0 @@ * directory and this file is also loaded on startup. |
/** | ||
* Example Script to use the Daikin-Controller-Cloud library | ||
* | ||
* This example will open a Proxy Server when no tokens are provided | ||
* to allow a Login with the Daikin Cloud to get the needed tokens. | ||
* This example will open a Proxy Server or use Username/Password | ||
* login if data are provided when no tokens are provided to allow | ||
* a Login with the Daikin Cloud to get the needed tokens. | ||
* | ||
@@ -18,3 +19,3 @@ * The tokens will be stored in a tokenset.json file in the example | ||
async function main() { | ||
@@ -33,38 +34,48 @@ /** | ||
}; | ||
let tokenSet; | ||
// Set outputfile for tokenset.json | ||
const tokenFile = path.join(process.cwd(), 'tokenset.json'); | ||
options.logger('Writing tokenset to: ' + tokenFile); | ||
// Initialize Daikin Cloud Instance | ||
const daikinCloud = new DaikinCloud(tokenSet, options); | ||
// Event that will be triggered on new or updated tokens, save into file | ||
daikinCloud.on('token_update', tokenSet => { | ||
console.log(`UPDATED tokens, use for future and wrote to tokenset.json`); | ||
fs.writeFileSync(tokenFile, JSON.stringify(tokenSet)); | ||
}); | ||
await daikinCloud.initProxyServer(); | ||
console.log(`Please visit http://${options.proxyOwnIp}:${options.proxyWebPort} and Login to Daikin Cloud please.`); | ||
// wait for user Login and getting the tokens | ||
const resultTokenSet = await daikinCloud.waitForTokenFromProxy(); | ||
console.log('Retrieved tokens. Saved to ' + tokenFile); | ||
//console.log(`Retrieved tokens, use for future: ${JSON.stringify(resultTokenSet)}`); | ||
// stop Proxy server (and wait 1s before we do that to make sure | ||
// the success page can be displayed correctly because waitForTokenFromProxy | ||
// will resolve before the last request is sent to the browser! | ||
await new Promise(resolve => setTimeout(resolve, 1000)); | ||
await daikinCloud.stopProxyServer(); | ||
const tokenFile = path.join(process.cwd(), 'tokenset.json'); | ||
options.logger('Writing tokenset to: ' + tokenFile); | ||
// Initialize Daikin Cloud Instance | ||
const daikinCloud = new DaikinCloud(tokenSet, options); | ||
// Event that will be triggered on new or updated tokens, save into file | ||
daikinCloud.on('token_update', tokenSet => { | ||
console.log(`UPDATED tokens, use for future and wrote to tokenset.json`); | ||
fs.writeFileSync(tokenFile, JSON.stringify(tokenSet)); | ||
}); | ||
let args = process.argv.slice(2); | ||
if (args.length === 2 && args[0].includes('@')) { | ||
console.log(`Using provided Login credentials (${args[0]}/${args[1]}) for a direct Login`) | ||
const resultTokenSet = await daikinCloud.login(args[0], args[1]); | ||
console.log('Retrieved tokens. Saved to ' + tokenFile); | ||
} else { | ||
if (args.length && !args[0].includes('@')) { | ||
console.log('Ignore provided parameters because first parameter do not seem to be an email address'); | ||
console.log(); | ||
} | ||
await daikinCloud.initProxyServer(); | ||
console.log(`Please visit http://${options.proxyOwnIp}:${options.proxyWebPort} and Login to Daikin Cloud please.`); | ||
// wait for user Login and getting the tokens | ||
const resultTokenSet = await daikinCloud.waitForTokenFromProxy(); | ||
console.log('Retrieved tokens. Saved to ' + tokenFile); | ||
//console.log(`Retrieved tokens, use for future: ${JSON.stringify(resultTokenSet)}`); | ||
// stop Proxy server (and wait 1s before we do that to make sure | ||
// the success page can be displayed correctly because waitForTokenFromProxy | ||
// will resolve before the last request is sent to the browser! | ||
await new Promise(resolve => setTimeout(resolve, 1000)); | ||
await daikinCloud.stopProxyServer(); | ||
} | ||
process.exit(); | ||
} | ||
(async () => { | ||
await main(); | ||
})(); | ||
})(); | ||
204
index.js
@@ -63,3 +63,3 @@ const EventEmitter = require('events'); | ||
// id_token_signed_response_alg (default "RS256") | ||
token_endpoint_auth_method: 'none' // (default "client_secret_basic") | ||
token_endpoint_auth_method: 'none' // (default 'client_secret_basic') | ||
}); | ||
@@ -292,4 +292,204 @@ | ||
} | ||
/** | ||
* Returns the Tokens using mail authentication | ||
* @emits DaikinCloudController#token_update | ||
* @param {string} userName Mail address oassociated with the account | ||
* @param {string} password Password of the account | ||
* @returns {Promise<any>} Instance of openid-client.TokenSet with tokens | ||
* @public | ||
*/ | ||
async login(userName, password) { | ||
Proxy = Proxy || require('./lib/proxy'); | ||
// Initiate proxy without starting it | ||
if (!this.proxy) { | ||
const proxyOptions = { | ||
proxyOwnIp: this.options.proxyOwnIp, | ||
proxyListenBind: this.options.proxyListenBind, | ||
proxyPort: this.options.proxyPort, | ||
proxyWebPort: this.options.proxyWebPort, | ||
proxyDataDir: this.options.proxyDataDir, | ||
logLevel: this.options.logLevel, | ||
logger: this.options.logger | ||
}; | ||
this.proxy = new Proxy(this.openIdClient, proxyOptions); | ||
} | ||
let cookies; | ||
let location; | ||
let login_token; | ||
// Extract csrf state cookies | ||
let csrfStateCookie; | ||
try { | ||
const response = await got(this.proxy._generateInitialUrl(), { | ||
followRedirect: false | ||
}); | ||
let cookies = response.headers['set-cookie']; | ||
csrfStateCookie = cookies[1].split(';')[0].trim() + '; ' | ||
+ cookies[2].split(';')[0].trim(); | ||
location = response.headers['location']; | ||
} catch (err) { | ||
err.message = 'Error trying to reach initial URL: ' + err.message; | ||
throw err; | ||
} | ||
// Extract SAML Context | ||
let samlContext; | ||
try { | ||
const response = await got(location, { followRedirect: false }) | ||
location = response.headers['location']; | ||
let regex = /samlContext=([^&]+)/g; | ||
let match = regex.exec(location); | ||
samlContext = match[1]; | ||
} catch (err) { | ||
err.message = 'Error trying to follow redirect: ' + err.message; | ||
throw err; | ||
} | ||
// Extract API version | ||
let version; | ||
try { | ||
const body = await got('https://cdns.gigya.com/js/gigya.js', { | ||
searchParams: {'apiKey': '3_xRB3jaQ62bVjqXU1omaEsPDVYC0Twi1zfq1zHPu_5HFT0zWkDvZJS97Yw1loJnTm'} | ||
}).text(); | ||
let regex = /"(\d+-\d-\d+)"/g | ||
let match = regex.exec(body); | ||
version = match[1]; | ||
} catch (err) { | ||
err.message = 'Error trying to extract API version: ' + err.message; | ||
throw err; | ||
} | ||
// Extract the cookies used for the Single Sign On | ||
let ssoCookies; | ||
try { | ||
const response = await got('https://cdc.daikin.eu/accounts.webSdkBootstrap', { | ||
searchParams: { | ||
'apiKey': '3_xRB3jaQ62bVjqXU1omaEsPDVYC0Twi1zfq1zHPu_5HFT0zWkDvZJS97Yw1loJnTm', | ||
'sdk': 'js_latest', | ||
'format': 'json'} | ||
}); | ||
ssoCookies = response.headers['set-cookie']; | ||
} catch (err) { | ||
err.message = 'Error trying to extract SSO cookies: ' + err.message; | ||
throw err; | ||
} | ||
// Login | ||
cookies = ssoCookies[0].split(';')[0].trim() + '; ' | ||
+ ssoCookies[2].split(';')[0].trim() + '; ' | ||
+ 'hasGmid=ver4; ' | ||
+ 'gig_bootstrap_3_xRB3jaQ62bVjqXU1omaEsPDVYC0Twi1zfq1zHPu_5HFT0zWkDvZJS97Yw1loJnTm=cdc_ver4; ' | ||
+ 'gig_canary_3_QebFXhxEWDc8JhJdBWmvUd1e0AaWJCISbqe4QIHrk_KzNVJFJ4xsJ2UZbl8OIIFY=false; ' | ||
+ 'gig_canary_ver_3_QebFXhxEWDc8JhJdBWmvUd1e0AaWJCISbqe4QIHrk_KzNVJFJ4xsJ2UZbl8OIIFY=' + version + '; ' | ||
+ 'apiDomain_3_QebFXhxEWDc8JhJdBWmvUd1e0AaWJCISbqe4QIHrk_KzNVJFJ4xsJ2UZbl8OIIFY=cdc.daikin.eu; '; | ||
try { | ||
const json = await got('https://cdc.daikin.eu/accounts.login', { | ||
'headers': { | ||
'content-type': 'application/x-www-form-urlencoded', | ||
'cookie': cookies}, | ||
searchParams: { | ||
'loginID': userName, | ||
'password': password, | ||
'sessionExpiration':'31536000', | ||
'targetEnv':'jssdk', | ||
'include': 'profile,', | ||
'loginMode': 'standard', | ||
'riskContext': '{"b0":7527,"b2":4,"b5":1', | ||
'APIKey': '3_xRB3jaQ62bVjqXU1omaEsPDVYC0Twi1zfq1zHPu_5HFT0zWkDvZJS97Yw1loJnTm', | ||
'sdk': 'js_latest', | ||
'authMode': 'cookie', | ||
'pageURL': 'https://my.daikin.eu/content/daikinid-cdc-saml/en/login.html?samlContext=' + samlContext, | ||
'sdkBuild': '12208', | ||
'format': 'json'}, | ||
'method': 'POST', | ||
}).json(); | ||
if (json && json.errorCode === 0 && json.sessionInfo && json.sessionInfo.login_token) { | ||
login_token = json.sessionInfo.login_token; | ||
} else { | ||
throw new Error(json.errorDetails || `Unknown Login error: ${JSON.stringify(json)}`); | ||
} | ||
} catch (err) { | ||
err.message = 'Login failed: ' + err.message; | ||
throw err; | ||
} | ||
let samlResponse; | ||
let relayState; | ||
cookies = cookies + | ||
+ 'glt_3_xRB3jaQ62bVjqXU1omaEsPDVYC0Twi1zfq1zHPu_5HFT0zWkDvZJS97Yw1loJnTm=' + login_token + '; ' | ||
+ 'gig_loginToken_3_QebFXhxEWDc8JhJdBWmvUd1e0AaWJCISbqe4QIHrk_KzNVJFJ4xsJ2UZbl8OIIFY=' + login_token + '; ' | ||
+ 'gig_loginToken_3_QebFXhxEWDc8JhJdBWmvUd1e0AaWJCISbqe4QIHrk_KzNVJFJ4xsJ2UZbl8OIIFY_exp=' + (Date.now() + 3600000) + '; ' | ||
+ 'gig_loginToken_3_QebFXhxEWDc8JhJdBWmvUd1e0AaWJCISbqe4QIHrk_KzNVJFJ4xsJ2UZbl8OIIFY_visited=%2C3_xRB3jaQ62bVjqXU1omaEsPDVYC0Twi1zfq1zHPu_5HFT0zWkDvZJS97Yw1loJnTm;'; | ||
try { | ||
const body = await got('https://cdc.daikin.eu/saml/v2.0/3_xRB3jaQ62bVjqXU1omaEsPDVYC0Twi1zfq1zHPu_5HFT0zWkDvZJS97Yw1loJnTm/idp/sso/continue', { | ||
searchParams: { | ||
'samlContext': samlContext, | ||
'loginToken': login_token}, | ||
headers: { | ||
'cookie': cookies | ||
} | ||
}).text(); | ||
let regex = /value="([^"]+=*)"/g; | ||
let matches = regex.exec(body); | ||
samlResponse = matches[1]; | ||
matches = regex.exec(body); | ||
relayState = matches[1]; | ||
} catch (err) { | ||
err.message = 'Authentication on SAML Identity Provider failed: ' + err.message; | ||
throw err; | ||
} | ||
// Fetch the daikinunified URL | ||
let daikinunified; | ||
const params = new URLSearchParams({ | ||
'SAMLResponse': samlResponse, | ||
'RelayState': relayState | ||
}); | ||
try { | ||
const response = await got.post('https://daikin-unicloud-prod.auth.eu-west-1.amazoncognito.com/saml2/idpresponse', { | ||
headers: { | ||
'content-type': 'application/x-www-form-urlencoded', | ||
'cookie': csrfStateCookie | ||
}, | ||
body: params.toString(), | ||
followRedirect: false | ||
}); | ||
daikinunified = response.headers['location']; | ||
if (!daikinunified.startsWith('daikinunified://')) { | ||
throw new Error(`Invalid final Authentication redirect. Location is ${daikinunified}`); | ||
} | ||
} catch (err) { | ||
err.message = 'Impossible to retrieve SAML Identity Provider\'s response: ' + err.message; | ||
throw err; | ||
} | ||
this.tokenSet = await this.proxy._retrieveTokens(daikinunified); | ||
/** | ||
* Inform the using application about changed Tokens (in this case it are new received tokens) | ||
* to store on application side | ||
* | ||
* @event DaikinCloudController#token_update | ||
* @property {TokenSet} Instance of openid-client-TokenSet with updated tokens | ||
**/ | ||
this.emit('token_update', this.tokenSet); | ||
return this.tokenSet; | ||
} | ||
} | ||
module.exports = DaikinCloudController; | ||
module.exports = DaikinCloudController; |
{ | ||
"name": "daikin-controller-cloud", | ||
"version": "0.1.3", | ||
"version": "0.2.0", | ||
"description": "Interact with Daikin Cloud devices and retrieve Tokens", | ||
@@ -20,4 +20,4 @@ "author": "Apollon77 <iobroker@fischer-ka.de>", | ||
"devDependencies": { | ||
"@alcalzone/release-script": "^2.2.0", | ||
"pkg": "^5.3.0" | ||
"@alcalzone/release-script": "^2.2.1", | ||
"pkg": "^5.3.1" | ||
}, | ||
@@ -24,0 +24,0 @@ "repository": { |
@@ -25,5 +25,2 @@ # daikin-controller-cloud | ||
## Example: | ||
See example folder, check the settings (add your own IP at minimum!) and start it with `node example.js`. | ||
## Using tokensaver.js | ||
@@ -37,8 +34,7 @@ | ||
Or, more conveniently, use one of the binaries supplied in this repository. | ||
TODO: add auto-binaries | ||
Or, more conveniently, use one of the binaries (Linux, macOS and Windows) supplied with the [Releases](https://github.com/Apollon77/daikin-controller-cloud/releases). | ||
## Code-Usage example | ||
See example folder, check the settings (add your own IP at minimum!) and start it with `node example.js`. | ||
TODO, for now: see example | ||
## Issue reporting and enhancements | ||
@@ -58,10 +54,14 @@ * Create Issues here in Github | ||
### 0.2.0 (2021-07-30) | ||
* (csu333) Add direct login method using email/password as second option beside proxy | ||
* (csu333) Add direct login also to tokensaver.js | ||
### 0.1.3 (2021-07-16) | ||
* Added tokensaver.js | ||
* Added script to auto-generate binaries based on tokensaver.js | ||
* (gigatexel ) Added tokensaver.js | ||
* (gigatexel/Apollon77) Added script to auto-generate binaries based on tokensaver.js | ||
### 0.1.1 (2021-03-29) | ||
* Initial release version | ||
* (Apollon77) Initial release version | ||
### 0.0.x | ||
* Initial version | ||
* (Apollon77) Initial version |
664560
1073