merb-auth 0.1.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/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