impress/app/models/auth_user.rb
Emi Matchu 6618651fcb Use completely random NeoPass usernames for now
Ahh, I had assumed the `uid` provided by NeoPass would be the user's
Neopets username, but in hindsight that was never gonna work out since
NeoPass doesn't think of things in terms of usernames at all!

For now, we create 100% random NeoPass usernames, of the form
"neopass-shoyru-5812" or similar. This will be an important fallback
anyway, because it's possible to have a NeoPass with *no* Neopets.com
account attached.

But hopefully we'll be able to work with TNT to request the user's main
Neopets account's username somehow, to use that as the default when
possible!
2024-04-01 05:57:06 -07:00

91 lines
No EOL
3.3 KiB
Ruby

class AuthUser < AuthRecord
self.table_name = 'users'
devise :database_authenticatable, :encryptable, :registerable, :validatable,
:rememberable, :trackable, :recoverable, :omniauthable,
omniauth_providers: [:neopass]
validates :name, presence: true, uniqueness: {case_sensitive: false},
length: {maximum: 30}
has_one :user, foreign_key: :remote_id, inverse_of: :auth_user
# It's important to keep AuthUser and User in sync. When we create an AuthUser
# (e.g. through the registration process), we create a matching User, too. And
# when the AuthUser's name changes, we update User to match.
#
# TODO: Should we sync deletions too? We don't do deletions anywhere in app
# right now, so I'll hold off to avoid leaving dead code around.
after_create :create_user!
after_update :sync_name_with_user!, if: :saved_change_to_name?
def create_user!
User.create!(name: name, auth_server_id: 1, remote_id: id)
end
def sync_name_with_user!
user.name = name
user.save!
end
def uses_omniauth?
provider? && uid?
end
def email_required?
!uses_omniauth?
end
def password_required?
super && !uses_omniauth?
end
def self.from_omniauth(auth)
raise MissingAuthInfoError, "Email missing" if auth.info.email.blank?
transaction do
find_or_create_by!(provider: auth.provider, uid: auth.uid) do |user|
# TODO: Can we somehow get the Neopets username if one exists, instead
# of just using total randomness?
user.name = build_unique_username
# Copy the email address from their Neopets account to their DTI
# account, unless they already have a DTI account with this email, in
# which case, ignore it. (It's primarily for their own convenience with
# password recovery!)
email_exists = AuthUser.where(email: auth.info.email).exists?
user.email = auth.info.email unless email_exists
end
end
end
def self.build_unique_username
# Start with a base name like "neopass-kougra-".
random_species_name = Species.all.pluck(:name).sample
base_name = "neopass-#{random_species_name}"
# Fetch the list of names that already start with that.
name_query = sanitize_sql_like(base_name) + "%"
similar_names = where("name LIKE ?", name_query).pluck(:name).to_set
# Shuffle the list of four-digit numbers to create 10000 possible names,
# then use the first one that's not already claimed.
potential_names = (0..9999).map { |n| "#{base_name}-#{n}" }.shuffle
name = potential_names.find { |name| !similar_names.include?(name) }
return name unless name.nil?
# If that failed, try again but with six digits.
potential_names = (0..999999).map { |n| "#{base_name}-#{n}" }.shuffle
name = potential_names.find { |name| !similar_names.include?(name) }
return name unless name.nil?
# If *that* failed, then golly gee, we have millions of NeoPass users
# running around using the default username. Good for us, I guess?? If so,
# uhh, let's cross that bridge when we come to it. (At time of writing,
# there are about 60k total registered DTI users at *all*.)
raise "Failed to build unique username (all million+ names starting with " +
"\"#{base_name}\" are taken??)"
end
class MissingAuthInfoError < ArgumentError;end
end