2019-05-02 17:17:27 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2013-02-05 13:16:51 -06:00
|
|
|
class EmailToken < ActiveRecord::Base
|
2023-01-09 06:20:10 -06:00
|
|
|
class TokenAccessError < StandardError
|
|
|
|
end
|
2021-11-25 01:34:39 -06:00
|
|
|
|
2013-02-05 13:16:51 -06:00
|
|
|
belongs_to :user
|
|
|
|
|
2021-11-25 01:34:39 -06:00
|
|
|
validates :user_id, :email, :token_hash, presence: true
|
2013-02-05 13:16:51 -06:00
|
|
|
|
2021-11-25 01:34:39 -06:00
|
|
|
scope :unconfirmed, -> { where(confirmed: false) }
|
2023-01-09 06:20:10 -06:00
|
|
|
scope :active,
|
2023-11-28 23:38:07 -06:00
|
|
|
-> do
|
2023-01-09 06:20:10 -06:00
|
|
|
where(expired: false).where(
|
|
|
|
"created_at >= ?",
|
|
|
|
SiteSetting.email_token_valid_hours.hours.ago,
|
|
|
|
)
|
2023-11-28 23:38:07 -06:00
|
|
|
end
|
2021-11-25 01:34:39 -06:00
|
|
|
|
|
|
|
after_initialize do
|
|
|
|
if self.token_hash.blank?
|
|
|
|
@token ||= SecureRandom.hex
|
|
|
|
self.token_hash = self.class.hash_token(@token)
|
|
|
|
end
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
after_create do
|
2021-11-25 01:34:39 -06:00
|
|
|
EmailToken
|
|
|
|
.where(user_id: self.user_id)
|
|
|
|
.where(scope: [nil, self.scope])
|
|
|
|
.where.not(id: self.id)
|
2017-07-27 20:20:09 -05:00
|
|
|
.update_all(expired: true)
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
|
|
|
|
2023-01-09 06:20:10 -06:00
|
|
|
before_validation { self.email = self.email.downcase if self.email }
|
2014-03-04 13:03:04 -06:00
|
|
|
|
2021-11-25 01:34:39 -06:00
|
|
|
before_save do
|
|
|
|
if self.scope.blank?
|
|
|
|
Discourse.deprecate("EmailToken#scope cannot be empty.", output_in_test: true)
|
|
|
|
end
|
2013-02-22 10:49:48 -06:00
|
|
|
end
|
|
|
|
|
2024-05-06 22:06:31 -05:00
|
|
|
self.ignored_columns = %w[token] # TODO: Remove when 20240212034010_drop_deprecated_columns has been promoted to pre-deploy
|
2024-05-06 13:18:53 -05:00
|
|
|
|
2021-11-25 01:34:39 -06:00
|
|
|
def self.scopes
|
2023-01-09 06:20:10 -06:00
|
|
|
@scopes ||= Enum.new(signup: 1, password_reset: 2, email_login: 3, email_update: 4)
|
2013-02-22 10:49:48 -06:00
|
|
|
end
|
|
|
|
|
2021-11-25 01:34:39 -06:00
|
|
|
def token
|
|
|
|
raise TokenAccessError.new if @token.blank?
|
2021-12-12 23:29:47 -06:00
|
|
|
|
|
|
|
@token
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
|
|
|
|
2021-11-25 01:34:39 -06:00
|
|
|
def self.confirm(token, scope: nil, skip_reviewable: false)
|
|
|
|
User.transaction do
|
|
|
|
email_token = confirmable(token, scope: scope)
|
|
|
|
return if email_token.blank?
|
|
|
|
|
|
|
|
email_token.update!(confirmed: true)
|
|
|
|
|
|
|
|
user = email_token.user
|
|
|
|
user.send_welcome_message = !user.active?
|
|
|
|
user.email = email_token.email
|
|
|
|
user.active = true
|
2023-01-09 06:20:10 -06:00
|
|
|
user.custom_fields.delete("activation_reminder")
|
2021-11-25 01:34:39 -06:00
|
|
|
user.save!
|
|
|
|
user.create_reviewable if !skip_reviewable
|
|
|
|
user.set_automatic_groups
|
|
|
|
DiscourseEvent.trigger(:user_confirmed_email, user)
|
2022-11-01 11:33:32 -05:00
|
|
|
Invite.redeem_for_existing_user(user) if scope == EmailToken.scopes[:signup]
|
2021-11-25 01:34:39 -06:00
|
|
|
|
|
|
|
user.reload
|
|
|
|
end
|
|
|
|
rescue ActiveRecord::RecordInvalid
|
|
|
|
# If the user's email is already taken, just return nil (failure)
|
2014-08-25 14:30:52 -05:00
|
|
|
end
|
|
|
|
|
2021-11-25 01:34:39 -06:00
|
|
|
def self.confirmable(token, scope: nil)
|
|
|
|
return nil if token.blank?
|
2013-02-05 13:16:51 -06:00
|
|
|
|
2023-01-09 06:20:10 -06:00
|
|
|
relation = unconfirmed.active.includes(:user).where(token_hash: hash_token(token))
|
2016-05-02 16:15:32 -05:00
|
|
|
|
2021-11-25 01:34:39 -06:00
|
|
|
# TODO(2022-01-01): All email tokens should have scopes by now
|
|
|
|
if !scope
|
|
|
|
relation.first
|
2016-05-02 16:15:32 -05:00
|
|
|
else
|
2021-11-25 01:34:39 -06:00
|
|
|
relation.where(scope: scope).first || relation.where(scope: nil).first
|
2016-03-07 13:40:11 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-11-25 01:34:39 -06:00
|
|
|
def self.enqueue_signup_email(email_token, to_address: nil)
|
|
|
|
Jobs.enqueue(
|
|
|
|
:critical_user_email,
|
2022-02-04 17:43:53 -06:00
|
|
|
type: "signup",
|
2021-11-25 01:34:39 -06:00
|
|
|
user_id: email_token.user_id,
|
|
|
|
email_token: email_token.token,
|
2023-01-09 06:20:10 -06:00
|
|
|
to_address: to_address,
|
2021-11-25 01:34:39 -06:00
|
|
|
)
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
2016-01-04 10:48:54 -06:00
|
|
|
|
2021-11-25 01:34:39 -06:00
|
|
|
def self.hash_token(token)
|
|
|
|
Digest::SHA256.hexdigest(token)
|
2016-01-04 10:48:54 -06:00
|
|
|
end
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
2013-05-23 21:48:32 -05:00
|
|
|
|
|
|
|
# == Schema Information
|
|
|
|
#
|
|
|
|
# Table name: email_tokens
|
|
|
|
#
|
|
|
|
# id :integer not null, primary key
|
|
|
|
# user_id :integer not null
|
2019-01-11 13:29:56 -06:00
|
|
|
# email :string not null
|
2013-05-23 21:48:32 -05:00
|
|
|
# confirmed :boolean default(FALSE), not null
|
|
|
|
# expired :boolean default(FALSE), not null
|
2014-08-27 00:19:25 -05:00
|
|
|
# created_at :datetime not null
|
|
|
|
# updated_at :datetime not null
|
2021-11-25 01:34:39 -06:00
|
|
|
# token_hash :string not null
|
|
|
|
# scope :integer
|
2013-05-23 21:48:32 -05:00
|
|
|
#
|
|
|
|
# Indexes
|
|
|
|
#
|
2021-12-06 11:17:32 -06:00
|
|
|
# index_email_tokens_on_token_hash (token_hash) UNIQUE
|
|
|
|
# index_email_tokens_on_user_id (user_id)
|
2013-05-23 21:48:32 -05:00
|
|
|
#
|