test: implement JWT Authorization for test client

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Arksine
2021-04-15 20:33:42 -04:00
committed by Arksine
parent 0c765f7b71
commit a4273d2d0a
5 changed files with 554 additions and 34 deletions

View File

@@ -120,6 +120,12 @@ var api = {
url: "/server/files/config_examples/"
},
// Server APIs
server_info: {
url: "/server/info",
method: "server.info"
},
// Machine APIs
reboot: {
url: "/machine/reboot",
@@ -136,11 +142,28 @@ var api = {
},
oneshot_token: {
url: "/access/oneshot_token"
},
login: {
url: "/access/login"
},
logout: {
url: "/access/logout"
},
refresh_jwt: {
url: "/access/refresh_jwt"
},
user: {
url: "/access/user"
},
reset_password: {
url: "/access/user/password"
}
}
var websocket = null;
var apikey = null;
var auth_token = null;
var refresh_token = window.localStorage.getItem('refresh_token');
var paused = false;
var klippy_ready = false;
var api_type = 'http';
@@ -859,6 +882,17 @@ function run_request(url, method, callback=null)
let settings = {
url: url,
method: method,
statusCode: {
401: function() {
if (refresh_token != null) {
refresh_json_web_token(run_request, url, method, callback);
}
else {
auth_token = null;
$("#do_login").click();
}
}
},
success: (resp, status) => {
console.log(resp);
if (callback != null)
@@ -872,7 +906,9 @@ function run_request(url, method, callback=null)
settings.contentType = false,
settings.processData = false
}
if (apikey != null)
if (auth_token != null)
settings.headers = {"Authorization": `Bearer ${auth_token}`};
else if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.ajax(settings);
}
@@ -894,9 +930,9 @@ function form_delete_request(api_url, query_string="", callback=null) {
function form_download_request(uri) {
let dl_url = origin + uri;
if (apikey != null) {
form_get_request(api.oneshot_token.url,
callback=(resp) => {
if (apikey != null || auth_token != null) {
form_get_request(api.oneshot_token.url, "",
(resp) => {
let token = resp.result;
dl_url += "?token=" + token;
do_download(dl_url);
@@ -961,7 +997,9 @@ function jstree_populate_children(node, callback) {
if (api_type == "http") {
let qs = `?path=${node.id}`;
let settings = {url: origin + api.directory.url + qs};
if (apikey != null)
if (auth_token != null)
settings.headers = {"Authorization": `Bearer ${auth_token}`};
else if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.get(settings, (resp, status) => {
callback(generate_children(resp.result, node));
@@ -1130,6 +1168,7 @@ class KlippyWebsocket {
this.onmessage = null;
this.onopen = null;
this.id = null;
this.reconnect = true
this.connect();
}
@@ -1138,14 +1177,15 @@ class KlippyWebsocket {
// to reconnect if its closed. This is nice as it allows the
// client to easily recover from Klippy restarts without user
// intervention
if (apikey != null) {
if (apikey != null || auth_token != null) {
// Fetch a oneshot token to pass websocket authorization
let token_settings = {
url: origin + api.oneshot_token.url,
headers: {
"X-Api-Key": apikey
}
}
if (auth_token != null)
token_settings.headers = {"Authorization": `Bearer ${auth_token}`};
else
token_settings.headers = {"X-Api-Key": apikey};
$.get(token_settings, (data, status) => {
let token = data.result;
let url = this.base_address + "/websocket?token=" + token;
@@ -1172,10 +1212,14 @@ class KlippyWebsocket {
klippy_ready = false;
this.connected = false;
this.id = null;
console.log("Websocket Closed, reconnecting in 1s: ", e.reason);
setTimeout(() => {
this.connect();
}, 1000);
// TODO: Need to cancel any pending JSON-RPC requests
if (this.reconnect) {
console.log("Websocket Closed, reconnecting in 1s: ", e.reason);
setTimeout(() => {
if (this.reconnect)
this.connect();
}, 1000);
}
};
this.ws.onerror = (err) => {
@@ -1202,7 +1246,7 @@ class KlippyWebsocket {
}
close() {
// TODO: Cancel the timeout
this.reconnect = false
this.ws.close();
}
@@ -1227,25 +1271,172 @@ function create_websocket(url) {
json_rpc.register_transport(websocket);
}
function login_jwt_user(user, pass, do_create) {
if (!user || !pass) {
alert("Invalid username/password")
return;
}
let close_btn_name = "#login_close"
if (do_create)
close_btn_name = "#signup_close"
let settings = {
url: origin + api.login.url,
data: JSON.stringify({username: user, password: pass}),
contentType: "application/json",
dataType: 'json'
}
if (do_create) {
settings.url = origin + api.user.url;
if (auth_token != null)
settings.headers = {"Authorization": `Bearer ${auth_token}`};
else if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
}
$.post(settings, (resp, status) => {
let res = resp.result;
console.log("Login Response:");
console.log(res);
auth_token = res.token;
refresh_token = res.refresh_token;
window.localStorage.setItem('refresh_token', refresh_token);
$('.req-login').prop('disabled', false);
$(close_btn_name).click();
check_authorization();
}).fail(() => {
console.log("Login Failed");
alert("Login Failed")
});
}
function logout_jwt_user() {
if (auth_token == null) {
console.log("No User Logged In")
return;
}
let settings = {
url: origin + api.logout.url,
contentType: "application/json",
dataType: 'json',
headers: {
"Authorization": `Bearer ${auth_token}`
}
}
$.post(settings, (resp, status) => {
let res = resp.result;
console.log("Logout Response:");
console.log(res);
auth_token = null;
refresh_token = null;
window.localStorage.removeItem('refresh_token');
$('.req-login').prop('disabled', true);
}).fail(() => {
console.log("Logout User Failed");
});
}
function delete_jwt_user(pass) {
if (!pass) {
alert("Invalid Password, Cannot Delete User");
return;
}
if (auth_token == null) {
console.log("No User Logged In")
return;
}
let settings = {
method: 'DELETE',
url: origin + api.user.url,
contentType: "application/json",
data: JSON.stringify({password: pass}),
dataType: 'json',
headers: {
"Authorization": `Bearer ${auth_token}`
},
success: (resp, status) => {
let res = resp.result;
console.log("Delete User Response:");
console.log(res);
auth_token = null;
refresh_token = null;
window.localStorage.removeItem('refresh_token');
$('.req-login').prop('disabled', true);
}
}
$.ajax(settings)
.fail(() => {
console.log("Delete User Failed");
});
}
function change_jwt_password(old_pass, new_pass) {
if (!old_pass || !new_pass) {
alert("Invalid input for change password")
return;
}
let settings = {
url: origin + api.reset_password.url,
data: JSON.stringify({password: old_pass, new_password: new_pass}),
contentType: "application/json",
dataType: 'json',
headers: {
"Authorization": `Bearer ${auth_token}`
}
}
$.post(settings, (resp, status) => {
let res = resp.result;
console.log("Change Password Response:");
console.log(res);
$("#changepass_close").click();
}).fail(() => {
console.log("Failed to change password");
alert("Password Reset Failed")
});
}
function refresh_json_web_token(callback, ...args) {
let settings = {
url: origin + api.refresh_jwt.url,
data: JSON.stringify({refresh_token: refresh_token}),
contentType: "application/json",
dataType: 'json',
}
$.post(settings, (resp, status) => {
let res = resp.result;
console.log("Refresh JWT Response:");
console.log(res);
auth_token = res.token;
$('.req-login').prop('disabled', false);
if (callback != null)
callback(...args);
}).fail(() => {
console.log("Refresh JWT Failed");
auth_token = null;
refresh_token = null;
window.localStorage.removeItem('refresh_token');
$('.req-login').prop('disabled', true);
$("#do_login").click();
});
}
function check_authorization() {
// send a HTTP "run gcode" command
let settings = {
url: origin + api.printer_info.url,
url: origin + api.server_info.url,
statusCode: {
401: function() {
// Show APIKey Popup
let result = window.prompt("Enter a valid API Key:", "");
if (result == null || result.length != 32) {
console.log("Invalid API Key: " + result);
apikey = null;
} else {
apikey = result;
}
check_authorization();
if (refresh_token != null) {
refresh_json_web_token(check_authorization);
} else {
$("#do_login").click();
}
}
}
}
if (apikey != null)
if (auth_token != null)
settings.headers = {"Authorization": `Bearer ${auth_token}`};
else if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.get(settings, (data, status) => {
// Create a websocket if /printer/info successfully returns
@@ -1261,11 +1452,9 @@ function do_download(url) {
window.onload = () => {
// Handle changes between the HTTP and Websocket API
$('.reqws').prop('disabled', true);
$('.req-login').prop('disabled', true);
$('input[type=radio][name=test_type]').on('change', function() {
api_type = $(this).val();
let disable_transfer = (!$('#cbxFileTransfer').is(":checked") && is_printing);
$('.toggleable').prop(
'disabled', (api_type == 'websocket' || disable_transfer));
$('.reqws').prop('disabled', (api_type == 'http'));
$('#apimethod').prop('hidden', (api_type == "websocket"));
$('#apiargs').prop('hidden', (api_type == "http"));
@@ -1467,7 +1656,9 @@ window.onload = () => {
let sendtype = $("input[type=radio][name=api_cmd_type]:checked").val();
let url = $('#apirequest').val();
let settings = {url: url}
if (apikey != null)
if (auth_token != null)
settings.headers = {"Authorization": `Bearer ${auth_token}`};
else if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
if (sendtype == "get") {
console.log("Sending GET " + url);
@@ -1553,7 +1744,9 @@ window.onload = () => {
return false;
}
};
if (apikey != null)
if (auth_token != null)
settings.headers = {"Authorization": `Bearer ${auth_token}`};
else if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.ajax(settings);
$('#upload-file').val('');
@@ -1645,11 +1838,13 @@ window.onload = () => {
$('#btntestmesh').click(() => {
if (api_type == 'http') {
let settings = {url: origin + api.object_status.url + "?bed_mesh"};
if (apikey != null)
if (auth_token != null)
settings.headers = {"Authorization": `Bearer ${auth_token}`};
else if (apikey != null)
settings.headers = {"X-Api-Key": apikey};
$.get(settings, (resp, status) => {
process_mesh(resp.result)
return false;
process_mesh(resp.result)
return false;
});
} else {
get_mesh();
@@ -1726,5 +1921,108 @@ window.onload = () => {
form_download_request(api.moonraker_log.url);
});
$('#btnloginuser').click(() => {
$("#do_login").click();
});
$('#btncreateuser').click(() => {
$("#do_signup").click();
});
$('#btnlogout').click(() => {
logout_jwt_user();
});
$('#btndeluser').click(() => {
let password = window.prompt("Verify your password:")
delete_jwt_user(password);
});
$('#btnchangepass').click(() => {
$("#do_changepass").click();
});
$('#btnsetapikey').click(() => {
let defkey = apikey;
if (!defkey)
defkey = ""
let new_key = window.prompt("Enter your API Key", defkey);
if (!new_key)
apikey = null;
else
apikey = new_key;
check_authorization();
});
$("#do_login").leanModal({
top : 200,
overlay : 0.4,
closeButton: "#login_close"
});
$("#do_signup").leanModal({
top : 200,
overlay : 0.4,
closeButton: "#signup_close"
});
$("#do_changepass").leanModal({
top : 200,
overlay : 0.4,
closeButton: "#changepass_close"
});
$("#login_close").click(() => {
//$("#login_username").val("");
$("#login_password").val("");
$("#nav_home").click();
});
$("#signup_close").click(() => {
$("#signup_username").val("");
$("#signup_password").val("");
$("#signup_verify_pass").val("");
$("#nav_home").click();
});
$("#changepass_close").click(() => {
$("#changepass_oldpass").val("");
$("#changepass_newpass").val("");
$("#changepass_verify_pass").val("");
$("#nav_home").click();
});
$("#login_form").submit((evt)=> {
let user = $("#login_username").val()
let pass = $("#login_password").val()
if (user != "" && pass != "")
login_jwt_user(user, pass, false);
else
alert("Invalid username/password");
return false;
});
$("#signup_form").submit((evt)=> {
let user = $("#signup_username").val()
let pass = $("#signup_password").val()
let verify_pass = $("#signup_verify_pass").val()
if (user != "" && pass != "" && pass == verify_pass)
login_jwt_user(user, pass, true);
else
alert("Invalid username/password");
return false;
});
$("#changepass_form").submit((evt)=> {
let old_pass = $("#changepass_oldpass").val()
let new_pass = $("#changepass_newpass").val()
let verify_pass = $("#changepass_verify_pass").val()
if (old_pass != "" && new_pass != "" && new_pass == verify_pass)
change_jwt_password(old_pass, new_pass)
else
alert("All fields are required to change password");
return false;
});
check_authorization();
};