I am trying to apply JWT(JSON Web Token) in Meteor server-side API's with Iron-router.
I am able to create token but I can't get method in iron-router to write middleware for verifying that token.
Can you Please explain Which method is used to write middleware and how to define it ?
In iron-router, middleware is defined using the onBeforeAction method (see "Server Hooks and Connect" in the guide). So for JWT verification, you can use something like this:
import { Meteor } from 'meteor/meteor';
import { Router } from 'meteor/iron:router';
import jwt from 'jsonwebtoken';
Router.onBeforeAction(function(req, res, next) {
jwt.verify(req.query.token, "secret", (err, decoded) => {
if (err) {
console.log(`Failed to verify token. Error: "${err}"`);
res.statusCode = 401;
res.end('invalid token');
} else {
next();
}
});
}, {where: 'server'});
// example server-side route for testing
Router.route('/server', { where: 'server' })
.get(function () {
this.response.end('ok');
})
Failing example (true negative):
> curl -i localhost:3000/server?token=totally-wrong
HTTP/1.1 401 Unauthorized
date: Mon, 25 Nov 2019 16:41:33 GMT
connection: keep-alive
transfer-encoding: chunked
invalid token
Succeeding example (true positive):
> curl -i localhost:3000/server?token=eyJhbGciOiJIUzI1NiJ9.MTIzNDU2Nzg5MA.oy8UH-Y_-5ztrLKo8zgx-8b8AKvUTy4ijH-ItJU32qo
HTTP/1.1 200 OK
date: Mon, 25 Nov 2019 16:41:52 GMT
connection: keep-alive
transfer-encoding: chunked
ok
There're a handful of ways to achieve this, this simplest in my opinion is to apply your logic within the action method of the route like so:
Router.route("/dashboard", {
name: "dashboard",
action: function() {
const token = this.params.query.token
try {
var decoded = jwt.verify(token, "secret");
this.render()
} catch (err) {
this.render("home"); // go home
}
}
});
You may also try tinkering with hooks or plugins as mentioned in the docs.
Related
I'm getting the following error:
WebPushError: Received unexpected response code
at IncomingMessage.<anonymous> (/Users/sepp/.../node_modules/web-push/src/web-push-lib.js:347:20)
at IncomingMessage.emit (node:events:406:35)
at endReadableNT (node:internal/streams/readable:1331:12)
at processTicksAndRejections (node:internal/process/task_queues:83:21) {
statusCode: 401,
headers: {
'content-type': 'text/plain; charset=utf-8',
'x-content-type-options': 'nosniff',
'x-frame-options': 'SAMEORIGIN',
'x-xss-protection': '0',
date: 'Wed, 01 Feb 2023 19:57:43 GMT',
'content-length': '40',
'alt-svc': 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000',
connection: 'close'
},
body: 'authorization header must be specified.\n',
endpoint: 'https://fcm.googleapis.com/fcm/send/duj-etc-etc
The code involved is:
import * as webPush from "web-push";
const subDetails = {
endpoint: "https://fcm.googleapis.com/fcm/send/duja6etc-etc",
expirationTime: null,
keys: {
p256dh: "BHtwM-etc-etc",
auth: "aYkx0etc-etc"
}
}
await webPush.sendNotification(subDetails, "test message", );
I found this issue on Github, and there was some debilitation as to whether or not it has to do with the environment. I am running my front-end page and back-end server both locally. There is a 'x-frame-options': 'SAMEORIGIN' header in the response.
As you can see from the code above, I do not have VAPID set up.
If I use console.log(webPush.generateRequestDetails(pushSub.details, args.msg)) to see what the headers and body of the request are, I get the following details, which show that the auth header is not set:
{
method: 'POST',
headers: {
TTL: 2419200,
'Content-Length': 121,
'Content-Type': 'application/octet-stream',
'Content-Encoding': 'aes128gcm'
},
body: <Buffer ....>,
endpoint: 'https://fcm.googleapis.com/fcm/send/duj-etc-etc'
}
Questions
Are there any special requirements for localhost stuff?
What does it take for auth headers to be included?
EDIT: The browser I'm using is Opera GX. I did find a browser support table, which says that opera does not yet support push on desktop. The error still seems to imply something else may be the issue. Testing in Firefox Dev Edition, it works! Unfortunately, in Chrome the same exact error as Opera GX is given.
The issue is two-fold.
Issue #1: Opera GX does not support push notifications on desktop. Check this table for details on your browser.
Issue #2: For any push services which use a https://fcm.googleapis.com/fcm/send/ endpoint, you'll need auth headers. To create them, you'll need a VAPID. Here's how to set that up in web-push:
Create your public and private keys in command line (you many need to do ./node_modules/.bin/web-push instead):
$ web-push generate-vapid-keys --json
Store the private key somewhere safe only your server can get to it. Public key will be needed by both front and back end.
Update your code to generate auth headers and add them to the request
import * as webPush from "web-push";
const subDetails = {
endpoint: "https://fcm.googleapis.com/fcm/send/duja6etc-etc",
expirationTime: null,
keys: {
p256dh: "BHtwM-etc-etc",
auth: "aYkx0etc-etc"
}
}
const VAPID = {
publicKey: "lalalla-etc-etc-put-anywhere",
privateKey: "lCRVkwS-etc-etc-put-somewhere-safe"
}
const parsedUrl = new URL(subDetails.endpoint);
const audience = `${parsedUrl.protocol}//${parsedUrl.hostname}`;
// technically, the audience doesn't change between calls, so this can be cached in a non-minimal example
const vapidHeaders = webPush.getVapidHeaders(
audience,
'mailto: example#web-push-node.org',
VAPID.publicKey,
VAPID.privateKey,
'aes128gcm'
);
await webPush.sendNotification(subDetails, "test msg", {
headers: vapidHeaders
});
The code above should work fine in chrome and firefox. Let me know if this minimal example needs more for some other browser.
Trying to make a Nextjs app with apollo, graphql and prisma. When I'm trying to query users on my front end, I'm getting a 400 error. The query works in apollo studio / sandbox so I have no idea how to fix it.
error.message: Response not successful: Received status code 400
And nothing in the server log.
schema.ts:
export const typeDefs = gql`
type User {
id: String
name: String
email: String
image: String
}
type Query {
AllUsersQuery: [User]!
}
my resolver.ts:
export const resolvers = {
Query: {
AllUsersQuery: async (_parent: any, __args: any, context: any) => await context.prisma.user.findMany(),
},
};
And where I'm calling it from board.tsx:
const AllUsersQuery = gql`
query {
user{
id
name
email
}
}
`
const Board = () => {
const { data, loading, error } = useQuery(AllUsersQuery);
if (loading) return <div> Loading... </div>
if (error) return <div> Oops something went wrong: {error.message}</div>
return (
<>
<div>{data?.user.email}</div>
</>
)
}
export default Board
response/request header:
XHRPOSThttp://localhost:3000/api/graphql
[HTTP/1.1 400 Bad Request 13ms]
POST
http://localhost:3000/api/graphql
Status
400
Bad Request
VersionHTTP/1.1
Transferred766 B (1.21 kB size)
Referrer Policystrict-origin-when-cross-origin
HTTP/1.1 400 Bad Request
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Content-Type: application/json
Vary: Accept-Encoding
Content-Encoding: gzip
Date: Sun, 02 Oct 2022 03:20:09 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked
POST /api/graphql HTTP/1.1
Host: localhost:3000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:105.0) Gecko/20100101 Firefox/105.0
Accept: */*
Accept-Language: en-CA,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Referer: http://localhost:3000/board
content-type: application/json
Content-Length: 91
Origin: http://localhost:3000
Connection: keep-alive
Cookie: _ga=GA1.1.1971964746.1663710154; next-auth.csrf-token=33c501d5216dea4b6a029d34c13d640814228810867843882008780ce09eb536%7C83d7939882d2f38e49f72a501e895459abb7fbac2fbab0d106c6462fe35cbe7e; next-auth.callback-url=http%3A%2F%2Flocalhost%3A3000%2Flogin; next-auth.session-token=c7358df1-2605-4a34-bb5d-a1091a00b871
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
What am I doing wrong? Thanks
It appears you need to clean and flip some naming conventions. You likely want to define a resolver on your root Query type simply as users, which in turn is the field you'll access on the returned optional (data) on execution. Defining the query in this way provides flexibility; for example perhaps you add a filter arg in the future with a simple id or more complex input type. In the case, the lack of flexibility becomes evident.
export const typeDefs = gql`
type User {
id: String
name: String
email: String
image: String
}
type Query {
users: [User]!
}
// update resolver to reflect schema change on Query
Query: {
users: async (_parent: any, __args: any, context: any) => await context.prisma.user.findMany(),
},
Now, for your (currently filterless) query operation it makes sense to declare AllUsersQuery, an accurate description of your client implementation. In the case you added an arg to fetch a subset (which would require updating resolver to something like users(ids: [String]): [User], a different naming convention could be provided for the operation (UsersByIdsQuery)
const AllUsersQuery = gql`
query {
users {
id
name
email
}
}
`
const Board = () => {
const { data, loading, error } = useQuery(AllUsersQuery);
if (loading) return <div> Loading... </div>
if (error) return <div> Oops something went wrong: {error.message}</div>
return (
<>
<div>{data?.users.email}</div>
</>
)
}
export default Board
If you wanted to leave your schema, resolver, and operation as is, you would need to update the field requested on your query as reflected in your schema, and similarly access that field on the data optional in your code.
const AllUsersQuery = gql`
query {
AllUsersQuery {
id
name
email
}
}
I have created a middleware as pages/api/open/_middleware.ts. Here's the code:
import { NextResponse } from 'next/server';
import { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// create an instance of the class to access the public methods. This uses `next()`,
// you could use `redirect()` or `rewrite()` as well
console.log(
request.method,
request.body,
request.headers.get('Authorization')
);
let response = NextResponse.next();
// get the cookies from the request
if (request.method === 'GET')
return NextResponse.json({ name: 'UnAuthenticated' });
return response;
}
I tried to make request from VSCode Http Client, Postman and Python too. But in all cases the request.body is consoled as null:
VSCode Client:
POST http://localhost:3000/api/open HTTP/1.1
Content-Type: application/json
Authorization: xxxxxxxxxxxxxxxxx
Accept: application/json
{
"name": "My First Project",
"description": "This is my first project",
"url": "http://localhost:3000/api/open"
}
Postman Client:
Python Requests module:
>>> from requests import post
>>> post("http://localhost:3000/api/open",json={"a":1})
<Response [200]>
>>> headers={"Content-Type":"application/json"}
>>> post("http://localhost:3000/api/open",json={"a":1},headers=headers)
<Response [200]>
>>>
But all of this print null in console:
event - compiled successfully in 60 ms (151 modules)
POST null xxxxxxxxxxxxxxxxx
Headers are parsed correctly but the body never gets parsed even after specifying content-type.
Can someone help me understand what's going wrong here? Aren't middleware supposed to intercept the request body?
my axios code:
const instance = axios.create({
baseURL: process.env.BASE_API,
timeout: 5000,
withCredentials: true,
headers: {
'content-type': 'application/x-www-form-urlencoded;charset=UTF-8'
}
})
function get (url, getData) {
return instance.get(url, {
params: getData
})
}
function post (url, postData) {
return instance.post(url, qs.stringify(postData))
}
function put (url, putData) {
return instance.put(url, qs.stringify(putData))
}
export default {
get: get,
post: post,
put: put
}
Post request with content-type': 'application/x-www-form-urlencoded; charset=UTF-8 is useful
However, when using PUT, the request header does not have a content-type': 'application/x-www-form-urlencoded; charset=UTF-8
Causes the put request to become an options
It's not so clear from your question what exactly you are trying to ask. I'll assume you want your PUT request to actually send a PUT request instead of just an OPTIONS request. I'm also assuming that you are making requests to an API that you control.
I had the same problem (i.e. I was only seeing OPTIONS requests when I tried to make PUT calls) and I discovered that in my API I did not have the PUT options enabled in my CORS settings. I'm using rails so all I had to do is add :put to my cors middleware:
config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :put, :options]
end
end
I figured this out based on this answer
I've read node.js Extracting POST data.
But here's my problem, how to extract POST data with Express when I received a HTTP request looking like this?
POST /messages HTTP/1.1
Host: localhost:3000
Connection: keep-alive
Content-Length: 9
User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.52 Safari/536.5
Content-Type: application/xml
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4 Accept-Charset: UTF-8,*;q=0.5
msg=hello
I can't seem to get the msg=hello key-value pair out of the body with Express.
I've tried all of these methods req.header() req.param() req.query() req.body but they seem to be empty.
How to get the body's content?
app.post('/messages', function (req, res) {
req.??
});
Your problem is bodyParser does not handle 'application/xml', I solved this mainly by reading this post: https://groups.google.com/forum/?fromgroups=#!topic/express-js/6zAebaDY6ug
You need to write your own parser, I've published the below with more detail to github:
https://github.com/brandid/express-xmlBodyParser
var utils = require('express/node_modules/connect/lib/utils', fs = require('fs'), xml2js = require('xml2js');
function xmlBodyParser(req, res, next) {
if (req._body) return next();
req.body = req.body || {};
// ignore GET
if ('GET' == req.method || 'HEAD' == req.method) return next();
// check Content-Type
if ('text/xml' != utils.mime(req)) return next();
// flag as parsed
req._body = true;
// parse
var buf = '';
req.setEncoding('utf8');
req.on('data', function(chunk){ buf += chunk });
req.on('end', function(){
parser.parseString(buf, function(err, json) {
if (err) {
err.status = 400;
next(err);
} else {
req.body = json;
next();
}
});
});
}
then use it with
app.use (xmlBodyParser);
If you have this in the config:
app.use(express.bodyParser());
And this in your view:
form(name='test',method='post',action='/messages')
input(name='msg')
Then this should work:
app.post('/messages', function (req, res) {
console.log(req.body.msg);
//if it's a parameter then this will work
console.log(req.params.msg)
});
I believe you need to configure express to use the bodyParser middleware.
app.use(express.bodyParser());
See the express documentation.
It says:
For example we can POST some json, and echo the json back using the bodyParser middleware which will parse json request bodies (as well as others), and place the result in req.body
req.body() should now return the expected post body.
I hope this helps!
It's POSSIBLE (not sure what it depends on, but it happened to me once, it might be the bodyParser) that the request body is formatted in such a way that your JSON data is ITSELF being treated as a key in a key-value pair, with a blank corresponding value. What's worked for me in this situation was to extract the JSON object first and then proceed as normal:
var value;
for (var item in req.body)
{
var jObject = JSON.parse(item);
if (jObject.valueYouWant != undefined)
{
value = jObject.valueYouWant;
}
}
This is probably pretty suboptimal, but if nothing else works (I tried for ages trying to find a better way and found none) this might work for you.
You are posting xml as I can see, the answers you got were based on JSON input. If you want the content of your xml displayed, process the raw request :
app.post('/processXml',function (req, res)
{
var thebody = '';
req.on('data' , function(chunk)
{
thebody += chunk;
}).on('end', function()
{
console.log("body:", thebody);
});
});
As an example using curl as your postman:
curl -d '<myxml prop1="white" prop2="red">this is great</myxml>' -H
"Content-type: application/xml" -X POST
http://localhost:3000/processXml
Outputting:
'<myxml prop1="white" prop2="red">this is great</myxml>'
Make sure your body-parser middleware doesn't get in the way: body-parser-xml processes your request object on the fly to a json object, after which you cannot process your raw request anymore. (And you can guess who was stuck several hours after this...)