Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added 2FA, fix bugs, auto format file #37

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
![](images/widget_display.jpg)

# Homebridge Status Widget
- Users need to install the [`Authy Client.js`](https://github.com/LoSunny/Scriptable-Authy-Client) if they want to use 2FA with this code
- Script for the iOS App Scriptable that shows a small summary of your Homebridge instance
- All infos shown are based and provided by the Homebridge Config UI X found at https://github.com/oznu/homebridge-config-ui-x
- Thanks to the github user oznu for providing such a nice programm!
Expand All @@ -25,12 +26,12 @@
- set overwritePersistedConfig to false
- set the widget up with a single parameter in the format 'USE_CONFIG:yourfilename.json' ![](images/use_config_via_parameter.jpeg)
- as long as overwritePersistedConfig is false, any change to the config won't take any effect because the persisted one is used if usePersistedConfiguration is true
- another updatable way (but configuration is lost):
- set the widget up with parameter in the format \<username>,,\<password>,,\<hbServiceMachineBaseUrl>
- a valid real example: "admin,,mypassword123,,http://192.168.178.33:8581"
- if you have authentication set to non in UI-X then just provide any char. Valid would be e.g. "x,,x,,http://192.168.178.33:8581"
- maybe you need to set usePersistedConfiguration in the config to false to use this older way
- screenshot of an example when setting it up: ![](images/example_parameter_setup.jpeg)
- ~~another updatable way (but configuration is lost):~~ You cannot use this method if you want to enable 2FA
- ~~set the widget up with parameter in the format \<username>,,\<password>,,\<hbServiceMachineBaseUrl>~~
- ~~a valid real example: "admin,,mypassword123,,http://192.168.178.33:8581"~~
- ~~if you have authentication set to non in UI-X then just provide any char. Valid would be e.g. "x,,x,,http://192.168.178.33:8581"~~
- ~~maybe you need to set usePersistedConfiguration in the config to false to use this older way~~
- ~~screenshot of an example when setting it up:~~ ![](images/example_parameter_setup.jpeg)

- hard coded way in the script (not recommended): you need to configure
- the **URL** of the system running the Homebridge Config UI X (the hb-service), including the port e.g. http://192.168.178.33:8581
Expand Down Expand Up @@ -116,4 +117,4 @@
- Homebridge 1.1.6
- iOS 14.2
- if your Homebridge Config UI X is reachable and the authentication process succeeded but the further API requests take to long or fail you will get a screen similar to ![](images/unknown.jpg)
- open a github issue if you can't figure it out what the problem is
- open a github issue if you can't figure it out what the problem is
88 changes: 62 additions & 26 deletions homebridgeStatusWidget.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
let configurationFileName = 'purple.json' // change this to an own name e.g. 'configBlack.json' . This name can then be given as a widget parameter in the form 'USE_CONFIG:yourfilename.json' so you don't loose your preferred configuration across script updates (but you will loose it if i have to change the configuration format)
const usePersistedConfiguration = true; // false would mean to use the visible configuration below; true means the state saved in iCloud (or locally) will be used
const overwritePersistedConfig = false; // if you like your configuration, run the script ONCE with this param to true, then it is saved and can be used via 'USE_CONFIG:yourfilename.json' in widget params
// overwritePersistedConfig is useless if twoFactorAuthentication is true
const twoFactorAuthentication = true; // See https://github.com/LoSunny/homebridgeStatusWidget for details
// *********

const CONFIGURATION_JSON_VERSION = 2; // never change this! If i need to change the structure of configuration class, i will increase this counter. Your created config files sadly won't be compatible afterwards.
Expand Down Expand Up @@ -42,14 +44,14 @@ class Configuration {
jsonVersion = CONFIGURATION_JSON_VERSION; // do not change this
enableSiriFeedback = true; // when running script via Siri, she should speak the text that is defined below BUT might be bugged atm, i wrote the dev about it

// logo is downloaded only the first time! It is saved in iCloud and then loaded from there everytime afterwards
// logo is downloaded only the first time! It is saved in iCloud and then loaded from there everytime afterwards
logoUrl = 'https://github.com/homebridge/branding/blob/master/logos/homebridge-silhouette-round-white.png?raw=true';

// icons:
icon_statusGood = 'checkmark.circle.fill'; // can be any SFSymbol
icon_colorGood = '#' + Color.green().hex; // must have form like '#FFFFFF'
icon_statusBad = 'exclamationmark.triangle.fill'; // can be any SFSymbol
icon_colorBad = '#' + Color.red().hex;// must have form like '#FFFFFF'
icon_colorBad = '#' + Color.red().hex; // must have form like '#FFFFFF'
icon_statusUnknown = 'questionmark.circle.fill'; // can be any SFSymbol
icon_colorUnknown = '#' + Color.yellow().hex; // must have form like '#FFFFFF'

Expand Down Expand Up @@ -94,6 +96,7 @@ class Configuration {
siri_spokenAnswer_all_UTD = 'Everything is up to date';

error_noConnectionText = ' ' + this.failIcon + ' UI-Service not reachable!\n ' + this.bulletPointIcon + ' Server started?\n ' + this.bulletPointIcon + ' UI-Service process started?\n ' + this.bulletPointIcon + ' Server-URL ' + this.hbServiceMachineBaseUrl + ' correct?\n ' + this.bulletPointIcon + ' Are you in the same network?';
access_token = "";
}

// CONFIGURATION END //////////////////////
Expand All @@ -113,6 +116,7 @@ const NOTIFICATION_JSON_FILE_NAME = 'notificationState.json'; // never change th
let CONFIGURATION = new Configuration();
const noAuthUrl = () => CONFIGURATION.hbServiceMachineBaseUrl + '/api/auth/noauth';
const authUrl = () => CONFIGURATION.hbServiceMachineBaseUrl + '/api/auth/login';
const checkAuthUrl = () => CONFIGURATION.hbServiceMachineBaseUrl + '/api/auth/check';
const cpuUrl = () => CONFIGURATION.hbServiceMachineBaseUrl + '/api/status/cpu';
const hbStatusUrl = () => CONFIGURATION.hbServiceMachineBaseUrl + '/api/status/homebridge';
const ramUrl = () => CONFIGURATION.hbServiceMachineBaseUrl + '/api/status/ram';
Expand Down Expand Up @@ -142,10 +146,10 @@ const NOTIFICATION_JSON_VERSION = 1; // never change this!

const INITIAL_NOTIFICATION_STATE = {
'jsonVersion': NOTIFICATION_JSON_VERSION,
'hbRunning': {'status': true},
'hbUtd': {'status': true},
'pluginsUtd': {'status': true},
'nodeUtd': {'status': true}
'hbRunning': { 'status': true },
'hbUtd': { 'status': true },
'pluginsUtd': { 'status': true },
'nodeUtd': { 'status': true }
};

class LineChart {
Expand Down Expand Up @@ -219,36 +223,43 @@ async function createWidget() {
let fm = CONFIGURATION.fileManagerMode === 'LOCAL' ? FileManager.local() : FileManager.iCloud();

if (args.widgetParameter) {
// you can either provide as parameter:
// - the config.json file name you want to load the credentials from (must be created before it can be used but highly recommended)
// valid example: 'USE_CONFIG:yourfilename.json' (the 'yourfilename' part can be changed by you)
// this single parameter must start with USE_CONFIG: and end with .json
// - credentials + URL directly (all other changes to the script are lost when you update it e.g. via https://scriptdu.de )
// credentials must be separated by two commas like <username>,,<password>,,<hbServiceMachineBaseUrl>
// a valid real example: admin,,mypassword123,,http://192.168.178.33:8581
// If no password is needed for you to login just enter anything: xyz,,xyz,,http://192.168.178.33:8581
// you can either provide as parameter:
// - the config.json file name you want to load the credentials from (must be created before it can be used but highly recommended)
// valid example: 'USE_CONFIG:yourfilename.json' (the 'yourfilename' part can be changed by you)
// this single parameter must start with USE_CONFIG: and end with .json
// - credentials + URL directly (all other changes to the script are lost when you update it e.g. via https://scriptdu.de )
// credentials must be separated by two commas like <username>,,<password>,,<hbServiceMachineBaseUrl>
// a valid real example: admin,,mypassword123,,http://192.168.178.33:8581
// If no password is needed for you to login just enter anything: xyz,,xyz,,http://192.168.178.33:8581
if (args.widgetParameter.length > 0) {
let foundCredentialsInParameter = useCredentialsFromWidgetParameter(args.widgetParameter);
let fileNameSuccessfullySet = false;
if (!foundCredentialsInParameter) {
fileNameSuccessfullySet = checkIfConfigFileParameterIsProvided(fm, args.widgetParameter);
}
if (!foundCredentialsInParameter && !fileNameSuccessfullySet) {
throw('Format of provided parameter not valid\n2 Valid examples: 1. USE_CONFIG:yourfilename.json\n2. admin,,mypassword123,,http://192.168.178.33:8581');
throw ('Format of provided parameter not valid\n2 Valid examples: 1. USE_CONFIG:yourfilename.json\n2. admin,,mypassword123,,http://192.168.178.33:8581');
}
if (twoFactorAuthentication && foundCredentialsInParameter) {
throw ('You cannot use this method if you want to enable 2FA');
}
}
}
let pathToConfig = getFilePath(configurationFileName, fm);
if (usePersistedConfiguration && !overwritePersistedConfig) {
CONFIGURATION = await getPersistedObject(fm, pathToConfig, CONFIGURATION_JSON_VERSION, CONFIGURATION, false);
log('Configuration ' + configurationFileName + ' is used! Trying to authenticate...');
} else if (!usePersistedConfiguration && twoFactorAuthentication) {
CONFIGURATION.access_token = (await getPersistedObject(fm, pathToConfig, CONFIGURATION_JSON_VERSION, CONFIGURATION, false)).access_token;
log('Loaded access_token from config');
}

// authenticate against the hb-service
let token = await getAuthToken();
if (token === undefined) {
throw('Credentials not valid');
throw ('Credentials not valid');
}
CONFIGURATION.access_token = token;
let widget = new ListWidget();

handleSettingOfBackgroundColor(widget);
Expand Down Expand Up @@ -283,7 +294,7 @@ async function createWidget() {
let nodeJsVersionInfos = await getNodeJsVersionInfos(token);
let nodeJsUpToDate = nodeJsVersionInfos === undefined ? undefined : !nodeJsVersionInfos.updateAvailable;

if (usePersistedConfiguration || overwritePersistedConfig) {
if (usePersistedConfiguration || overwritePersistedConfig || twoFactorAuthentication) {
// if here, the configuration seems valid -> save it for next time
log('The valid configuration ' + configurationFileName + ' has been saved. Changes can only be applied if overwritePersistedConfig is set to true. Should be set to false after applying changes again!')
persistObject(fm, CONFIGURATION, pathToConfig);
Expand Down Expand Up @@ -559,7 +570,7 @@ function checkIfConfigFileParameterIsProvided(fm, givenParameter) {
if (givenParameter.trim().startsWith('USE_CONFIG:') && givenParameter.trim().endsWith('.json')) {
configurationFileName = givenParameter.trim().split('USE_CONFIG:')[1];
if (!fm.fileExists(getFilePath(configurationFileName, fm))) {
throw('Config file with provided name ' + configurationFileName + ' does not exist!\nCreate it first by running the script once providing the name in variable configurationFileName and maybe with variable overwritePersistedConfig set to true');
throw ('Config file with provided name ' + configurationFileName + ' does not exist!\nCreate it first by running the script once providing the name in variable configurationFileName and maybe with variable overwritePersistedConfig set to true');
}
return true;
}
Expand All @@ -582,12 +593,31 @@ function useCredentialsFromWidgetParameter(givenParameter) {

async function getAuthToken() {
if (CONFIGURATION.hbServiceMachineBaseUrl === '>enter the ip with the port here<') {
throw('Base URL to machine not entered! Edit variable called hbServiceMachineBaseUrl')
throw ('Base URL to machine not entered! Edit variable called hbServiceMachineBaseUrl')
}
let req = new Request(checkAuthUrl());
if (CONFIGURATION.access_token != null && CONFIGURATION.access_token != '') {
req.timeoutInterval = CONFIGURATION.requestTimeoutInterval;
let headers = {
'accept': '*\/*',
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + CONFIGURATION.access_token
};
req.headers = headers;
let result;
try {
result = await req.loadJSON();
if (result.status === 'OK') {
return CONFIGURATION.access_token;
}
} catch (e) {}
}
let req = new Request(noAuthUrl());

req = new Request(noAuthUrl());
req.timeoutInterval = CONFIGURATION.requestTimeoutInterval;
const headers = {
'accept': '*\/*', 'Content-Type': 'application/json'
'accept': '*\/*',
'Content-Type': 'application/json'
};
req.method = 'POST';
req.headers = headers;
Expand All @@ -610,6 +640,8 @@ async function getAuthToken() {
'password': CONFIGURATION.password,
'otp': 'string'
};
if (twoFactorAuthentication)
body.otp = await importModule('Authy Client').getToken("homebridge")
req.body = JSON.stringify(body);
req.method = 'POST';
req.headers = headers;
Expand All @@ -625,7 +657,8 @@ async function fetchData(token, url) {
let req = new Request(url);
req.timeoutInterval = CONFIGURATION.requestTimeoutInterval;
let headers = {
'accept': '*\/*', 'Content-Type': 'application/json',
'accept': '*\/*',
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
};
req.headers = headers;
Expand All @@ -649,7 +682,7 @@ async function getHomebridgeStatus(token) {
async function getHomebridgeVersionInfos(token) {
if (CONFIGURATION.pluginsOrSwUpdatesToIgnore.includes('HOMEBRIDGE_UTD')) {
log('You configured Homebridge to not be checked for updates. Widget will show that it\'s UTD!');
return {updateAvailable: false};
return { updateAvailable: false };
}
const hbVersionData = await fetchData(token, hbVersionUrl());
if (hbVersionData === undefined) {
Expand All @@ -661,7 +694,7 @@ async function getHomebridgeVersionInfos(token) {
async function getNodeJsVersionInfos(token) {
if (CONFIGURATION.pluginsOrSwUpdatesToIgnore.includes('NODEJS_UTD')) {
log('You configured Node.js to not be checked for updates. Widget will show that it\'s UTD!');
return {updateAvailable: false};
return { updateAvailable: false };
}
const nodeJsData = await fetchData(token, nodeJsUrl());
if (nodeJsData === undefined) {
Expand All @@ -676,16 +709,19 @@ async function getPluginVersionInfos(token) {
if (pluginsData === undefined) {
return undefined;
}
if (pluginsData.statusCode === 403) {
return undefined;
}
for (plugin of pluginsData) {
if (CONFIGURATION.pluginsOrSwUpdatesToIgnore.includes(plugin.name)) {
log('You configured ' + plugin.name + ' to not be checked for updates. Widget will show that it\'s UTD!');
continue;
}
if (plugin.updateAvailable) {
return {plugins: pluginsData, updateAvailable: true};
return { plugins: pluginsData, updateAvailable: true };
}
}
return {plugins: pluginsData, updateAvailable: false};
return { plugins: pluginsData, updateAvailable: false };
}

async function getUsedRamString(ramData) {
Expand Down