Add scrypt

This commit is contained in:
Ari Timonen 2020-04-02 03:08:19 +03:00
parent b0c9697109
commit 3a4aa11d84
11 changed files with 141 additions and 49 deletions

View file

@ -6,7 +6,7 @@ RAILS_ENV=development
APP_SECRET=fe837ea72667ec3d8ecb94cfba1a1bba
DEPLOY_PATH=/var/www
FILES_PATH=/var/www/files
FILES_PATH=/var/www/public/files
PUMA_WORKERS=0
PUMA_MIN_THREADS=1

View file

@ -8,6 +8,11 @@ RAILS_ENV=production
# App secret for cookie encryption
APP_SECRET=randomstringhere
# Use
# SCrypt::Engine.calibrate!(max_mem: 16 * 1024 * 1024)
# SCrypt::Engine.generate_salt
SCRYPT_SALT_OPTS=
# Since this is inside Docker container, it doesn't really matter
DEPLOY_PATH=/var/www

View file

@ -21,6 +21,7 @@ gem 'unicorn'
# Model plugins
gem 'unread'
gem 'scrypt'
# gem 'impressionist'
# gem 'ratyrate'
# gem "acts_as_rateable", :git => "git://github.com/anton-zaytsev/acts_as_rateable.git"

View file

@ -200,6 +200,9 @@ GEM
faraday (0.17.3)
multipart-post (>= 1.2, < 3)
ffi (1.12.2)
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
rake
font-awesome-sass (4.1.0)
sass (~> 3.2)
geckodriver-helper (0.24.0)
@ -393,6 +396,8 @@ GEM
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
scrypt (3.0.7)
ffi-compiler (>= 1.0, < 2.0)
selenium-webdriver (3.142.7)
childprocess (>= 0.5, < 4.0)
rubyzip (>= 1.2.2)
@ -511,6 +516,7 @@ DEPENDENCIES
rubocop
sanitize
sass-rails (~> 5.0.3)
scrypt
selenium-webdriver
signet (= 0.11.0)
simplecov

View file

@ -90,15 +90,18 @@ class UsersController < ApplicationController
# FIXME: maybe move to session controller
def login
if params[:login] && (u = User.authenticate(params[:login]))
if u.banned? Ban::TYPE_SITE
flash[:notice] = t(:accounts_locked)
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]
flash[:notice] << " \n%s" % I18n.t(:password_md5_scrypt) if u.password_hash_changed?
save_session u
end
else
flash[:notice] = t(:login_successful)
save_session u
flash[:error] = t(:login_unsuccessful)
end
else
flash[:error] = t(:login_unsuccessful)
end
# FIXME: check return on rails 6
if session[:return_to]

View file

