about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2017-06-25 16:54:30 +0200
committerGitHub <noreply@github.com>2017-06-25 16:54:30 +0200
commitf7301bd5b94d3033b5dbb9ff65dd1ed8ac825ce5 (patch)
treeddc52c8b89c84ee825d451cc50a6946d7d77b2bb
parent099a3b4eaccc37338eda9f45fc26991ea7115200 (diff)
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
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock2
-rw-r--r--app/controllers/auth/registrations_controller.rb5
-rw-r--r--app/helpers/settings_helper.rb12
-rw-r--r--app/javascript/styles/tables.scss12
-rw-r--r--app/models/session_activation.rb48
-rw-r--r--app/models/user.rb6
-rw-r--r--app/views/auth/registrations/_sessions.html.haml23
-rw-r--r--app/views/auth/registrations/edit.html.haml4
-rw-r--r--config/initializers/devise.rb2
-rw-r--r--config/locales/en.yml37
-rw-r--r--db/migrate/20170624134742_add_description_to_session_activations.rb7
-rw-r--r--db/schema.rb5
-rw-r--r--spec/rails_helper.rb2
-rw-r--r--yarn.lock11
15 files changed, 147 insertions, 30 deletions
diff --git a/Gemfile b/Gemfile
index 77fffe7a6..aecd82702 100644
--- 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
index 00ce84556..627a01787 100644
--- 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
index d385c08e1..60ace04d7 100644
--- 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
index 172ef33ca..847eff2e7 100644
--- 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
index f7def8cf3..6e54c59c0 100644
--- 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
index 71e9f023c..75339b5f7 100644
--- 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
index fccf1089b..c31a0c644 100644
--- 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
new file mode 100644
index 000000000..11c0d4e31
--- /dev/null
+++ 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
index 38d4349cb..fbc8d017b 100644
--- 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
index 6d3a73ef6..d51471d30 100644
--- 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
index 0d33aae3f..1d8e3f6b0 100644
--- 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
new file mode 100644
index 000000000..9dbb15564
--- /dev/null
+++ 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
index b6aceb930..1e7d6c0b3 100644
--- 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
index 31c94b1e4..cfc9eec9e 100644
--- 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
index ef870d7e2..d1a1687a0 100644
--- 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: