revise_auth 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 93ae9905de3248b59ccc8901f7c86b6201e7f40150293dc1f4c49c973a7c5145
4
+ data.tar.gz: 4de33a4af773cdcff4ea5a14c40055a89817b4b4acc0214f249740551518284c
5
+ SHA512:
6
+ metadata.gz: 27dbb6e0796fb42449c83ec90916521104f2849cd4b3ed896aea6ebe72ca0478ac8f5d739c74014de0611f53beaee6b1d2395270cf52f4133b7e18dcee39bba0
7
+ data.tar.gz: e56b36a7a646737361d0eaa8f6de42353eb24a09ffe26ae6f20b668facf5c4cd45cb009e0397a01495d38bde81dd04d77ebddf5d5ffd373d923bce50464933fd
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2023 Chris Oliver
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # ReviseAuth
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem "revise_auth"
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install revise_auth
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require "bundler/setup"
2
+
3
+ require "bundler/gem_tasks"
@@ -0,0 +1,29 @@
1
+ class ReviseAuth::EmailController < ReviseAuthController
2
+ before_action :authenticate_user!, except: [:show]
3
+
4
+ # GET /profile/email?confirmation_token=abcdef
5
+ def show
6
+ if User.find_by(confirmation_token: params[:confirmation_token])&.confirm_email_change
7
+ flash[:notice] = "Your email address has been successfully confirmed."
8
+ user_signed_in?
9
+ redirect_to (user_signed_in? ? profile_path : root_path)
10
+ else
11
+ redirect_to root_path, alert: "Unable to confirm email address."
12
+ end
13
+ end
14
+
15
+ def update
16
+ if current_user.update(email_params)
17
+ current_user.send_confirmation_instructions
18
+ flash[:notice] = "A confirmation email has been sent to #{current_user.unconfirmed_email}"
19
+ end
20
+
21
+ redirect_to profile_path
22
+ end
23
+
24
+ private
25
+
26
+ def email_params
27
+ params.require(:user).permit(:unconfirmed_email)
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ class ReviseAuth::PasswordController < ReviseAuthController
2
+ before_action :validate_current_password, only: [:update]
3
+
4
+ def update
5
+ if current_user.update(password_params)
6
+ flash[:notice] = "Your password has been changed successfully."
7
+ end
8
+
9
+ redirect_to profile_path
10
+ end
11
+
12
+ private
13
+
14
+ def password_params
15
+ params.require(:user).permit(:password, :password_confirmation)
16
+ end
17
+ end
@@ -0,0 +1,44 @@
1
+ class ReviseAuth::RegistrationsController < ReviseAuthController
2
+ before_action :authenticate_user!, except: [:new, :create]
3
+
4
+ def new
5
+ @user = User.new
6
+ end
7
+
8
+ def create
9
+ @user = User.new(sign_up_params)
10
+ if @user.save
11
+ login(@user)
12
+ redirect_to root_path
13
+ else
14
+ render :new, status: :unprocessable_entity
15
+ end
16
+ end
17
+
18
+ def edit
19
+ end
20
+
21
+ def update
22
+ if current_user.update(profile_params)
23
+ redirect_to profile_path, notice: "Account updated successfully."
24
+ else
25
+ render :edit, status: :unprocessable_entity
26
+ end
27
+ end
28
+
29
+ def destroy
30
+ current_user.destroy
31
+ logout
32
+ redirect_to root_path, status: :see_other, alert: I18n.t("revise_auth.account_deleted")
33
+ end
34
+
35
+ private
36
+
37
+ def sign_up_params
38
+ params.require(:user).permit(:name, :email, :password, :password_confirmation)
39
+ end
40
+
41
+ def profile_params
42
+ params.require(:user).permit(:name)
43
+ end
44
+ end
@@ -0,0 +1,19 @@
1
+ class ReviseAuth::SessionsController < ReviseAuthController
2
+ def new
3
+ end
4
+
5
+ def create
6
+ if user = User.find_by(email: params[:email])&.authenticate(params[:password])
7
+ login(user)
8
+ redirect_to root_path
9
+ else
10
+ flash[:alert] = I18n.t("revise_auth.invalid_email_or_password")
11
+ render :new, status: :unprocessable_entity
12
+ end
13
+ end
14
+
15
+ def destroy
16
+ logout
17
+ redirect_to root_path
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+ class ReviseAuthController < ApplicationController
2
+ def validate_current_password
3
+ unless current_user.authenticate(params[:current_password])
4
+ flash[:alert] = "Your current password is incorrect. Please try again."
5
+ render :edit, status: :unprocessable_entity
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ class ReviseAuth::Mailer < ApplicationMailer
2
+ def confirm_email
3
+ mail to: params[:user].unconfirmed_email
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ <p>Welcome <%= params[:user].unconfirmed_email %>!</p>
2
+
3
+ <p>You can confirm your account email through the link below:</p>
4
+
5
+ <p><%= link_to 'Confirm my account', profile_email_url(confirmation_token: params[:user].confirmation_token) %></p>
6
+
7
+ <p>This link will expire in 24 hours.</p>
@@ -0,0 +1,71 @@
1
+ <h1>Profile</h1>
2
+
3
+ <%= form_with model: current_user, url: profile_email_path do |form| %>
4
+ <fieldset>
5
+ <legend>Change Email Address</legend>
6
+
7
+ <% if current_user.unconfirmed_email? %>
8
+ <p>Waiting for confirmation of <%= current_user.unconfirmed_email %></p>
9
+ <% end %>
10
+
11
+ <% if form.object.errors.any? %>
12
+ <ul>
13
+ <% form.object.errors.full_messages.each do |message| %>
14
+ <li><%= message %></li>
15
+ <% end %>
16
+ </ul>
17
+ <% end %>
18
+
19
+ <p>Your email address is: <%= current_user.email %>
20
+ <p>To change your email, we will send a confirmation email to your new address to complete the change.</p>
21
+
22
+ <div>
23
+ <%= form.label :unconfirmed_email, "Email address" %>
24
+ <%= form.email_field :unconfirmed_email, required: true %>
25
+ </div>
26
+
27
+ <div>
28
+ <%= form.button "Save Changes" %>
29
+ </div>
30
+ </fieldset>
31
+ <% end %>
32
+
33
+ <%= form_with model: current_user, url: profile_password_path do |form| %>
34
+ <fieldset>
35
+ <legend>Change Password</legend>
36
+
37
+ <% if form.object.errors.any? %>
38
+ <ul>
39
+ <% form.object.errors.full_messages.each do |message| %>
40
+ <li><%= message %></li>
41
+ <% end %>
42
+ </ul>
43
+ <% end %>
44
+
45
+ <div>
46
+ <%= label_tag :current_password %>
47
+ <%= password_field_tag :current_password, nil, required: true %>
48
+ </div>
49
+
50
+ <div>
51
+ <%= form.label :password, "New password" %>
52
+ <%= form.password_field :password, required: true %>
53
+ </div>
54
+
55
+ <div>
56
+ <%= form.label :password_confirmation %>
57
+ <%= form.password_field :password_confirmation, required: true %>
58
+ </div>
59
+
60
+ <div>
61
+ <%= form.button "Save Changes" %>
62
+ </div>
63
+ </fieldset>
64
+ <% end %>
65
+
66
+ <%= form_with url: profile_path, method: :delete do |form| %>
67
+ <fieldset>
68
+ <legend>Delete my account</legend>
69
+ <%= form.button "Delete account", data: { turbo_confirm: "Are you sure?" } %>
70
+ </fieldset>
71
+ <% end %>
@@ -0,0 +1,30 @@
1
+ <h1>Sign Up</h1>
2
+
3
+ <%= form_with model: @user, url: sign_up_path do |form| %>
4
+ <% if form.object.errors.any? %>
5
+ <ul>
6
+ <% form.object.errors.full_messages.each do |message| %>
7
+ <li><%= message %></li>
8
+ <% end %>
9
+ </ul>
10
+ <% end %>
11
+
12
+ <div>
13
+ <%= form.label :email %>
14
+ <%= form.email_field :email, required: true, autofocus: true %>
15
+ </div>
16
+
17
+ <div>
18
+ <%= form.label :password %>
19
+ <%= form.password_field :password, required: true %>
20
+ </div>
21
+
22
+ <div>
23
+ <%= form.label :password_confirmation %>
24
+ <%= form.password_field :password_confirmation, required: true %>
25
+ </div>
26
+
27
+ <div>
28
+ <%= form.button "Sign Up" %>
29
+ </div>
30
+ <% end %>
@@ -0,0 +1,17 @@
1
+ <h1>Log in</h1>
2
+
3
+ <%= form_with url: login_path do |form| %>
4
+ <div>
5
+ <%= form.label :email %>
6
+ <%= form.email_field :email, required: true, autofocus: true %>
7
+ </div>
8
+
9
+ <div>
10
+ <%= form.label :password %>
11
+ <%= form.password_field :password, required: true %>
12
+ </div>
13
+
14
+ <div>
15
+ <%= form.button "Login" %>
16
+ </div>
17
+ <% end %>
@@ -0,0 +1,6 @@
1
+ en:
2
+ revise_auth:
3
+ account_deleted: "Your account has been deleted."
4
+ invalid_email_or_password: "Invalid email or password."
5
+ sign_up_or_login: "Sign up or log in to continue."
6
+
data/config/routes.rb ADDED
@@ -0,0 +1,20 @@
1
+ Rails.application.routes.draw do
2
+ scope module: :revise_auth do
3
+ get "sign_up", to: "registrations#new"
4
+ post "sign_up", to: "registrations#create"
5
+
6
+ get "login", to: "sessions#new"
7
+ post "login", to: "sessions#create"
8
+
9
+ get "profile", to: "registrations#edit"
10
+ patch "profile", to: "registrations#update"
11
+ delete "profile", to: "registrations#destroy"
12
+
13
+ patch "profile/email", to: "email#update"
14
+ patch "profile/password", to: "password#update"
15
+
16
+ get "profile/email", to: "email#show"
17
+
18
+ delete "logout", to: "sessions#destroy"
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ module ReviseAuth
2
+ class Engine < ::Rails::Engine
3
+ initializer "revise_auth.controller" do
4
+ ActiveSupport.on_load(:action_controller_base) do
5
+ include ReviseAuth::Authentication
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,45 @@
1
+ module ReviseAuth
2
+ module Model
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_secure_password
7
+ has_secure_token :confirmation_token
8
+
9
+ validates :email, format: {with: URI::MailTo::EMAIL_REGEXP}, presence: true, uniqueness: true
10
+ validates :unconfirmed_email, format: {with: URI::MailTo::EMAIL_REGEXP}, allow_blank: true
11
+
12
+ before_save do
13
+ self.email = email.downcase
14
+ self.unconfirmed_email = unconfirmed_email&.downcase
15
+ end
16
+ end
17
+
18
+ # Generates a confirmation token and send email to the user
19
+ def send_confirmation_instructions
20
+ update!(
21
+ confirmation_token: self.class.generate_unique_secure_token(length: ActiveRecord::SecureToken::MINIMUM_TOKEN_LENGTH),
22
+ confirmation_sent_at: Time.current
23
+ )
24
+ ReviseAuth::Mailer.with(user: self).confirm_email.deliver_later
25
+ end
26
+
27
+ # Confirms an email address change
28
+ def confirm_email_change
29
+ if confirmation_period_expired?
30
+ false
31
+ else
32
+ update(
33
+ confirmed_at: Time.current,
34
+ email: unconfirmed_email,
35
+ unconfirmed_email: nil
36
+ )
37
+ end
38
+ end
39
+
40
+ # Checks whether the confirmation token is within the valid time
41
+ def confirmation_period_expired?
42
+ confirmation_sent_at.before?(1.day.ago)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,3 @@
1
+ module ReviseAuth
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,65 @@
1
+ require "revise_auth/version"
2
+ require "revise_auth/engine"
3
+
4
+ module ReviseAuth
5
+ autoload :Model, "revise_auth/model"
6
+
7
+ module Authentication
8
+ # Provides methods for controllers and views for authentication
9
+ #
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ helper_method :user_signed_in?
14
+ helper_method :current_user
15
+ end
16
+
17
+ # Returns a boolean whether the user is signed in or not
18
+ def user_signed_in?
19
+ !!current_user
20
+ end
21
+
22
+ # Authenticates the user if not already authenticated
23
+ # Returns a User or nil
24
+ def current_user
25
+ Current.user ||= authenticate_user
26
+ end
27
+
28
+ # Authenticates a user or redirects to the login page
29
+ def authenticate_user!
30
+ redirect_to login_path, alert: I18n.t("revise_auth.sign_up_or_login") unless user_signed_in?
31
+ end
32
+
33
+ # Authenticates the current user
34
+ # - from session cookie
35
+ # - (future) from Authorization header
36
+ def authenticate_user
37
+ Current.user = authenticated_user_from_session
38
+ end
39
+
40
+ # Returns a user from session cookie
41
+ def authenticated_user_from_session
42
+ user_id = session[:user_id]
43
+ return unless user_id
44
+ User.find_by(id: user_id)
45
+ end
46
+
47
+ # Logs in the user
48
+ # - Set Current.user for the current request
49
+ # - Save a session cookie so the next request is authenticated
50
+ def login(user)
51
+ Current.user = user
52
+ session[:user_id] = user.id
53
+ end
54
+
55
+ def logout
56
+ Current.user = nil
57
+ session.delete(:user_id)
58
+ end
59
+ end
60
+
61
+ class Current < ActiveSupport::CurrentAttributes
62
+ # Stores the current user for the request
63
+ attribute :user
64
+ end
65
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :revise_auth do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: revise_auth
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Oliver
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-01-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 7.0.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 7.0.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: bcrypt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.1'
41
+ description: Hotwire compatible authentication for Ruby on Rails apps
42
+ email:
43
+ - excid3@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - MIT-LICENSE
49
+ - README.md
50
+ - Rakefile
51
+ - app/controllers/revise_auth/email_controller.rb
52
+ - app/controllers/revise_auth/password_controller.rb
53
+ - app/controllers/revise_auth/registrations_controller.rb
54
+ - app/controllers/revise_auth/sessions_controller.rb
55
+ - app/controllers/revise_auth_controller.rb
56
+ - app/mailers/revise_auth/mailer.rb
57
+ - app/views/revise_auth/mailer/confirm_email.html.erb
58
+ - app/views/revise_auth/registrations/edit.html.erb
59
+ - app/views/revise_auth/registrations/new.html.erb
60
+ - app/views/revise_auth/sessions/new.html.erb
61
+ - config/locales/en.yml
62
+ - config/routes.rb
63
+ - lib/revise_auth.rb
64
+ - lib/revise_auth/engine.rb
65
+ - lib/revise_auth/model.rb
66
+ - lib/revise_auth/version.rb
67
+ - lib/tasks/revise_auth_tasks.rake
68
+ homepage: https://github.com/excid3/revise_auth
69
+ licenses:
70
+ - MIT
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubygems_version: 3.4.3
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: Simple authentication for Ruby on Rails apps
91
+ test_files: []