Avoid CORS in login, add token validation
This commit is contained in:
parent
fcbb561d62
commit
71290cdc02
3 changed files with 61 additions and 17 deletions
|
@ -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
61
main.js
|
@ -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
14
xhr.js
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue