merb-auth 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 PragmaQuest Inc
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,64 @@
1
+ MerbAuth
2
+ =========
3
+
4
+ An user authentication slice for Merb, with support for ActiveRecord and DataMapper.
5
+ See http://github.com/ctran/merb-skel for an example application that uses this slice
6
+
7
+ == Installation:
8
+
9
+ > gem install merb
10
+ > gem install merb-slices
11
+
12
+ To use activerecord adapter
13
+ > gem install activerecord
14
+ > gem merb_activerecord
15
+
16
+ To use datamapper adapter
17
+ > gem install datamapper
18
+ > gem install merb_datamapper
19
+
20
+ === config/init.rb
21
+
22
+ # add the slice as a regular dependency
23
+ dependency 'merb-auth'
24
+
25
+ === config/router.rb
26
+
27
+ # This will add the following routes /login, /logout and /signup
28
+ r.slice(:MerbAuth)
29
+
30
+ # This will add the following routes /merb-auth/login, /merb-auth/logout and /merb-auth/signup
31
+ r.add_slice(:MerbAuth)
32
+
33
+ # This will add the following routes /user/login, /user/logout and /user/signup
34
+ r.add_slice(:MerbAuth, 'user') # same as :path => 'user'
35
+
36
+ === Normally you should also run the following rake task:
37
+ > rake slices:merb_auth:install
38
+
39
+ == Customization/Overrides
40
+
41
+ By default, the user model class MerbAuth::User is aliased to User.
42
+
43
+ You can also put your application-level overrides in:
44
+
45
+ host-app/slices/merb-auth/app - controllers, models, views ...
46
+
47
+ Templates are located in this order:
48
+
49
+ 1. host-app/slices/merb-auth/app/views/*
50
+ 2. gems/merb-auth/app/views/*
51
+ 3. host-app/app/views/*
52
+
53
+ You can use the host application's layout by configuring the
54
+ merb-auth slice in a before_app_loads block:
55
+
56
+ Merb::Slices.config[:merb_auth] = { :layout => :application }
57
+
58
+ By default :merb_auth is used. If you need to override
59
+ stylesheets or javascripts, just specify your own files in your layout
60
+ instead/in addition to the ones supplied (if any) in
61
+ host-app/public/slices/merb-auth.
62
+
63
+ In any case don't edit those files directly as they may be clobbered any time
64
+ rake merb_auth:install is run.
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'spec/rake/spectask'
4
+ require 'merb-core/version'
5
+ require 'merb-core/test/tasks/spectasks'
6
+
7
+ PLUGIN = "merb-auth"
8
+ NAME = "merb-auth"
9
+ AUTHOR = "Cuong Tran"
10
+ EMAIL = "ctran@pragmaquest.com"
11
+ HOMEPAGE = "http://merb-slices.rubyforge.org/merb-auth/"
12
+ SUMMARY = "Merb Slice that provides user authentication"
13
+ SLICE_VERSION = "0.1.0"
14
+
15
+ spec = Gem::Specification.new do |s|
16
+ s.name = NAME
17
+ s.version = SLICE_VERSION
18
+ s.platform = Gem::Platform::RUBY
19
+ s.has_rdoc = true
20
+ s.extra_rdoc_files = ["README", "LICENSE"]
21
+ s.summary = SUMMARY
22
+ s.description = s.summary
23
+ s.author = AUTHOR
24
+ s.email = EMAIL
25
+ s.homepage = HOMEPAGE
26
+ s.add_dependency('merb-slices', '>= 0.9.4')
27
+ s.require_path = 'lib'
28
+ s.files = %w(LICENSE README Rakefile) + Dir.glob("{lib,spec,app,public}/**/*")
29
+ end
30
+
31
+ Rake::GemPackageTask.new(spec) do |pkg|
32
+ pkg.gem_spec = spec
33
+ end
34
+
35
+ desc "Install Foo as a gem"
36
+ task :install => [:package] do
37
+ sh %{sudo gem install pkg/#{NAME}-#{SLICE_VERSION} --no-update-sources --local}
38
+ end
39
+
40
+ namespace :jruby do
41
+
42
+ desc "Run :package and install the resulting .gem with jruby"
43
+ task :install => :package do
44
+ sh %{#{SUDO} jruby -S gem install pkg/#{NAME}-#{VERSION}.gem --no-rdoc --no-ri}
45
+ end
46
+
47
+ end
@@ -0,0 +1,4 @@
1
+ class MerbAuth::Application < Merb::Controller
2
+ controller_for_slice
3
+ include MerbAuth::ControllerMixin
4
+ end
@@ -0,0 +1,150 @@
1
+ module MerbAuth
2
+ module ControllerMixin
3
+ protected
4
+ # Returns true or false if the user is logged in.
5
+ # Preloads @current_user with the user model if they're logged in.
6
+ def logged_in?
7
+ current_user != :false
8
+ end
9
+
10
+ # Accesses the current user from the session. Set it to :false if login fails
11
+ # so that future calls do not hit the database.
12
+ def current_user
13
+ @current_user ||= (login_from_session || login_from_basic_auth || login_from_cookie || :false)
14
+ end
15
+
16
+ # Store the given user in the session.
17
+ def current_user=(new_user)
18
+ session[:user] = (new_user.nil? || new_user.is_a?(Symbol)) ? nil : new_user.id
19
+ @current_user = new_user
20
+ end
21
+
22
+ # Check if the user is authorized
23
+ #
24
+ # Override this method in your controllers if you want to restrict access
25
+ # to only a few actions or if you want to check if the user
26
+ # has the correct rights.
27
+ #
28
+ # Example:
29
+ #
30
+ # # only allow nonbobs
31
+ # def authorized?
32
+ # current_user.username != "bob"
33
+ # end
34
+ def authorized?
35
+ logged_in?
36
+ end
37
+
38
+ # Filter method to enforce a login requirement.
39
+ #
40
+ # To require logins for all actions, use this in your controllers:
41
+ #
42
+ # before_filter :login_required
43
+ #
44
+ # To require logins for specific actions, use this in your controllers:
45
+ #
46
+ # before_filter :login_required, :only => [ :edit, :update ]
47
+ #
48
+ # To skip this in a subclassed controller:
49
+ #
50
+ # skip_before_filter :login_required
51
+ #
52
+ def login_required
53
+ authorized? || throw(:halt, :access_denied)
54
+ end
55
+
56
+ # Redirect as appropriate when an access request fails.
57
+ #
58
+ # The default HTML action is to redirect to the login screen.
59
+ #
60
+ # The default XML action is to render the text Couldn't authenticate you.
61
+ # To provide this response wrapped in XML, make sure to specify an
62
+ # XML layout, such as /app/views/layouts/application.xml.builder.
63
+ #
64
+ # Override this method in your controllers if you want to have special
65
+ # behavior in case the user is not authorized
66
+ # to access the requested action. For example, a popup window might
67
+ # simply close itself.
68
+ def access_denied
69
+ case content_type
70
+ when :html
71
+ store_location
72
+ redirect url(:login)
73
+ when :xml
74
+ headers["Status"] = "Unauthorized"
75
+ headers["WWW-Authenticate"] = %(Basic realm="Web Password")
76
+ self.status = 401
77
+ render "Couldn't authenticate you"
78
+ end
79
+ end
80
+
81
+ # Store the URI of the current request in the session.
82
+ #
83
+ # We can return to this location by calling #redirect_back_or_default.
84
+ def store_location
85
+ session[:return_to] = request.uri
86
+ end
87
+
88
+ # Redirect to the URI stored by the most recent store_location call or
89
+ # to the passed default.
90
+ def redirect_back_or_default(default)
91
+ loc = session[:return_to] || default
92
+ session[:return_to] = nil
93
+ redirect loc
94
+ end
95
+
96
+ # Inclusion hook to make #current_user and #logged_in?
97
+ # available as ActionView helper methods.
98
+ # def self.included(base)
99
+ # base.send :helper_method, :current_user, :logged_in?
100
+ # end
101
+
102
+ # Called from #current_user. First attempt to login by the user id stored in the session.
103
+ def login_from_session
104
+ self.current_user = find_user_by_id(session[:user]) if session[:user]
105
+ end
106
+
107
+ # Called from #current_user. Now, attempt to login by basic authentication information.
108
+ def login_from_basic_auth
109
+ username, passwd = get_auth_data
110
+ self.current_user = verify_login(username, passwd) if username && passwd
111
+ end
112
+
113
+ # Called from #current_user. Finaly, attempt to login by an expiring token in the cookie.
114
+ def login_from_cookie
115
+ user = cookies[:auth_token] && find_user_by_remember_token(cookies[:auth_token])
116
+ if user && user.remember_token?
117
+ user.remember_me
118
+ cookies[:auth_token] = { :value => user.remember_token, :expires => Time.parse(user.remember_token_expires_at.strftime) }
119
+ self.current_user = user
120
+ end
121
+ end
122
+
123
+ def reset_session
124
+ session.data.each{|k,v| session.data.delete(k)}
125
+ end
126
+
127
+ protected
128
+ def verify_login(username, password)
129
+ MerbAuth::User.authenticate(username, password)
130
+ end
131
+
132
+ def find_user_by_id(user_id)
133
+ MerbAuth::User.find_by_id(user_id)
134
+ end
135
+
136
+ def find_user_by_remember_token(token)
137
+ MerbAuth::User.find_by_remember_token(token)
138
+ end
139
+
140
+ private
141
+ @@http_auth_headers = %w(Authorization HTTP_AUTHORIZATION X-HTTP_AUTHORIZATION X_HTTP_AUTHORIZATION REDIRECT_X_HTTP_AUTHORIZATION)
142
+
143
+ # gets BASIC auth info
144
+ def get_auth_data
145
+ auth_key = @@http_auth_headers.detect { |h| request.env.has_key?(h) }
146
+ auth_data = request.env[auth_key].to_s.split unless auth_key.blank?
147
+ return auth_data && auth_data[0] == 'Basic' ? Base64.decode64(auth_data[1]).split(':')[0..1] : [nil, nil]
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,41 @@
1
+ class MerbAuth::Users < MerbAuth::Application
2
+ provides :html
3
+
4
+ skip_before :login_required
5
+
6
+ def login
7
+ if request.post?
8
+ self.current_user = verify_login(params[:username], params[:password])
9
+ if logged_in?
10
+ if params[:remember_me] == "1"
11
+ self.current_user.remember_me
12
+ cookies[:auth_token] = {
13
+ :value => self.current_user.remember_token,
14
+ :expires => Time.parse(current_user.remember_token_expires_at.strftime)
15
+ }
16
+ end
17
+ return redirect_back_or_default('/')
18
+ end
19
+ end
20
+
21
+ render
22
+ end
23
+
24
+ def logout
25
+ self.current_user.forget_me if logged_in?
26
+ cookies.delete :auth_token
27
+ reset_session
28
+ redirect_back_or_default('/')
29
+ end
30
+
31
+ def signup
32
+ cookies.delete :auth_token
33
+ @user = MerbAuth::User.new(params['merb_auth::user'] || {})
34
+
35
+ if request.post? && @user.save
36
+ return redirect_back_or_default('/')
37
+ end
38
+
39
+ render
40
+ end
41
+ end
@@ -0,0 +1,64 @@
1
+ module Merb
2
+ module MerbAuth
3
+ module ApplicationHelper
4
+
5
+ # @param *segments<Array[#to_s]> Path segments to append.
6
+ #
7
+ # @return <String>
8
+ # A path relative to the public directory, with added segments.
9
+ def image_path(*segments)
10
+ public_path_for(:image, *segments)
11
+ end
12
+
13
+ # @param *segments<Array[#to_s]> Path segments to append.
14
+ #
15
+ # @return <String>
16
+ # A path relative to the public directory, with added segments.
17
+ def javascript_path(*segments)
18
+ public_path_for(:javascript, *segments)
19
+ end
20
+
21
+ # @param *segments<Array[#to_s]> Path segments to append.
22
+ #
23
+ # @return <String>
24
+ # A path relative to the public directory, with added segments.
25
+ def stylesheet_path(*segments)
26
+ public_path_for(:stylesheet, *segments)
27
+ end
28
+
29
+ # Construct a path relative to the public directory
30
+ #
31
+ # @param <Symbol> The type of component.
32
+ # @param *segments<Array[#to_s]> Path segments to append.
33
+ #
34
+ # @return <String>
35
+ # A path relative to the public directory, with added segments.
36
+ def public_path_for(type, *segments)
37
+ File.join(::MerbAuth.public_dir_for(type), *segments)
38
+ end
39
+
40
+ # Construct an app-level path.
41
+ #
42
+ # @param <Symbol> The type of component.
43
+ # @param *segments<Array[#to_s]> Path segments to append.
44
+ #
45
+ # @return <String>
46
+ # A path within the host application, with added segments.
47
+ def app_path_for(type, *segments)
48
+ File.join(::MerbAuth.app_dir_for(type), *segments)
49
+ end
50
+
51
+ # Construct a slice-level path.
52
+ #
53
+ # @param <Symbol> The type of component.
54
+ # @param *segments<Array[#to_s]> Path segments to append.
55
+ #
56
+ # @return <String>
57
+ # A path within the slice source (Gem), with added segments.
58
+ def slice_path_for(type, *segments)
59
+ File.join(::MerbAuth.dir_for(type), *segments)
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,47 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us" lang="en-us">
3
+ <head>
4
+ <title>Auth::Slice Sample Layout</title>
5
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
6
+ <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.1/build/reset-fonts-grids/reset-fonts-grids.css">
7
+ <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.1/build/base/base-min.css">
8
+ <link href="<%= public_path_for :stylesheet, 'master.css' %>" type="text/css" charset="utf-8" rel="stylesheet" media="all"/>
9
+ <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js"></script>
10
+ <script type="text/javascript">
11
+ (function($) {
12
+ $(document).ready(function() {
13
+ $(":text:visible:enabled:first").focus();
14
+ });
15
+ })(jQuery);
16
+ </script>
17
+ </head>
18
+ <body>
19
+ <div id="container">
20
+ <div id="header-container">
21
+ <span style="float:right;margin-right:50px">
22
+ <% if logged_in? %>
23
+ Welcome <%= current_user.name %>
24
+ | <a href="<%= url(:logout) %>">Sign out</a>
25
+ <% else %>
26
+ <a href="<%= url(:signup) %>">Join now</a>
27
+ | <a href="<%= url(:login) %>">Sign in</a>
28
+ <% end %>
29
+ </span>
30
+ <h1>Sample MerbAuth App</h1>
31
+ <hr />
32
+ </div>
33
+
34
+ <div id="main-container">
35
+ <%= catch_content :for_layout %>
36
+ </div>
37
+
38
+ <hr />
39
+
40
+ <div id="footer-container">
41
+ <div class="left"><%= __FILE__ %></div>
42
+ <div class="right">&copy; 2008 PragmaQuest Inc. All rights reserved.</div>
43
+ </div>
44
+ </div>
45
+ </body>
46
+ </html>
47
+
@@ -0,0 +1,31 @@
1
+
2
+ <div>
3
+ <div class="form_description">
4
+ <h2>Member sign in</h2>
5
+ </div>
6
+
7
+ <form action="<%= url(:login) %>" method="post">
8
+ <% if request.post? -%>
9
+ <div class="error">
10
+ <h2>Incorrect username and password</h2>
11
+ </div>
12
+ <% end -%>
13
+
14
+ <p><label for="username">Username or e-mail</label>
15
+ <input type="text" name="username" value="<%= params[:username] %>"/></p>
16
+
17
+ <p><label for="password">Password</label>
18
+ <input type="password" name="password"/></p>
19
+
20
+ <p><input type="checkbox" name='remember_me' id='remember_me' value="1"/>
21
+ <label id='remember_me_lb' for="remember_me">Keep me signed in</label></p>
22
+
23
+ <p><input type="submit" value="Sign in"/> <span style="margin-left:20px"><a href="#">Forgot password?</a></span></p>
24
+ </form>
25
+
26
+ <form action="#" method="post" style="display:none">
27
+ <p><label for="openid_url">OpenID</label>
28
+ <input type="text" value="" size="40" id="openid_url" name="openid_url"/>  <small>(e.g. http://username.myopenid.com)</small>
29
+ </p>
30
+ </form>
31
+ </div>
@@ -0,0 +1,29 @@
1
+
2
+ <div>
3
+ <div class="form_description">
4
+ <h2>Sign up here (it's absolutely free)</h2>
5
+ </div>
6
+
7
+ <% form_for @user, :action => url(:signup) do %>
8
+ <%= error_messages_for :user, lambda{|err| "<li>#{err.join('<br/>')}</li>"} %>
9
+
10
+ <p>
11
+ <%= text_control :name, :label => "Name" %>
12
+ </p>
13
+ <p>
14
+ <%= text_control :username, :label => "Username" %>
15
+ </p>
16
+ <p>
17
+ <%= text_control :email, :label => "E-mail" %>
18
+ </p>
19
+ <p>
20
+ <%= password_control :password, :label => "Password" %>
21
+ </p>
22
+ <p>
23
+ <%= password_control :password_confirmation, :label => "Password Confirmation" %>
24
+ </p>
25
+ <p>
26
+ <%= submit_button "Sign up" %> <span style="margin-left:20px">Already a member? <a href="<%= url(:login) %>">Sign in here</a></span>
27
+ </p>
28
+ <% end %>
29
+ </div>
@@ -0,0 +1,52 @@
1
+ module MerbAuth
2
+ module Adapter
3
+ module Activerecord
4
+ def self.included(base)
5
+ base.class_eval do
6
+ include MerbAuth::BaseModel
7
+ extend ClassMethods
8
+
9
+ validates_presence_of :name
10
+ validates_presence_of :username
11
+ validates_presence_of :email
12
+ validates_length_of :username, :within => 3..40
13
+ validates_length_of :password, :within => 4..40, :if => :password_required?
14
+ validates_presence_of :password_confirmation, :if => :password_required?
15
+ validates_confirmation_of :password, :if => :password_required?
16
+ validates_uniqueness_of :username, :case_sensitive => false, :if => lambda { |u| !u.username.blank? }
17
+ validates_uniqueness_of :email, :case_sensitive => false, :if => lambda { |u| !u.email.blank? }
18
+
19
+ before_save :encrypt_password
20
+
21
+ def username=(value)
22
+ self[:username] = value.downcase if value
23
+ end
24
+ end
25
+ end
26
+
27
+ module ClassMethods
28
+ def drop_db_table
29
+ self.connection.drop_table("users")
30
+ end
31
+
32
+ def create_db_table
33
+ self.connection.create_table("users") do |t|
34
+ t.string :name, :limit => 40, :null => false
35
+ t.string :username, :limit => 40, :null => false
36
+ t.string :email, :null => false
37
+ t.string :crypted_password, :limit => 40
38
+ t.string :salt, :limit => 40, :null => false
39
+ t.string :remember_token
40
+ t.date :remember_token_expires_at
41
+ t.timestamps
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ MerbAuth.send(:remove_const, :User) if defined? MerbAuth::User
50
+ class MerbAuth::User < ActiveRecord::Base
51
+ include MerbAuth::Adapter::Activerecord
52
+ end
@@ -0,0 +1,78 @@
1
+ require 'dm-core'
2
+ require 'dm-validations'
3
+ require 'dm-timestamps'
4
+ require 'dm-aggregates'
5
+
6
+ module MerbAuth
7
+ module Adapter
8
+ module Datamapper
9
+ def self.included(base)
10
+ base.class_eval do
11
+ include ::DataMapper::Resource
12
+ include MerbAuth::BaseModel
13
+ extend ClassMethods
14
+
15
+ storage_names[:default] = 'users'
16
+
17
+ property :id, Integer, :serial => true
18
+ property :name, String, :length => 3..40, :nullable => false
19
+ property :username, String, :length => 3..40, :nullable => false
20
+ property :email, String, :format => :email_address, :nullable => false
21
+ property :crypted_password, String, :length => 40
22
+ property :salt, String, :length => 40
23
+ property :remember_token_expires_at, Date
24
+ property :remember_token, String
25
+ property :created_at, DateTime
26
+ property :updated_at, DateTime
27
+
28
+ validates_is_unique :username, :email
29
+ validates_length :password, :in => 4..40, :if => :password_required?
30
+ validates_is_confirmed :password, :groups => :create
31
+
32
+ before :save, :encrypt_password
33
+
34
+ def username=(value)
35
+ attribute_set(:username, value.downcase) if value
36
+ end
37
+ end
38
+ end
39
+
40
+ module ClassMethods
41
+ def create_db_table
42
+ self.auto_migrate!
43
+ end
44
+
45
+ def drop_db_table
46
+ self.repository do |r|
47
+ r.adapter.destroy_model_storage(r, self)
48
+ end
49
+ end
50
+
51
+ def find_by_id(id)
52
+ MerbAuth::User.first(:id => id)
53
+ end
54
+
55
+ def find_by_remember_token(rt)
56
+ MerbAuth::User.first(:remember_token => rt)
57
+ end
58
+
59
+ def find_by_username(username)
60
+ if MerbAuth::User.properties[:activated_at]
61
+ MerbAuth::User.first(:username => username, :activated_at.not => nil)
62
+ else
63
+ MerbAuth::User.first(:username => username)
64
+ end
65
+ end
66
+
67
+ def find_by_activiation_code(activation_code)
68
+ MerbAuth::User.first(:activation_code => activation_code)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ MerbAuth.send(:remove_const, :User) if defined? MerbAuth::User
76
+ class MerbAuth::User
77
+ include MerbAuth::Adapter::Datamapper
78
+ end