revise_auth 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []