commit: f7301bd5b94d3033b5dbb9ff65dd1ed8ac825ce5
parent: 099a3b4eaccc37338eda9f45fc26991ea7115200
Author: Eugen Rochko <eugen@zeonfederated.com>
Date: Sun, 25 Jun 2017 16:54:30 +0200
Add overview of active sessions (#3929)
* Add overview of active sessions
* Better display of browser/platform name
* Improve how browser information is stored and displayed for sessions overview
* Fix test
Diffstat:
15 files changed, 147 insertions(+), 30 deletions(-)
diff --git a/Gemfile b/Gemfile
@@ -20,6 +20,7 @@ gem 'paperclip-av-transcoder', '~> 0.6'
gem 'addressable', '~> 2.5'
gem 'bootsnap'
+gem 'browser'
gem 'cld3', '~> 3.1'
gem 'devise', '~> 4.2'
gem 'devise-two-factor', '~> 3.0'
diff --git a/Gemfile.lock b/Gemfile.lock
@@ -70,6 +70,7 @@ GEM
bootsnap (1.0.0)
msgpack (~> 1.0)
brakeman (3.6.2)
+ browser (2.4.0)
builder (3.2.3)
bullet (5.5.1)
activesupport (>= 3.0.0)
@@ -483,6 +484,7 @@ DEPENDENCIES
binding_of_caller (~> 0.7)
bootsnap
brakeman (~> 3.6)
+ browser
bullet (~> 5.5)
bundler-audit (~> 0.5)
capistrano (~> 3.8)
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
@@ -5,6 +5,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
before_action :check_enabled_registrations, only: [:new, :create]
before_action :configure_sign_up_params, only: [:create]
+ before_action :set_sessions, only: [:edit, :update]
def destroy
not_found
@@ -41,4 +42,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
def determine_layout
%w(edit update).include?(action_name) ? 'admin' : 'auth'
end
+
+ def set_sessions
+ @sessions = current_user.session_activations
+ end
end
diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb
@@ -41,4 +41,16 @@ module SettingsHelper
def hash_to_object(hash)
HashObject.new(hash)
end
+
+ def session_device_icon(session)
+ device = session.detection.device
+
+ if device.mobile?
+ 'mobile'
+ elsif device.tablet?
+ 'tablet'
+ else
+ 'desktop'
+ end
+ end
end
diff --git a/app/javascript/styles/tables.scss b/app/javascript/styles/tables.scss
@@ -42,6 +42,18 @@
strong {
font-weight: 500;
}
+
+ &.inline-table {
+ td,
+ th {
+ padding: 8px 0;
+ }
+
+ & > tbody > tr:nth-child(odd) > td,
+ & > tbody > tr:nth-child(odd) > th {
+ background: transparent;
+ }
+ }
}
samp {
diff --git a/app/models/session_activation.rb b/app/models/session_activation.rb
@@ -8,31 +8,49 @@
# session_id :string not null
# created_at :datetime not null
# updated_at :datetime not null
+# user_agent :string default(""), not null
+# ip :inet
#
class SessionActivation < ApplicationRecord
- LIMIT = Rails.configuration.x.max_session_activations
-
- def self.active?(id)
- id && where(session_id: id).exists?
+ def detection
+ @detection ||= Browser.new(user_agent)
end
- def self.activate(id)
- activation = create!(session_id: id)
- purge_old
- activation
+ def browser
+ detection.id
end
- def self.deactivate(id)
- return unless id
- where(session_id: id).destroy_all
+ def platform
+ detection.platform.id
end
- def self.purge_old
- order('created_at desc').offset(LIMIT).destroy_all
+ before_save do
+ self.user_agent = '' if user_agent.nil?
end
- def self.exclusive(id)
- where('session_id != ?', id).destroy_all
+ class << self
+ def active?(id)
+ id && where(session_id: id).exists?
+ end
+
+ def activate(options = {})
+ activation = create!(options)
+ purge_old
+ activation
+ end
+
+ def deactivate(id)
+ return unless id
+ where(session_id: id).destroy_all
+ end
+
+ def purge_old
+ order('created_at desc').offset(Rails.configuration.x.max_session_activations).destroy_all
+ end
+
+ def exclusive(id)
+ where('session_id != ?', id).destroy_all
+ end
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
@@ -91,8 +91,10 @@ class User < ApplicationRecord
settings.auto_play_gif
end
- def activate_session
- session_activations.activate(SecureRandom.hex).session_id
+ def activate_session(request)
+ session_activations.activate(session_id: SecureRandom.hex,
+ user_agent: request.user_agent,
+ ip: request.ip).session_id
end
def exclusive_session(id)
diff --git a/app/views/auth/registrations/_sessions.html.haml b/app/views/auth/registrations/_sessions.html.haml
@@ -0,0 +1,23 @@
+%h6= t 'sessions.title'
+%p.muted-hint= t 'sessions.explanation'
+
+%table.table.inline-table
+ %thead
+ %tr
+ %th= t 'sessions.browser'
+ %th= t 'sessions.ip'
+ %th= t 'sessions.activity'
+ %tbody
+ - @sessions.each do |session|
+ %tr
+ %td
+ %span{ title: session.user_agent }= fa_icon session_device_icon(session)
+ = ' '
+ = t 'sessions.description', browser: t("sessions.browsers.#{session.browser}"), platform: t("sessions.platforms.#{session.platform}")
+ %td
+ %samp= session.ip
+ %td
+ - if request.session['auth_id'] == session.session_id
+ = t 'sessions.current_session'
+ - else
+ %time.time-ago{ datetime: session.updated_at.iso8601, title: l(session.updated_at) }= l(session.updated_at)
diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml
@@ -12,6 +12,10 @@
.actions
= f.button :button, t('generic.save_changes'), type: :submit
+%hr/
+
+= render 'sessions'
+
- if open_deletion?
%hr/
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
@@ -1,6 +1,6 @@
Warden::Manager.after_set_user except: :fetch do |user, warden|
SessionActivation.deactivate warden.raw_session['auth_id']
- warden.raw_session['auth_id'] = user.activate_session
+ warden.raw_session['auth_id'] = user.activate_session(warden.request)
end
Warden::Manager.after_fetch do |user, warden|
diff --git a/config/locales/en.yml b/config/locales/en.yml
@@ -320,6 +320,43 @@ en:
missing_resource: Could not find the required redirect URL for your account
proceed: Proceed to follow
prompt: 'You are going to follow:'
+ sessions:
+ activity: Last activity
+ browser: Browser
+ browsers:
+ alipay: Alipay
+ blackberry: Blackberry
+ chrome: Chrome
+ edge: Microsoft Edge
+ firefox: Firefox
+ generic: Unknown browser
+ ie: Internet Explorer
+ micro_messenger: MicroMessenger
+ nokia: Nokia S40 Ovi Browser
+ opera: Opera
+ phantom_js: PhantomJS
+ qq: QQ Browser
+ safari: Safari
+ uc_browser: UCBrowser
+ weibo: Weibo
+ current_session: Current session
+ description: "%{browser} on %{platform}"
+ explanation: These are the web browsers currently logged in to your Mastodon account.
+ ip: IP
+ platforms:
+ adobe_air: Adobe Air
+ android: Android
+ blackberry: Blackberry
+ chrome_os: ChromeOS
+ firefox_os: Firefox OS
+ ios: iOS
+ linux: Linux
+ mac: Mac
+ other: unknown platform
+ windows: Windows
+ windows_mobile: Windows Mobile
+ windows_phone: Windows Phone
+ title: Sessions
settings:
authorized_apps: Authorized apps
back: Back to Mastodon
diff --git a/db/migrate/20170624134742_add_description_to_session_activations.rb b/db/migrate/20170624134742_add_description_to_session_activations.rb
@@ -0,0 +1,7 @@
+class AddDescriptionToSessionActivations < ActiveRecord::Migration[5.1]
+ def change
+ add_column :session_activations, :user_agent, :string, null: false, default: ''
+ add_column :session_activations, :ip, :inet
+ add_foreign_key :session_activations, :users, on_delete: :cascade
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170623152212) do
+ActiveRecord::Schema.define(version: 20170624134742) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -255,6 +255,8 @@ ActiveRecord::Schema.define(version: 20170623152212) do
t.string "session_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.string "user_agent", default: "", null: false
+ t.inet "ip"
t.index ["session_id"], name: "index_session_activations_on_session_id", unique: true
t.index ["user_id"], name: "index_session_activations_on_user_id"
end
@@ -404,6 +406,7 @@ ActiveRecord::Schema.define(version: 20170623152212) do
add_foreign_key "reports", "accounts", column: "action_taken_by_account_id", on_delete: :nullify
add_foreign_key "reports", "accounts", column: "target_account_id", on_delete: :cascade
add_foreign_key "reports", "accounts", on_delete: :cascade
+ add_foreign_key "session_activations", "users", on_delete: :cascade
add_foreign_key "statuses", "accounts", column: "in_reply_to_account_id", on_delete: :nullify
add_foreign_key "statuses", "accounts", on_delete: :cascade
add_foreign_key "statuses", "statuses", column: "in_reply_to_id", on_delete: :nullify
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
@@ -23,7 +23,7 @@ Devise::Test::ControllerHelpers.module_eval do
original_sign_in(resource, scope: scope)
SessionActivation.deactivate warden.raw_session["auth_id"]
- warden.raw_session["auth_id"] = resource.activate_session
+ warden.raw_session["auth_id"] = resource.activate_session(warden.request)
end
end
diff --git a/yarn.lock b/yarn.lock
@@ -7184,16 +7184,7 @@ webpack-bundle-analyzer@^2.8.2:
opener "^1.4.3"
ws "^2.3.1"
-webpack-dev-middleware@^1.10.2:
- version "1.10.2"
- resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.10.2.tgz#2e252ce1dfb020dbda1ccb37df26f30ab014dbd1"
- dependencies:
- memory-fs "~0.4.1"
- mime "^1.3.4"
- path-is-absolute "^1.0.0"
- range-parser "^1.0.3"
-
-webpack-dev-middleware@^1.11.0:
+webpack-dev-middleware@^1.10.2, webpack-dev-middleware@^1.11.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.11.0.tgz#09691d0973a30ad1f82ac73a12e2087f0a4754f9"
dependencies: