diff --git a/Gemfile b/Gemfile index 2b9b7b6..13c0e1a 100644 --- a/Gemfile +++ b/Gemfile @@ -13,16 +13,18 @@ gem 'dotenv-rails' # DB gem 'mysql2' gem 'dalli' +gem 'connection_pool' # Needed for MT # Web server gem 'faraday' gem 'puma' -gem 'unicorn' +# gem 'unicorn' # Model plugins gem 'unread' gem 'scrypt' -# gem 'impressionist' +gem 'active_flag' +# gem 'impressionist # gem 'ratyrate' # gem "acts_as_rateable", :git => "git://github.com/anton-zaytsev/acts_as_rateable.git" @@ -30,6 +32,13 @@ gem 'scrypt' gem 'google-api-client', '~> 0.10.3' gem 'steam-condenser', github: 'koraktor/steam-condenser-ruby' +# Auth +gem 'omniauth' +gem 'omniauth-steam' +gem 'omniauth-rails_csrf_protection' +# FIXME +# gem 'rails_csrf_protection' + # View and model helper gems gem 'time_difference' gem 'public_suffix' @@ -133,6 +142,7 @@ group :development, :test do gem 'pry-byebug' gem 'spring' gem "rails_best_practices" + gem 'awesome_print' # For n+1 uqeries # gem 'bullet' end diff --git a/Gemfile.lock b/Gemfile.lock index 763ce45..8f23815 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -87,6 +87,8 @@ GEM erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) + active_flag (1.5.0) + activerecord (>= 5) active_link_to (1.0.5) actionpack addressable @@ -119,6 +121,7 @@ GEM archive-zip (0.12.0) io-like (~> 0.3.0) ast (2.4.0) + awesome_print (1.8.0) bbcoder (1.1.1) better_errors (2.6.0) coderay (>= 1.0.0) @@ -163,6 +166,7 @@ GEM execjs coffee-script-source (1.12.2) concurrent-ruby (1.1.6) + connection_pool (2.2.2) countries (3.0.1) i18n_data (~> 0.10.0) sixarm_ruby_unaccent (~> 1.1) @@ -228,6 +232,7 @@ GEM haml (5.1.2) temple (>= 0.8.0) tilt + hashie (4.1.0) httpclient (2.8.3) hurley (0.2) i18n (0.9.5) @@ -253,7 +258,6 @@ GEM thor (>= 0.14, < 2.0) json (2.3.0) jwt (2.2.1) - kgio (2.11.3) loofah (2.4.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) @@ -284,6 +288,18 @@ GEM mini_portile2 (~> 2.4.0) nokogumbo (2.0.2) nokogiri (~> 1.8, >= 1.8.4) + omniauth (1.9.1) + hashie (>= 3.4.6) + rack (>= 1.6.2, < 3) + omniauth-openid (1.0.1) + omniauth (~> 1.0) + rack-openid (~> 1.3.1) + omniauth-rails_csrf_protection (0.1.2) + actionpack (>= 4.2) + omniauth (>= 1.3.1) + omniauth-steam (1.0.6) + multi_json + omniauth-openid os (1.0.1) parallel (1.19.1) parser (2.7.0.5) @@ -306,6 +322,9 @@ GEM puma (4.3.3) nio4r (~> 2.0) rack (2.2.2) + rack-openid (1.3.1) + rack (>= 1.1.0) + ruby-openid (>= 2.1.8) rack-test (1.1.0) rack (>= 1.0, < 3) rails (6.0.2.2) @@ -349,7 +368,6 @@ GEM rake (>= 0.8.7) thor (>= 0.20.3, < 2.0) rainbow (3.0.0) - raindrops (0.19.1) rake (13.0.1) rb-fsevent (0.10.3) rb-inotify (0.10.1) @@ -377,6 +395,7 @@ GEM rexml ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 1.7) + ruby-openid (2.9.2) ruby-progressbar (1.10.1) ruby-vips (2.0.17) ffi (~> 1.9) @@ -440,9 +459,6 @@ GEM execjs (>= 0.3.0, < 3) unicode-display_width (1.6.1) unicode_utils (1.4.0) - unicorn (5.5.4) - kgio (~> 2.6) - raindrops (~> 0.7) unread (0.11.0) activerecord (>= 3) web-console (4.0.1) @@ -462,9 +478,11 @@ PLATFORMS ruby DEPENDENCIES + active_flag active_link_to active_record_union annotate + awesome_print bbcoder better_errors binding_of_caller @@ -474,6 +492,7 @@ DEPENDENCIES carrierwave codeclimate-test-reporter coffee-rails + connection_pool country_select dalli database_cleaner-active_record @@ -494,6 +513,9 @@ DEPENDENCIES neat (~> 1.6.0) newrelic_rpm nokogiri + omniauth + omniauth-rails_csrf_protection + omniauth-steam phantomjs poltergeist pry-byebug @@ -527,7 +549,6 @@ DEPENDENCIES timecop tinymce-rails uglifier - unicorn unread web-console will_paginate diff --git a/app/assets/stylesheets/themes/default/layout/_header.scss b/app/assets/stylesheets/themes/default/layout/_header.scss index 5634579..07352cf 100644 --- a/app/assets/stylesheets/themes/default/layout/_header.scss +++ b/app/assets/stylesheets/themes/default/layout/_header.scss @@ -2,12 +2,13 @@ Banner */ + header .banner { height: 180px; #authentication { - @include span-columns(5 of 12); - @include shift(7); + @include span-columns(6 of 12); + @include shift(6); padding: 30px 0; padding-top: 50px; color: white; @@ -52,8 +53,14 @@ header .banner { .fields { @include span-columns(12); + img { + padding-top: 4px; + padding-right: 5px; + @include span-columns(2 of 12); + } + input { - @include span-columns(6); + @include span-columns(5 of 12); margin-bottom: 10px; } } @@ -73,10 +80,6 @@ header .banner { } } - .password-reset { - float: right; - } - .links { float: right; position: relative; @@ -116,15 +119,23 @@ header .banner { } .buttons { - @include span-columns(12); + @include span-columns(12 of 12); font-family: $header-font-family; text-align: right; text-transform: uppercase; font-size: 12px; - .login, + .login { + @include span-columns(5 of 12); + @include shift(1.7); + } + .register { - @include span-columns(6); + @include span-columns(5 of 12); + + .password-reset { + float: right; + } } .login input { diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 48a189a..9ecb8c9 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -7,7 +7,9 @@ class ApplicationController < ActionController::Base before_action :update_user before_action :set_controller_and_action_names - protect_from_forgery + # Omniauth has its own CSRF + protect_from_forgery :except => [:callback] + respond_to :html, :js def cuser @@ -24,6 +26,18 @@ class ApplicationController < ActionController::Base redirect_to addr end + def return_back + if session[:return_to] + return_to + elsif request.env["HTTP_REFERER"] + redirect_to request.env["HTTP_REFERER"] + else + redirect_to "/" + end + rescue + redirect_to "/" + end + def redirect_to_back if request.env["HTTP_REFERER"] redirect_to request.env["HTTP_REFERER"] diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index cf89d4c..7b26c3b 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -60,9 +60,6 @@ class UsersController < ApplicationController raise AccessError unless @user.can_create? cuser if @user.valid? and @user.save - @user.profile = Profile.new - @user.profile.user = @user - @user.profile.save! redirect_to action: :show, id: @user.id save_session @user else @@ -88,28 +85,26 @@ class UsersController < ApplicationController redirect_to users_url end + def callback + @user = User.focfah(auth_hash, request.ip) + login_user(@user) + if @user.created_at > (Time.zone.now - 1.week.ago) + render :edit + else + return_back + end + end + # FIXME: maybe move to session controller def login if params[:login] if (u = User.authenticate(params[:login])) - if u.banned? Ban::TYPE_SITE - flash[:notice] = t(:accounts_locked) - else - flash[:notice] = "%s (%s)" % [t(:login_successful), u.password_hash_s] - # FIXME: this doesn't work because model is saved before - flash[:notice] << " \n%s" % I18n.t(:password_md5_scrypt) if u.password_hash_changed? - save_session u - end + login_user(u) else flash[:error] = t(:login_unsuccessful) end end - # FIXME: check return on rails 6 - if session[:return_to] - return_to - else - redirect_to_back - end + return_back end def logout @@ -134,10 +129,25 @@ class UsersController < ApplicationController @user = User.find(params[:id]) end + def login_user(user) + if user.banned? Ban::TYPE_SITE + flash[:error] = t(:accounts_locked) + else + flash[:notice] = "%s (%s)" % [t(:login_successful), user.password_hash_s] + # FIXME: this doesn't work because model is saved before + flash[:notice] << " \n%s" % I18n.t(:password_md5_scrypt) if user.password_hash_changed? + save_session user + end + end + def save_session user session[:user] = user.id user.lastip = request.ip user.lastvisit = Time.now.utc - user.save + user.save! + end + + def auth_hash + request.env['omniauth.auth'] end end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 968046d..9aa7c7b 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -18,4 +18,9 @@ module UsersHelper # link_to_remote text, options, html_options end + + def steamid_tool + df = DataFile.where("name LIKE '%SteamID Finder%'").first + df ? data_file_url(df) : "/" + end end diff --git a/app/models/user.rb b/app/models/user.rb index d9d8cb4..1733346 100755 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -28,7 +28,9 @@ # require 'digest/md5' +require 'steamid' require "scrypt" +require 'securerandom' class SteamIdValidator < ActiveModel::Validator def validate(record) @@ -50,11 +52,10 @@ class User < ActiveRecord::Base PASSWORD_MD5_SCRYPT = 2 #attr_protected :id, :created_at, :updated_at, :lastvisit, :lastip, :password, :version - attr_accessor :raw_password, :password_updated + attr_accessor :raw_password, :password_updated, :password_force, :fullname attribute :lastvisit, :datetime, default: Time.now.utc attribute :password_hash, :integer, default: PASSWORD_SCRYPT - attr_accessor :password_force belongs_to :team, :optional => true has_one :profile, :dependent => :destroy @@ -142,7 +143,9 @@ class User < ActiveRecord::Base # validates_inclusion_of :password_hash, in: => [User::PASSWORD_SCRYPT, User::PASSWORD_MD5, User::PASSWORD_MD5_SCRYPT] validate :validate_team + before_validation :set_name before_create :init_variables + after_create :create_profile before_save :correct_steamid_universe accepts_nested_attributes_for :profile @@ -168,6 +171,17 @@ class User < ActiveRecord::Base username end + def set_name + return unless fullname + if fullname.include?(" ") + # TODO: check this + self.firstname = fullname.match(/(?:^|(?:\.\s))(\w+)/)[1] + self.surname = fullname.match(/\s(\w+)$/)[1] + else + self.firstname = fullname + end + end + def password_hash_s case self.password_hash when User::PASSWORD_MD5 @@ -327,6 +341,14 @@ class User < ActiveRecord::Base def init_variables self.public_email = false self.time_zone = "Amsterdam" + self.raw_password = SecureRandom.base64(32) unless raw_password and new_record? + self.profile = profile.build unless profile&.present? + end + + def create_profile + if profile + profile.save + end end # NOTE: function does not call save @@ -363,6 +385,18 @@ class User < ActiveRecord::Base true end + def fix_attributes + if errors[:username] + i = 2 + loop do + new_username = "%s%d" % [username, i] + i+=1 + break if User.find_by_username(new_username).count == 0 or i > 50 + end + self.username = new_username + end + end + def can_update? cuser cuser and (self == cuser or cuser.admin?) end @@ -410,8 +444,8 @@ class User < ActiveRecord::Base return nil end - def self.get id - id ? find(id) : "" + def self.get(id) + id ? User.find(id) : "" end def self.historic steamid @@ -442,4 +476,26 @@ class User < ActiveRecord::Base allowed << :username if cuser&.admin? || operation == 'create' params.require(:user).permit(*allowed) end -end + + def self.focfah(auth_hash, lastip) + return nil unless auth_hash&.include?(:provider) + byebug + case auth_hash[:provider] + when 'steam' + return nil unless auth_hash&.include?(:uid) + steamid = SteamID::from_steamID64(auth_hash[:uid]) + user = User.where("LOWER(steamid) = LOWER(?)", steamid).first + unless user + user = User.new(username: auth_hash[:info][:nickname], lastip: lastip, fullname: auth_hash[:info][:name], steamid: steamid) + user.fix_attributes + # TODO: user make valid by force + # user.profile.country + # get profile picture, :image + # This really shouldn't fail. + user.save! + end + return user + end + return nil + end +end \ No newline at end of file diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb index 5725834..8aac041 100644 --- a/app/views/users/new.html.erb +++ b/app/views/users/new.html.erb @@ -1,31 +1,49 @@

Registration

- <%= link_to "You can use this tool to find your SteamID.", "/files/client/steamid_finder.exe" %> - <%= form_for @user, html: { class: "square" } do |f| %> <%= render 'shared/errors', messages: @user.errors.full_messages %>
+
+ <%= link_to "Create your account via Steam", "/auth/steam", method: :POST %> +
<%= f.label :username %> <%= f.text_field :username %>
+
+ Pick unique nickname for yourself. +
<%= f.label :raw_password, "Password" %> <%= f.password_field :raw_password %>
+
+ Please don't use same password as any important place. +
<%= f.label :email %> <%= f.text_field :email %>
+
+ The email is needed to reset password, verify identity and send account related emails. We don't send spam or sell your email. By default the email is private and only seen by admins. +
<%= f.label :steamid %> <%= f.text_field :steamid, placeholder: "0:1:23456789" %>
+
+ You can use <%= link_to "this tool", steamid_tool %> or + <%= link_to "this web page", 'https://steamidfinder.com/' %> + to find your SteamID. We need the steam id to identify unique players. If you use fake one, some things on website might be broken. +
<%= f.label :birthdate %> - <%= date_select :user, :birthdate, order: [:year, :month, :day], start_year: 1950 %> + <%= date_select :user, :birthdate, order: [:year, :month, :day], start_year: 1950, include_blank: true, default: nil %> +
+
+ Only needed for fun stats (age etc.). You don't need to give valid one.
diff --git a/app/views/widgets/_login.html.erb b/app/views/widgets/_login.html.erb index fd1652b..3ed7bd5 100644 --- a/app/views/widgets/_login.html.erb +++ b/app/views/widgets/_login.html.erb @@ -1,5 +1,8 @@ <%= form_tag({ controller: "users", action: "login" }, { class: 'dark' }) do %>
+ <%= link_to "/auth/steam", method: :POST do %> + <%= image_tag '/images/icons/steam_login.png' %> + <% end %> <%= text_field "login", "username", placeholder: "Username" %> <%= password_field "login", "password", placeholder: "Password" %>
diff --git a/config/application.rb b/config/application.rb index 821bc14..3c5c363 100644 --- a/config/application.rb +++ b/config/application.rb @@ -23,12 +23,29 @@ module Ensl # Custom directories with classes and modules you want to be autoloadable. config.autoload_paths += Dir["#{config.root}/app/services/**/", "#{config.root}/app/models/concerns/"] + # Be sure to restart your server when you modify this file. + config.session_store :cookie_store, key: '_ensl_session' + # Load secrets from .env ENV['APP_SECRET'] ||= (0...32).map { (65 + rand(26)).chr }.join config.secret_token = ENV['APP_SECRET'] - # Use cookies - config.session_store :cookie_store, key: '_ENSL_session_key', expire_after: 30.days.to_i + # Use a different cache store + config.cache_store = :dalli_store, 'memcached:11211' + + # Use smtp-Server + config.action_mailer.delivery_method = :smtp + config.action_mailer.smtp_settings = { + address: 'smtp', + domain: ENV['MAIL_DOMAIN'] + } + + # Specifies the header that your server uses for sending files + # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache + config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx + + # Use a different logger for distributed setups + config.logger = Logger.new(Rails.root.join("log", Rails.env + ".log" ), 5 , 10 * 1024 * 1024) # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. @@ -43,14 +60,21 @@ module Ensl # Enable the asset pipeline config.assets.enabled = true + # Version of your assets, change this if you want to expire all your assets + config.assets.version = '1.0' + # il8n fix config.i18n.fallbacks = true config.i18n.enforce_available_locales = false - # Version of your assets, change this if you want to expire all your assets - config.assets.version = '1.0' - # Tiny mce config.tinymce.install = :copy + + # Send deprecation notices to registered listeners + config.active_support.deprecation = :notify + + # Enable threaded mode + # Almost nothing is thread-safe, do not + # config.threadsafe! end end diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb new file mode 100644 index 0000000..2192403 --- /dev/null +++ b/config/initializers/omniauth.rb @@ -0,0 +1,3 @@ +Rails.application.config.middleware.use OmniAuth::Builder do + provider :steam, ENV['STEAM_WEB_API_KEY'] +end \ No newline at end of file diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb deleted file mode 100644 index 63f42ea..0000000 --- a/config/initializers/session_store.rb +++ /dev/null @@ -1,3 +0,0 @@ -# Be sure to restart your server when you modify this file. - -Ensl::Application.config.session_store :cookie_store, key: '_ensl_session' diff --git a/config/routes.rb b/config/routes.rb index 5737a48..9507d54 100755 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,4 @@ -Ensl::Application.routes.draw do +Rails.application.routes.draw do if Rails.env.production? %w(403 404 422 500).each do |code| get code, to: "errors#show", code: code @@ -71,6 +71,7 @@ Ensl::Application.routes.draw do post 'forgot' end end + post 'auth/:provider/callback', to: 'users#callback' resources :locks resources :contesters diff --git a/lib/steamid.rb b/lib/steamid.rb new file mode 100644 index 0000000..1ade079 --- /dev/null +++ b/lib/steamid.rb @@ -0,0 +1,12 @@ +module SteamID + def self.to_steamID(steamid) + m = steamid.match(/^(STEAM_){0,1}([0-5]):([01]:\d+)$/) + return ((m[3].to_i * 2) + m[2].to_i) + 76561197960265728 + end + + def self.from_steamID64(sid) + y = sid.to_i - 76561197960265728 + x = y % 2 + return "0:%d:%d" % [x, (y - x).div(2)] + end +end \ No newline at end of file diff --git a/public/images/icons/steam_login.png b/public/images/icons/steam_login.png new file mode 100644 index 0000000..1ecbd19 Binary files /dev/null and b/public/images/icons/steam_login.png differ