How to add fetch options to Workbox? - progressive-web-apps

I need to add credentials: 'same-origin' to all fetch requests in order to make a PWA work in a password-protected website. Otherwise if I leave the website and come back later, the browser doesn't ask for my password and returns an Unauthorized error.
How do I do that with Workbox ? I noticed the RequestWrapper class includes fetchOptions, but I can't find a way to use them.

In v2, precaching should already set credentials: 'same-origin' as the FetchOptions on all outgoing requests.
For runtime caching, you can get this behavior by constructing your own RequestWrapper instance and passing that in to the runtime caching handler you're using:
const requestWrapper = new RequestWrapper({
cacheName: 'my-cache-name', // Change this for each separate cache.
fetchOptions: {
credentials: 'same-origin',
},
plugins: [], // Add any plugins you need here, e.g. CacheExpiration.
});
const staleWhileRevalidateHandler = new StaleWhileRevalidate({requestWrapper});
workboxSW.router.registerRoute(
new RegExp('/path/prefix'),
staleWhileRevalidateHandler
);
(I'm not sure how you're using the Workbox libraries, but you may need to explicitly import additional scripts to get access to the RequestWrapper class, like https://unpkg.com/workbox-runtime-caching#2.0.3/build/importScripts/workbox-runtime-caching.prod.v2.0.3.js)

Thanks to Jess Posnick for the answer.
To avoid rewriting all workbox strategies, I ended up using a custom plugin:
const addFetchOptionsPlugin = {
requestWillFetch: ({ request }) => new Request(request, {
credentials: 'same-origin', redirect: 'follow'
})
};
workbox.router.registerRoute(
new RegExp('my-route'),
workbox.strategies.cacheFirst({
cacheName: 'my-cache',
plugins: [addFetchOptionsPlugin]
})
);

Related

How to dynamically change base URL using redux-toolkit?

I use the redux toolkit to create an API
The question is short: how to dynamically change the baseUrl in the API?
The question in detail:
Here is the createApi method
An object is passed to this method, one of the keys of which is "baseQuery"
export const WebApi = createApi({
reducerPath: 'API',
baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:3001/api' }),
endpoints: () => ({}),
});
And here's the question, how do I dynamically change the baseUrl? It is in the store, but I can't just put it here.
I tried the solution from the customization query documentation
https://redux-toolkit.js.org/rtk-query/usage/customizing-queries#constructing-a-dynamic-base-url-using-redux-state
But it does not allow to change exactly the baseUrl, only dynamically process the request itself, which already comes AFTER the baseUrl
So, how can I get and change baseUrl in the createApi method?
So, the answer is:
right in the file where you create api
past code below:
const dynamicBaseQuery: BaseQueryFn<string | FetchArgs,
unknown,
FetchBaseQueryError> = async (args, WebApi, extraOptions) => {
const baseUrl = (WebApi.getState() as any).configuration.baseUrl;
const rawBaseQuery = fetchBaseQuery({ baseUrl });
return rawBaseQuery(args, WebApi, extraOptions);
};
I use baseUrl from store, because my config already in it. But you can make an async await fetch here, to get data from any place
and this constant, dynamicBaseQuery insert into baseQuery key in your createApi
export const WebApi = createApi({
reducerPath: 'API',
baseQuery: dynamicBaseQuery,
endpoints: () => ({}),
});
If you pass in a full url (starting with http:// or https://) fetchBaseQuery will skip baseUrl and use the supplied url instead. So you can use the logic that you linked above to just prepend your current baseUrl to the url of the query.

Mistake in using DOMPurify on the backend to sanitize form data?

I was wondering if it was possible to use DOMPurify to sanitize user input on a form before it is saved to database. Here's what I've got in my routes.js folder for my form post:
.post('/questionForm', (req, res, next) =>{
console.log(req.body);
/*console.log(req.headers);*/
const questions = new QuestionForm({
_id: mongoose.Types.ObjectId(),
price: req.body.price,
seats: req.body.seats,
body_style: req.body.body_style,
personality: req.body.personality,
activity: req.body.activity,
driving: req.body.driving,
priority: req.body.priority
});
var qClean = DOMPurify.sanitize(questions);
//res.redirect(200, path)({
// res: "Message recieved. Check for a response later."
//});
qClean.save()
.then(result => {
//res.redirect(200, '/path')({
// //res: "Message recieved. Check for a response later."
//});
res.status(200).json({
docs:[questions]
});
})
.catch(err => {
console.log(err);
});
});
I also imported the package at the top of the page with
import DOMPurify from 'dompurify';
When I run the server and submit a post request, it throws a 500 error and claims that dompurify.sanitize is not a function. Am I using it in the wrong place, and/or is it even correct to use it in the back end at all?
This might be a bit late, but for others like me happening to run into this use case I found an npm package that seems well suited so far. It's called isomorphic-dompurify.
isomorphic-dompurify
DOMPurify needs a DOM to interact with; usually supplied by the browser. Isomorphic-dompurify feeds DOMPurify another package, "jsdom", as a dependency that acts like a supplementary virtual DOM so DOMPurify knows how to sanitize your input server-side.
In the packages' own words "DOMPurify needs a DOM tree to base on, which is not available in Node by default. To work on the server side, we need a fake DOM to be created and supplied to DOMPurify. It means that DOMPurify initialization logic on server is not the same as on client".
Building on #Seth Lyness's excellent answer --
If you'd rather not add another dependency, you can just use this code before you require DOMPurify. Basically what isometric-dompurify is doing is just creating a jsdom object and putting it in global.window.
const jsdom = require('jsdom');
const {JSDOM} = jsdom;
const {window} = new JSDOM('<!DOCTYPE html>');
global.window = window;

Getting callback URL mismatch in an Angular 2 application

I use Auth0 to authorize users via Google, Facebook and others. This works perfectly if you click log in while the URL is on the list of white-listed callback URLs in Auth0.
But my web application can have any number of different URLs, so having a simple white-list with some allowed URLs does not work.
The login always tries to redirect back to the same URL as I logged in from, and this URL is most of the time not in the list of allowed URLs.
I have tried all kinds of variations of the above settings, but I only get errors like these ones:
The url "https://x.com/posts/gif/hot/1" is not in the list of allowed callback URLs
The url "https://x.com/posts/world/new/1" is not in the list of allowed callback URLs
The url "https://x.com/posts/nature/hot/6" is not in the list of allowed callback URLs
The url "https://x.com/posts/gaming/hot/3" is not in the list of allowed callback URLs
The Lock configuration related code:
options = {
auth: {
callbackURL: 'https://x.com',
// redirectUrl: 'https://x.com',
responseType: 'token',
// sso: true,
// redirect: true,
params: {
scope: 'openid user_id name nickname email picture'
}
}
};
// Configure Auth0
lock = new Auth0Lock('x', 'x.auth0.com', this.options);
constructor(private _router: Router) {
this.userProfile = JSON.parse(localStorage.getItem('profile'));
// Add callback for the Lock `authenticated` event
this.lock.on('authenticated', (authResult) => {
localStorage.setItem('id_token', authResult.idToken);
// Fetch profile information
this.lock.getProfile(authResult.idToken, (error, profile) => {
if (error) {
throw new Error(error);
}
});
});
};
The login method:
public login() {
// Call the show method to display the widget.
this.lock.show({
callbackUrl: 'https://x.com',
state: this._router.url
});
};
I'm assuming you're using the latest version of Lock (Lock 10) and if that's the case there are a few issues with the code you included:
The URL to which Auth0 will redirect to after the user completes the authentication step is specified through auth: { redirectUrl: '...' } and you have that line commented and instead the code is incorrectly using callbackURL.
According to the docs, the show method no longer takes any arguments.
Independently of the Lock version the state parameter should be used to mitigate CSRF attacks so using it exclusively to pass contextual information may be insecure.
Given you have the redirectUrl commented you probably also gave it a try; did you got the same behavior when using that parameter?
Based on the documentation the required configuration for what you're trying to achieve should be accomplished by having:
options = {
auth: {
redirectUrl: 'https://example.com/login/callback',
responseType: 'token',
params: {
state: '[your_state_value]',
scope: 'openid user_id name nickname email picture'
}
}
};
public login() {
// Call the show method to display the widget.
this.lock.show();
};

Sencha Touch: How to build a restful proxy url syntax?

Defined as a model and its associations, I wish the http calls to follow best practices of restful. For example, if I make the call
user.posts();
I wish to run a call http defined as
users/1/posts
If a call is then put on post with id 32 then the url of reference must be
users/1/posts/32
So I want to avoid using the filter property as is the default for a get
/posts.php?filter=[{"property":"user_id","value":1}]
This is because the api rest are already defined and I can not change them.
I would like to build a minimally invasive solution and reading on various forums the best way is to do an ovveride the method buildURL the proxy rest but was not able to retrieve all the data needed to build the final URL. Can anyone give me an example?
thanks
Try the following:
window.serverUrl = "192.168.1.XX"
var service = "login.php"
var dataTosend: {
username:"xx",
password: "yy"
}
var methode:"POST" / "GET"
this.service(service,methode,dataTosend,onSucessFunction,onFailureFunction);
onSucessFunction: function(res) {
alert("onSucessFunction"):
},
onFailureFunction: function(res) {
alert("onFailureFunction"):
},
service: function(svc, callingMethod, data, successFunc, failureFunc) {
Ext.Ajax.request({
scope: this,
useDefaultXhrHeader: false,
method: callingMethod,
url: window.serverUrl + svc,
params: data,
reader: {
type: 'json'
},
failure: failureFunc,
success: successFunc
});
I hope this will solve your problem...

Handling CSRF/XSRF tokens with Angular frontend and Drupal 7 backend

I'm in the process of building a new AngularJS frontend for a Drupal 7 website. This is using the Services module with session-based authentication, across two domains using CORS. I am able to authenticate with Drupal, retrieve the user object and session data, and then get the CSRF token from the services module. What I'm having trouble with is setting all this up in the header so that subsequent requests are authenticated. I understand the overall concept but am new to both AngularJS and preventing CSRF attacks.
From what I have gathered reading about this set-up with AngularJS and RubyOnRails, there can be inconsistencies between platforms concerning what the token is named and how it is processed. There also seems to be a number of suggestions on how to set this token in the header. However, I'm having trouble in finding a solid example of how to get these platforms speaking the same language.
The only thing I'm doing with my $httpProvider in app.js is:
delete $httpProvider.defaults.headers.common['X-Requested-With'];
The login controller, in controller.js:
.controller('LoginCtrl', ['$scope', '$http', '$cookies', 'SessionService', function($scope, $http, $cookies, SessionService) {
$scope.login = function(user) {
//set login url and variables
var url = 'http://mywebsite.com/service/default/user/login.json';
var postDataString = 'name=' + encodeURIComponent(user.username) + '&pass=' + encodeURIComponent(user.password);
$http({
method: 'POST',
url: url,
data : postDataString,
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}).success(function (data, status, headers, config) {
var sessId = data.sessid;
var sessName = data.session_name;
$cookies[sessName] = sessId;
var xsrfUrl = 'http://mywebsite.com/services/session/token';
$http({
method: 'GET',
url: xsrfUrl
}).success(function (data, status, headers, config) {
$cookies["XSRF-TOKEN"] = data;
SessionService.setUserAuthenticated(true);
}).error(function (data, status, headers, config) {
console.log('error loading xsrf/csrf');
});
}).error(function (data, status, headers, config) {
if(data) {
console.log(data);
var msgText = data.join("\n");
alert(msgText);
} else {
alert('Unable to login');
}
});
};
The solution has to do with how the cookies need to be set and then passed through subsequent requests. Attempts to set them manually did not go well but the solution was simpler than I expected. Each $http call needs to set the options:
withCredentials: true
Another change I made was to use the term CSRF instead of XSRF, to be consistent with Drupal. I didn't use any built-in AngularJS CSRF functionality.
addItem: function(data)
{
return $http.post('api/programs/'+$stateParams.id+'/workouts', {item:data},{
headers:
{
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-CSRF-Token': $('meta[name="xxtkn"]').attr('content')
}
});
}
since it has been a year of this topic! not sure still encountering the same problem but for the ones who comes to search for answers here is how i handle it!
Pay attention the headers{} part i define a new header and call it X-CSRF-Token and grab value from the DOM of (serverside) generated html or php. It is not a good practise to also request the csrf token from the server.Cuz attacker could somehow request that as well. Since you save it as a cookie. Attacker can steal the cookie! No need to save it in a cookie! send the token with header and read it in the serverside to match it!
and for multitab of a same page issue. I use the same token thruout the whole session.
Only regenerate on login, logout and change of major site or user settings.
There is a great library callse ng-drupal-7-services. If you use this in you project it solves authentication / reauthentication and file / node creation aut of the box and you can fokuse on the importent stuff in your project.
So Authentication is there solved like this:
function login(loginData) {
//UserResource ahndles all requeste of the services 3.x user resource.
return UserResource
.login(loginData)
.success(function (responseData, status, headers, config) {
setAuthenticationHeaders(responseData.token);
setLastConnectTime(Date.now());
setConnectionState((responseData.user.uid === 0)?false:true)
setCookies(responseData.sessid, responseData.session_name);
setCurrentUser(responseData.user);
AuthenticationChannel.pubLoginConfirmed(responseData);
})
.error(function (responseError, status, headers, config) {
AuthenticationChannel.pubLoginFailed(responseError);
});
};
(function() {
'use strict';
AuthenticationHttpInterceptor.$inject = [ '$injector'];
function AuthenticationHttpInterceptor($injector) {
var intercepter = {
request : doRequestCongiguration,
};
return intercepter;
function doRequestCongiguration (config) {
var tokenHeaders = null;
// Need to manually retrieve dependencies with $injector.invoke
// because Authentication depends on $http, which doesn't exist during the
// configuration phase (when we are setting up interceptors).
// Using $injector.invoke ensures that we are provided with the
// dependencies after they have been created.
$injector.invoke(['AuthenticationService', function (AuthenticationService) {
tokenHeaders = AuthenticationService.getAuthenticationHeaders();
}]);
//add headers_______________________
//add Authorisation and X-CSRF-TOKEN if given
if (tokenHeaders) {
angular.extend(config.headers, tokenHeaders);
}
//add flags_________________________________________________
//add withCredentials to every request
//needed because we send cookies in our request headers
config.withCredentials = true;
return config;
};
There is also some kind of kitchen sink for this project here: Drupal-API-Explorer
Yes, each platform has their own convention in naming their tokens.
Here is a small lib put together hoping to make it easy to use with different platforms. This will allow you to use set names and could be used across all requests. It also works for cross-domain requests.
https://github.com/pasupulaphani/angular-csrf-cross-domain