Password Change UI

This commit is contained in:
Elia Zammuto
2021-05-29 23:35:44 +02:00
parent 8961b0462e
commit ec9481392a
5 changed files with 173 additions and 14 deletions

View File

@@ -1,5 +1,5 @@
<div id="app" class="container">
<h1>Configuration</h1>
<h1 class="my-4">Configuration</h1>
<div class="form" v-if="config">
<!--Header-->
<ul class="nav nav-tabs">
@@ -20,7 +20,7 @@
<!--Log Level-->
<div class="mb-3">
<label for="min_log_level" class="form-label">Log Level</label>
<select id="min_log_level" class="form-control" v-model="config.min_log_level">
<select id="min_log_level" class="form-select" v-model="config.min_log_level">
<option :value="0">Verbose</option>
<option :value="1">Debug</option>
<option :value="2">Info</option>
@@ -34,7 +34,7 @@
<!--Origin PIN Allowed-->
<div class="mb-3">
<label for="origin_pin_allowed" class="form-label">Origin PIN Allowed</label>
<select id="origin_pin_allowed" class="form-control" v-model="config.origin_pin_allowed">
<select id="origin_pin_allowed" class="form-select" v-model="config.origin_pin_allowed">
<option value="pc">Only localhost may access /pin and Web UI</option>
<option value="lan">Only those in LAN may access /pin and Web UI</option>
<option value="wan">Anyone may access /pin and Web UI</option>
@@ -198,7 +198,7 @@
<!--HEVC Suppport -->
<div class="mb-3">
<label for="hevc_mode" class="form-label">HEVC Support</label>
<select id="hevc_mode" class="form-control" v-model="config.hevc_mode">
<select id="hevc_mode" class="form-select" v-model="config.hevc_mode">
<option value="0">Sunshine will specify support for HEVC based on encoder</option>
<option value="1">Sunshine will not advertise support for HEVC</option>
<option value="2">Sunshine will advertise support for HEVC Main profile</option>
@@ -213,7 +213,7 @@
<!--Encoder -->
<div class="mb-3">
<label for="encoder" class="form-label">Force a Specific Encoder</label>
<select id="encoder" class="form-control" v-model="config.encoder">
<select id="encoder" class="form-select" v-model="config.encoder">
<option value="null">Autodetect</option>
<option value="nvenc">nVidia NVENC</option>
<option value="amdvce">AMD AMF/VCE</option>
@@ -265,7 +265,7 @@
<!--NVENC SETTINGS-->
<div class="mb-3">
<label for="nv_preset" class="form-label">NVEnc Preset</label>
<select id="nv_preset" class="form-control" v-model="config.nv_preset">
<select id="nv_preset" class="form-select" v-model="config.nv_preset">
<option value="default">Default</option>
<option value="hp">High Performance</option>
<option value="hq">High Quality</option>
@@ -282,7 +282,7 @@
</div>
<div class="mb-3">
<label for="nv_rc" class="form-label">NVEnc Rate Control</label>
<select id="nv_rc" class="form-control" v-model="config.nv_rc">
<select id="nv_rc" class="form-select" v-model="config.nv_rc">
<option value="auto">auto -- let ffmpeg decide rate control</option>
<option value="constqp">constqp -- constant QP mode</option>
<option value="vbr">vbr -- variable bitrate</option>
@@ -294,7 +294,7 @@
</div>
<div class="mb-3">
<label for="nv_coder" class="form-label">NVEnc Rate Control</label>
<select id="nv_coder" class="form-control" v-model="config.nv_coder">
<select id="nv_coder" class="form-select" v-model="config.nv_coder">
<option value="auto">auto</option>
<option value="cabac">cabac</option>
<option value="cavlc">cavlc</option>
@@ -306,7 +306,7 @@
<!--Presets-->
<div class="mb-3">
<label for="amd_quality" class="form-label">AMD AMF Quality</label>
<select id="amd_quality" class="form-control" v-model="config.amd_quality">
<select id="amd_quality" class="form-select" v-model="config.amd_quality">
<option value="default">Default</option>
<option value="speed">Speed</option>
<option value="balanced">Balanced</option>
@@ -314,7 +314,7 @@
</div>
<div class="mb-3">
<label for="amd_rc" class="form-label">AMD AMF Rate Control</label>
<select id="amd_rc" class="form-control">
<select id="amd_rc" class="form-select">
<option value="auto">auto -- let ffmpeg decide rate control</option>
<option value="constqp">constqp -- constant QP mode</option>
<option value="vbr_latency">vbr_latency -- Latency Constrained Variable Bitrate</option>
@@ -324,7 +324,7 @@
</div>
<div class="mb-3">
<label for="amd_coder" class="form-label">AMD AMF Rate Control</label>
<select id="amd_coder" class="form-control" v-model="config.amd_coder">
<select id="amd_coder" class="form-select" v-model="config.amd_coder">
<option value="auto">auto</option>
<option value="cabac">cabac</option>
<option value="cavlc">cavlc</option>

