I am unable to parse CSRF token from html in Cypress.
I am following this link : Logging in using CSRF Token in Cypress
Trying to follow strategy #1 in the above link but I keep getting token as undefined.
This is how my html looks like:
Return html looks like this
This is how my code looks like:
cy.request({
url: returnUrlFromLoginAPI,
followRedirect: false
})
.its('body')
.then((body) => {
const $html = Cypress.$(body)
const requestVerificationToken = $html.find("input[name=__RequestVerificationToken]").val()
console.log(requestVerificationToken)
})
})
.find looks for descendants for the current elements set and my meta tag was part of this set and not a descendant. that's why you should use .filter, cause filter looks for current set and its descendants
Not sure what the "correct" way is, but I did this:
cy.get('[name=__RequestVerificationToken]').then($rvt => {
console.log($rvt.val());
}
Related
I'm learning stimulus and trying to get add a checkbox feature where you can mark an order as complete from the show page without using a form. I followed this tutorial, but am not getting the correct results. The checkbox does nothing when clicked and unchecks when refreshed; however if I manually set the complete attribute to true, the checkbox is automatically checked when loading the page, as it should.
I have a model "Order" with a boolean attribute "complete". Here's my show.html.erb section
<tr data-controller="todo" data-todo-update-url="<%= order_path(#order.id) %>">
<td>
<div>
<input type="checkbox"
data-action="todo#toggle"
data-target="todo.completed"
<% if #order.complete %> checked <% end %> >
</div>
</td>
</tr>
Here's my stimulus todo_controller
import { Controller } from "#hotwired/stimulus"
export default class extends Controller {
static targets = [ "completed" ]
toggle(event) {
// Inside the toggle(event) function, let’s start by getting the value of the checkbox,
// and put it into a FormData object
let formData = new FormData()
formData.append("#order[complete]", this.completedTarget.completed);
// Let’s post that data to the "update-url" value we set on the Todo row.
// We’ll set the method to PATCH so that it gets routed to our todo#update on our controller.
// The credentials and headers included ensure we send the session cookie and the CSRF protection token and
// and prevent an ActionController::InvalidAuthenticityToken error.
fetch(this.data.get("update-url"), {
body: formData,
method: 'PATCH',
dataType: 'script',
credentials: "include",
headers: {
"X-CSRF-Token": getMetaValue("csrf-token")
}
})
// We can take the Response object and verify that our request was successful.
// If there was an error, we’ll revert the checkbox change.
.then(function(response) {
if (response.status != 204) {
event.target.complete = !event.target.complete
}
})
}
}
Can someone tell me where my code is going wrong?
This is more a long comment than a solution but few things I see :
You create an empty form and append a single input with name
"#order[complete]" though your Stimulus controller is Javascript
and has no knowledge of # the such way you use in Ruby. Also params
names are usually model[field] then I think you don't need the #.
"order[complete]" should be fine.
Also you grab the value from a specific target for the aforementionned value with this.completedTarget.completed. Should you not rather pick the value of the input field ? and rather grab this.completedTarget.value or maybe the checked status this.completedTarget.checked
You are getting the URL to your fetch from a data attribute. I am not a stimulus expert but it doesn't look like anything Stimulus related. As of now you have written it this.data.get("update-url") but in regular javascript, something like this.element.dataset.todoUpdateUrl should work.
And just to be sure there is no confusion to about where you will
call this , just declare it at the top of your Stimulus methd like
this : var backUrl = this.element.dataset.todoUpdateUrl. And fill the url to your fetch as just fetch(backUrl,...
you pass the formData directly to your fetch body. If this doesn't work, try to stringify it and extract the entries like : JSON.stringify(Object.fromEntries(formData)). Also I am not too sure about the dataType: 'script', you may just omit that alltogether.
There may be other problems that I don't see. Also when you are dealing with JS, don't only look to your Rails console, especially if nothing hits the backend. Open the developper / inspect tool in your browser and monitor the console there, you should see all the XHR (async) requests made to your app.
If nothing happens, then your fetch is not firing and there need to be more investigations made..
I have a form where I've stored the auth key in a hidden field.
hidden_field_tag 'auth_key', Settings.biometric.auth_key
I am sending an ajax request to an API where I'm setting the auth key in the header which requires the key:
var authKey = $("input[id='auth_key']").val();
beforeSend: function (xhr) {
xhr.setRequestHeader ("auth", authKey);
}
All is working fine, just that the auth key resides in the form and is easily inspectable by any malicious user.
I think this might not be the right way to do this
What is the best approach to perform this?
TL;DR:
Anyone who has access to the client-side (a.k.a your User using a browser), can ultimately find a way to get this auth_key, because a client-user has and will always have power/access on what "data" gets sent/received/stored, especially easier here in webapps because of built-in browser developer tools.
Some Explanations:
Disclaimer: I am not well versed in this field, so if anyone, please let me know.
Yes, it can be encrypted in the client-side, but a user/hacker can decrypt them because the "encryption" trace can be found somewhere in your JS script file:
// application.js example
...beforeSend: function (xhr) {
var encryptedAuthKey = localstorage.get('encrypted-auth-key');
var decryptionPassword = 'abcd';
var authKey = doSomeFancyDecryption(encryptedAuthKey, decryptionPassword);
xhr.setRequestHeader ("auth", authKey);
}
However, a secure way would be to encrypt them using a password stored in the server-side, so that the client cannot debug/inspect/find this password in the JS code... except that this is not possible, see below:
// application.js example
...beforeSend: function (xhr) {
var encryptedAuthKey = localstorage.get('encrypted-auth-key');
var decryptionPassword = someFunctionThatPerformsAjaxRequestToServerAndReturnsTheDecryptionPassword();
var authKey = doSomeFancyDecryption(encryptedAuthKey, decryptionPassword);
function someFunctionThatPerformsAjaxRequestToServerAndReturnsTheDecryptionPassword() {
// do some ajax request with Auth header equals Something...
// ummm... what's the value of this something?
// ummm... I cannot pass in my Username and Password, of course!
// ummm... I cannot pass in another-kind of "auth_key", which just basically loops this process itself.
}
xhr.setRequestHeader ("auth", authKey);
}
You can "sign" your request so that you won't need to directly supply anymore the auth_key as part of your request (but the client-user can still hack this and get the auth-key and create their own request themselves, precisely because they can see and have access to your underlying code like below):
// application.js example
...beforeSend: function (xhr) {
var authKey = localstorage.get('auth-key');
var params = // assign all input fields as key-values object here
var url = this.url;
var signature = generateSignatureUsingHMAC(authKey, url, params)
xhr.setRequestHeader ("Signature", signature);
}
I would personally do something like the signature-based authorization above
In my Rails API I am using JSONAPI structure which Ember expects by default.
I have a Rails route http://localhost:3000/profile which will return the currently logged in user JSON.
How do I make an arbitary request to this /profile endpoint in Emberjs so I can get my logged in user's JSON in my router's model() hook?
I tried following this guide here:
https://guides.emberjs.com/v2.10.0/models/finding-records/
And have this code:
return this.get('store').query('user', {
filter: {
email: 'jim#gmail.com'
}
}).then(function(users) {
return users.get("firstObject");
});
It is returning the incorrect user however. It also seems like it doesn't matter what the value of 'email' is, I can pass it 'mud' and it will return all users in my database.
Is there no way for me to make a simple GET request to /profile in my model() hook of my profile route in Ember?
Update
It has come to my attention that the filter thing in Ember is actually just appending a query parameter onto the end of the request URL.
So having my above filter, it would be like making a request:
GET http://localhost:3000/users?filter['email']=jim#gmail.com
Which doesn't help because my Rails doesn't know anything about filter query parameter.
I was hoping Ember will automatically find the user and do some black magic to filter the user to match email address for me, not me having to manually build extra logic in my Rails API to find a single record.
Hurrmmmmmmm...sure feels like I'm fighting against the conventions of Ember at the moment.
Update
Thanks to Lux, I finally got it working with the following approach:
Step 1 - Generate the User adapter:
ember generate adapter user
Step 2 - write the AJAX request in the queryRecord method override for User adapter
import ApplicationAdapter from './application';
import Ember from 'ember';
export default ApplicationAdapter.extend({
apiManager: Ember.inject.service(),
queryRecord: function(store, type, query) {
if(query.profile) {
return Ember.RSVP.resolve(
Ember.$.ajax({
type: "GET",
url: this.get('apiManager').requestURL('profile'),
dataType: 'json',
headers: {"Authorization": "Bearer " + localStorage.jwt}
})
);
}
}
});
Step 3 - make the model() hook request like so:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.get('store').queryRecord('user', {profile: true});
}
});
Well, query is for server side filtering. If you want it client-side use something like store.findAll('user').then(users => users.findBy('email', 'bla#bla.bla'));.
But this is not what you want. You have your server side filter. It's just under /profile. Not under /user.
However interesting is what /profile actually responds. A single-record-response or a multi-record-response. The best would probably a single-record-response since you only want to return one user. So how can we do this with ember? Well, we use store.queryRecord().
And because ember does not know anything about /profile we have to tell it ember in the user-adapter with something like this:
queryRecord: function(store, type, query) {
if(query.profile) {
return Ember.RSVP.resolve(Ember.$.getJSON('/profile'));
}
}
And then you can just return store.queryRecord('user', { profile: true })
I'd like to have a dynamic redirect URL for my Facebook OAuth2 integration. For example, if my redirect URL is this in my Facebook app:
http://www.mysite.com/oauth_callback?foo=bar
I'd like the redirect URL for a specific request be something like this, so that on the server, I have some context about how to process the auth code:
http://www.mysite.com/oauth_callback?foo=bar&user=6234
My redirect gets invoked after the authorization dialog is submitted, and I get back an auth code, but when I try to get my access token, I'm getting an OAuthException error back from Facebook. My request looks like this (line breaks added for clarity):
https://graph.facebook.com/oauth/access_token
?client_id=MY_CLIENT_ID
&redirect_uri=http%3A%2F%2Fwww.mysite.com%2Foauth_callback%3Ffoo%3Dbar%26user%3D6234
&client_secret=MY_SECRET
&code=RECEIVED_CODE
All of my parameters are URL-encoded, and the code looks valid, so my only guess is that the problem parameter is my redirect_uri. I've tried setting redirect_uri to all of the following, to no avail:
The actual URL of the request to my site
The URL of the request to my site, minus the code parameter
The URL specified in my Facebook application's configuration
Are custom redirect URI parameters supported? If so, am I specifying them correctly? If not, will I be forced to set a cookie, or is there some better pattern for supplying context to my web site?
I figured out the answer; rather than adding additional parameters to the redirect URL, you can add a state parameter to the request to https://www.facebook.com/dialog/oauth:
https://www.facebook.com/dialog/oauth
?client_id=MY_CLIENT_ID
&scope=MY_SCOPE
&redirect_uri=http%3A%2F%2Fwww.mysite.com%2Foauth_callback%3Ffoo%3Dbar
&state=6234
That state parameter is then passed to the callback URL.
If, for any reason, you can't use the option that Jacob suggested as it's my case, you can urlencode your redirect_uri parameter before passing it and it will work, even with a complete querystring like foo=bar&morefoo=morebar in it.
I was trying to implement a Facebook login workflow against API v2.9 following this tutorial. I tried the solutions described above. Manuel's answer is sort of correct, but what I observed is url encoding is not needed. Plus, you can only pass one parameter. Only the first query parameter will be considered, the rest will be ignored. Here is an example,
Request a code via https://www.facebook.com/v2.9/dialog/oauth?client_id={app-id}&redirect_uri=http://{url}/login-redirect?myExtraParameter={some-value}
You'd get a callback for your url. It will look like http://{url}/login-redirect?code={code-from-facebook}&myExtraParameter={value-passed-in-step-1}. Note that facebook would make a callback with myExtraParameter. You can extract the value for myExtraParameter from callback url.
Then you can request access token with https://graph.facebook.com/v2.9/oauth/access_token?client_id={app-id}&client_secret={app-secret}&code={code-from-facebook}&redirect_uri=http://{url}/login-redirect?myExtraParameter={value-extracted-in-step-2}
Additional parameter passed in step 1 after the first query parameter will be ignored. Also make sure to not include any invalid characters in your query parameter (see this for more information).
You're best off specifying a unique callback for each oAuth provider, /oauth/facebook, /oauth/twitter etc.
If you really want the same file to respond to all oAuth requests, either include it in the individual files or setup a path that will call the same file on your server using .htaccess redirects or something similar: /oauth/* > oauth_callback.ext
You should set your custom state parameter using the login helper as such:
use Facebook\Facebook;
use Illuminate\Support\Str;
$fb = new Facebook([
'app_id' => env('FB_APP_ID'),
'app_secret' => env('FB_APP_SECRET'),
'default_graph_version' => env('FB_APP_VER'),
]);
$helper = $fb->getRedirectLoginHelper();
$permissions = [
'public_profile',
'user_link',
'email',
'read_insights',
'pages_show_list',
'instagram_basic',
'instagram_manage_insights',
'manage_pages'
];
$random = Str::random(20);
$OAuth2Client = $fb->getOAuth2Client();
$redirectLoginHelper = $fb->getRedirectLoginHelper();
$persistentDataHandler = $redirectLoginHelper->getPersistentDataHandler();
$persistentDataHandler->set('state', $random);
$loginUrl = $OAuth2Client->getAuthorizationUrl(
url('/') . '/auth/facebook',
$random,
$permissions
);
Hey if you are using official facebook php skd then you can set custom state param like this
$helper = $fb->getRedirectLoginHelper();
$helper->getPersistentDataHandler()->set('state',"any_data");
$url = $helper->getLoginUrl($callback_url, $fb_permissions_array);
I have an uploadify component, which sends the files back to rails application. The problem I noticed at some point is, that for some special values data passed along are altered by the flash object.
On the client side I have
$(document).ready(function() {
$('#photo_image').uploadify({
...
'scriptData': {
authenticity_token = 'M++Q3HNclKS7QBEM71lkF/8IkjTwr2JdtqJ4WNXVDro='
...
}
});
});
What Rails is getting:
"authenticity_token"=>"M Q3HNclKS7QBEM71lkF/8IkjTwr2JdtqJ4WNXVDro="
When there is no '+' sign in the token everything works just fine. It looks like the flash is altering the string somehow. Any idea how to escape it? I tried CGI.escape, but result is exactly the same, '+' are stripped...
You have to use encodeURIComponent() to encode special characters:
$(document).ready(function() {
$('#photo_image').uploadify({
...
'scriptData': {
authenticity_token = encodeURIComponent('M++Q3HNclKS7QBEM71lkF/8IkjTwr2JdtqJ4WNXVDro=')
...
}
});
});
Actual solution is, to escape the token twice. So for example "encodeURIComponent(encodeURIComponent(token)))" or #{u u token}.