From 86d8a09688bffb99b8f781c4a82810a260e6935c Mon Sep 17 00:00:00 2001 From: zefie Date: Fri, 7 Oct 2022 19:31:05 -0400 Subject: [PATCH] initial work on wtv-admin features --- .../ServiceVault/wtv-admin/admin.js | 94 +++++++++++ .../ServiceVault/wtv-admin/ban.js | 94 +++++++++++ .../ServiceVault/wtv-admin/deleteaccount.js | 112 +++++++++++++ .../ServiceVault/wtv-admin/deleteuser.js | 118 ++++++++++++++ .../ServiceVault/wtv-admin/findaccount.js | 97 +++++++++++ .../ServiceVault/wtv-admin/images/nuke.gif | Bin 0 -> 20903 bytes .../wtv-admin/images/nuke_inverted.gif | Bin 0 -> 20831 bytes .../ServiceVault/wtv-admin/unban.js | 104 ++++++++++++ zefie_wtvp_minisrv/WTVAdmin.js | 153 ++++++++++++++++++ zefie_wtvp_minisrv/WTVRegister.js | 1 - zefie_wtvp_minisrv/WTVShared.js | 114 +++++++++++-- zefie_wtvp_minisrv/app.js | 49 +----- zefie_wtvp_minisrv/config.json | 4 + zefie_wtvp_minisrv/package.json | 2 +- zefie_wtvp_minisrv/user_config.example.json | 9 ++ 15 files changed, 891 insertions(+), 60 deletions(-) create mode 100644 zefie_wtvp_minisrv/ServiceVault/wtv-admin/admin.js create mode 100644 zefie_wtvp_minisrv/ServiceVault/wtv-admin/ban.js create mode 100644 zefie_wtvp_minisrv/ServiceVault/wtv-admin/deleteaccount.js create mode 100644 zefie_wtvp_minisrv/ServiceVault/wtv-admin/deleteuser.js create mode 100644 zefie_wtvp_minisrv/ServiceVault/wtv-admin/findaccount.js create mode 100644 zefie_wtvp_minisrv/ServiceVault/wtv-admin/images/nuke.gif create mode 100644 zefie_wtvp_minisrv/ServiceVault/wtv-admin/images/nuke_inverted.gif create mode 100644 zefie_wtvp_minisrv/ServiceVault/wtv-admin/unban.js create mode 100644 zefie_wtvp_minisrv/WTVAdmin.js diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-admin/admin.js b/zefie_wtvp_minisrv/ServiceVault/wtv-admin/admin.js new file mode 100644 index 00000000..fad5cf02 --- /dev/null +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-admin/admin.js @@ -0,0 +1,94 @@ +var minisrv_service_file = true; + +var WTVAdmin = require("./WTVAdmin.js"); +var wtva = new WTVAdmin(minisrv_config, ssid_sessions[socket.ssid], service_name); +var auth = wtva.isAuthorized(); +if (auth === true) { + var password = null; + if (request_headers.Authorization) { + var authheader = request_headers.Authorization.split(' '); + if (authheader[0] == "Basic") { + password = Buffer.from(authheader[1], 'base64').toString(); + if (password) password = password.split(':')[1]; + } + } + if (wtva.checkPassword(password)) { + headers = "200 OK\r\nContent-Type: text/html"; + data = ` + + +${minisrv_config.config.service_name} Admin Tricks + + + + + +
+
+

${minisrv_config.config.service_name} Admin Tricks

+
+ + + + + + + + + + + + + + + + + +
+
Standard Tricks + +Account Lookup +
+
Ban an SSID + +Delete an Account +
+
Unban an SSID + +Delete User from Account +
+
Whitelist an SSID + +Remove Pass from User +
+
Grant Admin to SSID + +Revoke Admin from SSID +
+
+ + +
+
+ + +
+
+ + + + +
+ + +`; + } else { + var errpage = wtvshared.doErrorPage(401, "Please enter the administration password, you can leave the username blank."); + headers = errpage[0]; + data = errpage[1]; + } +} else { + var errpage = wtvshared.doErrorPage(403, auth); + headers = errpage[0]; + data = errpage[1]; +} \ No newline at end of file diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-admin/ban.js b/zefie_wtvp_minisrv/ServiceVault/wtv-admin/ban.js new file mode 100644 index 00000000..21836a21 --- /dev/null +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-admin/ban.js @@ -0,0 +1,94 @@ +var minisrv_service_file = true; + +var WTVAdmin = require("./WTVAdmin.js"); +var wtva = new WTVAdmin(minisrv_config, ssid_sessions[socket.ssid], service_name); +var auth = wtva.isAuthorized(); +if (auth === true) { + var password = null; + if (request_headers.Authorization) { + var authheader = request_headers.Authorization.split(' '); + if (authheader[0] == "Basic") { + password = Buffer.from(authheader[1], 'base64').toString(); + if (password) password = password.split(':')[1]; + } + } + if (wtva.checkPassword(password)) { + if (request_headers.query.ssid) { + var ssid = request_headers.query.ssid.toLowerCase(); + if (ssid == socket.ssid) { + var nobanself = true; + } else { + var fake_config = wtvshared.getUserConfig(); + if (!fake_config.config) fake_config.config = {}; + if (!fake_config.config.ssid_block_list) fake_config.config.ssid_block_list = []; + var entry_exists = false; + Object.keys(fake_config.config.ssid_block_list).forEach(function (k) { + if (fake_config.config.ssid_block_list[k] == ssid) { + entry_exists = true; + } + }); + if (!entry_exists) { + fake_config.config.ssid_block_list.push(ssid); + wtvshared.writeToUserConfig(fake_config); + reloadConfig(); + } + } + } + headers = `200 OK +Content-Type: text/html +wtv-expire-all: wtv-admin:/ban`; + if (ssid) { + headers += "\nwtv-noback-all: wtv-admin:/ban"; + } + data = ` + + +${minisrv_config.config.service_name} Admin Tricks + + + + +
+
+

${minisrv_config.config.service_name} Admin Tricks

+
+ + + +
+

Ban an SSID

+
+ + +


` + if (request_headers.query.ssid) { + if (nobanself) { + data += "Cannot ban yourself." + } else { + if (entry_exists) { + data += "SSID " + request_headers.query.ssid + " is already in the ban list.

"; + } else { + data += "SSID " + request_headers.query.ssid + " added to the ban list.

"; + } + } + } + data += ` +
+
+
+

+Go Back +

+ + +`; + } else { + var errpage = wtvshared.doErrorPage(401, "Please enter the administration password, you can leave the username blank."); + headers = errpage[0]; + data = errpage[1]; + } +} else { + var errpage = wtvshared.doErrorPage(403, auth); + headers = errpage[0]; + data = errpage[1]; +} \ No newline at end of file diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-admin/deleteaccount.js b/zefie_wtvp_minisrv/ServiceVault/wtv-admin/deleteaccount.js new file mode 100644 index 00000000..8c102af6 --- /dev/null +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-admin/deleteaccount.js @@ -0,0 +1,112 @@ +var minisrv_service_file = true; + +var WTVAdmin = require("./WTVAdmin.js"); +var wtva = new WTVAdmin(minisrv_config, ssid_sessions[socket.ssid], service_name); +var auth = wtva.isAuthorized(); +if (auth === true) { + var password = null; + if (request_headers.Authorization) { + var authheader = request_headers.Authorization.split(' '); + if (authheader[0] == "Basic") { + password = Buffer.from(authheader[1], 'base64').toString(); + if (password) password = password.split(':')[1]; + } + } + if (wtva.checkPassword(password)) { + if (request_headers.query.ssid) { + var ssid_match = false; + var ssid = request_headers.query.ssid.toLowerCase(); + var user_info = wtva.getAccountInfoBySSID(ssid); + if (request_headers.query.confirm_delete) { + user_info = null; + if (ssid == socket.ssid) { + ssid_match = true; + } else { + // delete + var userAccount = wtva.getAccountBySSID(ssid); + userAccount.unregisterBox(); + } + } + } + headers = `200 OK +Content-Type: text/html +wtv-expire-all: wtv-admin:/deleteaccount +wtv-noback-all: wtv-admin:/deleteaccount`; + data = ` + + +${minisrv_config.config.service_name} Admin Tricks + + + + +
+
+

${minisrv_config.config.service_name} Admin Tricks

+
+ + + +
+

Delete an Account

+
+   +


` + if (ssid) { + if (user_info) { + data += ` +User Information: + + +`; + if (user_info.account_users) { + data += ``; + if (Object.keys(user_info.account_users).length > 1) { + data += `` + } + } + data += ` + + + +` + data += `
Username:${user_info.username} (User ID: ${user_info.user_id})
SSID:${user_info.ssid}
Primary User:${user_info.account_users['subscriber'].subscriber_username}
Additional Users:`; + Object.keys(user_info.account_users).forEach(function (k) { + if (k == "subscriber") return; + data += user_info.account_users[k].subscriber_username + "
"; + }) + data += `
+
+ + + +
+
`; + } else if (request_headers.query.confirm_delete && ssid_match) { + data += `Cannot delete yourself in this manner.
Try ${minisrv_config.config.service_name} Tricks Unregister.


`; + } else if (request_headers.query.confirm_delete) { + data += "Account for SSID \"" + ssid + "\" has been deleted.

"; + } else { + data += "Could not find an account for SSID \"" + ssid + "\"

"; + } + } + data += ` +
+
+
+

+Go Back +

+ + +`; + } else { + var errpage = wtvshared.doErrorPage(401, "Please enter the administration password, you can leave the username blank."); + headers = errpage[0]; + data = errpage[1]; + } +} else { + var errpage = wtvshared.doErrorPage(403, auth); + headers = errpage[0]; + data = errpage[1]; +} \ No newline at end of file diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-admin/deleteuser.js b/zefie_wtvp_minisrv/ServiceVault/wtv-admin/deleteuser.js new file mode 100644 index 00000000..78f16fb6 --- /dev/null +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-admin/deleteuser.js @@ -0,0 +1,118 @@ +var minisrv_service_file = true; + +var WTVAdmin = require("./WTVAdmin.js"); +var wtva = new WTVAdmin(minisrv_config, ssid_sessions[socket.ssid], service_name); +var auth = wtva.isAuthorized(); +if (auth === true) { + var password = null; + if (request_headers.Authorization) { + var authheader = request_headers.Authorization.split(' '); + if (authheader[0] == "Basic") { + password = Buffer.from(authheader[1], 'base64').toString(); + if (password) password = password.split(':')[1]; + } + } + if (wtva.checkPassword(password)) { + if (request_headers.query.username) { + var show_cannot_modify_self = false; + var show_cannot_remove_primary = false; + var show_box_was_unregistered = false; + var user_info = wtva.getAccountInfo(request_headers.query.username.toLowerCase()); // username search + if (user_info) { + if (user_info.ssid == socket.ssid) { + show_cannot_modify_self = true; + } + if (request_headers.query.confirm_delete) { + if (!show_cannot_modify_self) { + // delete + var userAccount = wtva.getAccountBySSID(user_info.ssid); + userAccount.switchUserID(0, false, false); + var userCount = Object.keys(user_info.account_users).length; + if (user_info.user_id === 0) { + show_cannot_remove_primary = true; + } else { + var result = userAccount.removeUser(user_info.user_id); + } + } + } + } + } + headers = `200 OK +Content-Type: text/html +wtv-expire-all: wtv-admin:/deleteuser +wtv-noback-all: wtv-admin:/deleteuser`; + data = ` + + +${minisrv_config.config.service_name} Admin Tricks + + + + +
+
+

${minisrv_config.config.service_name} Admin Tricks

+
+ + + +
+

Delete a user from an account

+
+   +


` + if (request_headers.query.username) { + if (user_info && !request_headers.query.confirm_delete) { + data += ` +User Information: + + +`; + if (user_info.account_users) { + data += ``; + } + data += ` + + + +` + data += `
Username:${user_info.username} (User ID: ${user_info.user_id})
SSID:${user_info.ssid}
Primary User:${user_info.account_users['subscriber'].subscriber_username}
+
+ + + +
+
`; + } else if (request_headers.query.confirm_delete && show_cannot_modify_self) { + data += `Cannot modify your account in this manner.
Try wtv-setup.


`; + } else if (request_headers.query.confirm_delete && show_cannot_remove_primary) { + data += `Cannot delete a primary user in this manner.
Try deleting the account.

`; + } else if (request_headers.query.confirm_delete && show_box_was_unregistered) { + data += `Account for "${user_info.username}" was deleted, and SSID ${user_info.ssid} unregistered, as it was the only user.

`; + } else if (request_headers.query.confirm_delete) { + if (result) data += `User "${user_info.username}" has been deleted from account belonging to SSID ${user_info.ssid}.

`; + else data += `Could not delete "${user_info.username}" from SSID ${user_info.ssid}.

`; + } else { + data += "Could not find an account for user \"" + request_headers.query.username + "\"

"; + } + } + data += ` +
+
+
+

+Go Back +

+ + +`; + } else { + var errpage = wtvshared.doErrorPage(401, "Please enter the administration password, you can leave the username blank."); + headers = errpage[0]; + data = errpage[1]; + } +} else { + var errpage = wtvshared.doErrorPage(403, auth); + headers = errpage[0]; + data = errpage[1]; +} \ No newline at end of file diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-admin/findaccount.js b/zefie_wtvp_minisrv/ServiceVault/wtv-admin/findaccount.js new file mode 100644 index 00000000..d2518f59 --- /dev/null +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-admin/findaccount.js @@ -0,0 +1,97 @@ +var minisrv_service_file = true; + +var WTVAdmin = require("./WTVAdmin.js"); +var wtva = new WTVAdmin(minisrv_config, ssid_sessions[socket.ssid], service_name); +var auth = wtva.isAuthorized(); +if (auth === true) { + var password = null; + if (request_headers.Authorization) { + var authheader = request_headers.Authorization.split(' '); + if (authheader[0] == "Basic") { + password = Buffer.from(authheader[1], 'base64').toString(); + if (password) password = password.split(':')[1]; + } + } + if (wtva.checkPassword(password)) { + if (request_headers.query.username) { + var user_info = wtva.getAccountInfo(request_headers.query.username.toLowerCase()); // username search + if (!user_info) user_info = wtva.getAccountInfoBySSID(request_headers.query.username.toLowerCase()); // ssid search + } + headers = `200 OK +Content-Type: text/html +wtv-expire-all: wtv-admin:/findaccount +wtv-noback-all: wtv-admin:/findaccount`; + data = ` + + +${minisrv_config.config.service_name} Admin Tricks + + + + +
+
+

${minisrv_config.config.service_name} Admin Tricks

+
+ + + +
+

Account Lookup

+
+   +


` + if (request_headers.query.username) { + if (user_info) { + data += ` +User Information: + + +`; + if (user_info.account_users) { + data += ``; + if (Object.keys(user_info.account_users).length > 1) { + data += `` + } + } + data += ` + + + +` + data += `
Username:${user_info.username} (User ID: ${user_info.user_id})
SSID:${user_info.ssid}
Primary User:${user_info.account_users['subscriber'].subscriber_username}
Additional Users:`; + Object.keys(user_info.account_users).forEach(function (k) { + if (k == "subscriber") return; + data += user_info.account_users[k].subscriber_username + "
"; + }) + data += `
+Ban SSID +           +Unban SSID +           +Delete Account +
`; + } else { + data += "Could not find user \"" + request_headers.query.username + "\"

"; + } + } + data += ` +
+
+
+

+Go Back +

+ + +`; + } else { + var errpage = wtvshared.doErrorPage(401, "Please enter the administration password, you can leave the username blank."); + headers = errpage[0]; + data = errpage[1]; + } +} else { + var errpage = wtvshared.doErrorPage(403, auth); + headers = errpage[0]; + data = errpage[1]; +} \ No newline at end of file diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-admin/images/nuke.gif b/zefie_wtvp_minisrv/ServiceVault/wtv-admin/images/nuke.gif new file mode 100644 index 0000000000000000000000000000000000000000..f192803298f5a7388a50e5106e68ee78137001c3 GIT binary patch literal 20903 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 OcV!nH?x7q40suQRT;-Jj literal 0 HcmV?d00001 diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-admin/images/nuke_inverted.gif b/zefie_wtvp_minisrv/ServiceVault/wtv-admin/images/nuke_inverted.gif new file mode 100644 index 0000000000000000000000000000000000000000..a52ecdf2a92783d07c0e25e51ea162a469365f96 GIT binary patch literal 20831 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 literal 0 HcmV?d00001 diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-admin/unban.js b/zefie_wtvp_minisrv/ServiceVault/wtv-admin/unban.js new file mode 100644 index 00000000..d2e06283 --- /dev/null +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-admin/unban.js @@ -0,0 +1,104 @@ +var minisrv_service_file = true; + +var WTVAdmin = require("./WTVAdmin.js"); +var wtva = new WTVAdmin(minisrv_config, ssid_sessions[socket.ssid], service_name); +var auth = wtva.isAuthorized(); +var ssids_removed = []; +if (auth === true) { + var password = null; + if (request_headers.Authorization) { + var authheader = request_headers.Authorization.split(' '); + if (authheader[0] == "Basic") { + password = Buffer.from(authheader[1], 'base64').toString(); + if (password) password = password.split(':')[1]; + } + } + if (wtva.checkPassword(password)) { + if (request_headers.query.unban_ssid) { + var config_changed = false; + var fake_config = wtvshared.getUserConfig(); + if (!fake_config.config) fake_config.config = {}; + if (!fake_config.config.ssid_block_list) fake_config.config.ssid_block_list = []; + if (typeof request_headers.query.unban_ssid === 'string') { + Object.keys(fake_config.config.ssid_block_list).forEach(function (k) { + if (fake_config.config.ssid_block_list[k] == request_headers.query.unban_ssid) { + fake_config.config.ssid_block_list.splice(k, 1); + ssids_removed.push(request_headers.query.unban_ssid) + config_changed = true; + } + }); + } else { + Object.keys(fake_config.config.ssid_block_list).forEach(function (k) { + Object.keys(request_headers.query.unban_ssid).forEach(function (j) { + if (fake_config.config.ssid_block_list[k] == request_headers.query.unban_ssid[j]) { + fake_config.config.ssid_block_list.splice(k,1); + ssids_removed.push(request_headers.query.unban_ssid[j]) + config_changed = true; + } + }); + }); + } + if (config_changed) { + wtvshared.writeToUserConfig(fake_config); + minisrv_config = reloadConfig(); + } + } + headers = `200 OK +Content-Type: text/html +wtv-expire-all: wtv-admin:/unban`; + if (request_headers.query.unban_ssid) { + headers += "\nwtv-noback-all: wtv-admin:/unban"; + } + data = ` + + +${minisrv_config.config.service_name} Admin Tricks + + + + +
+
+

${minisrv_config.config.service_name} Admin Tricks

+
+ + + +
+

Unban an SSID

`; + if (minisrv_config.config.ssid_block_list.length > 0) { + data += '
'; + data += '
'; + } else { + data += "No SSIDs are in the ban list.

"; + } + if (ssids_removed.length > 0) { + if (config_changed) { + data += "SSID(s) " + ssids_removed + " removed from the ban list.

"; + } + } + data += ` +
+
+
+

+Go Back +

+ + +`; + } else { + var errpage = wtvshared.doErrorPage(401, "Please enter the administration password, you can leave the username blank."); + headers = errpage[0]; + data = errpage[1]; + } +} else { + var errpage = wtvshared.doErrorPage(403, auth); + headers = errpage[0]; + data = errpage[1]; +} \ No newline at end of file diff --git a/zefie_wtvp_minisrv/WTVAdmin.js b/zefie_wtvp_minisrv/WTVAdmin.js new file mode 100644 index 00000000..78aff4ca --- /dev/null +++ b/zefie_wtvp_minisrv/WTVAdmin.js @@ -0,0 +1,153 @@ +class WTVAdmin { + + fs = require('fs'); + path = require('path'); + minisrv_config = []; + wtvr = null; + wtvshared = null; + wtvclient = null; + WTVClientSessionData = require('./WTVClientSessionData.js'); + service_name = "wtv-admin"; + + constructor(minisrv_config, wtvclient, service_name) { + this.minisrv_config = minisrv_config; + var { WTVShared } = require('./WTVShared.js'); + var WTVRegister = require('./WTVRegister.js'); + this.wtvclient = wtvclient; + this.wtvshared = new WTVShared(minisrv_config); + this.wtvr = new WTVRegister(minisrv_config); + this.clientAddress = wtvclient.getClientAddress(); + this.service_name = service_name; + } + + + ip2long(ip) { + var components; + + if (components = ip.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)) { + var iplong = 0; + var power = 1; + for (var i = 4; i >= 1; i -= 1) { + iplong += power * parseInt(components[i]); + power *= 256; + } + return iplong; + } + else return -1; + } + + isInSubnet(ip, subnet) { + var mask, base_ip, long_ip = this.ip2long(ip); + if ((mask = subnet.match(/^(.*?)\/(\d{1,2})$/)) && ((base_ip = this.ip2long(mask[1])) >= 0)) { + var freedom = Math.pow(2, 32 - parseInt(mask[2])); + return (long_ip > base_ip) && (long_ip < base_ip + freedom - 1); + } + else return false; + } + + rejectConnection(reason_is_ssid) { + var rejectReason; + if (reason_is_ssid) { + rejectReason = this.wtvclient.ssid + " is not in the whitelist."; + console.log(" * Request from SSID", this.wtvshared.filterSSID(this.wtvclient.ssid), "(" + this.clientAddress + ") for wtv-admin, but that SSID is not in the admin whitelist."); + } else { + rejectReason = this.clientAddress + " is not in the whitelist for SSID " + this.wtvclient.ssid + "."; + console.log(" * Request from SSID", this.wtvshared.filterSSID(this.wtvclient.ssid), "(" + this.clientAddress + ") for wtv-admin, but that IP is not authorized for that SSID."); + } + return rejectReason; + } + + checkPassword(password) { + if (this.minisrv_config.services[this.service_name].password) { + return (password == this.minisrv_config.services[this.service_name].password); + } else { + // no password set + return true; + } + } + + isAuthorized() { + var allowed_ssid = false; + var allowed_ip = false; + if (this.minisrv_config.services[this.service_name].authorized_ssids) { + var self = this; + Object.keys(self.minisrv_config.services[this.service_name].authorized_ssids).forEach(function (k) { + if (typeof self.minisrv_config.services[self.service_name].authorized_ssids[k] == "string") { + var ssid = self.minisrv_config.services[self.service_name].authorized_ssids[k] + if (ssid == self.wtvclient.ssid) allowed_ssid = true; + allowed_ip = true; // no ip block defined + } else { + var ssid = k; + if (ssid == self.wtvclient.ssid) { + allowed_ssid = true; + Object.keys(self.minisrv_config.services[self.service_name].authorized_ssids[k]).forEach(function (j) { + if (self.isInSubnet(self.clientAddress, self.minisrv_config.services[self.service_name].authorized_ssids[k][j])) { + allowed_ip = true; + } + }); + } + } + }); + } + return (allowed_ssid && allowed_ip) ? true : this.rejectConnection(!allowed_ssid); + } + + getAccountInfo(username, directory = null) { + var search_dir = this.minisrv_config.config.SessionStore; + var account_data = null; + var self = this; + if (directory) search_dir = directory; + this.fs.readdirSync(search_dir).forEach(file => { + if (self.fs.lstatSync(search_dir + self.path.sep + file).isDirectory() && account_data === null) { + account_data = self.getAccountInfo(username, search_dir + self.path.sep + file); + } + if (account_data !== null) return; + if (!file.match(/.*\.json/ig)) return; + try { + var temp_session_data_file = self.fs.readFileSync(search_dir + self.path.sep + file, 'Utf8'); + var temp_session_data = JSON.parse(temp_session_data_file); + + if (temp_session_data.subscriber_username.toLowerCase() == username.toLowerCase()) { + account_data = [temp_session_data, (search_dir + self.path.sep + file).replace(this.minisrv_config.config.SessionStore + this.path.sep, "").split(this.path.sep)[0]]; + } + } catch (e) { + console.error(" # Error parsing Session Data JSON", search_dir + self.path.sep + file, e); + } + }); + if (account_data !== null) { + if (account_data.ssid) return account_data; + var account_info = {}; + account_info.ssid = account_data[1]; + account_info.username = account_data[0].subscriber_username; + account_info.user_id = account_data[0].subscriber_userid; + var userSession = new this.WTVClientSessionData(this.minisrv_config, account_info.ssid); + userSession.user_id = 0; + account_info.account_users = userSession.listPrimaryAccountUsers(); + return account_info; + } + return null; + } + + getAccountInfoBySSID(ssid) { + var account_info = {}; + var userSession = new this.WTVClientSessionData(this.minisrv_config, ssid); + userSession.user_id = 0; + if (userSession.isRegistered(false)) { + account_info.ssid = ssid; + account_info.account_users = userSession.listPrimaryAccountUsers(); + account_info.username = account_info.account_users['subscriber'].subscriber_username; + account_info.user_id = 0; + return account_info; + } + else return false; + } + + + getAccountBySSID(ssid) { + var userSession = new this.WTVClientSessionData(this.minisrv_config, ssid); + userSession.user_id = 0; + return userSession; + } +} + +module.exports = WTVAdmin; diff --git a/zefie_wtvp_minisrv/WTVRegister.js b/zefie_wtvp_minisrv/WTVRegister.js index 58bd4fba..838b8dd0 100644 --- a/zefie_wtvp_minisrv/WTVRegister.js +++ b/zefie_wtvp_minisrv/WTVRegister.js @@ -3,7 +3,6 @@ class WTVRegister { fs = require('fs'); path = require('path'); minisrv_config = []; - service_owner = "a minisrv user"; session_store_dir = null; diff --git a/zefie_wtvp_minisrv/WTVShared.js b/zefie_wtvp_minisrv/WTVShared.js index b6099c85..b87c2818 100644 --- a/zefie_wtvp_minisrv/WTVShared.js +++ b/zefie_wtvp_minisrv/WTVShared.js @@ -48,7 +48,6 @@ class WTVShared { return query } - htmlEntitize(string, process_newline = false) { string = this.html_entities.encode(string).replace(/'/g, "'"); @@ -144,6 +143,27 @@ class WTVShared { return (this.isMiniBrowser(ssid_session) || parseInt(ssid_session.get("wtv-system-version")) < 3500) ? true : false; } + getUserConfig() { + try { + if (this.fs.lstatSync(__dirname + "/user_config.json")) { + try { + var minisrv_user_config = JSON.parse(this.fs.readFileSync(__dirname + this.path.sep + "user_config.json")); + } catch (e) { + console.error("ERROR: Could not read user_config.json", e); + var throw_me = true; + } + } else { + var minisrv_user_config = {} + } + return minisrv_user_config; + } catch (e) { + if (minisrv_config.config.debug_flags) { + if (minisrv_config.config.debug_flags.debug) console.error(" * Notice: Could not find user configuration (user_config.json). Using default configuration."); + } + } + } + + readMiniSrvConfig(user_config = true, notices = true) { if (notices) console.log(" *** Reading global configuration..."); try { @@ -169,21 +189,12 @@ class WTVShared { if (user_config) { try { - if (this.fs.lstatSync(__dirname + "/user_config.json")) { - if (notices) console.log(" *** Reading user configuration..."); - try { - var minisrv_user_config = JSON.parse(this.fs.readFileSync(__dirname + this.path.sep + "user_config.json")); - } catch (e) { - console.error("ERROR: Could not read user_config.json", e); - var throw_me = true; - } - // file exists and we read and parsed it, but the variable is undefined - // Likely a syntax parser error that did not trip the exception check above - try { - minisrv_config = integrateConfig(minisrv_config, minisrv_user_config) - } catch (e) { - console.error("ERROR: Could not read user_config.json", e); - } + if (notices) console.log(" *** Reading user configuration..."); + var minisrv_user_config = this.getUserConfig() + try { + minisrv_config = integrateConfig(minisrv_config, minisrv_user_config) + } catch (e) { + console.error("ERROR: Could not read user_config.json", e); } } catch (e) { if (minisrv_config.config.debug_flags) { @@ -192,7 +203,76 @@ class WTVShared { } } - return minisrv_config; + // defaults + minisrv_config.config.debug_flags = []; + minisrv_config.config.debug_flags.debug = false; + minisrv_config.config.debug_flags.quiet = true; // will squash minisrv_config.config.debug_flags.debug even if its true + minisrv_config.config.debug_flags.show_headers = false; + + if (minisrv_config.config.verbosity) { + switch (minisrv_config.config.verbosity) { + case 0: + minisrv_config.config.debug_flags.debug = false; + minisrv_config.config.debug_flags.quiet = true; + minisrv_config.config.debug_flags.show_headers = false; + if (notices) console.log(" * Console Verbosity level 0 (quietest)") + break; + case 1: + minisrv_config.config.debug_flags.debug = false; + minisrv_config.config.debug_flags.quiet = true; + minisrv_config.config.debug_flags.show_headers = true; + if (notices) console.log(" * Console Verbosity level 1 (headers shown)") + break; + case 2: + minisrv_config.config.debug_flags.debug = true; + minisrv_config.config.debug_flags.quiet = true; + minisrv_config.config.debug_flags.show_headers = false; + if (notices) console.log(" * Console Verbosity level 2 (verbose without headers)") + break; + case 3: + minisrv_config.config.debug_flags.debug = true; + minisrv_config.config.debug_flags.quiet = true; + minisrv_config.config.debug_flags.show_headers = true; + if (notices) console.log(" * Console Verbosity level 3 (verbose with headers)") + break; + default: + minisrv_config.config.debug_flags.debug = true; + minisrv_config.config.debug_flags.quiet = false; + minisrv_config.config.debug_flags.show_headers = true; + if (notices) console.log(" * Console Verbosity level 4 (debug verbosity)") + break; + } + } + + if (notices) console.log(" *** Configuration successfully read."); + this.minisrv_config = minisrv_config; + return this.minisrv_config; + } + + writeToUserConfig(config) { + if (config) { + try { + var minisrv_user_config = this.getUserConfig(); + + // write back + try { + var new_user_config = {}; + Object.assign(new_user_config, minisrv_user_config, config); + if (this.minisrv_config.config.debug_flags.debug) console.log(" * Writing new user configuration..."); + this.fs.writeFileSync(__dirname + this.path.sep + "user_config.json", JSON.stringify(new_user_config, null, "\t")); + } + catch (e) { + if (this.minisrv_config.config.debug_flags) { + if (this.minisrv_config.config.debug_flags.debug) console.error(" * WARNING: Could not update user config. Data may have been lost.", e); + } + } + + } catch (e) { + if (this.minisrv_config.config.debug_flags) { + if (this.minisrv_config.config.debug_flags.debug) console.error(" * Notice: Could not find user configuration (user_config.json). Using default configuration."); + } + } + } } getMiniSrvConfig() { diff --git a/zefie_wtvp_minisrv/app.js b/zefie_wtvp_minisrv/app.js index 9060af09..31c1def2 100644 --- a/zefie_wtvp_minisrv/app.js +++ b/zefie_wtvp_minisrv/app.js @@ -165,6 +165,7 @@ async function processPath(socket, service_vault_file_path, request_headers = ne request_is_async: request_is_async, minisrv_version_string: z_title, parseBool: parseBool, + reloadConfig: reloadConfig, cwd: __dirname // current working directory, updated below in function } @@ -1728,6 +1729,12 @@ function getGitRevision() { return null; } } +var minisrv_config = null; + +function reloadConfig() { + minisrv_config = wtvshared.readMiniSrvConfig(true, false); // snatches minisrv_config + return minisrv_config; +} // SERVER START var git_commit = getGitRevision() @@ -1736,7 +1743,7 @@ if (git_commit) console.log("**** Welcome to " + z_title + " (git " + git_commit else console.log("**** Welcome to " + z_title + " ****"); const wtvshared = new WTVShared(); // creates minisrv_config -var minisrv_config = wtvshared.getMiniSrvConfig(); // snatches minisrv_config +minisrv_config = wtvshared.getMiniSrvConfig(); // snatches minisrv_config const wtvmime = new WTVMime(minisrv_config); if (git_commit) { @@ -1832,46 +1839,6 @@ process.on('uncaughtException', function (err) { console.error((err && err.stack) ? err.stack : err); }); -// defaults -minisrv_config.config.debug_flags = []; -minisrv_config.config.debug_flags.debug = false; -minisrv_config.config.debug_flags.quiet = true; // will squash minisrv_config.config.debug_flags.debug even if its true -minisrv_config.config.debug_flags.show_headers = false; - -if (minisrv_config.config.verbosity) { - switch (minisrv_config.config.verbosity) { - case 0: - minisrv_config.config.debug_flags.debug = false; - minisrv_config.config.debug_flags.quiet = true; - minisrv_config.config.debug_flags.show_headers = false; - console.log(" * Console Verbosity level 0 (quietest)") - break; - case 1: - minisrv_config.config.debug_flags.debug = false; - minisrv_config.config.debug_flags.quiet = true; - minisrv_config.config.debug_flags.show_headers = true; - console.log(" * Console Verbosity level 1 (headers shown)") - break; - case 2: - minisrv_config.config.debug_flags.debug = true; - minisrv_config.config.debug_flags.quiet = true; - minisrv_config.config.debug_flags.show_headers = false; - console.log(" * Console Verbosity level 2 (verbose without headers)") - break; - case 3: - minisrv_config.config.debug_flags.debug = true; - minisrv_config.config.debug_flags.quiet = true; - minisrv_config.config.debug_flags.show_headers = true; - console.log(" * Console Verbosity level 3 (verbose with headers)") - break; - default: - minisrv_config.config.debug_flags.debug = true; - minisrv_config.config.debug_flags.quiet = false; - minisrv_config.config.debug_flags.show_headers = true; - console.log(" * Console Verbosity level 4 (debug verbosity)") - break; - } -} var initstring = ''; ports.sort(); diff --git a/zefie_wtvp_minisrv/config.json b/zefie_wtvp_minisrv/config.json index b29efdad..40308694 100644 --- a/zefie_wtvp_minisrv/config.json +++ b/zefie_wtvp_minisrv/config.json @@ -111,6 +111,10 @@ "port": 1608, "connections": 3 }, + "wtv-admin": { + "port": 1698, + "password": "viRak-7" + }, "http": { "port": 1650, "connections": 3, diff --git a/zefie_wtvp_minisrv/package.json b/zefie_wtvp_minisrv/package.json index c1e248ad..5e5fa6a4 100644 --- a/zefie_wtvp_minisrv/package.json +++ b/zefie_wtvp_minisrv/package.json @@ -1,6 +1,6 @@ { "name": "zefie_wtvp_minisrv", - "version": "0.9.30", + "version": "0.9.31", "description": "WebTV Service (WTVP) Emulation Server", "main": "app.js", "homepage": "https://github.com/zefie/zefie_wtvp_minisrv", diff --git a/zefie_wtvp_minisrv/user_config.example.json b/zefie_wtvp_minisrv/user_config.example.json index 8e2cccf0..9216bac5 100644 --- a/zefie_wtvp_minisrv/user_config.example.json +++ b/zefie_wtvp_minisrv/user_config.example.json @@ -35,6 +35,15 @@ "8100000000000000" ] }, + "wtv-admin": { + "authorized_ssids": { + "8100000000000000": [ + "192.168.1.0/24", + "127.0.0.1" + ] + }, + "password": "my-secure-password" + }, "wtv-log": { "write_logs_to_disk": true },