Recaptcha gem error "No site key specified." - ruby-on-rails

I am trying to setup Recaptcha in my rails 5 application as it's described in the documentation but it fails.
I use this gem: recaptcha (4.6.6), ruby 2.5.0 and rails 5.1.4
In view form:
<%= flash[:recaptcha_error] %>
<%= recaptcha_tags %>
In devise registrations controller:
prepend_before_action :check_captcha, only: :create
private
def check_captcha
unless verify_recaptcha
self.resource = resource_class.new sign_up_params
resource.validate # Look for any other validation errors besides Recaptcha
respond_with_navigational(resource) { redirect_to new_user_registration_path }
end
end
In my initializers/recaptcha.rb
Recaptcha.configure do |config|
config.site_key = Rails.application.config_for(:recaptcha)['site_key']
config.secret_key = Rails.application.config_for(:recaptcha)['secret_key']
end
In my recaptcha.yml:
default: &default
site_key: <%= ENV["RECAPTCHA_SITE_KEY"] %>
secret_key: <%= ENV["RECAPTCHA_SECRET_KEY"] %>
development:
<<: *default
test:
<<: *default
staging:
<<: *default
production:
<<: *default
In /etc/environments:
# RECAPTCHA
RECAPTCHA_SITE_KEY=6Lfg3ksUAAAAABOD_OXCtPO60*******
RECAPTCHA_SECRET_KEY=6Lfg3ksUAAAAAOmFGdAxdo8*******
PROBLEM
After adding ENV variables to /etc/environments, I exported it with this command:
for line in $( cat /etc/environment ) ; do export $line ; done
Then I check that Recaptcha module is configured correctly:
/home/deploy/apps/app_name/current$ bundle exec rails c
Loading staging environment (Rails 5.1.4)
2.5.0 :001 > Recaptcha::Configuration.new
=> #<Recaptcha::Configuration:0x0000000006601908 #skip_verify_env=["test", "cucumber"], #handle_timeouts_gracefully=true, #secret_key="6Lfg3ksUAAAAAOmFGdAxdo8H*************", #site_key="6Lfg3ksUAAAAABOD_OXCtPO*************">
2.5.0 :002 > Recaptcha::Configuration.new.site_key!
=> "6Lfg3ksUAAAAABOD_OXCtPO*************"
Also, I see these ENV variables when I run printenv command (so it's really loaded)
After that, I restarted rails and got an error
No site key specified.
/home/deploy/apps/app_name/shared/bundle/ruby/2.5.0/gems/recaptcha-4.6.6/lib/recaptcha/configuration.rb:47:in `site_key!'
/home/deploy/apps/app_name/shared/bundle/ruby/2.5.0/gems/recaptcha-4.6.6/lib/recaptcha/client_helper.rb:79:in `recaptcha_components'
/home/deploy/apps/app_name/shared/bundle/ruby/2.5.0/gems/recaptcha-4.6.6/lib/recaptcha/client_helper.rb:15:in `recaptcha_tags'
/home/deploy/apps/app_name/releases/20180310222304/app/views/users/registrations/new.html.erb:27:in `block in _app_views_users_registrations_new_html_erb___216558772140569572_69973306795360'
/home/deploy/apps/app_name/shared/bundle/ruby/2.5.0/gems/actionview-5.1.4/lib/action_view/helpers/capture_helper.rb:39:in `block in capture'
/home/deploy/apps/app_name/shared/bundle/ruby/2.5.0/gems/actionview-5.1.4/lib/action_view/helpers/capture_helper.rb:203:in `with_output_buffer'
/home/deploy/apps/app_name/shared/bundle/ruby/2.5.0/gems/actionview-5.1.4/lib/action_view/helpers/capture_helper.rb:39:in `capture'
/home/deploy/apps/app_name/shared/bundle/ruby/2.5.0/gems/actionview-5.1.4/lib/action_view/helpers/form_helper.rb:450:in `form_for'
/home/deploy/apps/app_name/releases/20180310222304/app/views/users/registrations/new.html.erb:21:in `_app_views_users_registrations_new_html_erb___216558772140569572_69973306795360'
/home/deploy/apps/app_name/shared/bundle/ruby/2.5.0/gems/actionview-5.1.4/lib/action_view/template.rb:157:in `block in render'

I am posting here in case someone is looking for a Rails 5.2 solution to setting up Recaptcha keys. This solution utilizes the new config/master.key and config/credentials.yml.enc encryption file.
Add the Recaptcha keys to the credentials.yml.enc file by editing the file in your local terminal:
EDITOR="vim" rails credentials:edit
After adding the keys to the credentials file (see example below), save and exit the file. Upon exit, the credentials.yml.enc file will then be automatically encrypted. The master.key is necessary for decryption by your application. Before encryption:
recaptcha_site_key: 6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy
recaptcha_secret_key: 6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxx
3. Create a file named config/recaptcha.rb in your Rails application and add the following code to it:
Recaptcha.configure do |config|
config.site_key = Rails.application.credentials.dig(:recaptcha_site_key)
config.secret_key = Rails.application.credentials.dig(:recaptcha_secret_key)
end
This solution works locally and on Ubuntu/nginx in production. You won't need a gem or environment variables for it to work. If the master.key fails to decrypt, you may need to delete both the credentials.yml.enc file and possibly even the master.key file, then repeat this process locally (EDITOR="vim" rails credentials:edit, etc.) before copying over a new master.key to production and re-deploying.

I still don't know what is the cause of the "No site key specified" error.
I really don't like gem 'recapthca' works directly with ENV variables,
and also I spent too much time on investigations.
So, I decided to not use this gem and write my own code.
I use only Invisible Recaptcha in my application.
Config file (loads secret and site keys)
# /config/recaptcha.yml
default: &default
site_key: <%= ENV["RECAPTCHA_SITE_KEY"] %>
secret_key: <%= ENV["RECAPTCHA_SECRET_KEY"] %>
development:
<<: *default
test:
<<: *default
staging:
<<: *default
production:
<<: *default
Application helper (a button with Recaptcha helper)
# /app/helpers/application_helper.rb
module ApplicationHelper
def submit_with_recaptcha(text, custom_options)
unless custom_options[:data].has_key?(:form_id)
raise "Data Form Id option not found ('{data: {form_id: 'id_without_dash'}')."
end
options = {
type: 'button',
data: {
form_id: custom_options[:data][:form_id],
sitekey: recaptcha_site_key,
callback: "submit#{custom_options[:data][:form_id].camelize}#{Time.current.to_i}"
},
class: (custom_options[:class].split(' ') + ['g-recaptcha']).uniq.join(' ')
}
script_code = <<-SCRIPT
function #{options[:data][:callback]}() {
document.getElementById('#{options[:data][:form_id]}').submit();
}
SCRIPT
javascript_tag(script_code) + content_tag(:div, class: 'recaptcha_wrapper'){ submit_tag(text, options) }
end
private
def recaptcha_site_key
Rails.application.config_for(:recaptcha)['site_key']
end
end
Verification service (as it uses external API)
# app/services/google_recaptcha/verification.rb
module GoogleRecaptcha
# https://developers.google.com/recaptcha/docs/verify
class Verification
# response - params['g-recaptcha-response'])
def self.successful?(recaptcha_params, remoteip)
verify_url = URI.parse('https://www.google.com/recaptcha/api/siteverify')
verify_request = Net::HTTP::Post.new(verify_url.path)
verify_request.set_form_data(
response: recaptcha_params,
secret: secret_key,
remoteip: remoteip
)
connection = Net::HTTP.new(verify_url.host, verify_url.port)
connection.use_ssl = true
Rails.logger.info '[RECAPTCHA] Sending verification request.'
verify_response = connection.start { |http| http.request(verify_request) }
response_data = JSON.parse(verify_response.body)
Rails.logger.info "[RECAPTCHA] Verification response is#{' not' unless response_data['success']} successful."
response_data['success']
end
private
def self.secret_key
Rails.application.config_for(:recaptcha)['secret_key']
end
end
end
Controller Concern (Recaptcha verification in before_action)
# app/controllers/concerns/recaptchable.rb
module Recaptchable
extend ActiveSupport::Concern
included do
before_action :verify_recaptcha, only: [:create]
end
private
def verify_recaptcha
unless GoogleRecaptcha::Verification.successful?(recaptcha_params['g-recaptcha-response'], request.remote_ip)
render :new
return
end
end
def recaptcha_params
params.permit(:'g-recaptcha-response')
end
end
Usage
Add concern to your controller:
class MyController < ShopController
include Recaptchable
end
Add www.google.com/recaptcha/api.js javascript to your page
Add submit_with_recaptcha helper into your form
<%= form_for #delivery, url: users_delivery_path, method: 'post' do |f| %>
<%= submit_with_recaptcha t('order.deliver.to_confirmation'), data: {form_id: 'new_delivery'}, class: 'btn-round' %>
<% end %>
<%= javascript_include_tag "https://www.google.com/recaptcha/api.js?hl=#{I18n.locale}", 'data-turbolinks-track': 'reload' %>
That's it.

Note: I'm posting this answer here for people who may find this question. Here is how I solved the problem.
I use local_env.yml for my environment variables. I just started using the gem and added RECAPTCHA_SITE_KEY & RECAPTCHA_SECRET_KEY to local_env.yml. I got the same error.
It took a bit to figure out that the gem directly used the variables. I ended up putting the following statements in ~/.bashrc similar to what the documentation said but without the quotes around the values.
export RECAPTCHA_SITE_KEY=6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy
export RECAPTCHA_SECRET_KEY=6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx
I host my applications on Heroku. I executed the following terminal commands to set my environment variables in Heroku.
heroku config:set RECAPTCHA_SITE_KEY=‘6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy’
heroku config:set RECAPTCHA_SECRET_KEY=‘6LcGuI4U6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxxAAAAGAWMYRKFGfHUCSD0SPrMX2lfyl9’

Are you using nginx? Nginx removes ENV vars (except TZ) and it seems that the recaptcha gem is particularly sensitive to this. From experience, when using the dotenv gem other ENV vars work ok, recaptcha ENV vars are ignored.
You can solve the issue by adding the env vars to the top of your nginx.conf.
env RECAPTCHA_SITE_KEY=value1;
env RECAPTCHA_SECRET_KEY=value2;
Here's nginx's documentation on the matter.

Related

What's the correct way of defining secret_key_base on Rails 6?

What's the correct way of defining secret_key_base on Rails 6 now that we have per-environment credentials?
My environment has the variable SECRET_KEY_BASE but Rails is not picking it up. I tried defining secret_key_base in config\credentials\production.yml.enc but it has no effect on Rails.application.credentials.secret_key_base
I know config/secrets.yml with
staging:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
production:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
works, but, is that the Rails 6 way?
The right way to access and check for secret_key_base in Rails 6 is no longer:~
Rails.application.credentials.secret_key_base
it now is:
Rails.application.secret_key_base
I'm not sure if this is Rails 6 or it's been like this forever. This becomes pretty clear when looking at this method, and its implementation:
https://github.com/rails/rails/blob/09a2979f75c51afb797dd60261a8930f84144af8/railties/lib/rails/application.rb#L410-L427
# The secret_key_base is used as the input secret to the application's key generator, which in turn
# is used to create all MessageVerifiers/MessageEncryptors, including the ones that sign and encrypt cookies.
#
# In development and test, this is randomly generated and stored in a
# temporary file in <tt>tmp/development_secret.txt</tt>.
#
# In all other environments, we look for it first in ENV["SECRET_KEY_BASE"],
# then credentials.secret_key_base, and finally secrets.secret_key_base. For most applications,
# the correct place to store it is in the encrypted credentials file.
def secret_key_base
if Rails.env.development? || Rails.env.test?
secrets.secret_key_base ||= generate_development_secret
else
validate_secret_key_base(
ENV["SECRET_KEY_BASE"] || credentials.secret_key_base || secrets.secret_key_base
)
end
end
Both development and test mode have their own way of generating and storing the secret key base. For everything else, it pics it up from the environment, or credentials or secrets, in that order.
Docker users in development might consider this in their entrypoint.sh:
if [ "$RAILS_ENV" = "development" ]; then
printf $SECRET_KEY_BASE > ./tmp/development_secret.txt
fi
I've tried to solve that problem few days ago.
And what I learned:
First attempt
I try to use credentials per environment with
$ EDITOR=nano rails credentials:edit --environment development
$ EDITOR=nano rails credentials:edit --environment staging
$ EDITOR=nano rails credentials:edit --environment production
My creds files and keys were placed in config/credentials.
I set necessary variables straight there. It's usable solution, but we met a problem with our deployment at Kubernetes cluster, when our devopses wants to use helm configs. So, predefined credentials is not applicable for that case.
Second attempt
After that I've tried to use ENV-variables in my credentials files.
Unfortunately, it's not works too:
secret_key_base: <%= ENV['SECRET_KEY_BASE'] %>
Final attempt
Finally, I did graceful degradation to gem config with default configuration, when you per-environment settings placed there:
config/settings.yml
config/settings/development.yml
config/settings/production.yml
config/settings/test.yml
And my settings.yml file consists only ENV-variables, like so:
secret_key_base: <%= ENV['SECRET_KEY_BASE'] %>
db:
host: <%= ENV['DB_HOST'] %>
port: <%= ENV['DB_PORT'] %>
pool: <%= ENV['DB_POOL'] %>
user: <%= ENV['DB_USER'] %>
password: <%= ENV['DB_PASSWORD'] %>
database: <%= ENV['DB_DATABASE'] %>
...
It's workable solution, but seems like step-backward.
As I know now, we cant use ENV-vars in credentials any simple way.

How to use environment variable to avoid hard-coding postgresql's username and password in database.yml?

I created a new Rails app called sample_app and I use postgresql as my db (I already created a postgresql username and password). I use this setup guide https://gorails.com/setup/ubuntu/16.04
So I run this command rails new sample_app -d postgresql. And then I have to edit the config/database.yml to match the username and password to my postgresql's username and password I just created. But I don't want to hard-code because I will be using git.
I found this tutorial from digital ocean which suggest to use:
username: <%= ENV['APPNAME_DATABASE_USER'] %>
password: <%= ENV['APPNAME_DATABASE_PASSWORD'] %>
Is this the correct code? If so, since my app is called sample_app, my code should be?
username: <%= ENV['SAMPLE_APP_DATABASE_USER'] %>
password: <%= ENV['SAMPLE_APP_DATABASE_PASSWORD'] %>
If this is not the correct one, can you help me? Thank you!
There are many ways you can set the environment variables.
Here are two of them,
Option One: Setting ENV variables via a yml file
Create a file config/local_env.yml:
config/local_env.yml:
SAMPLE_APP_DATABASE_USER: 'your username'
SAMPLE_APP_DATABASE_PASSWORD: '******'
The above are the names you will use like,ENV['SAMPLE_APP_DATABASE_USER']. these can be names as your wish. you can take any name, but we should use the same name in the ENV reference.
add it to gitignore:
/config/local_env.yml
Change some code in application.rb
application.rb:
config.before_configuration do
env_file = File.join(Rails.root, 'config', 'local_env.yml')
YAML.load(File.open(env_file)).each do |key, value|
ENV[key.to_s] = value
end if File.exists?(env_file)
end
The code opens the config/local_env.yml file, reads each key/value pair, and sets environment variables.
Using Environment Variables:
username: <%= ENV['SAMPLE_APP_DATABASE_USER'] %>
password: <%= ENV['SAMPLE_APP_DATABASE_PASSWORD'] %>
Option Two: Use the Figaro Gem
The gem takes advantage of Ruby’s ability to set environment variables as well as read them. The gem reads a config/application.yml file and sets environment variables before anything else is configured in the Rails application.
Here’s how to use it. In your Gemfile, add:
gem 'figaro'
and run bundle install
The gem provides a generator:
$ bundle exec figaro install
The generator creates a config/application.yml file and modifies the .gitignore file to prevent the file from being checked into a git repository.
You can add environment variables as key/value pairs to config/application.yml:
SAMPLE_APP_DATABASE_USER: 'your username'
SAMPLE_APP_DATABASE_PASSWORD: '******'
The environment variables will be available anywhere in your application as ENV variables:
ENV["SAMPLE_APP_DATABASE_USER"]
Here are the remaining ways you can achieve the same.
You can call it anything you want...
username: <%= ENV['CARROTS'] %>
password: <%= ENV['BEANS'] %>
You just have to make sure your deploy script sets the variables CARROTS and BEANS correctly.
try this gem dotenv-rails
add this to Gemfile:
gem 'dotenv-rails', :groups => [:development, :test]
bundle it. Now create a .env file on your apps's directory with following content:
SAMPLE_APP_DATABASE_USER: "devuser"
SAMPLE_APP_DATABASE_PASSWORD: "devuser"
restart the server you're good to go. these variables are exported when you boot your app which you can access in your database.yml file
username: <%= ENV['SAMPLE_APP_DATABASE_USER'] %>
password: <%= ENV['SAMPLE_APP_DATABASE_PASSWORD'] %>
read dotenv-rails documentation for more info

