ateam-merb-auth 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/LICENSE +44 -0
  2. data/README +212 -0
  3. data/Rakefile +50 -0
  4. data/TODO +14 -0
  5. data/activerecord_generators/ma_migration/ma_migration_generator.rb +41 -0
  6. data/activerecord_generators/ma_migration/templates/schema/migrations/%time_stamp%_add_ma_user.rb +21 -0
  7. data/app/controllers/application.rb +7 -0
  8. data/app/controllers/sessions.rb +3 -0
  9. data/app/controllers/users.rb +3 -0
  10. data/app/helpers/application_helper.rb +64 -0
  11. data/app/mailers/user_mailer.rb +24 -0
  12. data/app/mailers/views/user_mailer/activation.text.erb +1 -0
  13. data/app/mailers/views/user_mailer/forgot_password.text.erb +5 -0
  14. data/app/mailers/views/user_mailer/signup.text.erb +7 -0
  15. data/app/views/layout/merb_auth.html.erb +16 -0
  16. data/app/views/sessions/new.html.erb +14 -0
  17. data/app/views/users/new.html.erb +18 -0
  18. data/datamapper_generators/ma_migration/ma_migration_generator.rb +38 -0
  19. data/datamapper_generators/ma_migration/templates/schema/migrations/add_ma_user.rb +21 -0
  20. data/lib/merb-auth.rb +184 -0
  21. data/lib/merb-auth/adapters/activerecord/init.rb +26 -0
  22. data/lib/merb-auth/adapters/activerecord/map.rb +44 -0
  23. data/lib/merb-auth/adapters/activerecord/model.rb +81 -0
  24. data/lib/merb-auth/adapters/common.rb +161 -0
  25. data/lib/merb-auth/adapters/datamapper/init.rb +28 -0
  26. data/lib/merb-auth/adapters/datamapper/map.rb +35 -0
  27. data/lib/merb-auth/adapters/datamapper/model.rb +72 -0
  28. data/lib/merb-auth/adapters/map.rb +0 -0
  29. data/lib/merb-auth/adapters/sequel/init.rb +26 -0
  30. data/lib/merb-auth/adapters/sequel/map.rb +35 -0
  31. data/lib/merb-auth/adapters/sequel/model.rb +86 -0
  32. data/lib/merb-auth/controller/controller.rb +113 -0
  33. data/lib/merb-auth/controller/sessions_base.rb +41 -0
  34. data/lib/merb-auth/controller/users_base.rb +55 -0
  35. data/lib/merb-auth/initializer.rb +47 -0
  36. data/lib/merb-auth/merbtasks.rb +168 -0
  37. data/lib/merb-auth/slicetasks.rb +102 -0
  38. data/plugins/forgotten_password/app/controllers/passwords.rb +90 -0
  39. data/plugins/forgotten_password/app/models/user.rb +52 -0
  40. data/plugins/forgotten_password/app/views/passwords/edit.html.erb +9 -0
  41. data/plugins/forgotten_password/app/views/passwords/new.html.erb +4 -0
  42. data/plugins/forgotten_password/forgotten_password.rb +6 -0
  43. data/plugins/forgotten_password/init.rb +8 -0
  44. data/plugins/forgotten_password/spec/controller_spec.rb +236 -0
  45. data/plugins/forgotten_password/spec/model_spec.rb +52 -0
  46. data/plugins/forgotten_password/spec/spec_helper.rb +36 -0
  47. data/public/javascripts/master.js +0 -0
  48. data/public/stylesheets/master.css +2 -0
  49. data/spec/controllers/plugins/test_plugin.rb +17 -0
  50. data/spec/controllers/sessions_spec.rb +118 -0
  51. data/spec/controllers/users_spec.rb +119 -0
  52. data/spec/mailers/user_mailer_spec.rb +75 -0
  53. data/spec/merb_auth_spec.rb +231 -0
  54. data/spec/models/ar_model_spec.rb +50 -0
  55. data/spec/models/common_spec.rb +0 -0
  56. data/spec/models/model_spec.rb +23 -0
  57. data/spec/models/sq_model_spec.rb +50 -0
  58. data/spec/shared_specs/shared_model_spec.rb +445 -0
  59. data/spec/spec_helper.rb +114 -0
  60. data/spec/spec_helpers/helpers.rb +18 -0
  61. data/spec/spec_helpers/valid_model_hashes.rb +10 -0
  62. data/stubs/app/controllers/application.rb +2 -0
  63. data/stubs/app/controllers/main.rb +2 -0
  64. data/stubs/app/mailers/views/activation.text.erb +1 -0
  65. data/stubs/app/mailers/views/signup.text.erb +7 -0
  66. data/stubs/app/views/sessions/new.html.erb +14 -0
  67. data/stubs/app/views/users/new.html.erb +18 -0
  68. metadata +119 -0