View File

@@ -37,6 +37,9 @@
<li class="nav-item">
<a class="nav-link" href="/config">Configuration</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/password">Change Password</a>
</li>
</ul>
</div>
</nav>

97
assets/web/password.html Normal file
View File

@@ -0,0 +1,97 @@
<div id="app" class="container">
<h1 class="my-4">Password Change</h1>
<form @submit.prevent="save">
<div class="card d-flex p-4 flex-row">
<div class="col-md-6 px-4">
<h4>Current Credentials</h4>
<div class="mb-3">
<label for="currentUsername" class="form-label">Username</label>
<input required type="text" class="form-control" id="currentUsername" v-model="passwordData.currentUsername">
<div class="form-text">&nbsp;</div>
</div>
<div class="mb-3">
<label for="currentPassword" class="form-label">Password</label>
<input autocomplete="current-password" type="password" class="form-control" id="currentPassword" v-model="passwordData.currentPassword">
</div>
</div>
<div class="col-md-6 px-4">
<h4>New Credentials</h4>
<div class="mb-3">
<label for="newUsername" class="form-label">New Username</label>
<input type="text" class="form-control" id="newUsername" v-model="passwordData.newUsername">
<div class="form-text">If not specified, the username will not change
</div>
</div>
<div class="mb-3">
<label for="newPassword" class="form-label">Password</label>
<input autocomplete="new-password" required type="password" class="form-control" id="newPassword" v-model="passwordData.newPassword">
</div>
<div class="mb-3">
<label for="confirmNewPassword" class="form-label">Confirm Password</label>
<input autocomplete="new-password" required type="password" class="form-control" id="confirmNewPassword" v-model="passwordData.confirmNewPassword">
</div>
</div>
</div>
<div class="alert alert-danger" v-if="error"><b>Error: </b>{{error}}</div>
<div class="alert alert-success" v-if="success"><b>Success! </b>This page will reload soon, your browser will ask you for the new credentials</div>
<div class="mb-3 buttons">
<button class="btn btn-primary">Save</button>
</div>
</form>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
error: null,
success: false,
passwordData: {
currentUsername: '',
currentPassword: '',
newUsername: '',
newPassword: '',
confirmNewPassword: ''
}
}
},
methods: {
save() {
this.error = null;
fetch("/api/password", {
method: "POST",
body: JSON.stringify(this.passwordData)
}).then((r) => {
if (r.status == 200){
r.json().then((rj) => {
if(rj.status.toString() === "true"){
this.success = true;
setTimeout(()=>{
document.location.reload();
},5000);
} else {
this.error = rj.error;
}
})
}
else {
this.error = "Internal Server Error"
}
});
}
}
})
</script>
<style>
.config-page {
padding: 1em;
border: 1px solid #dee2e6;
border-top: none;
}
.buttons {
padding: 1em 0;
}
</style>

View File

