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
|
class Devise::OmniauthCallbacksController < ApplicationController
|
||||||
|
rescue_from AuthUser::AuthAlreadyConnected, with: :auth_already_connected
|
||||||
rescue_from AuthUser::MissingAuthInfoError, with: :missing_auth_info
|
rescue_from AuthUser::MissingAuthInfoError, with: :missing_auth_info
|
||||||
rescue_from ActiveRecord::RecordInvalid, with: :validation_failed
|
rescue_from ActiveRecord::RecordInvalid, with: :validation_failed
|
||||||
|
|
||||||
def neopass
|
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"])
|
@auth_user = AuthUser.from_omniauth(request.env["omniauth.auth"])
|
||||||
|
|
||||||
if @auth_user.previously_new_record?
|
if @auth_user.previously_new_record?
|
||||||
|
@ -18,11 +41,20 @@ class Devise::OmniauthCallbacksController < ApplicationController
|
||||||
sign_in_and_redirect @auth_user, event: :authentication
|
sign_in_and_redirect @auth_user, event: :authentication
|
||||||
end
|
end
|
||||||
|
|
||||||
def failure
|
def connect_with_neopass
|
||||||
flash[:warning] =
|
raise AccessDenied unless auth_user_signed_in?
|
||||||
"Hrm, something went wrong in our connection to NeoPass, sorry! " +
|
|
||||||
"Maybe wait a moment and try again?"
|
current_auth_user.connect_omniauth!(request.env["omniauth.auth"])
|
||||||
redirect_to new_auth_user_session_path
|
|
||||||
|
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
|
end
|
||||||
|
|
||||||
def missing_auth_info(error)
|
def missing_auth_info(error)
|
||||||
|
@ -33,7 +65,7 @@ class Devise::OmniauthCallbacksController < ApplicationController
|
||||||
"Hrm, our connection with NeoPass didn't return the information we " +
|
"Hrm, our connection with NeoPass didn't return the information we " +
|
||||||
"usually expect, sorry! If this keeps happening, please email me at " +
|
"usually expect, sorry! If this keeps happening, please email me at " +
|
||||||
"matchu@openneo.net so I can help investigate!"
|
"matchu@openneo.net so I can help investigate!"
|
||||||
redirect_to new_auth_user_session_path
|
redirect_to return_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def validation_failed(error)
|
def validation_failed(error)
|
||||||
|
@ -44,6 +76,21 @@ class Devise::OmniauthCallbacksController < ApplicationController
|
||||||
"Hrm, the connection with NeoPass worked, but we had trouble saving " +
|
"Hrm, the connection with NeoPass worked, but we had trouble saving " +
|
||||||
"your account, sorry! If this keeps happening, please email me at " +
|
"your account, sorry! If this keeps happening, please email me at " +
|
||||||
"matchu@openneo.net so I can help investigate!"
|
"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
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,6 +8,8 @@ class AuthUser < AuthRecord
|
||||||
validates :name, presence: true, uniqueness: {case_sensitive: false},
|
validates :name, presence: true, uniqueness: {case_sensitive: false},
|
||||||
length: {maximum: 30}
|
length: {maximum: 30}
|
||||||
|
|
||||||
|
validates :uid, uniqueness: {scope: :provider, allow_nil: true}
|
||||||
|
|
||||||
has_one :user, foreign_key: :remote_id, inverse_of: :auth_user
|
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,
|
# If the email is blank, ensure that it's `nil` rather than an empty string,
|
||||||
|
@ -79,6 +81,23 @@ class AuthUser < AuthRecord
|
||||||
encrypted_password?
|
encrypted_password?
|
||||||
end
|
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
|
def disconnect_neopass
|
||||||
# If there's no NeoPass, we're already done!
|
# If there's no NeoPass, we're already done!
|
||||||
return true if !uses_neopass?
|
return true if !uses_neopass?
|
||||||
|
@ -100,6 +119,10 @@ class AuthUser < AuthRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.find_by_omniauth(auth)
|
||||||
|
find_by(provider: auth.provider, uid: auth.uid)
|
||||||
|
end
|
||||||
|
|
||||||
def self.from_omniauth(auth)
|
def self.from_omniauth(auth)
|
||||||
raise MissingAuthInfoError, "Email missing" if auth.info.email.blank?
|
raise MissingAuthInfoError, "Email missing" if auth.info.email.blank?
|
||||||
|
|
||||||
|
@ -160,5 +183,6 @@ class AuthUser < AuthRecord
|
||||||
"\"#{base_name}\" are taken??)"
|
"\"#{base_name}\" are taken??)"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class AuthAlreadyConnected < ArgumentError;end
|
||||||
class MissingAuthInfoError < ArgumentError;end
|
class MissingAuthInfoError < ArgumentError;end
|
||||||
end
|
end
|
|
@ -96,6 +96,20 @@
|
||||||
<%= form.submit "Disconnect your NeoPass",
|
<%= form.submit "Disconnect your NeoPass",
|
||||||
disabled: !@auth_user.uses_password? && !@auth_user.email? %>
|
disabled: !@auth_user.uses_password? && !@auth_user.email? %>
|
||||||
<% end %>
|
<% 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 %>
|
<% end %>
|
||||||
|
|
||||||
<% content_for :stylesheets do %>
|
<% content_for :stylesheets do %>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
🌟✨🌟✨🌟✨🌟✨🌟
|
🌟✨🌟✨🌟✨🌟✨🌟
|
||||||
<br />
|
<br />
|
||||||
<%= button_to "Log in with NeoPass",
|
<%= 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!
|
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.
|
# 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|
|
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 "name", limit: 30, null: false
|
||||||
t.string "encrypted_password", limit: 64
|
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 "uid"
|
||||||
t.string "neopass_email"
|
t.string "neopass_email"
|
||||||
t.index ["email"], name: "index_users_on_email", unique: true
|
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 ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
||||||
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
|
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue