Bind web UI access tokens to sessions (#3940)

* Add overview of active sessions

* Better display of browser/platform name

* Improve how browser information is stored and displayed for sessions overview

* Fix test

* Fix #2347 - Bind web UI access token to session

When you logout, session also destroys the access token, so it's no longer
valid. If access token is destroyed some other way, the session is also
destroyed, requiring a re-login.

Fix #1681 - Add scheduler to remove revoked access tokens and grants

* Fix test
This commit is contained in:
Eugen Rochko 2017-06-25 23:51:32 +02:00 committed by GitHub
parent 436ce03772
commit ed7dc1704d
7 changed files with 63 additions and 22 deletions

View File

@ -11,6 +11,7 @@ class ApplicationController < ActionController::Base
include UserTrackingConcern include UserTrackingConcern
helper_method :current_account helper_method :current_account
helper_method :current_session
helper_method :single_user_mode? helper_method :single_user_mode?
rescue_from ActionController::RoutingError, with: :not_found rescue_from ActionController::RoutingError, with: :not_found
@ -68,6 +69,10 @@ class ApplicationController < ActionController::Base
@current_account ||= current_user.try(:account) @current_account ||= current_user.try(:account)
end end
def current_session
@current_session ||= SessionActivation.find_by(session_id: session['auth_id'])
end
def cache_collection(raw, klass) def cache_collection(raw, klass)
return raw unless klass.respond_to?(:with_includes) return raw unless klass.respond_to?(:with_includes)

View File

@ -5,7 +5,7 @@ class HomeController < ApplicationController
def index def index
@body_classes = 'app-body' @body_classes = 'app-body'
@token = find_or_create_access_token.token @token = current_session.token
@web_settings = Web::Setting.find_by(user: current_user)&.data || {} @web_settings = Web::Setting.find_by(user: current_user)&.data || {}
@admin = Account.find_local(Setting.site_contact_username) @admin = Account.find_local(Setting.site_contact_username)
@streaming_api_base_url = Rails.configuration.x.streaming_api_base_url @streaming_api_base_url = Rails.configuration.x.streaming_api_base_url
@ -16,14 +16,4 @@ class HomeController < ApplicationController
def authenticate_user! def authenticate_user!
redirect_to(single_user_mode? ? account_path(Account.first) : about_path) unless user_signed_in? redirect_to(single_user_mode? ? account_path(Account.first) : about_path) unless user_signed_in?
end end
def find_or_create_access_token
Doorkeeper::AccessToken.find_or_create_for(
Doorkeeper::Application.where(superapp: true).first,
current_user.id,
Doorkeeper::OAuth::Scopes.from_string('read write follow'),
Doorkeeper.configuration.access_token_expires_in,
Doorkeeper.configuration.refresh_token_enabled?
)
end
end end

View File

@ -3,16 +3,23 @@
# #
# Table name: session_activations # Table name: session_activations
# #
# id :integer not null, primary key # id :integer not null, primary key
# user_id :integer not null # user_id :integer not null
# session_id :string not null # session_id :string not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# user_agent :string default(""), not null # user_agent :string default(""), not null
# ip :inet # ip :inet
# access_token_id :integer
# #
class SessionActivation < ApplicationRecord class SessionActivation < ApplicationRecord
belongs_to :access_token, class_name: 'Doorkeeper::AccessToken', dependent: :destroy
delegate :token,
to: :access_token,
allow_nil: true
def detection def detection
@detection ||= Browser.new(user_agent) @detection ||= Browser.new(user_agent)
end end
@ -25,9 +32,8 @@ class SessionActivation < ApplicationRecord
detection.platform.id detection.platform.id
end end
before_save do before_create :assign_access_token
self.user_agent = '' if user_agent.nil? before_save :assign_user_agent
end
class << self class << self
def active?(id) def active?(id)
@ -53,4 +59,22 @@ class SessionActivation < ApplicationRecord
where('session_id != ?', id).destroy_all where('session_id != ?', id).destroy_all
end end
end end
private
def assign_user_agent
self.user_agent = '' if user_agent.nil?
end
def assign_access_token
superapp = Doorkeeper::Application.find_by(superapp: true)
return if superapp.nil?
self.access_token = Doorkeeper::AccessToken.create!(application_id: superapp.id,
resource_owner_id: user_id,
scopes: 'read write follow',
expires_in: Doorkeeper.configuration.access_token_expires_in,
use_refresh_token: Doorkeeper.configuration.refresh_token_enabled?)
end
end end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
require 'sidekiq-scheduler'
class Scheduler::DoorkeeperCleanupScheduler
include Sidekiq::Worker
def perform
Doorkeeper::AccessToken.where('revoked_at IS NOT NULL').where('revoked_at < NOW()').delete_all
Doorkeeper::AccessGrant.where('revoked_at IS NOT NULL').where('revoked_at < NOW()').delete_all
end
end

View File

@ -15,3 +15,6 @@
feed_cleanup_scheduler: feed_cleanup_scheduler:
cron: '0 0 * * *' cron: '0 0 * * *'
class: Scheduler::FeedCleanupScheduler class: Scheduler::FeedCleanupScheduler
doorkeeper_cleanup_scheduler:
cron: '1 1 * * 0'
class: Scheduler::DoorkeeperCleanupScheduler

View File

@ -0,0 +1,6 @@
class AddAccessTokenIdToSessionActivations < ActiveRecord::Migration[5.1]
def change
add_column :session_activations, :access_token_id, :integer
add_foreign_key :session_activations, :oauth_access_tokens, column: :access_token_id, on_delete: :cascade
end
end

View File

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170624134742) do ActiveRecord::Schema.define(version: 20170625140443) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -257,6 +257,7 @@ ActiveRecord::Schema.define(version: 20170624134742) do
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.string "user_agent", default: "", null: false t.string "user_agent", default: "", null: false
t.inet "ip" t.inet "ip"
t.integer "access_token_id"
t.index ["session_id"], name: "index_session_activations_on_session_id", unique: true t.index ["session_id"], name: "index_session_activations_on_session_id", unique: true
t.index ["user_id"], name: "index_session_activations_on_user_id" t.index ["user_id"], name: "index_session_activations_on_user_id"
end end
@ -406,6 +407,7 @@ ActiveRecord::Schema.define(version: 20170624134742) do
add_foreign_key "reports", "accounts", column: "action_taken_by_account_id", on_delete: :nullify 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", column: "target_account_id", on_delete: :cascade
add_foreign_key "reports", "accounts", on_delete: :cascade add_foreign_key "reports", "accounts", on_delete: :cascade
add_foreign_key "session_activations", "oauth_access_tokens", column: "access_token_id", on_delete: :cascade
add_foreign_key "session_activations", "users", 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", column: "in_reply_to_account_id", on_delete: :nullify
add_foreign_key "statuses", "accounts", on_delete: :cascade add_foreign_key "statuses", "accounts", on_delete: :cascade