mirror of
https://github.com/discourse/discourse.git
synced 2025-06-05 14:07:30 +08:00
FEATURE: per client user tokens
Revamped system for managing authentication tokens. - Every user has 1 token per client (web browser) - Tokens are rotated every 10 minutes New system migrates the old tokens to "legacy" tokens, so users still remain logged on. Also introduces weekly job to expire old auth tokens.
This commit is contained in:
115
app/models/user_auth_token.rb
Normal file
115
app/models/user_auth_token.rb
Normal file
@ -0,0 +1,115 @@
|
||||
# frozen_string_literal: true
|
||||
require 'digest/sha1'
|
||||
|
||||
class UserAuthToken < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
|
||||
ROTATE_TIME = 10.minutes
|
||||
# used when token did not arrive at client
|
||||
URGENT_ROTATE_TIME = 1.minute
|
||||
|
||||
attr_accessor :unhashed_auth_token
|
||||
|
||||
def self.generate!(info)
|
||||
token = SecureRandom.hex(16)
|
||||
hashed_token = hash_token(token)
|
||||
user_auth_token = UserAuthToken.create!(
|
||||
user_id: info[:user_id],
|
||||
user_agent: info[:user_agent],
|
||||
client_ip: info[:client_ip],
|
||||
auth_token: hashed_token,
|
||||
prev_auth_token: hashed_token,
|
||||
rotated_at: Time.zone.now
|
||||
)
|
||||
user_auth_token.unhashed_auth_token = token
|
||||
user_auth_token
|
||||
end
|
||||
|
||||
def self.lookup(unhashed_token, opts=nil)
|
||||
|
||||
mark_seen = opts && opts[:seen]
|
||||
|
||||
token = hash_token(unhashed_token)
|
||||
expire_before = SiteSetting.maximum_session_age.hours.ago
|
||||
|
||||
user_token = find_by("(auth_token = :token OR
|
||||
prev_auth_token = :token OR
|
||||
(auth_token = :unhashed_token AND legacy)) AND created_at > :expire_before",
|
||||
token: token, unhashed_token: unhashed_token, expire_before: expire_before)
|
||||
|
||||
if user_token &&
|
||||
user_token.auth_token_seen &&
|
||||
user_token.prev_auth_token == token &&
|
||||
user_token.prev_auth_token != user_token.auth_token &&
|
||||
user_token.rotated_at > 1.minute.ago
|
||||
return nil
|
||||
end
|
||||
|
||||
if mark_seen && user_token && !user_token.auth_token_seen && user_token.auth_token == token
|
||||
user_token.update_columns(auth_token_seen: true)
|
||||
end
|
||||
|
||||
user_token
|
||||
end
|
||||
|
||||
def self.hash_token(token)
|
||||
Digest::SHA1.base64digest("#{token}#{GlobalSetting.safe_secret_key_base}")
|
||||
end
|
||||
|
||||
def self.cleanup!
|
||||
where('rotated_at < :time',
|
||||
time: SiteSetting.maximum_session_age.hours.ago - ROTATE_TIME).delete_all
|
||||
|
||||
end
|
||||
|
||||
def rotate!(info=nil)
|
||||
user_agent = (info && info[:user_agent] || self.user_agent)
|
||||
client_ip = (info && info[:client_ip] || self.client_ip)
|
||||
|
||||
token = SecureRandom.hex(16)
|
||||
|
||||
result = UserAuthToken.exec_sql("
|
||||
UPDATE user_auth_tokens
|
||||
SET
|
||||
auth_token_seen = false,
|
||||
user_agent = :user_agent,
|
||||
client_ip = :client_ip,
|
||||
prev_auth_token = case when auth_token_seen then auth_token else prev_auth_token end,
|
||||
auth_token = :new_token,
|
||||
rotated_at = :now
|
||||
WHERE id = :id AND (auth_token_seen or rotated_at < :safeguard_time)
|
||||
", id: self.id,
|
||||
user_agent: user_agent,
|
||||
client_ip: client_ip&.to_s,
|
||||
now: Time.zone.now,
|
||||
new_token: UserAuthToken.hash_token(token),
|
||||
safeguard_time: 30.seconds.ago
|
||||
)
|
||||
|
||||
if result.cmdtuples > 0
|
||||
reload
|
||||
self.unhashed_auth_token = token
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: user_auth_tokens
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# user_id :integer not null
|
||||
# auth_token :string not null
|
||||
# prev_auth_token :string
|
||||
# user_agent :string
|
||||
# auth_token_seen :boolean default(FALSE), not null
|
||||
# legacy :boolean default(FALSE), not null
|
||||
# client_ip :inet
|
||||
# rotated_at :datetime
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
#
|
Reference in New Issue
Block a user