Adding devise confirmable module within web api or any other technique for same functionality? - ruby-on-rails-4.1

I would like to add devise confirmable functionality to web service, I would like to know steps to integrate it, code in registration_controller.rb :-
class Api::RegistrationsController < Devise::RegistrationsController
skip_before_filter :verify_authenticity_token
respond_to :json
def create
user = User.new(user_params)
if user.save
render(
json: Jbuilder.encode do |j|
j.sucess true
j.count 1
j.type "userObject"
j.message "signed up successfully."
j.data user, :email, :role, :authentication_token, :activation_state
end,
status: 201
)
return
else
warden.custom_failure!
render :json=> user.errors, :status=>422
end
end
protected
def user_params
params.require(:user).permit!
#params[:user].permit(:email, :password, :password_confirmation)
end
end

Related

Rspec test cookie value always nil

guys! I'm facing a problem (rails 7.0.4) I need to test a controller's method called set_locale. It sets a cookie[:locale] with one of these values: :en, :pt-BR, sets the locale with cookies[:locale] value and then redirects to the current page translated with the chosen language. Everything is working fine when using the app but I can't write a good test. This is the LocalesController:
class LocalesController < ApplicationController
before_action :set_locale
def default_locale_option
cookies.delete(:locale)
cookies.permanent[:locale] = I18n.default_locale # save cookies with default language
end
def set_locale
if params[:locale].present?
if params[:locale] == 'default'
default_locale_option
else
cookies.permanent[:locale] = params[:locale] # save cookies
end
end
locale = cookies[:locale]&.to_sym # this reads cookies
if I18n.available_locales.include?(locale)
I18n.locale = locale # use cookies locale
redirect_to request.referrer # to the same page
end
end
end
This is the test that I wrote. I'm trying to pass the value :en but it says : "expected: :en got: nil". I'm new in testing stuff...any idea?
require 'rails_helper'
RSpec.describe "Locales", type: :request do
describe "GET /set_locale" do
it "returns http found" do
get set_locale_path
expect(response).to have_http_status(:found)
end
end
describe "check language switch" do
it "should change the language from pt_BR to English" do
#pt-Br is the default language in I18n
get set_locale_path, params: {locale: :en}
expect(cookies[:locale]&.to_sym).to eq(:en)
end
end
end
Thanks in advance!

Error: Form responses must redirect to another location

