auth-slice 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.
@@ -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