# frozen_string_literal: true class EmailToken < ActiveRecord::Base belongs_to :user validates :token, :user_id, :email, presence: true before_validation(on: :create) do self.token = EmailToken.generate_token self.email = self.email.downcase if self.email end after_create do # Expire the previous tokens EmailToken.where(user_id: self.user_id) .where("id != ?", self.id) .update_all(expired: true) end def self.token_length 16 end def self.valid_after SiteSetting.email_token_valid_hours.hours.ago end def self.unconfirmed where(confirmed: false) end def self.active where(expired: false).where('created_at > ?', valid_after) end def self.generate_token SecureRandom.hex(EmailToken.token_length) end def self.valid_token_format?(token) token.present? && token =~ /\h{#{token.length / 2}}/i end def self.atomic_confirm(token) failure = { success: false } return failure unless valid_token_format?(token) email_token = confirmable(token) return failure if email_token.blank? user = email_token.user failure[:user] = user row_count = EmailToken.where(confirmed: false, id: email_token.id, expired: false).update_all 'confirmed = true' if row_count == 1 { success: true, user: user, email_token: email_token } else failure end end def self.confirm(token, skip_reviewable: false) User.transaction do result = atomic_confirm(token) user = result[:user] if result[:success] # If we are activating the user, send the welcome message user.send_welcome_message = !user.active? user.email = result[:email_token].email user.active = true user.custom_fields.delete('activation_reminder') user.save! user.create_reviewable unless skip_reviewable user.set_automatic_groups DiscourseEvent.trigger(:user_confirmed_email, user) end if user if Invite.redeem_from_email(user.email).present? user.reload end user end end rescue ActiveRecord::RecordInvalid # If the user's email is already taken, just return nil (failure) end def self.confirmable(token) EmailToken.where(token: token) .where(expired: false, confirmed: false) .where("created_at >= ?", EmailToken.valid_after) .includes(:user) .first end end # == Schema Information # # Table name: email_tokens # # id :integer not null, primary key # user_id :integer not null # email :string not null # token :string not null # confirmed :boolean default(FALSE), not null # expired :boolean default(FALSE), not null # created_at :datetime not null # updated_at :datetime not null # # Indexes # # index_email_tokens_on_token (token) UNIQUE # index_email_tokens_on_user_id (user_id) #