auth-slice 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,80 @@
1
+ require 'dm-validations'
2
+ require 'dm-timestamps'
3
+ require 'dm-aggregates'
4
+
5
+ module AuthSlice
6
+ module Adapter
7
+ module Datamapper
8
+ def self.create_user_model
9
+ Object.class_eval <<-end_eval
10
+ class ::AuthSlice::User
11
+ include AuthSlice::Adapter::Datamapper
12
+ end
13
+ end_eval
14
+ end
15
+
16
+ def self.included(base)
17
+ base.class_eval do
18
+ include ::DataMapper::Resource
19
+ include AuthSlice::BaseModel
20
+ extend ClassMethods
21
+
22
+ storage_names[:default] = 'users'
23
+
24
+ property :id, Integer, :serial => true
25
+ property :name, String, :length => 3..40, :nullable => false
26
+ property :username, String, :length => 3..40, :nullable => false
27
+ property :email, String, :format => :email_address, :nullable => false
28
+ property :crypted_password, String, :length => 40
29
+ property :salt, String, :length => 40
30
+ property :remember_token_expires_at, Date
31
+ property :remember_token, String
32
+ property :created_at, DateTime
33
+ property :updated_at, DateTime
34
+
35
+ validates_is_unique :username, :email
36
+ validates_length :password, :in => 4..40, :if => :password_required?
37
+ validates_is_confirmed :password, :groups => :create
38
+
39
+ before :save, :encrypt_password
40
+
41
+ def username=(value)
42
+ attribute_set(:username, value.downcase) if value
43
+ end
44
+ end
45
+ end
46
+
47
+ module ClassMethods
48
+ def create_db_table
49
+ self.auto_migrate!
50
+ end
51
+
52
+ def drop_db_table
53
+ self.repository do |r|
54
+ r.adapter.destroy_model_storage(r, self)
55
+ end
56
+ end
57
+
58
+ def find_by_id(id)
59
+ AuthSlice::User.first(:id => id)
60
+ end
61
+
62
+ def find_by_remember_token(rt)
63
+ AuthSlice::User.first(:remember_token => rt)
64
+ end
65
+
66
+ def find_by_username(username)
67
+ if AuthSlice::User.properties[:activated_at]
68
+ AuthSlice::User.first(:username => username, :activated_at.not => nil)
69
+ else
70
+ AuthSlice::User.first(:username => username)
71
+ end
72
+ end
73
+
74
+ def find_by_activiation_code(activation_code)
75
+ AuthSlice::User.first(:activation_code => activation_code)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,76 @@
1
+ require 'digest/sha1'
2
+
3
+ module AuthSlice
4
+ module BaseModel
5
+ def self.included(base)
6
+ base.send(:include, InstanceMethods)
7
+ base.send(:extend, ClassMethods)
8
+
9
+ base.class_eval do
10
+ attr_accessor :password, :password_confirmation
11
+ end
12
+ end
13
+
14
+ module InstanceMethods
15
+ def authenticated?(password)
16
+ crypted_password == encrypt(password)
17
+ end
18
+
19
+ # before filter
20
+ def encrypt_password
21
+ return if password.blank?
22
+ self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{username}--") if new_record?
23
+ self.crypted_password = encrypt(password)
24
+ end
25
+
26
+ # Encrypts the password with the user salt
27
+ def encrypt(password)
28
+ self.class.encrypt(password, salt)
29
+ end
30
+
31
+ def remember_token?
32
+ remember_token_expires_at && Date.today < remember_token_expires_at
33
+ end
34
+
35
+ def remember_me_until(time)
36
+ self.remember_token_expires_at = time
37
+ self.remember_token = encrypt("#{email}--#{remember_token_expires_at}")
38
+ save
39
+ end
40
+
41
+ def remember_me_for(days)
42
+ remember_me_until (Date.today + days)
43
+ end
44
+
45
+ # These create and unset the fields required for remembering users between browser closes
46
+ # Default of 2 weeks
47
+ def remember_me
48
+ remember_me_for(14)
49
+ end
50
+
51
+ def forget_me
52
+ self.remember_token_expires_at = nil
53
+ self.remember_token = nil
54
+ self.save
55
+ end
56
+
57
+ protected
58
+ def password_required?
59
+ crypted_password.blank? || !password.blank?
60
+ end
61
+ end
62
+
63
+ module ClassMethods
64
+ # Encrypts some data with the salt.
65
+ def encrypt(password, salt)
66
+ Digest::SHA1.hexdigest("--#{salt}--#{password}--")
67
+ end
68
+
69
+ # Authenticates a user by their username and unencrypted password. Returns the user or nil.
70
+ def authenticate(username, password)
71
+ u = find_by_username(username) # need to get the salt
72
+ u && u.authenticated?(password) ? u : nil
73
+ end
74
+ end
75
+ end
76
+ 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 AuthSlice 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'/>
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,63 @@
1
+ if defined?(Merb::Plugins)
2
+
3
+ require 'merb-slices'
4
+ require 'merb_helpers'
5
+ require File.join(File.dirname(__FILE__), 'auth-slice', 'model')
6
+
7
+ Merb::Plugins.add_rakefiles "auth-slice/merbtasks"
8
+
9
+ # Register the Slice for the current host application
10
+ Merb::Slices::register(__FILE__)
11
+
12
+ # Slice configuration - set this in a before_app_loads callback.
13
+ # By default a Slice uses its own layout.
14
+ Merb::Slices::config[:auth_slice] = { :layout => :auth_slice }
15
+
16
+ # All Slice code is expected to be namespaced inside a module
17
+ module AuthSlice
18
+ # Slice metadata
19
+ self.description = "AuthSlice is an user authentication Merb slice!"
20
+ self.version = "0.1.0"
21
+ self.author = "ctran@pragmaquest.com"
22
+
23
+ # Initialization hook - runs before AfterAppLoads BootLoader
24
+ def self.init
25
+ end
26
+
27
+ # Activation hook - runs after AfterAppLoads BootLoader
28
+ def self.activate
29
+ AuthSlice.use_adapter(Merb.orm_generator_scope) if Merb.orm_generator_scope != :merb_default
30
+ Object.const_set(Merb::Slices::config[:auth_slice][:user_model_class], AuthSlice::User) if Merb::Slices::config[:auth_slice][:user_model_class]
31
+ end
32
+
33
+ # Deactivation hook - triggered by Merb::Slices#deactivate
34
+ def self.deactivate
35
+ end
36
+
37
+ # Setup routes inside the host application
38
+ #
39
+ # @param scope<Merb::Router::Behaviour>
40
+ # Routes will be added within this scope (namespace). In fact, any
41
+ # router behaviour is a valid namespace, so you can attach
42
+ # routes at any level of your router setup.
43
+ def self.setup_router(scope)
44
+ scope.match('/login').to(:controller => 'users', :action => 'login').name(:login)
45
+ scope.match('/logout').to(:controller => 'users', :action => 'logout').name(:logout)
46
+ scope.match('/signup').to(:controller => 'users', :action => 'signup').name(:signup)
47
+ end
48
+ end
49
+
50
+ # Setup the slice layout for AuthSlice
51
+ #
52
+ # Use AuthSlice.push_path and AuthSlice.push_app_path
53
+ # to set paths to auth-slice-level and app-level paths. Example:
54
+ #
55
+ # AuthSlice.push_path(:application, AuthSlice.root)
56
+ # AuthSlice.push_app_path(:application, Merb.root / 'slices' / 'auth-slice')
57
+ # ...
58
+ #
59
+ # Any component path that hasn't been set will default to AuthSlice.root
60
+ #
61
+ # Or just call setup_default_structure! to setup a basic Merb MVC structure.
62
+ AuthSlice.setup_default_structure!
63
+ end
@@ -0,0 +1,110 @@
1
+ $SLICED_APP=true # we're running inside the host application context
2
+
3
+ namespace :slices do
4
+ namespace :auth_slice do
5
+
6
+ desc "Install AuthSlice"
7
+ task :install => [:preflight, :setup_directories, :copy_assets, :migrate]
8
+
9
+ desc "Test for any dependencies"
10
+ task :preflight do
11
+ # implement this to test for structural/code dependencies
12
+ # like certain directories or availability of other files
13
+ end
14
+
15
+ desc "Setup directories"
16
+ task :setup_directories do
17
+ puts "Creating directories for host application"
18
+ [:application, :view, :model, :controller, :helper, :mailer, :part, :public].each do |type|
19
+ if File.directory?(AuthSlice.dir_for(type))
20
+ if !File.directory?(dst_path = AuthSlice.app_dir_for(type))
21
+ relative_path = dst_path.relative_path_from(Merb.root)
22
+ puts "- creating directory :#{type} #{File.basename(Merb.root) / relative_path}"
23
+ mkdir_p(dst_path)
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ desc "Copy public assets to host application"
30
+ task :copy_assets do
31
+ puts "Copying assets for AuthSlice - do not edit these as the will be overwritten!"
32
+ [:image, :javascript, :stylesheet].each do |type|
33
+ src_path = AuthSlice.dir_for(type)
34
+ dst_path = AuthSlice.app_dir_for(type)
35
+ Dir[src_path / '**/*'].each do |file|
36
+ relative_path = file.relative_path_from(src_path)
37
+ puts "- installing :#{type} #{relative_path}"
38
+ mkdir_p(dst_path / File.dirname(relative_path))
39
+ copy_entry(file, dst_path / relative_path, false, false, true)
40
+ end
41
+ end
42
+ end
43
+
44
+ desc "Migrate the database"
45
+ task :migrate do
46
+ AuthSlice::User.create_db_table
47
+ end
48
+
49
+ desc "Run slice specs within the host application context"
50
+ task :spec => [ "spec:explain", "spec:default" ]
51
+
52
+ namespace :spec do
53
+
54
+ slice_root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
55
+
56
+ task :explain do
57
+ puts "\nNote: By running AuthSlice specs inside the application context any\n" +
58
+ "overrides could break existing specs. This isn't always a problem,\n" +
59
+ "especially in the case of views. Use these spec tasks to check how\n" +
60
+ "well your application conforms to the original slice implementation."
61
+ end
62
+
63
+ Spec::Rake::SpecTask.new('default') do |t|
64
+ t.spec_opts = ["--format", "specdoc", "--colour"]
65
+ t.spec_files = Dir["#{slice_root}/spec/**/*_spec.rb"].sort
66
+ end
67
+
68
+ desc "Run all model specs, run a spec for a specific Model with MODEL=MyModel"
69
+ Spec::Rake::SpecTask.new('model') do |t|
70
+ t.spec_opts = ["--format", "specdoc", "--colour"]
71
+ if(ENV['MODEL'])
72
+ t.spec_files = Dir["#{slice_root}/spec/models/**/#{ENV['MODEL']}_spec.rb"].sort
73
+ else
74
+ t.spec_files = Dir["#{slice_root}/spec/models/**/*_spec.rb"].sort
75
+ end
76
+ end
77
+
78
+ desc "Run all controller specs, run a spec for a specific Controller with CONTROLLER=MyController"
79
+ Spec::Rake::SpecTask.new('controller') do |t|
80
+ t.spec_opts = ["--format", "specdoc", "--colour"]
81
+ if(ENV['CONTROLLER'])
82
+ t.spec_files = Dir["#{slice_root}/spec/controllers/**/#{ENV['CONTROLLER']}_spec.rb"].sort
83
+ else
84
+ t.spec_files = Dir["#{slice_root}/spec/controllers/**/*_spec.rb"].sort
85
+ end
86
+ end
87
+
88
+ desc "Run all view specs, run specs for a specific controller (and view) with CONTROLLER=MyController (VIEW=MyView)"
89
+ Spec::Rake::SpecTask.new('view') do |t|
90
+ t.spec_opts = ["--format", "specdoc", "--colour"]
91
+ if(ENV['CONTROLLER'] and ENV['VIEW'])
92
+ t.spec_files = Dir["#{slice_root}/spec/views/**/#{ENV['CONTROLLER']}/#{ENV['VIEW']}*_spec.rb"].sort
93
+ elsif(ENV['CONTROLLER'])
94
+ t.spec_files = Dir["#{slice_root}/spec/views/**/#{ENV['CONTROLLER']}/*_spec.rb"].sort
95
+ else
96
+ t.spec_files = Dir["#{slice_root}/spec/views/**/*_spec.rb"].sort
97
+ end
98
+ end
99
+
100
+ desc "Run all specs and output the result in html"
101
+ Spec::Rake::SpecTask.new('html') do |t|
102
+ t.spec_opts = ["--format", "html"]
103
+ t.libs = ['lib', 'server/lib' ]
104
+ t.spec_files = Dir["#{slice_root}/spec/**/*_spec.rb"].sort
105
+ end
106
+
107
+ end
108
+
109
+ end
110
+ end
@@ -0,0 +1,6 @@
1
+ module AuthSlice
2
+ def self.use_adapter(adapter)
3
+ adapter_impl = Object.full_const_get("AuthSlice::Adapter::#{adapter.to_s.to_const_string}")
4
+ adapter_impl.create_user_model
5
+ end
6
+ end