Compare commits
18 commits
8e269df3c1
...
0a046ed9c1
Author | SHA1 | Date | |
---|---|---|---|
0a046ed9c1 | |||
5cc219c795 | |||
09bccd41da | |||
889c454c65 | |||
f6d3992045 | |||
0f5bb2a861 | |||
ae2b62956a | |||
3e92d89765 | |||
ed89380152 | |||
b5e203c0e5 | |||
54a052848a | |||
b827727102 | |||
89fc99c918 | |||
66978bf5a0 | |||
88a2688ac8 | |||
21b967f83d | |||
d5c3bc087e | |||
82aea20679 |
21 changed files with 459 additions and 91 deletions
|
@ -82,7 +82,7 @@ $container_width: 800px
|
||||||
input, button, select, label
|
input, button, select, label
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
|
|
||||||
input[type=text], input[type=password], input[type=search], input[type=number], select, textarea
|
input[type=text], input[type=password], input[type=search], input[type=number], input[type=email], select, textarea
|
||||||
border-radius: 3px
|
border-radius: 3px
|
||||||
background: #fff
|
background: #fff
|
||||||
border: 1px solid $input-border-color
|
border: 1px solid $input-border-color
|
||||||
|
|
56
app/assets/stylesheets/auth_users/edit.sass
Normal file
56
app/assets/stylesheets/auth_users/edit.sass
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
@import "../partials/clean/constants"
|
||||||
|
|
||||||
|
body.auth_users-edit, body.auth_users-update
|
||||||
|
.settings-form
|
||||||
|
border: 1px solid $module-border-color
|
||||||
|
background: $module-bg-color
|
||||||
|
border-radius: 1em
|
||||||
|
padding: 1em 1.25em
|
||||||
|
|
||||||
|
&:not(:last-of-type)
|
||||||
|
margin-bottom: 2em
|
||||||
|
|
||||||
|
h2
|
||||||
|
font-size: 1.5rem
|
||||||
|
margin-bottom: .25em
|
||||||
|
|
||||||
|
.hint
|
||||||
|
font-style: italic
|
||||||
|
font-size: .85em
|
||||||
|
opacity: .9
|
||||||
|
|
||||||
|
fieldset
|
||||||
|
padding-block: .5em
|
||||||
|
|
||||||
|
fieldset:not(:last-of-type)
|
||||||
|
border-bottom: 1px solid $module-border-color
|
||||||
|
margin-bottom: .5em
|
||||||
|
|
||||||
|
.field
|
||||||
|
margin-bottom: 1em
|
||||||
|
|
||||||
|
.field_with_errors
|
||||||
|
display: inline
|
||||||
|
|
||||||
|
label
|
||||||
|
font-weight: bold
|
||||||
|
|
||||||
|
.error-explanation
|
||||||
|
color: $error-color
|
||||||
|
background: $error-bg-color
|
||||||
|
border: 1px solid $error-border-color
|
||||||
|
border-radius: .5em
|
||||||
|
padding: .5em
|
||||||
|
margin-bottom: .5em
|
||||||
|
|
||||||
|
header
|
||||||
|
font-weight: bold
|
||||||
|
|
||||||
|
ul
|
||||||
|
padding-left: 2em
|
||||||
|
|
||||||
|
.neopass-info
|
||||||
|
margin-bottom: .5em
|
||||||
|
|
||||||
|
.neopass-explanation
|
||||||
|
font-size: .85em
|
|
@ -41,17 +41,19 @@
|
||||||
padding: .5em .75em .45em
|
padding: .5em .75em .45em
|
||||||
color: #fff
|
color: #fff
|
||||||
text-decoration: none
|
text-decoration: none
|
||||||
-moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5)
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5)
|
||||||
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5)
|
|
||||||
text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25)
|
text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25)
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.25)
|
border-bottom: 1px solid rgba(0, 0, 0, 0.25)
|
||||||
position: relative
|
position: relative
|
||||||
font-weight: bold
|
font-weight: bold
|
||||||
line-height: 1
|
line-height: 1
|
||||||
&:hover
|
&:hover:not(:disabled)
|
||||||
color: #fff
|
color: #fff
|
||||||
&:active
|
&:active:not(:disabled)
|
||||||
transform: translateY(1px)
|
transform: translateY(1px)
|
||||||
|
&:disabled
|
||||||
|
background: #999999 image-url("alert-overlay.png") repeat-x
|
||||||
|
cursor: not-allowed
|
||||||
|
|
||||||
=reset-awesome-button
|
=reset-awesome-button
|
||||||
border-radius: 0
|
border-radius: 0
|
||||||
|
|
|
@ -12,7 +12,6 @@ class ApplicationController < ActionController::Base
|
||||||
before_action :set_locale
|
before_action :set_locale
|
||||||
|
|
||||||
before_action :configure_permitted_parameters, if: :devise_controller?
|
before_action :configure_permitted_parameters, if: :devise_controller?
|
||||||
before_action :check_neopass_access, if: :devise_controller?
|
|
||||||
before_action :save_return_to_path,
|
before_action :save_return_to_path,
|
||||||
if: ->(c) { c.controller_name == 'sessions' && c.action_name == 'new' }
|
if: ->(c) { c.controller_name == 'sessions' && c.action_name == 'new' }
|
||||||
|
|
||||||
|
@ -88,12 +87,6 @@ class ApplicationController < ActionController::Base
|
||||||
devise_parameter_sanitizer.permit(:account_update, keys: [:email])
|
devise_parameter_sanitizer.permit(:account_update, keys: [:email])
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_neopass_access
|
|
||||||
@can_use_neopass = (
|
|
||||||
params[:neopass] == Rails.configuration.neopass_access_secret
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def save_return_to_path
|
def save_return_to_path
|
||||||
if params[:return_to]
|
if params[:return_to]
|
||||||
Rails.logger.debug "Saving return_to path: #{params[:return_to].inspect}"
|
Rails.logger.debug "Saving return_to path: #{params[:return_to].inspect}"
|
||||||
|
|
60
app/controllers/auth_users_controller.rb
Normal file
60
app/controllers/auth_users_controller.rb
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
class AuthUsersController < ApplicationController
|
||||||
|
before_action :authenticate_user!, except: [:new, :create]
|
||||||
|
|
||||||
|
def create
|
||||||
|
@auth_user = AuthUser.create(auth_user_params)
|
||||||
|
|
||||||
|
if @auth_user.persisted?
|
||||||
|
sign_in :auth_user, @auth_user
|
||||||
|
flash[:notice] = "Welcome to Dress to Impress, #{@auth_user.name}! 💖"
|
||||||
|
redirect_to root_path
|
||||||
|
else
|
||||||
|
render action: :new, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit
|
||||||
|
@auth_user = current_auth_user
|
||||||
|
end
|
||||||
|
|
||||||
|
def new
|
||||||
|
@auth_user = AuthUser.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@auth_user = load_auth_user
|
||||||
|
|
||||||
|
# If the user has a password, then the `current_password` field is required
|
||||||
|
# when updating. If not, then it's not!
|
||||||
|
success = @auth_user.uses_password? ?
|
||||||
|
@auth_user.update_with_password(auth_user_params) :
|
||||||
|
@auth_user.update(auth_user_params)
|
||||||
|
|
||||||
|
if success
|
||||||
|
# NOTE: Changing the password will sign you out, so make sure we stay
|
||||||
|
# signed in!
|
||||||
|
bypass_sign_in @auth_user, scope: :auth_user
|
||||||
|
|
||||||
|
flash[:notice] = "Settings successfully saved."
|
||||||
|
redirect_to action: :edit
|
||||||
|
else
|
||||||
|
render action: :edit, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def auth_user_params
|
||||||
|
params.require(:auth_user).permit(:name, :email, :password,
|
||||||
|
:password_confirmation, :current_password)
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_auth_user
|
||||||
|
# Well, what we *actually* do is just use `current_auth_user`, and enforce
|
||||||
|
# that the provided user ID matches. The user ID param is only really for
|
||||||
|
# REST semantics and such!
|
||||||
|
raise AccessDenied unless auth_user_signed_in?
|
||||||
|
raise AccessDenied unless current_auth_user.id == params[:id].to_i
|
||||||
|
current_auth_user
|
||||||
|
end
|
||||||
|
end
|
|
@ -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
|
||||||
|
|
29
app/controllers/neopass_connections_controller.rb
Normal file
29
app/controllers/neopass_connections_controller.rb
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
class NeopassConnectionsController < ApplicationController
|
||||||
|
def destroy
|
||||||
|
@user = load_user
|
||||||
|
|
||||||
|
if @user.disconnect_neopass
|
||||||
|
flash[:notice] = "Your NeoPass has been disconnected. In the future, " +
|
||||||
|
"to log into this account, you'll need to use your password or your " +
|
||||||
|
"recovery email. You can also connect a different NeoPass, if you'd " +
|
||||||
|
"like."
|
||||||
|
else
|
||||||
|
flash[:alert] = "Whoops, there was an error disconnecting your " +
|
||||||
|
"NeoPass from your account, sorry. If this keeps happening, let us " +
|
||||||
|
"know!"
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to edit_auth_user_path
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def load_user
|
||||||
|
# Well, what we *actually* do is just use `current_user`, and enforce that
|
||||||
|
# the provided user ID matches. The user ID param is only really for REST
|
||||||
|
# semantics and such!
|
||||||
|
raise AccessDenied unless user_signed_in?
|
||||||
|
raise AccessDenied unless current_user.id == params[:user_id].to_i
|
||||||
|
current_user
|
||||||
|
end
|
||||||
|
end
|
|
@ -69,6 +69,10 @@ module ApplicationHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def can_use_neopass
|
||||||
|
params[:neopass] == Rails.configuration.neopass_access_secret
|
||||||
|
end
|
||||||
|
|
||||||
def contact_email
|
def contact_email
|
||||||
"matchu@openneo.net"
|
"matchu@openneo.net"
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,8 +8,15 @@ 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,
|
||||||
|
# or else the database's uniqueness constraint will object to multiple users
|
||||||
|
# who all have the empty string as their email.
|
||||||
|
before_validation { self.email = nil if email.blank? }
|
||||||
|
|
||||||
# It's important to keep AuthUser and User in sync. When we create an AuthUser
|
# 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
|
# (e.g. through the registration process), we create a matching User, too. And
|
||||||
# when the AuthUser's name changes, we update User to match.
|
# when the AuthUser's name changes, we update User to match.
|
||||||
|
@ -33,18 +40,96 @@ class AuthUser < AuthRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def email_required?
|
def email_required?
|
||||||
|
# Email is required when creating a new account from scratch, but it isn't
|
||||||
|
# required when creating a new account via third-party login (e.g. it's
|
||||||
|
# already taken). It's also okay to remove your email address, though this
|
||||||
|
if new_record?
|
||||||
|
# When creating a new account, email is required when building it from
|
||||||
|
# scratch, but not required when using third-party login. This is mainly
|
||||||
|
# because third-party login can't reliably offer an unused email!
|
||||||
!uses_omniauth?
|
!uses_omniauth?
|
||||||
|
else
|
||||||
|
# TODO: I had wanted to make email required if you already have one, to
|
||||||
|
# make it harder to accidentally remove? I expected
|
||||||
|
# `email_before_last_save` to be the way to check this, but it
|
||||||
|
# seemed to be `nil` when calling this, go figure! For now, we're
|
||||||
|
# allowing email to be removed.
|
||||||
|
#
|
||||||
|
# NOTE: This is important for the case where you're disconnecting a
|
||||||
|
# NeoPass, but you don't have an email set, because your NeoPass
|
||||||
|
# email already belonged to another account. I don't think it makes
|
||||||
|
# sense to require people to add an alternate real email address in
|
||||||
|
# order to be able to disconnect a NeoPass from a DTI account they
|
||||||
|
# maybe even created by accident!
|
||||||
|
false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def password_required?
|
def password_required?
|
||||||
super && !uses_omniauth?
|
super && !uses_omniauth?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def uses_neopass?
|
||||||
|
provider == "neopass"
|
||||||
|
end
|
||||||
|
|
||||||
|
def neopass_friendly_id
|
||||||
|
neopass_email || uid
|
||||||
|
end
|
||||||
|
|
||||||
|
def uses_password?
|
||||||
|
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?
|
||||||
|
|
||||||
|
begin
|
||||||
|
# Remove all of the NeoPass fields, and return whether we were
|
||||||
|
# successful. (I don't know why it wouldn't be, but let's be resilient!)
|
||||||
|
#
|
||||||
|
# NOTE: I considered leaving `neopass_email` in place, to help us support
|
||||||
|
# users who accidentally got locked out… but I think it's more
|
||||||
|
# important to respect data privacy and not be holding onto an
|
||||||
|
# email address the user doesn't realize we have!
|
||||||
|
update(provider: nil, uid: nil, neopass_email: nil)
|
||||||
|
rescue => error
|
||||||
|
# If something strange happens, log it and gracefully return `false`!
|
||||||
|
Sentry.capture_exception error
|
||||||
|
Rails.logger.error error
|
||||||
|
false
|
||||||
|
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?
|
||||||
|
|
||||||
transaction do
|
transaction do
|
||||||
find_or_create_by!(provider: auth.provider, uid: auth.uid) do |user|
|
find_or_create_by!(provider: auth.provider, uid: auth.uid) do |user|
|
||||||
|
# This account is new! Let's do the initial setup.
|
||||||
|
|
||||||
# TODO: Can we somehow get the Neopets username if one exists, instead
|
# TODO: Can we somehow get the Neopets username if one exists, instead
|
||||||
# of just using total randomness?
|
# of just using total randomness?
|
||||||
user.name = build_unique_username
|
user.name = build_unique_username
|
||||||
|
@ -55,6 +140,17 @@ class AuthUser < AuthRecord
|
||||||
# password recovery!)
|
# password recovery!)
|
||||||
email_exists = AuthUser.where(email: auth.info.email).exists?
|
email_exists = AuthUser.where(email: auth.info.email).exists?
|
||||||
user.email = auth.info.email unless email_exists
|
user.email = auth.info.email unless email_exists
|
||||||
|
end.tap do |user|
|
||||||
|
# If this account already existed, make sure we've saved the latest
|
||||||
|
# email to `neopass_email`.
|
||||||
|
#
|
||||||
|
# We track this separately from `email`, which the user can edit, to
|
||||||
|
# use in the Settings UI to indicate what NeoPass you're linked to. (In
|
||||||
|
# practice, this *shouldn't* ever change after initial setup, because
|
||||||
|
# NeoPass emails are immutable? But why not be resilient!)
|
||||||
|
unless user.previously_new_record?
|
||||||
|
user.update!(neopass_email: auth.info.email)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -87,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
|
|
@ -4,6 +4,7 @@ class User < ApplicationRecord
|
||||||
PreviewTopContributorsCount = 3
|
PreviewTopContributorsCount = 3
|
||||||
|
|
||||||
belongs_to :auth_user, foreign_key: :remote_id, inverse_of: :user
|
belongs_to :auth_user, foreign_key: :remote_id, inverse_of: :user
|
||||||
|
delegate :disconnect_neopass, to: :auth_user
|
||||||
|
|
||||||
has_many :closet_hangers
|
has_many :closet_hangers
|
||||||
has_many :closet_lists
|
has_many :closet_lists
|
||||||
|
|
117
app/views/auth_users/edit.html.erb
Normal file
117
app/views/auth_users/edit.html.erb
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
<h2>Settings</h2>
|
||||||
|
|
||||||
|
<%= form_with(model: @auth_user, method: :put, class: "settings-form") do |f| %>
|
||||||
|
<h2>Your info</h2>
|
||||||
|
<%= render "devise/shared/error_messages", resource: @auth_user %>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<div class="field">
|
||||||
|
<%= f.label :name, 'DTI Username' %>
|
||||||
|
<span class="hint">Use this to log in to Dress to Impress!</span>
|
||||||
|
<br />
|
||||||
|
<%= f.text_field :name, autocomplete: "username" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<%= f.label :email %>
|
||||||
|
<span class="hint">This can help you recover your account later.</span>
|
||||||
|
<br />
|
||||||
|
<%= f.email_field :email, autocomplete: "email" %>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<div class="field">
|
||||||
|
<%= f.label :password, "New password" %>
|
||||||
|
<span class="hint">Leave blank if you don't want to change it.</span>
|
||||||
|
<br />
|
||||||
|
<%= f.password_field :password, autocomplete: "new-password" %>
|
||||||
|
<% if @minimum_password_length %>
|
||||||
|
<br />
|
||||||
|
<span class="hint"><%= @minimum_password_length %> characters minimum</span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<%= f.label :password_confirmation, "New password confirmation" %><br />
|
||||||
|
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<%# Current password is only required if you have one! %>
|
||||||
|
<% if @auth_user.uses_password? %>
|
||||||
|
<fieldset>
|
||||||
|
<div class="field">
|
||||||
|
<%= f.label :current_password %>
|
||||||
|
<span class="hint">We need your current password to confirm your changes.</span>
|
||||||
|
<br />
|
||||||
|
<%= f.password_field :current_password, autocomplete: "current-password" %>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<%= f.submit "Save changes" %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if @auth_user.uses_neopass? %>
|
||||||
|
<%= form_with url: user_neopass_connection_path(@auth_user.user),
|
||||||
|
method: :delete, class: "settings-form", data: {
|
||||||
|
turbo_confirm: "Are you sure? Without a NeoPass, you'll need to use " +
|
||||||
|
"your password or your recovery email \"#{@auth_user.email}\" to " +
|
||||||
|
"log in again.\n\nMake sure you have everything all set up first! " +
|
||||||
|
"Otherwise, you might be locked out of this account forever!"
|
||||||
|
} do |form|
|
||||||
|
%>
|
||||||
|
<h2>Your NeoPass</h2>
|
||||||
|
<section class="neopass-info">
|
||||||
|
<strong>
|
||||||
|
NeoPass ID:
|
||||||
|
</strong>
|
||||||
|
<%= @auth_user.neopass_friendly_id %>
|
||||||
|
</section>
|
||||||
|
<section class="neopass-explanation">
|
||||||
|
<p>
|
||||||
|
You can log into your Dress to Impress account with NeoPass, or with
|
||||||
|
your username and password. If you ever lose access to your NeoPass,
|
||||||
|
you can still use "Forgot your password?" to recover your Dress to
|
||||||
|
Impress account, using the Email saved in "Your info".
|
||||||
|
</p>
|
||||||
|
<% if !@auth_user.uses_password? && !@auth_user.email %>
|
||||||
|
<p>
|
||||||
|
You can't remove this NeoPass yet, because you need to either set a
|
||||||
|
password or a recovery email first. (Ideally both!)
|
||||||
|
</p>
|
||||||
|
<% elsif !@auth_user.uses_password? %>
|
||||||
|
<p>
|
||||||
|
Be extra careful here! Your account doesn't have a password set.
|
||||||
|
</p>
|
||||||
|
<% elsif !@auth_user.email? %>
|
||||||
|
<p>
|
||||||
|
Be extra careful here! Your account doesn't have an email set.
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
</section>
|
||||||
|
<%= form.submit "Disconnect your NeoPass",
|
||||||
|
disabled: !@auth_user.uses_password? && !@auth_user.email? %>
|
||||||
|
<% end %>
|
||||||
|
<% elsif can_use_neopass %>
|
||||||
|
<%= 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 %>
|
||||||
|
<%= stylesheet_link_tag "auth_users/edit" %>
|
||||||
|
<% end %>
|
|
@ -1,7 +1,7 @@
|
||||||
<h2>Sign up</h2>
|
<h2>Sign up</h2>
|
||||||
|
|
||||||
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
|
<%= form_with(model: @auth_user, method: :post) do |f| %>
|
||||||
<%= render "devise/shared/error_messages", resource: resource %>
|
<%= render "devise/shared/error_messages", resource: @auth_user %>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Choose a username, and an email address we can use to reset your password.
|
Choose a username, and an email address we can use to reset your password.
|
|
@ -1,44 +0,0 @@
|
||||||
<h2>Settings</h2>
|
|
||||||
|
|
||||||
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
|
|
||||||
<%= render "devise/shared/error_messages", resource: resource %>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<%= f.label :name, 'Username' %><br />
|
|
||||||
<%= f.text_field :name, autofocus: true, autocomplete: "username" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<%= f.label :email %><br />
|
|
||||||
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
|
|
||||||
<div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
|
|
||||||
<%= f.password_field :password, autocomplete: "new-password" %>
|
|
||||||
<% if @minimum_password_length %>
|
|
||||||
<br />
|
|
||||||
<em><%= @minimum_password_length %> characters minimum</em>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<%= f.label :password_confirmation %><br />
|
|
||||||
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
|
|
||||||
<%= f.password_field :current_password, autocomplete: "current-password" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actions">
|
|
||||||
<%= f.submit "Update" %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<%= link_to "Back", :back %>
|
|
|
@ -1,10 +1,10 @@
|
||||||
<h2>Log in</h2>
|
<h2>Log in</h2>
|
||||||
|
|
||||||
<% if @can_use_neopass %>
|
<% if can_use_neopass %>
|
||||||
🌟✨🌟✨🌟✨🌟✨🌟
|
🌟✨🌟✨🌟✨🌟✨🌟
|
||||||
<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!
|
||||||
%>
|
%>
|
||||||
🌟✨🌟✨🌟✨🌟✨🌟
|
🌟✨🌟✨🌟✨🌟✨🌟
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
<% if resource.errors.any? %>
|
<% if resource.errors.any? %>
|
||||||
<div id="error_explanation" data-turbo-cache="false">
|
<div class="error-explanation" data-turbo-cache="false">
|
||||||
<h2>
|
<header>
|
||||||
<%= I18n.t("errors.messages.not_saved",
|
<%= I18n.t("errors.messages.not_saved", count: resource.errors.count,
|
||||||
count: resource.errors.count,
|
resource: "user") %>
|
||||||
resource: resource.class.model_name.human.downcase)
|
</header>
|
||||||
%>
|
|
||||||
</h2>
|
|
||||||
<ul>
|
<ul>
|
||||||
<% resource.errors.full_messages.each do |message| %>
|
<% resource.errors.full_messages.each do |message| %>
|
||||||
<li><%= message %></li>
|
<li><%= message %></li>
|
||||||
|
|
|
@ -1,19 +1,11 @@
|
||||||
<%- if controller_name != 'sessions' %>
|
<%- if controller_name != 'sessions' %>
|
||||||
<%= link_to "Log in", new_session_path(resource_name) %><br />
|
<%= link_to "Log in", new_auth_user_session_path %><br />
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
|
<%- if controller_name != 'auth_users' %>
|
||||||
<%= link_to "Sign up", new_registration_path(resource_name) %><br />
|
<%= link_to "Sign up", new_auth_user_path %><br />
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
|
<%- if controller_name != 'passwords' && controller_name != 'registrations' %>
|
||||||
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
|
<%= link_to "Forgot your password?", new_auth_user_password_path %><br />
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
|
|
||||||
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
|
|
||||||
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
= userbar_contributions_summary(current_user)
|
= userbar_contributions_summary(current_user)
|
||||||
= link_to t('.userbar.items'), user_closet_hangers_path(current_user), :id => 'userbar-items-link'
|
= link_to t('.userbar.items'), user_closet_hangers_path(current_user), :id => 'userbar-items-link'
|
||||||
= link_to t('.userbar.outfits'), current_user_outfits_path
|
= link_to t('.userbar.outfits'), current_user_outfits_path
|
||||||
= link_to t('.userbar.settings'), edit_auth_user_registration_path
|
= link_to t('.userbar.settings'), edit_auth_user_path
|
||||||
= button_to t('.userbar.logout'), destroy_auth_user_session_path, method: :delete,
|
= button_to t('.userbar.logout'), destroy_auth_user_session_path, method: :delete,
|
||||||
params: {return_to: request.fullpath}
|
params: {return_to: request.fullpath}
|
||||||
- else
|
- else
|
||||||
|
|
|
@ -2,7 +2,9 @@ OpenneoImpressItems::Application.routes.draw do
|
||||||
root :to => 'outfits#new'
|
root :to => 'outfits#new'
|
||||||
|
|
||||||
# Login and account management!
|
# Login and account management!
|
||||||
devise_for :auth_users, path: "users"
|
devise_for :auth_users, path: "users", skip: [:registrations]
|
||||||
|
resources :auth_users, only: [:new, :create, :update]
|
||||||
|
get '/users/edit', to: 'auth_users#edit', as: 'edit_auth_user'
|
||||||
|
|
||||||
# The outfit editor!
|
# The outfit editor!
|
||||||
# TODO: It's a bit silly that outfits/new points to outfits#edit.
|
# TODO: It's a bit silly that outfits/new points to outfits#edit.
|
||||||
|
@ -66,6 +68,8 @@ OpenneoImpressItems::Application.routes.draw do
|
||||||
|
|
||||||
resources :neopets_connections, path: 'neopets-connections',
|
resources :neopets_connections, path: 'neopets-connections',
|
||||||
only: [:create, :destroy]
|
only: [:create, :destroy]
|
||||||
|
|
||||||
|
resource :neopass_connection, path: "neopass-connection", only: [:destroy]
|
||||||
end
|
end
|
||||||
get 'users/current-user/closet' => 'closet_hangers#index', :as => :your_items
|
get 'users/current-user/closet' => 'closet_hangers#index', :as => :your_items
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddNeoPassEmailToUsers < ActiveRecord::Migration[7.1]
|
||||||
|
def change
|
||||||
|
add_column :users, :neopass_email, :string
|
||||||
|
end
|
||||||
|
end
|
|
@ -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_01_124406) 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
|
||||||
|
@ -31,7 +31,9 @@ ActiveRecord::Schema[7.1].define(version: 2024_04_01_124406) do
|
||||||
t.datetime "remember_created_at"
|
t.datetime "remember_created_at"
|
||||||
t.string "provider"
|
t.string "provider"
|
||||||
t.string "uid"
|
t.string "uid"
|
||||||
|
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