├── .gitignore ├── Gemfile ├── History.txt ├── Manifest ├── Rakefile ├── TODO ├── UNLICENSE ├── example ├── database.yml ├── dm_extend_app.rb ├── dm_sinbook.rb ├── extend_views │ ├── edit.haml │ ├── index.haml │ ├── login.haml │ ├── show.haml │ └── signup.haml ├── mm_app.rb ├── tc_app.rb └── tc_sinbook.rb ├── lib ├── models │ ├── abstract_user.rb │ ├── activerecord_user.rb │ ├── ar_adapter.rb │ ├── datamapper_user.rb │ ├── dm_adapter.rb │ ├── mm_adapter.rb │ ├── mongoid_adapter.rb │ ├── mongoid_user.rb │ ├── mongomapper_user.rb │ ├── rufus_tokyo_user.rb │ ├── sequel_adapter.rb │ ├── sequel_user.rb │ └── tc_adapter.rb ├── sinatra-authentication.rb ├── sinatra-authentication │ └── models.rb └── views │ ├── edit.haml │ ├── index.haml │ ├── login.haml │ ├── show.haml │ └── signup.haml ├── readme.markdown ├── sinatra-authentication-0.3.2.gem ├── sinatra-authentication-0.4.2.gem ├── sinatra-authentication.gemspec ├── spec ├── run_all_specs.rb └── unit │ ├── ar_model_spec.rb │ ├── dm_model_spec.rb │ ├── mm_model_spec.rb │ ├── mongoid_model_spec.rb │ ├── sequel_model_spec.rb │ ├── tc_model_spec.rb │ └── user_specs.rb └── test ├── activerecord_test.rb ├── datamapper_test.rb ├── lib ├── ar_app.rb ├── dm_app.rb ├── dm_extend_app.rb ├── dm_sinbook.rb ├── extend_views │ ├── edit.haml │ ├── index.haml │ ├── login.haml │ ├── show.haml │ └── signup.haml ├── helper.rb ├── mm_app.rb ├── mongoid_app.rb ├── sequel_app.rb ├── tc_app.rb └── tc_sinbook.rb ├── mongoid_test.rb ├── mongomapper_test.rb ├── route_tests.rb ├── rufus_tokyo_test.rb └── sequel_test.rb /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/ 2 | *.swp 3 | *.db 4 | *.tct 5 | .bundle 6 | Gemfile.lock 7 | vendor/bundle 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gemspec # include gems from gemspec 3 | 4 | group :test do 5 | gem 'rake' 6 | gem 'rspec' 7 | end 8 | 9 | ############################################## 10 | # Other Dependancies 11 | # - Uncomment as needed. 12 | ############################################## 13 | # 14 | # HAML 15 | # gem 'haml' 16 | # 17 | # Mongoid 18 | # gem 'mongoid' 19 | # 20 | # MongoMapper 21 | # gem 'mongo_mapper' 22 | # 23 | # Active Record 24 | # gem 'activerecord' 25 | # 26 | # SQLite3 27 | # gem 'sqlite3' 28 | # 29 | # Sequel 30 | # gem 'sequel' 31 | # 32 | # Rufus Tokyo 33 | # gem 'rufus-tokyo' 34 | -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | == 0.0.1 2009-04-06 2 | 3 | * 1 major enhancement: 4 | * Initial release 5 | -------------------------------------------------------------------------------- /Manifest: -------------------------------------------------------------------------------- 1 | History.txt 2 | Manifest 3 | Rakefile 4 | TODO 5 | lib/models/abstract_user.rb 6 | lib/models/datamapper_user.rb 7 | lib/models/dm_adapter.rb 8 | lib/models/mongomapper_user.rb 9 | lib/models/mm_adapter.rb 10 | lib/models/rufus_tokyo_user.rb 11 | lib/models/tc_adapter.rb 12 | lib/sinatra-authentication.rb 13 | lib/views/edit.haml 14 | lib/views/index.haml 15 | lib/views/login.haml 16 | lib/views/show.haml 17 | lib/views/signup.haml 18 | readme.markdown 19 | test/datamapper_test.rb 20 | test/lib/dm_app.rb 21 | test/lib/helper.rb 22 | test/lib/tc_app.rb 23 | test/lib/test.db 24 | test/lib/users.tct 25 | test/route_tests.rb 26 | test/rufus_tokyo_test.rb 27 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | #require 'spec/rake/spectask' 4 | 5 | begin 6 | require 'jeweler' 7 | 8 | Jeweler::Tasks.new do |gemspec| 9 | gemspec.name = 'sinatra-authentication' 10 | gemspec.version = '0.4.1' 11 | gemspec.description = "Simple authentication plugin for sinatra." 12 | gemspec.summary = "Simple authentication plugin for sinatra." 13 | gemspec.homepage = "http://github.com/maxjustus/sinatra-authentication" 14 | gemspec.author = "Max Justus Spransy" 15 | gemspec.email = "maxjustus@gmail.com" 16 | gemspec.add_dependency "sinatra" 17 | gemspec.add_dependency "dm-core" 18 | gemspec.add_dependency "dm-migrations" 19 | gemspec.add_dependency "dm-validations" 20 | gemspec.add_dependency "dm-timestamps" 21 | gemspec.add_dependency "rufus-tokyo" 22 | gemspec.add_dependency "sinbook" 23 | gemspec.add_dependency "rack-flash3" 24 | end 25 | Jeweler::GemcutterTasks.new 26 | rescue LoadError 27 | puts "Jeweler (or a dependency) not available. Install it first!" 28 | end 29 | 30 | Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext } 31 | 32 | require 'rake/testtask' 33 | 34 | Rake::TestTask.new do |t| 35 | t.libs << "test" 36 | t.test_files = FileList['test/activerecord_test.rb'] 37 | t.verbose = true 38 | end 39 | 40 | #desc 'Run all specs' 41 | #Spec::Rake::SpecTask.new('specs') do |t| 42 | # t.spec_files = FileList['spec/**/*.rb'] 43 | #end 44 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | TODO: 2 | ensure all calls to new in adaptors are equivelant to "find or create by" 3 | - this is done in the Tc adaptor, and essentially works in Dm adaptor 4 | because I validate the uniqueness of email 5 | implement some way to allow for the creation of users with different 6 | permission levels in an untamperable manner. Perhaps with some secret key 7 | that must be sent in the form 8 | - look at other permissions systems for some feature ideas 9 | - add a config method that you pass a hash for configuring it's behavior 10 | - secret signup urls 11 | - account activation through email 12 | 13 | - ?implement a session store which isn't cookie based 14 | - turn on sessions unless they're already on 15 | - randomize the session key on every installation? 16 | - clean up adapters 17 | - write simple attribute declaration method for TcUser 18 | - condense the adapters down to the simplest solution that could possibly work 19 | - right now it's like I have two seperate goals, both which are important 20 | one is to write a simple ORM for rufus tokyo, the other is to create a simple adapter 21 | for different database backends. I think it would be better if I made the datamapper adapter more abstract 22 | and the api simpler, and then changed the way the controllers and views work to interface with the more abstract and simpler adapter. 23 | maybe make the adapter class called UserAdapter, and then TkUser and DmUser become User. All my controller method calls go to UserAdapter 24 | but then for people wanting to talk to the model, they just use User, since they aren't dealing with multiple backends and thus don't need 25 | a creepy adapter. 26 | 27 | - make site admin work the same for dm and tc, because I like how permission == -2 is site admin, then you could set a user as site admin instead of it being limited to the first user created 28 | and they wouldn't be deletable. 29 | or maybe I just make a heirarchy for that so users with lower permissions can't delete users with higher. 30 | just remember, this is supposed to be a simple authentication solution 31 | 32 | 33 | - for the User adapter 34 | - add pagination to all 35 | - serious cleanup of rufus_tokyo_user.rb 36 | - add virtual attribute declaration 37 | - add validations 38 | - remove the object syntax method_missing? and stick to hash accessors? 39 | - or rather then use method missing, dynamically create class methods based in the contents of the hash? 40 | - or create a validator tool for hashes. hash.valid? 41 | - change User to AbstractUser and DmUser and TcUser to User 42 | 43 | - add error messages for failed logins and stuff 44 | 45 | - PROBLEM the way I have method missing working right now, it doesn't behave the same way I've documented, since I use it for attributes 46 | - throw configuration errors on startup 47 | - investigate why sinatra_auth doesn't seem to work unless it's the last thing required.. 48 | 49 | - add facebook connect 50 | - using facebooker and frankie 51 | - using the same method as datamapper vs tokyo in that the functionality is only included if the libraries are required 52 | before sinatra_auth is. 53 | - when a user signs in using facebook and doesn't have an email specified, an email field is included in the edit form. 54 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /example/database.yml: -------------------------------------------------------------------------------- 1 | db: 2 | adapter: sqlite3 3 | database: test.db 4 | -------------------------------------------------------------------------------- /example/dm_extend_app.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'haml' 4 | require 'dm-core' 5 | require 'dm-migrations' 6 | require 'rack-flash' 7 | require 'sinatra-authentication' 8 | 9 | class DmUser 10 | property :name, String 11 | end 12 | 13 | DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/test.db") 14 | DataMapper.auto_migrate! 15 | 16 | set :sinatra_authentication_view_path, Pathname(__FILE__).dirname.expand_path + "extend_views/" 17 | use Rack::Session::Cookie, :secret => "heyhihello" 18 | use Rack::Flash 19 | 20 | set :environment, 'development' 21 | set :public, 'public' 22 | set :views, 'views' 23 | 24 | get '/' do 25 | haml "= render_login_logout", :layout => :layout 26 | end 27 | -------------------------------------------------------------------------------- /example/dm_sinbook.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'haml' 4 | require 'sinbook' 5 | require 'dm-core' 6 | require 'dm-migrations' 7 | require 'sinatra-authentication' 8 | 9 | facebook do 10 | api_key 'aa2db1b96cb7b57f0c5b1d4d3d8f0a22' 11 | secret '21d94ee63969ae3b3f833689838ca00f' 12 | app_id 48652736613 13 | url 'peoplewithjetpacks.com:4568/' 14 | callback 'peoplewithjetpacks.com:4568/' 15 | end 16 | 17 | set :port, 4568 18 | 19 | DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/test.db") 20 | DataMapper.auto_migrate! 21 | 22 | use Rack::Session::Cookie, :secret => "heyhihello" 23 | 24 | set :environment, 'development' 25 | set :public, 'public' 26 | set :views, 'views' 27 | 28 | get '/' do 29 | haml :main 30 | end 31 | 32 | get '/test' do 33 | login_required 34 | 'hihihi' 35 | end 36 | 37 | __END__ 38 | 39 | @@ layout 40 | %html{:xmlns=>"http://www.w3.org/1999/xhtml", :'xmlns:fb'=>"http://www.facebook.com/2008/fbml"} 41 | %head 42 | %title Welcome to my Facebook Connect website! 43 | %script{:type => 'text/javascript', :src => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php/en_US'} 44 | %body 45 | = render_login_logout 46 | = yield 47 | :javascript 48 | FB.init("#{fb.api_key}", "/receiver") 49 | 50 | @@ main 51 | - if fb[:user] 52 | Hi, 53 | %fb:profile-pic{:uid => fb[:user]} 54 | %fb:name{:uid => fb[:user], :useyou => 'false', :firstnameonly => 'true'} 55 | ! 56 | 57 | -------------------------------------------------------------------------------- /example/extend_views/edit.haml: -------------------------------------------------------------------------------- 1 | #sinatra_authentication 2 | #sinatra_authentication_flash= flash[:notice] 3 | %h1 4 | Edit 5 | - if @user.id == current_user.id 6 | account 7 | - else 8 | - if @user.email 9 | = @user.email 10 | - elsif @user.fb_uid 11 | 12 | - else 13 | account 14 | %form{:action => "/users/#{@user.id}/edit", :method => "post"} 15 | .field 16 | .label 17 | %label{:for => "user_email"} Email 18 | %input{ :id => "user_email", :name => "user[email]", :size => 30, :type => "text", :value => @user.email } 19 | .field 20 | .label 21 | %label{:for => "user_password"} New password 22 | %input{ :id => "user_password", :name => "user[password]", :size => 30, :type => "password" } 23 | .field 24 | .label 25 | %label{:for => "user_password_confirmation"} Confirm 26 | %input{ :id => "user_password_confirmation", :name => "user[password_confirmation]", :size => 30, :type => "password" } 27 | -# don't render permission field if admin and editing yourself so you don't shoot yourself in the foot 28 | - if current_user.admin? && current_user.id != @user.id 29 | .field 30 | .label 31 | %label{:for => 'permission_level'} Permission level 32 | %select{ :id => "permission_level", :name => "user[permission_level]" } 33 | %option{:value => -1, :selected => @user.admin?} 34 | Admin 35 | %option{:value => 1, :selected => @user.permission_level == 1} 36 | Authenticated user 37 | .buttons 38 | %input{ :value => "Update", :type => "submit" } 39 | - if Sinatra.const_defined?('FacebookObject') 40 | - unless @user.fb_uid 41 | | 42 | = render_facebook_connect_link('Link account with Facebook') 43 | -------------------------------------------------------------------------------- /example/extend_views/index.haml: -------------------------------------------------------------------------------- 1 | #sinatra_authentication 2 | %h1.page_title Users 3 | %table 4 | %tr 5 | %th 6 | - if current_user.admin? 7 | %th permission level 8 | - @users.each do |user| 9 | %tr 10 | %td 11 | - if user.email 12 | = user.email 13 | - elsif user.fb_uid 14 | 15 | - else 16 | "user #{user.id}" 17 | - if current_user.admin? 18 | %td= user.permission_level 19 | %td 20 | = user.name 21 | %td 22 | %a{:href => "/users/#{user.id}"} show 23 | - if current_user.admin? 24 | %td 25 | %a{:href => "/users/#{user.id}/edit"} edit 26 | %td 27 | -# this doesn't work for tk 28 | - if !user.site_admin? 29 | %a{:href => "/users/#{user.id}/delete", :onclick => "return confirm('you sure?')"} delete 30 | - else 31 | site admin 32 | -------------------------------------------------------------------------------- /example/extend_views/login.haml: -------------------------------------------------------------------------------- 1 | #sinatra_authentication 2 | #sinatra_authentication_flash= flash[:notice] 3 | %h1.page_title Login 4 | %form{:action => "/login", :method => "post"} 5 | .field 6 | .label 7 | %label{:for => "user_email'"} Email 8 | %input{:id => "user_email", :name => "email", :size => 30, :type => "text"} 9 | .field 10 | .label 11 | %label{:for => "user_password"} Password 12 | %input{:id => "user_password", :name => "password", :size => 30, :type => "password"} 13 | .buttons 14 | %input{:value => "login", :type => "submit"} 15 | %a{:href => "/signup", :class => 'sinatra_authentication_link'} 16 | Signup 17 | - if Sinatra.const_defined?('FacebookObject') 18 | .third_party_signup 19 | %h3.section_title One click login: 20 | .login_link.facebook_login 21 | = render_facebook_connect_link('Login using facebook', :size => 'large') 22 | -------------------------------------------------------------------------------- /example/extend_views/show.haml: -------------------------------------------------------------------------------- 1 | #sinatra_authentication 2 | %h1.page_title 3 | - if @user.email 4 | = @user.email 5 | - elsif @user.fb_uid 6 | 7 | - if current_user.admin? 8 | %h2 permission level 9 | = @user.permission_level 10 | -------------------------------------------------------------------------------- /example/extend_views/signup.haml: -------------------------------------------------------------------------------- 1 | %h1 This view is overridden 2 | #sinatra_authentication 3 | #sinatra_authentication_flash= flash[:notice] 4 | %h1.page_title Signup 5 | %form{:action => "/signup", :method => "post"} 6 | .field 7 | .label 8 | %label{:for => "user_email"} Email 9 | %input{ :id => "user_email", :name => "user[email]", :size => 30, :type => "text" } 10 | .field 11 | .label 12 | %label{:for => "user_password"} Password 13 | %input{ :id => "user_password", :name => "user[password]", :size => 30, :type => "password" } 14 | .field 15 | .label 16 | %label{:for => "user_name"} Name 17 | %input{ :id => "user_name", :name => "user[name]", :size => 30, :type => "text" } 18 | .field 19 | .label 20 | %label{:for => "user_password_confirmation"} Confirm Password 21 | %input{ :id => "user_password_confirmation", :name => "user[password_confirmation]", :size => 30, :type => "password" } 22 | .buttons 23 | %input{ :value => "Create account", :type => "submit" } 24 | %a{:href => "/login", :class => 'sinatra_authentication_link'} 25 | Login 26 | - if Sinatra.const_defined?('FacebookObject') 27 | .third_party_signup 28 | %h3.section_title One click signup: 29 | .login_link.facebook_login 30 | = render_facebook_connect_link('Signup using facebook', :size => 'large') 31 | -------------------------------------------------------------------------------- /example/mm_app.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra/base' 3 | require 'haml' 4 | require 'mongo_mapper' 5 | require 'sinatra-authentication' 6 | 7 | logger = Logger.new($stdout) 8 | MongoMapper.connection = Mongo::Connection.new('db.mongohq.com', 27017, :logger => logger) 9 | MongoMapper.database = "fdbk" 10 | MongoMapper.database.authenticate(ENV['mongohq_user'], ENV['mongohq_pass']) 11 | 12 | class TestApp < Sinatra::Base 13 | use Rack::Session::Cookie, :secret => "heyhihello" 14 | 15 | set :environment, 'development' 16 | set :public, 'public' 17 | set :views, 'views' 18 | 19 | get '/' do 20 | haml "= render_login_logout", :layout => :layout 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /example/tc_app.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'haml' 4 | require 'rufus/tokyo' 5 | require 'sinatra-authentication' 6 | 7 | use Rack::Session::Cookie, :secret => "heyhihello" 8 | TcUserTable.cabinet_path = File.dirname(__FILE__) 9 | 10 | set :environment, 'development' 11 | set :public, 'public' 12 | set :views, 'views' 13 | 14 | get '/' do 15 | haml "= render_login_logout", :layout => :layout 16 | end 17 | -------------------------------------------------------------------------------- /example/tc_sinbook.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'haml' 3 | require 'sinbook' 4 | require 'rufus/tokyo' 5 | require 'sinatra' 6 | require 'sinatra-authentication' 7 | 8 | use Rack::Session::Cookie, :secret => "heyhihello" 9 | TcUserTable.cabinet_path = File.dirname(__FILE__) 10 | 11 | facebook do 12 | api_key 'aa2db1b96cb7b57f0c5b1d4d3d8f0a22' 13 | secret '21d94ee63969ae3b3f833689838ca00f' 14 | app_id 48652736613 15 | url 'peoplewithjetpacks.com:4568/' 16 | callback 'peoplewithjetpacks.com:4568/' 17 | end 18 | 19 | set :port, 4568 20 | 21 | get '/' do 22 | haml :main 23 | end 24 | 25 | get '/test' do 26 | login_required 27 | 'hihihi' 28 | end 29 | 30 | __END__ 31 | 32 | @@ layout 33 | %html{:xmlns=>"http://www.w3.org/1999/xhtml", :'xmlns:fb'=>"http://www.facebook.com/2008/fbml"} 34 | %head 35 | %title Welcome to my Facebook Connect website! 36 | %script{:type => 'text/javascript', :src => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php/en_US'} 37 | %script{:type => 'text/javascript', :src => 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js'} 38 | :javascript 39 | $(document).ready(function(){ 40 | /* test facebook crap works with ajax */ 41 | $('.sinatra-authentication-login').click(function(){ 42 | $.get($(this).attr('href'), {}, function(data){ 43 | $('#test_box').html(data); 44 | }); 45 | return false; 46 | }); 47 | }); 48 | %body 49 | = render_login_logout 50 | = yield 51 | :javascript 52 | FB.init("#{fb.api_key}", "/receiver") 53 | #test_box 54 | 55 | @@ main 56 | - if fb[:user] 57 | Hi, 58 | %fb:profile-pic{:uid => fb[:user]} 59 | %fb:name{:uid => fb[:user], :useyou => 'false', :firstnameonly => 'true'} 60 | ! 61 | %br/ 62 | 63 | -------------------------------------------------------------------------------- /lib/models/abstract_user.rb: -------------------------------------------------------------------------------- 1 | current_path = File.expand_path("..", __FILE__) 2 | if Object.const_defined?("DataMapper") 3 | #require 'dm-core' 4 | require 'dm-timestamps' 5 | require 'dm-validations' 6 | require File.join(current_path, 'datamapper_user') 7 | require File.join(current_path, 'dm_adapter') 8 | elsif Object.const_defined?("Rufus") && Rufus.const_defined?("Tokyo") 9 | require File.join(current_path, 'rufus_tokyo_user') 10 | require File.join(current_path, 'tc_adapter') 11 | elsif Object.const_defined?("MongoMapper") 12 | require File.join(current_path, 'mongomapper_user') 13 | require File.join(current_path, 'mm_adapter') 14 | elsif Object.const_defined?("Sequel") 15 | require File.join(current_path, 'sequel_user') 16 | require File.join(current_path, 'sequel_adapter') 17 | elsif Object.const_defined?("Mongoid") 18 | require File.join(current_path, 'mongoid_user') 19 | require File.join(current_path, 'mongoid_adapter') 20 | end 21 | 22 | class User 23 | if Object.const_defined?("DataMapper") 24 | include DmAdapter 25 | elsif Object.const_defined?("Rufus") && Rufus.const_defined?("Tokyo") 26 | include TcAdapter 27 | elsif Object.const_defined?("MongoMapper") 28 | include MmAdapter 29 | elsif Object.const_defined?("Sequel") 30 | include SequelAdapter 31 | elsif Object.const_defined?("Mongoid") 32 | include MongoidAdapter 33 | elsif Object.const_defined?("ActiveRecord") 34 | include ArAdapter 35 | else 36 | throw "you need to require either 'dm-core', 'mongo_mapper', 'sequel', 'mongoid', or 'rufus-tokyo' for sinatra-authentication to work" 37 | end 38 | 39 | def initialize(interfacing_class_instance) 40 | @instance = interfacing_class_instance 41 | end 42 | 43 | def id 44 | @instance.id 45 | end 46 | 47 | def self.authenticate(email, pass) 48 | current_user = get(:email => email) 49 | return nil if current_user.nil? 50 | return current_user if User.encrypt(pass, current_user.salt) == current_user.hashed_password 51 | nil 52 | end 53 | 54 | def db_instance 55 | @instance 56 | end 57 | 58 | protected 59 | 60 | def self.encrypt(pass, salt) 61 | Digest::SHA1.hexdigest(pass+salt) 62 | end 63 | 64 | def self.random_string(len) 65 | #generate a random password consisting of strings and digits 66 | chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a 67 | newpass = "" 68 | 1.upto(len) { |i| newpass << chars[rand(chars.size-1)] } 69 | return newpass 70 | end 71 | 72 | #def self.page_limit 73 | # 20 74 | #end 75 | 76 | #def self.page_offset(page = 0) 77 | # page.to_i * self.page_limit 78 | #end 79 | end 80 | 81 | class Hash 82 | def stringify 83 | inject({}) do |options, (key, value)| 84 | options[key.to_s] = value.to_s 85 | options 86 | end 87 | end 88 | 89 | def stringify! 90 | each do |key, value| 91 | delete(key) 92 | store(key.to_s, value.to_s) 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /lib/models/activerecord_user.rb: -------------------------------------------------------------------------------- 1 | unless ActiveRecord::Base.connection.table_exists?("ar_users") 2 | class CreateArUsers < ActiveRecord::Migration 3 | def self.up 4 | create_table :ar_users do |t| 5 | t.string :email 6 | t.string :hashed_password 7 | t.string :salt 8 | t.integer :permission_level 9 | t.string :fb_uid 10 | 11 | t.timestamps 12 | end 13 | 14 | add_index :ar_users, :email, :unique => true 15 | end 16 | 17 | def self.down 18 | remove_index :ar_users, :email 19 | drop_table :ar_users 20 | end 21 | end 22 | 23 | CreateArUsers.up 24 | end 25 | 26 | #require 'logger' 27 | #ActiveRecord::Base.logger = Logger.new(STDOUT) 28 | 29 | class ArUser < ActiveRecord::Base 30 | 31 | attr_accessor :password, :password_confirmation 32 | 33 | validates_format_of :email, :with => /(\A(\s*)\Z)|(\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z)/i 34 | validates_uniqueness_of :email 35 | validates_presence_of :password_confirmation, :unless => Proc.new { |t| t.hashed_password } 36 | validates_presence_of :password, :unless => Proc.new { |t| t.hashed_password } 37 | validates_confirmation_of :password 38 | 39 | def password=(pass) 40 | @password = pass 41 | self.salt = User.random_string(10) if !self.salt 42 | self.hashed_password = User.encrypt(@password, self.salt) 43 | end 44 | 45 | def admin? 46 | self.permission_level == -1 || self.id == 1 47 | end 48 | 49 | def site_admin? 50 | self.id == 1 51 | end 52 | 53 | def to_ary 54 | self.attributes.values 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/models/ar_adapter.rb: -------------------------------------------------------------------------------- 1 | module ArAdapter 2 | def self.included(base) 3 | base.extend ClassMethods 4 | base.class_eval { include ArAdapter::InstanceMethods } 5 | end 6 | 7 | module ClassMethods 8 | #pass all args to this 9 | def all 10 | result = ArUser.all(:order => ["created_at DESC"]) 11 | result.collect {|instance| self.new instance} 12 | end 13 | 14 | def get(hash) 15 | if user = ArUser.where(hash).first 16 | self.new user 17 | else 18 | nil 19 | end 20 | end 21 | 22 | def set(attributes) 23 | user = ArUser.new attributes 24 | user.save 25 | self.new user 26 | end 27 | 28 | def set!(attributes) 29 | user = ArUser.new attributes 30 | user.save! 31 | self.new user 32 | end 33 | 34 | def delete(pk) 35 | user = ArUser.first(pk).first 36 | user.destroy 37 | user.destroyed? 38 | end 39 | end 40 | 41 | module InstanceMethods 42 | def valid 43 | @instance.valid? 44 | end 45 | 46 | def errors 47 | @instance.errors.collect do |k,v| 48 | "#{k} #{v}" 49 | end.join(', ') 50 | end 51 | 52 | def update(attributes) 53 | @instance.update_attributes attributes 54 | #self 55 | end 56 | 57 | def saved 58 | @instance.valid? 59 | end 60 | 61 | def method_missing(meth, *args, &block) 62 | #cool I just found out * on an array turns the array into a list of args for a function 63 | @instance.send(meth, *args, &block) 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/models/datamapper_user.rb: -------------------------------------------------------------------------------- 1 | class DmUser 2 | include DataMapper::Resource 3 | 4 | property :id, Serial 5 | property :email, String, :length => (5..40), :unique => true, :format => :email_address 6 | property :hashed_password, String 7 | property :salt, String 8 | #Was DateTime should be DateTime? 9 | property :created_at, Time 10 | property :permission_level, Integer, :default => 1 11 | if Sinatra.const_defined?('FacebookObject') 12 | property :fb_uid, String 13 | end 14 | 15 | attr_accessor :password, :password_confirmation 16 | #protected equievelant? :protected => true doesn't exist in dm 0.10.0 17 | #protected :id, :salt 18 | #doesn't behave correctly, I'm not even sure why I did this. 19 | 20 | validates_presence_of :password_confirmation, :unless => Proc.new { |t| t.hashed_password } 21 | validates_presence_of :password, :unless => Proc.new { |t| t.hashed_password } 22 | validates_confirmation_of :password 23 | 24 | def password=(pass) 25 | @password = pass 26 | self.salt = User.random_string(10) if !self.salt 27 | self.hashed_password = User.encrypt(@password, self.salt) 28 | end 29 | 30 | def admin? 31 | self.permission_level == -1 || self.id == 1 32 | end 33 | 34 | def site_admin? 35 | self.id == 1 36 | end 37 | 38 | protected 39 | 40 | def method_missing(m, *args) 41 | return false 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/models/dm_adapter.rb: -------------------------------------------------------------------------------- 1 | module DmAdapter 2 | def self.included(base) 3 | base.extend ClassMethods 4 | base.class_eval { include DmAdapter::InstanceMethods } 5 | end 6 | 7 | module ClassMethods 8 | #pass all args to this 9 | def all 10 | result = DmUser.all(:order => [:created_at.desc]) 11 | result.collect {|instance| self.new instance} 12 | end 13 | 14 | def get(hash) 15 | if user = DmUser.first(hash) 16 | self.new user 17 | else 18 | nil 19 | end 20 | end 21 | 22 | def set(attributes) 23 | user = DmUser.new attributes 24 | user.save 25 | self.new user 26 | end 27 | 28 | def set!(attributes) 29 | user = DmUser.new attributes 30 | user.save! 31 | self.new user 32 | end 33 | 34 | def delete(pk) 35 | user = DmUser.first(:id => pk) 36 | user.destroy 37 | end 38 | end 39 | 40 | module InstanceMethods 41 | def valid 42 | @instance.valid? 43 | end 44 | 45 | def errors 46 | @instance.errors.collect do |k,v| 47 | "#{k} #{v}" 48 | end.join(', ') 49 | end 50 | 51 | def update(attributes) 52 | @instance.update attributes 53 | #self 54 | end 55 | 56 | def method_missing(meth, *args, &block) 57 | #cool I just found out * on an array turns the array into a list of args for a function 58 | @instance.send(meth, *args, &block) 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/models/mm_adapter.rb: -------------------------------------------------------------------------------- 1 | module MmAdapter 2 | def self.included(base) 3 | base.extend ClassMethods 4 | base.class_eval { include MmAdapter::InstanceMethods } 5 | end 6 | 7 | module ClassMethods 8 | def all 9 | result = MmUser.all(:order => 'created_at desc') 10 | result.collect {|instance| self.new instance} 11 | end 12 | 13 | def get(hash) 14 | if user = MmUser.first(hash) 15 | self.new user 16 | else 17 | nil 18 | end 19 | end 20 | 21 | def set(attributes) 22 | #puts attributes.inspect 23 | user = MmUser.new attributes 24 | user.id = nil unless user.save 25 | self.new user 26 | end 27 | 28 | def set!(attributes) 29 | user = MmUser.new attributes 30 | user.save(:validate => false) 31 | self.new user 32 | end 33 | 34 | def delete(pk) 35 | user = MmUser.first(:id => pk) 36 | #returns nil on success. Is this correct? Will it return something else on failure? 37 | user.destroy 38 | user.destroyed? 39 | end 40 | end 41 | 42 | module InstanceMethods 43 | def valid 44 | @instance.valid? 45 | end 46 | 47 | def update(attributes) 48 | @instance.update_attributes attributes 49 | @instance.save 50 | end 51 | 52 | def saved 53 | @instance.valid? 54 | end 55 | 56 | def errors 57 | @instance.errors.full_messages.join(', ') 58 | end 59 | 60 | def method_missing(meth, *args, &block) 61 | #cool I just found out * on an array turns the array into a list of args for a function 62 | @instance.send(meth, *args, &block) 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/models/mongoid_adapter.rb: -------------------------------------------------------------------------------- 1 | module MongoidAdapter 2 | def self.included(base) 3 | base.extend ClassMethods 4 | base.class_eval { include MongoidAdapter::InstanceMethods } 5 | end 6 | 7 | module ClassMethods 8 | def all 9 | result = MongoidUser.criteria.order_by([[:created_at, :desc]]) 10 | result.collect {|instance| self.new instance } 11 | end 12 | 13 | def get(hash) 14 | if user = MongoidUser.where(hash).first 15 | self.new user 16 | else 17 | nil 18 | end 19 | end 20 | 21 | def set(attributes) 22 | #puts attributes.inspect 23 | user = MongoidUser.new attributes 24 | #puts user.inspect 25 | #puts user.to_json 26 | user.save 27 | self.new user 28 | end 29 | 30 | def set!(attributes) 31 | user = MongoidUser.new attributes 32 | user.save(:validate => false) 33 | self.new user 34 | end 35 | 36 | def delete(pk) 37 | user = MongoidUser.find(pk) 38 | #returns nil on success. Is this correct? Will it return something else on failure? 39 | user.destroy 40 | user.destroyed? 41 | end 42 | end 43 | 44 | module InstanceMethods 45 | def valid 46 | @instance.valid? 47 | end 48 | 49 | def update(attributes) 50 | @instance.update_attributes(attributes) 51 | @instance.save 52 | end 53 | 54 | def saved 55 | @instance.valid? 56 | end 57 | 58 | def errors 59 | @instance.errors.full_messages.join(', ') 60 | end 61 | 62 | def method_missing(meth, *args, &block) 63 | #cool I just found out * on an array turns the array into a list of args for a function 64 | @instance.send(meth, *args, &block) 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/models/mongoid_user.rb: -------------------------------------------------------------------------------- 1 | class MongoidUser 2 | include Mongoid::Document 3 | include Mongoid::Timestamps 4 | field :email 5 | field :hashed_password 6 | field :salt 7 | field :permission_level, :type => Integer, :default => 1 8 | if Sinatra.const_defined?('FacebookObject') 9 | field :fb_uid 10 | end 11 | 12 | # Validations 13 | validates_uniqueness_of :email 14 | validates_format_of :email, :with => /(\A(\s*)\Z)|(\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z)/i 15 | validates_presence_of :password 16 | validates_confirmation_of :password 17 | 18 | #attr_protected :_id, :salt 19 | 20 | attr_accessor :password, :password_confirmation 21 | 22 | def password=(pass) 23 | @password = pass 24 | self.salt = User.random_string(10) if !self.salt 25 | self.hashed_password = User.encrypt(@password, self.salt) 26 | end 27 | 28 | def admin? 29 | self.permission_level == -1 || self.id == 1 30 | end 31 | 32 | def site_admin? 33 | self.id == 1 34 | end 35 | 36 | protected 37 | 38 | def method_missing(m, *args) 39 | return false 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/models/mongomapper_user.rb: -------------------------------------------------------------------------------- 1 | class MmUser 2 | include MongoMapper::Document 3 | 4 | email_regexp = /(\A(\s*)\Z)|(\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z)/i 5 | key :email, String, :unique => true, :format => email_regexp 6 | key :hashed_password, String 7 | key :salt, String 8 | key :permission_level, Integer, :default => 1 9 | if Sinatra.const_defined?('FacebookObject') 10 | key :fb_uid, String 11 | end 12 | 13 | timestamps! 14 | 15 | attr_accessor :password, :password_confirmation 16 | validates_presence_of :password, :allow_blank => true 17 | validates_confirmation_of :password 18 | 19 | def password=(pass) 20 | @password = pass 21 | self.salt = User.random_string(10) if !self.salt 22 | self.hashed_password = User.encrypt(@password, self.salt) 23 | end 24 | 25 | def admin? 26 | self.permission_level == -1 || self.id == 1 27 | end 28 | 29 | def site_admin? 30 | self.id == 1 31 | end 32 | 33 | protected 34 | 35 | def method_missing(m, *args) 36 | return false 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/models/rufus_tokyo_user.rb: -------------------------------------------------------------------------------- 1 | class TcUser 2 | attr_accessor :errors 3 | #include RufusOrm 4 | 5 | #custom_attribute :salt 6 | #custom_attribute :hashed_password 7 | #custom_attribute :hashed_permission_level 8 | #custom_attribute :created_at 9 | #custom_attribute :created_at_i 10 | 11 | #attribute method? 12 | #if I'm gonna write all this, I might as well create a tinyyyy 13 | #orm, that's more just like a way to define custom attributes for cabinets 14 | #something worth noting though is that even datamapper defines custom 15 | #attributes by allowing the developer to override setter methods. 16 | #and it just calls all the setter methods defined in the model. 17 | #the only trouble with this route is it assumes a predefined schema. 18 | #and thus it knows what setter methods to call. 19 | #I would write a class method that allows you to declare attributes like 20 | #attribute :salt, with an optional block (which gets passed a hash of attributes) 21 | #if a block isn't defined, it looks in the class for a salt=(attributes) function, calls it and marges the 22 | #result into the hash going into the database, like 'attributes.merge{"salt" => result}' 23 | #so my 'set' method or whatever I choose to call it, has to somehow look through each 24 | #declared attribute, call the method associated with it, and merge the result into the hash going 25 | #into the database. 26 | # 27 | #but what if I don't want an attribute passed in to be stored into the database? What if I just want to 28 | #create a virtual attribute, for declaring other attributes? 29 | #I might create a class variable that I store all the attributes in, and the I can get to it from any setter, 30 | #and then after I've called all the setters, I store that class variable into the database. 31 | #or, I do all of this on the instance level, and have a save method. 32 | 33 | def initialize(attributes, errors = []) 34 | @attributes = attributes 35 | if @attributes['created_at'] 36 | @attributes['created_at'] = Time.parse(@attributes['created_at']) 37 | end 38 | 39 | @errors = errors 40 | end 41 | 42 | def self.query(&block) 43 | connection = TcUserTable.new 44 | result_set = connection.query(&block) 45 | output = result_set.collect { |result_hash| TcUser.new(result_hash) } 46 | connection.close 47 | output 48 | end 49 | 50 | def self.get(key) 51 | connection = TcUserTable.new 52 | result = connection[key] 53 | connection.close 54 | if result 55 | self.new(result.merge({:pk => key})) 56 | else 57 | false 58 | end 59 | end 60 | 61 | def self.set(attributes) 62 | #this way of validating is real crap, replace it with Validator maybe 63 | #and maybe replace all this hash merging with setters for the various attributes that update @attributes, and then I can call save to store to the database 64 | #or maybe just write a little method that makes hash merger look a little cleaner 65 | pk = attributes.delete(:pk) if attributes[:pk] 66 | 67 | errors = [] 68 | 69 | email_regexp = /(\A(\s*)\Z)|(\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z)/i 70 | attributes = attributes.stringify 71 | 72 | unless attributes['email'] =~ email_regexp 73 | errors << 'Email is invalid' 74 | end 75 | 76 | if attributes['password'] != attributes.delete('password_confirmation') && attributes['password'] != nil 77 | errors << "Passwords don't match" 78 | end 79 | 80 | if attributes['password'] != nil && attributes['password'].length == 0 81 | errors << "You need to provide a password" 82 | end 83 | 84 | if errors.length == 0 85 | password = attributes.delete('password') 86 | if !attributes['salt'] 87 | salt = User.random_string(10) 88 | attributes.merge!({'salt' => salt}) 89 | attributes.merge!('hashed_password' => User.encrypt(password, salt)) 90 | end 91 | permission_level = attributes['permission_level'] ? attributes['permission_level'] : '1' 92 | attributes.merge!('permission_level' => permission_level) 93 | if attributes['created_at'] 94 | attributes.merge!('created_at_i' => Time.parse(attributes['created_at']).to_i) 95 | else 96 | attributes.merge!('created_at' => Time.now.to_s) 97 | attributes.merge!('created_at_i' => Time.now.to_i.to_s) 98 | end 99 | 100 | existing_user = TcUser.query do |q| 101 | q.add 'email', :streq, attributes['email'] 102 | end[0] 103 | 104 | if existing_user && existing_user[:pk] != pk 105 | errors << "Email is already taken" 106 | return self.new(attributes, errors) 107 | else 108 | connection = TcUserTable.new 109 | pk ||= connection.genuid.to_s 110 | #site admin if their first 111 | attributes.merge!({'permission_level' => '-2'}) if pk == '1' 112 | result = connection[pk] = attributes 113 | #might not need this in newer version of rufus 114 | result.merge!({:pk => pk}) 115 | connection.close 116 | self.new(result, errors) 117 | end 118 | else 119 | self.new(attributes, errors) 120 | end 121 | end 122 | 123 | def self.set!(attributes) 124 | connection = TcUserTable.new 125 | pk = connection.genuid.to_s 126 | result = connection[pk] = attributes 127 | result.merge!({:pk => pk}) 128 | connection.close 129 | self.new(result) 130 | end 131 | 132 | def self.delete(pk) 133 | connection = TcUserTable.new 134 | connection.delete(pk) 135 | connection.close 136 | end 137 | 138 | def update(attributes) 139 | self.errors = [] 140 | new_attributes = @attributes.merge(attributes) 141 | updated_user = TcUser.set(new_attributes) 142 | self.errors = updated_user.errors 143 | updated_user.errors.length < 1 144 | end 145 | 146 | def [](key) 147 | @attributes[key] 148 | end 149 | 150 | #saves to database and returns self 151 | def []=(key, value) 152 | @attributes[key] = value 153 | #change so that it sets the attributes and then you call save to save to the database? 154 | connection = TcUserTable.new 155 | connection[@attributes[:pk]] = @attributes.merge!({key => value}) 156 | connection.close 157 | self 158 | end 159 | 160 | def id 161 | @attributes[:pk] 162 | end 163 | 164 | def admin? 165 | @attributes['permission_level'] == '-1' || site_admin? 166 | end 167 | 168 | def site_admin? 169 | #-2 is the site admin 170 | @attributes['permission_level'] == '-2' 171 | end 172 | 173 | #from hash extension for making hashes like javascript objects 174 | def method_missing(meth,*args) 175 | if /=$/=~(meth=meth.id2name) then 176 | self[meth[0...-1]] = (args.length<2 ? args[0] : args) 177 | elsif @attributes[meth] 178 | @attributes[meth] 179 | else 180 | false 181 | end 182 | end 183 | end 184 | 185 | if Rufus::Tokyo.const_defined?('Table') 186 | class TokyoTableDad < Rufus::Tokyo::Table 187 | end 188 | elsif Rufus::Edo.const_defined?('Table') 189 | class TokyoTableDad < Rufus::Edo::Table 190 | end 191 | else 192 | throw 'wtf?' 193 | end 194 | 195 | class TcUserTable < TokyoTableDad 196 | @@path = false 197 | def initialize 198 | #make this path configurable somehow 199 | raise "you need to define a path for the user cabinet to be stored at, like so: TcUserTable.cabinet_path = 'folder/where/you/wanna/store/your/database'" unless @@path 200 | super(@@path + '/users.tct') 201 | end 202 | 203 | def self.cabinet_path=(path) 204 | @@path = path 205 | end 206 | end 207 | -------------------------------------------------------------------------------- /lib/models/sequel_adapter.rb: -------------------------------------------------------------------------------- 1 | module SequelAdapter 2 | def self.included(base) 3 | base.extend ClassMethods 4 | base.class_eval { include SequelAdapter::InstanceMethods } 5 | end 6 | 7 | module ClassMethods 8 | def all 9 | result = SequelUser.order(:created_at.desc).all 10 | result.collect {|instance| self.new instance} 11 | end 12 | 13 | def get(hash) 14 | if user = SequelUser.first(hash) 15 | self.new user 16 | else 17 | nil 18 | end 19 | end 20 | 21 | def set(attributes) 22 | user = SequelUser.new attributes 23 | if user.valid? 24 | user.save 25 | #false 26 | end 27 | 28 | self.new user 29 | end 30 | 31 | def set!(attributes) 32 | user = SequelUser.new attributes 33 | user.save! 34 | self.new user 35 | end 36 | 37 | def delete(pk) 38 | user = SequelUser.first(:id => pk) 39 | user.destroy 40 | !user.exists? 41 | end 42 | end 43 | 44 | module InstanceMethods 45 | def errors 46 | @instance.errors.full_messages.join(', ') 47 | end 48 | 49 | def valid 50 | @instance.valid? 51 | end 52 | 53 | def update(attributes) 54 | @instance.set attributes 55 | if @instance.valid? 56 | @instance.save 57 | true 58 | else 59 | false 60 | end 61 | end 62 | 63 | def method_missing(meth, *args, &block) 64 | #cool I just found out * on an array turns the array into a list of args for a function 65 | @instance.send(meth, *args, &block) 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/models/sequel_user.rb: -------------------------------------------------------------------------------- 1 | unless DB.table_exists? :sequel_users 2 | DB.create_table :sequel_users do 3 | primary_key :id 4 | String :email, :unique => true 5 | String :hashed_password 6 | String :salt 7 | DateTime :created_at 8 | Integer :permission_level, :default => 1 9 | if Sinatra.const_defined?('FacebookObject') 10 | String :fb_uid 11 | end 12 | 13 | #check{{char_length(email)=>5..40}} 14 | end 15 | end 16 | 17 | class SequelUser < Sequel::Model 18 | attr_writer :password_confirmation 19 | plugin :validation_helpers 20 | plugin :timestamps, :create => :created_at 21 | 22 | def validate 23 | super 24 | email_regexp = /(\A(\s*)\Z)|(\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z)/i 25 | validates_format email_regexp, :email 26 | validates_presence :email 27 | validates_unique :email 28 | validates_presence :password if new? 29 | errors.add :passwords, ' don\'t match' unless @password == @password_confirmation 30 | #validate equality? 31 | end 32 | #TODO validate format of email 33 | 34 | def password=(pass) 35 | @password = pass 36 | self.salt = User.random_string(10) if !self.salt 37 | self.hashed_password = User.encrypt(@password, self.salt) 38 | end 39 | 40 | def admin? 41 | self.permission_level == -1 || self.id == 1 42 | end 43 | 44 | def site_admin? 45 | self.id == 1 46 | end 47 | 48 | protected 49 | 50 | def method_missing(m, *args) 51 | return false 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/models/tc_adapter.rb: -------------------------------------------------------------------------------- 1 | module TcAdapter 2 | def self.included(base) 3 | base.extend ClassMethods 4 | base.class_eval { 5 | include TcAdapter::InstanceMethods 6 | alias :class_id :id 7 | } 8 | end 9 | 10 | module ClassMethods 11 | #TODO add pagination 12 | def all 13 | result = TcUser.query do |q| 14 | q.order_by 'created_at_i', :numdesc 15 | end 16 | 17 | #these will be the same for all adapters, they should be defined in the user class, and these methods should have a different name? 18 | result.collect {|instance| self.new instance } 19 | end 20 | 21 | def get(hash) 22 | if hash[:id] 23 | pk = hash[:id] 24 | if pk.length > 0 25 | result = TcUser.get(pk) 26 | else 27 | nil 28 | end 29 | else 30 | result = TcUser.query do |q| 31 | hash.each do |key, value| 32 | q.add key.to_s, :streq, value.to_s 33 | end 34 | end[0] 35 | end 36 | #elsif hash[:email] 37 | # result = TcUser.query do |q| 38 | # q.add 'email', :streq, hash[:email] 39 | # end[0] 40 | #the zero is because this returns an array but get should return the first result 41 | #end 42 | 43 | if result 44 | self.new result 45 | else 46 | nil 47 | end 48 | end 49 | 50 | def set(attributes) 51 | user = TcUser.query do |q| 52 | q.add 'email', :streq, attributes['email'] 53 | end 54 | 55 | if user == [] #no user 56 | self.new TcUser.set(attributes) 57 | else 58 | if attributes['email'].length == 0 59 | error = 'You need to provide an email address' 60 | else 61 | error = 'That email is already taken' 62 | end 63 | 64 | TcUser.new(attributes, error) 65 | end 66 | end 67 | 68 | def set!(attributes) 69 | self.new TcUser.set!(attributes) 70 | end 71 | 72 | def delete(pk) 73 | #true or false 74 | !!TcUser.delete(pk) 75 | end 76 | end 77 | 78 | module InstanceMethods 79 | def update(attributes) 80 | @instance.update attributes 81 | end 82 | 83 | def errors 84 | @instance.errors.join(', ') 85 | end 86 | 87 | def valid 88 | @instance.errors.length == 0 89 | end 90 | 91 | def method_missing(meth, *args, &block) 92 | #cool I just found out * on an array turn the array into a list of args for a function 93 | @instance.send(meth, *args, &block) 94 | end 95 | 96 | #this was the only thing that didn't get passed on to method_missing because this is a method of object doh 97 | def id 98 | @instance.id 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/sinatra-authentication.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require File.expand_path("../models/abstract_user", __FILE__) 3 | 4 | module Sinatra 5 | module SinatraAuthentication 6 | def self.registered(app) 7 | #INVESTIGATE 8 | #the possibility of sinatra having an array of view_paths to load from 9 | #PROBLEM 10 | #sinatra 9.1.1 doesn't have multiple view capability anywhere 11 | #so to get around I have to do it totally manually by 12 | #loading the view from this path into a string and rendering it 13 | app.set :sinatra_authentication_view_path, File.expand_path('../views/', __FILE__) 14 | unless defined?(settings.template_engine) 15 | app.set :template_engine, :haml 16 | end 17 | 18 | app.get '/users/?' do 19 | login_required 20 | redirect "/" unless current_user.admin? 21 | 22 | @users = User.all 23 | if @users != [] 24 | send settings.template_engine, get_view_as_string("index.#{settings.template_engine}"), :layout => use_layout? 25 | else 26 | redirect '/signup' 27 | end 28 | end 29 | 30 | app.get '/users/:id/?' do 31 | login_required 32 | 33 | if params[:id].to_i != current_user.id and !current_user.admin? 34 | redirect "/" 35 | end 36 | @user = User.get(:id => params[:id]) 37 | send settings.template_engine, get_view_as_string("show.#{settings.template_engine}"), :layout => use_layout? 38 | end 39 | 40 | #convenience for ajax but maybe entirely stupid and unnecesary 41 | app.get '/logged_in' do 42 | if session[:user] 43 | "true" 44 | else 45 | "false" 46 | end 47 | end 48 | 49 | app.get '/login/?' do 50 | if session[:user] 51 | redirect '/' 52 | else 53 | send settings.template_engine, get_view_as_string("login.#{settings.template_engine}"), :layout => use_layout? 54 | end 55 | end 56 | 57 | app.post '/login/?' do 58 | if user = User.authenticate(params[:email], params[:password]) 59 | session[:user] = user.id 60 | 61 | if Rack.const_defined?('Flash') 62 | flash[:notice] = "Login successful." 63 | end 64 | 65 | if session[:return_to] 66 | redirect_url = session[:return_to] 67 | session[:return_to] = false 68 | redirect redirect_url 69 | else 70 | redirect '/' 71 | end 72 | else 73 | if Rack.const_defined?('Flash') 74 | flash[:error] = "The email or password you entered is incorrect." 75 | end 76 | redirect '/login' 77 | end 78 | end 79 | 80 | app.get '/logout/?' do 81 | session[:user] = nil 82 | if Rack.const_defined?('Flash') 83 | flash[:notice] = "Logout successful." 84 | end 85 | return_to = ( session[:return_to] ? session[:return_to] : '/' ) 86 | redirect return_to 87 | end 88 | 89 | app.get '/signup/?' do 90 | if session[:user] 91 | redirect '/' 92 | else 93 | send settings.template_engine, get_view_as_string("signup.#{settings.template_engine}"), :layout => use_layout? 94 | end 95 | end 96 | 97 | app.post '/signup/?' do 98 | @user = User.set(params[:user]) 99 | if @user.valid && @user.id 100 | session[:user] = @user.id 101 | if Rack.const_defined?('Flash') 102 | flash[:notice] = "Account created." 103 | end 104 | redirect '/' 105 | else 106 | if Rack.const_defined?('Flash') 107 | flash[:error] = "There were some problems creating your account: #{@user.errors}." 108 | end 109 | redirect '/signup?' + hash_to_query_string(params['user']) 110 | end 111 | end 112 | 113 | app.get '/users/:id/edit/?' do 114 | login_required 115 | redirect "/users" unless current_user.admin? || current_user.id.to_s == params[:id] 116 | @user = User.get(:id => params[:id]) 117 | send settings.template_engine, get_view_as_string("edit.#{settings.template_engine}"), :layout => use_layout? 118 | end 119 | 120 | app.post '/users/:id/edit/?' do 121 | login_required 122 | redirect "/users" unless current_user.admin? || current_user.id.to_s == params[:id] 123 | 124 | user = User.get(:id => params[:id]) 125 | user_attributes = params[:user] 126 | if params[:user][:password] == "" 127 | user_attributes.delete("password") 128 | user_attributes.delete("password_confirmation") 129 | end 130 | 131 | if user.update(user_attributes) 132 | if Rack.const_defined?('Flash') 133 | flash[:notice] = 'Account updated.' 134 | end 135 | redirect '/' 136 | else 137 | if Rack.const_defined?('Flash') 138 | flash[:error] = "Whoops, looks like there were some problems with your updates: #{user.errors}." 139 | end 140 | redirect "/users/#{user.id}/edit?" + hash_to_query_string(user_attributes) 141 | end 142 | end 143 | 144 | app.get '/users/:id/delete/?' do 145 | login_required 146 | redirect "/users" unless current_user.admin? || current_user.id.to_s == params[:id] 147 | 148 | if User.delete(params[:id]) 149 | if Rack.const_defined?('Flash') 150 | flash[:notice] = "User deleted." 151 | end 152 | else 153 | if Rack.const_defined?('Flash') 154 | flash[:error] = "Deletion failed." 155 | end 156 | end 157 | redirect '/' 158 | end 159 | 160 | 161 | if Sinatra.const_defined?('FacebookObject') 162 | app.get '/connect/?' do 163 | if fb[:user] 164 | if current_user.class != GuestUser 165 | user = current_user 166 | else 167 | user = User.get(:fb_uid => fb[:user]) 168 | end 169 | 170 | if user 171 | if !user.fb_uid || user.fb_uid != fb[:user] 172 | user.update :fb_uid => fb[:user] 173 | end 174 | session[:user] = user.id 175 | else 176 | user = User.set!(:fb_uid => fb[:user]) 177 | session[:user] = user.id 178 | end 179 | end 180 | redirect '/' 181 | end 182 | 183 | app.get '/receiver' do 184 | %[ 185 | 186 | 187 | 188 | 189 | ] 190 | end 191 | end 192 | end 193 | end 194 | 195 | module Helpers 196 | def hash_to_query_string(hash) 197 | hash.collect {|k,v| "#{k}=#{v}"}.join('&') 198 | end 199 | 200 | def login_required 201 | #not as efficient as checking the session. but this inits the fb_user if they are logged in 202 | user = current_user 203 | if user && user.class != GuestUser 204 | return true 205 | else 206 | session[:return_to] = request.fullpath 207 | redirect '/login' 208 | return false 209 | end 210 | end 211 | 212 | def current_user 213 | if session[:user] 214 | User.get(:id => session[:user]) 215 | else 216 | GuestUser.new 217 | end 218 | end 219 | 220 | def logged_in? 221 | !!session[:user] 222 | end 223 | 224 | def use_layout? 225 | !request.xhr? 226 | end 227 | 228 | #BECAUSE sinatra 9.1.1 can't load views from different paths properly 229 | def get_view_as_string(filename) 230 | view = File.join(settings.sinatra_authentication_view_path, filename) 231 | data = "" 232 | f = File.open(view, "r") 233 | f.each_line do |line| 234 | data += line 235 | end 236 | return data 237 | end 238 | 239 | def render_login_logout(html_attributes = {:class => ""}) 240 | css_classes = html_attributes.delete(:class) 241 | parameters = '' 242 | html_attributes.each_pair do |attribute, value| 243 | parameters += "#{attribute}=\"#{value}\" " 244 | end 245 | 246 | result = "
" 247 | if logged_in? 248 | logout_parameters = html_attributes 249 | # a tad janky? 250 | logout_parameters.delete(:rel) 251 | result += "Edit account " 252 | if Sinatra.const_defined?('FacebookObject') 253 | if fb[:user] 254 | result += "Logout" 255 | else 256 | result += "Logout" 257 | end 258 | else 259 | result += "Logout" 260 | end 261 | else 262 | result += " " 263 | result += "" 264 | end 265 | 266 | result += "
" 267 | end 268 | 269 | if Sinatra.const_defined?('FacebookObject') 270 | def render_facebook_connect_link(text = 'Login using facebook', options = {:size => 'small'}) 271 | if options[:size] == 'small' 272 | size = 'Small' 273 | elsif options[:size] == 'medium' 274 | size = 'Medium' 275 | elsif options[:size] == 'large' 276 | size = 'Large' 277 | elsif options[:size] == 'xlarge' 278 | size = 'BigPun' 279 | else 280 | size = 'Small' 281 | end 282 | 283 | %[] 288 | end 289 | end 290 | end 291 | 292 | register SinatraAuthentication 293 | end 294 | 295 | class GuestUser 296 | def guest? 297 | true 298 | end 299 | 300 | def permission_level 301 | 0 302 | end 303 | 304 | # current_user.admin? returns false. current_user.has_a_baby? returns false. 305 | # (which is a bit of an assumption I suppose) 306 | def method_missing(m, *args) 307 | return false 308 | end 309 | end 310 | -------------------------------------------------------------------------------- /lib/sinatra-authentication/models.rb: -------------------------------------------------------------------------------- 1 | # Allows you to use the models from cron scripts etc 2 | if !Object.const_defined?("Sinatra") 3 | class Sinatra;end # To please the testing for Sinatra.const_defined?('FacebookObject') 4 | require File.expand_path(File.join(__FILE__,'../../models/') + 'abstract_user.rb') 5 | end -------------------------------------------------------------------------------- /lib/views/edit.haml: -------------------------------------------------------------------------------- 1 | #sinatra_authentication 2 | - if Rack.const_defined?('Flash') && flash[:notice] 3 | #sinatra_authentication_flash= flash[:notice] 4 | - if Rack.const_defined?('Flash') && flash[:error] 5 | #sinatra_authentication_flash= flash[:error] 6 | %h1 7 | Edit 8 | - if @user.id == current_user.id 9 | account 10 | - else 11 | - if @user.email 12 | = @user.email 13 | - elsif @user.fb_uid 14 | 15 | - else 16 | account 17 | %form{:action => "/users/#{@user.id}/edit", :method => "post"} 18 | .field 19 | .label 20 | %label{:for => "user_email"} Email 21 | %input{ :id => "user_email", :name => "user[email]", :size => 30, :type => "text", :value => @user.email } 22 | .field 23 | .label 24 | %label{:for => "user_password"} New password 25 | %input{ :id => "user_password", :name => "user[password]", :size => 30, :type => "password" } 26 | .field 27 | .label 28 | %label{:for => "user_password_confirmation"} Confirm 29 | %input{ :id => "user_password_confirmation", :name => "user[password_confirmation]", :size => 30, :type => "password" } 30 | -# don't render permission field if admin and editing yourself so you don't shoot yourself in the foot 31 | - if current_user.admin? && current_user.id != @user.id 32 | .field 33 | .label 34 | %label{:for => 'permission_level'} Permission level 35 | %select{ :id => "permission_level", :name => "user[permission_level]" } 36 | %option{:value => -1, :selected => @user.admin?} 37 | Admin 38 | %option{:value => 1, :selected => @user.permission_level == 1} 39 | Authenticated user 40 | .buttons 41 | %input{ :value => "Update", :type => "submit" } 42 | - if Sinatra.const_defined?('FacebookObject') 43 | - unless @user.fb_uid 44 | | 45 | = render_facebook_connect_link('Link account with Facebook') 46 | -------------------------------------------------------------------------------- /lib/views/index.haml: -------------------------------------------------------------------------------- 1 | #sinatra_authentication 2 | %h1.page_title Users 3 | %table 4 | %tr 5 | %th 6 | - if current_user.admin? 7 | %th permission level 8 | - @users.each do |user| 9 | %tr 10 | %td 11 | - if user.email 12 | = user.email 13 | - elsif user.fb_uid 14 | 15 | - else 16 | "user #{user.id}" 17 | - if current_user.admin? 18 | %td= user.permission_level 19 | %td 20 | %a{:href => "/users/#{user.id}"} show 21 | - if current_user.admin? 22 | %td 23 | %a{:href => "/users/#{user.id}/edit"} edit 24 | %td 25 | -# this doesn't work for tk 26 | - if !user.site_admin? 27 | %a{:href => "/users/#{user.id}/delete", :onclick => "return confirm('you sure?')"} delete 28 | - else 29 | site admin 30 | -------------------------------------------------------------------------------- /lib/views/login.haml: -------------------------------------------------------------------------------- 1 | #sinatra_authentication 2 | - if Rack.const_defined?('Flash') && flash[:notice] 3 | #sinatra_authentication_flash= flash[:notice] 4 | - if Rack.const_defined?('Flash') && flash[:error] 5 | #sinatra_authentication_flash= flash[:error] 6 | %h1.page_title Login 7 | %form{:action => "/login", :method => "post"} 8 | .field 9 | .label 10 | %label{:for => "user_email'"} Email 11 | %input{:id => "user_email", :name => "email", :size => 30, :type => "text"} 12 | .field 13 | .label 14 | %label{:for => "user_password"} Password 15 | %input{:id => "user_password", :name => "password", :size => 30, :type => "password"} 16 | .buttons 17 | %input{:value => "Login", :type => "submit"} 18 | %a{:href => "/signup", :class => 'sinatra_authentication_link'} 19 | Sign up 20 | - if Sinatra.const_defined?('FacebookObject') 21 | .third_party_signup 22 | %h3.section_title One click login: 23 | .login_link.facebook_login 24 | = render_facebook_connect_link('Login using facebook', :size => 'large') 25 | -------------------------------------------------------------------------------- /lib/views/show.haml: -------------------------------------------------------------------------------- 1 | #sinatra_authentication 2 | %h1.page_title 3 | - if @user.email 4 | = @user.email 5 | - elsif @user.fb_uid 6 | 7 | - if current_user.admin? 8 | %h2 permission level 9 | = @user.permission_level 10 | -------------------------------------------------------------------------------- /lib/views/signup.haml: -------------------------------------------------------------------------------- 1 | #sinatra_authentication 2 | - if Rack.const_defined?('Flash') && flash[:notice] 3 | #sinatra_authentication_flash= flash[:notice] 4 | - if Rack.const_defined?('Flash') && flash[:error] 5 | #sinatra_authentication_flash= flash[:error] 6 | %h1.page_title Sign Up 7 | %form{:action => "/signup", :method => "post"} 8 | .field 9 | .label 10 | %label{:for => "user_email"} Email 11 | %input{ :id => "user_email", :name => "user[email]", :size => 30, :type => "text" , :value => params[:email]} 12 | .field 13 | .label 14 | %label{:for => "user_password"} Password 15 | %input{ :id => "user_password", :name => "user[password]", :size => 30, :type => "password" } 16 | .field 17 | .label 18 | %label{:for => "user_password_confirmation"} Confirm Password 19 | %input{ :id => "user_password_confirmation", :name => "user[password_confirmation]", :size => 30, :type => "password" } 20 | .buttons 21 | %input{ :value => "Sign up", :type => "submit" } 22 | %a{:href => "/login", :class => 'sinatra_authentication_link'} 23 | Login 24 | - if Sinatra.const_defined?('FacebookObject') 25 | .third_party_signup 26 | %h3.section_title One click signup: 27 | .login_link.facebook_login 28 | = render_facebook_connect_link('Signup using facebook', :size => 'large') 29 | -------------------------------------------------------------------------------- /readme.markdown: -------------------------------------------------------------------------------- 1 | ### A little sinatra gem that implements user authentication, with support for Datamapper, Mongomapper, Mongoid, Sequel and Rufus-Tokyo 2 | 3 | ## INSTALLATION: 4 | 5 | in your sinatra app simply require either "dm-core", 'sequel', 'rufus-tokyo', 'mongoid' or "mongo_mapper", "digest/sha1", 'rack-flash' (if you want flash messages) and then "sinatra-authentication" and turn on session storage 6 | with a super secret key, like so: 7 | 8 | require "dm-core" 9 | #for using auto_migrate! 10 | require "dm-migrations" 11 | require "digest/sha1" 12 | require 'rack-flash' 13 | require "sinatra-authentication" 14 | 15 | use Rack::Session::Cookie, :secret => 'A1 sauce 1s so good you should use 1t on a11 yr st34ksssss' 16 | #if you want flash messages 17 | use Rack::Flash 18 | 19 | If you're using rufus-tokyo, you also need to set the database path for Users. like so: 20 | 21 | require "rufus_tokyo" 22 | require "digest/sha1" 23 | require 'rack-flash' 24 | require "sinatra-authentication" 25 | 26 | #Setting the database path for Users 27 | TcUserTable.cabinet_path = File.dirname(__FILE__) + 'folder/where/you/wanna/store/your/database' 28 | 29 | use Rack::Session::Cookie, :secret => 'A1 sauce 1s so good you should use 1t on a11 yr st34ksssss' 30 | #if you want flash messages 31 | use Rack::Flash 32 | 33 | ## DEFAULT ROUTES: 34 | 35 | * get '/login' 36 | * get '/logout' 37 | * get '/signup' 38 | * get/post '/users' 39 | * get '/users/:id' 40 | * get/post '/users/:id/edit' 41 | * get '/users/:id/delete' 42 | 43 | If you fetch any of the user pages using ajax, they will automatically render without a layout 44 | 45 | ## ADDITIONAL ROUTES WHEN USING SINBOOK FOR FACEBOOK INTEGRATION: 46 | 47 | * get '/receiver' 48 | * get '/connect' 49 | 50 | ## FLASH MESSAGES 51 | 52 | Flash messages are implemented using rack-flash. To set them up add this to your code: 53 | 54 | require 'rack-flash' 55 | 56 | #be sure and do this after after 'use Rack:Session:Cookie...' 57 | use Rack::Flash 58 | 59 | And then sinatra-authentication related flash messages will be made available through flash[:notice] (successes) and flash[:error] (failures) 60 | 61 | -# somewhere in a haml view: 62 | = flash[:notice] 63 | = flash[:error] 64 | 65 | ## HELPER METHODS: 66 | 67 | This plugin provides the following helper methods for your sinatra app: 68 | 69 | * login_required 70 | > which you place at the beginning of any routes you want to be protected 71 | * current_user 72 | * logged_in? 73 | * render_login_logout(html_attributes) 74 | > Which renders login/logout and singup/edit account links. 75 | If you pass a hash of html parameters to render_login_logout all the links will get set to them. 76 | Which is useful for if you're using some sort of lightbox 77 | 78 | ## SIMPLE PERMISSIONS: 79 | 80 | By default the user class includes a method called admin? which simply checks 81 | if user.permission_level == -1. 82 | 83 | you can take advantage of this method in your views or controllers by calling 84 | current_user.admin? 85 | i.e. 86 | 87 | - if current_user.admin? 88 | %a{:href => "/adminey_link_route_thing"} do something adminey 89 | 90 | (these view examples are in HAML, by the way) 91 | 92 | You can also extend the user class with any convenience methods for determining permissions. 93 | i.e. 94 | 95 | #somewhere in the murky depths of your sinatra app 96 | class User 97 | def peasant? 98 | self.permission_level == 0 99 | end 100 | end 101 | 102 | then in your views you can do 103 | 104 | - if current_user.peasant? 105 | %h1 hello peasant! 106 | %p Welcome to the caste system! It's very depressing. 107 | 108 | if no one is logged in, current_user returns a GuestUser instance, which responds to current_user.guest? 109 | with true, current_user.permission_level with 0 and any other method calls with false 110 | 111 | This makes some view logic easier since you don't always have to check if the user is logged in, 112 | although a logged_in? helper method is still provided 113 | 114 | ## RUFUS TOKYO 115 | 116 | when using rufus-tokyo, current_user returns a hash, so to get the primary key of the current_user you would do current_user[:pk]. 117 | if you wanna set an attribute, you can do something like current_user["has_a_dog"] = true 118 | and if you want to open a connection with the cabinet directly, you can do something like 119 | 120 | user_connection = TcUser.new 121 | users_with_gmail = user_connection.query do |q| 122 | q.add 'email', :strinc, 'gmail' 123 | end 124 | user_connection.close 125 | 126 | ## FACEBOOK 127 | 128 | # at present, sinatra authentication supports sinbook for interacting with the facebook api. 129 | 130 | If you want to allow users to login using facebook, just require 'sinbook' before requiring 'sinatra-authentication'. 131 | The routes '/receiver' and '/connect' will be added. as well as connect links on the login and edit account pages. 132 | You'll still have to include and initialize the facebook connect javascript in your layout yourself, like so: 133 | 134 | (This example layout assumes you're using sinbook) 135 | 136 | !!! 137 | %head 138 | %title Welcome to my Facebook Connect website! 139 | %script{:type => 'text/javascript', :src => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php/en_US'} 140 | %body 141 | = yield 142 | :javascript 143 | FB.init("#{fb.api_key}", "/receiver") 144 | 145 | Just remember to specify '/receiver' as the path to the xd-receiver file in your call to 'FB.init'. 146 | 147 | The render_login_logout helper 'logout' link will log the user out of facebook and your app. 148 | 149 | I've also included a little helper method 'render_facebook_connect_link' for rendering the facebook connect link with the correct 'onconnect' javascript callback. 150 | The callback redirects to '/connect'. 151 | This is important because the way I've implemented facebook connect support is by pinging '/connect' after the user 152 | successfully connects with facebook. 153 | 154 | If you choose to render the connect button yourself, be sure to have the 'onconnect' callback include "window.location = '/connect'". 155 | 156 | '/connect' redirects to '/' on completion. 157 | 158 | The 'render_facebook_connect_link' helper uses html instead of fbml, so ajax requests to '/login' or "/users/#{user.id}/edit" 159 | will render the connect link without you needing to parse any fbml. 160 | 161 | If the user is already logged into the app and connects with facebook via the user edit page, 162 | it adds their fb_uid to their profile in the database, 163 | which will allow them to log in using their email and password, OR their facebook account. 164 | 165 | If they aren't already logged in to the app through the normal login form, 166 | it creates a new user in the database without an email address or password. 167 | They can later add this data by going to "/users/#{current_user.id}/edit", 168 | which will allow them to log in using their email address and password, OR their facebook account. 169 | 170 | ## OVERRIDING DEFAULT VIEWS 171 | 172 | Right now if you're going to override sinatra-authentication's views, you have to override all of them. 173 | This is something I hope to change in a future release. 174 | 175 | To override the default view path do something like this: 176 | 177 | set :sinatra_authentication_view_path, Pathname(__FILE__).dirname.expand_path + "my_views/" 178 | 179 | And then the views you'll need to define are: 180 | 181 | * show.haml 182 | * index.haml 183 | * signup.haml 184 | * login.haml 185 | * edit.haml 186 | 187 | To override haml, set template_engine in your Sinatra App: 188 | 189 | configure do 190 | set :template_engine, :erb # for example 191 | end 192 | 193 | The signup and edit form fields are named so they pass a hash called 'user' to the server: 194 | 195 | %input{:name => "user[email]", :size => 30, :type => "text", :value => @user.email} 196 | %input{:name => "user[password]", :size => 30, :type => "password"} 197 | %input{:name => "user[password_confirmation]", :size => 30, :type => "password"} 198 | 199 | %select{:name => "user[permission_level]"} 200 | %option{:value => -1, :selected => @user.admin?} 201 | Admin 202 | %option{:value => 1, :selected => @user.permission_level == 1} 203 | Authenticated user 204 | 205 | if you add attributes to the User class and pass them in the user hash your new attributes will be set along with the others. 206 | 207 | The login form fields just pass a field called email and a field called password: 208 | 209 | %input{:name => "email", :size => 30, :type => "text"} 210 | %input{:name => "password", :size => 30, :type => "password"} 211 | 212 | To add methods or properties to the User class, you have to access the underlying database user class, like so: 213 | 214 | class DmUser 215 | property :name, String 216 | property :has_dog, Boolean, :default => false 217 | end 218 | 219 | And then to access/update your newly defined attributes you use the User class: 220 | 221 | current_user.name 222 | current_user.has_dog 223 | 224 | current_user.update({:has_dog => true}) 225 | 226 | new_user = User.set({:email => 'max@max.com' :password => 'hi', :password_confirmation => 'hi', :name => 'Max', :has_dog => false}) 227 | 228 | User.all(:has_dog => true).each do |user| 229 | user.update({has_dog => false}) 230 | end 231 | 232 | User.all(:has_dog => false).each do |user| 233 | user.delete 234 | end 235 | 236 | the User class passes additional method calls along to the interfacing database class, so most calls to Datamapper/Sequel/Mongomapper/RufusTokyo functions should work as expected. 237 | 238 | If you need to get associations on current_user from the underlying ORM use current_user.db_instance, take this case for example: 239 | class Brain 240 | include DataMapper::Resource 241 | property :type, String 242 | property :iq, Integer 243 | end 244 | 245 | class DmUser 246 | has n, :brains 247 | end 248 | 249 | get '/' do 250 | @user_brains = current_user.db_instance.brains 251 | end 252 | 253 | The database user classes are named as follows: 254 | 255 | * for Datamapper: 256 | > DmUser 257 | * for Sequel: 258 | > SequelUser 259 | * for Rufus Tokyo: 260 | > TcUser 261 | * for Mongoid: 262 | > MongoidUser 263 | * for Mongomapper: 264 | > MmUser 265 | 266 | ## Deprecations 267 | * All database adapters now store created_at as a Time object. 268 | 269 | ## Known issues 270 | * First user in database is not properly recognized as site admin 271 | > Proposed fix: add site_admin_email option when initialization functionality is added 272 | 273 | ## Roadmap 274 | 275 | * Move database adapter initialization, along with auto configuration of sinbook and rack flash functionality into a Sinatra::SinatraAuthentication.init(args) method 276 | * Refactor/redesign database adapter interface, make User class AbstractUser and all ORM user classes User, with corresponding specs 277 | * Remove Facebook connect support and add support for Omniauth 278 | * Provide a method for overriding specific views, and/or specifying your own form partial, (passed an instance of User) 279 | * Add Remember me (forever) checkbox to login form 280 | * Add next url parameter support for login/signup 281 | * Add verb selection on configuration (Sign in / Log in) 282 | * Provide optional support through init method for inclusion of username 283 | > Where login form accepts either email or username (through the same field) 284 | * Add email functionality 285 | > Confirmation emails 286 | > Forgotten password emails 287 | * Look into what might be neccesary to allow for logging in using Ajax 288 | 289 | ## Maybe 290 | 291 | * Allow passing custom database attributes into init method, also dynamically altering corresponding signup and user edit views. (potentially leaky abstraction) 292 | > As an alternative, create a generic interface for accessing database row names through the various ORMs. 293 | > So when users alter their User schemas, I can make my views 'Just Work'. 294 | * Add HTTP basic auth support 295 | * Add pluggable OAuth consumer/provider support 296 | 297 | ## License 298 | 299 | This software is released under the Unlicense. See the UNLICENSE file in this repository or http://unlicense.org for details. 300 | -------------------------------------------------------------------------------- /sinatra-authentication-0.3.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxjustus/sinatra-authentication/cb919a7cc01631924d7b830bab41f85f54a7305d/sinatra-authentication-0.3.2.gem -------------------------------------------------------------------------------- /sinatra-authentication-0.4.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxjustus/sinatra-authentication/cb919a7cc01631924d7b830bab41f85f54a7305d/sinatra-authentication-0.4.2.gem -------------------------------------------------------------------------------- /sinatra-authentication.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = %q{sinatra-authentication} 8 | s.version = "0.4.2" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Max Justus Spransy"] 12 | s.date = %q{2010-12-14} 13 | s.description = %q{Simple authentication plugin for sinatra.} 14 | s.email = %q{maxjustus@gmail.com} 15 | s.extra_rdoc_files = [ 16 | "TODO" 17 | ] 18 | s.files = [ 19 | "History.txt", 20 | "Manifest", 21 | "Rakefile", 22 | "TODO", 23 | "example/dm_extend_app.rb", 24 | "example/dm_sinbook.rb", 25 | "example/extend_views/edit.haml", 26 | "example/extend_views/index.haml", 27 | "example/extend_views/login.haml", 28 | "example/extend_views/show.haml", 29 | "example/extend_views/signup.haml", 30 | "example/mm_app.rb", 31 | "example/tc_app.rb", 32 | "example/tc_sinbook.rb", 33 | "lib/models/abstract_user.rb", 34 | "lib/models/datamapper_user.rb", 35 | "lib/models/dm_adapter.rb", 36 | "lib/models/mm_adapter.rb", 37 | "lib/models/mongoid_adapter.rb", 38 | "lib/models/mongoid_user.rb", 39 | "lib/models/mongomapper_user.rb", 40 | "lib/models/rufus_tokyo_user.rb", 41 | "lib/models/sequel_adapter.rb", 42 | "lib/models/sequel_user.rb", 43 | "lib/models/tc_adapter.rb", 44 | "lib/sinatra-authentication.rb", 45 | "lib/sinatra-authentication/models.rb", 46 | "lib/views/edit.haml", 47 | "lib/views/index.haml", 48 | "lib/views/login.haml", 49 | "lib/views/show.haml", 50 | "lib/views/signup.haml", 51 | "readme.markdown", 52 | "sinatra-authentication-0.3.2.gem", 53 | "sinatra-authentication.gemspec", 54 | "spec/run_all_specs.rb", 55 | "spec/unit/dm_model_spec.rb", 56 | "spec/unit/mm_model_spec.rb", 57 | "spec/unit/mongoid_model_spec.rb", 58 | "spec/unit/sequel_model_spec.rb", 59 | "spec/unit/tc_model_spec.rb", 60 | "spec/unit/user_specs.rb", 61 | "test/datamapper_test.rb", 62 | "test/lib/dm_app.rb", 63 | "test/lib/dm_extend_app.rb", 64 | "test/lib/dm_sinbook.rb", 65 | "test/lib/extend_views/edit.haml", 66 | "test/lib/extend_views/index.haml", 67 | "test/lib/extend_views/login.haml", 68 | "test/lib/extend_views/show.haml", 69 | "test/lib/extend_views/signup.haml", 70 | "test/lib/helper.rb", 71 | "test/lib/mm_app.rb", 72 | "test/lib/mongoid_app.rb", 73 | "test/lib/sequel_app.rb", 74 | "test/lib/tc_app.rb", 75 | "test/lib/tc_sinbook.rb", 76 | "test/mongoid_test.rb", 77 | "test/mongomapper_test.rb", 78 | "test/route_tests.rb", 79 | "test/rufus_tokyo_test.rb", 80 | "test/sequel_test.rb" 81 | ] 82 | s.homepage = %q{http://github.com/maxjustus/sinatra-authentication} 83 | s.require_paths = ["lib"] 84 | s.rubygems_version = %q{1.3.7} 85 | s.summary = %q{Simple authentication plugin for sinatra.} 86 | s.test_files = [ 87 | "spec/run_all_specs.rb", 88 | "spec/unit/dm_model_spec.rb", 89 | "spec/unit/mm_model_spec.rb", 90 | "spec/unit/mongoid_model_spec.rb", 91 | "spec/unit/sequel_model_spec.rb", 92 | "spec/unit/tc_model_spec.rb", 93 | "spec/unit/user_specs.rb", 94 | "test/datamapper_test.rb", 95 | "test/lib/dm_app.rb", 96 | "test/lib/dm_extend_app.rb", 97 | "test/lib/dm_sinbook.rb", 98 | "test/lib/helper.rb", 99 | "test/lib/mm_app.rb", 100 | "test/lib/mongoid_app.rb", 101 | "test/lib/sequel_app.rb", 102 | "test/lib/tc_app.rb", 103 | "test/lib/tc_sinbook.rb", 104 | "test/mongoid_test.rb", 105 | "test/mongomapper_test.rb", 106 | "test/route_tests.rb", 107 | "test/rufus_tokyo_test.rb", 108 | "test/sequel_test.rb" 109 | ] 110 | 111 | if s.respond_to? :specification_version then 112 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION 113 | s.specification_version = 3 114 | 115 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 116 | s.add_runtime_dependency(%q, [">= 0"]) 117 | s.add_runtime_dependency(%q, [">= 0"]) 118 | s.add_runtime_dependency(%q, [">= 0"]) 119 | s.add_runtime_dependency(%q, [">= 0"]) 120 | s.add_runtime_dependency(%q, [">= 0"]) 121 | s.add_runtime_dependency(%q, [">= 0"]) 122 | s.add_runtime_dependency(%q, [">= 0"]) 123 | s.add_runtime_dependency(%q, [">= 0"]) 124 | else 125 | s.add_dependency(%q, [">= 0"]) 126 | s.add_dependency(%q, [">= 0"]) 127 | s.add_dependency(%q, [">= 0"]) 128 | s.add_dependency(%q, [">= 0"]) 129 | s.add_dependency(%q, [">= 0"]) 130 | s.add_dependency(%q, [">= 0"]) 131 | s.add_dependency(%q, [">= 0"]) 132 | s.add_dependency(%q, [">= 0"]) 133 | end 134 | else 135 | s.add_dependency(%q, [">= 0"]) 136 | s.add_dependency(%q, [">= 0"]) 137 | s.add_dependency(%q, [">= 0"]) 138 | s.add_dependency(%q, [">= 0"]) 139 | s.add_dependency(%q, [">= 0"]) 140 | s.add_dependency(%q, [">= 0"]) 141 | s.add_dependency(%q, [">= 0"]) 142 | s.add_dependency(%q, [">= 0"]) 143 | end 144 | end 145 | 146 | -------------------------------------------------------------------------------- /spec/run_all_specs.rb: -------------------------------------------------------------------------------- 1 | #!/bin/ruby 2 | 3 | specs = ['sequel_model', 'dm_model', 'mm_model', 'tc_model', 'mongoid_model'] 4 | specs.each do |spec| 5 | spec_path = File.dirname(__FILE__) + "/unit/#{spec}_spec.rb" 6 | @output = %x[rspec #{spec_path}] 7 | puts @output 8 | end 9 | -------------------------------------------------------------------------------- /spec/unit/ar_model_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), '../../test/lib/ar_app') 2 | require File.join(File.dirname(__FILE__), '../../test/lib/helper') 3 | require File.join(File.dirname(__FILE__), 'user_specs') 4 | -------------------------------------------------------------------------------- /spec/unit/dm_model_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), '../../test/lib/dm_app') 2 | require File.join(File.dirname(__FILE__), '../../test/lib/helper') 3 | require File.join(File.dirname(__FILE__), 'user_specs') 4 | -------------------------------------------------------------------------------- /spec/unit/mm_model_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), '../../test/lib/mm_app') 2 | require File.join(File.dirname(__FILE__), '../../test/lib/helper') 3 | require File.join(File.dirname(__FILE__), 'user_specs') 4 | -------------------------------------------------------------------------------- /spec/unit/mongoid_model_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), '../../test/lib/mongoid_app') 2 | require File.join(File.dirname(__FILE__), '../../test/lib/helper') 3 | require File.join(File.dirname(__FILE__), '/user_specs') 4 | -------------------------------------------------------------------------------- /spec/unit/sequel_model_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), '../../test/lib/sequel_app') 2 | require File.join(File.dirname(__FILE__), '../../test/lib/helper') 3 | require File.join(File.dirname(__FILE__), 'user_specs') 4 | 5 | describe User do 6 | describe '#get' do 7 | it 'Should pass arguments to the underlying ORM' do 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/unit/tc_model_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), '../../test/lib/tc_app') 2 | require File.join(File.dirname(__FILE__), '../../test/lib/helper') 3 | require File.join(File.dirname(__FILE__), 'user_specs') 4 | -------------------------------------------------------------------------------- /spec/unit/user_specs.rb: -------------------------------------------------------------------------------- 1 | describe User do 2 | before :each do 3 | User.all.each do |u| 4 | User.delete(u.id) 5 | end 6 | 7 | @user = User.set(TestHelper.gen_user_for_model) 8 | end 9 | 10 | describe 'instance' do 11 | describe '#update' do 12 | before :each do 13 | @user.update(:password => 'password', :password_confirmation => 'password') 14 | end 15 | 16 | it 'Returns an instance if User' do 17 | @user.class.should == User 18 | end 19 | 20 | it 'Should return true on success' do 21 | @user.update(:email => 'max@max.com', :password => 'hi', :password_confirmation => 'hi').should == true 22 | @user.update(:email => 'max@max.com').should == true 23 | end 24 | 25 | it 'Does not allow duplicate email addresses on update' do 26 | @user_two = User.set(:email => 'steve@steve.com', :password => 'hi', :password_confirmation => 'hi') 27 | @user.update(:email => 'steve@steve.com').should == false 28 | #@user.class.should == User 29 | end 30 | 31 | it 'Should say saved is false when invalid' do 32 | @user.update(:password => 'paz') 33 | @user.saved.should == false 34 | end 35 | end 36 | 37 | describe '#errors' do 38 | it 'Returns a string of errors' do 39 | @user.update(:password => 'paz') 40 | @user.errors.class.should == String && @user.errors.length.should > 0 41 | end 42 | end 43 | 44 | describe '#valid' do 45 | it 'Should return false when invalid' do 46 | @user.update(:password => 'hi') 47 | @user.valid.should == false 48 | end 49 | 50 | it 'Should validate format of email' do 51 | @user.update(:email => 'meeewewe!!') 52 | @user.valid.should == false 53 | end 54 | 55 | it 'Should return true when valid' do 56 | @user.valid.should == true 57 | end 58 | end 59 | end 60 | 61 | describe '#all' do 62 | it 'Should return an array of instances of User' do 63 | User.all[0].class.should == User 64 | end 65 | 66 | it 'Should return users in descending order of creation time' do 67 | @newest_user = User.set(:email => 'hey@hi.com', :password => 'hihihi', :password_confirmation => 'hihihi', :created_at => Time.now + 1) 68 | User.all[0].email.should == @newest_user.email 69 | end 70 | 71 | #it 'Should accept a page argument in a hash and limit results to 20 by default' do 72 | # 40.times do |n| 73 | # @newest_user = User.set(:email => "#{n}hey@hi.com", :password => 'hihihi', :password_confirmation => 'hihihi', :created_at => Time.now + n) 74 | # end 75 | # users = User.all(:page => 0) 76 | # users[0].email.should == @newest_user.email 77 | # users.size.should == 20 78 | # User.all(:page => 1)[0].email.should == "19hey@hi.com" 79 | #end 80 | end 81 | 82 | describe '#set' do 83 | it 'Returns an instance if User' do 84 | @user.class.should == User 85 | end 86 | 87 | it 'Timestamps user with Time' do 88 | @user.created_at.class.should == Time 89 | end 90 | 91 | it 'Should set first user in database as site admin' do 92 | @user.site_admin?.should == true 93 | end 94 | 95 | #it 'Should say saved is true' do 96 | # @user.saved.should == true 97 | #end 98 | end 99 | 100 | describe '#set!' do 101 | before :each do 102 | @user = User.set!({:password => 'hi'}) 103 | end 104 | 105 | it 'Returns an instance if User' do 106 | @user.class.should == User 107 | end 108 | 109 | #it 'Should say saved is true' do 110 | # @user.saved.should == true 111 | #end 112 | end 113 | 114 | describe '#delete' do 115 | it 'Returns a boolean' do 116 | User.delete(@user.id).should == true 117 | end 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /test/activerecord_test.rb: -------------------------------------------------------------------------------- 1 | require 'lib/ar_app' 2 | require 'lib/helper' 3 | require 'test/unit' 4 | require 'rack/test' 5 | require 'route_tests' 6 | -------------------------------------------------------------------------------- /test/datamapper_test.rb: -------------------------------------------------------------------------------- 1 | require 'lib/dm_app' 2 | require 'lib/helper' 3 | require 'test/unit' 4 | require 'rack/test' 5 | require 'route_tests' 6 | -------------------------------------------------------------------------------- /test/lib/ar_app.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'active_record' 4 | require 'rack-flash' 5 | 6 | ActiveRecord::Base.establish_connection( 7 | :adapter => "sqlite3", 8 | :database => "#{Dir.pwd}/test.db" 9 | ) 10 | 11 | require File.join(File.dirname(__FILE__), '../../lib/sinatra-authentication') 12 | 13 | use Rack::Session::Cookie, :secret => "heyhihello" 14 | use Rack::Flash 15 | 16 | set :environment, 'development' 17 | set :public, 'public' 18 | set :views, 'views' 19 | 20 | get '/' do 21 | send TEMPLATE, "= render_login_logout", :layout => :layout 22 | end 23 | 24 | __END__ 25 | 26 | @@ layout 27 | = yield 28 | -------------------------------------------------------------------------------- /test/lib/dm_app.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'dm-core' 4 | require 'dm-migrations' 5 | require 'rack-flash' 6 | require File.join(File.dirname(__FILE__), '../../lib/sinatra-authentication') 7 | 8 | DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/test.db") 9 | DataMapper.auto_migrate! 10 | 11 | use Rack::Session::Cookie, :secret => "heyhihello" 12 | use Rack::Flash 13 | 14 | set :environment, 'development' 15 | set :public, 'public' 16 | set :views, 'views' 17 | 18 | get '/' do 19 | send TEMPLATE, "= render_login_logout", :layout => :layout 20 | end 21 | -------------------------------------------------------------------------------- /test/lib/dm_extend_app.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'dm-core' 4 | require 'dm-migrations' 5 | require 'rack-flash' 6 | require File.join(File.dirname(__FILE__), '../../lib/sinatra-authentication') 7 | 8 | 9 | class DmUser 10 | property :name, String 11 | end 12 | 13 | DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/test.db") 14 | DataMapper.auto_migrate! 15 | 16 | set :sinatra_authentication_view_path, Pathname(__FILE__).dirname.expand_path + "extend_views/" 17 | use Rack::Session::Cookie, :secret => "heyhihello" 18 | use Rack::Flash 19 | 20 | set :environment, 'development' 21 | set :public, 'public' 22 | set :views, 'views' 23 | 24 | get '/' do 25 | puts User.all(:name => 'max') 26 | send TEMPLATE, "= render_login_logout", :layout => :layout 27 | end 28 | -------------------------------------------------------------------------------- /test/lib/dm_sinbook.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'sinbook' 4 | require 'dm-core' 5 | require File.join(File.dirname(__FILE__), '../../lib/sinatra-authentication') 6 | 7 | facebook do 8 | api_key 'aa2db1b96cb7b57f0c5b1d4d3d8f0a22' 9 | secret '21d94ee63969ae3b3f833689838ca00f' 10 | app_id 48652736613 11 | url 'peoplewithjetpacks.com:4568/' 12 | callback 'peoplewithjetpacks.com:4568/' 13 | end 14 | 15 | set :port, 4568 16 | 17 | DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/test.db") 18 | DataMapper.auto_migrate! 19 | 20 | use Rack::Session::Cookie, :secret => "heyhihello" 21 | 22 | set :environment, 'development' 23 | set :public, 'public' 24 | set :views, 'views' 25 | 26 | get '/' do 27 | send TEMPLATE, :main 28 | end 29 | 30 | get '/test' do 31 | login_required 32 | 'hihihi' 33 | end 34 | 35 | __END__ 36 | 37 | @@ layout 38 | %html{:xmlns=>"http://www.w3.org/1999/xhtml", :'xmlns:fb'=>"http://www.facebook.com/2008/fbml"} 39 | %head 40 | %title Welcome to my Facebook Connect website! 41 | %script{:type => 'text/javascript', :src => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php/en_US'} 42 | %body 43 | = render_login_logout 44 | = yield 45 | :javascript 46 | FB.init("#{fb.api_key}", "/receiver") 47 | 48 | @@ main 49 | - if fb[:user] 50 | Hi, 51 | %fb:profile-pic{:uid => fb[:user]} 52 | %fb:name{:uid => fb[:user], :useyou => 'false', :firstnameonly => 'true'} 53 | ! 54 | 55 | -------------------------------------------------------------------------------- /test/lib/extend_views/edit.haml: -------------------------------------------------------------------------------- 1 | #sinatra_authentication 2 | #sinatra_authentication_flash= session[:flash] 3 | %h1 4 | Edit 5 | - if @user.id == current_user.id 6 | account 7 | - else 8 | - if @user.email 9 | = @user.email 10 | - elsif @user.fb_uid 11 | 12 | - else 13 | account 14 | %form{:action => "/users/#{@user.id}/edit", :method => "post"} 15 | .field 16 | .label 17 | %label{:for => "user_email"} Email 18 | %input{ :id => "user_email", :name => "user[email]", :size => 30, :type => "text", :value => @user.email } 19 | .field 20 | .label 21 | %label{:for => "user_email"} Name 22 | %input{ :id => "user_email", :name => "user[name]", :size => 30, :type => "text", :value => @user.name } 23 | .field 24 | .label 25 | %label{:for => "user_password"} New password 26 | %input{ :id => "user_password", :name => "user[password]", :size => 30, :type => "password" } 27 | .field 28 | .label 29 | %label{:for => "user_password_confirmation"} Confirm 30 | %input{ :id => "user_password_confirmation", :name => "user[password_confirmation]", :size => 30, :type => "password" } 31 | -# don't render permission field if admin and editing yourself so you don't shoot yourself in the foot 32 | - if current_user.admin? && current_user.id != @user.id 33 | .field 34 | .label 35 | %label{:for => 'permission_level'} Permission level 36 | %select{ :id => "permission_level", :name => "user[permission_level]" } 37 | %option{:value => -1, :selected => @user.admin?} 38 | Admin 39 | %option{:value => 1, :selected => @user.permission_level == 1} 40 | Authenticated user 41 | .buttons 42 | %input{ :value => "Update", :type => "submit" } 43 | - if Sinatra.const_defined?('FacebookObject') 44 | - unless @user.fb_uid 45 | | 46 | = render_facebook_connect_link('Link account with Facebook') 47 | -------------------------------------------------------------------------------- /test/lib/extend_views/index.haml: -------------------------------------------------------------------------------- 1 | #sinatra_authentication 2 | %h1.page_title Users 3 | %table 4 | %tr 5 | %th 6 | - if current_user.admin? 7 | %th permission level 8 | - @users.each do |user| 9 | %tr 10 | %td 11 | - if user.email 12 | = user.email 13 | - elsif user.fb_uid 14 | 15 | - else 16 | "user #{user.id}" 17 | - if current_user.admin? 18 | %td= user.permission_level 19 | %td 20 | = user.name 21 | %td 22 | %a{:href => "/users/#{user.id}"} show 23 | - if current_user.admin? 24 | %td 25 | %a{:href => "/users/#{user.id}/edit"} edit 26 | %td 27 | -# this doesn't work for tk 28 | - if !user.site_admin? 29 | %a{:href => "/users/#{user.id}/delete", :onclick => "return confirm('you sure?')"} delete 30 | - else 31 | site admin 32 | -------------------------------------------------------------------------------- /test/lib/extend_views/login.haml: -------------------------------------------------------------------------------- 1 | #sinatra_authentication 2 | #sinatra_authentication_flash= session[:flash] 3 | %h1.page_title Login 4 | %form{:action => "/login", :method => "post"} 5 | .field 6 | .label 7 | %label{:for => "user_email'"} Email 8 | %input{:id => "user_email", :name => "email", :size => 30, :type => "text"} 9 | .field 10 | .label 11 | %label{:for => "user_password"} Password 12 | %input{:id => "user_password", :name => "password", :size => 30, :type => "password"} 13 | .buttons 14 | %input{:value => "login", :type => "submit"} 15 | %a{:href => "/signup", :class => 'sinatra_authentication_link'} 16 | Signup 17 | - if Sinatra.const_defined?('FacebookObject') 18 | .third_party_signup 19 | %h3.section_title One click login: 20 | .login_link.facebook_login 21 | = render_facebook_connect_link('Login using facebook', :size => 'large') 22 | -------------------------------------------------------------------------------- /test/lib/extend_views/show.haml: -------------------------------------------------------------------------------- 1 | #sinatra_authentication 2 | %h1.page_title 3 | - if @user.email 4 | = @user.email 5 | - elsif @user.fb_uid 6 | 7 | - if current_user.admin? 8 | %h2 permission level 9 | = @user.permission_level 10 | -------------------------------------------------------------------------------- /test/lib/extend_views/signup.haml: -------------------------------------------------------------------------------- 1 | #sinatra_authentication 2 | #sinatra_authentication_flash= session[:flash] 3 | %h1.page_title Signup 4 | %form{:action => "/signup", :method => "post"} 5 | .field 6 | .label 7 | %label{:for => "user_email"} Email 8 | %input{ :id => "user_email", :name => "user[email]", :size => 30, :type => "text" } 9 | .field 10 | .label 11 | %label{:for => "user_name"} Name 12 | %input{ :id => "user_name", :name => "user[name]", :size => 30, :type => "text" } 13 | .field 14 | .label 15 | %label{:for => "user_password"} Password 16 | %input{ :id => "user_password", :name => "user[password]", :size => 30, :type => "password" } 17 | .field 18 | .label 19 | %label{:for => "user_password_confirmation"} Confirm Password 20 | %input{ :id => "user_password_confirmation", :name => "user[password_confirmation]", :size => 30, :type => "password" } 21 | .buttons 22 | %input{ :value => "Create account", :type => "submit" } 23 | %a{:href => "/login", :class => 'sinatra_authentication_link'} 24 | Login 25 | - if Sinatra.const_defined?('FacebookObject') 26 | .third_party_signup 27 | %h3.section_title One click signup: 28 | .login_link.facebook_login 29 | = render_facebook_connect_link('Signup using facebook', :size => 'large') 30 | -------------------------------------------------------------------------------- /test/lib/helper.rb: -------------------------------------------------------------------------------- 1 | require 'haml' 2 | TEMPLATE = :haml 3 | 4 | class TestHelper 5 | def self.gen_user 6 | {'user[email]' => 'yodawg@gmail.com', 'user[password]' => 'password', 'user[password_confirmation]' => 'password'} 7 | end 8 | 9 | def self.gen_user_for_model 10 | {:email => 'yodawg@gmail.com', :password => 'password', :password_confirmation => 'password'} 11 | end 12 | end 13 | 14 | def app 15 | Sinatra::Application 16 | end 17 | -------------------------------------------------------------------------------- /test/lib/mm_app.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'rack-flash' 4 | require 'mongo_mapper' 5 | 6 | #logger = Logger.new($stdout) 7 | #MongoMapper.connection = Mongo::Connection.new('db.mongohq.com', 27017, :logger => logger) 8 | #MongoMapper.database = "fdbk" 9 | #MongoMapper.database.authenticate(ENV['mongohq_user'], ENV['mongohq_pass']) 10 | 11 | require File.join(File.dirname(__FILE__), '../../lib/sinatra-authentication') 12 | 13 | MongoMapper.database = "sinatraauthtest" 14 | 15 | use Rack::Session::Cookie, :secret => "heyhihello" 16 | use Rack::Flash 17 | 18 | set :environment, 'development' 19 | set :public_folder, 'public' 20 | set :views, 'views' 21 | 22 | get '/' do 23 | send TEMPLATE, "= render_login_logout", :layout => :layout 24 | end 25 | -------------------------------------------------------------------------------- /test/lib/mongoid_app.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'rack-flash' 4 | require 'mongoid' 5 | 6 | #logger = Logger.new($stdout) 7 | #MongoMapper.connection = Mongo::Connection.new('db.mongohq.com', 27017, :logger => logger) 8 | #MongoMapper.database = "fdbk" 9 | #MongoMapper.database.authenticate(ENV['mongohq_user'], ENV['mongohq_pass']) 10 | 11 | require File.join(File.dirname(__FILE__), '../../lib/sinatra-authentication') 12 | 13 | Mongoid.configure do |config| 14 | name = "sinatraauth_test" 15 | host = "localhost" 16 | config.master = Mongo::Connection.new.db(name) 17 | end 18 | 19 | use Rack::Session::Cookie, :secret => "heyhihello" 20 | use Rack::Flash 21 | 22 | set :environment, 'development' 23 | set :public, 'public' 24 | set :views, 'views' 25 | 26 | get '/' do 27 | send TEMPLATE, "= render_login_logout", :layout => :layout 28 | end 29 | -------------------------------------------------------------------------------- /test/lib/sequel_app.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'sequel' 4 | require 'rack-flash' 5 | 6 | #DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/test.db") 7 | #DataMapper.auto_migrate! 8 | DB = Sequel.sqlite(:database => 'test.db') 9 | 10 | require File.join(File.dirname(__FILE__), '../../lib/sinatra-authentication') 11 | 12 | use Rack::Session::Cookie, :secret => "heyhihello" 13 | use Rack::Flash 14 | 15 | set :environment, 'development' 16 | set :public, 'public' 17 | set :views, 'views' 18 | 19 | get '/' do 20 | send TEMPLATE, "= render_login_logout", :layout => :layout 21 | end 22 | -------------------------------------------------------------------------------- /test/lib/tc_app.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'rufus/tokyo' 4 | require 'rack-flash' 5 | require File.join(File.dirname(__FILE__), '../../lib/sinatra-authentication') 6 | 7 | use Rack::Session::Cookie, :secret => "heyhihello" 8 | use Rack::Flash 9 | TcUserTable.cabinet_path = File.dirname(__FILE__) 10 | 11 | set :environment, 'development' 12 | set :public, 'public' 13 | set :views, 'views' 14 | 15 | get '/' do 16 | send TEMPLATE, "= render_login_logout", :layout => :layout 17 | end 18 | -------------------------------------------------------------------------------- /test/lib/tc_sinbook.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinbook' 3 | require 'rufus/tokyo' 4 | require 'sinatra' 5 | require File.join(File.dirname(__FILE__), '../../lib/sinatra-authentication') 6 | 7 | use Rack::Session::Cookie, :secret => "heyhihello" 8 | TcUserTable.cabinet_path = File.dirname(__FILE__) 9 | 10 | facebook do 11 | api_key 'aa2db1b96cb7b57f0c5b1d4d3d8f0a22' 12 | secret '21d94ee63969ae3b3f833689838ca00f' 13 | app_id 48652736613 14 | url 'peoplewithjetpacks.com:4568/' 15 | callback 'peoplewithjetpacks.com:4568/' 16 | end 17 | 18 | set :port, 4568 19 | 20 | get '/' do 21 | send TEMPLATE, :main 22 | end 23 | 24 | get '/test' do 25 | login_required 26 | 'hihihi' 27 | end 28 | 29 | __END__ 30 | 31 | @@ layout 32 | %html{:xmlns=>"http://www.w3.org/1999/xhtml", :'xmlns:fb'=>"http://www.facebook.com/2008/fbml"} 33 | %head 34 | %title Welcome to my Facebook Connect website! 35 | %script{:type => 'text/javascript', :src => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php/en_US'} 36 | %script{:type => 'text/javascript', :src => 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js'} 37 | :javascript 38 | $(document).ready(function(){ 39 | /* test facebook crap works with ajax */ 40 | $('.sinatra-authentication-login').click(function(){ 41 | $.get($(this).attr('href'), {}, function(data){ 42 | $('#test_box').html(data); 43 | }); 44 | return false; 45 | }); 46 | }); 47 | %body 48 | = render_login_logout 49 | = yield 50 | :javascript 51 | FB.init("#{fb.api_key}", "/receiver") 52 | #test_box 53 | 54 | @@ main 55 | - if fb[:user] 56 | Hi, 57 | %fb:profile-pic{:uid => fb[:user]} 58 | %fb:name{:uid => fb[:user], :useyou => 'false', :firstnameonly => 'true'} 59 | ! 60 | %br/ 61 | 62 | -------------------------------------------------------------------------------- /test/mongoid_test.rb: -------------------------------------------------------------------------------- 1 | require 'lib/mongoid_app' 2 | require 'lib/helper' 3 | require 'test/unit' 4 | require 'rack/test' 5 | require 'route_tests' 6 | -------------------------------------------------------------------------------- /test/mongomapper_test.rb: -------------------------------------------------------------------------------- 1 | require 'lib/mm_app' 2 | require 'lib/helper' 3 | require 'test/unit' 4 | require 'rack/test' 5 | require 'route_tests' 6 | 7 | #Test::Unit::TestCase.send :include, Rack::Test::Methods 8 | 9 | #class SinatraAuthMongoMapperTest < Test::Unit::TestCase 10 | # include Rack::Test::Methods 11 | # 12 | # def app 13 | # TestApp 14 | # end 15 | # 16 | # def setup 17 | # post '/signup', TestHelper.gen_user 18 | # follow_redirect! 19 | # get '/logout' 20 | # end 21 | # 22 | # def test_should_login 23 | # post '/login', {'email' => TestHelper.gen_user['user[email]'], 'password' => TestHelper.gen_user['user[password]']} 24 | # follow_redirect! 25 | # 26 | # assert_equal 'http://example.org/', last_request.url 27 | # #assert cookie_jar['user'] 28 | # assert last_request.env['rack.session'][:user] 29 | # assert last_response.ok? 30 | # end 31 | # 32 | # def test_should_logout 33 | # post '/login', {'email' => TestHelper.gen_user['user[email]'], 'password' => TestHelper.gen_user['user[password]']} 34 | # get '/logout' 35 | # follow_redirect! 36 | # 37 | # assert !last_request.env['rack.session'][:user] 38 | # assert_equal 'http://example.org/', last_request.url 39 | # end 40 | #end 41 | -------------------------------------------------------------------------------- /test/route_tests.rb: -------------------------------------------------------------------------------- 1 | Test::Unit::TestCase.send :include, Rack::Test::Methods 2 | 3 | class SinatraAuthDataMapperTest < Test::Unit::TestCase 4 | 5 | def setup 6 | post '/signup', TestHelper.gen_user 7 | follow_redirect! 8 | get '/logout' 9 | end 10 | 11 | def test_should_login 12 | post '/login', {'email' => TestHelper.gen_user['user[email]'], 'password' => TestHelper.gen_user['user[password]']} 13 | follow_redirect! 14 | 15 | assert_equal 'http://example.org/', last_request.url 16 | #assert cookie_jar['user'] 17 | assert last_request.env['rack.session'][:user] 18 | assert last_response.ok? 19 | end 20 | 21 | def test_should_logout 22 | post '/login', {'email' => TestHelper.gen_user['user[email]'], 'password' => TestHelper.gen_user['user[password]']} 23 | get '/logout' 24 | follow_redirect! 25 | 26 | assert !last_request.env['rack.session'][:user] 27 | assert_equal 'http://example.org/', last_request.url 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/rufus_tokyo_test.rb: -------------------------------------------------------------------------------- 1 | require 'lib/tc_app' 2 | require 'lib/helper' 3 | require 'test/unit' 4 | require 'rack/test' 5 | require 'route_tests' 6 | -------------------------------------------------------------------------------- /test/sequel_test.rb: -------------------------------------------------------------------------------- 1 | require 'lib/sequel_app' 2 | require 'lib/helper' 3 | require 'test/unit' 4 | require 'rack/test' 5 | require 'route_tests' 6 | --------------------------------------------------------------------------------