diff --git a/Gemfile b/Gemfile index 4c89951..1516b7f 100644 --- a/Gemfile +++ b/Gemfile @@ -42,3 +42,4 @@ gem 'haml-rails' gem 'mm-multi-parameter-attributes' gem 'feedzirra' gem 'htmlentities' +gem 'sorcery' diff --git a/Gemfile.lock b/Gemfile.lock index 88f2f6a..8597b3e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -29,6 +29,7 @@ GEM i18n (~> 0.6) multi_json (~> 1.0) arel (3.0.2) + bcrypt-ruby (3.0.1) bson (1.6.4) bson_ext (1.6.4) bson (~> 1.6.4) @@ -44,6 +45,8 @@ GEM erubis (2.7.0) execjs (1.4.0) multi_json (~> 1.0) + faraday (0.8.1) + multipart-post (~> 1.1) feedzirra (0.0.31) activesupport (>= 3.0.8) builder (~> 3.0.0) @@ -85,7 +88,12 @@ GEM activesupport (~> 3.0) plucky (~> 0.4.0) multi_json (1.3.6) + multipart-post (1.1.5) nokogiri (1.4.7) + oauth (0.4.6) + oauth2 (0.5.2) + faraday (~> 0.7) + multi_json (~> 1.0) plucky (0.4.4) mongo (~> 1.5) polyglot (0.3.3) @@ -121,6 +129,10 @@ GEM tilt (~> 1.3) sax-machine (0.0.20) nokogiri (> 0.0.0) + sorcery (0.7.12) + bcrypt-ruby (~> 3.0.0) + oauth (~> 0.4.4) + oauth2 (~> 0.5.1) sprockets (2.1.3) hike (~> 1.2) rack (~> 1.0) @@ -149,4 +161,5 @@ DEPENDENCIES mongo_mapper rails (= 3.2.3) sass-rails (~> 3.2.3) + sorcery uglifier (>= 1.0.3) diff --git a/app/assets/javascripts/sessions.js.coffee b/app/assets/javascripts/sessions.js.coffee new file mode 100644 index 0000000..7615679 --- /dev/null +++ b/app/assets/javascripts/sessions.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/users.js.coffee b/app/assets/javascripts/users.js.coffee new file mode 100644 index 0000000..7615679 --- /dev/null +++ b/app/assets/javascripts/users.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/stylesheets/sessions.css.scss b/app/assets/stylesheets/sessions.css.scss new file mode 100644 index 0000000..7bef9cf --- /dev/null +++ b/app/assets/stylesheets/sessions.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the sessions controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/users.css.scss b/app/assets/stylesheets/users.css.scss new file mode 100644 index 0000000..31a2eac --- /dev/null +++ b/app/assets/stylesheets/users.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Users controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e8065d9..10a1ffb 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,9 @@ class ApplicationController < ActionController::Base protect_from_forgery + + private + + def not_authenticated + redirect_to login_path, :notice => "Please log in" + end end diff --git a/app/controllers/feeds_controller.rb b/app/controllers/feeds_controller.rb index f23d1bb..72b3a7a 100644 --- a/app/controllers/feeds_controller.rb +++ b/app/controllers/feeds_controller.rb @@ -1,6 +1,6 @@ class FeedsController < ApplicationController - http_basic_authenticate_with :name => ENV['auth_username'], :password => ENV['auth_password'] - + before_filter :require_login + # GET /feeds # GET /feeds.json def index diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000..c4480a9 --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,19 @@ +class SessionsController < ApplicationController + def new + end + + def create + user = login(params[:email], params[:password], params[:remember_me]) + if user + redirect_back_or_to root_url, :notice => "Logged in OK" + else + flash.now.alert = "Email or password was invalid" + render :new + end + end + + def destroy + logout + redirect_to root_path, :notice => "Logged out" + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 0000000..dcf538d --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,83 @@ +class UsersController < ApplicationController + # GET /users + # GET /users.json + def index + @users = User.all + + respond_to do |format| + format.html # index.html.erb + format.json { render json: @users } + end + end + + # GET /users/1 + # GET /users/1.json + def show + @user = User.find(params[:id]) + + respond_to do |format| + format.html # show.html.erb + format.json { render json: @user } + end + end + + # GET /users/new + # GET /users/new.json + def new + @user = User.new + + respond_to do |format| + format.html # new.html.erb + format.json { render json: @user } + end + end + + # GET /users/1/edit + def edit + @user = User.find(params[:id]) + end + + # POST /users + # POST /users.json + def create + @user = User.new(params[:user]) + + respond_to do |format| + if @user.save + format.html { redirect_to @user, notice: 'User was successfully created.' } + format.json { render json: @user, status: :created, location: @user } + else + format.html { render action: "new" } + format.json { render json: @user.errors, status: :unprocessable_entity } + end + end + end + + # PUT /users/1 + # PUT /users/1.json + def update + @user = User.find(params[:id]) + + respond_to do |format| + if @user.update_attributes(params[:user]) + format.html { redirect_to @user, notice: 'User was successfully updated.' } + format.json { head :no_content } + else + format.html { render action: "edit" } + format.json { render json: @user.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /users/1 + # DELETE /users/1.json + def destroy + @user = User.find(params[:id]) + @user.destroy + + respond_to do |format| + format.html { redirect_to users_url } + format.json { head :no_content } + end + end +end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb new file mode 100644 index 0000000..309f8b2 --- /dev/null +++ b/app/helpers/sessions_helper.rb @@ -0,0 +1,2 @@ +module SessionsHelper +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb new file mode 100644 index 0000000..2310a24 --- /dev/null +++ b/app/helpers/users_helper.rb @@ -0,0 +1,2 @@ +module UsersHelper +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..55b2682 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,17 @@ +class User + include MongoMapper::Document + + key :email, String + key :crypted_password, String + key :salt, String + + authenticates_with_sorcery! +# attr_accessible :email, :password, :password_confirmation + + validates_presence_of :email + validates_presence_of :password, :on => :create + validates_uniqueness_of :email + validates_length_of :password, :minimum => 3, :message => "password must be at least 3 characters long", :if => :password + validates_confirmation_of :password, :message => "should match confirmation", :if => :password + +end diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index b426519..fe17765 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -10,6 +10,14 @@ %body = link_to(image_tag("TAL_logo_blue-h100.png", :size => "275x100", :alt => "Talk About Local logo"), :root, :class => "logo") + #userbar + - if current_user + Logged in as + = current_user.email + = link_to "Log out", logout_path + - else + = link_to "Log in", login_path + %p#notice= notice = yield diff --git a/app/views/sessions/new.html.haml b/app/views/sessions/new.html.haml new file mode 100644 index 0000000..f818cae --- /dev/null +++ b/app/views/sessions/new.html.haml @@ -0,0 +1,19 @@ +%h1 Log in + += form_tag sessions_path do + .field + = label_tag :email + %br + = text_field_tag :email, params[:email] + + .field + = label_tag :password + %br + = password_field_tag :password + + .field + = check_box_tag :remember_me, 1, params[:remember_me] + = label_tag :remember_me + + .actions + = submit_tag "Log in" \ No newline at end of file diff --git a/app/views/users/_form.html.haml b/app/views/users/_form.html.haml new file mode 100644 index 0000000..52700d4 --- /dev/null +++ b/app/views/users/_form.html.haml @@ -0,0 +1,19 @@ += form_for @user do |f| + -if @user.errors.any? + #error_explanation + %h2= "#{pluralize(@user.errors.count, "error")} prohibited this user from being saved:" + %ul + - @user.errors.full_messages.each do |msg| + %li= msg + + .field + = f.label :email + = f.text_field :email + .field + = f.label :password + = f.password_field :password + .field + = f.label :password_confirmation + = f.password_field :password_confirmation + .actions + = f.submit 'Save' diff --git a/app/views/users/edit.html.haml b/app/views/users/edit.html.haml new file mode 100644 index 0000000..44f7526 --- /dev/null +++ b/app/views/users/edit.html.haml @@ -0,0 +1,7 @@ +%h1 Editing user + += render 'form' + += link_to 'Show', @user +\| += link_to 'Back', users_path diff --git a/app/views/users/index.html.haml b/app/views/users/index.html.haml new file mode 100644 index 0000000..af5f9b0 --- /dev/null +++ b/app/views/users/index.html.haml @@ -0,0 +1,23 @@ +%h1 Listing users + +%table + %tr + %th Email + %th Crypted password + %th Salt + %th + %th + %th + + - @users.each do |user| + %tr + %td= user.email + %td= user.crypted_password + %td= user.salt + %td= link_to 'Show', user + %td= link_to 'Edit', edit_user_path(user) + %td= link_to 'Destroy', user, :confirm => 'Are you sure?', :method => :delete + +%br + += link_to 'New User', new_user_path diff --git a/app/views/users/new.html.haml b/app/views/users/new.html.haml new file mode 100644 index 0000000..7b9e858 --- /dev/null +++ b/app/views/users/new.html.haml @@ -0,0 +1,5 @@ +%h1 New user + += render 'form' + += link_to 'Back', users_path diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml new file mode 100644 index 0000000..6497755 --- /dev/null +++ b/app/views/users/show.html.haml @@ -0,0 +1,15 @@ +%p#notice= notice + +%p + %b Email: + = @user.email +%p + %b Crypted password: + = @user.crypted_password +%p + %b Salt: + = @user.salt + += link_to 'Edit', edit_user_path(@user) +\| += link_to 'Back', users_path diff --git a/config/initializers/sorcery.rb b/config/initializers/sorcery.rb new file mode 100644 index 0000000..72fd06a --- /dev/null +++ b/config/initializers/sorcery.rb @@ -0,0 +1,407 @@ +# The first thing you need to configure is which modules you need in your app. +# The default is nothing which will include only core features (password encryption, login/logout). +# Available submodules are: :user_activation, :http_basic_auth, :remember_me, +# :reset_password, :session_timeout, :brute_force_protection, :activity_logging, :external +# Rails.application.config.sorcery.submodules = [:remember_me, :reset_password] +Rails.application.config.sorcery.submodules = [:remember_me] + +# Here you can configure each submodule's features. +Rails.application.config.sorcery.configure do |config| + # -- core -- + # What controller action to call for non-authenticated users. You can also + # override the 'not_authenticated' method of course. + # Default: `:not_authenticated` + # +# config.not_authenticated_action = + + + # When a non logged in user tries to enter a page that requires login, save + # the URL he wanted to reach, and send him there after login, using 'redirect_back_or_to'. + # Default: `true` + # + # config.save_return_to_url = + + + # Set domain option for cookies; Useful for remember_me submodule. + # Default: `nil` + # + # config.cookie_domain = + + + # -- session timeout -- + # How long in seconds to keep the session alive. + # Default: `3600` + # + # config.session_timeout = + + + # Use the last action as the beginning of session timeout. + # Default: `false` + # + # config.session_timeout_from_last_action = + + + # -- http_basic_auth -- + # What realm to display for which controller name. For example {"My App" => "Application"} + # Default: `{"application" => "Application"}` + # + # config.controller_to_realm_map = + + + # -- activity logging -- + # will register the time of last user login, every login. + # Default: `true` + # + # config.register_login_time = + + + # will register the time of last user logout, every logout. + # Default: `true` + # + # config.register_logout_time = + + + # will register the time of last user action, every action. + # Default: `true` + # + # config.register_last_activity_time = + + + # -- external -- + # What providers are supported by this app, i.e. [:twitter, :facebook, :github, :google, :liveid] . + # Default: `[]` + # + # config.external_providers = + + + # You can change it by your local ca_file. i.e. '/etc/pki/tls/certs/ca-bundle.crt' + # Path to ca_file. By default use a internal ca-bundle.crt. + # Default: `'path/to/ca_file'` + # + # config.ca_file = + + + # Twitter wil not accept any requests nor redirect uri containing localhost, + # make sure you use 0.0.0.0:3000 to access your app in development + # + # config.twitter.key = "" + # config.twitter.secret = "" + # config.twitter.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=twitter" + # config.twitter.user_info_mapping = {:email => "screen_name"} + # + # config.facebook.key = "" + # config.facebook.secret = "" + # config.facebook.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=facebook" + # config.facebook.user_info_mapping = {:email => "name"} + # + # config.github.key = "" + # config.github.secret = "" + # config.github.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=github" + # config.github.user_info_mapping = {:email => "name"} + # + # config.google.key = "" + # config.google.secret = "" + # config.google.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=google" + # config.google.user_info_mapping = {:email => "email", :username => "name"} + # + # To use liveid in development mode you have to replace mydomain.com with + # a valid domain even in development. To use a valid domain in development + # simply add your domain in your /etc/hosts file in front of 127.0.0.1 + # + # config.liveid.key = "" + # config.liveid.secret = "" + # config.liveid.callback_url = "http://mydomain.com:3000/oauth/callback?provider=liveid" + # config.liveid.user_info_mapping = {:username => "name"} + + + # --- user config --- + config.user_config do |user| + # -- core -- + # specify username attributes, for example: [:username, :email]. + # Default: `[:username]` + # + user.username_attribute_names = [:email] + + + # change *virtual* password attribute, the one which is used until an encrypted one is generated. + # Default: `:password` + # + # user.password_attribute_name = + + + # downcase the username before trying to authenticate, default is false + # Default: `false` + # + # user.downcase_username_before_authenticating = + + + # change default email attribute. + # Default: `:email` + # + # user.email_attribute_name = + + + # change default crypted_password attribute. + # Default: `:crypted_password` + # + # user.crypted_password_attribute_name = + + + # what pattern to use to join the password with the salt + # Default: `""` + # + # user.salt_join_token = + + + # change default salt attribute. + # Default: `:salt` + # + # user.salt_attribute_name = + + + # how many times to apply encryption to the password. + # Default: `nil` + # + # user.stretches = + + + # encryption key used to encrypt reversible encryptions such as AES256. + # WARNING: If used for users' passwords, changing this key will leave passwords undecryptable! + # Default: `nil` + # + # user.encryption_key = + + + # use an external encryption class. + # Default: `nil` + # + # user.custom_encryption_provider = + + + # encryption algorithm name. See 'encryption_algorithm=' for available options. + # Default: `:bcrypt` + # + # user.encryption_algorithm = + + + # make this configuration inheritable for subclasses. Useful for ActiveRecord's STI. + # Default: `false` + # + # user.subclasses_inherit_config = + + + # -- user_activation -- + # the attribute name to hold activation state (active/pending). + # Default: `:activation_state` + # + # user.activation_state_attribute_name = + + + # the attribute name to hold activation code (sent by email). + # Default: `:activation_token` + # + # user.activation_token_attribute_name = + + + # the attribute name to hold activation code expiration date. + # Default: `:activation_token_expires_at` + # + # user.activation_token_expires_at_attribute_name = + + + # how many seconds before the activation code expires. nil for never expires. + # Default: `nil` + # + # user.activation_token_expiration_period = + + + # your mailer class. Required. + # Default: `nil` + # + # user.user_activation_mailer = + + + # when true sorcery will not automatically + # email activation details and allow you to + # manually handle how and when email is sent. + # Default: `false` + # + # user.activation_mailer_disabled = + + + # activation needed email method on your mailer class. + # Default: `:activation_needed_email` + # + # user.activation_needed_email_method_name = + + + # activation success email method on your mailer class. + # Default: `:activation_success_email` + # + # user.activation_success_email_method_name = + + + # do you want to prevent or allow users that did not activate by email to login? + # Default: `true` + # + # user.prevent_non_active_users_to_login = + + + # -- reset_password -- + # reset password code attribute name. + # Default: `:reset_password_token` + # + # user.reset_password_token_attribute_name = + + + # expires at attribute name. + # Default: `:reset_password_token_expires_at` + # + # user.reset_password_token_expires_at_attribute_name = + + + # when was email sent, used for hammering protection. + # Default: `:reset_password_email_sent_at` + # + # user.reset_password_email_sent_at_attribute_name = + + + # mailer class. Needed. + # Default: `nil` + # + # user.reset_password_mailer = + + + # reset password email method on your mailer class. + # Default: `:reset_password_email` + # + # user.reset_password_email_method_name = + + + # when true sorcery will not automatically + # email password reset details and allow you to + # manually handle how and when email is sent + # Default: `false` + # + # user.reset_password_mailer_disabled = + + + # reset password email + # method on your mailer + # class. + # Default: `:reset_password_email` + # + # user.reset_password_email_method_name = + + + # how many seconds before the reset request expires. nil for never expires. + # Default: `nil` + # + # user.reset_password_expiration_period = + + + # hammering protection, how long to wait before allowing another email to be sent. + # Default: `5 * 60` + # + # user.reset_password_time_between_emails = + + + # -- brute_force_protection -- + # Failed logins attribute name. + # Default: `:failed_logins_count` + # + # user.failed_logins_count_attribute_name = + + + # This field indicates whether user is banned and when it will be active again. + # Default: `:lock_expires_at` + # + # user.lock_expires_at_attribute_name = + + + # How many failed logins allowed. + # Default: `50` + # + # user.consecutive_login_retries_amount_limit = + + + # How long the user should be banned. in seconds. 0 for permanent. + # Default: `60 * 60` + # + # user.login_lock_time_period = + + # Unlock token attribute name + # Default: `:unlock_token` + # + # user.unlock_token_attribute_name = + + # Unlock token mailer method + # Default: `:send_unlock_token_email` + # + # user.unlock_token_email_method_name = + + # when true sorcery will not automatically + # send email with unlock token + # Default: `false` + # + # user.unlock_token_mailer_disabled = true + + # Unlock token mailer class + # Default: `nil` + # + # user.unlock_token_mailer = UserMailer + + # -- activity logging -- + # Last login attribute name. + # Default: `:last_login_at` + # + # user.last_login_at_attribute_name = + + + # Last logout attribute name. + # Default: `:last_logout_at` + # + # user.last_logout_at_attribute_name = + + + # Last activity attribute name. + # Default: `:last_activity_at` + # + # user.last_activity_at_attribute_name = + + + # How long since last activity is he user defined logged out? + # Default: `10 * 60` + # + # user.activity_timeout = + + + # -- external -- + # Class which holds the various external provider data for this user. + # Default: `nil` + # + # user.authentications_class = + + + # User's identifier in authentications class. + # Default: `:user_id` + # + # user.authentications_user_id_attribute_name = + + + # Provider's identifier in authentications class. + # Default: `:provider` + # + # user.provider_attribute_name = + + + # User's external unique identifier in authentications class. + # Default: `:uid` + # + # user.provider_uid_attribute_name = + end + + # This line must come after the 'user config' block. + # Define which model authenticates with sorcery. + config.user_class = "User" +end diff --git a/config/routes.rb b/config/routes.rb index 21b5390..64f4f28 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,10 @@ Apollo::Application.routes.draw do + get "logout" => "sessions#destroy", :as => "logout" + get "login" => "sessions#new", :as => "login" + + resources :users + resources :sessions + get "posts/near" resources :feeds do diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml new file mode 100644 index 0000000..6c01ae1 --- /dev/null +++ b/test/fixtures/users.yml @@ -0,0 +1,11 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html + +one: + email: MyString + crypted_password: MyString + salt: MyString + +two: + email: MyString + crypted_password: MyString + salt: MyString diff --git a/test/functional/sessions_controller_test.rb b/test/functional/sessions_controller_test.rb new file mode 100644 index 0000000..75db968 --- /dev/null +++ b/test/functional/sessions_controller_test.rb @@ -0,0 +1,9 @@ +require 'test_helper' + +class SessionsControllerTest < ActionController::TestCase + test "should get new" do + get :new + assert_response :success + end + +end diff --git a/test/functional/users_controller_test.rb b/test/functional/users_controller_test.rb new file mode 100644 index 0000000..ca4cd29 --- /dev/null +++ b/test/functional/users_controller_test.rb @@ -0,0 +1,49 @@ +require 'test_helper' + +class UsersControllerTest < ActionController::TestCase + setup do + @user = users(:one) + end + + test "should get index" do + get :index + assert_response :success + assert_not_nil assigns(:users) + end + + test "should get new" do + get :new + assert_response :success + end + + test "should create user" do + assert_difference('User.count') do + post :create, user: { crypted_password: @user.crypted_password, email: @user.email, salt: @user.salt } + end + + assert_redirected_to user_path(assigns(:user)) + end + + test "should show user" do + get :show, id: @user + assert_response :success + end + + test "should get edit" do + get :edit, id: @user + assert_response :success + end + + test "should update user" do + put :update, id: @user, user: { crypted_password: @user.crypted_password, email: @user.email, salt: @user.salt } + assert_redirected_to user_path(assigns(:user)) + end + + test "should destroy user" do + assert_difference('User.count', -1) do + delete :destroy, id: @user + end + + assert_redirected_to users_path + end +end diff --git a/test/unit/helpers/sessions_helper_test.rb b/test/unit/helpers/sessions_helper_test.rb new file mode 100644 index 0000000..7d44e09 --- /dev/null +++ b/test/unit/helpers/sessions_helper_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class SessionsHelperTest < ActionView::TestCase +end diff --git a/test/unit/helpers/users_helper_test.rb b/test/unit/helpers/users_helper_test.rb new file mode 100644 index 0000000..96af37a --- /dev/null +++ b/test/unit/helpers/users_helper_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class UsersHelperTest < ActionView::TestCase +end diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb new file mode 100644 index 0000000..82f61e0 --- /dev/null +++ b/test/unit/user_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class UserTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end