how can I implement authenticate_or_request_with_http_token in grape API. Below is my code :
module Articles
class ArticleData < Grape::API
include ActionController::HttpAuthentication::Token::ControllerMethods
# http_basic do |email, password|
# user = User.find_by_email(email)
# user && user.valid_password?(password)
# end
before do
error!("401 Unauthorized", 401) unless authenticated
end
helpers do
def authenticated
authenticate_or_request_with_http_token do |token, options|
apiKey = ApiKey.where(auth_token: token).first
#ApiKey.exists?(access_token: token)
end
end
end
resource :article_data do
desc "Return all article data"
get do
Article.all
end
desc "create a new article"
## This takes care of parameter validation
params do
requires :title, type: String
requires :author, type: String
#requires :content, type: Text
end
#This takes care of creating article
post do
Article.create!({
title:params[:title],
content:params[:content],
author:params[:author],
user_id:params[:user_id],
image_url:params[:image_url]
})
end
desc "update article"
# this takes care of parameter validation
params do
requires :image_url, type: String
end
put ':id' do
Article.find(params[:id]).update({
content:params[:content],
image_url:params[:image_url]
})
end
desc "delete article by id"
# this takes care of parameter validation
params do
requires :id, type: String
end
delete ':id' do
Article.find(params[:id]).destroy!
end
end
end
end
I got this error NoMethodError (undefined method `authenticate_or_request_with_http_token' for #): when I run the curl command : curl http://localhost:3000/api/v1/article_data.json. Any help will be much appreciated. Thank you in advance.
Related
I have an almost working sign up form with Devise. Whilst it seems to work, it's not saving the fields in my Custaddress table other than the user_id. Any help to work out how to have it save the rest of the information would be great. The following is greatly truncated!!
User.rb contains:
class User < ApplicationRecord
has_one :custaddress
accepts_nested_attributes_for :custaddress
end
Custaddress contains:
class Custaddress < ApplicationRecord
has_many :orders
belongs_to :user
end
Registrations controller contains:
As you can see, this just builds on the "standard" Devise controller. There is no create or new here as I assume it's using the standard methods.
class Users::RegistrationsController < Devise::RegistrationsController
invisible_captcha only: :create
protected
def build_resource(hash = {})
self.resource = resource_class.new_with_session(hash, session)
resource.build_custaddress
# Jumpstart: Skip email confirmation on registration.
# Require confirmation when user changes their email only
resource.skip_confirmation!
# Registering to accept an invitation should display the invitation on sign up
if params[:invite] && (invite = AccountInvitation.find_by(token: params[:invite]))
#account_invitation = invite
# Build and display account fields in registration form if enabled
elsif Jumpstart.config.register_with_account?
account = resource.owned_accounts.first
account ||= resource.owned_accounts.new
account.account_users.new(user: resource, admin: true)
end
end
def update_resource(resource, params)
# Jumpstart: Allow user to edit their profile without password
resource.update_without_password(params)
end
def sign_up(resource_name, resource)
if cookies[:ordernum]
order = Order.where(ordernum: cookies[:ordernum]).first
if order
order.update!(user: resource, custaddress: resource.custaddress)
cookies.delete "ordernum"
end
end
sign_in(resource_name, resource)
# If user registered through an invitation, automatically accept it after signing in
if params[:invite] && (account_invitation = AccountInvitation.find_by(token: params[:invite]))
account_invitation.accept!(current_user)
# Clear redirect to account invitation since it's already been accepted
stored_location_for(:user)
end
end
end
My new.html.erb contains:
<%= form_with(model: resource, as: resource_name, url: registration_path(resource_name, invite: params[:invite])) do |f| %>
<%= f.fields_for :custaddress do |cust| %>
<div class="form-group">
<%= cust.label "Apartment/Unit Number", class: "font-bold" %>
<%= cust.text_field :apartment, class: "form-control", placeholder: "Unit 2 or Apartment 307" %>
</div>
And much more!
My application controller has:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
include SetCurrentRequestDetails
include SetLocale
include Jumpstart::Controller
include Accounts::SubscriptionStatus
include Users::NavbarNotifications
include Users::TimeZone
include Pagy::Backend
include CurrentHelper
include Sortable
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :masquerade_user!
before_action :store_user_location!, if: :storable_location?
protected
# To add extra fields to Devise registration, add the attribute names to `extra_keys`
def configure_permitted_parameters
extra_keys = [:avatar, :first_name, :last_name, :time_zone, :preferred_language]
signup_keys = extra_keys + [:terms_of_service, :invite, owned_accounts_attributes: [:name], custaddress_attributes: [:address, :apartment, :city, :state, :country, :postcode, :mobile]]
devise_parameter_sanitizer.permit(:sign_up, keys: signup_keys)
devise_parameter_sanitizer.permit(:account_update, keys: extra_keys)
devise_parameter_sanitizer.permit(:accept_invitation, keys: extra_keys)
end
def after_sign_in_path_for(resource_or_scope)
stored_location_for(resource_or_scope) || super
end
# Helper method for verifying authentication in a before_action, but redirecting to sign up instead of login
def authenticate_user_with_sign_up!
unless user_signed_in?
store_location_for(:user, request.fullpath)
redirect_to new_user_registration_path, alert: t("create_an_account_first")
end
end
def require_current_account_admin
unless current_account_admin?
redirect_to root_path, alert: t("must_be_an_admin")
end
end
private
def storable_location?
request.get? && is_navigational_format? && !devise_controller? && !request.xhr?
end
def store_user_location!
# :user is the scope we are authenticating
store_location_for(:user, request.fullpath)
end
end
My logs are showing:
Processing by Users::RegistrationsController#create as JS
17:08:57 web.1 | Parameters: {"authenticity_token"=>"uVphxW4gCQntvHFxRb33dl9cqxv9vlL69Wc2zOMoF1M+pUk8c2HnHwgQFIkMbfmxYraVI7rYBVCPgfSD1u7OHg==", "user"=>{"first_name"=>"[FILTERED]", "last_name"=>"[FILTERED]", "email"=>"[FILTERED]", "password"=>"[FILTERED]", "time_zone"=>"Sydney", "custaddress_attributes"=>{"apartment"=>"", "address"=>"XXXXXXXX", "city"=>"XXXXXXX", "state"=>"XXXXX", "postcode"=>"XXXX", "mobile"=>"XXXXXXXX", "country"=>"XXXXXXX"}, "terms_of_service"=>"1"}, "enc-rmjxhdab"=>"", "button"=>""}
So, I know it's getting the information from the form. However I have no idea where it's saving the Custaddress record. It only seems to be associating the user_id:
Custaddress Create (0.2ms) INSERT INTO "custaddresses" ("user_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["user_id", 3], ["created_at", "2020-09-25 02:20:39.109187"], ["updated_at", "2020-09-25 02:20:39.109187"]]
12:20:39 web.1 | (33.9ms) COMMIT
Can anyone please help me work out why the custaddress isn't saving. I've spent hours on this and read every article on google (well it certainly feels like it).
I've tried to track this down, to no avail.
Adding an answer for completeness.
Devise's create action calls the build_resurce method that you are overriding. the problem is that you overrid it for the new action but didn't take into account the create action.
So, you are doing this:
def build_resource(hash = {})
self.resource = resource_class.new_with_session(hash, session)
resource.build_custaddress
For the new action there's no issue, resource.build_custaddress will instantiate a new custaddress object for you to be able to use the fields_for helper.
The problem is that, for the create action, at that point resource already has a custaddress with the values from the request (set by the previous line), then you do resource.build_custaddress and replace the current custaddress with a new empty one.
The solution is to only build a custaddress if it's not nil:
def build_resource(hash = {})
self.resource = resource_class.new_with_session(hash, session)
resource.build_custaddress if resource.custaddress.nil?
That way you'll have a new empty custaddress for the new action, but respect the values from the request for the create, edit or update actions.
I build an Rest API with rails. And I want to implement an expiration for the token. How does it works ?
I don't to implement devise because I don't need that really.
I just want when I create an user he received an token a refresh token.
So it's not really oauth because there are not 3rd party using the api.
This is my user model.
require 'securerandom'
class User
field :auth_token, type: String
field :name, type: String
field :phone, type: String
field :image, type: String
#Generate URLS for different image sizes...
def as_json(options)
json = super
self.image.styles.each do | format |
json = json.merge({"image_"+format[0].to_s => self.image(format[0])})
end
json
end
private
def set_auth_token
return if auth_token.present?
self.auth_token = generate_auth_token
end
def generate_auth_token
SecureRandom.hex(90)
end
end
So simple auth with a simple generated token works. But I think with an expiration token is more secure. Of course the connection is over SSL.
class ApplicationController < ActionController::API
include ActionController::HttpAuthentication::Token::ControllerMethods
def current_user
#current_user = User.find(params[:user_id])
end
protected
def authenticate
authenticate_token || authentication_request
end
def authenticate_token
authenticate_or_request_with_http_token do |token, options|
User.where(auth_token: token).first
end
end
def authentication_request(controller, realm)
controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.gsub(/"/, "")}")
controller.__send__ :render, :text => "HTTP Token: Access denied.\n", :status => :unauthorized
end
def request_http_token_authentication(realm = "Application")
self.headers["WWW-Authenticate"] = %(Token realm="#{realm.gsub(/"/, "")}")
render :json => {:error => "HTTP Token: Access denied."}, :status => :unauthorized
end
end
When you generate the token, save the time you'd like it to expire:
class User
field :auth_token, type: String
field :token_expiry, type: Time
def set_auth_token
return if auth_token.present? && token_expiry > Time.now
self.auth_token = generate_auth_token
self.token_expiry = Time.now + 1.day
end
Then when you check the token, check the expiry too:
def authenticate_token
authenticate_or_request_with_http_token do |token, options|
user = User.where(auth_token: token).first
user if user.token_expiry > Time.now
end
end
Can you give an advice or recommend some resources related to this topic? I understand how to it in a theory. But I also heard about jwt etc. What are the best practices to implement device/angular/rails role based auth/registration?
The short answer is to read this blog post which goes into details of how the concept is minimally implemented
This would be a long code answer, but I plan to write separate blog post on how to implement it in much more details...
but for now, here is how I implemented it in some project...
First the angular app part, you can use something like Satellizer which plays nicely...
here is the angular auth module in the front-end app
# coffeescript
config = (
$authProvider
$stateProvider
) ->
$authProvider.httpInterceptor = true # to automatically add the headers for auth
$authProvider.baseUrl = "http://path.to.your.api/"
$authProvider.loginRedirect = '/profile' # front-end route after login
$authProvider.logoutRedirect = '/' # front-end route after logout
$authProvider.signupRedirect = '/sign_in'
$authProvider.loginUrl = '/auth/sign_in' # api route for sign_in
$authProvider.signupUrl = '/auth/sign_up' # api route for sign_up
$authProvider.loginRoute = 'sign_in' # front-end route for login
$authProvider.signupRoute = 'sign_up' # front-end route for sign_up
$authProvider.signoutRoute = 'sign_out' # front-end route for sign_out
$authProvider.tokenRoot = 'data'
$authProvider.tokenName = 'token'
$authProvider.tokenPrefix = 'front-end-prefix-in-localstorage'
$authProvider.authHeader = 'Authorization'
$authProvider.authToken = 'Bearer'
$authProvider.storage = 'localStorage'
# state configurations for the routes
$stateProvider
.state 'auth',
url: '/'
abstract: true
templateUrl: 'modules/auth/auth.html'
data:
permissions:
only: ['guest']
redirectTo: 'profile'
.state 'auth.sign_up',
url: $authProvider.signupRoute
views:
'sign_up#auth':
templateUrl: 'modules/auth/sign_up.html'
controller: 'AuthenticationCtrl'
controllerAs: 'vm'
.state 'auth.sign_in',
url: $authProvider.loginRoute
views:
'sign_in#auth':
templateUrl: 'modules/auth/sign_in.html'
controller: 'AuthenticationCtrl'
controllerAs: 'vm'
this is the basic configurations for satellizer... as for the authentication controller... it's something like following
#signIn = (email, password, remember_me) ->
$auth.login
email: email
password: password
remember_me: remember_me
.then(success, error)
return
#signUp = (name, email, password) ->
$auth.signup
name: name
email: email
password: password
.then(success, error)
return
this is the basics for authenticating
as for the backend (RoR API) you should first allow CORS for the front-end app. and add gem 'jwt' to your gemfile.
second implement the API controller and the authentication controller
for example it might look something like the following
class Api::V1::ApiController < ApplicationController
# The API responds only to JSON
respond_to :json
before_action :authenticate_user!
protected
def authenticate_user!
http_authorization_header?
authenticate_request
set_current_user
end
# Bad Request if http authorization header missing
def http_authorization_header?
fail BadRequestError, 'errors.auth.missing_header' unless authorization_header
true
end
def authenticate_request
decoded_token ||= AuthenticationToken.decode(authorization_header)
#auth_token ||= AuthenticationToken.where(id: decoded_token['id']).
first unless decoded_token.nil?
fail UnauthorizedError, 'errors.auth.invalid_token' if #auth_token.nil?
end
def set_current_user
#current_user ||= #auth_token.user
end
# JWT's are stored in the Authorization header using this format:
# Bearer some_random_string.encoded_payload.another_random_string
def authorization_header
return #authorization_header if defined? #authorization_header
#authorization_header =
begin
if request.headers['Authorization'].present?
request.headers['Authorization'].split(' ').last
else
nil
end
end
end
end
class Api::V1::AuthenticationsController < Api::V1::ApiController
skip_before_action :authenticate_user!, only: [:sign_up, :sign_in]
def sign_in
# getting the current user from sign in request
#current_user ||= User.find_by_credentials(auth_params)
fail UnauthorizedError, 'errors.auth.invalid_credentials' unless #current_user
generate_auth_token(auth_params)
render :authentication, status: 201
end
def sign_out
# this auth token is assigned via api controller from headers
#auth_token.destroy!
head status: 204
end
def generate_auth_token(params)
#auth_token = AuthenticationToken.generate(#current_user, params[:remember_me])
end
end
The AuthenticationToken is a model used to keep track of the JWT tokens ( for session management like facebook)
here is the implementation for the AuthenticationToken model
class AuthenticationToken < ActiveRecord::Base
## Relations
belongs_to :user
## JWT wrappers
def self.encode(payload)
AuthToken.encode(payload)
end
def self.decode(token)
AuthToken.decode(token)
end
# generate and save new authentication token for the user
def self.generate(user, remember_me = false)
#auth_token = user.authentication_tokens.create
#auth_token.token = AuthToken.generate(#auth_token.id, remember_me)
#auth_token.save!
#auth_token
end
# check if a token can be used or not
# used by background job to clear the authentication collection
def expired?
AuthToken.decode(token).nil?
end
end
it uses a wrapper called AuthToken which wraps the JWT functionality
here is it's implementation
# wrapper around JWT to encapsulate it's code
# and exception handling and don't polute the AuthenticationToken model
class AuthToken
def self.encode(payload)
JWT.encode(payload, Rails.application.secrets.secret_key_base)
end
def self.decode(token)
payload = JWT.decode(token, Rails.application.secrets.secret_key_base)[0]
rescue JWT::ExpiredSignature
# It will raise an error if it is not a token that was generated
# with our secret key or if the user changes the contents of the payload
Rails.logger.info "Expired Token"
nil
rescue
Rails.logger.warn "Invalid Token"
nil
end
def self.generate(token_id, remember_me = false)
exp = remember_me ? 6.months.from_now : 6.hours.from_now
payload = { id: token_id.to_s, exp: exp.to_i }
self.encode(payload)
end
end
I have a Company model with attr_accessor :administrator, so when user creates company, he also need to fill some fields for administrator of this company. I'm trying to test, that he fill all fields correctly.
class Company < ActiveRecord::Base
attr_accessor :administrator
validates :name, presence: true
validates :administrator, presence: true, if: :administrator_is_valid?
private
def administrator_is_valid?
administrator[:name].present? and
administrator[:phone].present? and
administrator[:email].present? and
administrator[:password].present? and
administrator[:password_confirmation].present? and
administrator[:password] == administrator[:password_confirmation]
end
end
company_spec.rb is:
require 'rails_helper'
describe Company do
it 'is valid with name and administrator' do
company = Company.new(name: 'Company',
administrator: {
name: nil,
email: nil,
phone: nil,
password: 'password',
password_confirmation: ''
})
expect(company).to be_valid
end
end
So, as you see, I have a lot of mistakes in validation test, but RSpec pass it.
Thanks!
That's because you didn't construct your validation properly. See, if: administrator_is_valid? will return false for your test, telling Rails to skip this validation rule.
I suggest you drop using the presence validator in favor of using administrator_is_valid? method as a validation method, because after all, if the administrator is valid then it is present. The code should look like this
validate :administrator_is_valid?
private
def administrator_is_valid?
(administrator[:name].present? and
administrator[:phone].present? and
administrator[:email].present? and
administrator[:password].present? and
administrator[:password_confirmation].present? and
administrator[:password] == administrator[:password_confirmation]) or
errors.add(:administrator, 'is not valid')
end
You could clean up your code like this:
validate :administrator_is_valid?
private
def administrator_is_valid?
if administrator_cols_present? && administrator_passwords_match?
true
else
errors.add(:administrator, 'is not valid')
end
end
def administrator_cols_present?
%w(name phone email password password_confirmation).all? do |col|
administrator[col.to_sym].present? # or use %i() instead of to_sym
end
end
def administrator_passwords_match?
administrator[:password] == administrator[:password_confirmation]
end
Another improvement might be to move your administrator to a struct, then call valid? on the object.
admin = Struct.new(cols) do
def valid?
cols_present? && passwords_match?
end
def cols_present?
cols.values.all? { |col| col.present? }
end
def passwords_match?
cols[:password] == cols[:password_confirmation]
end
end
Then:
validate :administrator_is_valid?
def admin_struct
#admin_struct ||= admin.new(administrator)
end
def administrator_is_valid?
errors.add(:administrator, 'is not valid') unless admin_struct.valid?
end
I'm working with a team on checking a user's email input when they sign up for a web app. The user will not be allowed to sign up if their email is not found with the following API call using HTTParty. We are getting method_errors for whatever syntax is first within the function. For, example, in the method below, "include" comes up as an undefined method error.
def email_checker
include HTTParty
default_params :output => 'json'
format :json
base_uri 'app.close.io'
basic_auth 'insert_api_code_here', ' '
response = HTTParty.get('/api/v1/contact/')
#email_database = []
response['data'].each do |x|
x['emails'].each do |contact_info|
#email_database << contact_info['email']
end
end
unless #email_database.include? :email
errors.add :email, 'According to our records, your email has not been found!'
end
end
UPDATE: So we went with the inline version of using HTTParty and our registrations controller (working with devise) looks like this:
class RegistrationsController < Devise::RegistrationsController
def email_checker(email)
YAML.load(File.read('config/environments/local_env.yml')).each {|k, v| ENV[k.to_s] = v}
api_options = {
query: => {:output => 'json'},
format: :json,
base_uri: 'app.close.io',
basic_auth: ENV["API_KEY"], ' '
}
response = HTTParty.get('/api/v1/contact/', api_options)
#email_database = []
response['data'].each do |x|
x['emails'].each do |contact_info|
#email_database << contact_info['email']
end
end
unless #email_database.include? email
return false
else
return true
end
end
def create
super
if email_checker == false
direct_to 'users/sign_up'
#and return to signup with errors
else
User.save!
end
end
end
We're getting syntax error: "syntax error, unexpected =>" Did we screw up the format?
There are two different ways to use HTTParty, and you're trying to use both. Pick one :).
The class-based method would look something like this:
class CloseIo
include HTTParty
default_params :output => 'json'
format :json
base_uri 'app.close.io'
basic_auth 'insert_api_code_here', ' '
end
class UserController
def email_checker
response = CloseIo.get('/api/v1/contact/')
# ... the rest of your stuff
end
end
An inline version would look something like this
class UserController
def email_checker
api_options = {
query: :output => 'json',
format: :json,
base_uri: 'app.close.io',
basic_auth: 'insert_api_code_here'
}
response = HTTParty.get('/api/v1/contact/', api_options)
# ... do stuff
end
end