yellin 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +57 -0
  3. data/README.md +96 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/config/yellin_manifest.js +2 -0
  6. data/app/assets/javascripts/yellin/account_activations.js +2 -0
  7. data/app/assets/javascripts/yellin/application.js +13 -0
  8. data/app/assets/javascripts/yellin/password_resets.js +2 -0
  9. data/app/assets/javascripts/yellin/registrations.js +2 -0
  10. data/app/assets/javascripts/yellin/sessions.js +2 -0
  11. data/app/assets/stylesheets/yellin/account_activations.css +4 -0
  12. data/app/assets/stylesheets/yellin/application.css +15 -0
  13. data/app/assets/stylesheets/yellin/password_resets.css +4 -0
  14. data/app/assets/stylesheets/yellin/registrations.css +4 -0
  15. data/app/assets/stylesheets/yellin/sessions.css +4 -0
  16. data/app/controllers/yellin/account_activations_controller.rb +24 -0
  17. data/app/controllers/yellin/application_controller.rb +7 -0
  18. data/app/controllers/yellin/password_resets_controller.rb +63 -0
  19. data/app/controllers/yellin/registrations_controller.rb +27 -0
  20. data/app/controllers/yellin/sessions_controller.rb +36 -0
  21. data/app/helpers/yellin/account_activations_helper.rb +4 -0
  22. data/app/helpers/yellin/application_helper.rb +4 -0
  23. data/app/helpers/yellin/password_resets_helper.rb +4 -0
  24. data/app/helpers/yellin/registrations_helper.rb +4 -0
  25. data/app/helpers/yellin/sessions_helper.rb +64 -0
  26. data/app/jobs/yellin/application_job.rb +4 -0
  27. data/app/mailers/yellin/application_mailer.rb +6 -0
  28. data/app/mailers/yellin/user_mailer.rb +23 -0
  29. data/app/models/yellin/application_record.rb +5 -0
  30. data/app/views/layouts/yellin/mailer.html.erb +13 -0
  31. data/app/views/layouts/yellin/mailer.text.erb +1 -0
  32. data/app/views/yellin/password_resets/edit.html.erb +14 -0
  33. data/app/views/yellin/password_resets/new.html.erb +9 -0
  34. data/app/views/yellin/registrations/_form.html.erb +14 -0
  35. data/app/views/yellin/registrations/new.html.erb +5 -0
  36. data/app/views/yellin/sessions/new.html.erb +18 -0
  37. data/app/views/yellin/shared/_error_messages.html.erb +12 -0
  38. data/app/views/yellin/user_mailer/account_activation.html.erb +9 -0
  39. data/app/views/yellin/user_mailer/account_activation.text.erb +5 -0
  40. data/app/views/yellin/user_mailer/password_reset.html.erb +9 -0
  41. data/app/views/yellin/user_mailer/password_reset.text.erb +9 -0
  42. data/config/routes.rb +11 -0
  43. data/lib/generators/active_record/templates/migration_create.rb +15 -0
  44. data/lib/generators/active_record/templates/migration_update.rb +27 -0
  45. data/lib/generators/active_record/yellin_generator.rb +47 -0
  46. data/lib/generators/yellin/USAGE +8 -0
  47. data/lib/generators/yellin/templates/initializer.rb +29 -0
  48. data/lib/generators/yellin/yellin_generator.rb +13 -0
  49. data/lib/tasks/yellin_tasks.rake +27 -0
  50. data/lib/yellin/acts_as_user.rb +87 -0
  51. data/lib/yellin/engine.rb +11 -0
  52. data/lib/yellin/version.rb +3 -0
  53. data/lib/yellin.rb +43 -0
  54. metadata +155 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 69ebc2ac5998feed166538df0c55de6a2e4bece7