@@ -135,6 +135,14 @@ void getConfigPage(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response>
response->write(header + content);
}
template<class T>
void getPasswordPage(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
if(!authenticate(response, request)) return;
std::string header = read_file(WEB_DIR "header.html");
std::string content = read_file(WEB_DIR "password.html");
response->write(header + content);
}
void getApps(resp_https_t response, req_https_t request) {
if(!authenticate(response, request)) return;
std::string content = read_file(SUNSHINE_ASSETS_DIR "/" APPS_JSON);
@@ -306,6 +314,54 @@ void saveConfig(resp_https_t response, req_https_t request) {
}
}
void savePassword(resp_https_t response, req_https_t request) {
if(!authenticate(response, request)) return;
std::stringstream ss;
std::stringstream configStream;
ss << request->content.rdbuf();
pt::ptree inputTree,outputTree,fileTree;
auto g = util::fail_guard([&]() {
std::ostringstream data;
pt::write_json(data, outputTree);
response->write(data.str());
});
try {
//TODO: Input Validation
pt::read_json(ss, inputTree);
std::string username = inputTree.get<std::string>("currentUsername");
std::string newUsername = inputTree.get<std::string>("newUsername");
std::string password = inputTree.get<std::string>("currentPassword");
std::string newPassword = inputTree.get<std::string>("newPassword");
std::string confirmPassword = inputTree.get<std::string>("confirmNewPassword");
if(newUsername.length() == 0) newUsername = username;
std::string hash = crypto::hash_hexstr(password + config::sunshine.salt);
if(username == config::sunshine.username && hash == config::sunshine.password){
if(newPassword != confirmPassword){
outputTree.put("status",false);
outputTree.put("error","Password Mismatch");
}
fileTree.put("username",newUsername);
fileTree.put("password",crypto::hash_hexstr(newPassword + config::sunshine.salt));
fileTree.put("salt",config::sunshine.salt);
pt::write_json(config::sunshine.credentials_file,fileTree);
http::reload_user_creds(config::sunshine.credentials_file);
outputTree.put("status",true);
} else {
outputTree.put("status",false);
outputTree.put("error","Invalid Current Credentials");
}
}
catch(std::exception &e) {
BOOST_LOG(warning) << e.what();
outputTree.put("status", false);
outputTree.put("error", e.what());
return;
}
}
void start(std::shared_ptr<safe::signal_t> shutdown_event) {
auto ctx = std::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::tls);
ctx->use_certificate_chain_file(config::nvhttp.cert);
@@ -315,14 +371,16 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
http_server.resource["^/$"]["GET"] = getIndexPage;
http_server.resource["^/pin$"]["GET"] = getPinPage<SimpleWeb::HTTPS>;
http_server.resource["^/apps$"]["GET"] = getAppsPage<SimpleWeb::HTTPS>;
http_server.resource["^/clients$"]["GET"] = getClientsPage<SimpleWeb::HTTPS>;
http_server.resource["^/config$"]["GET"] = getConfigPage<SimpleWeb::HTTPS>;
http_server.resource["^/password$"]["GET"] = getPasswordPage<SimpleWeb::HTTPS>;
http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin<SimpleWeb::HTTPS>;
http_server.resource["^/api/apps$"]["GET"] = getApps;
http_server.resource["^/api/apps$"]["POST"] = saveApp;
http_server.resource["^/api/config$"]["GET"] = getConfig;
http_server.resource["^/api/config$"]["POST"] = saveConfig;
http_server.resource["^/api/password$"]["POST"] = savePassword;
http_server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp;
http_server.resource["^/clients$"]["GET"] = getClientsPage<SimpleWeb::HTTPS>;
http_server.resource["^/config$"]["GET"] = getConfigPage<SimpleWeb::HTTPS>;
http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin<SimpleWeb::HTTPS>;
http_server.config.reuse_address = true;
http_server.config.address = "0.0.0.0"s;
http_server.config.port = PORT_HTTP;

View File

@@ -4,6 +4,7 @@ void init(std::shared_ptr<safe::signal_t> shutdown_event);
int create_creds(const std::string &pkey, const std::string &cert);
std::string read_file(const char *path);
int write_file(const char *path, const std::string_view &contents);
int reload_user_creds(const std::string &file);
extern std::string unique_id;
extern net::net_e origin_pin_allowed;
} // namespace http