Make Client remember its token, add ID form checks
Refactor MattermostClient, deduplicating code and making it remember its token. Make functions that ask for a Mattermost ID check that they get something of the correct form.
This commit is contained in:
parent
88876414bc
commit
596cd63fb5
6 changed files with 80 additions and 48 deletions
|
@ -64,7 +64,7 @@
|
||||||
|
|
||||||
<script type="text/javascript" src="/js/ajax.js"></script>
|
<script type="text/javascript" src="/js/ajax.js"></script>
|
||||||
<script type="text/javascript" src="/js/util.js"></script>
|
<script type="text/javascript" src="/js/util.js"></script>
|
||||||
<script type="text/javascript" src="/js/model/credentials.js"></script>
|
<script type="text/javascript" src="/js/model/localstorage_credentials.js"></script>
|
||||||
<script type="text/javascript" src="/js/model/mm_client.js"></script>
|
<script type="text/javascript" src="/js/model/mm_client.js"></script>
|
||||||
<script type="text/javascript" src="/js/view/view.js"></script>
|
<script type="text/javascript" src="/js/view/view.js"></script>
|
||||||
<script type="text/javascript" src="/js/controller/controller.js"></script>
|
<script type="text/javascript" src="/js/controller/controller.js"></script>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
function createClient(endpoint) {
|
function createClient(endpoint) {
|
||||||
const api = new mm_client.MattermostApi(normalizedEndpoint(endpoint));
|
const api = new mm_client.MattermostApi(normalizedEndpoint(endpoint));
|
||||||
return new mm_client.MattermostClient(api);
|
return new mm_client.MattermostClient(api, localstorage_credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buttonDisable(element, text) {
|
function buttonDisable(element, text) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const credentials = (function() { "use strict";
|
const localstorage_credentials = (function() { "use strict";
|
||||||
|
|
||||||
const LOCALSTORAGE_KEY_SERVER = "mattermostServer";
|
const LOCALSTORAGE_KEY_SERVER = "mattermostServer";
|
||||||
const RE_SERVER_ITEM = new RegExp(`^${LOCALSTORAGE_KEY_SERVER}_(.*)\$`, "");
|
const RE_SERVER_ITEM = new RegExp(`^${LOCALSTORAGE_KEY_SERVER}_(.*)\$`, "");
|
|
@ -31,74 +31,84 @@ class MattermostApi {
|
||||||
|
|
||||||
|
|
||||||
class MattermostClient {
|
class MattermostClient {
|
||||||
constructor (api) {
|
constructor (api, credentials_provider) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
|
this.credentials = credentials_provider;
|
||||||
|
const creds = this.credentials.get(this.api.id);
|
||||||
|
this.token = creds ? creds.token : null;
|
||||||
|
console.info(`Created MattermostClient for ${this.api.id}, ${this.token ? "found token" : "did not find token"}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async authenticatedGet(url, queryParams) {
|
||||||
|
assert(this.token, "logged in");
|
||||||
|
const response = await this.api.get(url, this.token, queryParams);
|
||||||
|
return response.responseJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
async loggedIn() {
|
||||||
|
if (!this.token) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const meResponse = await this.userMe();
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof ajax.NotOkError && e.xhr.status == 401) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async logIn(login_id, password) {
|
async logIn(login_id, password) {
|
||||||
|
if (this.token && await this.tokenWorks()) {
|
||||||
|
throw Error("Already logged in on this server");
|
||||||
|
}
|
||||||
|
|
||||||
const response = await this.api.post("/users/login", undefined, {login_id, password});
|
const response = await this.api.post("/users/login", undefined, {login_id, password});
|
||||||
const token = response.getResponseHeader("Token");
|
const token = response.getResponseHeader("Token");
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw Error("No Token header in response to log in request");
|
throw Error("No Token header in response to log in request");
|
||||||
}
|
}
|
||||||
credentials.store(this.api.id, login_id, token);
|
this.credentials.store(this.api.id, login_id, token);
|
||||||
|
this.token = token;
|
||||||
return response.responseJson;
|
return response.responseJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
async logOut() {
|
async logOut() {
|
||||||
const stored = credentials.get(this.api.id);
|
assert(this.token, "logged in");
|
||||||
if (!stored || !stored.token) {
|
const response = await this.api.post("/users/logout", this.token);
|
||||||
throw Error("No token stored");
|
|
||||||
}
|
|
||||||
const response = await this.api.post("/users/logout", stored.token);
|
|
||||||
|
|
||||||
// Verify that the token is now invalidated
|
// Verify that the token is now invalidated
|
||||||
let tokenWorks;
|
if (await loggedIn()) {
|
||||||
try {
|
|
||||||
const meResponse = await this.usersMe();
|
|
||||||
tokenWorks = true;
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof ajax.NotOkError && e.xhr.status == 401) {
|
|
||||||
tokenWorks = false;
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (tokenWorks) {
|
|
||||||
throw new Error("Failed to log out: token still works after trying to log out");
|
throw new Error("Failed to log out: token still works after trying to log out");
|
||||||
}
|
}
|
||||||
|
|
||||||
credentials.clear(this.api.id);
|
this.credentials.clear(this.api.id);
|
||||||
|
this.token = null;
|
||||||
return response.responseJson;
|
return response.responseJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
async usersMe() {
|
user(user_id) {
|
||||||
const stored = credentials.get(this.api.id);
|
assertIsMattermostId(user_id);
|
||||||
if (!stored || !stored.token) {
|
return this.authenticatedGet(`/users/${user_id}`);
|
||||||
throw Error("No token stored");
|
}
|
||||||
}
|
userMe() { return this.authenticatedGet("/users/me"); }
|
||||||
const response = await this.api.get("/users/me", stored.token);
|
myTeams() { return this.authenticatedGet("/users/me/teams"); }
|
||||||
return response.responseJson;
|
|
||||||
|
myChannels(team_id) {
|
||||||
|
assertIsMattermostId(team_id);
|
||||||
|
return this.authenticatedGet(`/users/me/teams/${team_id}/channels`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async myTeams() {
|
channelPosts(channel_id, beforePost=null, afterPost=null, since=null) {
|
||||||
const stored = credentials.get(this.api.id);
|
assertIsMattermostId(channel_id);
|
||||||
const response = await this.api.get("/users/me/teams", stored.token);
|
assertIsNullOrMattermostId(beforePost);
|
||||||
return response.responseJson;
|
assertIsNullOrMattermostId(afterPost);
|
||||||
}
|
return this.authenticatedGet(`/channels/${channel_id}/posts`, {
|
||||||
|
|
||||||
async myChannels(team_id) {
|
|
||||||
const stored = credentials.get(this.api.id);
|
|
||||||
const response = await this.api.get(`/users/me/teams/${team_id}/channels`, stored.token);
|
|
||||||
return response.responseJson;
|
|
||||||
}
|
|
||||||
|
|
||||||
async channelPosts(channel_id, beforePost=null, afterPost=null, since=null) {
|
|
||||||
const stored = credentials.get(this.api.id);
|
|
||||||
const response = await this.api.get(`/channels/${channel_id}/posts`, stored.token, {
|
|
||||||
before: beforePost, after: afterPost, since
|
before: beforePost, after: afterPost, since
|
||||||
});
|
});
|
||||||
return response.responseJson;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
22
js/util.js
22
js/util.js
|
@ -44,3 +44,25 @@ function thisToArg(f) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AssertionError extends Error {}
|
||||||
|
/**
|
||||||
|
* Throw an AssertionError if the first argument is not true
|
||||||
|
*/
|
||||||
|
function assert(condition, message) {
|
||||||
|
if (!condition) {
|
||||||
|
if (message) {
|
||||||
|
throw new AssertionError(`Assertion failed: ${message}`);
|
||||||
|
} else {
|
||||||
|
throw new AssertionError("Assertion failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MATTERMOST_ID_REGEXP = /^[a-z0-9]{26}$/;
|
||||||
|
function assertIsMattermostId(string, name="") {
|
||||||
|
assert(MATTERMOST_ID_REGEXP.test(string), `${name} has the form of a Mattermost ID`);
|
||||||
|
}
|
||||||
|
function assertIsNullOrMattermostId(string, name="") {
|
||||||
|
assert(string === null || MATTERMOST_ID_REGEXP.test(string), `${name} is null or has the form of a Mattermost ID`);
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
function populateServerSelectionList() {
|
function populateServerSelectionList() {
|
||||||
const servers = credentials.getServers();
|
const servers = localstorage_credentials.getServers();
|
||||||
|
|
||||||
let nodes = [];
|
let nodes = [];
|
||||||
for (let server of servers) {
|
for (let server of servers) {
|
||||||
|
@ -49,7 +49,7 @@ function populateChannelList() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const servers = credentials.getServers();
|
const servers = localstorage_credentials.getServers();
|
||||||
|
|
||||||
byId("channel_list").innerHTML = "";
|
byId("channel_list").innerHTML = "";
|
||||||
for (let server of servers) {
|
for (let server of servers) {
|
||||||
|
|
Loading…
Reference in a new issue