mirror of
https://github.com/neocities/neocities.git
synced 2025-07-31 23:06:14 +02:00
merge phonevalidation with master
This commit is contained in:
commit
aad6546631
14 changed files with 256 additions and 2 deletions
2
Gemfile
2
Gemfile
|
@ -59,6 +59,8 @@ gem 'webp-ffi'
|
|||
gem 'rszr'
|
||||
gem 'zip_tricks'
|
||||
gem 'adequate_crypto_address'
|
||||
gem 'twilio-ruby'
|
||||
gem 'phonelib'
|
||||
|
||||
group :development, :test do
|
||||
gem 'pry'
|
||||
|
|
|
@ -146,6 +146,7 @@ GEM
|
|||
io-extra (1.4.0)
|
||||
ipaddress (0.8.3)
|
||||
json (2.6.2)
|
||||
jwt (2.7.1)
|
||||
keccak (1.3.1)
|
||||
llhttp-ffi (0.4.0)
|
||||
ffi-compiler (~> 1.0)
|
||||
|
@ -197,6 +198,7 @@ GEM
|
|||
ox (2.14.11)
|
||||
paypal-recurring (1.1.0)
|
||||
pg (1.5.3)
|
||||
phonelib (0.8.3)
|
||||
progress (3.6.0)
|
||||
pry (0.14.1)
|
||||
coderay (~> 1.1)
|
||||
|
@ -291,6 +293,10 @@ GEM
|
|||
timeout (0.3.0)
|
||||
tins (1.31.1)
|
||||
sync
|
||||
twilio-ruby (6.3.1)
|
||||
faraday (>= 0.9, < 3.0)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
nokogiri (>= 1.6, < 2.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
unf (0.1.4)
|
||||
|
@ -362,6 +368,7 @@ DEPENDENCIES
|
|||
nokogiri
|
||||
paypal-recurring
|
||||
pg
|
||||
phonelib
|
||||
pry
|
||||
puma (< 7)
|
||||
rack-cache
|
||||
|
@ -391,6 +398,7 @@ DEPENDENCIES
|
|||
thread
|
||||
tilt
|
||||
timecop
|
||||
twilio-ruby
|
||||
webmock
|
||||
webp-ffi
|
||||
will_paginate
|
||||
|
|
2
app.rb
2
app.rb
|
@ -77,6 +77,8 @@ before do
|
|||
# Skips the CSRF/validation check for stripe web hooks
|
||||
elsif email_not_validated? && !(request.path =~ /^\/site\/.+\/confirm_email|^\/settings\/change_email|^\/signout|^\/welcome|^\/supporter/)
|
||||
redirect "/site/#{current_site.username}/confirm_email"
|
||||
elsif current_site && current_site.phone_verification_needed? && !(request.path =~ /^\/site\/.+\/confirm_phone/)
|
||||
redirect "/site/#{current_site.username}/confirm_phone"
|
||||
else
|
||||
content_type :html, 'charset' => 'utf-8'
|
||||
redirect '/' if request.post? && !csrf_safe?
|
||||
|
|
|
@ -98,6 +98,20 @@ post '/create' do
|
|||
end
|
||||
|
||||
@site.email_confirmed = true if self.class.development?
|
||||
@site.phone_confirmed = true if self.class.development?
|
||||
|
||||
begin
|
||||
@site.phone_verification_required = true if self.class.production? && BlackBox.phone_verification_required?(site)
|
||||
rescue => e
|
||||
EmailWorker.perform_async({
|
||||
from: 'web@neocities.org',
|
||||
to: 'errors@neocities.org',
|
||||
subject: "[Neocities Error] Phone verification exception",
|
||||
body: "#{e.inspect}\n#{e.backtrace}",
|
||||
no_footer: true
|
||||
})
|
||||
end
|
||||
|
||||
@site.save
|
||||
|
||||
unless education_whitelisted?
|
||||
|
|
87
app/site.rb
87
app/site.rb
|
@ -295,4 +295,91 @@ get '/site/:username/unblock' do |username|
|
|||
|
||||
current_site.unblock! site
|
||||
redirect request.referer
|
||||
end
|
||||
|
||||
get '/site/:username/confirm_phone' do
|
||||
require_login
|
||||
redirect '/' unless current_site.phone_verification_needed?
|
||||
@title = 'Verify your Phone Number'
|
||||
erb :'site/confirm_phone'
|
||||
end
|
||||
|
||||
def restart_phone_verification
|
||||
current_site.phone_verification_sent_at = nil
|
||||
current_site.phone_verification_sid = nil
|
||||
current_site.save_changes validate: false
|
||||
redirect "/site/#{current_site.username}/confirm_phone"
|
||||
end
|
||||
|
||||
post '/site/:username/confirm_phone' do
|
||||
require_login
|
||||
redirect '/' unless current_site.phone_verification_needed?
|
||||
|
||||
if params[:phone_intl]
|
||||
phone = Phonelib.parse params[:phone_intl]
|
||||
|
||||
if !phone.valid?
|
||||
flash[:error] = "Invalid phone number, please try again."
|
||||
redirect "/site/#{current_site.username}/confirm_phone"
|
||||
end
|
||||
|
||||
if phone.types.include?(:premium_rate) || phone.types.include?(:shared_cost)
|
||||
flash[:error] = 'Neocities does not support this type of number, please use another number.'
|
||||
redirect "/site/#{current_site.username}/confirm_phone"
|
||||
end
|
||||
|
||||
current_site.phone_verification_sent_at = Time.now
|
||||
current_site.phone_verification_attempts += 1
|
||||
|
||||
if current_site.phone_verification_attempts > Site::PHONE_VERIFICATION_LOCKOUT_ATTEMPTS
|
||||
flash[:error] = 'You have exceeded the number of phone verification attempts allowed.'
|
||||
redirect "/site/#{current_site.username}/confirm_phone"
|
||||
end
|
||||
|
||||
current_site.save_changes validate: false
|
||||
|
||||
verification = $twilio.verify
|
||||
.v2
|
||||
.services($config['twilio_service_sid'])
|
||||
.verifications
|
||||
.create(to: phone.e164, channel: 'sms')
|
||||
|
||||
current_site.phone_verification_sid = verification.sid
|
||||
current_site.save_changes validate: false
|
||||
|
||||
flash[:success] = 'Validation message sent! Check your phone and enter the code below.'
|
||||
else
|
||||
|
||||
restart_phone_verification if current_site.phone_verification_sent_at < Time.now - Site::PHONE_VERIFICATION_EXPIRATION_TIME
|
||||
minutes_remaining = ((current_site.phone_verification_sent_at - (Time.now - Site::PHONE_VERIFICATION_EXPIRATION_TIME))/60).round
|
||||
|
||||
begin
|
||||
# Check code
|
||||
vc = $twilio.verify
|
||||
.v2
|
||||
.services($config['twilio_service_sid'])
|
||||
.verification_checks
|
||||
.create(verification_sid: current_site.phone_verification_sid, code: params[:code])
|
||||
|
||||
# puts vc.status (pending if failed, approved if it passed)
|
||||
if vc.status == 'approved'
|
||||
current_site.phone_verified = true
|
||||
current_site.save_changes validate: false
|
||||
else
|
||||
flash[:error] = "Code was not correct, please try again. If the phone number you entered was incorrect, you can re-enter the number after #{minutes_remaining} more minutes have passed."
|
||||
end
|
||||
|
||||
rescue Twilio::REST::RestError => e
|
||||
if e.message =~ /60202/
|
||||
flash[:error] = "You have exhausted your check attempts. Please try again in #{minutes_remaining} minutes."
|
||||
elsif e.message =~ /20404/ # Unable to create record
|
||||
restart_phone_verification
|
||||
else
|
||||
raise e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Will redirect to / automagically if phone was verified
|
||||
redirect "/site/#{current_site.username}/confirm_phone"
|
||||
end
|
|
@ -20,4 +20,7 @@ cache_control_ips:
|
|||
- 1.2.3.4
|
||||
- 4.5.6.7
|
||||
hcaptcha_site_key: "10000000-ffff-ffff-ffff-000000000001"
|
||||
hcaptcha_secret_key: "0x0000000000000000000000000000000000000000"
|
||||
hcaptcha_secret_key: "0x0000000000000000000000000000000000000000"
|
||||
twilio_account_sid: ACEDERPDERP
|
||||
twilio_auth_token: derpderpderp
|
||||
twilio_service_sid: VADERPDERPDERP
|
|
@ -55,3 +55,6 @@ test:
|
|||
cache_control_ips:
|
||||
- 1.2.3.4
|
||||
- 4.5.6.7
|
||||
twilio_account_sid: ACEDERPDERP
|
||||
twilio_auth_token: derpderpderp
|
||||
twilio_service_sid: VADERPDERPDERP
|
|
@ -177,3 +177,5 @@ $image_optim = ImageOptim.new pngout: false, svgo: false
|
|||
Money.locale_backend = nil
|
||||
Money.default_currency = Money::Currency.new("USD")
|
||||
Money.rounding_mode = BigDecimal::ROUND_HALF_UP
|
||||
|
||||
$twilio = Twilio::REST::Client.new $config['twilio_account_sid'], $config['twilio_auth_token']
|
||||
|
|
15
migrations/119_verify_phone.rb
Normal file
15
migrations/119_verify_phone.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
DB.add_column :sites, :phone_verification_required, :boolean, default: false
|
||||
DB.add_column :sites, :phone_verified, :boolean, default: false
|
||||
DB.add_column :sites, :phone_verification_sid, :text
|
||||
DB.add_column :sites, :phone_verification_sent_at, :time
|
||||
}
|
||||
|
||||
down {
|
||||
DB.drop_column :sites, :phone_verification_required
|
||||
DB.drop_column :sites, :phone_verified
|
||||
DB.drop_column :sites, :phone_verification_sid
|
||||
DB.drop_column :sites, :phone_verification_sent_at
|
||||
}
|
||||
end
|
11
migrations/120_fix_phone_sent_at.rb
Normal file
11
migrations/120_fix_phone_sent_at.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
DB.drop_column :sites, :phone_verification_sent_at
|
||||
DB.add_column :sites, :phone_verification_sent_at, Time
|
||||
}
|
||||
|
||||
down {
|
||||
DB.drop_column :sites, :phone_verification_sent_at
|
||||
DB.add_column :sites, :phone_verification_sent_at, :time
|
||||
}
|
||||
end
|
9
migrations/121_phone_verification_attempts.rb
Normal file
9
migrations/121_phone_verification_attempts.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
DB.add_column :sites, :phone_verification_attempts, :integer, default: 0
|
||||
}
|
||||
|
||||
down {
|
||||
DB.drop_column :sites, :phone_verification_attempts
|
||||
}
|
||||
end
|
|
@ -167,6 +167,9 @@ class Site < Sequel::Model
|
|||
BLACK_BOX_WAIT_TIME = 10.seconds
|
||||
MAX_DISPLAY_FOLLOWS = 56*3
|
||||
|
||||
PHONE_VERIFICATION_EXPIRATION_TIME = 10.minutes
|
||||
PHONE_VERIFICATION_LOCKOUT_ATTEMPTS = 3
|
||||
|
||||
many_to_many :tags
|
||||
|
||||
one_to_many :profile_comments
|
||||
|
@ -1789,6 +1792,11 @@ class Site < Sequel::Model
|
|||
end
|
||||
end
|
||||
|
||||
def phone_verification_needed?
|
||||
return true if phone_verification_required && !phone_verified
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def store_file(path, uploaded, opts={})
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
You're almost ready!<br>
|
||||
<% end %>
|
||||
|
||||
We sent an email to <strong><%= current_site.email %></strong> to make sure it's correct.<br>Please check your email, enter the confirmation code here, and you're all set.
|
||||
We sent an email to <strong><%= current_site.email %></strong> to make sure it's correct.<br>Please check your email, and enter the confirmation code here.
|
||||
</h3>
|
||||
|
||||
<div class="row">
|
||||
|
|
90
views/site/confirm_phone.erb
Normal file
90
views/site/confirm_phone.erb
Normal file
|
@ -0,0 +1,90 @@
|
|||
<section class="section plans welcome">
|
||||
<h2>Verify your phone number</h2>
|
||||
<div class="txt-Center"><img src="/img/catbus.png" width="90px"></div>
|
||||
<h3 class="subtitle">
|
||||
Last thing!<br>
|
||||
To prevent spam and keep the searchability of your site high, we have one last step:
|
||||
<br>please verify your mobile phone number.
|
||||
</h3>
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-100 txt-Center" style="margin-top: 10px;">
|
||||
<% if flash[:success] %>
|
||||
<div class="alert alert-block alert-success" style="margin-top: 20px">
|
||||
<%== flash[:success] %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if flash[:error] %>
|
||||
<div class="alert alert-block alert-error" style="margin-top: 20px">
|
||||
<%== flash[:error] %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<form method="POST" action="/site/<%= current_site.username %>/confirm_phone" class="content">
|
||||
<%== csrf_token_input_html %>
|
||||
|
||||
<% if current_site.phone_verification_sid %>
|
||||
<fieldset>
|
||||
<label for="token">Enter the 6 digit code:<br></label>
|
||||
<input id="code" name="code" type="text" class="input-Area" autofill="off" autocapitalize="off" autocorrect="off" value="<%= flash[:code] %>" style="width: 100px" maxlength=6>
|
||||
</fieldset>
|
||||
<input id="submitButton" class="btn-Action" type="submit" value="Verify Code" style="display: none" autocomplete="off">
|
||||
|
||||
<script>
|
||||
document.getElementById('code').addEventListener('input', function(e) {
|
||||
var inputVal = e.target.value;
|
||||
var submitButton = document.getElementById('submitButton');
|
||||
|
||||
// Check if there are exactly 6 digits in the input
|
||||
var isValid = /^\d{6}$/.test(inputVal);
|
||||
|
||||
if(isValid) {
|
||||
submitButton.style = 'display: inline-block';
|
||||
} else {
|
||||
submitButton.style = 'display: none';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<% else %>
|
||||
|
||||
<fieldset>
|
||||
<label for="phone">Enter your phone number<br><small>(including country code)</small></label>
|
||||
<input id="phone" name="phone" type="text" class="input-Area" autofill="off" autocapitalize="off" autocorrect="off" autocomplete="off" style="width: 290px">
|
||||
<input id="phone_intl" name="phone_intl" type="hidden">
|
||||
</fieldset>
|
||||
<input id="submitButton" class="btn-Action" type="submit" value="Send Verification Code" style="display: none">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/intl-tel-input@18.2.1/build/css/intlTelInput.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/intl-tel-input@18.2.1/build/js/intlTelInput.min.js"></script>
|
||||
<script>
|
||||
const input = document.querySelector("#phone");
|
||||
const iti = window.intlTelInput(input, {
|
||||
nationalMode: true,
|
||||
utilsScript: "https://cdn.jsdelivr.net/npm/intl-tel-input@18.2.1/build/js/utils.js",
|
||||
});
|
||||
|
||||
const handleChange = () => {
|
||||
let text;
|
||||
if(iti.isValidNumber()) {
|
||||
document.getElementById('submitButton').style = "display: inline-block"
|
||||
document.getElementById('phone_intl').value = iti.getNumber()
|
||||
} else {
|
||||
document.getElementById('submitButton').style = "display: none"
|
||||
}
|
||||
};
|
||||
|
||||
// listen to "keyup", but also "change" to update when the user selects a country
|
||||
input.addEventListener('change', handleChange);
|
||||
input.addEventListener('keyup', handleChange);
|
||||
</script>
|
||||
|
||||
<% end %>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
Loading…
Add table
Add a link
Reference in a new issue