homebridge-xbox-tv
Version:
Homebridge plugin to control Xbox game consoles.
191 lines (175 loc) • 7.36 kB
JavaScript
import QueryString from 'querystring';
import axios from 'axios';
import { WebApi } from '../constants.js';
import Functions from '../functions.js';
class Authentication {
constructor(config) {
this.webApiClientId = config.clientId || WebApi.ClientId;
this.webApiClientSecret = config.clientSecret;
this.tokensFile = config.tokensFile;
this.tokens = {
oauth: {},
user: {},
xsts: {}
};
this.functions = new Functions();
}
async refreshToken(token) {
try {
const payload = {
'client_id': this.webApiClientId,
'grant_type': 'refresh_token',
'scope': WebApi.Scopes,
'refresh_token': token,
};
if (this.webApiClientSecret) {
payload.client_secret = this.webApiClientSecret;
}
const postData = QueryString.stringify(payload);
const response = await axios.post(WebApi.Url.RefreshToken, postData, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });
const refreshToken = response.data;
refreshToken.issued = new Date().toISOString();
this.tokens.oauth = refreshToken;
return true;
} catch (error) {
throw new Error(`Refresh token error: ${error.message}`, { cause: error });
}
}
async getUserToken(accessToken) {
try {
const payload = {
'RelyingParty': 'http://auth.xboxlive.com',
'TokenType': 'JWT',
'Properties': {
'AuthMethod': 'RPS',
'SiteName': 'user.auth.xboxlive.com',
'RpsTicket': `d=${accessToken}`
}
};
const postData = JSON.stringify(payload);
const response = await axios.post(WebApi.Url.UserToken, postData, { headers: { 'Content-Type': 'application/json' } });
const userToken = response.data;
this.tokens.user = userToken;
this.tokens.xsts = {};
return true;
} catch (error) {
throw new Error(`User token error: ${error.message}`, { cause: error });
}
}
async getXstsToken(userToken) {
try {
const payload = {
'RelyingParty': 'http://xboxlive.com',
'TokenType': 'JWT',
'Properties': {
'UserTokens': [userToken],
'SandboxId': 'RETAIL',
}
};
const postData = JSON.stringify(payload);
const response = await axios.post(WebApi.Url.XstsToken, postData, { headers: { 'Content-Type': 'application/json', 'x-xbl-contract-version': '1' } });
const xstsToken = response.data;
this.tokens.xsts = xstsToken;
return true;
} catch (error) {
throw new Error(`Xsts token error: ${error.message}`, { cause: error });
}
}
async accessToken(webApiToken) {
try {
const payload = {
'client_id': this.webApiClientId,
'grant_type': 'authorization_code',
'scope': WebApi.Scopes,
'code': webApiToken,
'redirect_uri': WebApi.Url.Redirect
};
if (this.webApiClientSecret) {
payload.client_secret = this.webApiClientSecret;
}
const postData = QueryString.stringify(payload);
const response = await axios.post(WebApi.Url.AccessToken, postData, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });
const accessToken = response.data;
accessToken.issued = new Date().toISOString();
this.tokens.oauth = accessToken;
await this.functions.saveData(this.tokensFile, this.tokens);
return true;
} catch (error) {
throw new Error(`Access token error: ${error.message}`, { cause: error });
}
}
async refreshTokens(type) {
switch (type) {
case 'user':
if (this.tokens.user.Token) {
const tokenExpired = Date.now() > new Date(this.tokens.user.NotAfter).getTime();
if (tokenExpired) {
await this.refreshToken(this.tokens.oauth.refresh_token);
await this.getUserToken(this.tokens.oauth.access_token);
await this.refreshTokens('xsts');
return true;
} else {
await this.refreshTokens('xsts');
return true;
}
} else {
await this.getUserToken(this.tokens.oauth.access_token);
await this.refreshTokens('xsts');
return true;
}
case 'xsts':
if (this.tokens.xsts.Token) {
const tokenExpired = Date.now() > new Date(this.tokens.xsts.NotAfter).getTime();
if (tokenExpired) {
// when token expired. Now just refresh once and return.
await this.getXstsToken(this.tokens.user.Token);
return true;
} else {
return true;
}
} else {
await this.getXstsToken(this.tokens.user.Token);
return true;
}
default:
throw new Error(`Unknown refresh token type: ${type}`);
}
}
async checkAuthorization() {
if (this.webApiClientId) {
try {
const tokens = await this.functions.readData(this.tokensFile, true);
this.tokens = !tokens ? this.tokens : tokens;
const refreshToken = this.tokens.oauth.refresh_token ?? false;
if (refreshToken) {
await this.refreshTokens('user');
await this.functions.saveData(this.tokensFile, this.tokens);
return { headers: `XBL3.0 x=${this.tokens.xsts.DisplayClaims.xui[0].uhs};${this.tokens.xsts.Token}`, tokens: this.tokens };
} else {
throw new Error('No oauth token found. Use authorization manager first.');
}
} catch (error) {
throw new Error(error);
}
} else {
throw new Error(`Authorization not possible, check plugin settings - Client Id: ${this.webApiClientId}`);
}
}
async generateAuthorizationUrl() {
try {
const payload = {
'client_id': this.webApiClientId,
'response_type': 'code',
'approval_prompt': 'auto',
'scope': WebApi.Scopes,
'redirect_uri': WebApi.Url.Redirect
};
const params = QueryString.stringify(payload);
const oauth2URI = `${WebApi.Url.oauth2}?${params}`;
return oauth2URI;
} catch (error) {
throw new Error(`Authorization URL error: ${error}`);
}
}
}
export default Authentication;