userify 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+