diff --git a/app/models/attachment.rb b/app/models/attachment.rb new file mode 100644 index 00000000..9c65cec1 --- /dev/null +++ b/app/models/attachment.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true +class Attachment < ApplicationRecord + belongs_to :attachable, polymorphic: true + + has_attached_file :file, + styles: lambda { |a| + if a.instance.image? + { + thumb: 'x128#', + medium: 'x320>' + } + else + {} + end + } + + validates_attachment_content_type :file, content_type: Attachable.allowed_types + + def image? + Attachable.image_types.include?(file.instance.file_content_type) + end + + def audio? + Attachable.audio_types.include?(file.instance.file_content_type) + end + + def text? + Attachable.text_types.include?(file.instance.file_content_type) + end + + def pdf? + Attachable.pdf_types.include?(file.instance.file_content_type) + end + + def document? + text? || pdf? + end +end diff --git a/app/models/concerns/attachable.rb b/app/models/concerns/attachable.rb new file mode 100644 index 00000000..0a5a6b12 --- /dev/null +++ b/app/models/concerns/attachable.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true +module Attachable + extend ActiveSupport::Concern + + included do + has_many :attachments, as: :attachable, dependent: :destroy + end + + def images + attachments.where(file_content_type: image_types) + end + + def audios + attachments.where(file_content_type: audio_types) + end + + def texts + attachments.where(file_content_type: text_types) + end + + def pdfs + attachments.where(file_content_type: pdf_types) + end + + def documents + attachments.where(file_content_type: text_types + pdf_types) + end + + class << self + def image_types + ['image/png', 'image/gif', 'image/jpeg'] + end + + def audio_types + ['audio/ogg', 'audio/mp3'] + end + + def text_types + ['text/plain'] + end + + def pdf_types + ['application/pdf'] + end + + def allowed_types + image_types + audio_types + text_types + pdf_types + end + end +end diff --git a/app/models/topic.rb b/app/models/topic.rb index 69587135..f1c221f0 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Topic < ApplicationRecord include TopicsHelper + include Attachable belongs_to :user belongs_to :defer_to_map, class_name: 'Map', foreign_key: 'defer_to_map_id' @@ -21,23 +22,6 @@ class Topic < ApplicationRecord validates :permission, presence: true validates :permission, inclusion: { in: Perm::ISSIONS.map(&:to_s) } - # This method associates the attribute ":image" with a file attachment - has_attached_file :image - - # , styles: { - # thumb: '100x100>', - # square: '200x200#', - # medium: '300x300>' - # } - - # Validate the attached image is image/jpg, image/png, etc - validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/ - - # This method associates the attribute ":image" with a file attachment - has_attached_file :audio - # Validate the attached audio is audio/wav, audio/mp3, etc - validates_attachment_content_type :audio, content_type: /\Aaudio\/.*\Z/ - def synapses synapses1.or(synapses2) end diff --git a/config/environments/development.rb b/config/environments/development.rb index 8fef2145..381bca7b 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -29,4 +29,7 @@ Rails.application.configure do # Expands the lines which load the assets config.assets.debug = false config.assets.quiet = true + + # S3 file storage + config.paperclip_defaults = {} # store on local machine for dev end diff --git a/config/initializers/rack-attack.rb b/config/initializers/rack-attack.rb deleted file mode 100644 index 0fd76889..00000000 --- a/config/initializers/rack-attack.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true -class Rack::Attack - Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new - - # Throttle all requests by IP (60rpm) - # - # Key: "rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}" - # throttle('req/ip', :limit => 300, :period => 5.minutes) do |req| - # req.ip # unless req.path.start_with?('/assets') - # end - - # Throttle POST requests to /login by IP address - # - # Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}" - throttle('logins/ip', limit: 5, period: 20.seconds) do |req| - req.ip if req.path == '/login' && req.post? - end - - # Throttle POST requests to /login by email param - # - # Key: "rack::attack:#{Time.now.to_i/:period}:logins/email:#{req.email}" - # - # Note: This creates a problem where a malicious user could intentionally - # throttle logins for another user and force their login requests to be - # denied, but that's not very common and shouldn't happen to you. (Knock - # on wood!) - throttle('logins/email', limit: 5, period: 20.seconds) do |req| - if req.path == '/login' && req.post? - # return the email if present, nil otherwise - req.params['email'].presence - end - end - - throttle('load_url_title/req/5mins/ip', limit: 300, period: 5.minutes) do |req| - req.ip if req.path == 'hacks/load_url_title' - end - throttle('load_url_title/req/1s/ip', limit: 5, period: 1.second) do |req| - # If the return value is truthy, the cache key for the return value - # is incremented and compared with the limit. In this case: - # "rack::attack:#{Time.now.to_i/1.second}:load_url_title/req/ip:#{req.ip}" - # - # If falsy, the cache key is neither incremented nor checked. - - req.ip if req.path == 'hacks/load_url_title' - end - - self.throttled_response = lambda do |env| - now = Time.now - match_data = env['rack.attack.match_data'] - period = match_data[:period] - limit = match_data[:limit] - - headers = { - 'X-RateLimit-Limit' => limit.to_s, - 'X-RateLimit-Remaining' => '0', - 'X-RateLimit-Reset' => (now + (period - now.to_i % period)).to_s - } - - [429, headers, ['']] - end -end diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb new file mode 100644 index 00000000..b79f0d10 --- /dev/null +++ b/config/initializers/rack_attack.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true +module Rack + class Attack + Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + + # Throttle all requests by IP (60rpm) + # + # Key: "rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}" + # throttle('req/ip', :limit => 300, :period => 5.minutes) do |req| + # req.ip # unless req.path.start_with?('/assets') + # end + + # Throttle POST requests to /login by IP address + # + # Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}" + throttle('logins/ip', limit: 5, period: 20.seconds) do |req| + req.ip if req.path == '/login' && req.post? + end + + # Throttle POST requests to /login by email param + # + # Key: "rack::attack:#{Time.now.to_i/:period}:logins/email:#{req.email}" + # + # Note: This creates a problem where a malicious user could intentionally + # throttle logins for another user and force their login requests to be + # denied, but that's not very common and shouldn't happen to you. (Knock + # on wood!) + throttle('logins/email', limit: 5, period: 20.seconds) do |req| + if req.path == '/login' && req.post? + # return the email if present, nil otherwise + req.params['email'].presence + end + end + + throttle('load_url_title/req/5mins/ip', limit: 300, period: 5.minutes) do |req| + req.ip if req.path == 'hacks/load_url_title' + end + throttle('load_url_title/req/1s/ip', limit: 5, period: 1.second) do |req| + # If the return value is truthy, the cache key for the return value + # is incremented and compared with the limit. In this case: + # "rack::attack:#{Time.now.to_i/1.second}:load_url_title/req/ip:#{req.ip}" + # + # If falsy, the cache key is neither incremented nor checked. + + req.ip if req.path == 'hacks/load_url_title' + end + + self.throttled_response = lambda do |env| + now = Time.zone.now + match_data = env['rack.attack.match_data'] + period = match_data[:period] + limit = match_data[:limit] + + headers = { + 'X-RateLimit-Limit' => limit.to_s, + 'X-RateLimit-Remaining' => '0', + 'X-RateLimit-Reset' => (now + (period - now.to_i % period)).to_s + } + + [429, headers, ['']] + end + end +end diff --git a/config/initializers/warden_hooks.rb b/config/initializers/warden_hooks.rb index da983955..a0d8fd88 100644 --- a/config/initializers/warden_hooks.rb +++ b/config/initializers/warden_hooks.rb @@ -1,9 +1,10 @@ -Warden::Manager.after_set_user do |user,auth,opts| +# frozen_string_literal: true +Warden::Manager.after_set_user do |user, auth, opts| scope = opts[:scope] auth.cookies.signed["#{scope}.id"] = user.id auth.cookies.signed["#{scope}.expires_at"] = 30.minutes.from_now end -Warden::Manager.before_logout do |user, auth, opts| +Warden::Manager.before_logout do |_user, auth, opts| scope = opts[:scope] auth.cookies.signed["#{scope}.id"] = nil auth.cookies.signed["#{scope}.expires_at"] = nil diff --git a/db/migrate/20170122201451_create_attachments.rb b/db/migrate/20170122201451_create_attachments.rb new file mode 100644 index 00000000..a798fcfd --- /dev/null +++ b/db/migrate/20170122201451_create_attachments.rb @@ -0,0 +1,12 @@ +class CreateAttachments < ActiveRecord::Migration[5.0] + def change + create_table :attachments do |t| + t.references :attachable, polymorphic: true + t.attachment :file + t.timestamps + end + + remove_attachment :topics, :image + remove_attachment :topics, :audio + end +end diff --git a/db/schema.rb b/db/schema.rb index 7d146be5..a14b6715 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20161218183817) do +ActiveRecord::Schema.define(version: 20170122201451) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -26,6 +26,18 @@ ActiveRecord::Schema.define(version: 20161218183817) do t.index ["user_id"], name: "index_access_requests_on_user_id", using: :btree end + create_table "attachments", force: :cascade do |t| + t.string "attachable_type" + t.integer "attachable_id" + t.string "file_file_name" + t.string "file_content_type" + t.integer "file_file_size" + t.datetime "file_updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["attachable_type", "attachable_id"], name: "index_attachments_on_attachable_type_and_attachable_id", using: :btree + end + create_table "delayed_jobs", force: :cascade do |t| t.integer "priority", default: 0, null: false t.integer "attempts", default: 0, null: false @@ -269,17 +281,9 @@ ActiveRecord::Schema.define(version: 20161218183817) do t.text "link" t.integer "user_id" t.integer "metacode_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.text "permission" - t.string "image_file_name", limit: 255 - t.string "image_content_type", limit: 255 - t.integer "image_file_size" - t.datetime "image_updated_at" - t.string "audio_file_name", limit: 255 - t.string "audio_content_type", limit: 255 - t.integer "audio_file_size" - t.datetime "audio_updated_at" t.integer "defer_to_map_id" t.index ["metacode_id"], name: "index_topics_on_metacode_id", using: :btree t.index ["user_id"], name: "index_topics_on_user_id", using: :btree