4
+ data.tar.gz: 6ca97b96038a8039843d2c9151704bd7dc0ff29a
5
+ SHA512:
6
+ metadata.gz: 371978b77a2a02cf8fc7290267b7d8faf7c68b8af8b0df61261de3f35297bf07d0f49198dfeeb6d0b70d6590322d4255682da1c36ac8ca0c2344df97d2e6b5d4
7
+ data.tar.gz: 6b12e489f519fe28545128f4d2d13ae6335babce08724b315c22e6f58afad07c5886a8796b5deb0b0fd9dddefe0b052baeb17db663571c6ba3f3043e9573d59d
data/LICENSE ADDED
@@ -0,0 +1,57 @@
1
+ This Engine is based on, and re-uses code from Michael Hartl's Ruby on Rails
2
+ Tutorial. The original code is licensed under the MIT and Beerware licenses
3
+ (see below). New modifications are similarly licensed under the MIT license:
4
+
5
+ =============================================================================
6
+ The MIT License
7
+
8
+ Copyright (c) 2017 Luke Hogan
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in
18
+ all copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ THE SOFTWARE.
27
+
28
+
29
+ Original licenses:
30
+ =============================================================================
31
+ Copyright 2016 Michael Hartl
32
+
33
+ Permission is hereby granted, free of charge, to any person obtaining
34
+ a copy of this software and associated documentation files (the
35
+ "Software"), to deal in the Software without restriction, including
36
+ without limitation the rights to use, copy, modify, merge, publish,
37
+ distribute, sublicense, and/or sell copies of the Software, and to
38
+ permit persons to whom the Software is furnished to do so, subject to
39
+ the following conditions:
40
+
41
+ The above copyright notice and this permission notice shall be
42
+ included in all copies or substantial portions of the Software.
43
+
44
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
45
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
46
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
47
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
48
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
49
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
50
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
51
+
52
+ =============================================================================
53
+ THE BEERWARE LICENSE (Revision 42)
54
+
55
+ Michael Hartl wrote this code. As long as you retain this notice you can do
56
+ whatever you want with this stuff. If we meet some day, and you think this
57
+ stuff is worth it, you can buy me a beer in return.
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # Yellin
2
+ Sometimes you need secure authentication for your Rails app, but a huge library like Devise is overkill, and implementing your own using `has_secure_password` involves a bunch of typing (especially the tests). This is what Yellin is for -- it's a small Rails engine that implements a simple but secure authorization mechanism using `has_secure_password`.
3
+
4
+ ### Features
5
+ * User registration
6
+ * Account activation
7
+ * Session authentication
8
+ * Password reset
9
+
10
+ ### Password Resets
11
+ Matasano (now part of NCC Group) considers automated password reset one of their 7 Deadly Web App Features. The implementation here takes into careful consideration the advice in [this discussion](https://news.ycombinator.com/item?id=5033513).
12
+
13
+ ### Credit where credit is due
14
+ This gem is heavily based on the authentication system developed in Michael Hartl's [Ruby on Rails Tutorial](http://railstutorial.org). Basically, I've always liked most of that code, but I got tired of re-implementing my own modified version whenever I didn't want to use Devise. So I did the obvious thing and enginified it.
15
+
16
+ ## Usage
17
+ Yellin provides a generator that takes care of all configuration you need to use it. Run the generator with the name of the user model class you want to use. If the model already exists, it will be updated. Otherwise, it will be created from scratch.
18
+
19
+ ```bash
20
+ $ rails generate yellin User
21
+ ```
22
+
23
+ If you decide Yellin is lame and need to remove it, you can use the normal `destroy` command:
24
+
25
+ ```bash
26
+ $ rails destroy yellin User
27
+ ```
28
+
29
+ ### Configuration options
30
+ The generator installs a generator to `config/initializers/yellin.rb`. It looks like this:
31
+
32
+ ```ruby
33
+ Yellin.app_name = Default App Name
34
+ Yellin.default_from_address = 'noreply@example.com'
35
+ Yellin.minimum_password_length = 12
36
+ Yellin.reset_timeout_hours = 2
37
+ Yellin.user_class = "User"
38
+ ```
39
+
40
+ The last line (`Yellin.user_class`) is absolutely necessary and will be set correctly based on the model to which you point the generator. *The quotes are essential.*
41
+
42
+ `Yellin.app_name` is used to identify your app in the emails sent to users when they sign up or reset their passwords. Set it accordingly. `Yellin.default_from_address` is the email address from which these emails will be sent.
43
+
44
+ In order to send emails, you must have ActionMailer configured correctly. For example, in development:
45
+
46
+ ```ruby
47
+ # config/environments/development.rb
48
+ Rails.application.configure do
49
+ ...
50
+ config.action_mailer.raise_delivery_errors = true
51
+ config.action_mailer.delivery_method = :test
52
+ host = 'localhost:3000'
53
+ config.action_mailer.default_url_options = { host: host, protocol: 'http' }
54
+ ...
55
+ end
56
+ ```
57
+
58
+ You also must have a root route defined in `routes.rb`, or Yellin will break in several places (probably along with lots of other things).
59
+
60
+ ### Views and Forms
61
+ You'll likely want to modify the views and forms used by Yellin -- probably at least the user signup form. To encourage you to modify these views, Yellin provides a rake task to copy the default files into your workspace:
62
+
63
+ ```bash
64
+ $ rails yellin:install:views
65
+ ```
66
+
67
+ You'll find the files copied under `app/views/yellin`. Modify them in place -- if you move them, expect Yellin to misbehave.
68
+
69
+ If you change your mind, you can remove the view files like so:
70
+
71
+ ```bash
72
+ $ rails yellin:uninstall:views
73
+ ```
74
+
75
+ ## Installation
76
+ Add this line to your application's Gemfile:
77
+
78
+ ```ruby
79
+ gem 'yellin'
80
+ ```
81
+
82
+ And then execute:
83
+ ```bash
84
+ $ bundle install
85
+ ```
86
+
87
+ Or install it yourself as:
88
+ ```bash
89
+ $ gem install yellin
90
+ ```
91
+
92
+ ## Contributing
93
+ Bugfixes, enhancements, spelling corrections are all welcome. Use the pull request button :)
94
+
95
+ ## License
96
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Yellin'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ require 'bundler/gem_tasks'
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task default: :test
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts/yellin .js
2
+ //= link_directory ../stylesheets/yellin .css
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file. JavaScript code in this file should be added after the last require_* statement.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,24 @@
1
+ require_dependency "yellin/application_controller"
2
+
3
+ module Yellin
4
+ class AccountActivationsController < ApplicationController
5
+
6
+ def edit
7
+ @user = Yellin.user_class.find_by(email: params[:email])
8
+ if @user && !@user.activated? && @user.authenticated?(:activation, params[:id])
9
+ @user.activate
10
+ log_in @user
11
+ flash[:success] = Yellin.flash[:activation_success]
12
+ begin
13
+ redirect_to @user
14
+ rescue NoMethodError
15
+ redirect_to main_app.root_url
16
+ end
17
+ else
18
+ flash[:danger] = Yellin.flash[:activation_invalid]
19
+ redirect_to main_app.root_url
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ module Yellin
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+
5
+ include Yellin::SessionsHelper
6
+ end
7
+ end
@@ -0,0 +1,63 @@
1
+ require_dependency "yellin/application_controller"
2
+
3
+ module Yellin
4
+ class PasswordResetsController < ApplicationController
5
+ before_action :get_user, only: [:edit, :update]
6
+ before_action :valid_user, only: [:edit, :update]
7
+ before_action :check_expiration, only: [:edit, :update]
8
+
9
+ def new
10
+ end
11
+
12
+ def create
13
+ @user = Yellin.user_class.find_by(email: params[:password_reset][:email].downcase)
14
+ if @user
15
+ @user.create_reset_digest
16
+ @user.send_password_reset_email
17
+ flash[:info] = Yellin.flash[:reset_sent]
18
+ redirect_to main_app.root_url
19
+ else
20
+ flash.now[:danger] = Yellin.flash[:reset_invalid]
21
+ render 'new'
22
+ end
23
+ end
24
+
25
+ def edit
26
+ end
27
+
28
+ def update
29
+ if params[:user][:password].empty?
30
+ @user.errors.add(:password, "can't be empty")
31
+ render 'edit'
32
+ elsif @user.reset_password(user_params)
33
+ flash[:success] = Yellin.flash[:reset_success]
34
+ redirect_to login_path
35
+ else
36
+ render 'edit'
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def user_params
43
+ params.require(:user).permit(:password, :password_confirmation)
44
+ end
45
+
46
+ def get_user
47
+ @user = Yellin.user_class.find_by(email: params[:email])
48
+ end
49
+
50
+ def valid_user
51
+ unless (@user && @user.activated? && @user.authenticated?(:reset, params[:id]))
52
+ redirect_to main_app.root_url
53
+ end
54
+ end
55
+
56
+ def check_expiration
57
+ if @user.password_reset_expired?
58
+ flash[:danger] = Yellin.flash[:reset_expired]
59
+ redirect_to new_password_reset_url
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,27 @@
1
+ require_dependency "yellin/application_controller"
2
+
3
+ module Yellin
4
+ class RegistrationsController < ApplicationController
5
+
6
+ def create
7
+ @user = Yellin.user_class.new(signup_params)
8
+ if @user.save
9
+ @user.send_activation_email
10
+ flash[:info] = Yellin.flash[:activation_pending]
11
+ redirect_to main_app.root_url
12
+ else
13
+ render 'new'
14
+ end
15
+ end
16
+
17
+ def new
18
+ @user = Yellin.user_class.new
19
+ end
20
+
21
+ private
22
+ def signup_params
23
+ user_key = Yellin.user_class.model_name.param_key
24
+ params.require(user_key).permit(:email, :password, :password_confirmation)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,36 @@
1
+ require_dependency "yellin/application_controller"
2
+
3
+ module Yellin
4
+ class SessionsController < ApplicationController
5
+
6
+ def create
7
+ @user = Yellin.user_class.find_by(email: params[:session][:email].downcase)
8
+ if @user && @user.authenticate(params[:session][:password])
9
+ if @user.activated?
10
+ log_in(@user)
11
+ params[:session][:remember_me] == '1' ? remember(@user) : forget(@user)
12
+ begin
13
+ redirect_back_or @user
14
+ rescue NoMethodError
15
+ redirect_to main_app.root_url
16
+ end
17
+ else
18
+ flash[:warning] = Yellin.flash[:account_inactive]
19
+ redirect_to main_app.root_url
20
+ end
21
+ else
22
+ flash.now[:danger] = Yellin.flash[:bad_credentials]
23
+ render 'new'
24
+ end
25
+ end
26
+
27
+ def destroy
28
+ log_out if logged_in?
29
+ redirect_to main_app.root_url
30
+ end
31
+
32
+ def new
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,4 @@
1
+ module Yellin
2
+ module AccountActivationsHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Yellin
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Yellin
2
+ module PasswordResetsHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Yellin
2
+ module RegistrationsHelper
3
+ end
4
+ end
@@ -0,0 +1,64 @@
1
+ module Yellin
2
+ module SessionsHelper
3
+
4
+ def current_user
5
+ if (user_id = session[:user_id])
6
+ @current_user ||= Yellin.user_class.find_by(id: user_id)
7
+ elsif (user_id = cookies.signed[:user_id])
8
+ user = Yellin.user_class.find_by(id: user_id)
9
+ if user && user.authenticated?(:remember, cookies[:remember_token])
10
+ log_in user
11
+ @current_user = user
12
+ end
13
+ end
14
+ end
15
+
16
+ def current_user?(user)
17
+ user == current_user
18
+ end
19
+
20
+ def log_in(user)
21
+ session[:user_id] = user.id
22
+ end
23
+
24
+ def log_out
25
+ forget(current_user)
26
+ session.delete(:user_id)
27
+ @current_user = nil
28
+ end
29
+
30
+ def logged_in?
31
+ !current_user.nil?
32
+ end
33
+
34
+ def remember(user)
35
+ user.remember
36
+ cookies.permanent.signed[:user_id] = user.id
37
+ cookies.permanent[:remember_token] = user.remember_token
38
+ end
39
+
40
+ def forget(user)
41
+ user.forget
42
+ cookies.delete(:user_id)
43
+ cookies.delete(:remember_token)
44
+ end
45
+
46
+ def store_location
47
+ session[:forwarding_url] = request.original_url if request.get?
48
+ end
49
+
50
+ def redirect_back_or(default)
51
+ redirect_to(session[:forwarding_url] || default)
52
+ session.delete(:forwarding_url)
53
+ end
54
+
55
+ def logged_in_user
56
+ unless logged_in?
57
+ store_location
58
+ flash[:danger] = "Please sign in."
59
+ redirect_to login_url
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,4 @@
1
+ module Yellin
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Yellin
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: Yellin.default_from_address
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,23 @@
1
+ module Yellin
2
+ class UserMailer < ApplicationMailer
3
+ # Subject can be set in your I18n file at config/locales/en.yml
4
+ # with the following lookup:
5
+ #
6
+ # en.user_mailer.account_activation.subject
7
+ #
8
+ def account_activation(user)
9
+ @user = user
10
+ mail to: user.email, subject: 'Account activation'
11
+ end
12
+
13
+ # Subject can be set in your I18n file at config/locales/en.yml
14
+ # with the following lookup:
15
+ #
16
+ # en.user_mailer.password_reset.subject
17
+ #
18
+ def password_reset(user)
19
+ @user = user
20
+ mail to: user.email, subject: 'Password reset'
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ module Yellin
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5
+ <style>
6
+ /* Email styles need to be inline */
7
+ </style>
8
+ </head>
9
+
10
+ <body>
11
+ <%= yield %>
12
+ </body>
13
+ </html>
@@ -0,0 +1 @@
1
+ <%= yield %>
@@ -0,0 +1,14 @@
1
+ <% provide(:title, "Reset password") %>
2
+ <h1>Reset password</h1>
3
+
4
+ <%= form_for(@user, url: password_reset_path(params[:id])) do |f| %>
5
+ <%= render 'yellin/shared/error_messages', object: f.object %>
6
+ <%= hidden_field_tag :email, @user.email %>
7
+ <%= f.label :password %>
8
+ <%= f.password_field :password, class: 'form-control' %>
9
+
10
+ <%= f.label :password_confirmation, "Confirmation" %>
11
+ <%= f.password_field :password_confirmation, class: 'form-control' %>
12
+
13
+ <%= f.submit "Update password", class: "btn btn-primary" %>
14
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <% provide(:title, "Forgot password") %>
2
+
3
+ <h1>Forgot password</h1>
4
+ <%= form_for(:password_reset, url: password_resets_path) do |f| %>
5
+ <%= f.label :email %>
6
+ <%= f.email_field :email, class: 'form-control' %>
7
+
8
+ <%= f.submit "Submit", class: "btn btn-primary" %>
9
+ <% end %>
@@ -0,0 +1,14 @@
1
+ <%= form_for(@user, url: yield(:form_action)) do |f| %>
2
+ <%= render 'yellin/shared/error_messages', object: f.object %>
3
+
4
+ <%= f.label :email %>
5
+ <%= f.email_field :email %>
6
+
7
+ <%= f.label :password %>
8
+ <%= f.password_field :password %>
9
+
10
+ <%= f.label :password_confirmation, "Confirm password" %>
11
+ <%= f.password_field :password_confirmation %>
12
+
13
+ <%= f.submit yield(:button_text), class: "btn btn-primary" %>
14
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <% provide(:title, 'Sign up') %>
2
+ <% provide(:form_action, signup_path) %>
3
+ <% provide(:button_text, "Create my account") %>
4
+ <h1>Sign up</h1>
5
+ <%= render 'form' %>
@@ -0,0 +1,18 @@
1
+ <% provide(:title, "Sign in") %>
2
+ <h1>Sign in</h1>
3
+ <%= form_for(:session, url: login_path) do |f| %>
4
+ <%= f.label :email %>
5
+ <%= f.email_field :email %>
6
+
7
+ <%= f.label :password %>
8
+ <%= link_to "(forgot password)", new_password_reset_path %>
9
+ <%= f.password_field :password %>
10
+
11
+ <%= f.label :remember_me, class: "checkbox inline" do %>
12
+ <%= f.check_box :remember_me %>
13
+ <span>Remember me on this computer</span>
14
+ <% end %>
15
+
16
+ <%= f.submit "Sign in", class: "btn btn-primary" %>
17
+ <% end %>
18
+ <p>New user? <%= link_to "Sign up now", signup_path %></p>
@@ -0,0 +1,12 @@
1
+ <% if object.errors.any? %>
2
+ <div id="error_explanation">
3
+ <div class="alert alert-danger">
4
+ The form contains <%= pluralize(object.errors.count, "error") %>.
5
+ </div>
6
+ <ul>
7
+ <% object.errors.full_messages.each do |msg| %>
8
+ <li><%= msg %></li>
9
+ <% end %>
10
+ </ul>
11
+ </div>
12
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <h1>Account activation</h1>
2
+
3
+ <p>
4
+ Welcome to <%= Yellin.app_name %>
5
+ </p>
6
+
7
+ <p>To activate your account, click here:</p>
8
+
9
+ <%= link_to "Activate", edit_account_activation_url(@user.activation_token, email: @user.email) %>
@@ -0,0 +1,5 @@
1
+ Welcome to <%= Yellin.app_name %>
2
+
3
+ To activate your account, click here:
4
+
5
+ <%= edit_account_activation_url(@user.activation_token, email: @user.email) %>
@@ -0,0 +1,9 @@
1
+ <h1>Password reset</h1>
2
+
3
+ <p>To reset your password, click the link below:</p>
4
+
5
+ <%= link_to "Reset password", edit_password_reset_url(@user.reset_token, email: @user.email) %>
6
+
7
+ <p>This link will expire in two hours.</p>
8
+
9
+ <p>If you did not request your password to be reset, please ignore this message and your password will stay as it is.</p>
@@ -0,0 +1,9 @@
1
+ Password reset
2
+
3
+ To reset your password, click the link below:
4
+
5
+ <%= edit_password_reset_url(@user.reset_token, email: @user.email) %>
6
+
7
+ This link will expire in two hours.
8
+
9
+ If you did not request your password to be reset, please ignore this message and your password will stay as it is.
data/config/routes.rb ADDED
@@ -0,0 +1,11 @@
1
+ Yellin::Engine.routes.draw do
2
+ get 'signup', to: 'registrations#new'
3
+ post 'signup', to: 'registrations#create'
4
+ get 'login', to: 'sessions#new'
5
+ post 'login', to: 'sessions#create'
6
+ delete 'logout', to: 'sessions#destroy'
7
+ get 'password_resets/new'
8
+ get 'password_resets/edit'
9
+ resources :account_activations, only: [:edit]
10
+ resources :password_resets, only: [:create, :edit, :new, :update]
11
+ end
@@ -0,0 +1,15 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :<%= table_name %><%= primary_key_type %> do |t|
4
+ t.string :email
5
+ t.string :password_digest
6
+ t.string :remember_digest
7
+ t.string :activation_digest
8
+ t.datetime :activated_at
9
+ t.string :reset_digest
10
+ t.datetime :reset_sent_at
11
+ t.timestamps
12
+ t.index [:email], name: :index_users_on_email, unique: true
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def self.up
3
+ change_table :<%= table_name %> do |t|
4
+ t.string :email
5
+ t.string :password_digest
6
+ t.string :remember_digest
7
+ t.string :activation_digest
8
+ t.datetime :activated_at
9
+ t.string :reset_digest
10
+ t.datetime :reset_sent_at
11
+ t.index [:email], name: :index_users_on_email, unique: true
12
+ end
13
+ end
14
+
15
+ def self.down
16
+ raise ActiveRecord::IrreversibleMigration
17
+ ## Uncomment and edit below to remove any fields that were in your original model and you want to keep
18
+ # remove_column :<%= table_name %>, :email, :string
19
+ # remove_column :<%= table_name %>, :password_digest, :string
20
+ # remove_column :<%= table_name %>, :remember_digest, :string
21
+ # remove_column :<%= table_name %>, :activation_digest, :string
22
+ # remove_column :<%= table_name %>, :activated_at, :datetime
23
+ # remove_column :<%= table_name %>, :reset_digest, :string
24
+ # remove_column :<%= table_name %>, :reset_sent_at, :datetime
25
+ # remove_index :<%= table_name %>, name: :index_users_on_email
26
+ end
27
+ end
@@ -0,0 +1,47 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module ActiveRecord
4
+ module Generators
5
+ class YellinGenerator < ActiveRecord::Generators::Base
6
+ source_root File.expand_path('../templates', __FILE__)
7
+
8
+ def handle_migration
9
+ @preexisting_model = (behavior == :invoke && model_exists?) || (behavior == :revoke && migration_exists?)
10
+ if @preexisting_model
11
+ migration_template "migration_update.rb", "db/migrate/add_yellin_to_#{table_name}.rb"
12
+ else
13
+ migration_template "migration_create.rb", "db/migrate/yellin_create_#{table_name}.rb"
14
+ end
15
+ end
16
+
17
+ def handle_model
18
+ yellinize_model if behavior == :revoke
19
+ unless @preexisting_model
20
+ invoke "active_record:model", [name], migration: false
21
+ end
22
+ yellinize_model if behavior == :invoke
23
+ end
24
+
25
+ private
26
+ def yellinize_model
27
+ # Avoid subtracting from non-existent file
28
+ if model_exists?
29
+ inject_into_file "app/models/#{file_name}.rb", after: "class #{class_name} < ApplicationRecord\n" do <<-YELLIN
30
+ include Yellin::ActsAsUser
31
+ acts_as_user
32
+ YELLIN
33
+ end
34
+ end
35
+ end
36
+
37
+ def model_exists?
38
+ File.exists? File.join(destination_root, "app/models", "#{file_name}.rb")
39
+ end
40
+
41
+ def migration_exists?
42
+ pattern = File.join(destination_root, "db/migrate", "*_add_yellin_to_#{table_name}.rb")
43
+ Dir.glob(pattern).first
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ rails generate yellin Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -0,0 +1,29 @@
1
+ # this is used to identify the application in account activation emails
2
+ Yellin.app_name = <%= Rails.application.class.parent %>
3
+
4
+ # this is used in the mailer to send account activation and password reset emails
5
+ Yellin.default_from_address = "noreply@example.com"
6
+
7
+ # this defines the notifications seen by the user
8
+ Yellin.flash = {
9
+ account_inactive: "Account not activated. Check your email for the activation link.",
10
+ activation_invalid: "Invalid activation link.",
11
+ activation_pending: "Please check your email to activate your account.",
12
+ activation_success: "Account activated.",
13
+ bad_credentials: "Invalid email/password combination.",
14
+ login_required: "Please sign in.",
15
+ reset_expired: "Password reset has expired.",
16
+ reset_invalid: "Email address not found.",
17
+ reset_sent: "Email sent with password reset instructions.",
18
+ reset_success: "Your password has been reset.",
19
+ }
20
+
21
+ # this defines the minimum acceptable length of a password
22
+ Yellin.password_minimum_length = 12
23
+
24
+ # this is the number of hours after which a password reset expires. Should be an integer.
25
+ Yellin.reset_timeout_hours = 2
26
+
27
+ # this is the name of the class implementing the User API
28
+ Yellin.user_class = "<%= name %>"
29
+
@@ -0,0 +1,13 @@
1
+ class YellinGenerator < Rails::Generators::NamedBase
2
+ source_root File.expand_path('../templates', __FILE__)
3
+
4
+ def copy_initializer_file
5
+ template "initializer.rb", "config/initializers/yellin.rb"
6
+ end
7
+
8
+ def mount_engine
9
+ route "mount Yellin::Engine => '/'"
10
+ end
11
+
12
+ hook_for :orm
13
+ end
@@ -0,0 +1,27 @@
1
+ require 'fileutils'
2
+
3
+ namespace :yellin do
4
+ namespace :install do
5
+ desc "Install Yellin views"
6
+ task :views do
7
+ source_root = Gem.loaded_specs['yellin'].full_gem_path
8
+ Dir.glob(File.join(source_root, "app/views/**/*.erb")).each do |view|
9
+ view.slice!(source_root)
10
+ path = view.split('/')
11
+ filename = path.pop
12
+ FileUtils.mkdir_p(File.join(Rails.root, path))
13
+ source = File.join(source_root, path, filename)
14
+ target = File.join(Rails.root, path, filename)
15
+ copy_file source, target
16
+ end
17
+ end
18
+ end
19
+
20
+ namespace :uninstall do
21
+ desc "Uninstall Yellin views"
22
+ task :views do
23
+ FileUtils.rm_rf(File.join(Rails.root, "app/views/yellin"))
24
+ FileUtils.rm_rf(File.join(Rails.root, "app/views/layouts/yellin"))
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,87 @@
1
+ module Yellin
2
+ module ActsAsUser
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ end
7
+
8
+ module ClassMethods
9
+ def acts_as_user
10
+ attr_accessor :remember_token, :activation_token, :reset_token
11
+ has_secure_password
12
+ before_save :downcase_email
13
+ before_create :create_activation_digest
14
+ validates :password, presence: true, length: { minimum: Yellin.password_minimum_length }, allow_nil: true
15
+ # See https://davidcel.is/posts/stop-validating-email-addresses-with-regex/ for regex reasoning
16
+ validates :email, presence: true, length: { maximum: 255 }, format: { with: /@/ },
17
+ uniqueness: { case_sensitive: false }
18
+
19
+ def self.digest(string)
20
+ cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
21
+ BCrypt::Password.create(string, cost: cost)
22
+ end
23
+
24
+ def self.new_token
25
+ SecureRandom.urlsafe_base64
26
+ end
27
+
28
+ include Yellin::ActsAsUser::LocalInstanceMethods
29
+ end
30
+ end
31
+
32
+ module LocalInstanceMethods
33
+ def activate
34
+ update_attribute(:activated_at, Time.zone.now)
35
+ end
36
+
37
+ def activated?
38
+ !activated_at.nil? && activated_at < Time.zone.now
39
+ end
40
+
41
+ def authenticated?(attribute, token)
42
+ digest = send("#{attribute}_digest")
43
+ return false if digest.nil?
44
+ BCrypt::Password.new(digest).is_password?(token)
45
+ end
46
+
47
+ def create_reset_digest
48
+ self.reset_token = Yellin.user_class.new_token
49
+ update_columns(reset_digest: Yellin.user_class.digest(reset_token), reset_sent_at: Time.zone.now)
50
+ end
51
+
52
+ def forget
53
+ update_attribute(:remember_digest, nil)
54
+ end
55
+
56
+ def password_reset_expired?
57
+ reset_sent_at < Yellin.reset_timeout_hours.hours.ago
58
+ end
59
+
60
+ def remember
61
+ self.remember_token = Yellin.user_class.new_token
62
+ update_attribute(:remember_digest, Yellin.user_class.digest(remember_token))
63
+ end
64
+
65
+ def reset_password(params)
66
+ update_attributes(password: params[:password], password_confirmation: params[:password_confirmation], reset_digest: nil)
67
+ end
68
+
69
+ def send_activation_email
70
+ UserMailer.account_activation(self).deliver_now
71
+ end
72
+
73
+ def send_password_reset_email
74
+ UserMailer.password_reset(self).deliver_now
75
+ end
76
+
77
+ def downcase_email
78
+ self.email.downcase!
79
+ end
80
+
81
+ def create_activation_digest
82
+ self.activation_token = Yellin.user_class.new_token
83
+ self.activation_digest = Yellin.user_class.digest(activation_token)
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,11 @@
1
+ module Yellin
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Yellin
4
+
5
+ initializer 'yellin.action_controller' do |app|
6
+ ActiveSupport.on_load :action_controller do
7
+ helper Yellin::SessionsHelper
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module Yellin
2
+ VERSION = '0.1.0'
3
+ end
data/lib/yellin.rb ADDED
@@ -0,0 +1,43 @@
1
+ require "yellin/engine"
2
+ require "yellin/acts_as_user"
3
+
4
+ module Yellin
5
+ mattr_accessor :app_name, :default_from_address, :flash, :password_minimum_length, :reset_timeout_hours, :user_class
6
+
7
+ def self.app_name
8
+ @@app_name || Rails.application.class.parent
9
+ end
10
+
11
+ def self.default_from_address
12
+ @@default_from_address || 'noreply@exmaple.com'
13
+ end
14
+
15
+ def self.flash
16
+ default_flash = {
17
+ account_inactive: "Account not activated. Check your email for the activation link.",
18
+ activation_invalid: "Invalid activation link.",
19
+ activation_pending: "Please check your email to activate your account.",
20
+ activation_success: "Account activated.",
21
+ bad_credentials: "Invalid email/password combination.",
22
+ login_required: "Please sign in.",
23
+ reset_expired: "Password reset has expired.",
24
+ reset_invalid: "Email address not found.",
25
+ reset_sent: "Email sent with password reset instructions.",
26
+ reset_success: "Your password has been reset.",
27
+ }
28
+ @@flash || default_flash
29
+ end
30
+
31
+ def self.password_minimum_length
32
+ @@password_minimum_length || 12
33
+ end
34
+
35
+ def self.reset_timeout_hours
36
+ @@reset_timeout_hours || 2
37
+ end
38
+
39
+ def self.user_class
40
+ @@user_class.constantize
41
+ end
42
+
43
+ end
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yellin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Luke Hogan
8
+ - Michael Hartl
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2017-04-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 5.0.2
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 5.0.2
28
+ - !ruby/object:Gem::Dependency
29
+ name: bcrypt
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 3.1.11
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 3.1.11
42
+ - !ruby/object:Gem::Dependency
43
+ name: sqlite3
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rails-controller-testing
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ description: Rails engine providing user authentication and basic account management
71
+ (account confirmation, password reset); Based on authentication framework developed
72
+ during Michael Hartl's Ruby on Rails Tutorial.
73
+ email:
74
+ - hoganld@gmail.com
75
+ executables: []
76
+ extensions: []
77
+ extra_rdoc_files: []
78
+ files:
79
+ - LICENSE
80
+ - README.md
81
+ - Rakefile
82
+ - app/assets/config/yellin_manifest.js
83
+ - app/assets/javascripts/yellin/account_activations.js
84
+ - app/assets/javascripts/yellin/application.js
85
+ - app/assets/javascripts/yellin/password_resets.js
86
+ - app/assets/javascripts/yellin/registrations.js
87
+ - app/assets/javascripts/yellin/sessions.js
88
+ - app/assets/stylesheets/yellin/account_activations.css
89
+ - app/assets/stylesheets/yellin/application.css
90
+ - app/assets/stylesheets/yellin/password_resets.css
91
+ - app/assets/stylesheets/yellin/registrations.css
92
+ - app/assets/stylesheets/yellin/sessions.css
93
+ - app/controllers/yellin/account_activations_controller.rb
94
+ - app/controllers/yellin/application_controller.rb
95
+ - app/controllers/yellin/password_resets_controller.rb
96
+ - app/controllers/yellin/registrations_controller.rb
97
+ - app/controllers/yellin/sessions_controller.rb
98
+ - app/helpers/yellin/account_activations_helper.rb
99
+ - app/helpers/yellin/application_helper.rb
100
+ - app/helpers/yellin/password_resets_helper.rb
101
+ - app/helpers/yellin/registrations_helper.rb
102
+ - app/helpers/yellin/sessions_helper.rb
103
+ - app/jobs/yellin/application_job.rb
104
+ - app/mailers/yellin/application_mailer.rb
105
+ - app/mailers/yellin/user_mailer.rb
106
+ - app/models/yellin/application_record.rb
107
+ - app/views/layouts/yellin/mailer.html.erb
108
+ - app/views/layouts/yellin/mailer.text.erb
109
+ - app/views/yellin/password_resets/edit.html.erb
110
+ - app/views/yellin/password_resets/new.html.erb
111
+ - app/views/yellin/registrations/_form.html.erb
112
+ - app/views/yellin/registrations/new.html.erb
113
+ - app/views/yellin/sessions/new.html.erb
114
+ - app/views/yellin/shared/_error_messages.html.erb
115
+ - app/views/yellin/user_mailer/account_activation.html.erb
116
+ - app/views/yellin/user_mailer/account_activation.text.erb
117
+ - app/views/yellin/user_mailer/password_reset.html.erb
118
+ - app/views/yellin/user_mailer/password_reset.text.erb
119
+ - config/routes.rb
120
+ - lib/generators/active_record/templates/migration_create.rb
121
+ - lib/generators/active_record/templates/migration_update.rb
122
+ - lib/generators/active_record/yellin_generator.rb
123
+ - lib/generators/yellin/USAGE
124
+ - lib/generators/yellin/templates/initializer.rb
125
+ - lib/generators/yellin/yellin_generator.rb
126
+ - lib/tasks/yellin_tasks.rake
127
+ - lib/yellin.rb
128
+ - lib/yellin/acts_as_user.rb
129
+ - lib/yellin/engine.rb
130
+ - lib/yellin/version.rb
131
+ homepage: https://github.com/dukemagen/yellin.git
132
+ licenses:
133
+ - MIT
134
+ metadata: {}
135
+ post_install_message:
136
+ rdoc_options: []
137
+ require_paths:
138
+ - lib
139
+ required_ruby_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ required_rubygems_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ requirements: []
150
+ rubyforge_project:
151
+ rubygems_version: 2.5.2
152
+ signing_key:
153
+ specification_version: 4
154
+ summary: Rails engine providing authentication based on `has_secure_password`.
155
+ test_files: []