diff --git a/.rubocop.yml b/.rubocop.yml index d41f566..d801f02 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -4,36 +4,38 @@ AllCops: - db/seeds.rb - bin/**/* -Style/StringLiterals: - Enabled: false +Style/ClassAndModuleChildren: + Exclude: + - test/test_helper.rb Style/Documentation: Enabled: false -Style/MethodDefParentheses: - Enabled: false +Style/EmptyLines: + Exclude: + - config/initializers/sorcery.rb Style/IndentationConsistency: EnforcedStyle: rails +Style/MethodDefParentheses: + Enabled: false + +Style/StringLiterals: + Enabled: false + +Metrics/AbcSize: + Exclude: + - db/migrate/**/* + Metrics/LineLength: Max: 95 - -Metrics/MethodLength: - Exclude: - - db/migrate/* - -Metrics/LineLength: Exclude: - Rakefile - config/**/* - lib/tasks/**/* - test/test_helper.rb -Metrics/AbcSize: +Metrics/MethodLength: Exclude: - - db/migrate/**/* - -Style/ClassAndModuleChildren: - Exclude: - - test/test_helper.rb + - db/migrate/* diff --git a/Gemfile b/Gemfile index b084b73..22ba1ac 100644 --- a/Gemfile +++ b/Gemfile @@ -15,10 +15,10 @@ gem 'json', '~> 1.8.3' gem 'mysql2', '~> 0.3.20' gem 'responders', '~> 2.1.0' gem 'sass-rails', '~> 5.0' +gem 'sorcery', '~> 0.9.1' gem 'twilio-ruby', '~> 4.3.0' gem 'uglifier', '>= 1.3.0' -# gem 'sorcery' # gem 'faraday' # gem 'faraday_middleware' # gem 'rack-protection', '~> 1.5.3' diff --git a/Gemfile.lock b/Gemfile.lock index df3c315..60f80dc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -69,6 +69,8 @@ GEM erubis (2.7.0) eventmachine (1.0.8) execjs (2.6.0) + faraday (0.9.1) + multipart-post (>= 1.2, < 3) ffi (1.9.10) figaro (1.1.1) thor (~> 0.14) @@ -144,6 +146,8 @@ GEM minitest (>= 5.0) ruby-progressbar multi_json (1.11.2) + multi_xml (0.5.5) + multipart-post (2.0.0) mysql2 (0.3.20) nenv (0.2.0) nokogiri (1.6.6.2) @@ -151,6 +155,13 @@ GEM notiffany (0.0.7) nenv (~> 0.1) shellany (~> 0.0) + oauth (0.4.7) + oauth2 (1.0.0) + faraday (>= 0.8, < 0.10) + jwt (~> 1.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (~> 1.2) parser (2.2.2.6) ast (>= 1.1, < 3.0) powerpack (0.1.1) @@ -221,6 +232,10 @@ GEM sexp_processor (4.5.1) shellany (0.0.1) slop (3.6.0) + sorcery (0.9.1) + bcrypt (~> 3.1) + oauth (~> 0.4, >= 0.4.4) + oauth2 (>= 0.8.0) spring (1.3.6) sprockets (3.3.4) rack (~> 1.0) @@ -282,6 +297,7 @@ DEPENDENCIES responders (~> 2.1.0) rubocop sass-rails (~> 5.0) + sorcery (~> 0.9.1) spring thin (~> 1.6.3) turbolinks diff --git a/README.md b/README.md index 73b3919..b32cfe2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SMS Pager -A simple api to send sms messages with [twillio](https://www.twilio.com/). +A simple app to send sms messages with [twillio](https://www.twilio.com/). ## Configure diff --git a/app/assets/javascripts/oauths.js b/app/assets/javascripts/oauths.js new file mode 100644 index 0000000..dee720f --- /dev/null +++ b/app/assets/javascripts/oauths.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/stylesheets/oauths.scss b/app/assets/stylesheets/oauths.scss new file mode 100644 index 0000000..c879339 --- /dev/null +++ b/app/assets/stylesheets/oauths.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Oauths 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/organisms/forms.scss b/app/assets/stylesheets/organisms/forms.scss index e96dc06..d2c7bc5 100644 --- a/app/assets/stylesheets/organisms/forms.scss +++ b/app/assets/stylesheets/organisms/forms.scss @@ -31,7 +31,7 @@ form { } fieldset { - margin: 15px; border: 0; + margin: 15px; } } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d8f5ed6..4133060 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -4,10 +4,4 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception respond_to :html, :json - - def current_user - # temp - Person.new(id: 9999) - end - helper_method :current_user end diff --git a/app/controllers/oauths_controller.rb b/app/controllers/oauths_controller.rb new file mode 100644 index 0000000..bed511a --- /dev/null +++ b/app/controllers/oauths_controller.rb @@ -0,0 +1,34 @@ +class OauthsController < ApplicationController + skip_before_filter :require_login + + def oauth + login_at(params[:provider]) + end + + def callback + provider = params[:provider] + @user = login_from(provider) + + if @user + redirect_to root_path, notice: "Logged in from #{provider.titleize}!" + else + begin + @user = create_from(provider) + # NOTE: this is the place to add '@user.activate!' + # if you are using user_activation submodule + + reset_session # protect from session fixation attack + auto_login(@user) + redirect_to root_path, notice: "Logged in from #{provider.titleize}!" + rescue + redirect_to root_path, alert: "Failed to login from #{provider.titleize}!" + end + end + end + + private + + def auth_params + params.permit(:code, :provider) + end +end diff --git a/app/helpers/oauths_helper.rb b/app/helpers/oauths_helper.rb new file mode 100644 index 0000000..d6a2251 --- /dev/null +++ b/app/helpers/oauths_helper.rb @@ -0,0 +1,2 @@ +module OauthsHelper +end diff --git a/app/models/authentication.rb b/app/models/authentication.rb new file mode 100644 index 0000000..69a2df1 --- /dev/null +++ b/app/models/authentication.rb @@ -0,0 +1,3 @@ +class Authentication < ActiveRecord::Base + belongs_to :user +end diff --git a/app/models/person.rb b/app/models/person.rb index 07624de..7f2a5bf 100644 --- a/app/models/person.rb +++ b/app/models/person.rb @@ -1,6 +1,9 @@ class Person < ActiveRecord::Base + authenticates_with_sorcery! has_many :parenthoods has_many :children, through: :parenthoods + has_many :authentications, dependent: :destroy + accepts_nested_attributes_for :authentications accepts_nested_attributes_for :children, reject_if: :all_blank validates :first_name, presence: true @@ -30,4 +33,10 @@ class Person < ActiveRecord::Base def to_i id end + + private + + ## SorceryCore expects the model to hold a crypted_password field + ## Since we are only using external oAuth providers, faking this one out. + def crypted_password; end end diff --git a/app/views/docs/index.html.haml b/app/views/docs/index.html.haml index c5b419d..25e9fdc 100644 --- a/app/views/docs/index.html.haml +++ b/app/views/docs/index.html.haml @@ -1,2 +1,4 @@ -%h2 Something helpful later +%p= link_to 'Login with Google', auth_at_provider_path(provider: :google) + +%h2 Something more helpful later %p= raw(ap @doc) diff --git a/app/views/oauths/_sub_nav.html.haml b/app/views/oauths/_sub_nav.html.haml new file mode 100644 index 0000000..e69de29 diff --git a/app/views/oauths/callback.html.haml b/app/views/oauths/callback.html.haml new file mode 100644 index 0000000..425fb4c --- /dev/null +++ b/app/views/oauths/callback.html.haml @@ -0,0 +1,2 @@ +%h1 Oauths#callback +%p Find me in app/views/oauths/callback.html.haml diff --git a/app/views/oauths/oauth.html.haml b/app/views/oauths/oauth.html.haml new file mode 100644 index 0000000..a694b6c --- /dev/null +++ b/app/views/oauths/oauth.html.haml @@ -0,0 +1,2 @@ +%h1 Oauths#oauth +%p Find me in app/views/oauths/oauth.html.haml diff --git a/config/initializers/sorcery.rb b/config/initializers/sorcery.rb new file mode 100644 index 0000000..1ddfc04 --- /dev/null +++ b/config/initializers/sorcery.rb @@ -0,0 +1,463 @@ +# 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 = [:external, :user_activation] + +# 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 = + + + # Allow the remember_me cookie to be set through AJAX + # Default: `true` + # + # config.remember_me_httponly = + + + # -- 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, :linkedin, :xing, :google, :liveid, :salesforce] . + # Default: `[]` + # + config.external_providers = [:google] + + + # 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 = + + + # For information about LinkedIn API: + # - user info fields go to https://developer.linkedin.com/documents/profile-fields + # - access permissions go to https://developer.linkedin.com/documents/authentication#granting + # + # config.linkedin.key = "" + # config.linkedin.secret = "" + # config.linkedin.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=linkedin" + # config.linkedin.user_info_fields = ['first-name', 'last-name'] + # config.linkedin.user_info_mapping = {first_name: "firstName", last_name: "lastName"} + # config.linkedin.access_permissions = ['r_basicprofile'] + # + # + # For information about XING API: + # - user info fields go to https://dev.xing.com/docs/get/users/me + # + # config.xing.key = "" + # config.xing.secret = "" + # config.xing.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=xing" + # config.xing.user_info_mapping = {first_name: "first_name", last_name: "last_name"} + # + # + # Twitter will 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.facebook.access_permissions = ["email", "publish_actions"] + # config.facebook.display = "page" + # config.facebook.api_version = "v2.2" + # + # 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 = ENV["google_key"] + config.google.secret = ENV["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" } + + # config.vk.key = "" + # config.vk.secret = "" + # config.vk.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=vk" + # config.vk.user_info_mapping = {:login => "domain", :name => "full_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"} + + # For information about JIRA API: + # https://developer.atlassian.com/display/JIRADEV/JIRA+REST+API+Example+-+OAuth+authentication + # to obtain the consumer key and the public key you can use the jira-ruby gem https://github.com/sumoheavy/jira-ruby + # or run openssl req -x509 -nodes -newkey rsa:1024 -sha1 -keyout rsakey.pem -out rsacert.pem to obtain the public key + # Make sure you have configured the application link properly + + # config.jira.key = "1234567" + # config.jira.secret = "jiraTest" + # config.jira.site = "http://localhost:2990/jira/plugins/servlet/oauth" + # config.jira.signature_method = "RSA-SHA1" + # config.jira.private_key_file = "rsakey.pem" + + # For information about Salesforce API: + # https://developer.salesforce.com/signup & + # https://www.salesforce.com/us/developer/docs/api_rest/ + # Salesforce callback_url must be https. You can run the following to generate self-signed ssl cert + # openssl req -new -newkey rsa:2048 -sha1 -days 365 -nodes -x509 -keyout server.key -out server.crt + # Make sure you have configured the application link properly + # config.salesforce.key = '123123' + # config.salesforce.secret = 'acb123' + # config.salesforce.callback_url = "https://127.0.0.1:9292/oauth/callback?provider=salesforce" + # config.salesforce.scope = "full" + # config.salesforce.user_info_mapping = {:email => "email"} + + # --- user config --- + config.user_config do |user| + # -- core -- + # specify username attributes, for example: [:username, :email]. + # Default: `[:email]` + # + # user.username_attribute_names = + + + # 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 = + + + # -- remember_me -- + # How long in seconds the session length will be + # Default: `604800` + # + # user.remember_me_for = + + + # -- 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 = 24.hours.seconds + + + # 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 = true + + + # 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 = true + + + # -- 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 = + + + # how many seconds before the reset request expires. nil for never expires. + # Default: `nil` + # + # user.reset_password_expiration_period = + + + # hammering protection, how long in seconds 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 the 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 = Authentication + + + # User's identifier in authentications class. + # Default: `:user_id` + # + user.authentications_user_id_attribute_name = :person_id + + + # 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 = "Person" +end diff --git a/config/routes.rb b/config/routes.rb index 1fcd5ea..fd4e26b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,8 @@ Rails.application.routes.draw do + post 'oauth/callback', to: 'oauths#callback' + get 'oauth/callback', to: 'oauths#callback' + get 'oauth/:provider', to: 'oauths#oauth', as: :auth_at_provider + get 'parents', to: 'parents#index', as: :list_parents get 'parent/new', to: 'parents#new', as: :new_parent post 'parent/new', to: 'parents#add', as: :add_parent diff --git a/db/migrate/20151008022535_sorcery_init.rb b/db/migrate/20151008022535_sorcery_init.rb new file mode 100644 index 0000000..92fdc21 --- /dev/null +++ b/db/migrate/20151008022535_sorcery_init.rb @@ -0,0 +1,19 @@ +class SorceryInit < ActiveRecord::Migration + def change + change_column :people, :email, :string, null: false + add_index :people, :email, unique: true + add_column :people, :activation_state, :string, default: nil + add_column :people, :activation_token, :string, default: nil + add_column :people, :activation_token_expires_at, :datetime, default: nil + + add_index :people, :activation_token + + create_table :authentications do |t| + t.integer :person_id, null: false + t.string :provider, :uid, null: false + + t.timestamps + end + add_index :authentications, [:provider, :uid] + end +end diff --git a/db/schema.rb b/db/schema.rb index 12333c0..885630b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,17 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150904033833) do +ActiveRecord::Schema.define(version: 20151008022535) do + + create_table "authentications", force: :cascade do |t| + t.integer "person_id", limit: 4, null: false + t.string "provider", limit: 255, null: false + t.string "uid", limit: 255, null: false + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "authentications", ["provider", "uid"], name: "index_authentications_on_provider_and_uid", using: :btree create_table "children", force: :cascade do |t| t.string "first_name", limit: 255 @@ -36,14 +46,19 @@ ActiveRecord::Schema.define(version: 20150904033833) do add_index "parenthoods", ["person_id", "child_id"], name: "parentship", using: :btree create_table "people", force: :cascade do |t| - t.string "first_name", limit: 255 - t.string "last_name", limit: 255 - t.string "phone", limit: 255 - t.string "email", limit: 255 - t.boolean "admin" - t.boolean "staff" + t.string "first_name", limit: 255 + t.string "last_name", limit: 255 + t.string "phone", limit: 255 + t.string "email", limit: 255, null: false + t.boolean "admin" + t.boolean "staff" + t.string "activation_state", limit: 255 + t.string "activation_token", limit: 255 + t.datetime "activation_token_expires_at" end + add_index "people", ["activation_token"], name: "index_people_on_activation_token", using: :btree + add_index "people", ["email"], name: "index_people_on_email", unique: true, using: :btree add_index "people", ["phone"], name: "index_people_on_phone", using: :btree end diff --git a/test/controllers/oauths_controller_test.rb b/test/controllers/oauths_controller_test.rb new file mode 100644 index 0000000..4a6d8ff --- /dev/null +++ b/test/controllers/oauths_controller_test.rb @@ -0,0 +1,13 @@ +require 'test_helper' + +class OauthsControllerTest < ActionController::TestCase + # test "should get oauth" do + # get :oauth + # assert_response :success + # end + # + # test "should get callback" do + # get :callback + # assert_response :success + # end +end diff --git a/test/fixtures/person.yml b/test/fixtures/people.yml similarity index 95% rename from test/fixtures/person.yml rename to test/fixtures/people.yml index e0a2f34..a40dab2 100644 --- a/test/fixtures/person.yml +++ b/test/fixtures/people.yml @@ -39,6 +39,6 @@ marlin: sarah: first_name: Sarah last_name: Smith - email: marlin.smith@mailinator.com + email: sarah.smith@mailinator.com phone: 5005550006 children: sally, nemo diff --git a/test/models/authentication_test.rb b/test/models/authentication_test.rb new file mode 100644 index 0000000..f01e61d --- /dev/null +++ b/test/models/authentication_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class AuthenticationTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end