kenn-userify 0.1.1
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.
- data/CHANGELOG.textile +3 -0
- data/LICENSE +21 -0
- data/README.textile +118 -0
- data/Rakefile +52 -0
- data/TODO.textile +5 -0
- data/app/controllers/userify/user_controller.rb +143 -0
- data/app/models/userify_mailer.rb +19 -0
- data/app/views/layouts/_error_messages.html.haml +4 -0
- data/app/views/layouts/application.html.haml +9 -0
- data/app/views/user/forgot.html.haml +10 -0
- data/app/views/user/reset.html.haml +12 -0
- data/app/views/user/signin.html.haml +20 -0
- data/app/views/user/signup.html.haml +14 -0
- data/app/views/userify_mailer/confirmation.html.erb +4 -0
- data/app/views/userify_mailer/reset_password.html.erb +6 -0
- data/config/userify_routes.rb +8 -0
- data/generators/userify/USAGE +1 -0
- data/generators/userify/lib/insert_commands.rb +56 -0
- data/generators/userify/lib/rake_commands.rb +22 -0
- data/generators/userify/templates/README +22 -0
- data/generators/userify/templates/migrations/create_users.rb +36 -0
- data/generators/userify/templates/uid.rb +67 -0
- data/generators/userify/templates/user.rb +3 -0
- data/generators/userify/userify_generator.rb +30 -0
- data/lib/userify/authentication.rb +76 -0
- data/lib/userify/extensions/errors.rb +4 -0
- data/lib/userify/extensions/rescue.rb +1 -0
- data/lib/userify/user.rb +129 -0
- data/lib/userify.rb +14 -0
- data/rails/init.rb +1 -0
- metadata +95 -0
data/CHANGELOG.textile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2009 Kenn Ejima
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
h1. Userify
|
2
|
+
|
3
|
+
Super simple authentication system for Rails, using username, email and password.
|
4
|
+
|
5
|
+
Userify focuses on the following 6 conventional actions, and that's all.
|
6
|
+
|
7
|
+
( signup / signin / signout / activate / forgot / reset )
|
8
|
+
|
9
|
+
Userify is inspired by "Clearance":http://github.com/thoughtbot/clearance.
|
10
|
+
|
11
|
+
h2. Limitations
|
12
|
+
|
13
|
+
Currently, Userify is available only when you create a new project. Don't expect it to work in existing projects.
|
14
|
+
|
15
|
+
h2. Requirements
|
16
|
+
|
17
|
+
Userify requires Haml. However, when you override the default template, you don't have to use Haml.
|
18
|
+
|
19
|
+
h2. How to use it?
|
20
|
+
|
21
|
+
Here's the setup method.
|
22
|
+
|
23
|
+
Install Userify gem, as well as Haml.
|
24
|
+
|
25
|
+
sudo gem sources -a http://gems.github.com # if you haven't do it already
|
26
|
+
sudo gem install haml
|
27
|
+
sudo gem install kenn-userify
|
28
|
+
|
29
|
+
In config/environment.rb:
|
30
|
+
|
31
|
+
config.gem "haml"
|
32
|
+
config.gem "kenn-userify", :lib => "userify", :source => "http://gems.github.com"
|
33
|
+
DO_NOT_REPLY = "donotreply@example.com"
|
34
|
+
|
35
|
+
Run the generator:
|
36
|
+
|
37
|
+
script/generate userify
|
38
|
+
|
39
|
+
In config/environments/development.rb and test.rb:
|
40
|
+
|
41
|
+
HOST = "localhost:3000"
|
42
|
+
|
43
|
+
Define root_url to *something* in your config/routes.rb. Assuming home controller for root:
|
44
|
+
|
45
|
+
map.root :controller => 'home'
|
46
|
+
|
47
|
+
Create user_controller.rb and home_controller.rb:
|
48
|
+
|
49
|
+
script/generate controller user
|
50
|
+
script/generate controller home
|
51
|
+
|
52
|
+
Rewrite user_controller.rb as follows:
|
53
|
+
|
54
|
+
class UserController < Userify::UsersController
|
55
|
+
end
|
56
|
+
|
57
|
+
Edit home_controller.rb:
|
58
|
+
|
59
|
+
class HomeController < ApplicationController
|
60
|
+
def index
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
Create app/views/home/index.html.haml:
|
65
|
+
|
66
|
+
- if signed_in?
|
67
|
+
%p= "Hello #{current_user.name}!"
|
68
|
+
%h1 Home
|
69
|
+
%p My app home
|
70
|
+
- if signed_in?
|
71
|
+
%p= link_to 'sign out', signout_url
|
72
|
+
- else
|
73
|
+
%p= link_to 'sign up', signup_url
|
74
|
+
%p= link_to 'sign in', signin_url
|
75
|
+
|
76
|
+
Rename public/index.html:
|
77
|
+
|
78
|
+
mv public/index.html public/_index.html
|
79
|
+
|
80
|
+
Run migration and start server:
|
81
|
+
|
82
|
+
rake db:migrate
|
83
|
+
script/server
|
84
|
+
|
85
|
+
Now, go to http://localhost:3000/ and check how everything works.
|
86
|
+
|
87
|
+
h2. Customize
|
88
|
+
|
89
|
+
There are a couple of ways to customize Userify:
|
90
|
+
|
91
|
+
1. Override methods in the user_controller subclass.
|
92
|
+
|
93
|
+
You might want to override the following methods in your user_controller subclass.
|
94
|
+
|
95
|
+
UserController methods:
|
96
|
+
|
97
|
+
url_after_signup
|
98
|
+
url_after_signin
|
99
|
+
url_after_signout
|
100
|
+
url_after_activate
|
101
|
+
url_after_forgot
|
102
|
+
url_after_reset
|
103
|
+
|
104
|
+
View templates:
|
105
|
+
|
106
|
+
app/views/layouts/application.html.haml
|
107
|
+
app/views/layouts/_error_messages.html.haml
|
108
|
+
app/views/user/forgot.html.haml
|
109
|
+
app/views/user/reset.html.haml
|
110
|
+
app/views/user/signin.html.haml
|
111
|
+
app/views/user/signup.html.haml
|
112
|
+
app/views/userify_mailer/confirmation.html.erb
|
113
|
+
app/views/userify_mailer/reset_password.html.erb
|
114
|
+
|
115
|
+
|
116
|
+
2. Unpack Userify gem into vendor/gems and directly edit source.
|
117
|
+
|
118
|
+
rake gems:unpack
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
generators = %w(userify)
|
5
|
+
|
6
|
+
namespace :generator do
|
7
|
+
desc "Cleans up the test app before running the generator"
|
8
|
+
task :cleanup do
|
9
|
+
generators.each do |generator|
|
10
|
+
FileList["generators/#{generator}/templates/**/*.*"].each do |each|
|
11
|
+
file = "test/rails_root/#{each.gsub("generators/#{generator}/templates/",'')}"
|
12
|
+
File.delete(file) if File.exists?(file)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
FileList["test/rails_root/db/**/*"].each do |each|
|
17
|
+
FileUtils.rm_rf(each)
|
18
|
+
end
|
19
|
+
FileUtils.rm_rf("test/rails_root/vendor/plugins/userify")
|
20
|
+
FileUtils.mkdir_p("test/rails_root/vendor/plugins")
|
21
|
+
userify_root = File.expand_path(File.dirname(__FILE__))
|
22
|
+
system("ln -s #{userify_root} test/rails_root/vendor/plugins/userify")
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "Run the generator on the tests"
|
26
|
+
task :generate do
|
27
|
+
generators.each do |generator|
|
28
|
+
system "cd test/rails_root && ./script/generate #{generator} && rake db:migrate db:test:prepare"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "Run the test suite"
|
34
|
+
task :default => ['test:all', 'test:features']
|
35
|
+
|
36
|
+
gem_spec = Gem::Specification.new do |gem_spec|
|
37
|
+
gem_spec.name = "userify"
|
38
|
+
gem_spec.version = "0.1.1"
|
39
|
+
gem_spec.summary = "Super simple authentication system for Rails, using username, email and password."
|
40
|
+
gem_spec.email = "kenn.ejima <at> gmail.com"
|
41
|
+
gem_spec.homepage = "http://github.com/kenn/userify"
|
42
|
+
gem_spec.description = "Super simple authentication system for Rails, using username, email and password."
|
43
|
+
gem_spec.authors = ["Kenn Ejima"]
|
44
|
+
gem_spec.files = FileList["[A-Z]*", "{app,config,generators,lib,rails}/**/*"]
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "Generate a gemspec file"
|
48
|
+
task :gemspec do
|
49
|
+
File.open("#{gem_spec.name}.gemspec", 'w') do |f|
|
50
|
+
f.write gem_spec.to_yaml
|
51
|
+
end
|
52
|
+
end
|
data/TODO.textile
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
class Userify::UserController < ApplicationController
|
2
|
+
unloadable
|
3
|
+
|
4
|
+
before_filter :redirect_to_root, :except => :signout, :if => :signed_in?
|
5
|
+
filter_parameter_logging :password
|
6
|
+
|
7
|
+
before_filter :assign_user_from_token, :only => [ :activate, :reset ]
|
8
|
+
before_filter :forbid_non_existent_token, :only => [ :activate, :reset ]
|
9
|
+
before_filter :forbid_confirmed_user, :only => :activate
|
10
|
+
|
11
|
+
def signup
|
12
|
+
case request.method
|
13
|
+
|
14
|
+
when :get
|
15
|
+
@user = ::User.new(params[:signup])
|
16
|
+
render :template => 'user/signup'
|
17
|
+
|
18
|
+
when :post
|
19
|
+
@user = ::User.new(params[:signup])
|
20
|
+
if @user.save
|
21
|
+
::UserifyMailer.deliver_confirmation @user
|
22
|
+
flash[:notice] = "You will receive an email within the next few minutes. " <<
|
23
|
+
"It contains instructions for confirming your account."
|
24
|
+
redirect_to url_after_signup
|
25
|
+
else
|
26
|
+
flash[:error] = generate_error_messages_for(@user)
|
27
|
+
redirect_to :back
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
def signin
|
34
|
+
case request.method
|
35
|
+
|
36
|
+
when :get
|
37
|
+
render :template => 'user/signin'
|
38
|
+
|
39
|
+
when :post
|
40
|
+
@user = ::User.authenticate(params[:signin][:email], params[:signin][:password])
|
41
|
+
if @user.nil?
|
42
|
+
flash[:error] = "Bad email or password."
|
43
|
+
redirect_to :back
|
44
|
+
else
|
45
|
+
if @user.email_confirmed?
|
46
|
+
if params[:signin] and params[:signin][:remember_me] == "1"
|
47
|
+
@user.remember_me!
|
48
|
+
cookies[:remember_token] = { :value => @user.token, :expires => @user.token_expires_at }
|
49
|
+
end
|
50
|
+
sign_in(@user)
|
51
|
+
flash[:notice] = "Signed in successfully."
|
52
|
+
redirect_back_or url_after_signin
|
53
|
+
else
|
54
|
+
::UserifyMailer.deliver_confirmation(@user)
|
55
|
+
deny_access("User has not confirmed email. Confirmation email will be resent.")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
def signout
|
63
|
+
current_user.forget_me! if current_user
|
64
|
+
cookies.delete :remember_token
|
65
|
+
reset_session
|
66
|
+
flash[:notice] = "You have been signed out."
|
67
|
+
redirect_to url_after_signout
|
68
|
+
end
|
69
|
+
|
70
|
+
def activate
|
71
|
+
@user.confirm_email!
|
72
|
+
|
73
|
+
sign_in(@user)
|
74
|
+
flash[:notice] = "Confirmed email and signed in."
|
75
|
+
redirect_to url_after_activate
|
76
|
+
end
|
77
|
+
|
78
|
+
def forgot
|
79
|
+
case request.method
|
80
|
+
|
81
|
+
when :get
|
82
|
+
render :template => 'user/forgot'
|
83
|
+
|
84
|
+
when :post
|
85
|
+
if user = ::User.find_by_email(params[:forgot][:email])
|
86
|
+
user.forgot_password!
|
87
|
+
::UserifyMailer.deliver_reset_password user
|
88
|
+
flash[:notice] = "You will receive an email within the next few minutes. " <<
|
89
|
+
"It contains instructions for changing your password."
|
90
|
+
redirect_to url_after_forgot
|
91
|
+
else
|
92
|
+
flash[:error] = "Unknown email"
|
93
|
+
redirect_to :back
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
def reset
|
100
|
+
case request.method
|
101
|
+
|
102
|
+
when :get
|
103
|
+
render :template => 'user/reset'
|
104
|
+
|
105
|
+
when :post
|
106
|
+
if @user.update_password(params[:user][:password])
|
107
|
+
@user.confirm_email! unless @user.email_confirmed?
|
108
|
+
sign_in(@user)
|
109
|
+
flash[:notice] = "You have successfully reset your password."
|
110
|
+
redirect_to url_after_reset
|
111
|
+
else
|
112
|
+
redirect_to :back
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
protected
|
119
|
+
def url_after_signup; root_url; end
|
120
|
+
def url_after_signin; root_url; end
|
121
|
+
def url_after_signout; root_url; end
|
122
|
+
def url_after_activate; root_url; end
|
123
|
+
def url_after_forgot; root_url; end
|
124
|
+
def url_after_reset; root_url; end
|
125
|
+
|
126
|
+
def assign_user_from_token
|
127
|
+
raise ActionController::Forbidden, "missing token" if params[:token].blank?
|
128
|
+
|
129
|
+
@user = ::User.find_by_token(params[:token])
|
130
|
+
end
|
131
|
+
|
132
|
+
def forbid_non_existent_token
|
133
|
+
raise ActionController::Forbidden, "non-existent token" unless @user
|
134
|
+
end
|
135
|
+
|
136
|
+
def forbid_confirmed_user
|
137
|
+
raise ActionController::Forbidden, "confirmed user" if @user and @user.email_confirmed?
|
138
|
+
end
|
139
|
+
|
140
|
+
def generate_error_messages_for(obj)
|
141
|
+
render_to_string :partial => 'layouts/error_messages', :object => obj.errors.full_messages
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class UserifyMailer < ActionMailer::Base
|
2
|
+
|
3
|
+
default_url_options[:host] = HOST
|
4
|
+
|
5
|
+
def reset_password(user)
|
6
|
+
from DO_NOT_REPLY
|
7
|
+
recipients user.email
|
8
|
+
subject "Reset your password"
|
9
|
+
body :user => user
|
10
|
+
end
|
11
|
+
|
12
|
+
def confirmation(user)
|
13
|
+
from DO_NOT_REPLY
|
14
|
+
recipients user.email
|
15
|
+
subject "Account confirmation"
|
16
|
+
body :user => user
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
%h2 Forgot your password?
|
2
|
+
|
3
|
+
%p We will email you a link to change your password.
|
4
|
+
|
5
|
+
- form_for :forgot, :url => forgot_path do |form|
|
6
|
+
.text_field
|
7
|
+
= form.label :email, "Email address"
|
8
|
+
= form.text_field :email
|
9
|
+
.submit_field
|
10
|
+
= form.submit "Reset password", :disable_with => "Please wait..."
|
@@ -0,0 +1,12 @@
|
|
1
|
+
%h2 Reset your password
|
2
|
+
|
3
|
+
%p Your password has been reset. Choose a new password below.
|
4
|
+
|
5
|
+
= error_messages_for :user
|
6
|
+
|
7
|
+
- form_for :user, :url => reset_path(:token => @user.token) do |form|
|
8
|
+
.password_field
|
9
|
+
= form.label :password, "Choose password"
|
10
|
+
= form.password_field :password
|
11
|
+
.submit_field
|
12
|
+
= form.submit "Save this password", :disable_with => "Please wait..."
|
@@ -0,0 +1,20 @@
|
|
1
|
+
%h2 Sign in
|
2
|
+
|
3
|
+
- form_for :signin, :url => signin_path do |form|
|
4
|
+
.text_field
|
5
|
+
= form.label :email, "Email or username"
|
6
|
+
= form.text_field :email
|
7
|
+
.text_field
|
8
|
+
= form.label :password
|
9
|
+
= form.password_field :password
|
10
|
+
.text_field
|
11
|
+
= form.check_box :remember_me
|
12
|
+
= form.label :remember_me
|
13
|
+
.submit_field
|
14
|
+
= form.submit "Sign in", :disable_with => "Please wait..."
|
15
|
+
|
16
|
+
%ul
|
17
|
+
%li
|
18
|
+
= link_to "Sign up", signup_path
|
19
|
+
%li
|
20
|
+
= link_to "Forgot password?", forgot_path
|
@@ -0,0 +1,14 @@
|
|
1
|
+
%h2 Sign up
|
2
|
+
|
3
|
+
- form_for :signup, :url => signup_path do |form|
|
4
|
+
= form.error_messages
|
5
|
+
.text_field
|
6
|
+
= form.label :username
|
7
|
+
= form.text_field :username
|
8
|
+
.text_field
|
9
|
+
= form.label :email
|
10
|
+
= form.text_field :email
|
11
|
+
.password_field
|
12
|
+
= form.label :password
|
13
|
+
= form.password_field :password
|
14
|
+
= form.submit 'Sign up', :disable_with => 'Please wait...'
|
@@ -0,0 +1,8 @@
|
|
1
|
+
ActionController::Routing::Routes.draw do |map|
|
2
|
+
map.signup 'signup', :controller => 'userify/user', :action => 'signup'
|
3
|
+
map.signin 'signin', :controller => 'userify/user', :action => 'signin'
|
4
|
+
map.signout 'signout', :controller => 'userify/user', :action => 'signout'
|
5
|
+
map.activate 'activate/:token', :controller => 'userify/user', :action => 'activate'
|
6
|
+
map.forgot 'password/forgot', :controller => 'userify/user', :action => 'forgot'
|
7
|
+
map.reset 'password/reset/:token', :controller => 'userify/user', :action => 'reset'
|
8
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
script/generate userify
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Mostly pinched from http://github.com/ryanb/nifty-generators/tree/master
|
2
|
+
|
3
|
+
Rails::Generator::Commands::Base.class_eval do
|
4
|
+
def file_contains?(relative_destination, line)
|
5
|
+
File.read(destination_path(relative_destination)).include?(line)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
Rails::Generator::Commands::Create.class_eval do
|
10
|
+
def route_name(name, path, route_options = {})
|
11
|
+
sentinel = 'ActionController::Routing::Routes.draw do |map|'
|
12
|
+
|
13
|
+
logger.route "map.#{name} '#{path}', :controller => '#{route_options[:controller]}', :action => '#{route_options[:action]}'"
|
14
|
+
unless options[:pretend]
|
15
|
+
gsub_file_once 'config/routes.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
|
16
|
+
"#{match}\n map.#{name} '#{path}', :controller => '#{route_options[:controller]}', :action => '#{route_options[:action]}'"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def insert_into(file, line)
|
22
|
+
logger.insert "#{line} into #{file}"
|
23
|
+
unless options[:pretend] || file_contains?(file, line)
|
24
|
+
gsub_file file, /^(class|module) .+$/ do |match|
|
25
|
+
"#{match}\n #{line}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Rails::Generator::Commands::Destroy.class_eval do
|
32
|
+
def route_name(name, path, route_options = {})
|
33
|
+
look_for = "\n map.#{name} '#{path}', :controller => '#{route_options[:controller]}', :action => '#{route_options[:action]}'"
|
34
|
+
logger.route "map.#{name} '#{path}', :controller => '#{route_options[:controller]}', :action => '#{route_options[:action]}'"
|
35
|
+
unless options[:pretend]
|
36
|
+
gsub_file 'config/routes.rb', /(#{look_for})/mi, ''
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def insert_into(file, line)
|
41
|
+
logger.remove "#{line} from #{file}"
|
42
|
+
unless options[:pretend]
|
43
|
+
gsub_file file, "\n #{line}", ''
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
Rails::Generator::Commands::List.class_eval do
|
49
|
+
def route_name(name, path, options = {})
|
50
|
+
logger.route "map.#{name} '#{path}', :controller => '#{options[:controller]}', :action => '#{options[:action]}'"
|
51
|
+
end
|
52
|
+
|
53
|
+
def insert_into(file, line)
|
54
|
+
logger.insert "#{line} into #{file}"
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
Rails::Generator::Commands::Create.class_eval do
|
2
|
+
def rake_db_migrate
|
3
|
+
logger.rake "db:migrate"
|
4
|
+
unless system("rake db:migrate")
|
5
|
+
logger.rake "db:migrate failed. Rolling back"
|
6
|
+
command(:destroy).invoke!
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
Rails::Generator::Commands::Destroy.class_eval do
|
12
|
+
def rake_db_migrate
|
13
|
+
logger.rake "db:rollback"
|
14
|
+
system "rake db:rollback"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Rails::Generator::Commands::List.class_eval do
|
19
|
+
def rake_db_migrate
|
20
|
+
logger.rake "db:migrate"
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
*******************************************************************************
|
3
|
+
|
4
|
+
Ok, enough fancy automatic stuff. Time for some old school monkey copy-pasting.
|
5
|
+
|
6
|
+
1. Define a HOST constant in your environments files.
|
7
|
+
In config/environments/test.rb and config/environments/development.rb it can be:
|
8
|
+
|
9
|
+
HOST = "localhost:3000"
|
10
|
+
|
11
|
+
In production.rb it must be the actual host your application is deployed to.
|
12
|
+
The constant is used by mailers to generate URLs in emails.
|
13
|
+
|
14
|
+
2. In config/environment.rb:
|
15
|
+
|
16
|
+
DO_NOT_REPLY = "donotreply@example.com"
|
17
|
+
|
18
|
+
3. Define root_url to *something* in your config/routes.rb:
|
19
|
+
|
20
|
+
map.root :controller => 'home'
|
21
|
+
|
22
|
+
*******************************************************************************
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class UserifyCreateUsers < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
|
4
|
+
case connection.adapter_name
|
5
|
+
when 'MySQL'
|
6
|
+
execute "ALTER DATABASE #{connection.current_database} CHARACTER SET utf8 COLLATE utf8_bin"
|
7
|
+
no_case_collation = 'utf8_general_ci'
|
8
|
+
mysql = true
|
9
|
+
when 'SQLite'
|
10
|
+
# COLLATE BINARY by default
|
11
|
+
no_case_collation = 'NOCASE'
|
12
|
+
mysql = false
|
13
|
+
end
|
14
|
+
|
15
|
+
create_table :users do |t|
|
16
|
+
t.string :email, :limit => 60, :null => false
|
17
|
+
t.string :fullname, :limit => 60
|
18
|
+
t.string :encrypted_password, :limit => 40, :null => false
|
19
|
+
t.string :salt, :limit => 40, :null => false
|
20
|
+
t.string :token, :limit => 27
|
21
|
+
t.datetime :token_expires_at
|
22
|
+
t.boolean :email_confirmed, :default => false, :null => false
|
23
|
+
t.timestamps
|
24
|
+
end
|
25
|
+
|
26
|
+
execute "ALTER TABLE users ADD username VARCHAR(30) #{mysql ? 'CHARACTER SET utf8' : ''} COLLATE #{no_case_collation}"
|
27
|
+
|
28
|
+
add_index :users, :username, :unique => true
|
29
|
+
add_index :users, :email, :unique => true
|
30
|
+
add_index :users, :token, :unique => true
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.down
|
34
|
+
drop_table :users
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
# Usage:
|
4
|
+
#
|
5
|
+
# UID.new.to_s => "aWgEPTl1tmebfsQzFP4bxwgy80V"
|
6
|
+
# UID.new(5).to_s => "8FsD5"
|
7
|
+
# UID.new.to_i => 838068072552051698674007079806269848286804777409
|
8
|
+
# 1000000000.base62 => "15ftgG"
|
9
|
+
# "123abcABC".base62 => 225587272106046
|
10
|
+
#
|
11
|
+
# By default, UIDs generates random BASE62 string of 27 characters, which is safer than 160bit SHA-1.
|
12
|
+
#
|
13
|
+
# >> ('f'*40).hex
|
14
|
+
# => 1461501637330902918203684832716283019655932542975
|
15
|
+
# >> ('z'*27).base62
|
16
|
+
# => 2480707824361381849652170924082266893544595652607
|
17
|
+
|
18
|
+
class UID
|
19
|
+
def initialize(n=27)
|
20
|
+
max = ("Z"*n).base62
|
21
|
+
@value = SecureRandom.random_number(max)
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
@value.base62
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_i
|
29
|
+
@value
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_hex
|
33
|
+
@value.to_s(16)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class String
|
38
|
+
BASE62_PRIMITIVES = {}.tap do |h|
|
39
|
+
(('0'..'9').to_a + ('A'..'Z').to_a + ('a'..'z').to_a).each_with_index {|e, i| h[e] = i }
|
40
|
+
end
|
41
|
+
|
42
|
+
def base62
|
43
|
+
i = 0
|
44
|
+
i_out = 0
|
45
|
+
self.split(//).reverse.each do |c|
|
46
|
+
place = BASE62_PRIMITIVES.size ** i
|
47
|
+
i_out += BASE62_PRIMITIVES[c] * place
|
48
|
+
i += 1
|
49
|
+
end
|
50
|
+
i_out
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
class Integer
|
56
|
+
BASE62_PRIMITIVES = ('0'..'9').to_a + ('A'..'Z').to_a + ('a'..'z').to_a
|
57
|
+
|
58
|
+
def base62
|
59
|
+
number = self
|
60
|
+
result = ''
|
61
|
+
while(number != 0)
|
62
|
+
result = BASE62_PRIMITIVES[number % BASE62_PRIMITIVES.size ].to_s + result
|
63
|
+
number /= BASE62_PRIMITIVES.size
|
64
|
+
end
|
65
|
+
result
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/lib/insert_commands.rb")
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + "/lib/rake_commands.rb")
|
3
|
+
|
4
|
+
class UserifyGenerator < Rails::Generator::Base
|
5
|
+
|
6
|
+
def manifest
|
7
|
+
record do |m|
|
8
|
+
m.insert_into "app/controllers/application_controller.rb",
|
9
|
+
"include Userify::Authentication"
|
10
|
+
|
11
|
+
user_model = "app/models/user.rb"
|
12
|
+
if File.exists?(user_model)
|
13
|
+
m.insert_into user_model, "include Userify::User"
|
14
|
+
else
|
15
|
+
m.directory File.join("app", "models")
|
16
|
+
m.file "user.rb", user_model
|
17
|
+
end
|
18
|
+
|
19
|
+
m.directory File.join("lib", "userify")
|
20
|
+
m.file "uid.rb", "lib/userify/uid.rb", :collision => :ask
|
21
|
+
|
22
|
+
m.migration_template "migrations/create_users.rb",
|
23
|
+
'db/migrate',
|
24
|
+
:migration_file_name => "userify_create_users"
|
25
|
+
|
26
|
+
m.readme "README"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Userify
|
2
|
+
module Authentication
|
3
|
+
|
4
|
+
def self.included(controller)
|
5
|
+
controller.send(:include, InstanceMethods)
|
6
|
+
|
7
|
+
controller.class_eval do
|
8
|
+
helper_method :current_user
|
9
|
+
helper_method :signed_in?
|
10
|
+
|
11
|
+
hide_action :current_user, :signed_in?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
def current_user
|
17
|
+
@_current_user ||= (user_from_cookie or user_from_session)
|
18
|
+
end
|
19
|
+
|
20
|
+
def signed_in?
|
21
|
+
! current_user.nil?
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def authenticate
|
27
|
+
deny_access unless signed_in?
|
28
|
+
end
|
29
|
+
|
30
|
+
def user_from_session
|
31
|
+
if session[:user_id]
|
32
|
+
return nil unless user = ::User.find_by_id(session[:user_id])
|
33
|
+
return user if user.email_confirmed?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def user_from_cookie
|
38
|
+
if token = cookies[:remember_token]
|
39
|
+
return nil unless user = ::User.find_by_token(token)
|
40
|
+
return user if user.remember?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def sign_in(user)
|
45
|
+
if user
|
46
|
+
session[:user_id] = user.id
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def redirect_back_or(default)
|
51
|
+
session[:return_to] ||= params[:return_to]
|
52
|
+
if session[:return_to]
|
53
|
+
redirect_to(session[:return_to])
|
54
|
+
else
|
55
|
+
redirect_to(default)
|
56
|
+
end
|
57
|
+
session[:return_to] = nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def redirect_to_root
|
61
|
+
redirect_to root_url
|
62
|
+
end
|
63
|
+
|
64
|
+
def store_location
|
65
|
+
session[:return_to] = request.request_uri if request.get?
|
66
|
+
end
|
67
|
+
|
68
|
+
def deny_access(flash_message = nil, opts = {})
|
69
|
+
store_location
|
70
|
+
flash[:failure] = flash_message if flash_message
|
71
|
+
redirect_to signin_url
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
ActionController::Base.rescue_responses.update('ActionController::Forbidden' => :forbidden)
|
data/lib/userify/user.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
module Userify
|
4
|
+
module User
|
5
|
+
|
6
|
+
def self.included(model)
|
7
|
+
require 'userify/uid'
|
8
|
+
|
9
|
+
model.extend ClassMethods
|
10
|
+
model.send(:include, InstanceMethods)
|
11
|
+
|
12
|
+
model.class_eval do
|
13
|
+
attr_accessible :username, :email, :password
|
14
|
+
attr_accessor :password
|
15
|
+
|
16
|
+
before_validation :normalize_email
|
17
|
+
|
18
|
+
validates_presence_of :username
|
19
|
+
validates_uniqueness_of :username
|
20
|
+
validates_presence_of :email
|
21
|
+
validates_uniqueness_of :email, :case_sensitive => false
|
22
|
+
validates_format_of :email, :with => /.+@.+\..+/
|
23
|
+
validates_presence_of :password, :if => :password_required?
|
24
|
+
|
25
|
+
before_save :initialize_salt, :encrypt_password, :initialize_token
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module InstanceMethods
|
30
|
+
def name
|
31
|
+
self.fullname or self.username
|
32
|
+
end
|
33
|
+
|
34
|
+
def authenticated?(password)
|
35
|
+
encrypted_password == encrypt(password)
|
36
|
+
end
|
37
|
+
|
38
|
+
def encrypt(string)
|
39
|
+
generate_hash("--#{salt}--#{string}--")
|
40
|
+
end
|
41
|
+
|
42
|
+
def remember?
|
43
|
+
token_expires_at and Time.now.utc < token_expires_at
|
44
|
+
end
|
45
|
+
|
46
|
+
def remember_me!
|
47
|
+
remember_me_until! 6.months.from_now.utc
|
48
|
+
end
|
49
|
+
|
50
|
+
def forget_me!
|
51
|
+
clear_token
|
52
|
+
save(false)
|
53
|
+
end
|
54
|
+
|
55
|
+
def confirm_email!
|
56
|
+
self.email_confirmed = true
|
57
|
+
self.token = nil
|
58
|
+
save(false)
|
59
|
+
end
|
60
|
+
|
61
|
+
def forgot_password!
|
62
|
+
generate_token
|
63
|
+
save(false)
|
64
|
+
end
|
65
|
+
|
66
|
+
def update_password(new_password)
|
67
|
+
self.password = new_password
|
68
|
+
clear_token if valid?
|
69
|
+
save
|
70
|
+
end
|
71
|
+
|
72
|
+
protected
|
73
|
+
|
74
|
+
def generate_hash(string)
|
75
|
+
Digest::SHA1.hexdigest(string)
|
76
|
+
end
|
77
|
+
|
78
|
+
def generate_random_base62(n=27)
|
79
|
+
UID.new(n).to_s
|
80
|
+
end
|
81
|
+
|
82
|
+
def normalize_email
|
83
|
+
self.email.downcase!
|
84
|
+
return true
|
85
|
+
end
|
86
|
+
|
87
|
+
def initialize_salt
|
88
|
+
self.salt = generate_hash("--#{Time.now.utc.to_s}--#{password}--") if new_record?
|
89
|
+
end
|
90
|
+
|
91
|
+
def encrypt_password
|
92
|
+
return if password.blank?
|
93
|
+
self.encrypted_password = encrypt(password)
|
94
|
+
end
|
95
|
+
|
96
|
+
def generate_token
|
97
|
+
self.token = generate_random_base62
|
98
|
+
self.token_expires_at = nil
|
99
|
+
end
|
100
|
+
|
101
|
+
def clear_token
|
102
|
+
self.token = nil
|
103
|
+
self.token_expires_at = nil
|
104
|
+
end
|
105
|
+
|
106
|
+
def initialize_token
|
107
|
+
generate_token if new_record?
|
108
|
+
end
|
109
|
+
|
110
|
+
def password_required?
|
111
|
+
encrypted_password.blank? or !password.blank?
|
112
|
+
end
|
113
|
+
|
114
|
+
def remember_me_until!(time)
|
115
|
+
self.token_expires_at = time
|
116
|
+
self.token = generate_random_base62
|
117
|
+
save(false)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
module ClassMethods
|
122
|
+
def authenticate(email_or_username, password)
|
123
|
+
user = find(:first, :conditions => ['username = ? OR email = ?', email_or_username, email_or_username])
|
124
|
+
user && user.authenticated?(password) ? user : nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
data/lib/userify.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'userify/extensions/errors'
|
2
|
+
require 'userify/extensions/rescue'
|
3
|
+
require 'userify/authentication'
|
4
|
+
require 'userify/user'
|
5
|
+
|
6
|
+
class ActionController::Routing::RouteSet
|
7
|
+
def load_routes_with_userify!
|
8
|
+
userify_routes = File.join(File.dirname(__FILE__), *%w[.. config userify_routes.rb])
|
9
|
+
add_configuration_file(userify_routes) unless configuration_files.include? userify_routes
|
10
|
+
load_routes_without_userify!
|
11
|
+
end
|
12
|
+
|
13
|
+
alias_method_chain :load_routes!, :userify
|
14
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'userify'
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kenn-userify
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kenn Ejima
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-05-12 08:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Super simple authentication system for Rails, using username, email and password.
|
17
|
+
email: kenn.ejima <at> gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- CHANGELOG.textile
|
26
|
+
- LICENSE
|
27
|
+
- Rakefile
|
28
|
+
- README.textile
|
29
|
+
- TODO.textile
|
30
|
+
- app/controllers
|
31
|
+
- app/controllers/userify
|
32
|
+
- app/controllers/userify/user_controller.rb
|
33
|
+
- app/models
|
34
|
+
- app/models/userify_mailer.rb
|
35
|
+
- app/views
|
36
|
+
- app/views/layouts
|
37
|
+
- app/views/layouts/_error_messages.html.haml
|
38
|
+
- app/views/layouts/application.html.haml
|
39
|
+
- app/views/user
|
40
|
+
- app/views/user/forgot.html.haml
|
41
|
+
- app/views/user/reset.html.haml
|
42
|
+
- app/views/user/signin.html.haml
|
43
|
+
- app/views/user/signup.html.haml
|
44
|
+
- app/views/userify_mailer
|
45
|
+
- app/views/userify_mailer/confirmation.html.erb
|
46
|
+
- app/views/userify_mailer/reset_password.html.erb
|
47
|
+
- config/userify_routes.rb
|
48
|
+
- generators/userify
|
49
|
+
- generators/userify/lib
|
50
|
+
- generators/userify/lib/insert_commands.rb
|
51
|
+
- generators/userify/lib/rake_commands.rb
|
52
|
+
- generators/userify/templates
|
53
|
+
- generators/userify/templates/migrations
|
54
|
+
- generators/userify/templates/migrations/create_users.rb
|
55
|
+
- generators/userify/templates/README
|
56
|
+
- generators/userify/templates/uid.rb
|
57
|
+
- generators/userify/templates/user.rb
|
58
|
+
- generators/userify/USAGE
|
59
|
+
- generators/userify/userify_generator.rb
|
60
|
+
- lib/userify
|
61
|
+
- lib/userify/authentication.rb
|
62
|
+
- lib/userify/extensions
|
63
|
+
- lib/userify/extensions/errors.rb
|
64
|
+
- lib/userify/extensions/rescue.rb
|
65
|
+
- lib/userify/user.rb
|
66
|
+
- lib/userify.rb
|
67
|
+
- rails/init.rb
|
68
|
+
has_rdoc: true
|
69
|
+
homepage: http://github.com/kenn/userify
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
version:
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: "0"
|
86
|
+
version:
|
87
|
+
requirements: []
|
88
|
+
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 1.2.0
|
91
|
+
signing_key:
|
92
|
+
specification_version: 3
|
93
|
+
summary: Super simple authentication system for Rails, using username, email and password.
|
94
|
+
test_files: []
|
95
|
+
|