Merge pull request #146 from TheElixZammuto/web-ui-welcome
Show a Welcome Page if credentials are created the first time
This commit is contained in:
@@ -87,7 +87,8 @@ sunshine needs access to uinput to create mouse and gamepad events:
|
|||||||
- When Moonlight request you insert the correct pin on sunshine:
|
- When Moonlight request you insert the correct pin on sunshine:
|
||||||
- Type in the URL bar of your browser: `https://xxx.xxx.xxx.xxx:47990` where `xxx.xxx.xxx.xxx` is the IP address of your computer
|
- Type in the URL bar of your browser: `https://xxx.xxx.xxx.xxx:47990` where `xxx.xxx.xxx.xxx` is the IP address of your computer
|
||||||
- Ignore any warning given by your browser about "insecure website"
|
- Ignore any warning given by your browser about "insecure website"
|
||||||
- Type in the username and password shown the first time you run Sunshine
|
- You should compile the next page with a new username and a password, needed to login into the next step
|
||||||
|
- Press "Save" and log in using the credentials given above
|
||||||
- Go to "PIN" in the Header
|
- Go to "PIN" in the Header
|
||||||
- Type in your PIN and press Enter, you should get a Success Message
|
- Type in your PIN and press Enter, you should get a Success Message
|
||||||
- Click on one of the Applications listed
|
- Click on one of the Applications listed
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Sunshine</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
|
integrity="sha384-wEmeIV1mKuiNpC+IOBjI7aAzPcEZeedi5yW5f2yOq55WWLwNGmvvx4Um1vskeMj0" crossorigin="anonymous">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-p34f1UUtsS3wqzfto5wAAmdvj+osOnFyQFpp4Ua3gs/ZVWx6oOypYoCJhGGScy+8" crossorigin="anonymous">
|
||||||
|
</script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<main role="main" id="app" style="max-width: 600px;margin: 0 auto;">
|
||||||
|
<div class="container-parent">
|
||||||
|
<div class="container py-3">
|
||||||
|
<h1 class="mb-0">Welcome to Sunshine!</h1>
|
||||||
|
<p class="mb-0 align-self-start">Before Getting Started, write down below these credentials</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-warning">These Credentials down below are needed to access the rest of the application.<br> Keep them safe, since <b>you will never see them again!</b></div>
|
||||||
|
<form @submit.prevent="save" class="card p-4" style="width: 100%;">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="" class="form-label">Username: </label>
|
||||||
|
<input type="text" class="form-control" v-model="passwordData.newUsername">
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="" class="form-label">Password: </label>
|
||||||
|
<input type="password" class="form-control" v-model="passwordData.newPassword">
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="" class="form-label">Password: </label>
|
||||||
|
<input type="password" class="form-control" v-model="passwordData.confirmNewPassword">
|
||||||
|
</div>
|
||||||
|
<button class="mb-2 btn btn-primary" style="margin: 1em auto;">Login</button>
|
||||||
|
<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>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
error: null,
|
||||||
|
success: false,
|
||||||
|
passwordData: {
|
||||||
|
newUsername: 'sunshine',
|
||||||
|
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>
|
||||||
+48
-17
@@ -73,6 +73,15 @@ void send_unauthorized(resp_https_t response, req_https_t request) {
|
|||||||
response->write(SimpleWeb::StatusCode::client_error_unauthorized, headers);
|
response->write(SimpleWeb::StatusCode::client_error_unauthorized, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void send_redirect(resp_https_t response, req_https_t request, const char *path) {
|
||||||
|
auto address = request->remote_endpoint_address();
|
||||||
|
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv;
|
||||||
|
const SimpleWeb::CaseInsensitiveMultimap headers {
|
||||||
|
{ "Location", path }
|
||||||
|
};
|
||||||
|
response->write(SimpleWeb::StatusCode::redirection_temporary_redirect, headers);
|
||||||
|
}
|
||||||
|
|
||||||
bool authenticate(resp_https_t response, req_https_t request) {
|
bool authenticate(resp_https_t response, req_https_t request) {
|
||||||
auto address = request->remote_endpoint_address();
|
auto address = request->remote_endpoint_address();
|
||||||
auto ip_type = net::from_address(address);
|
auto ip_type = net::from_address(address);
|
||||||
@@ -83,6 +92,12 @@ bool authenticate(resp_https_t response, req_https_t request) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//If credentials are shown, redirect the user to a /welcome page
|
||||||
|
if(config::sunshine.username.empty()){
|
||||||
|
send_redirect(response,request,"/welcome");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
auto fg = util::fail_guard([&]() {
|
auto fg = util::fail_guard([&]() {
|
||||||
send_unauthorized(response, request);
|
send_unauthorized(response, request);
|
||||||
});
|
});
|
||||||
@@ -185,6 +200,17 @@ void getPasswordPage(resp_https_t response, req_https_t request) {
|
|||||||
response->write(header + content);
|
response->write(header + content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void getWelcomePage(resp_https_t response, req_https_t request) {
|
||||||
|
print_req(request);
|
||||||
|
if(!config::sunshine.username.empty()){
|
||||||
|
send_redirect(response,request,"/");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string header = read_file(WEB_DIR "header-no-nav.html");
|
||||||
|
std::string content = read_file(WEB_DIR "welcome.html");
|
||||||
|
response->write(header + content);
|
||||||
|
}
|
||||||
|
|
||||||
void getApps(resp_https_t response, req_https_t request) {
|
void getApps(resp_https_t response, req_https_t request) {
|
||||||
if(!authenticate(response, request)) return;
|
if(!authenticate(response, request)) return;
|
||||||
|
|
||||||
@@ -371,7 +397,7 @@ void saveConfig(resp_https_t response, req_https_t request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void savePassword(resp_https_t response, req_https_t request) {
|
void savePassword(resp_https_t response, req_https_t request) {
|
||||||
if(!authenticate(response, request)) return;
|
if(!config::sunshine.username.empty() && !authenticate(response, request)) return;
|
||||||
|
|
||||||
print_req(request);
|
print_req(request);
|
||||||
|
|
||||||
@@ -390,27 +416,31 @@ void savePassword(resp_https_t response, req_https_t request) {
|
|||||||
try {
|
try {
|
||||||
//TODO: Input Validation
|
//TODO: Input Validation
|
||||||
pt::read_json(ss, inputTree);
|
pt::read_json(ss, inputTree);
|
||||||
auto username = inputTree.get<std::string>("currentUsername");
|
auto username = inputTree.count("currentUsername") > 0 ? inputTree.get<std::string>("currentUsername") : "";
|
||||||
auto newUsername = inputTree.get<std::string>("newUsername");
|
auto newUsername = inputTree.get<std::string>("newUsername");
|
||||||
auto password = inputTree.get<std::string>("currentPassword");
|
auto password = inputTree.count("currentPassword") > 0 ? inputTree.get<std::string>("currentPassword") : "";
|
||||||
auto newPassword = inputTree.get<std::string>("newPassword");
|
auto newPassword = inputTree.get<std::string>("newPassword");
|
||||||
auto confirmPassword = inputTree.get<std::string>("confirmNewPassword");
|
auto confirmPassword = inputTree.get<std::string>("confirmNewPassword");
|
||||||
if(newUsername.length() == 0) newUsername = username;
|
if(newUsername.length() == 0) newUsername = username;
|
||||||
|
if(newUsername.length() == 0){
|
||||||
auto hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string();
|
|
||||||
if(username == config::sunshine.username && hash == config::sunshine.password) {
|
|
||||||
if(newPassword != confirmPassword) {
|
|
||||||
outputTree.put("status", false);
|
|
||||||
outputTree.put("error", "Password Mismatch");
|
|
||||||
}
|
|
||||||
|
|
||||||
http::save_user_creds(config::sunshine.credentials_file, newUsername, newPassword);
|
|
||||||
http::reload_user_creds(config::sunshine.credentials_file);
|
|
||||||
outputTree.put("status", true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
outputTree.put("status", false);
|
outputTree.put("status", false);
|
||||||
outputTree.put("error", "Invalid Current Credentials");
|
outputTree.put("error", "Invalid Username");
|
||||||
|
} else {
|
||||||
|
auto hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string();
|
||||||
|
if(config::sunshine.username.empty() || (username == config::sunshine.username && hash == config::sunshine.password)) {
|
||||||
|
if(newPassword != confirmPassword) {
|
||||||
|
outputTree.put("status", false);
|
||||||
|
outputTree.put("error", "Password Mismatch");
|
||||||
|
} else {
|
||||||
|
http::save_user_creds(config::sunshine.credentials_file, newUsername, newPassword);
|
||||||
|
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) {
|
catch(std::exception &e) {
|
||||||
@@ -467,6 +497,7 @@ void start() {
|
|||||||
server.resource["^/clients$"]["GET"] = getClientsPage;
|
server.resource["^/clients$"]["GET"] = getClientsPage;
|
||||||
server.resource["^/config$"]["GET"] = getConfigPage;
|
server.resource["^/config$"]["GET"] = getConfigPage;
|
||||||
server.resource["^/password$"]["GET"] = getPasswordPage;
|
server.resource["^/password$"]["GET"] = getPasswordPage;
|
||||||
|
server.resource["^/welcome$"]["GET"] = getWelcomePage;
|
||||||
server.resource["^/api/pin"]["POST"] = savePin;
|
server.resource["^/api/pin"]["POST"] = savePin;
|
||||||
server.resource["^/api/apps$"]["GET"] = getApps;
|
server.resource["^/api/apps$"]["GET"] = getApps;
|
||||||
server.resource["^/api/apps$"]["POST"] = saveApp;
|
server.resource["^/api/apps$"]["POST"] = saveApp;
|
||||||
|
|||||||
+4
-14
@@ -54,15 +54,11 @@ int init() {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!user_creds_exist(config::sunshine.credentials_file)) {
|
if(user_creds_exist(config::sunshine.credentials_file)) {
|
||||||
if(save_user_creds(config::sunshine.credentials_file, "sunshine"s, crypto::rand_alphabet(16), true)) {
|
if(reload_user_creds(config::sunshine.credentials_file)) return -1;
|
||||||
return -1;
|
} else {
|
||||||
}
|
BOOST_LOG(info) << "Open the Web UI to set your new username and password and getting started";
|
||||||
}
|
}
|
||||||
if(reload_user_creds(config::sunshine.credentials_file)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,12 +88,6 @@ int save_user_creds(const std::string &file, const std::string &username, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
BOOST_LOG(info) << "New credentials have been created"sv;
|
BOOST_LOG(info) << "New credentials have been created"sv;
|
||||||
|
|
||||||
if(run_our_mouth) {
|
|
||||||
BOOST_LOG(info) << "Username: "sv << username;
|
|
||||||
BOOST_LOG(info) << "Password: "sv << password;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user