I have dumbed down my use case to a simple test, in which I click a button and an UPDATE put request is sent using an axios API. Three other request methods-- post, get, and delete, all work properly and are recognized. My PUT method, however, gives a 404 Not Found error, as though I havent established the method with express in my server.
This is the code for the request, triggered by this event handler (for the button):
const handleUpdate = async (e, id) => {
e.stopPropagation();
// navigate(`/restaurants/${id}/update`);
//update test
try {
const updatedRestaurant = await RestaurantFinder.put(`/${id}`, {
name: "taco bell",
location: "dogtown",
price_range: "2"
});
console.log(updatedRestaurant);
navigate("/");
} catch(err) {
console.log(err);
}
};
This is the api's instantiation:
import axios from "axios";
export default axios.create({
baseURL: "http://localhost:3001/api/v1/restaurants"
}
);
This is the request in Express. Note that the first log "does this exist???" is never displayed. The address for the put request to "http://localhost:3001/api/v1/restaurants/id" is never found.
//UPDATE a restaurant
app.put("/api/v1/restaurants/:id"), async (req, res) => {
console.log("does this exist???");
try {
const results = await db.query(
"UPDATE restaurants SET name = $1, location = $2, price_range = $3 where id = $4 returning *",
[req.body.name, req.body.location, req.body.price_range, req.params.id]
);
res.status(200).json({
status: "succes",
data: {
restaurant: results.rows[0],
},
});
} catch (err) {
console.log(err);
}
console.log(req.params.id);
console.log(req.body);
};
I have pored over StackOverflow for answers, I think I've viewed over 50 posts at this point. This is something that should be so simple, and yet I can't find a single answer for it or see where it's happening to anyone else.
If anyone could please help walk me through what is going wrong, I would be so grateful!
Edit 1 #Stephen:
const app = express();
app.use(cors(
{
methods: ["POST", "GET", "DELETE", "PUT"]
}
));
app.use(express.json());
So i changed it to this with no luck. Originally i was just using cors(), and my understanding is that it defaults to allowing a put method.
Does your server allow the put method? Perhaps your server only allows the others in the access-control-allow-methods field.
See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
For testing over http, make sure both of these are set by your server.
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: PUT, GET, POST, DELETE, OPTIONS
Related
I've implemented "Sign in with Apple" on my site. When I try it on my phone, it redirects me to a blank white page with the same URL as the redirect_uri I've configured.
I can't find any info on why this is happening. What's a possible fix?
UPDATE
It seems as if Apple JS SDK is creating a FORM HTML DOM element, sets the POST URL of the FORM to point to the redirect_uri, and finally programmatically clicks form.submit(). This for some reason causes the page to navigate to the redirect_uri and show the POST response as a new page.
I figured this out by tracking the Apple JS SDK in the debugger.
Here is my code
//---- Frontend ----
AppleID.auth.init({
clientId : '<client_id>',
scope : 'email',
redirectURI : 'mySite.com/apple_auth',
state : 'origin:web',
nonce : Date.now(),
//usePopup : true //not using this one. When false or undefined, Apple will make a POST request to the defined redirect_uri
})
// Listen for authorization success.
document.addEventListener('AppleIDSignInOnSuccess', (event) => {
// Handle successful response.
console.log(event.detail.data);
});
// Listen for authorization failures.
document.addEventListener('AppleIDSignInOnFailure', (event) => {
// Handle error.
console.log(event.detail.error);
});
//...
myButton.onClick = ()=>{
try {
var res = await AppleID.auth.signIn()
} catch(err) {
var x = 0
}
}
//---- Backend ----
var appleSignin = require("apple-signin-auth")
app.express.post('/apple_auth', async (req, res)=>{
var body = req.body
try {
const appleRes = await appleSignin.verifyIdToken(
body.id_token, // We need to pass the token that we wish to decode.
{
audience: '<client_id', // client id - The same one we used on the frontend, this is the secret key used for encoding and decoding the token.
ignoreExpiration: true, // Token will not expire unless you manually do so.
}
)
//do something with the Apple response
} catch (err) {
// Token is not verified
console.error(err)
}
})
From the documentation...
The HTTP body contains the result parameters with a content-type of application/x-www-form-urlencoded.
Make sure you've configured the urlencoded() body-parsing middleware in your Express app.
app.use(express.urlencoded());
Make sure you check for errors and actually send a response from your /apple_auth Express route
const { code, id_token, state, user, error } = req.body;
if (error) {
return res.status(500).send(error);
}
try {
const appleRes = await appleSignin.verifyIdToken(id_token, {
audience: "<client_id>",
ignoreExpiration: true,
});
// do something with the Apple response, then send a response
res.send(appleRes.sub);
} catch (err) {
console.error(err);
res.sendStatus(500); // send a 500 response status
}
I know this question has been asked several times, but the solution provided didn't work for me.
I have a protected route to find a user. The request is validated by an authenticate middleware, which basically checks if the user's cookie is valid, and then the route getUser is called. The route works well when I don't use the middleware, so the issue may come from authenticate.
I have used return res.status(200).json({}) as suggested.
When testing the route with the cookie, chai makes two calls. The first one succeeds, but as soon as the route is hit, another call is made, without any cookie. Weird. In Postman, same thing. It works wonderfully without authenticate, but returns unauthorized with it. In the terminal, the error is:
[ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
Here is the authenticate middleware and the route:
// router
const router = express.Router();
router.get("/user/:id", authenticate, getUser);
// authenticate
const authenticate = (req: Authenticate, res: Response, next: NextFunction) => {
const { mycookie } = req.cookies;
if (mycookie) {
jwt.verify(mycookie, process.env.JWT_TOKEN_KEY, (error, parsedToken) => {
if (error) {
return res.sendStatus(403);
}
req.cookie = { _id: parsedToken._id, locale: parsedToken.locale };
return next();
});
}
return res.sendStatus(401);
};
export default authenticate;
// the get - user/:id route
const getUser = async (
req: GetUser,
res: IResponse
): Promise<IResponse> => {
try {
const user = await UserControler.findUserById(req.params.id);
return res.status(200).json({ user });
} catch (err) {
throw new Error("error.unknown");
}
};
export default getUser;
How to fix this?
In your authenticate functions, you forgot to add an else statement to if (mycookie).
Unauthorized will therefore always be sent, which is clearly the reason why you get unauthorized with a good cookie, and then as unauthorized as already been sent, the error you describe will be throw when getUser tries to sent the http response.
Wrap return res.sendStatus(401); in an else statement and it should work fine.
I've got a Node app using express-openapi-validator that takes a an api spec file (which is a .yml file), with request and response validation. The express-openapi-validator package routes the request to a handler file (defined in the spec). This is what one of the handlers might look like:
function getUsers(req, res) {
const { 'x-user-id': userId } = req.headers
res.status(200).json(`Your userId is ${userId}`)
}
I've got an API key feature, where users can get a new API key, and the other endpoints that need the caller to have the API key in the request headers to validate the request.
I know it should be possible to use middleware to validate the request, but I can't figure out how to use custom middleware with the express-openapi-validator package on select endpoints.
For eg:
GET /apikey = does not require api key
GET /resource = requires api key
How do I configure this?
Here's what the openapi validator code in my app.js looks like:
new OpenApiValidator({
apiSpec,
validateResponses: true,
operationHandlers: path.join(__dirname, './handlers'),
})
.install(app)
.then(() => {
app.use((err, _, res) => {
res.status(err.status || 500).json({
message: err.message,
errors: err.errors,
});
});
});
I actually ended up finding a solution for this myself.
First of all, I'm using version 4.10.5 of express-openapi-validator, so the code above is slightly different.
Here's what it looks like now:
// index.js
app.use(
OpenApiValidator.middleware({
apiSpec,
validateResponses: true,
operationHandlers: path.join(__dirname, './handlers'),
validateSecurity: {
handlers: {
verifyApiKey(req, scopes) {
return middleware.verifyApiKey(req)
},
bearerAuth(req, scopes) {
return middleware.verifyToken(req)
}
}
},
}),
);
app.use((err, req, res, next) => {
res.status(err.status || 500).json({
message: err.message,
errors: err.errors,
});
The way I ended up using middleware in my routes is below:
I've added a securitySchemes section in my swagger.yml file, like so:
components:
securitySchemes:
verifyApiKey:
type: apiKey
in: header
name: x-api-key
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
There's a bit more information about it here: https://swagger.io/docs/specification/authentication/
On each route that needs the middleware, I'm adding a security section, like so:
/team:
post:
security:
- bearerAuth: []
description: Create a new team
operationId: createTeam
x-eov-operation-id: createTeam
x-eov-operation-handler: team
As you can see in my code above (in the index.js file), I've got a validateSecurity key, with a handlers key that then has the correlating keys that are in my swagger.yml (verifyApiKey and bearerAuth). These functions get the request and scope to check if they're valid. These functions return a boolean value, so true means that the middleware lets the request through, and false means a 403 response will be returned.
validateSecurity: {
handlers: {
verifyApiKey(req, scopes) {
return middleware.verifyApiKey(req)
},
bearerAuth(req, scopes) {
return middleware.verifyToken(req)
}
}
},
Please respond if I've got anything above wrong, or if the explanation can be clearer. If you have questions, please post them below.
You can simply pass array of handlers instead of just 1 function, like in express.
So in you code, the getUsers function that probably is what the x-eov-operation-id refers to, would be an array of 2 functions:
const getUsers = [
apiKeyMiddleware,
(req, res) => {
const { 'x-user-id': userId } = req.headers
res.status(200).json(`Your userId is ${userId}`)
}
];
I was in a similar situation as you, using OpenAPI/Swagger packages like that limited my ability to add specific middleware per endpoint, so my solution was I created an npm module called #zishone/chaindler.
You can use it like this:
const { Chain } = require('#zishone/chaindler');
function getUsers(req, res) {
const { 'x-user-id': userId } = req.headers
res.status(200).json(`Your userId is ${userId}`)
}
function postUsers(req, res) {
// ...
}
function mw1(req, res, next) {
next()
}
function mw2(req, res, next) {
next()
}
module.exports = {
getUsers: new Chain(mw1, mw2).handle(getUsers),
postUsers: new Chain(mw1).handle(postUsers)
}
Basically it just chains the middlewares then calls them one by one then call the handler/controller last.
Thanks for reading my question!
I'm making a chat-app where people can chat with each other who are born at the same date.
So the question is,
How do I change the route with an additional birthdate which is changing with every different user with different birthdates?
I already can signup/login and save the birthdate for every user.
So I need to take the birthdate from the mongoDB to put it in the url so nobody gets directed to the wrong chat.
router.get("/", chatController.getAll);
app.use('/api/v1/chat', passport.authenticate('jwt', { session: false }), apiChatRouter);
const getAll = (req,res)=>{
Message.find({},(err,docs)=>{
if(!err){
res.json({
"status":"succes",
"data":{
"chat":docs
}
});
}
});
}
fetch('http://localhost:3000/api/v1/chat/', {
//nodige headers meegeven met localstorage
'headers':{
'Authorization': 'Bearer ' + localStorage.getItem('token')
}
}).then(result => {
return result.json();
}).then(json =>{
console.log(json);
}).catch(err =>{
console.log("Unauthorized")
});
If you want to check the rest of the code:
https://github.com/abuijzen/Herexamen-Webtech3
You can use express path param please check the below example
To redirect user to other path you can user
res.redirect('/1990-03-29');
to get param from url
app.get('/api/v1/chat/:bd', function (req, res) {
// http://localhost:xx/api/v1/chat/1990-03-29
req.params // eqal { "bd": "1990-03-29" }
})
Please check routing and redirect
So I've created a bunch of mutations and queries and stitched them together that works and wanted to introduce authentication into the mix. I added an HTTP Header "x-token" to hold my sign-in token to be able to delete things like their job or the user itself.
const getMe = async req => {
const token = req.headers['x-token'];
if (token) {
try {
return await jwt.verify(token, "notSoSecret");
} catch (e) {
throw new AuthenticationError(
'Your session expired. Sign in again.',
);
}
}
};
const server = new ApolloServer({
typeDefs: schema,
resolvers,
formatError: error => {
// remove the internal sequelize error message
// leave only the important validation error
const message = error.message
.replace('SequelizeValidationError: ', '')
.replace('Validation error: ', '');
return {
...error,
message,
};
},
context: async ({ req }) => {
const me = await getMe(req);
return {
models,
me,
secret: "notSoSecret",
}
},
path: "/graphql"
});
server.applyMiddleware({ app });
sequelize.sync().then(async () => {
createUsersWithJob();
});
app.get("/playground", graphiql({ endpoint: "/graphql" }));
const handler = serverless(app);
export { handler };
const createUsersWithJob = ... //creates seed data
So when I add the token and I look into my command line console, I actually see that I'm setting the header that I want, but it loops over and over again and doesn't stop. Also playground gets an error "Server cannot be reached"
{
"error": "Response not successful: Received status code 400"
}
and running a deleteUser mutation does not work, or any other mutation and query for that matter until I remove the HTTP Header that I set on playground.
There is a secondary issue where everything in this root file runs twice, but that's not as big for me at the moment as the header issue outlined.
If anyone has any insight into this, I'd love to know more. Thanks in advance.
edit: just a quick edit to say that it works fine when I hardcode a pre-existing user.
I had quite a struggle to get the React version of GraphQL Playground working within a very simple html setup, but I figured something out that might help you as well (fingers crossed).
I added a headers section to the config in the GraphQLPlayground.init call, like so:
const root = document.getElementById('root');
GraphQLPlayground.init(root, {
endpoint: "/graphql",
headers: {
"Authorization": "Bearer " + token
}
})
I have an element with an id root since this is embedded in HTML.
Not sure this will help you though, as I just noticed from your code sample you're calling graphiql which is a different GraphQL client than GraphQL Playground..
GraphIQL: https://github.com/skevy/graphiql-app
GraphQL Playground: https://github.com/prisma/graphql-playground