Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@

# Ignore master key for decrypting credentials and more.
/config/master.key
/config/credentials/development.key
/config/credentials/production.key
7 changes: 6 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ gem 'puma', '~> 4.1'

gem 'grape'
gem 'grape_on_rails_routes'
gem 'grape-route-helpers'
gem 'grape-swagger'

# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'
gem 'bcrypt', '~> 3.1.7'

# Use Active Storage variant
# gem 'image_processing', '~> 1.2'
Expand All @@ -43,5 +44,9 @@ group :development do
gem 'spring-watcher-listen', '~> 2.0.0'
end

group :test do
gem 'factory_bot_rails'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
13 changes: 13 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ GEM
tzinfo (~> 1.1)
zeitwerk (~> 2.2, >= 2.2.2)
ast (2.4.1)
bcrypt (3.1.15)
bootsnap (1.4.8)
msgpack (~> 1.0)
builder (3.2.4)
Expand Down Expand Up @@ -86,6 +87,11 @@ GEM
dry-inflector (~> 0.1, >= 0.1.2)
dry-logic (~> 1.0, >= 1.0.2)
erubi (1.9.0)
factory_bot (6.1.0)
activesupport (>= 5.0.0)
factory_bot_rails (6.1.0)
factory_bot (~> 6.1.0)
railties (>= 5.0.0)
ffi (1.13.1)
globalid (0.4.2)
activesupport (>= 4.2.0)
Expand All @@ -96,6 +102,10 @@ GEM
mustermann-grape (~> 1.0.0)
rack (>= 1.3.0)
rack-accept
grape-route-helpers (2.1.0)
activesupport
grape (>= 0.16.0)
rake
grape-swagger (1.2.1)
grape (~> 1.3)
grape_on_rails_routes (0.3.2)
Expand Down Expand Up @@ -213,9 +223,12 @@ PLATFORMS
ruby

DEPENDENCIES
bcrypt (~> 3.1.7)
bootsnap (>= 1.4.2)
byebug
factory_bot_rails
grape
grape-route-helpers
grape-swagger
grape_on_rails_routes
listen (~> 3.2)
Expand Down
24 changes: 24 additions & 0 deletions app/api/api/auth.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module API
class Auth < Grape::API
namespace :session do
desc 'Acquires a session cookie from a username and password'
params do
requires :username, type: String
requires :password, type: String
end
post auth: false do
session.destroy

user = User.find_by(email: params[:username])

error! :not_found, 404 unless user.try(:authenticate, params[:password])

session[:user_id] = user.id
cookies[:_csrf_token] = session[:csrf_token] = SecureRandom.base64(32)

status :ok
{}
end
end
end
end
7 changes: 7 additions & 0 deletions app/api/api_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module APIHelpers
extend Grape::API::Helpers

def session
env['rack.session']
end
end
5 changes: 5 additions & 0 deletions app/api/api_routes.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
class APIRoutes < Grape::API
use ActionDispatch::Session::CookieStore, key: '_sbhacks_session'
helpers APIHelpers
format :json
auth :from_session

mount API::Auth
end
23 changes: 23 additions & 0 deletions app/lib/auth/session_auth_middleware.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module Auth
class SessionAuthMiddleware < Grape::Middleware::Auth::Base
def call(env)
self.env = env

if context.route.options[:auth] != false
throw(:error, status: 401, message: 'Unauthorized') unless context.session[:user_id]
throw(:error, status: 403, message: 'Forbidden') unless verified_request?
end

@app.call(env)
end

private

def verified_request?
return true if Rails.env.development? && env['HTTP_X_IGNORE_CSRF']

method = context.route.options[:method]
method == 'GET' || method == 'HEAD' || context.session[:csrf_token] == env['HTTP_X_CSRF_TOKEN']
end
end
end
10 changes: 10 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class User < ApplicationRecord
has_secure_password

validates :name, presence: true
validates :email, format: {
with: URI::MailTo::EMAIL_REGEXP,
message: 'is not a valid email address'
}
validates :password, length: { in: 8..72 }
end
4 changes: 4 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,9 @@ class Application < Rails::Application
# Middleware like session, flash, cookies can be added back manually.
# Skip views, helpers and assets when generating a new resource.
config.api_only = true

