ng-token-auth passwordResetSuccessUrl not redirecting - ruby-on-rails

I am building a rails app with AngularJs on the front end. When resetting a users' password, the email sends as expected, however the reset link does not redirect to the configured passwordResetSuccessUrl and simply goes to:
http://localhost:8000/auth/password/edit?reset_password_token=KNsWjzizNWNsMqzSss8F
As you can see, the redirect_url is not there, should I see it in the url? Also, when clicking through the link, the app does not fire the auth:password-reset-confirm-success event as noted in ng-token-auth documentation.
here's the request:
{"email":"myemail#gmail.com","redirect_url":"http://localhost:8000/#!/updatePassword"}
the response is a 500 error (i find this odd, the email is sent and no error in the server log) with the message:
{"message":"password reset email sent"}
Here's some relevant configuration code:
function authConfig($authProvider,envServiceProvider) {
let url = envServiceProvider.is('development') ? 'http://localhost:8000' : 'https://miles-board.herokuapp.com';
$authProvider.configure([{
'default': {
apiUrl: url,
emailRegistrationPath: '/users',
confirmationSuccessUrl: window.location.href,
validateOnPageLoad: false,
passwordResetSuccessUrl: url+'/#!/updatePassword'
}},
{'user': {
apiUrl: url,
emailRegistrationPath: '/users',
confirmationSuccessUrl: window.location.href,
validateOnPageLoad: false,
passwordResetSuccessUrl: url+'/#!/updatePassword'
}},
{'team_owner' : {
apiUrl: url,
emailRegistrationPath: '/team_owners',
confirmationSuccessUrl: window.location.href,
validateOnPageLoad: false,
passwordResetSuccessUrl: url+'/#!/updatePassword'
}
}]
);
console.log('is dev: ', envServiceProvider.is('development'))
}
I had issues with the email confirmation, as well, so I assume I've missed something in my set up, but I'm not sure what.
Being new to rails, I have to ask a very newbie question: I have the devise_token_auth gem installed, do I need to install devise as well, or does the devise_token_auth include that?
In my devise_token_auth.rb intializer, I have included this as well:
config.default_password_reset_url = 'http://localhost:8000/#!/updatePassword'
Please let me know if there's any other information I can provide.