Ruby on Rails, retrieving API key from secrets.yml

Can someone help me understand how to retrieve an API key if I'm storing it into secrets.yml?
If I have some kind of google API key 'yt_key':
secrets.yml
development:
secret_key_base: 390257802398523094820 #some key
yt_key: A423092389042430 #some key
test:
secret_key_base: 43208947502938530298525#some key
yt_key: A423092389042430 #some key
production:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
yt_key: <%= ENV["YT_KEY"] %>
I'm just following the examples, this is how I would set it up right?
So if I publish this to production, I would save the A423092389042430 in heroku and under YT_KEY, correct?
But in development, would I do it this way to retrieve the data:
in /config/application.rb
Yt.configure do |config|
config.api_key = '<%= ENV["YT_KEY"] %>'
end
or should this be in the the class:
module Sample
class Application < Rails::Application
Yt.configure do |config|
config.api_key = '<%= ENV["YT_KEY"] %>'
end
config.active_record.raise_in_transactional_callbacks = true
end
end
Or did I set up the configure wrong?
ENV["YT_KEY"] references the 'YT_KEY' environment variable which you'll have to set with a Heroku config variable.
In your app, you can access your secrets like this:
Rails.application.secrets.key_name
Since you're storing the 'YT_KEY' as an environment variable in production only, you should configure Yt like so:
(You can do this in a initializer file located at app/initializers/yt.rb)
Yt.configure do |config|
config.api_key = Rails.application.secrets.yt_key
end
That way, the correct key will be set in each environment.
It's good practice to use different keys for each environment, so should get another key for your production environment. Also, you should avoid storing secret production environment keys in the code. That's why it's common to use ENV variables for production keys.
Let me know if you need any clarification!
Do it this way, we are doing this way since a long time and working very well for us and this is a good convention as well.
secrets.yml
development:
secret_key_base: 390257802398523094820 #some key
yt_key: A423092389042430 #some key
test:
secret_key_base: 43208947502938530298525#some key
yt_key: A423092389042430 #some key
production:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
yt_key: <%= ENV["YT_KEY"] %>
Add these line to your application.rb file
config_files = ['secrets.yml']
config_files.each do |file_name|
file_path = File.join(Rails.root, 'config', file_name)
config_keys = HashWithIndifferentAccess.new(YAML::load(IO.read(file_path)))[Rails.env]
config_keys.each do |k,v|
ENV[k.upcase] ||= v
end
end
and now you can access yt_key this way ENV["YT_KEY"] or any other key you add like some_key to ENV["SOME_KEY"].
It's often recommended to not put your custom keys in secret.yml instead make another file like app_keys.yml and put all keys there.
You can also use Figaro gem.
Once installed, you'll have a config/application.yml file. Inside it you can store your api keys etc.:
SENDGRID_USERNAME: a-name
SENDGRID_PASSWORD: password
Now, anywhere in your .rb files, you can reference it using vars:
# Noticed how I keep my vars uppercase throughout.
ENV["SENDGRID_USERNAME"]
ENV["SENDGRID_PASSWORD"]
# Production vars go below the `production` line
production:
ENV["MY_PRODUCTION_VAR"]
If using those env keys inside your html.erb then you'll need to wrap it with <%= ... %>

