homebridge-fordpass
Advanced tools
Comparing version 1.7.0 to 1.7.1
@@ -16,9 +16,28 @@ "use strict"; | ||
exports.Connection = void 0; | ||
const querystring_1 = __importDefault(require("querystring")); | ||
const axios_1 = __importDefault(require("axios")); | ||
const authUrl = 'https://sso.ci.ford.com/'; | ||
const crypto_1 = __importDefault(require("crypto")); | ||
const base64url_1 = __importDefault(require("base64url")); | ||
const url_1 = require("url"); | ||
const vehiclesUrl = 'https://services.cx.ford.com/api/dashboard/v1/users/vehicles'; | ||
const catWithRefreshTokenUrl = 'https://api.mps.ford.com/api/token/v2/cat-with-refresh-token'; | ||
const catWithCIAccessTokenUrl = 'https://api.mps.ford.com/api/token/v2/cat-with-ci-access-token'; | ||
const authorizeUrl = 'https://sso.ci.ford.com/v1.0/endpoint/default/authorize'; | ||
const tokenUrl = 'https://sso.ci.ford.com/oidc/endpoint/default/token'; | ||
const defaultAppId = '71A3AD0A-CF46-4CCF-B473-FC7FE5BC4592'; | ||
const clientId = '9fb503e0-715b-47e8-adfd-ad4b7770f73b'; | ||
const userAgent = 'FordPass/5 CFNetwork/1333.0.4 Darwin/21.5.0'; | ||
const randomStr = (len) => { | ||
let result = ''; | ||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | ||
const charactersLength = characters.length; | ||
for (let i = 0; i < len; i++) { | ||
result += characters.charAt(Math.floor(Math.random() * charactersLength)); | ||
} | ||
return result; | ||
}; | ||
const codeChallenge = (str) => { | ||
const hash = crypto_1.default.createHash('sha256'); | ||
hash.update(str); | ||
return (0, base64url_1.default)(hash.digest()); | ||
}; | ||
const headers = { | ||
@@ -36,44 +55,2 @@ 'User-Agent': userAgent, | ||
} | ||
auth() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const url = authUrl + 'oidc/endpoint/default/token'; | ||
const options = { | ||
method: 'POST', | ||
url: url, | ||
headers: Object.assign({ 'Content-Type': 'application/x-www-form-urlencoded' }, headers), | ||
data: querystring_1.default.stringify({ | ||
client_id: clientId, | ||
grant_type: 'password', | ||
username: this.config.username, | ||
password: this.config.password, | ||
}), | ||
}; | ||
try { | ||
const result = yield (0, axios_1.default)(options); | ||
if (result.status === 200 && result.data.access_token) { | ||
const nextResult = yield axios_1.default.post('https://api.mps.ford.com/api/token/v2/cat-with-ci-access-token', { | ||
ciToken: result.data.access_token, | ||
}, { | ||
headers: Object.assign({ 'Content-Type': 'application/json', 'Application-Id': this.applicationId }, headers), | ||
}); | ||
if (nextResult.status === 200 && nextResult.data.access_token) { | ||
this.config.access_token = nextResult.data.access_token; | ||
this.config.refresh_token = nextResult.data.refresh_token; | ||
return nextResult.data; | ||
} | ||
else { | ||
this.log.error(`Auth failed with status: ${nextResult.status}`); | ||
} | ||
} | ||
else { | ||
this.log.error(`Auth failed with status: ${result.status}`); | ||
} | ||
return; | ||
} | ||
catch (error) { | ||
this.log.error(`Auth failed with error: ${error.code || error.response.status}`); | ||
return; | ||
} | ||
}); | ||
} | ||
refreshAuth() { | ||
@@ -83,3 +60,3 @@ return __awaiter(this, void 0, void 0, function* () { | ||
if (this.config.refresh_token) { | ||
const result = yield axios_1.default.post('https://api.mps.ford.com/api/token/v2/cat-with-refresh-token', { | ||
const result = yield axios_1.default.post(catWithRefreshTokenUrl, { | ||
refresh_token: this.config.refresh_token, | ||
@@ -135,4 +112,144 @@ }, { | ||
} | ||
auth() { | ||
var _a; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const numOfRedirects = 2; | ||
const code = randomStr(43); | ||
let cookies = undefined; | ||
const url = `${authorizeUrl}?redirect_uri=fordapp://userauthorized&response_type=code&scope=openid&max_age=3600&client_id=9fb503e0-715b-47e8-adfd-ad4b7770f73b&code_challenge=${codeChallenge(code)}&code_challenge_method=S256`; | ||
let nextUrl = url; | ||
for (let i = 0; i < numOfRedirects; i++) { | ||
const options = { | ||
method: 'GET', | ||
maxRedirects: 0, | ||
url: nextUrl, | ||
headers: Object.assign({}, headers), | ||
}; | ||
if (options.headers && cookies) { | ||
options.headers.cookie = cookies; | ||
} | ||
try { | ||
yield (0, axios_1.default)(options); | ||
throw new Error('Authentication failed'); | ||
} | ||
catch (err) { | ||
if (((_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.status) === 302) { | ||
nextUrl = err.response.headers.location; | ||
cookies = err.response.headers['set-cookie']; | ||
} | ||
else { | ||
this.log.error(`Auth failed with status: ${err.status}`); | ||
} | ||
} | ||
} | ||
return this.doLogin(nextUrl, code, cookies); | ||
}); | ||
} | ||
doLogin(url, code_verifier, cookies) { | ||
var _a; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (!this.config.username || !this.config.password) { | ||
throw new Error('Username or password is empty in config'); | ||
} | ||
const options = { | ||
method: 'POST', | ||
maxRedirects: 0, | ||
url: url, | ||
headers: Object.assign({ 'Content-Type': 'application/x-www-form-urlencoded', cookie: cookies }, headers), | ||
data: new url_1.URLSearchParams({ | ||
operation: 'verify', | ||
'login-form-type': 'pwd', | ||
username: this.config.username, | ||
password: this.config.password, | ||
}).toString(), | ||
}; | ||
try { | ||
yield (0, axios_1.default)(options); | ||
throw new Error('Authentication failed'); | ||
} | ||
catch (err) { | ||
if (((_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.status) === 302) { | ||
const nextUrl = err.response.headers.location; | ||
const cookies = err.response.headers['set-cookie']; | ||
return this.getAuthorizationCode(nextUrl, code_verifier, cookies); | ||
} | ||
else { | ||
this.log.error(`Auth failed with status: ${err.status}`); | ||
} | ||
} | ||
}); | ||
} | ||
getAuthorizationCode(url, code_verifier, cookies) { | ||
var _a; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const options = { | ||
method: 'GET', | ||
maxRedirects: 0, | ||
url: url, | ||
headers: Object.assign({ cookie: cookies }, headers), | ||
}; | ||
try { | ||
yield (0, axios_1.default)(options); | ||
throw new Error('Authentication failed'); | ||
} | ||
catch (err) { | ||
if (((_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.status) === 302) { | ||
const nextUrl = err.response.headers.location; | ||
const params = new url_1.URLSearchParams(nextUrl.split('?')[1]); | ||
const code = params.get('code'); | ||
if (code) { | ||
return this.getAccessToken(code, code_verifier); | ||
} | ||
} | ||
else { | ||
this.log.error(`Auth failed with status: ${err.status}`); | ||
} | ||
} | ||
}); | ||
} | ||
getAccessToken(code, code_verifier) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const options = { | ||
method: 'POST', | ||
url: tokenUrl, | ||
headers: Object.assign({ 'Content-Type': 'application/x-www-form-urlencoded' }, headers), | ||
data: new url_1.URLSearchParams({ | ||
client_id: clientId, | ||
grant_type: 'authorization_code', | ||
scope: 'openid', | ||
redirect_uri: 'fordapp://userauthorized', | ||
code: code, | ||
code_verifier: code_verifier, | ||
}).toString(), | ||
}; | ||
try { | ||
const res = yield (0, axios_1.default)(options); | ||
if (res.status === 200 && res.data.access_token) { | ||
return this.getRefreshToken(res.data.access_token); | ||
} | ||
} | ||
catch (err) { | ||
this.log.error(`Auth failed with error: ${err}`); | ||
} | ||
}); | ||
} | ||
getRefreshToken(access_token) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const res = yield axios_1.default.post(catWithCIAccessTokenUrl, { | ||
ciToken: access_token, | ||
}, { | ||
headers: Object.assign({ 'Content-Type': 'application/json', 'Application-Id': this.applicationId }, headers), | ||
}); | ||
if (res.status === 200 && res.data.access_token) { | ||
this.config.access_token = res.data.access_token; | ||
this.config.refresh_token = res.data.refresh_token; | ||
return res.data; | ||
} | ||
else { | ||
this.log.error(`Auth failed with status: ${res.status}`); | ||
} | ||
}); | ||
} | ||
} | ||
exports.Connection = Connection; | ||
//# sourceMappingURL=fordpass-connection.js.map |
@@ -98,2 +98,5 @@ "use strict"; | ||
callback(undefined, lockNumber); | ||
if (!this.config.access_token) { | ||
return; | ||
} | ||
const status = yield vehicle.status(); | ||
@@ -130,2 +133,5 @@ if (status) { | ||
callback(undefined, engineStatus); | ||
if (!this.config.access_token) { | ||
return; | ||
} | ||
const status = yield vehicle.status(); | ||
@@ -159,2 +165,5 @@ if (status) { | ||
callback(undefined, level); | ||
if (!this.config.access_token) { | ||
return; | ||
} | ||
const status = yield vehicle.status(); | ||
@@ -161,0 +170,0 @@ if (status) { |
{ | ||
"displayName": "Homebridge FordPass", | ||
"name": "homebridge-fordpass", | ||
"version": "1.7.0", | ||
"version": "1.7.1", | ||
"description": "Fordpass plugin for homebridge: https://homebridge.io/", | ||
@@ -53,3 +53,4 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"axios": "^0.27.2" | ||
"axios": "^0.27.2", | ||
"base64url": "^3.0.1" | ||
}, | ||
@@ -56,0 +57,0 @@ "devDependencies": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
106621
861
2
+ Addedbase64url@^3.0.1
+ Addedbase64url@3.0.1(transitive)