How can I pass values between koa-router routes - koa

I wanted to move the authentication procedure from all routes into one route (koa-router provides the all() middleware for all methods on a router for this). However, in the process, I decode a token whose decoding I need for further execution. How can I access this decoded token from another route?
const Router = require('koa-router');
const router = new Router({ prefix: '/test' });
router.all('/', async (ctx, next) => {
//decode
await next();
})
router.get('/', async ctx=> {
// Here I need to access decoded, too
});

the Koa Context object encapsulates the request, response and a state object, along with much more. This state object is the recommended namespace where you can pass data between middleware.
Modifiying the provided example gets:
const http = require('http')
const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const router = new Router({ prefix: '/test' })
router.all('/', async (ctx, next) => {
// decode token
const x = 'foo'
// assign decoded token to ctx.state
ctx.state.token = x
await next()
})
router.get('/', async ctx=> {
// access ctx.state
console.log(ctx.state.token)
})
app.use(router.routes())
http.createServer(app.callback()).listen(3000)
Navigate to http://localhost:3000/test and see the decoded token logged to the console.

Related

How to pass RESTDataSource response headers to the ApolloServer response header (express)

I have ApolloServer running where the frontend makes a query request and the ApolloService fetches the request and then performs a request with RESTDataSource to a third-party service, I receive a response with a header.
Currently, ApolloServer only parses the body through the resolver and sends it back to the client
I wanted to pass also the header received to the client
I don't know how to do that at the RESTDataSource level since I don't have access to the Apollo response
I hope this was clear enough to explain the problem
export abstract class myClass extends RESTDataSource {
getSomething() {
const endpoint = this.endpointPath;
return this.get(endpoint);
}
async didReceiveResponse<T>(response, request): Promise<T | null> {
// these are the response headers desired to have them sent back to the client
console.log(response.headers);
if (response.ok) {
return this.parseBody(response) as any as Promise<T>;
} else {
throw await this.errorFromResponse(response);
}
}
}
In the appolloService initialization i have
const apolloServer = new ApolloServer({
context: async ({ res, req }) => {
// these headers are not the same as received from the getSomething() response above
console.log(res.getHeaders)
}
)}
I solved the issue by passing the res to the context and accessing the response in the didReceiveResponse, then adding the headers needed.
adding a response to context
const apolloServer = new ApolloServer({
context: async ({ res, req }) => {
return {
res: res,
};}
using the response to append the headers to it
async didReceiveResponse<T>(response, request): Promise<T | null> {
// use this.authHeader value in the class anywhere
this.context.res.setHeader(
"x-is-request-cached",
response.headers.get("x-is-request-cached") ?? false
);
this.context.res.setHeader(
"x-request-cached-time",
response.headers.get("x-request-cached-time")
);
if (response.ok) {
return (await this.parseBody(response)) as any as Promise<T>;
} else {
throw await this.errorFromResponse(response);
}}
by doing this you will achieve the desired outcome of passing the headers to the graphQl client

How do I sign API requests (AWS SigV4) to Lambda behind Proxy & API Gateway?

I'm working on a project where we currently use Cognito User pools for auth., but after some research we found that if we want more fine-grained access-control we should use an Identity pool instead.
The theory is simple : first we create an Identity Pool that uses the Cognito user pool as Auth provider. Then in API Gateway we set up our Lambda to use Authorizer: AWS_IAM. To access it, User now has to :
Sign in to User pool, which gives user a JWT Token.
Exchange that JWT Token with the Identity pool for temporary AWS Credentials.
Use those new credentials to sign API request to the protected Lambda.
Steps 1 and 2 work fine, with a test user we manage to get the JWT Token and successfully exchange it for AWS credentials. They look like this (modified for security reasons):
awsAccessKey: ASIAZFDXSW29NWI3QZ01
awsSecretKey: B+DrYdPMFGbDd1VRLSPV387uHT715zs7IsvdNnDk
awsSessionToken: IQoJb3JpZ2luX2VjEA8aCWV1LXdlc3QtMyJHMEUCIQC4kHasZrfnaMezJkcPtDD8YizZlKESas/a5N9juG/wIQIgShWaOIgIc4X9Xrtlc+wiGuSC1AQNncwoac2vFkpJ3gkqxAQIWBAAGgw2NTI5NTE0MDE0MDIiDDuTZ1aGOpVffl3+XCqhBDmjCS3+1vSsMqV1GxZ96WMoIoEC1DMffPrBhc+NnBf94eMOI4g03M5gAm3uKAVCBkKO713TsQMaf4GOqqNemFC8LcJpKNrEQb+c+kJqqf7VWeWxveuGuPdHl1dmD2/lIc8giY0+q4Wgtbgs6i0/gR5HzdPfantrElu+cRNrn/wIq4Akf+aARUm14XsIgq7/1fT9aKSHpTgrnTLHeXLKOyf/lZ947XdH71IHDZXBUdwdPikJP/Rikwill6RRTVw7kGNOoacagCmmK7CD6uh9h0OnoW3Qw5df+zX5Z8U7U55AyQfEyzeB7bW3KH65yJn6sopegxIIFfcG2CLIvtb5cZYImAz/4BdnppYpsrEgLPUTvRAXn6KUa5sXgc5Vd7tJeRo5qpYckrR2qfbebsU+0361BCYK2HxGJqsUyt1GVsEoAosxofpn/61mYJXqfeR0ifCAgL7OMOquvlaUVXhHmnhWnUSIOUQ+XtRc+DxUDjwn5RPD7QTwLHIat7d4BI4gZJPAcMT9gZrBVO/iN88lk5R0M5LBzFwd5jiUW46H/G755I4e5ZHaT1I37TY3tbcObIFGVVNz5iHDpK/NePTJevKTshe8cYxXczOQgos4J/RsNpqouO9qRgT9JDyXjU3Etyxqm9RzbLYgV3fl5WwZl5ofVmrBsy3adq+088qEz5b9cogPgDggA/nQaPv7nAZHT8u0ct/hw230pmXUDGCutjOML2G6ZYGOoUCy+BitAN0SZOYWlbZlYomIGKMNQuXjV4z+S9CEW8VunqW4Rgl7rTba6xbI0DdX9upYEczeln6pTl+2UPEDYf6usayFfMsGDvJXesqC5EOtWco1Z8tem/wDQIH7ZbioQHZ7UJDd5ntUAruFveY7sXmKsQbtah/RB5W5HLYy19hCmyGpYMnVXxR0FcNGImsweNcprtw9MmQqy2SUK9V6Rwn1yIE6svfAT3NVyzp9ILbP/qSQLGHNhm4CNd8+EJZZa9rcmCbQiQ+iBJ8FW+AmRSCC4LiB1dhuH1KsFo88DyNhYdVf3py8XV4CDR7l+UyuZMrIQsERwx9JzwVBjfv9COT948mvyGTY
The issue is the signing. Our Lambda is behind a CloudFront proxy + API Gateway. Requests to e.g john.dev.project.io are forwarded to the 'real' API origin at api.dev.project.io.
Using Postman and setting AWS Signature, the request doesn't work and gives following error :
The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.\n\nThe Canonical String for this request should have been\n'................................................................................................................................................................................................................................................................'\n\nThe String-to-Sign should have been\n'............................................................................'\n
We found however, that by overriding the Host header to the real origin of the API, request now works fine :
So it seems that since the custom URL we use and the original API URL are different, signatures don't match. The problem is that by default browsers don't allow you to override Host header for security reasons, so our front-end signed requests always fail.
Maybe the proxy is also modifying other headers before forwarding to origin, which would also invalidate the signature from my understanding...
Any help appreciated in solving this issue!
I was facing a similar issue when trying to make a signed request to an API Gateway endpoint behind an Akamai proxy.
The trick to solve it was indeed to generate a request as if you were sending it directly to the API Gateway URL, sign that request using sigv4 and then send that signed request to the proxy endpoint instead.
I've put together a simple NodeJS code to exemplify how to do this:
const AWS = require("aws-sdk");
const { HttpRequest } = require("#aws-sdk/protocol-http");
const { SignatureV4 } = require("#aws-sdk/signature-v4");
const { NodeHttpHandler } = require("#aws-sdk/node-http-handler");
const { Sha256 } = require("#aws-crypto/sha256-browser");
const REGION = "ca-central-1";
const PROXY_DOMAIN = "proxy.domain.com";
const PROXY_PATH = "/proxypath";
const API_GATEWAY_DOMAIN = "API-ID.execute-api.ca-central-1.amazonaws.com";
const API_GATEWAY_PATH = "/apigateway/path";
const IDENTITY_ID = "{{identity-pool-region}}:{{identity-pool-id}}";
const POOL_REGION = "{{identity-pool-region}}";
const REQUEST_BODY = { test: "test" };
const METHOD = "POST";
const udpatedSignedRequestExample = async () => {
try {
const BODY = JSON.stringify(REQUEST_BODY);
const request = new HttpRequest({
body: BODY,
headers: {
"Content-Type": "application/json",
host: API_GATEWAY_DOMAIN,
},
hostname: API_GATEWAY_DOMAIN,
port: 443,
method: METHOD,
path: API_GATEWAY_PATH,
});
console.log("request", request);
const credentials = await getCredentials();
console.log(credentials);
const signedRequest = await signRequest(request, credentials);
console.log("signedRequest", signedRequest);
const updatedSignedRequest = updateRequest(signedRequest);
console.log("updatedSignedRequest", updatedSignedRequest);
const response = await makeSignedRequest(updatedSignedRequest);
console.log(response.statusCode + " " + response.body.statusMessage);
} catch (error) {
console.log(error);
}
};
const getCredentials = async () => {
var cognitoidentity = new AWS.CognitoIdentity({ region: POOL_REGION });
var params = {
IdentityId: IDENTITY_ID,
};
const response = await cognitoidentity
.getCredentialsForIdentity(params)
.promise();
return {
accessKeyId: response.Credentials.AccessKeyId,
secretAccessKey: response.Credentials.SecretKey,
sessionToken: response.Credentials.SessionToken,
expiration: response.Credentials.Expiration,
};
};
const signRequest = async (request, credentials) => {
const signer = new SignatureV4({
credentials: credentials,
region: REGION,
service: "execute-api",
sha256: Sha256,
});
const signedRequest = await signer.sign(request);
return signedRequest;
};
const updateRequest = (httpRequest) => {
httpRequest.hostname = PROXY_DOMAIN;
httpRequest.path = PROXY_PATH;
httpRequest.headers.host = PROXY_DOMAIN;
return httpRequest;
};
const makeSignedRequest = async (httpRequest) => {
const client = new NodeHttpHandler();
const { response } = await client.handle(httpRequest);
return response;
};
udpatedSignedRequestExample();
Hope that helps.

Puppeteer - how to iterate through queryObjects to collect the url of a WebSocket object?

I am using Puppeteer in a Node.js module. I retrieve a WebSocket object Prototype with queryObjects and I need to extract the url property.
// Get a handle to the websocket object prototype
const prototypeHandle = await page.evaluateHandle(() => WebSocket.prototype);
// Query all websocket instances into a jsHandle object
const jsHandle = await page.queryObjects(prototypeHandle);
// Count amount of map objects in heap
// const count = await page.evaluate(maps => maps.length, jsHandle); // returns the expected amount (x2)
// How to iterate through jsHandle to collect the url of each websockets
await jsHandle.dispose();
await prototypeHandle.dispose();
You do not get any response because WebSocket is not a simple JSON object which can be stringified and given back to you when you evaluate using page.evaluate.
To get the URL of the connected websocket in the page, you can map through the collected WebSocket instances/objects and extract the url out of them.
const browser = await puppeteer.launch();
const page = (await browser.pages())[0];
// create a dummy websocket connection for testing purpose
await page.evaluate(() => new WebSocket('wss://echo.websocket.org/'));
const wsPrototypeHandle = await page.evaluateHandle(
() => WebSocket.prototype
);
const wsInstances = await page.queryObjects(wsPrototypeHandle);
const wsUrls = await page.evaluate(
(e) => e.map((e) => e['url']), // <-- simply access the object here
wsInstances
);
console.log(wsUrls);
Which will result in following,
[ 'wss://echo.websocket.org/' ]

How to get session object in Microsoft azure bot sdk 4.0 in node.js?

Attaching the code snippet below. UniversalBot and ChatConnector has been deprecated in botbuilder 4.1.5.
var bot;
try {
bot = new BasicBot(conversationState, userState, botConfig);
} catch (err) {
console.error(`[botInitializationError]: ${ err }`);
process.exit();
}
// Create HTTP server
// let server = restify.createServer();
let server = express();
server.listen(process.env.port || process.env.PORT || 3978, function() {
console.log(`\n${ server.name } listening to ${ server.url }`);
console.log(`\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator`);
console.log(`\nTo talk to your bot, open basic-bot.bot file in the Emulator`);
});
// Listen for incoming activities and route them to your bot main dialog.
server.post('/api/messages', (req, res) => {
// Route received a request to adapter for processing
adapter.processActivity(req, res, async (turnContext) => {
// route to bot activity handler.
await bot.onTurn(turnContext);
});
});
Your question is fairly general.
The session object from 3.x has been removed. Instead acccessors are used. You will want to do following in the bot class:
public onTurn = async (turnContext: TurnContext) => {
const userProfile = await this.userProfile.get(turnContext, new UserProfile());
const conversationData = await this.dialogStateAccessor.get(turnContext, { dialogStack: undefined });
// set vars in cache
userProfile.yourUserVarProp = "userValue";
conversationData.yourConversationVarProp = "conversationValue";
// persist userVars through dialog turn
await this.userProfile.set(turnContext, userProfile);
// persist conversationVars through dialog turn
await this.dialogStateAccessor.set(turnContext, conversationData);
//
// -> your dialogs here (await dc.beginDialog("dialogname");)
//
// save uservars to db at end of a turn
await this.userState.saveChanges(turnContext);
// save conversationVars to db at end of a turn
await this.conversationState.saveChanges(turnContext);
}
But there is some additional constructor stuff
#param {ConversationState} conversationState A ConversationState object used to store the dialog state.
#param {UserState} userState A UserState object used to store values specific to the user.
... and creating the userProfile and dialogStateAccessor itself.
For the whole picture have better a look at https://github.com/Microsoft/BotBuilder-Samples/tree/master/samples/javascript_nodejs .
Or try the generator: https://learn.microsoft.com/en-us/azure/bot-service/javascript/bot-builder-javascript-quickstart?view=azure-bot-service-4.0.

Why do we await next when using koa routers?

Why do we do this
router.get('/data', async (ctx, next) => {
ctx.body = dummyjson.parse(data);
await next();
});
router.get('/data/:x', async (ctx, next) => {
const newData = dataRepeat.replace('%(x)', ctx.params.x);
ctx.body = dummyjson.parse(newData);
await next();
});
What is the use of await next()
It would work just fine without that. Similar thing was expected with koa 1. yield next was added at the end of the router.
I'll try to explain it using a very simple example:
const Koa = require('koa');
const app = new Koa();
// middleware
app.use(async function (ctx, next) {
console.log(1)
await next();
console.log(3)
});
// response
app.use(ctx => {
console.log(2)
});
app.listen(3000);
If you call localhost:3000 in your browser, the following will happen in your app:
The first app.use that you fired here was the middleware. So the request flow goes into that one first, logs 1to the console.
Then, when you see this await next(), it downstreams to the next use.
Here we just log 2 to the console. When this is finished (and no further await next is seen in the second use) the flow goes back to the first one which actually waited till the second one was finished.
Here we then continue with logging 3 to the console.
Hope this makes it a little more clear.
No, It is not necessary. It is depend on your requirement.
you use next() function when you call next middleware.
Check your router module and its version. I have use koa-router module and its version is 7.2.0 for routing. It self handle await next.
'use strict';
const Koa = require('koa'),
router = require('koa-router'),
app = new Koa();
let pubRouter = new router();
let securedRouter = new router();
let mapper = require('./mapper'),
// assign router to corresponding function
mapper(pubRouter, securedRouter);
app .use(logger(config.app.log))
.use(bodyParser())
.use(pubRouter.routes()).use(pubRouter.allowedMethods())
.use(jwt({
secret: publicKey,
algorithms: ['RS256']
}))
.use(async(ctx, next) => {
console.log('\n\n\n\n\n', ctx.state.user);
await next();
})
.use(securedRouter.routes()).use(securedRouter.allowedMethods())
.use(async(ctx, next) => {
ctx.body = 'Invalid URL!!!';
});
app.listen(port, () => console.log(`Server listening on port: ${port}`));

Resources