I need to render an html code I receive from an API.
In Rails 6 : I was doing this in my controller, and it was working fine. I called the webservice I received the response, and I was redirected to the code generated by the render. Fine !
class GatewayController < ApplicationController
def new
init_gateway_call
end
def create
call_gateway
render_gateway_response
end
private
...
def render_gateway_response
render(html: #gateway_response.message.html_safe)
end
end
new.html.erb :
<%= form_with url: gateway_path, local: true do |f| %>
...
<% end %>
And no : create.html.erb
** Rails 7 **
I call the webservice. I get the answer but my page idle and I get this error.
Error: Form responses must redirect to another location at FormSubmission.requestSucceededWithResponse (application-0f0c10fb8f5683e32fc53a93a8a323c328de61682ca16fb65a6a2b8a3ba5d087.js:1614)
at FetchRequest.receive (application-0f0c10fb8f5683e32fc53a93a8a323c328de61682ca16fb65a6a2b8a3ba5d087.js:1390)
at FetchRequest.perform (application-0f0c10fb8f5683e32fc53a93a8a323c328de61682ca16fb65a6a2b8a3ba5d087.js:1374)
So far, I tried:
# GatewayController
respond_to :create, format: :html, gateway_response: #gateway_response.message.html_safe
<%= gateway_response %>
Without success ... Do you have any idea? Otherwise it is going to be a long weekend ^^
I figured it out you while posting my question. The error message seems like a Turbo error. I had to had data-turbo false to my form.
<%= form_with url: gateway_path, local: true, data: { turbo: false } do |f| %>
...
<% end %>
And keep my controller like it was.
render(html: #gateway_response.message.html_safe)
Happy upgrade anyone
Setting data: {turbo: false} will cause the page to reload entirely. This takes away the entire point of turbo which is to reduce page reloads.
The reason the error occurs is because Turbo expects a 303 redirect response. The solution is to have the server respond with 422 or 500 status code when you are not redirecting.
if save
redirect_to root_path
else
render :new, status: 422
You can read about this here: https://turbo.hotwired.dev/handbook/drive#redirecting-after-a-form-submission
Of course, if you want the page to reload, you can use data-turbo: false
Thanks! I'm also developing on Rails 7 and data: { turbo: false } fixed my issue.
Instead of using turbo: false as suggested on the answer you need to add appropriate status like :see_other for redirects and 422 (unprocessible_entity) in case render.
render :new, status: 422
redirect_to xyz_path, status: :see_other

Fastapi auth with OAuth2PasswordBearer, how to check if an user is connected without raise an exception?

For an application, I have followed the fastAPI documentation for the authentification process.
By default, OAuth2PasswordBearer raise an HTTPException with status code 401. So, I can't check if an user is actually connected without return a 401 error to the client.
An example of what I want to do:
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/users/token")
def get_current_user(token: str = Depends(oauth2_scheme)):
try:
settings = get_settings()
payload = jwt.decode(token, settings.secret_key,
algorithms=[settings.algorithm_hash])
email = payload.get("email")
if email is None:
raise credentials_exception
token_data = TokenData(email=email)
except jwt.JWTError:
raise credentials_exception
user = UserNode.get_node_with_email(token_data.email)
if user is None:
raise credentials_exception
return user
#app.get('/')
def is_connected(user = Depends(get_current_user)
# here, I can't do anything if the user is not connected,
# because an exception is raised in the OAuth2PasswordBearer __call__ method ...
return
I see OAuth2PasswordBearer class have an "auto_error" attribute, which controls if the function returns None or raises an error:
if not authorization or scheme.lower() != "bearer":
if self.auto_error:
raise HTTPException(
status_code=HTTP_401_UNAUTHORIZED,
detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"},
)
else:
return None
So i think about a workaround:
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/users/token", auto_error=False)
def get_current_user(token: str = Depends(oauth2_scheme)):
if not token:
return None
# [ ... same token decoding logic than before ... ]
return user
#app.get('/')
def is_connected(user = Depends(get_current_user)
return user
It works, but I wonder what other ways there are to do this, is there a more "official" method?
This is a good question and as far as I know, there isn't an "official" answer that is universally agreed upon.
The approach I've seen most often in the FastAPI applications that I've reviewed involves creating multiple dependencies for each use case.
While the code works similarly to the example you've provided, the key difference is that it attempts to parse the JWT every time - and doesn't only raise the credentials exception when it does not exist. Make sure the dependency accounts for malformed JWTs, invalid JWTs, etc.
Here's an example adapted to the general structure you've specified:
# ...other code
oauth2_scheme = OAuth2PasswordBearer(
tokenUrl="api/users/token",
auto_error=False
)
auth_service = AuthService() # service responsible for JWT management
async def get_user_from_token(
token: str = Depends(oauth2_scheme),
user_node: UserNode = Depends(get_user_node),
) -> Optional[User]:
try:
email = auth_service.get_email_from_token(
token=token,
secret_key=config.SECRET_KEY
)
user = await user_node.get_node_with_email(email)
return user
except Exception:
# exceptions may include no token, expired JWT, malformed JWT,
# or database errors - either way we ignore them and return None
return None
def get_current_user_required(
user: Optional[User] = Depends(get_user_from_token)
) -> Optional[User]:
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="An authenticated user is required for that action.",
headers={"WWW-Authenticate": "Bearer"},
)
return user
def get_current_user_optional(
user: Optional[User] = Depends(get_user_from_token)
) -> Optional[User]:
return user

How to test a controller concern methods with RSpec in Rails 5 API app

I have the following controller concern module:
#controllers/concerns/response.rb
module Response
extend ActiveSupport::Concern
def json_response(object, status = :ok, opts = {})
response = {json: object, status: status}.merge(opts)
render response
end
...
end
ApplicationController includes it as follows:
class ApplicationController < ActionController::API
include Response
...
end
How would it be possible to test the above concern methods? What kind of RSpec tests should it be (controller, request)?
I tried to define a shared_examplesas follows:
#spec/shared/json_response.rb
require 'rails_helper'
RSpec.shared_examples 'JSON Responsive controller' do |controller_class|
let(:controller_class) { controller_class }
it 'render JSON response' do
expect(controller_class).to respond_to(:json_response)
end
end
and to use it in a controller spec:
#spec/controllers/concerns/fake_controller.rb
require 'rails_helper'
class FakeController < ApplicationController
end
RSpec.describe FakeController, type: :controller do
it_behaves_like 'JSON Responsive controller', FakeController
end
but it fails with:
Failures:
1) FakeController behaves like JSON Responsive controller render JSON response
Failure/Error: expect(controller_class).to respond_to(:json_response)
expected FakeController to respond to :json_response
Shared Example Group: "JSON Responsive controller" called from ./spec/controllers/concerns/fake_controller_spec.rb:7
# ./spec/shared/json_response.rb:7:in `block (2 levels) in <main>'
Finished in 0.23535 seconds (files took 1.11 seconds to load)
1 example, 1 failure
What am I missing ?
Here is the solution I came to to make it work.
Create a controller spec in spec/controller/fake_controller_spec.rb as follows:
require 'rails_helper'
class FakeController < ApplicationController
def render(*args)
args.first
end
end
RSpec.describe FakeController, type: :controller do
it_should_behave_like "JSON Responsive controller", FakeController
end
I had to override render(*args) method to be able to call render from inside Response module concern.
Create a shared_examples spec in spec/shared/json_response.rb:
require 'rails_helper'
RSpec.shared_examples 'JSON Responsive controller' do |including_controller|
let(:instance) { including_controller.new }
it 'should respond to #json_response' do
expect(instance).to respond_to(:json_response)
end
it 'should respond #respond_with_errors' do
expect(instance).to respond_to(:respond_with_errors)
end
it 'should respond to #paginated_response_status' do
expect(instance).to respond_to(:paginated_response_status)
end
context '#paginated_response_status' do
it 'returns 200 if collection is not paginated' do
expect(instance.paginated_response_status([1])).to eq :ok
end
it 'returns 206 if collection is paginated' do
collection = (1..35).to_a
expect(instance.paginated_response_status(collection)).to eq :partial_content
end
end
context '#respond_with_errors' do
it 'returns :unprocessable_entity status' do
model = double(:model)
errors = double(:errors, messages: {})
allow(model).to receive(:errors).and_return(errors)
response = instance.respond_with_errors(model)
expect(response[:status]).to eq :unprocessable_entity
end
end
context '#json_response' do
it 'returns JSON with default :ok status' do
model = double(:model)
response = instance.json_response(model)
expect(response[:status]).to eq :ok
end
it 'returns JSON with the specified status' do
model = double(:model)
response = instance.json_response(model, :partial_content)
expect(response[:status]).to eq :partial_content
end
end
end
Note, to be able to use shared example deined in shared folder, you have to add the following to rails_helper.rb file:
Dir[Rails.root.join('spec/shared/**/*.rb')].each { |f| require f }
...
RSpec.configure do |config|
..
end
Finally, here is the code to test defined in controllers/concerns/response.rb:
module Response
extend ActiveSupport::Concern
def json_response(object, status = :ok, opts = {})
response = {json: object, status: status}.merge(opts)
render response
end
def respond_with_errors(object)
render json: { errors: ErrorSerializer.serialize(object) }, status: :unprocessable_entity
end
def paginated_response_status(collection)
collection.size > WillPaginate.per_page ? :partial_content : :ok
end
end
ErrorSerializer is just another module that creates a JSON to return in case of errors:
#controllers/concerns/error_serializer.rb
module ErrorSerializer
extend ActiveSupport::Concern
def self.serialize(object)
object.errors.messages.map do |field, errors|
errors.map do |error_message|
{
status: 422,
title: 'Invalid attribute',
source: { pointer: "/data/attributes/#{field}" },
detail: error_message
}
end
end.flatten
end
end
Hope this helps.

Can't access faraday params on views

I have an API which has a database and another rails APP without active record, I'm also using ActiveResource to manage some db queries
My Front-end app controller is
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
skip_before_action :verify_authenticity_token
end
and i have an action to authenticate user from my front end to the API
from the front-end i have this method
def create_session
conn = Faraday.new(:url => 'http://localhost:3001')
conn.post '/auth_user', { :email => params[:email], :password => params[:password] }
end
and on API
def auth_user
user = User.find_by(email: params[:email])
if user && user.authenticate(params[:password])
conn = Faraday.new(:url => 'http://localhost:3000')
conn.post '/add_session', { :status => "right", :user => user.id }
else
conn = Faraday.new(:url => 'http://localhost:3000')
conn.post '/add_session', { :status => "wrong" }
end
end
Note that i have access to params[:email] and params[:password] and i get the right status to the front end
Then inside the front end i cannot access params[:status] or params[:user] excpet on byebug
i tried to do session[:user_id] = params[:user] and it's not working
what am i missing here ?
After some googling and playing around, I found out that i was doing it the wrong way.
Because the action has been executed and all it's variable has gone -As far as i could understand- so i managed to get this info by executing:
conn.get '/auth_user'
Then i could receive a response with json info which i need.

Resources