APP_CONFIG["value"] not working inside ActionMailer mail templates

Using APP_CONFIG to store values for system-wide access, works great but not for ActionMailer email views. Does anyone knows how to fix this?
In load_config.rb (config folder) i load it like:
APP_CONFIG = YAML.load_file("#{Rails.root}/config/application.yml")[Rails.env]
Then in my Mailer views (HAML) I try to use them like regularly in my application like:
Welcome to our application named:
= APP_CONFIG['app_name']
How would i get access to all my APP_CONFIG values inside action mailer views?
Try to something like this:
app/controllers/user_controller.rb
def some_method
app_name = APP_CONFIG['app_name']
UserMailer.welcome_mail(app_name).deliver
end
app/mailers/user_mailer.rb
class UserMailer < ActionMailer::Base
default :from => "info#mypage.com"
def welcome_mail(app_name)
#app_name = app_name
mail(:to => "test#mypage.com", :subject => "[system] User Welcome!")
end
end
app/views/user_mailer/welcome_mail.html.haml
%p Welcome to our application named:
=#app_name
I think it should work.
I fixed it like this:
default: &default
app_name: "My APP"
app_mail: "info#..."
development:
<<: *default
...
production:
<<: *default
...
The problem was it was using the RAILS_ENV and I had not merged the default section into the production and development mode, like this its clean and you can just do APP_CONFIG["any_var"] having the default (global) ones to store google analytics etc and the ENV specific ones under development and production :)

Problem with constants

In my app, I have many constants, so I made a constants file named det_constants.yml for these constants.
/config/det_constants.yml
DEFAULTS: &DEFAULTS
company_type: { "Private" : 1,
"Public" : 2 }
development:
<<: *DEFAULTS
test:
<<: *DEFAULTS
production:
<<: *DEFAULTS
I have a constants.rb file in lib folder, which loads this constant file.
/lib/constants.rb
module Constants
# Allows accessing config variables from det_constants.yml like so:
# Constants[:abc] => xyz
def self.[](key)
unless #config
raw_config = File.read(Rails.root.to_s + "/config/det_constants.yml")
#config = YAML.load(raw_config)[Rails.env].symbolize_keys
end
#config[key]
end
def self.[]=(key, value)
#config[key.to_sym] = value
end
end
In my view file, when I do
<%= Constants[:company_type] %>
it throws an error
NameError in Vendors#index
uninitialized constant ActionView::CompiledTemplates::Constants
at line
<%= Constants[:company_type] %>
However, If i do the same thing in console, it runs properly,
ruby-1.9.2-head > Constants[:company_type]
=> {"Private"=>1, "Public"=>2}
I don't know where is the problem. if there is a new and better way to do this in Rails 3, please let me know.
Ruby version: ruby 1.9.2p110 (2010-12-20 revision 30269) [i686-linux]
Rails version: Rails 3.0.3
I imagine you need:
<% require 'constants' %>
Also, you will need to restart the server following changes in lib/; it doesn't catch them automatically even in development mode.
What about other solutions, I recommend you watch the railscast called YAML Configuration File. There are also some gems such as Settingslogic to help you with that.

Resources