config.generators do |g|
g.test_framework :test_unit, fixture: false
end
end
end
1 change: 1 addition & 0 deletions config/credentials/development.yml.enc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hAbLzw==--Izatsmxe7g0ccMzf--NlgK4PY2R7zLYMwSPx4Yeg==
1 change: 1 addition & 0 deletions config/credentials/production.yml.enc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DClZHkmJZpADs4292n3axT+PHxN5YgAXk7CUqJEiQlW9+BfPJjeCH6IgLVI13ERsPqjE5HLwPwkewSbjw2pzwTDoZ6IbLbJFCCE5TSMnDvfOo8mjzaFoqMyFSouUN5BRinqJlbm7tLIuWauoH5UjE2LmWN6n7hr8iO6dchDzyWyhDJ+sH2xqaiDrlIgTeJGbxhSEOg4dHw==--I5PVRTkkRL16keFX--RS9Oq7j9pwZalWyU/Q26lw==
4 changes: 4 additions & 0 deletions config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!

# Tell Ruby to shut up about deprecation warnings during tests
# https://fuzzyblog.io/blog/rails/2020/01/28/turning-off-ruby-deprecation-warnings-when-running-tests.html
$VERBOSE = nil

Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.

Expand Down
3 changes: 3 additions & 0 deletions config/initializers/cookies.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Be sure to restart your server when you modify this file.

Rails.application.config.middleware.use ActionDispatch::Cookies
5 changes: 5 additions & 0 deletions config/initializers/grape.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Be sure to restart your server when you modify this file.

Rails.configuration.to_prepare do
Grape::Middleware::Auth::Strategies.add(:from_session, Auth::SessionAuthMiddleware)
end
3 changes: 3 additions & 0 deletions config/initializers/session_store.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Be sure to restart your server when you modify this file.

Rails.application.config.middleware.use ActionDispatch::Session::CookieStore, key: '_sbhacks_session'
11 changes: 11 additions & 0 deletions db/migrate/20200824035326_create_users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :name, nil: false
t.string :email, index: { unique: true }, nil: false
t.string :password_digest, nil: false

t.timestamps
end
end
end
11 changes: 10 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,18 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 0) do
ActiveRecord::Schema.define(version: 2020_08_24_035326) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"

create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.string "password_digest"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["email"], name: "index_users_on_email", unique: true
end

end
12 changes: 5 additions & 7 deletions db/seeds.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
#
# Examples:
#
# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
# Character.create(name: 'Luke', movie: movies.first)
User.create!(
name: 'SBHacks Admin',
email: '[email protected]',
password: 'password'
)
17 changes: 17 additions & 0 deletions test/api/auth_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require 'test_helper'

class AuthTest < ActionDispatch::IntegrationTest
test 'signing in sets the session id' do
user = sign_in_as(:user)

assert_equal user.id, @request.session[:user_id]
end

test 'signing in with invalid credentials clears the session' do
sign_in_as(:user)

post session_path, params: { username: 'nonexistent user', password: '' }

assert_nil @request.session[:user_id]
end
end
9 changes: 9 additions & 0 deletions test/factories/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FactoryBot.define do
factory :user do
name { 'Akshay Heda' }
sequence :email do |n|
"akshay.#{n}@example.com"
end
password { 'ilovesbhacks' }
end
end
Empty file removed test/fixtures/.keep
Empty file.
Empty file removed test/fixtures/files/.keep
Empty file.
31 changes: 31 additions & 0 deletions test/models/user_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require 'test_helper'

class UserTest < ActiveSupport::TestCase
test 'factory is valid' do
assert_predicate build(:user), :valid?
end

test 'requires a name' do
user = build(:user)
user.name = nil
assert_not_predicate user, :valid?

user.name = ''
assert_not_predicate user, :valid?
end

test 'requires a valid email address' do
user = build(:user)
user.email = 'not an email'
assert_not_predicate user, :valid?
end

test 'requires a password' do
user = build(:user)
user.password = nil
assert_not_predicate user, :valid?

user.password = 'short'
assert_not_predicate user, :valid?
end
end
7 changes: 4 additions & 3 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
ENV['RAILS_ENV'] ||= 'test'
require_relative '../config/environment'
require 'rails/test_help'
require 'test_helpers/sign_in_helper'

module ActiveSupport
class TestCase
include GrapeRouteHelpers::NamedRouteMatcher
include FactoryBot::Syntax::Methods
include SignInHelper
# Run tests in parallel with specified workers
parallelize(workers: :number_of_processors)

# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all

# Add more helper methods to be used by all tests here...
end
end
9 changes: 9 additions & 0 deletions test/test_helpers/sign_in_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module SignInHelper
def sign_in_as(user)
user = create(user) if user.is_a? Symbol

post session_path, params: { username: user.email, password: user.password }

user
end
end