@@ -0,0 +1,24 @@
1
+ class MerbAuth::UserMailer < Merb::MailController
2
+
3
+ controller_for_slice MerbAuth, :templates_for => :mailer, :path => "views"
4
+
5
+ def signup
6
+ @ivar = params[MA[:single_resource]]
7
+ Merb.logger.info "Sending Signup to #{@ivar.email} with code #{@ivar.activation_code}"
8
+ instance_variable_set("@#{MA[:single_resource]}", @ivar )
9
+ render_mail :text => :signup, :layout => nil
10
+ end
11
+
12
+ def activation
13
+ @ivar = params[MA[:single_resource]]
14
+ Merb.logger.info "Sending Activation email to #{@ivar.email}"
15
+ instance_variable_set("@#{MA[:single_resource]}", @ivar )
16
+ render_mail :text => :activation, :layout => nil
17
+ end
18
+
19
+ def forgot_password
20
+ @ivar = params[MA[:single_resource]]
21
+ instance_variable_set("@#{MA[:single_resource]}", @ivar )
22
+ render_mail :text => :forgot_password, :layout => nil
23
+ end
24
+ end
@@ -0,0 +1 @@
1
+ Your email has been authenticated <%= @ivar.email %>
@@ -0,0 +1,5 @@
1
+ Your password has been reset.
2
+
3
+ Please visit:
4
+
5
+ <%= url(:merb_auth_password, :id => @ivar.password_reset_key ) %>
@@ -0,0 +1,7 @@
1
+ Your account has been created.
2
+
3
+ Username: <%= @ivar.email %>
4
+
5
+ Visit this url to activate your account:
6
+
7
+ <%= url(MA[:routes][:user][:activate], :activation_code => @ivar.activation_code) %>
@@ -0,0 +1,16 @@
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
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
5
+ <title>Fresh MerbAuth Slice</title>
6
+ <link href="<%= public_path_for :stylesheet, 'master.css' %>" type="text/css" charset="utf-8" rel="stylesheet" media="all" />
7
+ <script src="<%= public_path_for :javascript, 'master.js' %>" type="text/javascript" charset="utf-8"></script>
8
+ </head>
9
+ <!-- you can override this layout at slices/merb_auth/app/views/layout/merb_auth.html.erb -->
10
+ <body class="merb_auth">
11
+ <div id="container">
12
+ <h1>MerbAuth Slice</h1>
13
+ <div id="main"><%= catch_content :for_layout %></div>
14
+ </div>
15
+ </body>
16
+ </html>
@@ -0,0 +1,14 @@
1
+ <%= form :action => url(:login) do -%>
2
+ <p><label for="email">Email</label><br/>
3
+ <%= text_field :name => "email" %></p>
4
+
5
+ <p><label for="password">Password</label><br/>
6
+ <%= password_field :name => "password" %></p>
7
+
8
+ <!-- Uncomment this if you want this functionality
9
+ <p><label for="remember_me">Remember me:</label>
10
+ <%= check_box :name => 'remember_me' %></p>
11
+ -->
12
+
13
+ <p><%= submit 'Log in' %></p>
14
+ <% end =%>
@@ -0,0 +1,18 @@
1
+ <%= error_messages_for @ivar %>
2
+ <%= form_for @ivar, :action => url(MA[:routes][:user][:index]) do %>
3
+ <p>
4
+ <%= text_field :email, :label => "Email" %>
5
+ </p>
6
+ <p>
7
+ <%= text_field :login, :label => "Nickname" %>
8
+ </p>
9
+ <p>
10
+ <%= password_field :password, :label => "Password" %>
11
+ </p>
12
+ <p>
13
+ <%= password_field :password_confirmation, :label => "Password Confirmation" %>
14
+ </p>
15
+ <p>
16
+ <%= submit "Sign up" %>
17
+ </p>
18
+ <% end =%>
@@ -0,0 +1,38 @@
1
+ class MaMigrationGenerator < Merb::GeneratorBase
2
+
3
+ attr_accessor :plural_name, :single_name, :time_stamp
4
+
5
+ def initialize(runtime_args, runtime_options = {})
6
+ @base = File.dirname(__FILE__)
7
+ super
8
+ @name = args.shift
9
+ end
10
+
11
+ def manifest
12
+ record do |m|
13
+ @m = m
14
+
15
+ @table_name = @name.split("::").last.snake_case.singularize.pluralize
16
+
17
+ @assigns = {
18
+ :time_stamp => @time_stamp,
19
+ :class_name => @name,
20
+ :table_name => @table_name
21
+ }
22
+
23
+ copy_dirs
24
+ copy_files
25
+ end
26
+
27
+ end
28
+
29
+ protected
30
+ def banner
31
+ <<-EOS
32
+ Creates a migration for merb-auth user model
33
+
34
+ USAGE: #{$0} #{spec.name} name"
35
+ EOS
36
+ end
37
+
38
+ end
@@ -0,0 +1,21 @@
1
+ migration 1, :create_ma_users_table do
2
+ up do
3
+ create_table :<%= table_name %> do
4
+ column :id, Integer, :serial => true
5
+ column :login, String, :nullable? => false
6
+ column :email, String, :nullable? => false
7
+ column :created_at, DateTime
8
+ column :updated_at, DateTime
9
+ column :activated_at, DateTime
10
+ column :activation_code, String
11
+ column :crypted_password, String
12
+ column :salt, String
13
+ column :remember_token_expires_at, DateTime
14
+ column :remember_token, String
15
+ column :password_reset_key, String
16
+ end
17
+ end
18
+ down do
19
+ drop_table :<%= table_name %>
20
+ end
21
+ end
data/lib/merb-auth.rb ADDED
@@ -0,0 +1,184 @@
1
+ if defined?(Merb::Plugins)
2
+
3
+ require "digest/sha1"
4
+ require "merb-mailer"
5
+ require "merb_helpers"
6
+
7
+ load File.join(File.dirname(__FILE__), "merb-auth", "initializer.rb")
8
+
9
+ Dir[File.dirname(__FILE__) / "merb-auth" / "controller" / "**" / "*.rb"].each do |f|
10
+ load f
11
+ end
12
+
13
+ adapter_path = File.join( File.dirname(__FILE__), "merb-auth", "adapters")
14
+ load File.join(adapter_path, "common.rb")
15
+
16
+ MA = MerbAuth
17
+ MA.register_adapter :datamapper, "#{adapter_path}/datamapper"
18
+ MA.register_adapter :activerecord, "#{adapter_path}/activerecord"
19
+ MA.register_adapter :sequel, "#{adapter_path}/sequel"
20
+
21
+ Merb::Plugins.add_rakefiles "merb-auth/merbtasks", "merb-auth/slicetasks"
22
+
23
+ # Register the Slice for the current host application
24
+ Merb::Slices::register(__FILE__)
25
+
26
+
27
+ class Merb::BootLoader::MaLoadPlugins < Merb::BootLoader
28
+ after Merb::BootLoader::LoadClasses
29
+
30
+ def self.run
31
+ MA.load_plugins!
32
+ end
33
+ end
34
+
35
+ # Slice configuration - set this in a before_app_loads callback.
36
+ # By default a Slice uses its own layout, so you can swicht to
37
+ # the main application layout or no layout at all if needed.
38
+ #
39
+ # Configuration options:
40
+ # :layout - the layout to use; defaults to :merb_auth
41
+ # :mirror - which path component types to use on copy operations; defaults to all
42
+ Merb::Slices::config[:merb_auth] ||= {}
43
+ Merb::Slices::config[:merb_auth][:layout] ||= :merb_auth
44
+
45
+ # All Slice code is expected to be namespaced inside a module
46
+ module MerbAuth
47
+
48
+
49
+ def self.plugins
50
+ @@plugins ||= {}
51
+ end
52
+
53
+ def self.add_routes(&blk)
54
+ custom_routes << blk
55
+ end
56
+
57
+ def self.custom_routes
58
+ @custom_routes ||= []
59
+ end
60
+
61
+ def self.setup_custom_routes!
62
+ Merb.logger.info "Adding custom routes"
63
+ custom_routes.each do |r|
64
+ r.call(MA[:router_scope])
65
+ end
66
+ end
67
+
68
+ # Slice metadata
69
+ self.description = "MerbAuth is a Merb slice that provides authentication"
70
+ self.version = "0.1.0"
71
+ self.author = "Merb Core"
72
+
73
+ # Stub classes loaded hook - runs before LoadClasses BootLoader
74
+ # right after a slice's classes have been loaded internally
75
+ def self.loaded
76
+ # Setup the login field to use
77
+ MA[:login_field] = (MA[:login_field] || :email).to_sym
78
+
79
+ MA.load_adapter!
80
+
81
+ Merb::Controller.send(:include, MA::Controller::Helpers)
82
+ # sends the methoods to the controllers as an include so that other mixins can
83
+ # overwrite them
84
+ MA::Users.send( :include, MA::Controller::UsersBase)
85
+ MA::Sessions.send( :include, MA::Controller::SessionsBase)
86
+ end
87
+
88
+ # Initialization hook - runs before AfterAppLoads BootLoader
89
+ def self.init
90
+
91
+ end
92
+
93
+ # Activation hook - runs after AfterAppLoads BootLoader
94
+ def self.activate
95
+ # Make the aliases for current stuff
96
+ Merb::Controller.module_eval do
97
+ alias_method :"current_#{MA[:single_resource]}", :current_ma_user
98
+ alias_method :"current_#{MA[:single_resource]}=", :current_ma_user=
99
+ end
100
+ end
101
+
102
+ # Deactivation hook - triggered by Merb::Slices#deactivate
103
+ def self.deactivate
104
+ end
105
+
106
+ # Setup routes inside the host application
107
+ #
108
+ # @param scope<Merb::Router::Behaviour>
109
+ # Routes will be added within this scope (namespace). In fact, any
110
+ # router behaviour is a valid namespace, so you can attach
111
+ # routes at any level of your router setup.
112
+ def self.setup_router(scope)
113
+ MA[:router_scope] = scope # Hangs onto the scope for the plugins which are loaded after the routes are setup
114
+
115
+ MA.setup_custom_routes!
116
+
117
+ plural_model_path = MA[:route_path_model] || MA[:plural_resource]
118
+ plural_model_path ||= "User".snake_case.singularize.pluralize
119
+ plural_model_path = plural_model_path.to_s.match(%r{^/?(.*?)/?$})[1]
120
+ single_model_name = plural_model_path.singularize
121
+
122
+ plural_session_path = MA[:route_path_session] || "sessions"
123
+ plural_session_path = plural_session_path.to_s.match(%r{^/?(.*?)/?$})[1]
124
+ single_session_name = plural_session_path.singularize
125
+
126
+ activation_name = (MA[:single_resource].to_s << "_activation").to_sym
127
+
128
+ MA[:routes] = {:user => {}}
129
+ MA[:routes][:user][:new] ||= :"new_#{single_model_name}"
130
+ MA[:routes][:user][:show] ||= :"#{single_model_name}"
131
+ MA[:routes][:user][:edit] ||= :"edit_#{single_model_name}"
132
+ MA[:routes][:user][:delete] ||= :"delete_#{single_model_name}"
133
+ MA[:routes][:user][:index] ||= :"#{plural_model_path}"
134
+ MA[:routes][:user][:activate] ||= :"#{single_model_name}_activation"
135
+
136
+ # Setup the model path
137
+ scope.to(:controller => "Users") do |c|
138
+ c.match("/#{plural_model_path}") do |u|
139
+ # setup the named routes
140
+ u.match("/new", :method => :get ).to( :action => "new" ).name(MA[:routes][:user][:new])
141
+ u.match("/:id", :method => :get ).to( :action => "show" ).name(MA[:routes][:user][:show])
142
+ u.match("/:id/edit", :method => :get ).to( :action => "edit" ).name(MA[:routes][:user][:edit])
143
+ u.match("/:id/delete", :method => :get ).to( :action => "delete" ).name(MA[:routes][:user][:delete])
144
+ u.match("/", :method => :get ).to( :action => "index" ).name(MA[:routes][:user][:index])
145
+ u.match("/activate/:activation_code", :method => :get).to( :action => "activate").name(MA[:routes][:user][:activate])
146
+
147
+ # Make the anonymous routes
148
+ u.match(%r{(/|/index)?(\.:format)?$}, :method => :get ).to( :action => "index")
149
+ u.match(%r{/new$}, :method => :get ).to( :action => "new")
150
+ u.match(%r{/:id(\.:format)?$}, :method => :get ).to( :action => "show")
151
+ u.match(%r{/:id/edit$}, :method => :get ).to( :action => "edit")
152
+ u.match(%r{/:id/delete$}, :method => :get ).to( :action => "delete")
153
+ u.match(%r{/?(\.:format)?$}, :method => :post ).to( :action => "create")
154
+ u.match(%r{/:id(\.:format)?$}, :method => :put ).to( :action => "update")
155
+ u.match(%r{/:id(\.:format)?$}, :method => :delete ).to( :action => "destroy")
156
+ end
157
+ end
158
+
159
+ scope.match("/signup").to(:controller => "Users", :action => "new" ).name(:signup)
160
+ scope.match("/login" ).to(:controller => "sessions", :action => "create" ).name(:login)
161
+ scope.match("/logout").to(:controller => "sessions", :action => "destroy").name(:logout)
162
+ end
163
+
164
+ end
165
+
166
+ # Setup the slice layout for MerbAuth
167
+ #
168
+ # Use MerbAuth.push_path and MerbAuth.push_app_path
169
+ # to set paths to merb_auth-level and app-level paths. Example:
170
+ #
171
+ # MerbAuth.push_path(:application, MerbAuth.root)
172
+ # MerbAuth.push_app_path(:application, Merb.root / "slices" / "merb-auth")
173
+ # ...
174
+ #
175
+ # Any component path that hasn't been set will default to MerbAuth.root
176
+ #
177
+ # Or just call setup_default_structure! to setup a basic Merb MVC structure.
178
+ MerbAuth.setup_default_structure!
179
+
180
+ end
181
+
182
+ Dir[File.join(File.dirname(__FILE__), "..", "plugins/*/init.rb")].each do |f|
183
+ require f
184
+ end
@@ -0,0 +1,26 @@
1
+ require 'active_record'
2
+
3
+ path = File.dirname(__FILE__)
4
+
5
+ if Merb.env?(:test)
6
+ # Need to make sure the class is removed when testing
7
+ # It should not impact a normal apps tests
8
+ if MA[:user]
9
+ klass = MA[:user]
10
+ Object.class_eval do
11
+ remove_const(klass.name) if klass
12
+ end
13
+ end
14
+ MA[:user] = nil
15
+ MerbAuth.module_eval do
16
+ remove_const("Adapter") if defined?(Adapter)
17
+ end
18
+ load path / ".." / "common.rb"
19
+ load path / "map.rb"
20
+ load path / "model.rb"
21
+
22
+ else
23
+ require path / ".." / "common"
24
+ require path / "map"
25
+ require path / "model"
26
+ end
@@ -0,0 +1,44 @@
1
+ module MerbAuth
2
+ module Adapter
3
+ module ActiveRecord
4
+ module Map
5
+
6
+ def self.included(base)
7
+ base.send(:include, InstanceMethods)
8
+ base.send(:extend, ClassMethods)
9
+ end
10
+
11
+ module InstanceMethods
12
+ end
13
+
14
+ module ClassMethods
15
+ def find_active_with_conditions(conditions)
16
+ if MA[:user].instance_methods.include?("activated_at")
17
+ MA[:user].with_scope(:find => {:conditions => "activated_at IS NOT NULL"}) do
18
+ MA[:user].find(:first, :conditions => conditions)
19
+ end
20
+ else
21
+ MA[:user].find(:first, :conditions => conditions)
22
+ end
23
+ end
24
+
25
+ def find_with_conditions(conditions)
26
+ MA[:user].find(:first, :conditions => conditions)
27
+ end
28
+
29
+ def find_all_with_login_like(login)
30
+ MA[:user].with_scope(:find => {:order => "login DESC", :limit => 1}) do
31
+ MA[:user].find(:all, :conditions => ["login LIKE ?", login])
32
+ end
33
+ end
34
+
35
+ # A method to assist with specs
36
+ def clear_database_table
37
+ MA[:user].delete_all
38
+ end
39
+ end
40
+
41
+ end # Map
42
+ end # ActiveRecord
43
+ end # Adapter
44
+ end # MerbAuthenticaiton
@@ -0,0 +1,81 @@
1
+ module MerbAuth
2
+ module Adapter
3
+ module ActiveRecord
4
+
5
+ def self.included(base)
6
+ # Ensure base is a resource
7
+ raise "Mixin class is not an activerecord class" unless base.ancestors.include?(::ActiveRecord::Base)
8
+ set_model_class_decs!(base)
9
+
10
+ base.send(:include, Map)
11
+ base.send(:include, InstanceMethods )
12
+ base.send(:include, Common)
13
+
14
+
15
+ MA[:single_resource] ||= base.name.snake_case.gsub("::", "__").to_sym
16
+ MA[:plural_resource] ||= MA[:single_resource].to_s.pluralize.to_sym
17
+
18
+ MA[:user] = base
19
+ end
20
+
21
+
22
+ module InstanceMethods
23
+
24
+ def login=(login_name)
25
+ self[:login] = login_name.downcase unless login_name.nil?
26
+ end
27
+
28
+ end
29
+
30
+
31
+ private
32
+ def self.set_model_class_decs!(base)
33
+ # base.instance_eval do
34
+ # # Virtual attribute for the unencrypted password
35
+ # attr_accessor :password, :password_confirmation
36
+ # validates_presence_of :login, :email
37
+ # validates_presence_of :password, :if => :password_required?
38
+ # validates_presence_of :password_confirmation, :if => :password_required?
39
+ # validates_length_of :password, :within => 4..40, :if => :password_required?
40
+ # validates_confirmation_of :password, :if => :password_required?
41
+ # validates_length_of :login, :within => 3..40
42
+ # validates_length_of :email, :within => 3..100
43
+ # validates_uniqueness_of :login, :email, :case_sensitive => false
44
+ # validates_uniqueness_of :password_reset_key, :if => Proc.new{|m| !m.password_reset_key.nil?}
45
+ #
46
+ #
47
+ # before_save :encrypt_password
48
+ # before_validation :set_login
49
+ # before_create :make_activation_code
50
+ # after_create :send_signup_notification
51
+ # end
52
+ end
53
+
54
+ module DefaultModelSetup
55
+
56
+ def self.included(base)
57
+ base.instance_eval do
58
+ # Virtual attribute for the unencrypted password
59
+ attr_accessor :password, :password_confirmation
60
+ validates_presence_of :login, :email
61
+ validates_presence_of :password, :if => :password_required?
62
+ validates_presence_of :password_confirmation, :if => :password_required?
63
+ validates_length_of :password, :within => 4..40, :if => :password_required?
64
+ validates_confirmation_of :password, :if => :password_required?
65
+ validates_length_of :login, :within => 3..40
66
+ validates_length_of :email, :within => 3..100
67
+ validates_uniqueness_of :login, :email, :case_sensitive => false
68
+ validates_uniqueness_of :password_reset_key, :if => Proc.new{|m| !m.password_reset_key.nil?}
69
+
70
+
71
+ before_save :encrypt_password
72
+ before_validation :set_login
73
+ before_create :make_activation_code
74
+ after_create :send_signup_notification
75
+ end
76
+ end
77
+ end # DefaultModelSetup
78
+
79
+ end # ActiveRecord
80
+ end # Adapter
81
+ end # MerbAuth