Avoid CORS in login, add token validation

This commit is contained in:
Midgard 2020-03-25 18:46:28 +01:00
parent fcbb561d62
commit 71290cdc02
Signed by: midgard
GPG key ID: 511C112F1331BBB4
3 changed files with 61 additions and 17 deletions

View file

@ -12,7 +12,8 @@
<tr><th>Username</th><td><input type="text" id="username"/></td></tr> <tr><th>Username</th><td><input type="text" id="username"/></td></tr>
<tr><th>Password</th><td><input type="password" id="password"/></td></tr> <tr><th>Password</th><td><input type="password" id="password"/></td></tr>
</table> </table>
<input type="submit" onclick="this.disabled = true; this.value = 'Logging in...'; logIn(); return false" value="Log in"/> <input type="button" onclick="this.disabled = true; this.value = 'Validating token'; validateToken(); return false" id="validate" value="Validate token"/> </input>(ignores username and password) <br/>
<input type="button" onclick="this.disabled = true; this.value = 'Logging in...'; logIn(); return false" value="Log in"/>
</form> </form>
<script type="text/javascript" src="xhr.js"></script> <script type="text/javascript" src="xhr.js"></script>
<script type="text/javascript" src="main.js"></script> <script type="text/javascript" src="main.js"></script>

61
main.js
View file

@ -1,5 +1,7 @@
"use strict"; "use strict";
const LOCALSTORAGE_KEY_SERVERS = "mattermostServers";
function byId(id, nullOk=false) { function byId(id, nullOk=false) {
const el = document.getElementById(id); const el = document.getElementById(id);
if (!el && !nullOk) { if (!el && !nullOk) {
@ -8,6 +10,18 @@ function byId(id, nullOk=false) {
return el; return el;
} }
function storeCredentials(endpoint, login_id, token) {
let storedServers = JSON.parse(window.localStorage.getItem(LOCALSTORAGE_KEY_SERVERS) || "[]");
if (!(endpoint in storedServers)) storedServers.push(endpoint);
window.localStorage.setItem(LOCALSTORAGE_KEY_SERVERS, JSON.stringify(storedServers));
window.localStorage.setItem(`${LOCALSTORAGE_KEY_SERVERS}_${endpoint}`, JSON.stringify({login_id, token}));
}
function getCredentials(endpoint) {
return JSON.parse(window.localStorage.getItem(`${LOCALSTORAGE_KEY_SERVERS}_${endpoint}`) || "null");
}
class MattermostApi { class MattermostApi {
constructor(endpoint) { constructor(endpoint) {
this.endpoint = endpoint; this.endpoint = endpoint;
@ -19,17 +33,17 @@ class MattermostApi {
"Token": `Authorization: Bearer ${token}` "Token": `Authorization: Bearer ${token}`
} }
}); });
return response.ok; if (!response.ok) {
throw response;
}
return response;
} }
logIn(username, password) { logIn(login_id, password) {
return ajax.postJson(`${this.endpoint}/users/login`, { return ajax.postJson(`${this.endpoint}/users/login`, {login_id, password})
"login_id": username,
"password": password
})
.then(response => { .then(response => {
let token = response.getHeader("Token"); let token = response.getHeader("Token");
window.localStorage.setItem("token", token); storeCredentials(this.endpoint, login_id, token);
return response; return response;
}) })
.then(response => { .then(response => {
@ -47,11 +61,38 @@ class MattermostApi {
} }
} }
/**
* Return an endpoint URL that has a protocol, domain and path
*/
function normalizedEndpoint(endpoint) {
let matches = endpoint.match(/^(https?:\/\/)?([^\/]+)(\/.*)?$/i);
if (!matches) throw Error("Invalid endpoint URL");
let protocol = matches[1] || "https://";
let domain = matches[2];
let path = matches[3] || "/api/v4";
return `${protocol}${domain}${path}`;
}
function logIn() { function logIn() {
let endpoint = byId("server").value; let endpoint = normalizedEndpoint(byId("server").value);
if (!endpoint.endsWith("/")) endpoint += "/";
endpoint += "api/v4";
let api = new MattermostApi(endpoint); let api = new MattermostApi(endpoint);
api.logIn(byId("username").value, byId("password").value); api.logIn(byId("username").value, byId("password").value);
} }
function validateToken() {
let endpoint = normalizedEndpoint(byId("server").value);
let api = new MattermostApi(endpoint);
api.validateToken(getCredentials(endpoint))
.then(() => {
byId("validate").value = "Validation succeeded";
byId("validate").disabled = false;
})
.catch(() => {
byId("validate").value = "Validation failed";
byId("validate").disabled = false;
});
}

14
xhr.js
View file

@ -33,20 +33,21 @@ function xhrInitForPromise(resolve, reject, url, method, headers) {
} }
function xhrParseJsonResponse(xhr) { function xhrParseJsonResponse(xhr) {
if (xhr.status == 0) { if (xhr.status === 0) {
console.error(xhr); console.error(xhr);
throw new NetworkError("Failed to connect to server"); throw new NetworkError("Failed to connect to server");
} }
let json;
if (!xhr.responseText) { if (!xhr.responseText) {
const json = null; json = null;
} else { } else {
const contentType = xhr.getResponseHeader("Content-Type"); const contentType = xhr.getResponseHeader("Content-Type");
if (contentType != MIME_JSON) { if (contentType != MIME_JSON) {
throw new UnexpectedMimeError(`Server did not reply with JSON but with ${contentType}`); throw new UnexpectedMimeError(`Server did not reply with JSON but with ${contentType}`);
} }
try { try {
const json = JSON.parse(xhr.responseText); json = JSON.parse(xhr.responseText);
} catch(e) { } catch(e) {
throw new InvalidJsonError(); throw new InvalidJsonError();
} }
@ -56,7 +57,7 @@ function xhrParseJsonResponse(xhr) {
ok: 200 <= xhr.status && xhr.status < 300, ok: 200 <= xhr.status && xhr.status < 300,
status: xhr.status, status: xhr.status,
statusText: xhr.statusText, statusText: xhr.statusText,
getHeader: xhr.getResponseHeader, getHeader: header => xhr.getResponseHeader(header),
json, json,
xhr, xhr,
}; };
@ -74,8 +75,9 @@ function getJson(url, options={}) {
function postJson(url, data={}, options={}) { function postJson(url, data={}, options={}) {
if (!options.headers) options.headers = {}; if (!options.headers) options.headers = {};
options.headers["Content-Type"] = MIME_JSON; // This triggers CORS, which is not acceptable
options.headers["Accept"] = MIME_JSON; //options.headers["Content-Type"] = MIME_JSON;
//options.headers["Accept"] = MIME_JSON;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let xhr = xhrInitForPromise(resolve, reject, url, "POST", options.headers); let xhr = xhrInitForPromise(resolve, reject, url, "POST", options.headers);