Okay, I got this working, here's what I had to do go get it working with an AngularJS front end and Rails back:
Override the PasswordsController create, update, edit, and after_resetting_password_path_for
For the create and update functions, the primary issue was that I needed it to render a json response, so where it says something like respond_with resource I changed to render json: resource, status: <status>, && return (you can change resource and status to what you need for your application, same with the render method)
For edit, instead of using after_sending_reset_password_instructions_path_for, I grabbed the redirect URL from the email and simply do a redirect_to params[:redirect_url]
and I changed after_resetting_password_path_for to redirect where I want the user to be logged in to.
I also had to change the reset_password_instructions.html.erb template. the line which contains edit_password_url to this:
<p><%= link_to t('.password_change_link'), edit_password_url(#resource, reset_password_token: #token, config: 'default', redirect_url: message['redirect-url'].to_s+'?reset_token='+#token).html_safe %></p>
Then in routes.rb, I had to let devise know to use my controller:
mount_devise_token_auth_for 'User', at: 'auth', controllers: { passwords: 'passwords' }
I'm not thrilled about the redirect URL portion in edit, it has a code smell to it that I don't exactly like, but it works. If some one has advice on that front, I would appreciate it!

Related

errror 422 after making api call from react to rails

I have a RoR api and I'm building a react with redux page. I'm calling my user's todos.
This is how it works so far: When it sign up, i store the token key in the state, and then use that to make an api call to get my todos (index).
I have my action here:
def index
#todos = current_user.todos
json_response(#todos)
end
This is the block that is failing:
loginHandler() {
const { email, password } = this.state;
const { login, tokenize, token } = this.props;
axios.post('http://localhost:3001/auth/login', {withCredentials: true,
email: email, password: password})
.then(response => {
login(true);
tokenize(response.data.auth_token);
axios.get('http://localhost:3001/todos', {Authorization: token}) // Error occurs
.then(response => {
console.log(response);
}).catch(error => console.log(error));
}).catch(error => console.log(error));
}
Where tokenize and login are actions passed to redux. The error is in the second axios call, where I'm getting a 422 Unprocessable Entity.
I made a manual test with httpie from my terminal and there, it works.
Also debug my todos#index action and this is the error from the server-side:
AbstractController::DoubleRenderError (Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".).
My RoR is 5.2 and ruby 2.6.3.
The login works, the signup works, but the get method for todos is not working ONLY in react. In httpie from my terminal it does work. I've been fighting for a while with this one and no resource online has been really helpful, which made me think there's some typo there, that I haven't seen.
Authorization should be sent in headers:
axios.get('http://localhost:3001/todos', { headers : { Authorization: token } })
https://flaviocopes.com/axios-send-authorization-header/

setting erb template in Rails with mailgun-ruby

I would like to use the mailgun-ruby MessageBuilder API to build and send application emails in my production environment. The documentation suggests that I do something like this:
def confirmation_instructions(record, token, opts={})
mg_client = Mailgun::Client.new ENV['MAILGUN_API_KEY']
mb_obj = Mailgun::MessageBuilder.new
mb_obj.from("from#site.com", {"first" => "First", "last" => "Last"})
mb_obj.add_recipient(:to, record.email)
mb_obj.subject("Please confirm your account")
mb_obj.body_html("<p>how would i pass the default confirmation_instructions erb template as an argument to this method?</p>")
mg_client.send_message("mail.site.com", mb_obj)
end
The problem is, once I started using Mailgun, I also stopped using ActionMailer's default mail() syntax. This seems to prevent Rails from manually selecting the template that matches the method name.
My first thought is I need to select it manually. An answer here says that I can change the template like this:
mail(to: user.email, subject: "New Projects") do |format|
format.html { render layout: layout_name }
format.text { render layout: layout_name }
end
But this syntax conflicts with what is expected by mailgun-ruby. I can't leave the body/text setting methods out, and if I do, it throws an error: Mailgun::CommunicationError (400 Bad Request: Need at least one of 'text' or 'html' parameters specified).
In the first example above, how would I pass the proper layout to body_html? That is, without directly writing the html in as an argument?
Update: my rake routes for confirmations
new_user_confirmation GET /users/confirmation/new(.:format) users/confirmations#new
user_confirmation GET /users/confirmation(.:format) users/confirmations#show
POST /users/confirmation(.:format) users/confirmations#create
update_user_confirmation PATCH /users/confirmation(.:format) users/confirmations#update
Looking at the gem's issues, it looks like it is not supported.
The way to go is to use your preferred template engine and give the output to the body_html.
You're not telling it, but looking at the method name, I guess you're using the Devise gem, and you'd like to send its content.
Here is how to make it working (didn't try it):
def confirmation_instructions(record, token, opts={})
email_html_body = ERB.new(File.read('devise/mailer/confirmation_instructions.html.erb'))
.result_with_hash(
email: current_user.email,
resource: resource,
token: #token
)
# ...
mb_obj.body_html(email_html_body)
# ...
end
Wow, so...Rails default mail() syntax does still work with Mailgun. I don't know why it was not at first attempt, but I think it had something to do with the fact that I had not set the production.rb config to config.action_mailer.delivery_method = :mailgun.

How can I rewrite a subdomain to another subdomain?

I have this simple rack middleware written to rewrite my subdomain :
def call(env)
request = Rack::Request.new(env)
if request.host.starts_with?("something-something")
[301, { "Location" => request.url.gsub('something-something','site-i-want') }, self]
else
#app.call(env)
end
And this works fine on development. But in production I get an error calling .each for TheNameOfMyRackMiddleware
Is there something that looks strangely syntactically incorrect about how I'm writing this?
I want this someting-something.mywebsite.com to go to site-i-want.mywebsite.com
I also tried it directly with my routes with this :
constraints :subdomain => 'something-something' do
redirect { |p, req| req.url.sub('something-something', 'site-i-want') }
end
Which works fine on development. But doesn't route before I get my failure saying that the site does not exist.
Entirely open to anyway of getting this accomplished.
How about trying this in your routes:
constraints subdomain: 'something-something' do
get ':any', to: redirect(subdomain: 'site-i-want', path: '/%{any}'), any: /.*/
end
It basically catches any request to the 'something-something' domain, and rewrites it with the existing path, but new subdomain.

WARNING: Can't verify CSRF token authenticity

I'm having trouble making a cross-domain request from my shopify site to my rails app, which is installed as a shopify app. The problem, as stated in the title, is that my server warns me that it Can't verify CSRF token authenticity I'm making the request from a form returned by my rails app, which includes the relevant CSRF token. The request is done with jQuery's ajax method, and the preflight OPTIONS request is being handled by rack-cors.
I've included the X-CSRF-Token in my headers as was suggested in this answer. My post request is being made from a form, so my question is not answered here. The options request (mentioned in this question) is indeed being handled, as I just confirmed by asking this question. I've been stuck on this for a while, and have done a bit of reading.
I'm going to try walking through the process code-snippet by code-snippet, and maybe by the time I finish writing this post I will have discovered the answer to my problem (if that happens, then you won't ever get a chance to read this paragraph).
Here are the new and create methods from my controller.
class AustraliaPostApiConnectionsController < ApplicationController
# GET /australia_post_api_connections/new
# GET /australia_post_api_connections/new.json
def new
# initializing variables
respond_to do |format|
puts "---------------About to format--------------------"
format.html { render layout: false } # new.html.erb
format.json { render json: #australia_post_api_connection }
end
end
# POST /australia_post_api_connections
# POST /australia_post_api_connections.json
def create
#australia_post_api_connection = AustraliaPostApiConnection.new(params[:australia_post_api_connection])
respond_to do |format|
if #australia_post_api_connection.save
format.js { render layout: false }
else
format.js { render layout: false }
end
end
end
end
(I wonder about the respond_to block in the create method, but I don't think that would cause the CSRF token to fail verification.)
Within my app, at /AUSController/index, I have an ajaxified GET request that brings back the form from /AUSController/new. My goal is to be able to make all the same calls from a cross-domain origin as I can from within my app. Right now the GET request works for both, and so I will neglect to include the 'new' form. When the HTML is finally rendered, the form element has the following:
<form method="post" id="new_australia_post_api_connection" data-remote="true" class="new_australia_post_api_connection" action="http://localhost:3000/australia_post_api_connections" accept-charset="UTF-8">
<!-- a bunch more fields here -->
<div class="field hidden">
<input type="hidden" value="the_csrf_token" name="authenticity_token" id="tokentag">
</div>
</div>
</div>
</form>
The CSRF token is generated by a call to form_authenticity_token as detailed in one of the references mentioned above.
The next step is done differently in the two cases:
My app successfully returns the new form to the shop upon an ajax request. I've tested this within the app, that is by making an ajax call to /controller/new from /controller/index, and then submitting the form. This works like a charm. The js that is returned from a successful POST within my app is as follows:
/ this is rendered when someone hits "calculate" and whenever the country select changes
:plain
$("#shipping-prices").html("#{escape_javascript(render(:partial => 'calculations', :object => #australia_post_api_connection))}")
Which renders the following partial,
= form_tag "/shipping_calculations", :method => "get" do
= label_tag :shipping_type
%br
- #service_list.each_with_index do |service, index|
- checked = true if index == 0
= radio_button_tag(:shipping_type, service[:code], checked)
= label_tag(:"shipping_type_#{service[:code]}", service[:name])
= " -- $#{service[:price]}"
%br
When I call it from the same domain, request.header contains the following:
HTTP_X_CSRF_TOKEN
the_token_I_expect=
rack.session
{
"session_id"=>"db90f199f65554c70a6922d3bd2b7e61",
"return_to"=>"/",
"_csrf_token"=>"the_token_I_expect=",
"shopify"=>#<ShopifyAPI::Session:0x000000063083c8 #url="some-shop.myshopify.com", #token="some_token">
}
And the HTML is rendered and displayed nicely.
From the cross domain source, however, things are understandibly more complicated. This is where CORS and CSRF tokens and routes and all these little details start creeping in. In particular, when I make the ajax call I use the following script (which does not live in my rails app, it lives on the cross-domain server). The action of this ajax request is attached to the submit button by the callback function from the GET request, and I've included the GET request for the sake of completion.
<script>
var host = "http://localhost:3000/"
var action = "australia_post_api_connections"
console.log("start")
$.ajax({
url: host + action,
type: "GET",
data: { weight: 20 },
crossDomain: true,
xhrFields: {
withCredentials: true
},
success: function(data) {
console.log("success");
$('#shipping-calculator').html(data);
$('#new_australia_post_api_connection')
.attr("action", host + action);
$('.error').hide();
$(".actions > input").click(function() {
console.log("click")
// validate and process form here
$('.error').hide();
var to_postcode = $("input#australia_post_api_connection_to_postcode").val();
// client side validation
if (to_postcode === "") {
$("#postcode > .error").show();
$("input#australia_post_api_connection_to_postcode").focus();
return false;
}
tokentag = $('#tokentag').val()
var dataHash = {
to_postcode: to_postcode,
authenticity_token: tokentag // included based on an SO answer
}
// included based on an SO answer
$.ajaxSetup({
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRF-TOKEN', tokentag);
}
});
$.ajax({
type: "POST",
url: host + action,
data: dataHash,
success: function(data) {
$('#shipping-prices').html(data);
}
}).fail(function() { console.log("fail") })
.always(function() { console.log("always") })
.complete(function() { console.log("complete") });
return false;
});
}
}).fail(function() { console.log("fail") })
.always(function() { console.log("always") })
.complete(function() { console.log("complete") });
$(function() {
});
</script>
However, when I call it from this remote location (the distant slopes of Shopify), I find the following in my request headers,
HTTP_X_CSRF_TOKEN
the_token_I_expect=
rack.session
{ }
And I receive a very unpleasant NetworkError: 500 Internal Server Error rather than the 200 OK! that I would like... On the server side we find the logs complaining that,
Started POST "/australia_post_api_connections" for 127.0.0.1 at 2013-01-08 19:20:25 -0800
Processing by AustraliaPostApiConnectionsController#create as */*
Parameters: {"weight"=>"20", "to_postcode"=>"3000", "from_postcode"=>"3000", "country_code"=>"AUS", "height"=>"16", "width"=>"16", "length"=>"16", "authenticity_token"=>"the_token_I_expect="}
WARNING: Can't verify CSRF token authenticity
Completed 500 Internal Server Error in 6350ms
AustraliaPostApiConnection::InvalidError (["From postcode can't be blank", "The following errors were returned by the Australia Post API", "Please enter Country code.", "Length can't be blank", "Length is not a number", "Height can't be blank", "Height is not a number", "Width can't be blank", "Width is not a number", "Weight can't be blank", "Weight is not a number"]):
app/models/australia_post_api_connection.rb:78:in `save'
The lack of a rack.session seems suspicious like the cause of my misery... but I haven't been able to find a satisfying answer.
Finally I have seen fit to include my rack-cors setup, in case it is useful.
# configuration for allowing some servers to access the aus api connection
config.middleware.use Rack::Cors do
allow do
origins 'some-shop.myshopify.com'
resource '/australia_post_api_connections',
:headers => ['Origin', 'Accept', 'Content-Type', 'X-CSRF-Token'],
:methods => [:get, :post]
end
end
Thank you so much for even reading all of this. I hope the answer has to do with that empty rack.session. That would be satisfying, at least.
Well one of my coworkers figured it out. The problem was, the has I was sending didn't have the same structure as the hash I was expecting in my controller.
In my controller I instantiate a new API connection as follows,
AustraliaPostApiConnection.new(params[:australia_post_api_connection])
I am looking for params[:australia_post_api_connection], but there is no such index in my data hash, which looks like,
var dataHash = {
to_postcode: to_postcode,
authenticity_token: tokentag // included based on an SO answer
}
To fix this I changed the JS file to contain,
var dataHash = {
to_postcode: to_postcode,
}
var params = {
australia_post_api_connection: dataHash,
authenticity_token: tokentag // included based on an SO answer
}
And now it works! Thanks co-worker!

Is there a "rails way" to retry a POST after authenticating a user?

My user experience involves users submitting a form before they've authenticated (using omniauth). I started doing something like this:
def self.require_facebook_authentication!(options={})
before_filter :redirect_to_facebook_if_not_authenticated options
end
def redirect_to_facebook_if_not_authenticated
if !logged_in?
session[:param_cache] = params
session[:original_destination] = request.fullpath
redirect_to '/auth/facebook'
end
end
Then, on hitting the auth callback, redirect to a page that submits a form with the post params inline, for a total of 3 redirects (/stuff/new/ on POST -> auth/facebook -> facebook -> /auth/facebook/callback [ html template with POST form ] -> /stuff/create). I'd rather not create an authentication popup; instead, I'd like to navigate to a separate page, log in, and redirect to the completed action.
I'm fairly new to Rails, so I'm still learning - is this already built in to another framework? Am I missing something really basic? Thanks in advance!
if you are asking as to whether or not there is a "RAILS" way that will automatically post the data after a redirect, the answer is no (see https://stackoverflow.com/questions/985596/redirect-to-using-post-in-rails)
In my opinion the safest, easiest, and most RESTful way to achieve what you want would be to simply have the params you are eventually posting stored in session so that you can redirect back to the original 'new' page and have the form automatically prefilled with the post data. Sure this is one extra step for the user, but since REST doesn't allow for redirects to POSTs it is imo the cleanest way to go about it
There may be a better way, but if you render this after authenticating, then the client will Ajax post the form contents then redirect.
<script>
new Ajax.Request(<%= session[:original_destination] %>, {
method: 'post',
params: '<%= session[:param_cache].to_query %>',
onSuccess: function(){
window.location = '<%= session[:original_destination] %>';
}
});
</script>

Resources