forked from cybrespace/mastodon
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:
parent
436ce03772
commit
ed7dc1704d
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue