Connect a NeoPass to an existing account
including validation logic to make sure it's not already connected to another one! The `intent` param on the NeoPass form is part of the key! Thanks OmniAuth for making it easy to pass that data through!
This commit is contained in:
parent
09bccd41da
commit
5cc219c795
6 changed files with 100 additions and 9 deletions
|
@ -1,8 +1,31 @@
|
|||
class Devise::OmniauthCallbacksController < ApplicationController
|
||||
rescue_from AuthUser::AuthAlreadyConnected, with: :auth_already_connected
|
||||
rescue_from AuthUser::MissingAuthInfoError, with: :missing_auth_info
|
||||
rescue_from ActiveRecord::RecordInvalid, with: :validation_failed
|
||||
|
||||
def neopass
|
||||
case intent_param
|
||||
when "login"
|
||||
sign_in_with_neopass
|
||||
when "connect"
|
||||
connect_with_neopass
|
||||
else
|
||||
flash[:alert] = "Hrm, the NeoPass form you used was missing some " +
|
||||
"\"intent\" information. That's surprising! Maybe try again?"
|
||||
redirect_to root_path
|
||||
end
|
||||
end
|
||||
|
||||
def failure
|
||||
flash[:warning] =
|
||||
"Hrm, something went wrong in our connection to NeoPass, sorry! " +
|
||||
"Maybe wait a moment and try again?"
|
||||
redirect_to new_auth_user_session_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sign_in_with_neopass
|
||||
@auth_user = AuthUser.from_omniauth(request.env["omniauth.auth"])
|
||||
|
||||
if @auth_user.previously_new_record?
|
||||
|
@ -18,11 +41,20 @@ class Devise::OmniauthCallbacksController < ApplicationController
|
|||
sign_in_and_redirect @auth_user, event: :authentication
|
||||
end
|
||||
|
||||
def failure
|
||||
flash[:warning] =
|
||||
"Hrm, something went wrong in our connection to NeoPass, sorry! " +
|
||||
"Maybe wait a moment and try again?"
|
||||
redirect_to new_auth_user_session_path
|
||||
def connect_with_neopass
|
||||
raise AccessDenied unless auth_user_signed_in?
|
||||
|
||||
current_auth_user.connect_omniauth!(request.env["omniauth.auth"])
|
||||
|
||||
flash[:notice] = "We've successfully connected your NeoPass!"
|
||||
redirect_to edit_auth_user_path
|
||||
end
|
||||
|
||||
def auth_already_connected(error)
|
||||
flash[:alert] = "This NeoPass is already connected to another account. " +
|
||||
"You'll need to log out of this account, log in with that NeoPass, " +
|
||||
"then disconnect it from the other account."
|
||||
redirect_to return_path
|
||||
end
|
||||
|
||||
def missing_auth_info(error)
|
||||
|
@ -33,7 +65,7 @@ class Devise::OmniauthCallbacksController < ApplicationController
|
|||
"Hrm, our connection with NeoPass didn't return the information we " +
|
||||
"usually expect, sorry! If this keeps happening, please email me at " +
|
||||
"matchu@openneo.net so I can help investigate!"
|
||||
redirect_to new_auth_user_session_path
|
||||
redirect_to return_path
|
||||
end
|
||||
|
||||
def validation_failed(error)
|
||||
|
@ -44,6 +76,21 @@ class Devise::OmniauthCallbacksController < ApplicationController
|
|||
"Hrm, the connection with NeoPass worked, but we had trouble saving " +
|
||||
"your account, sorry! If this keeps happening, please email me at " +
|
||||
"matchu@openneo.net so I can help investigate!"
|
||||
redirect_to new_auth_user_session_path
|
||||
redirect_to return_path
|
||||
end
|
||||
|
||||
def intent_param
|
||||
request.env['omniauth.params']["intent"]
|
||||
end
|
||||
|
||||
def return_path
|
||||
case intent_param
|
||||
when "login"
|
||||
new_auth_user_session_path
|
||||
when "connect"
|
||||
edit_auth_user_path
|
||||
else
|
||||
root_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,6 +8,8 @@ class AuthUser < AuthRecord
|
|||
validates :name, presence: true, uniqueness: {case_sensitive: false},
|
||||
length: {maximum: 30}
|
||||
|
||||
validates :uid, uniqueness: {scope: :provider, allow_nil: true}
|
||||
|
||||
has_one :user, foreign_key: :remote_id, inverse_of: :auth_user
|
||||
|
||||
# If the email is blank, ensure that it's `nil` rather than an empty string,
|
||||
|
@ -79,6 +81,23 @@ class AuthUser < AuthRecord
|
|||
encrypted_password?
|
||||
end
|
||||
|
||||
def connect_omniauth!(auth)
|
||||
raise MissingAuthInfoError, "Email missing" if auth.info.email.blank?
|
||||
|
||||
begin
|
||||
update!(provider: auth.provider, uid: auth.uid,
|
||||
neopass_email: auth.info.email)
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
# If this auth is already bound to another account, raise a specific
|
||||
# error about it, instead of the normal error.
|
||||
if errors.where(:uid).any? { |e| e.type == :taken }
|
||||
raise AuthAlreadyConnected, "there's already an account with " +
|
||||
"provider #{auth.provider}, uid #{auth.uid}"
|
||||
end
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def disconnect_neopass
|
||||
# If there's no NeoPass, we're already done!
|
||||
return true if !uses_neopass?
|
||||
|
@ -100,6 +119,10 @@ class AuthUser < AuthRecord
|
|||
end
|
||||
end
|
||||
|
||||
def self.find_by_omniauth(auth)
|
||||
find_by(provider: auth.provider, uid: auth.uid)
|
||||
end
|
||||
|
||||
def self.from_omniauth(auth)
|
||||
raise MissingAuthInfoError, "Email missing" if auth.info.email.blank?
|
||||
|
||||
|
@ -160,5 +183,6 @@ class AuthUser < AuthRecord
|
|||
"\"#{base_name}\" are taken??)"
|
||||
end
|
||||
|
||||
class AuthAlreadyConnected < ArgumentError;end
|
||||
class MissingAuthInfoError < ArgumentError;end
|
||||
end
|
|
@ -96,6 +96,20 @@
|
|||
<%= form.submit "Disconnect your NeoPass",
|
||||
disabled: !@auth_user.uses_password? && !@auth_user.email? %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= form_with url: auth_user_neopass_omniauth_authorize_path(intent: "connect"),
|
||||
method: :post, class: "settings-form", data: {turbo: false} do |form|
|
||||
%>
|
||||
<h2>Your NeoPass</h2>
|
||||
<section class="neopass-explanation">
|
||||
<p>
|
||||
If you connect a NeoPass, you can use it to log into this DTI account!
|
||||
You'll still be able to use your password to log in too, and you can
|
||||
disconnect this later if you'd like.
|
||||
</p>
|
||||
</section>
|
||||
<%= form.submit "Connect your NeoPass" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% content_for :stylesheets do %>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
🌟✨🌟✨🌟✨🌟✨🌟
|
||||
<br />
|
||||
<%= button_to "Log in with NeoPass",
|
||||
auth_user_neopass_omniauth_authorize_path,
|
||||
auth_user_neopass_omniauth_authorize_path(intent: "login"),
|
||||
data: {turbo: false} # Turbo can't handle this redirect!
|
||||
%>
|
||||
🌟✨🌟✨🌟✨🌟✨🌟
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class AddUniqueIndexForOmniauthToUsers < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
add_index :users, [:provider, :uid], unique: true
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2024_04_07_135246) do
|
||||
ActiveRecord::Schema[7.1].define(version: 2024_04_08_120359) do
|
||||
create_table "users", id: { type: :integer, unsigned: true }, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t|
|
||||
t.string "name", limit: 30, null: false
|
||||
t.string "encrypted_password", limit: 64
|
||||
|
@ -33,6 +33,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_04_07_135246) do
|
|||
t.string "uid"
|
||||
t.string "neopass_email"
|
||||
t.index ["email"], name: "index_users_on_email", unique: true
|
||||
t.index ["provider", "uid"], name: "index_users_on_provider_and_uid", unique: true
|
||||
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
||||
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue