How i can update single filed of array in state in redux? - redux

I need update one field in a state => books => (one indicated book) => (one of book field).
I try do this that
export default function(state = initialState, action){
case EDIT_BOOK:
return {
...state,
books :{
...state.books,
title: action.title
}
}
My state looks like
books:[
{
"_id": "5cfa9698361a8427b85dc79f",
"title": "Krzyżacy",
"author": "Henryk Sienkiewicz",
"__v": 0,
"state": "toReads"
},
{
"_id": "5cfa9bd1cb5c152ee4269a28",
"title": "Quo Vadis",
"author": "Henryk Sienkiewicz",
"state": "toReads",
"__v": 0
}
]
//Action
export const editBook = (id, title) => dispatch => {
axios
.put(`http://localhost:5000/books/${id}`, title)
.then(res => dispatch({
type: EDIT_BOOK,
payload: id,
newTitle: title
}))
My second problem is thus
Access to XMLHttpRequest at 'http://localhost:5000/books/5cfa9698361a8427b85dc79f' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

You are updating array incorrectly. See this pattern from redux documentation:
https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns#updating-an-item-in-an-array
Be carefull and do not change your books from array into object (use output of function updateObjectInArray from above link and put it into your books property)
case EDIT_BOOK:
return {
...state,
// here you are putting object instead of array into books
books :{
...state.books,
title: action.title
}
}
Solution for your second problem is using e.g. nginx in your local development environment for passing requests to your frontend application and backend api to the right address/port.
Nginx configuration will look something like this (simplified, just for giving a right direction):
server {
# port where nginx will run (you will debug your web application on this port from now)
listen 8081;
location / {
# address of your frontend application (e.g. webpack dev server)
proxy_pass http://127.0.0.1:3000;
}
location /backend {
# address of your backennd application
proxy_pass http://127.0.0.1:5000;
}
}
As I wrote above, after that configuration you need to debug application on nginx proxy and not on frontend server (e.g. webpack dev server), but the server should be still running. The nginx proxy application url according to the above configuration will be: http://localhost:8081
And whenever your application would like to call backend api, it will do it this way:
// your old address will change into ...
http://localhost:5000/books/${id}
// ... this new address
/backend/books/${id}

Related

Unable to make API call in NextJS app running on port different that port 3000

I am currently a CORS issue in my NextJs App. When I run the app on let's say on localhost:3030, and I make an API call to http://some-api.com, I get the following error:
Access to XMLHttpRequest at 'http://some-api.com' from origin 'http://localhost:3030' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'http://localhost:3000' that is not equal to the supplied origin.
I added the following lines to the next.config.js.
process.env.NEXT_PUBLIC_SOME_HOST=http://some-api.com
async rewrites() {
return [
{
source: '/api/:path*',
destination: process.env.NEXT_PUBLIC_SOME_HOST + '/:path*'
}
]
},
//avoiding CORS error, more here: https://vercel.com/support/articles/how-to-enable-cors
async headers() {
return [
{
// matching all API routes
source: "/api/:path*",
headers: [
{ key: "Access-Control-Allow-Credentials", value: "true" },
{ key: "Access-Control-Allow-Origin", value: process.env.NEXT_PUBLIC_SOME_HOST},
{ key: "Access-Control-Allow-Methods", value: "GET,OPTIONS,PATCH,DELETE,POST,PUT" },
{ key: "Access-Control-Allow-Headers", value: "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" },
]
}
]
},
But it still does not work.
I also attached of the response header on the OPTIONS call.
I can see that the access-control-allow-origin is set to http:localhost:3000 I think if I override that value to localhost:3030 it would work but I am not sure how to do that.
I am pretty new to NextJs, so some help would be greatly appreciated.
Updating the CorsFilter on the Back end API to allow response to http://localhost:3030 solved the problem.
My back end API is written in Spring Boot.
So I added the following:
httpServletResponse.setHeader("Access-Control-Allow-Origin", "http://localhost:3030");
Note: It also possible to allow all with "*"

Vue.js-Electron application with an Apollo client does not receive cookies from a remote API server

I try to build an application with a GraphQL backend (based on Node.js and graphql-yoga). The server is hosted on a Linux machine with nginx as a reverse proxy. The proxy configuration is listed below.
server {
listen 443 ssl;
listen [::]:443 ssl;
include snippets/self-signed.conf;
include snippets/ssl-params.conf;
server_name url.example.com;
charset utf-8;
# Always serve index.html for any request
location / {
root /srv/app/public;
try_files $uri /index.html;
}
location /graphql {
allow all;
proxy_pass http://localhost:3000$request_uri;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
location /playground {
# Additional configuration
}
location /subscriptions {
# Additional configuration
}
error_log /var/log/nginx/vue-app-error.log;
access_log /var/log/nginx/vue-app-access.log;
}
server {
listen 80;
listen [::]:80;
server_name url.example.com;
return 302 https://$server_name$request_uri;
To avoid CORS problems, I had to add the following entry in my servers configuration and it works.
const options = {
[
...
]
/**
* Cross-Origin Resource Sharing (CORS)
*/
cors: {
credentials: true,
origin: ['http://localhost:8080', 'https://url.example.com'], // frontend url
},
};
The authentication is based on JWT tokens packed into http-only cookies for access and refresh tokens. This is working as expected when I access my server with a browser and the cookies are shown in the developer tools. Within the AuthData, I additionally provide the username and a JWT token as a return value.
{
"data":{
"login":{
"user":"admin",
"name":" Admin",
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJhZG1pbiIsIm5hbWUiOiIgQWRtaW5pc3RyYXRvciIsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5jb20iLCJpYXQiOjE2NDg0ODM2MDcsImV4cCI6MTY0ODQ4NzIwN30.Dol41LStkscXrlGn3GJotf83k_d2EImyvDU68Dg8Bvw"
}
},
"loading":false,
"networkStatus":7
}
Now I wanted to play around with Electron, because some use cases of the application would be much easier. The stack is Vue.js and Electron with Tailwind and the Apollo client (I use the Apollo UploadClient here) as listed below.
import {
ApolloClient,
ApolloLink,
DefaultOptions,
InMemoryCache,
} from '#apollo/client/core';
import { setContext } from '#apollo/client/link/context';
import { createUploadLink } from 'apollo-upload-client';
const backendServer = 'https://url.example.com/graphql';
const httpLink = createUploadLink({
uri: backendServer,
credentials: 'include',
});
// Cache implementation
const cache = new InMemoryCache({
addTypename: false,
}); //
const defaultOptions = {
watchQuery: {
fetchPolicy: 'no-cache',
},
query: {
fetchPolicy: 'no-cache',
errorPolicy: 'all',
},
mutate: {
errorPolicy: 'all',
},
} as DefaultOptions;
const authLink = setContext((_, { headers }) => ({
headers: {
...headers,
/**
* use a custom header tag to classify the application
* for authentication handling in the backend
*/
'app-type': 'web',
},
}));
const apolloClient = new ApolloClient({
link: ApolloLink.from([authLink, httpLink]),
cache,
defaultOptions,
resolvers: {},
});
export default apolloClient;
This client is bound to my Vue.js instance via #vue/apollo-composable. So I think it is only available in the renderer process. React examples I found on the Internet were built the same way.
Now the problem: When I run server and client (Electron) application on my development machine, everything works as expected, and I can see the cookies in the application tab in the developer tools. I also can access the API without issues.
When I now bind my ApolloClient to the remote API server I do not receive any cookies. From the logs I can see that I receive the AuthData like above from the server, but not the cookies. So any further request results in a custom error message "unauthenticated access" provided by my API server.
{
"loading":false,
"networkStatus":8,
"error":{
"graphQLErrors":[
{
"message":"user is not authenticated...redirect to the login page",
"locations":[
{"line":2,"column":3}
],
"path":[
"users"
]
}
],
"clientErrors":[],
"networkError":null,
"message":"user is not authenticated...redirect to the login page"
},"errors":[
{
"message":"user is not authenticated...redirect to the login page",
"locations":[
{"line":2,"column":3}
],
"path":[
"users"
]
}
]
}
What am I missing or doing wrong, or what else could I test?
Additional tested direct access
I additionally tested direct accessing the gql API from the electron client without the nginx proxy but still no success. I get the login correctly but I'm not able to see the cookies provided by the server.
any suggestions?

Request ignored because of CORS in IdentityServer4

I have 3 projects:
Client App
ASP.NET API App
IdentityServer4 MVC App
I am able to send a request from API to IDP but trying to send a request from Client to IDP yields
"CORS request made for path: /api/Trial/TrialAction from origin: https://localhost:44389 but
was ignored because path was not for an allowed IdentityServer CORS endpoint"
even though I added the following to the IDP:
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", policyBuilder => policyBuilder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
and
// ...
app.UseRouting();
app.UseIdentityServer();
app.UseCors("CorsPolicy");
app.UseAuthorization();
// ...
The interesting part is, I can send a request from API to IDP without adding CORS configuration to IDP. What am I doing wrong?
Config.cs:
public static class Config
{
public static IEnumerable<IdentityResource> Ids =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
};
public static IEnumerable<ApiResource> Apis =>
new ApiResource[]
{
new ApiResource("myapi",
"My API",
new [] { "membershipType" }
)
};
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
ClientId = "mywebclient",
ClientName = "My Web Client",
AllowedGrantTypes = GrantTypes.Code, // Authorization code flow with PKCE protection
RequireClientSecret = false, // Without client secret
RequirePkce = true,
RedirectUris = { "https://localhost:44389/authentication/login-callback" },
PostLogoutRedirectUris = { "https://localhost:44389/authentication/logout-callback" },
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"albidersapi"
},
AllowedCorsOrigins = { "https://localhost:44389" },
RequireConsent = false,
}
};
}
do yo have the client and API in the same project as IdentityServer? I typically recommend that you keep them apart.
A wild guess could be to swap these two lines:
app.UseIdentityServer();
app.UseCors("CorsPolicy");
Because apparently IdentityServer captures the request to the API?
The most likely issue is that your call from your client to your API is not including the access token.
The debug log is coming from this file here. If you look at where your debug statement is originating from you will see that it is checking if the path matches any within IdentityServerOptions.Cors.CorsPaths. Here is an image of what those paths generally are from a debug service I made.
These paths are just the default information and authentication endpoints for IdentityServer4. In other words it thinks your request is unauthenticated because it likely isn't including the access token.
If you are using IdentityServer4's template logging implementation with Serilog, then you can also add this to your appsettings.json to see what the ASP.NET Core CORS middleware has to say. It will be logging after IdentityServer4's log
"Serilog": {
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.AspNetCore.Authentication": "Debug",
"Microsoft.AspNetCore.Cors": "Information",
"System": "Warning"
}
}
}
Here is what my debug log looked like when I made a request to an endpoint with a proper CORS policy, but the request didn't include its access token.
[21:05:47 Debug] IdentityServer.Hosting.CorsPolicyProvider CORS request made for path: /api/v1.0/users/{guid}/organizations from origin: https://localhost:44459 but was ignored because path was not for an allowed IdentityServer CORS endpoint
[21:05:47 Information] Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware No CORS policy found for the specified request.
So it's not a CORS issue really. It's an access token or authentication issue. It is also possible, however, that your endpoint isn't being hit properly. However, you should be receiving a 404 on the client in addition to the log seen above.

Firebase storage upload signed URL CORS issue

I am building an app for the web hosted on Firebase where I am trying to allow unauthenticated users to upload an image to Storage (after passing a reCAPTCHA).
To do this I have a React website and a Firebase function that validates the reCAPTCHA and then generates a signed URL for the client-side code to upload the image to the default bucket. I have deployed the Function to the cloud because there is no Storage emulator I can use for local development but my React app is being served locally.
The code below omits the usual boilerplate.
Firebase Function Code:
const id = uuidv4()
const bucket = storage.bucket("project-id.appspot.com");
const file = bucket.file(id)
const expires_at = Date.now() + 300000;
const config = {
action: 'write',
version: 'v2',
expires: expires_at,
contentType: 'application/octet-stream'
};
file.getSignedUrl(config, (err, url) => {
if (err) {
console.error(err);
res.status(500).end();
return;
}
res.send(url);
});
This returns a URL to the client of the form: https://storage.googleapis.com/project-id.appspot.com/db9a5cc5-6540-4f40-933f-cfdb287b15a9?GoogleAccessId=project-id%40appspot.gserviceaccount.com&Expires=1594719835&Signature=<signature here>
Client-side Code:
Once this is received on the client-side I try to upload the file to that URL using the PUT method:
// signed_url is the url returned from the first API call to the function above.
// image_file is the file data I get from using react-dropzone.
axios({
method: 'put',
url: signed_url,
data: image_file,
headers: {'Content-Type':'application/octet-stream'}
}).then(res => {
console.log("success");
console.log(res);
}).catch((error) => {
console.log(error);
});
This is where I get a CORS error of the form: Access to XMLHttpRequest at 'https://storage.googleapis.com/project-id.appspot.com/db9a5cc5-6540-4f40-933f-cfdb287b15a9?GoogleAccessId=project-id%40appspot.gserviceaccount.com&Expires=1594719835&Signature=<signature here>' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I used cURL to see if the CORS headers in the response were being set and they were not. I then used gsutil cors set config.json gs://project-id.appspot.com to set the CORS permissions on the default bucket. Here is the format of the configuration:
[
{
"origin": ["*"],
"responseHeader": [
"Origin",
"Accept",
"x-goog-resumable",
"Content-Type",
"Access-Control-Allow-Origin",
"Access-Control-Allow-Headers",
"Authorization",
"X-Requested-With"
],
"method": ["GET, POST, PUT, PATCH, POST, DELETE, OPTIONS"],
"maxAgeSeconds": 3600
}
]
I checked the service account to make sure they had the Service Account Token Creator permission and Storage Object Creator permissions set and they did.
I followed the steps at https://cloud.google.com/functions/docs/writing/http#gcloud and I have tried every combination of content-type headers and tried v2 and v4 of getSignedURL version, as well as following any other suggestions I could find online, but to no avail.

Access-Control-Allow-Origin Error when trying to add open graph meta tags dynamically via Firebase functions

I'm trying to add open graph meta tags dynamically to my index.html using firebase functions. I'm following this tutorial but because I'd like to allow users to share on their facebook I need to add open graph meta tags on-demand when user wants to share content.
So, it seems to me that running the function via rewrite in firebase.json is not best solution here and I'd like to call the function when user wants to share content to Facebook.
I try to use CORS but it seems I get the "Access-Control-Allow-Origin" error in the browser when I am sending my response back. If I redirect I have no issue. But I need to send the index.html that's been updated. How can I resolve this issue?
Here's my function:
const cors = require('cors')({ origin: true });
const fs = require('fs');
exports.handler = function(req, res) {
cors(req, res, () => {
let indexHTML = fs.readFileSync('./hosting/index.html').toString();
console.log(`#share-fb-formfeed inside cors`, indexHTML);
const ogPlaceholder = '<meta name="functions-insert-dynamic-og">';
res.set('Cache-Control', 'public, max-age=300, s-maxage=600');
res.status(200).send(indexHTML); //results in Access-Control-Allow-Origin
// res.redirect(`https://thekasis.com/feed`); //this works
});
}
Using Rewrites
I also removed cors in my function and here is my firebase.json but I'm not sure if running the Firebase function all the time would be a good idea. It would be ideal to run the function only on-demand when the user is sharing something and so I really like to be able to call the function client side on-demand.
{
"database": {
"rules": "database.rules.json"
},
"hosting": {
"public": "build/es5-bundled",
"rewrites": [{
"source": "**",
"function": "shareContentG-shareToFb"
}]
}
}

Resources