userify 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.textile ADDED
@@ -0,0 +1,3 @@
1
+ h2. 0.1.0 (5/8/2009)
2
+
3
+ * First cut of userify
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.markdown ADDED
@@ -0,0 +1,123 @@
1
+ Userify
2
+ =======
3
+
4
+ Super simple authentication system for Rails, using username, email and password.
5
+
6
+ Userify focuses on the following 6 conventional actions, and that's all.
7
+
8
+ ( signup / signin / signout / activate / forgot / reset )
9
+
10
+ Userify is inspired by **Clearance** <http://github.com/thoughtbot/clearance>.
11
+
12
+ Limitations
13
+ -----------
14
+
15
+ Currently, Userify is available only when you create a new project. Don't expect it to work in existing projects.
16
+
17
+ Requirements
18
+ ------------
19
+
20
+ Userify requires Haml. However, when you override the default template, you don't have to use Haml.
21
+
22
+ How to use it?
23
+ --------------
24
+
25
+ Here's the setup method.
26
+
27
+ Install Userify gem, as well as Haml.
28
+
29
+ sudo gem sources -a http://gems.github.com # if you haven't do it already
30
+ sudo gem install haml
31
+ sudo gem install userify
32
+
33
+ In config/environment.rb:
34
+
35
+ config.gem "haml"
36
+ config.gem "userify"
37
+ DO_NOT_REPLY = "donotreply@example.com"
38
+
39
+ Run the generator:
40
+
41
+ script/generate userify
42
+
43
+ In config/environments/development.rb and test.rb:
44
+
45
+ HOST = "localhost:3000"
46
+
47
+ Define root_url to *something* in your config/routes.rb. Assuming home controller for root:
48
+
49
+ map.root :controller => 'home'
50
+
51
+ Create user_controller.rb and home_controller.rb:
52
+
53
+ script/generate controller user
54
+ script/generate controller home
55
+
56
+ Rewrite user_controller.rb as follows:
57
+
58
+ class UserController < Userify::UserController
59
+ end
60
+
61
+ Edit home_controller.rb:
62
+
63
+ class HomeController < ApplicationController
64
+ def index
65
+ end
66
+ end
67
+
68
+ Create app/views/home/index.html.haml:
69
+
70
+ - if signed_in?
71
+ %p= "Hello #{current_user.name}!"
72
+ %h1 Home
73
+ %p My app home
74
+ - if signed_in?
75
+ %p= link_to 'sign out', signout_url
76
+ - else
77
+ %p= link_to 'sign up', signup_url
78
+ %p= link_to 'sign in', signin_url
79
+
80
+ Rename public/index.html:
81
+
82
+ mv public/index.html public/_index.html
83
+
84
+ Run migration and start server:
85
+
86
+ rake db:migrate
87
+ script/server
88
+
89
+ Now, go to <http://localhost:3000/> and check how everything works.
90
+
91
+ Customize
92
+ =========
93
+
94
+ There are a couple of ways to customize Userify:
95
+
96
+ ### 1. Override methods in the user_controller subclass.
97
+
98
+ You might want to override the following methods in your user_controller subclass.
99
+
100
+ UserController methods:
101
+
102
+ url_after_signup
103
+ url_after_signin
104
+ url_after_signout
105
+ url_after_activate
106
+ url_after_forgot
107
+ url_after_reset
108
+
109
+ View templates:
110
+
111
+ app/views/layouts/application.html.haml
112
+ app/views/layouts/_error_messages.html.haml
113
+ app/views/user/forgot.html.haml
114
+ app/views/user/reset.html.haml
115
+ app/views/user/signin.html.haml
116
+ app/views/user/signup.html.haml
117
+ app/views/userify_mailer/confirmation.html.erb
118
+ app/views/userify_mailer/reset_password.html.erb
119
+
120
+
121
+ ### 2. Unpack Userify gem into vendor/gems and directly edit source.
122
+
123
+ rake gems:unpack GEM=userify
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.2.0"
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,5 @@
1
+ h1. To-do
2
+
3
+ * Add tests
4
+ * Clean up dependencies
5
+ * Rework uid.rb
@@ -0,0 +1,161 @@
1
+ class Userify::UserController < ApplicationController
2
+ unloadable
3
+
4
+ before_filter :redirect_to_root, :only => [ :signup, :signin, :activate, :forgot, :reset ], :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
+ store_location(true) if session[:return_to].blank?
38
+ render :template => 'user/signin'
39
+
40
+ when :post
41
+ @user = ::User.authenticate(params[:signin][:email], params[:signin][:password])
42
+ if @user.nil?
43
+ flash[:error] = "Bad email or password."
44
+ redirect_to :back
45
+ else
46
+ if @user.email_confirmed?
47
+ if params[:signin] and params[:signin][:remember_me] == "1"
48
+ @user.remember_me!
49
+ cookies[:remember_token] = { :value => @user.token, :expires => @user.token_expires_at }
50
+ end
51
+ sign_in(@user)
52
+ flash[:notice] = "Signed in successfully."
53
+ redirect_back_or url_after_signin
54
+ else
55
+ ::UserifyMailer.deliver_confirmation(@user)
56
+ deny_access("User has not confirmed email. Confirmation email will be resent.")
57
+ end
58
+ end
59
+ end
60
+
61
+ end
62
+
63
+ def signout
64
+ current_user.forget_me! if current_user
65
+ cookies.delete :remember_token
66
+ reset_session
67
+ flash[:notice] = "You have been signed out."
68
+ redirect_to url_after_signout
69
+ end
70
+
71
+ def activate
72
+ @user.confirm_email!
73
+
74
+ sign_in(@user)
75
+ flash[:notice] = "Confirmed email and signed in."
76
+ redirect_to url_after_activate
77
+ end
78
+
79
+ def forgot
80
+ case request.method
81
+
82
+ when :get
83
+ render :template => 'user/forgot'
84
+
85
+ when :post
86
+ if user = ::User.find_by_email(params[:forgot][:email])
87
+ user.forgot_password!
88
+ ::UserifyMailer.deliver_reset_password user
89
+ flash[:notice] = "You will receive an email within the next few minutes. " <<
90
+ "It contains instructions for changing your password."
91
+ redirect_to url_after_forgot
92
+ else
93
+ flash[:error] = "Unknown email"
94
+ redirect_to :back
95
+ end
96
+ end
97
+
98
+ end
99
+
100
+ def reset
101
+ case request.method
102
+
103
+ when :get
104
+ render :template => 'user/reset'
105
+
106
+ when :post
107
+ if @user.update_password(params[:user][:password])
108
+ @user.confirm_email! unless @user.email_confirmed?
109
+ sign_in(@user)
110
+ flash[:notice] = "You have successfully reset your password."
111
+ redirect_to url_after_reset
112
+ else
113
+ redirect_to :back
114
+ end
115
+ end
116
+
117
+ end
118
+
119
+ protected
120
+ def url_after_signup
121
+ root_url
122
+ end
123
+
124
+ def url_after_signin
125
+ root_url
126
+ end
127
+
128
+ def url_after_signout
129
+ return :back
130
+ end
131
+
132
+ def url_after_activate
133
+ root_url
134
+ end
135
+
136
+ def url_after_forgot
137
+ root_url
138
+ end
139
+
140
+ def url_after_reset
141
+ root_url
142
+ end
143
+
144
+ def assign_user_from_token
145
+ raise ActionController::Forbidden, "missing token" if params[:token].blank?
146
+
147
+ @user = ::User.find_by_token(params[:token])
148
+ end
149
+
150
+ def forbid_non_existent_token
151
+ raise ActionController::Forbidden, "non-existent token" unless @user
152
+ end
153
+
154
+ def forbid_confirmed_user
155
+ raise ActionController::Forbidden, "confirmed user" if @user and @user.email_confirmed?
156
+ end
157
+
158
+ def generate_error_messages_for(obj)
159
+ render_to_string :partial => 'layouts/error_messages', :object => obj.errors.full_messages
160
+ end
161
+ 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,4 @@
1
+ %div= "#{pluralize error_messages.size, 'error'} occurred."
2
+ %ul
3
+ - error_messages.each do |error_message|
4
+ %li= error_message
@@ -0,0 +1,9 @@
1
+ !!!
2
+ %html{:xmlns => 'http://www.w3.org/1999/xhtml'}
3
+ %head
4
+ %title [Layout generated by userify]
5
+ %body
6
+ #flash
7
+ - flash.each do |key, value|
8
+ %div{ :class => "flash_#{key}" }= value
9
+ = yield
@@ -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,4 @@
1
+ Welcome <%= @user.name %>!
2
+
3
+ To activate your account, you must click the link below:
4
+ <%= activate_url :token => @user.token %>
@@ -0,0 +1,6 @@
1
+ Someone, hopefully you, has requested that we send you a link to change your password.
2
+
3
+ Here's the link:
4
+ <%= reset_url :token => @user.token %>
5
+
6
+ If you didn't request this, ignore this email. Don't worry. Your password hasn't been changed.
@@ -0,0 +1,8 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ map.signup 'user/signup', :controller => 'userify/user', :action => 'signup'
3
+ map.signin 'user/signin', :controller => 'userify/user', :action => 'signin'
4
+ map.signout 'user/signout', :controller => 'userify/user', :action => 'signout'
5
+ map.activate 'user/activate/:token', :controller => 'userify/user', :action => 'activate'
6
+ map.forgot 'user/forgot', :controller => 'userify/user', :action => 'forgot'
7
+ map.reset 'user/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 => 27, :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,3 @@
1
+ class User < ActiveRecord::Base
2
+ include Userify::User
3
+ 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
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
@@ -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(referer=false)
65
+ session[:return_to] = referer ? request.env["HTTP_REFERER"] : request.request_uri if request.get?
66
+ end
67
+
68
+ def deny_access(flash_message = nil, opts = {})
69
+ store_location
70
+ flash[:error] = flash_message if flash_message
71
+ redirect_to signin_url
72
+ end
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,4 @@
1
+ module ActionController
2
+ class Forbidden < StandardError
3
+ end
4
+ end
@@ -0,0 +1 @@
1
+ ActionController::Base.rescue_responses.update('ActionController::Forbidden' => :forbidden)
@@ -0,0 +1,128 @@
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, :fullname
14
+ attr_accessor :password
15
+
16
+ before_validation :normalize_email
17
+
18
+ validates_presence_of :username
19
+ validates_length_of :username, :maximum => columns_hash['username'].limit
20
+ validates_uniqueness_of :username
21
+ validates_presence_of :email
22
+ validates_length_of :email, :maximum => columns_hash['email'].limit
23
+ validates_uniqueness_of :email, :case_sensitive => false
24
+ validates_format_of :email, :with => /.+@.+\..+/
25
+ validates_presence_of :password, :if => :password_required?
26
+ validates_length_of :fullname, :maximum => columns_hash['fullname'].limit, :allow_nil => true
27
+
28
+ before_save :initialize_salt, :encrypt_password, :initialize_token
29
+ end
30
+ end
31
+
32
+ module InstanceMethods
33
+ def name
34
+ self.fullname or self.username
35
+ end
36
+
37
+ def authenticated?(password)
38
+ encrypted_password == encrypt(password)
39
+ end
40
+
41
+ def encrypt(string)
42
+ Digest::SHA1.hexdigest("--#{salt}--#{string}--")
43
+ end
44
+
45
+ def remember?
46
+ token_expires_at and Time.now.utc < token_expires_at
47
+ end
48
+
49
+ def remember_me!(duration=183)
50
+ remember_me_until! duration.days.from_now.utc
51
+ end
52
+
53
+ def forget_me!
54
+ clear_token
55
+ save(false)
56
+ end
57
+
58
+ def confirm_email!
59
+ self.email_confirmed = true
60
+ clear_token
61
+ save(false)
62
+ end
63
+
64
+ def forgot_password!
65
+ generate_token 24.hours.from_now.utc
66
+ save(false)
67
+ end
68
+
69
+ def update_password(new_password)
70
+ self.password = new_password
71
+ clear_token if valid?
72
+ save
73
+ end
74
+
75
+ protected
76
+
77
+ def generate_random_base62(n=27)
78
+ UID.new(n).to_s
79
+ end
80
+
81
+ def normalize_email
82
+ self.email.downcase! unless self.email.nil?
83
+ return true
84
+ end
85
+
86
+ def initialize_salt
87
+ self.salt = generate_random_base62 if new_record?
88
+ end
89
+
90
+ def encrypt_password
91
+ return if password.blank?
92
+ self.encrypted_password = encrypt(password)
93
+ end
94
+
95
+ def generate_token(time=nil)
96
+ self.token = generate_random_base62
97
+ self.token_expires_at = time
98
+ end
99
+
100
+ def clear_token
101
+ self.token = nil
102
+ self.token_expires_at = nil
103
+ end
104
+
105
+ def initialize_token
106
+ generate_token 24.hours.from_now.utc if new_record?
107
+ end
108
+
109
+ def password_required?
110
+ encrypted_password.blank? or !password.blank?
111
+ end
112
+
113
+ def remember_me_until!(time)
114
+ self.token = generate_random_base62
115
+ self.token_expires_at = time
116
+ save(false)
117
+ end
118
+ end
119
+
120
+ module ClassMethods
121
+ def authenticate(email_or_username, password)
122
+ user = find(:first, :conditions => ['username = ? OR email = ?', email_or_username, email_or_username])
123
+ user && user.authenticated?(password) ? user : nil
124
+ end
125
+ end
126
+
127
+ end
128
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'userify'
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: userify
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Kenn Ejima
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-23 00:00:00 -08: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.markdown
29
+ - TODO.textile
30
+ - app/controllers/userify/user_controller.rb
31
+ - app/models/userify_mailer.rb
32
+ - app/views/layouts/_error_messages.html.haml
33
+ - app/views/layouts/application.html.haml
34
+ - app/views/user/forgot.html.haml
35
+ - app/views/user/reset.html.haml
36
+ - app/views/user/signin.html.haml
37
+ - app/views/user/signup.html.haml
38
+ - app/views/userify_mailer/confirmation.html.erb
39
+ - app/views/userify_mailer/reset_password.html.erb
40
+ - config/userify_routes.rb
41
+ - generators/userify/lib/insert_commands.rb
42
+ - generators/userify/lib/rake_commands.rb
43
+ - generators/userify/templates/migrations/create_users.rb
44
+ - generators/userify/templates/README
45
+ - generators/userify/templates/uid.rb
46
+ - generators/userify/templates/user.rb
47
+ - generators/userify/USAGE
48
+ - generators/userify/userify_generator.rb
49
+ - lib/userify/authentication.rb
50
+ - lib/userify/extensions/errors.rb
51
+ - lib/userify/extensions/rescue.rb
52
+ - lib/userify/user.rb
53
+ - lib/userify.rb
54
+ - rails/init.rb
55
+ has_rdoc: true
56
+ homepage: http://github.com/kenn/userify
57
+ licenses: []
58
+
59
+ post_install_message:
60
+ rdoc_options: []
61
+
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ version:
76
+ requirements: []
77
+
78
+ rubyforge_project:
79
+ rubygems_version: 1.3.5
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: Super simple authentication system for Rails, using username, email and password.
83
+ test_files: []
84
+