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

View File

@ -198,15 +198,14 @@
</div> </div>
</form> </form>
</div> </div>
<div id="settings" class="window"> <div id="settings" class="window" data-type="settings">
<div class="header"> <div class="header">
<button class="lt" aria-label="Toggle channel list"></button> <button class="lt" aria-label="Toggle channel list"></button>
</div> </div>
<div class="container"> <div class="container">
<h1 class="title">Settings</h1>
<div class="row"> <div class="row">
<div class="col-sm-12">
<h1 class="title">Settings</h1>
</div>
<div class="col-sm-12"> <div class="col-sm-12">
<h2>Messages</h2> <h2>Messages</h2>
</div> </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> <textarea class="input" name="userStyles" id="user-specified-css-input" placeholder="You can override any style with CSS here"></textarea>
</div> </div>
</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>
</div> </div>

View File

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

View File

@ -17,3 +17,4 @@ require("./sync_sort");
require("./topic"); require("./topic");
require("./users"); require("./users");
require("./sign_out"); 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"), msg_unhandled: require("./msg_unhandled.tpl"),
network: require("./network.tpl"), network: require("./network.tpl"),
image_viewer: require("./image_viewer.tpl"), image_viewer: require("./image_viewer.tpl"),
session: require("./session.tpl"),
unread_marker: require("./unread_marker.tpl"), unread_marker: require("./unread_marker.tpl"),
user: require("./user.tpl"), user: require("./user.tpl"),
user_filtered: require("./user_filtered.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}`; 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(), lastUse: Date.now(),
ip: ip, ip: ip,
agent: friendlyAgent, agent: friendlyAgent,
}, client.config.sessions[token]); });
client.manager.updateUser(client.name, {
sessions: client.config.sessions
});
}; };
Client.prototype.setPassword = function(hash, callback) { Client.prototype.setPassword = function(hash, callback) {
@ -318,7 +322,6 @@ Client.prototype.setPassword = function(hash, callback) {
password: hash password: hash
}, function(err) { }, function(err) {
if (err) { if (err) {
log.error("Failed to update password of", client.name, err);
return callback(false); return callback(false);
} }

View File

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

View File

@ -237,7 +237,6 @@ function initializeClient(socket, client, token, lastMessage) {
socket.on("disconnect", function() { socket.on("disconnect", function() {
client.clientDetach(socket.id); client.clientDetach(socket.id);
}); });
client.clientAttach(socket.id, token);
socket.on( socket.on(
"input", "input",
@ -378,23 +377,59 @@ function initializeClient(socket, client, token, lastMessage) {
client.unregisterPushSubscription(token); client.unregisterPushSubscription(token);
}); });
socket.on("sign-out", () => { const sendSessionList = () => {
delete client.config.sessions[token]; 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, { client.manager.updateUser(client.name, {
sessions: client.config.sessions 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); socket.join(client.id);
const sendInitEvent = (tokenToSend) => { const sendInitEvent = (tokenToSend) => {
client.clientAttach(socket.id, token);
let networks = client.networks; let networks = client.networks;
if (lastMessage > -1) { if (lastMessage > -1) {
@ -423,14 +458,6 @@ function initializeClient(socket, client, token, lastMessage) {
client.updateSession(token, getClientIp(socket.request), socket.request); 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); sendInitEvent(token);
}); });
} else { } else {