@ -2,23 +2,24 @@
#
# Table name: users
#
# id :integer not null, primary key
# birthdate :date
# country :string(255)
# email :string(255)
# firstname :string(255)
# lastip :string(255)
# lastname :string(255)
# lastvisit :datetime
# password :string(255)
# public_email :boolean default(FALSE), not null
# steamid :string(255)
# time_zone :string(255)
# username :string(255)
# version :integer
# created_at :datetime
# updated_at :datetime
# team_id :integer
# id :integer not null, primary key
# birthdate :date
# country :string(255)
# email :string(255)
# firstname :string(255)
# lastip :string(255)
# lastname :string(255)
# lastvisit :datetime
# password :string(255)
# password_hash :integer default(0)
# public_email :boolean default(FALSE), not null
# steamid :string(255)
# time_zone :string(255)
# username :string(255)
# version :integer
# created_at :datetime
# updated_at :datetime
# team_id :integer
#
# Indexes
#
@ -27,6 +28,7 @@
#
require 'digest/md5'
require "scrypt"
class SteamIdValidator < ActiveModel::Validator
def validate(record)
@ -43,10 +45,15 @@ class User < ActiveRecord::Base
VERIFICATION_TIME = 604800
PASSWORD_SCRYPT = 0
PASSWORD_MD5 = 1
PASSWORD_MD5_SCRYPT = 2
#attr_protected :id, :created_at, :updated_at, :lastvisit, :lastip, :password, :version
attr_accessor :raw_password
attr_accessor :raw_password, :password_updated
attribute :lastvisit, :datetime, default: Time.now.utc
attribute :password_hash, :integer, default: PASSWORD_SCRYPT
belongs_to :team, :optional => true
has_one :profile, :dependent => :destroy
@ -121,8 +128,8 @@ class User < ActiveRecord::Base
validates_uniqueness_of :username, :email, :steamid
validates_length_of :firstname, :in => 1..15, :allow_blank => true
validates_length_of :lastname, :in => 1..25, :allow_blank => true
validates_length_of :username, :in => 2..20
validates_format_of :username, :with => /\A[A-Za-z0-9_\-\+]{2,20}\Z/
validates_length_of :username, :in => 1..30
validates_format_of :username, :with => /\A[A-Za-z0-9_\-\+]{1,30}\Z/
validates_presence_of :raw_password, :on => :create
validates_length_of :email, :maximum => 50
validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
@ -131,6 +138,7 @@ class User < ActiveRecord::Base
# validates_format_of :steamid, :with => /\A(STEAM_)?[0-5]:[01]:\d+\Z/
validates_length_of :time_zone, :maximum => 100, :allow_blank => true, :allow_nil => true
validates_inclusion_of [:public_email], :in => [true, false], :allow_nil => true
# validates_inclusion_of :password_hash, in: => [User::PASSWORD_SCRYPT, User::PASSWORD_MD5, User::PASSWORD_MD5_SCRYPT]
validate :validate_team
before_create :init_variables
@ -152,12 +160,25 @@ class User < ActiveRecord::Base
non_versioned_columns << 'birthdate'
non_versioned_columns << 'time_zone'
non_versioned_columns << 'public_email'
non_versioned_columns << 'password_hash'
non_versioned_columns << 'created_at'
def to_s
username
end
def password_hash_s
case self.password_hash
when User::PASSWORD_MD5
"MD5"
when User::PASSWORD_SCRYPT
"Scrypt"
when User::PASSWORD_MD5_SCRYPT
"Scrypt+MD5"
else
end
end
def email_s
email.gsub /@/, " (at) "
end
@ -307,9 +328,17 @@ class User < ActiveRecord::Base
self.time_zone = "Amsterdam"
end
# Password should store password and password_hash shoulds store
# NOTE: function does not call save
# Maybe it should return to not waste save?
def update_password
self.password = Digest::MD5.hexdigest(raw_password) if raw_password and raw_password.length > 0
if raw_password and raw_password.length > 0
self.password = SCrypt::Password.create(raw_password)
self.password_hash = User::PASSWORD_SCRYPT
elsif password_hash == User::PASSWORD_MD5
# Scrypt(Md5(passsword))
self.password = SCrypt::Password.create(password)
self.password_hash = User::PASSWORD_MD5_SCRYPT
end
end
def send_new_password
@ -339,7 +368,31 @@ class User < ActiveRecord::Base
end
def self.authenticate(login)
where("LOWER(username) = LOWER(?)", login[:username]).where(password: Digest::MD5.hexdigest(login[:password])).first
if (user = where("LOWER(username) = LOWER(?)", login[:username]).first)
case user.password_hash
when User::PASSWORD_SCRYPT
pass = SCrypt::Password.new(user.password)
return user if pass == login[:password]
when User::PASSWORD_MD5_SCRYPT
pass = SCrypt::Password.new(user.password)
# Match to Scrypt(Md5(password))
if pass == Digest::MD5.hexdigest(login[:password])
user.raw_password = login[:password]
user.update_password
user.save!
return user
end
# when User::PASSWORD_MD5
else
if user.password == Digest::MD5.hexdigest(login[:password])
user.raw_password = login[:password]
user.update_password
user.save!
return user
end
end
end
return nil
end
def self.get id

View file

@ -84,6 +84,7 @@ en:
login_out: "Logged out."
login_status: "Logged in as"
passwords_sent: "Password has been sent."
password_md5_scrypt: "Password has been upgraded to higher security level (MD5->SCRYPT)."
incorrect_information: "Incorrect Information."
weeks_create: "Week was successfully created."
weeks_update: "Week was successfully updated."

View file

@ -0,0 +1,7 @@
class AddPasswordHashToUsers < ActiveRecord::Migration[6.0]
def change
change_table :users do |u|
u.integer :password_hash, default: User::PASSWORD_MD5
end
end
end

View file

@ -0,0 +1,14 @@
class UpdatePasswordsToScrypt < ActiveRecord::Migration[6.0]
require 'scrypt'
require 'user'
def up
SCrypt::Engine.calibrate!(max_time: 0.03)
ActiveRecord::Base.transaction do
User.all.order(:id).each do |user|
user.update_password
user.save!
end
end
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_03_31_020637) do
ActiveRecord::Schema.define(version: 2020_04_01_031046) do
create_table "article_versions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.integer "article_id"
@ -805,6 +805,7 @@ ActiveRecord::Schema.define(version: 2020_03_31_020637) do
t.string "time_zone"
t.integer "version"
t.boolean "public_email", default: false, null: false
t.integer "password_hash", default: 1
t.index ["lastvisit"], name: "index_users_on_lastvisit"
t.index ["team_id"], name: "index_users_on_team_id"
end

View file

@ -2,23 +2,24 @@
#
# Table name: users
#
# id :integer not null, primary key
# birthdate :date
# country :string(255)
# email :string(255)
# firstname :string(255)
# lastip :string(255)
# lastname :string(255)
# lastvisit :datetime
# password :string(255)
# public_email :boolean default(FALSE), not null
# steamid :string(255)
# time_zone :string(255)
# username :string(255)
# version :integer
# created_at :datetime
# updated_at :datetime
# team_id :integer
# id :integer not null, primary key
# birthdate :date
# country :string(255)
# email :string(255)
# firstname :string(255)
# lastip :string(255)
# lastname :string(255)
# lastvisit :datetime
# password :string(255)
# password_hash :integer default(0)
# public_email :boolean default(FALSE), not null
# steamid :string(255)
# time_zone :string(255)
# username :string(255)
# version :integer
# created_at :datetime
# updated_at :datetime
# team_id :integer
#
# Indexes
#