Merge pull request #1536 from thelounge/xpaw/session-list

Implement session list and allow signing out other clients
This commit is contained in:
Pavel Djundik 2017-09-26 11:03:41 +03:00 committed by GitHub
commit 3fc0f4e686
10 changed files with 131 additions and 25 deletions

View File

@ -252,6 +252,14 @@ kbd {
color: #7f8c8d;
}
.session-list strong {
display: block;
}
.session-list p {
margin-bottom: 10px;
}
#chat .invite .from::before {
content: "\f003"; /* http://fontawesome.io/icon/envelope-o/ */
color: #2ecc40;
@ -668,6 +676,7 @@ kbd {
width: 100%;
}
#windows p,
#windows label,
#settings .error {
font-size: 14px;

View File

@ -198,15 +198,14 @@
</div>
</form>
</div>
<div id="settings" class="window">
<div id="settings" class="window" data-type="settings">
<div class="header">
<button class="lt" aria-label="Toggle channel list"></button>
</div>
<div class="container">
<h1 class="title">Settings</h1>
<div class="row">
<div class="col-sm-12">
<h1 class="title">Settings</h1>
</div>
<div class="col-sm-12">
<h2>Messages</h2>
</div>
@ -380,6 +379,18 @@
<textarea class="input" name="userStyles" id="user-specified-css-input" placeholder="You can override any style with CSS here"></textarea>
</div>
</div>
{{#unless public}}
<div class="session-list">
<h2>Sessions</h2>
<h3>Current session</h3>
<div id="session-current"></div>
<h3>Other sessions</h3>
<div id="session-list"></div>
</div>
{{/unless}}
</div>
</div>

View File

@ -383,8 +383,9 @@ $(function() {
}
document.title = title;
const type = chan.data("type");
var placeholder = "";
if (chan.data("type") === "channel" || chan.data("type") === "query") {
if (type === "channel" || type === "query") {
placeholder = `Write to ${chan.data("title")}`;
}
input.attr("placeholder", placeholder);
@ -404,6 +405,11 @@ $(function() {
socket.emit("names", {target: self.data("id")});
}
if (type === "settings") {
$("#session-list").html("<p>Loading…</p>");
socket.emit("sessions:get");
}
focus();
});

View File

@ -17,3 +17,4 @@ require("./sync_sort");
require("./topic");
require("./users");
require("./sign_out");
require("./sessions_list");

View File

@ -0,0 +1,31 @@
"use strict";
const $ = require("jquery");
const socket = require("../socket");
const templates = require("../../views");
socket.on("sessions:list", function(data) {
data.sort((a, b) => b.lastUse - a.lastUse);
let html = "";
data.forEach((connection) => {
if (connection.current) {
$("#session-current").html(templates.session(connection));
return;
}
html += templates.session(connection);
});
if (html.length === 0) {
html = "<p><em>You are not currently logged in to any other device.</em></p>";
}
$("#session-list").html(html);
});
$("#settings").on("click", ".remove-session", function() {
socket.emit("sign-out", $(this).data("token"));
return false;
});

View File

@ -34,6 +34,7 @@ module.exports = {
msg_unhandled: require("./msg_unhandled.tpl"),
network: require("./network.tpl"),
image_viewer: require("./image_viewer.tpl"),
session: require("./session.tpl"),
unread_marker: require("./unread_marker.tpl"),
user: require("./user.tpl"),
user_filtered: require("./user_filtered.tpl"),

17
client/views/session.tpl Normal file
View File

@ -0,0 +1,17 @@
<p>
{{#if current}}
<strong>{{agent}}</strong>
<a href="https://ipinfo.io/{{ip}}" target="_blank" rel="noopener">{{ip}}</a>
{{else}}
<button class="btn pull-right remove-session" data-token="{{token}}">Disconnect</button>
<strong>{{agent}}</strong>
<a href="https://ipinfo.io/{{ip}}" target="_blank" rel="noopener">{{ip}}</a>
<br>
{{#if active}}
<em>Currently active</em>
{{else}}
Last used on <time>{{localetime lastUse}}</time>
{{/if}}
{{/if}}
</p>

View File

@ -304,11 +304,15 @@ Client.prototype.updateSession = function(token, ip, request) {
friendlyAgent += ` on ${agent.os.name} ${agent.os.version}`;
}
client.config.sessions[token] = _.assign({
client.config.sessions[token] = _.assign(client.config.sessions[token], {
lastUse: Date.now(),
ip: ip,
agent: friendlyAgent,
}, client.config.sessions[token]);
});
client.manager.updateUser(client.name, {
sessions: client.config.sessions
});
};
Client.prototype.setPassword = function(hash, callback) {
@ -318,7 +322,6 @@ Client.prototype.setPassword = function(hash, callback) {
password: hash
}, function(err) {
if (err) {
log.error("Failed to update password of", client.name, err);
return callback(false);
}

View File

@ -153,7 +153,7 @@ ClientManager.prototype.updateUser = function(name, opts, callback) {
fs.writeFile(Helper.getUserConfigPath(name), newUser, (err) => {
if (err) {
log.error("Failed to update user", err);
log.error(`Failed to update user ${colors.green(name)} (${err})`);
}
if (callback) {

View File

@ -237,7 +237,6 @@ function initializeClient(socket, client, token, lastMessage) {
socket.on("disconnect", function() {
client.clientDetach(socket.id);
});
client.clientAttach(socket.id, token);
socket.on(
"input",
@ -378,23 +377,59 @@ function initializeClient(socket, client, token, lastMessage) {
client.unregisterPushSubscription(token);
});
socket.on("sign-out", () => {
delete client.config.sessions[token];
const sendSessionList = () => {
const sessions = _.map(client.config.sessions, (session, sessionToken) => ({
current: sessionToken === token,
active: _.find(client.attachedClients, (u) => u.token === sessionToken) !== undefined,
lastUse: session.lastUse,
ip: session.ip,
agent: session.agent,
token: sessionToken, // TODO: Ideally don't expose actual tokens to the client
}));
socket.emit("sessions:list", sessions);
};
socket.on("sessions:get", sendSessionList);
socket.on("sign-out", (tokenToSignOut) => {
// If no token provided, sign same client out
if (!tokenToSignOut) {
tokenToSignOut = token;
}
if (!(tokenToSignOut in client.config.sessions)) {
return;
}
delete client.config.sessions[tokenToSignOut];
client.manager.updateUser(client.name, {
sessions: client.config.sessions
}, (err) => {
if (err) {
log.error("Failed to update sessions for", client.name, err);
}
});
socket.emit("sign-out");
_.map(client.attachedClients, (attachedClient, socketId) => {
if (attachedClient.token !== tokenToSignOut) {
return;
}
const socketToRemove = manager.sockets.of("/").connected[socketId];
socketToRemove.emit("sign-out");
socketToRemove.disconnect();
});
// Do not send updated session list if user simply logs out
if (tokenToSignOut !== token) {
sendSessionList();
}
});
socket.join(client.id);
const sendInitEvent = (tokenToSend) => {
client.clientAttach(socket.id, token);
let networks = client.networks;
if (lastMessage > -1) {
@ -423,14 +458,6 @@ function initializeClient(socket, client, token, lastMessage) {
client.updateSession(token, getClientIp(socket.request), socket.request);
client.manager.updateUser(client.name, {
sessions: client.config.sessions
}, (err) => {
if (err) {
log.error("Failed to update sessions for", client.name, err);
}
});
sendInitEvent(token);
});
} else {