Nestjs wss handleConnection(socket) socket.handshake is undefined and cannot access headers to authenticate - node.js

I have a Nestjs gateway where I'm trying to run authorization logic depending on values from the headers but wherever I'm trying to access the handshake it always returns 'undefined'
I'm also trying this over SSL which might be making a difference.
main.ts:
import { WsAdapter } from "#nestjs/platform-ws";
app = await NestFactory.create(UserMicroserviceModule, {
httpsOptions: {
key: get_ssl("key"),
cert: get_ssl("cert"),
},
});
app.useWebSocketAdapter(new WsAdapter(app));
await app.listen(process.env.PORT || 443);
gateway.ts:
#UseGuards(SessionGuard)
#WebSocketGateway({ path: "/user" })
export class UserMicroserviceGateway implements OnGatewayConnection {
handleConnection(socket) {
socket.handshake // <== undefined
}
session.guard.ts:
const socket_cookie: any = context.switchToWs().getClient().handshake; // <== undefined
And also, although I've addeded the session guard on the whole gateway --- the guard does not trigger for handleConnection()

Guards and other decorators don't work for the handleConnection() method in nestjs currently and the handshake isn't a concept that exists on the vanilla socket but more of a socket.io thing, so I just switched to using that and manually ran the actions needed for verifications inside the handleConnection() method.

Related

How does one secure api keys on sveltekit 1.0

I am using ghost, i made an integration and i would like to hide the api key from the front-end. I do not believe i can set restrictions on the ghost cms (that would also work). And i do believe so +page.js files are run on the browser also, so im a little confused on how to achieve this?
The interal sveltekit module $env/static/private (docs) is how you use secure API keys. Sveltekit will not allow you to import this module into client code so it provides an extra layer of safety. Vite automatically loads your enviroment variables from .env files and process.env on build and injects your key into your server side bundle.
import { API_KEY } from '$env/static/private';
// Use your secret
Sveltekit has 4 modules for accessing enviroment variables
$env/static/private (covered)
$env/static/public accessiable by server and client and injected at build (docs)
$env/dynamic/private provided by your runtime adapter; only includes variables with that do not start with the your public prefix which defaults to PUBLIC_ and can only be imported by server files (docs)
$env/dynamic/public provided by your runtime adapter; only includes variables with that do start with the your public prefix which defaults to PUBLIC_ (docs)
You don't need to hide the key.
Ghost Content API Docs:
These keys are safe for use in browsers and other insecure environments, as they only ever provide access to public data.
One common way to hide your third-party API key(s) from public view is to set up proxy API routes.
The general idea is to have your client (browser) query a proxy API route that you provide/host, have that proxy route query the third-party API using your credentials (API key), and pass on the results from the third-party API back to the client.
Because the query to the third-party API takes place exclusively on the back-end, your credentials are never exposed to the client (browser) and thus not visible to the public.
In your use case, you would have to create 3 dynamic endpoint routes to replicate the structure of Ghost's API:
src/routes/api/[resource]/+server.js to match /posts/, /authors/, /tags/, etc.:
const API_KEY = <your_api_key>; // preferably pulled from ENV
const GHOST_URL = `https://<your_ghost_admin_domain>/ghost/api/content`;
export function GET({ params, url }) {
const { resource } = params;
const queryString = url.searchParams.toString();
return fetch(`${GHOST_URL}/${resource}/?key=${API_KEY}${queryString ? `&${queryString}` : ''}`, {
headers: {
'Accept-Version': '5.0' // Ghost API Version setting
}
});
}
src/routes/api/[resource]/[id]/+server.js to match /posts/{id}/, /authors/{id}/, etc.:
const API_KEY = <your_api_key>; // preferably pulled from ENV
const GHOST_URL = `https://<your_ghost_admin_domain>/ghost/api/content`;
export function GET({ params, url }) {
const { resource, id } = params;
const queryString = url.searchParams.toString();
return fetch(`${GHOST_URL}/${resource}/${id}/?key=${API_KEY}${queryString ? `&${queryString}` : ''}`, {
headers: {
'Accept-Version': '5.0' // Ghost API Version setting
}
});
}
src/routes/api/[resource]/slug/[slug]/+server.js to match /posts/slug/{slug}/, /authors/slug/{slug}/, etc.:
const API_KEY = <your_api_key>; // preferably pulled from ENV
const GHOST_URL = `https://<your_ghost_admin_domain>/ghost/api/content`;
export function GET({ params, url }) {
const { resource, slug } = params;
const queryString = url.searchParams.toString();
return fetch(`${GHOST_URL}/${resource}/slug/${slug}/?key=${API_KEY}${queryString ? `&${queryString}` : ''}`, {
headers: {
'Accept-Version': '5.0' // Ghost API Version setting
}
});
}
Then all you have to do is call your proxy routes in place of your original third-party API routes in your app:
// very barebones example
<script>
let uri;
let data;
async function get() {
const res = await fetch(`/api/${uri}`);
data = await res.json();
}
</script>
<input name="uri" bind:value={uri} />
<button on:click={get}>GET</button>
{data}
Note that using proxy API routes will also have the additional benefit of sidestepping potential CORS issues.

"Google Auth Library: Node.js" expects localhost:3000 as redirect uri

I am using #google-cloud/local-auth to auth an application on a node back end. I have set up a OAuth 2.0 Client IDs with the type of Web application on the google console and pointed the Authorized redirect URIs to http://localhost:3001 because that is the port I am using for dev, however I can see that the source code for #google-cloud/local-auth/build/src/index.js:56:15 clearly expects 3000
if (redirectUri.length === 0 ||
parts.port !== '3000' ||
parts.hostname !== 'localhost' ||
parts.pathname !== '/oauth2callback') {
throw new Error(invalidRedirectUri);
}
Why would this be hardcoded, what happens when you use a diferent port?
I am adding my code for context
import gsc from '#googleapis/searchconsole'
import { authenticate } from '#google-cloud/local-auth'
import { resolve } from 'path'
export default async () => {
// Obtain user credentials to use for the request
const auth = await authenticate({
keyfilePath: resolve('requests/client_secret.json'),
scopes: [
'https://www.googleapis.com/auth/webmasters',
'https://www.googleapis.com/auth/webmasters.readonly'
]
})
google.options({ auth })
const res = await gsc.sites.list({})
console.log(res.data)
}
This library is meant to demonstrate authentication for sample purposes; it should be treated as a starting point for building an application and is not a general-purpose solution. Read it here
That port number is expected since this library is for demonstration purposes, if you are developing a production-ready application, you need to use something like https://github.com/googleapis/google-auth-library-nodejs

How do I add parameters to long-form return requests in module.exports routes?

I'm coding for an API connection area, that's predominately graphql but needs to have some REST connections for certain things, and have equivalent to the following code:
foo.js
module.exports = {
routes: () => {
return [
{
method: 'GET',
path: '/existing_endpoint',
handler: module.exports.existing_endpoint
},
{
method: 'POST',
path: '/new_endpoint',
handler: module.exports.new_endpoint // <--- this not passing variables
}
]
},
existing_endpoint: async () => {
/* endpoint that isn't the concern of this */
},
new_endpoint: async (req, res) => {
console.log({req, res})
return 1
}
}
The existing GET endpoint works fine, but my POST endpoint always errors out with the console of {} where {req, res} should have been passed in by the router, I suspect because the POST isn't receiving. I've tried changing the POST declaration in the routes to module.exports.new_endpoint(req, res), but it tells me the variables aren't found, and the lead-in server.js does have the file (it looks more like this...), and doing similar with the server.js, also getting similar results, implying that's probably wrong too. Also, we have a really strict eslint setup, so I can't really change the format of the call.
Every example I've seen online using these libraries is some short form, or includes the function in the routes call, and isn't some long form like this. How do I do a POST in this format?
/* hapi, environment variables, apollog server, log engine, etc. */
/* preceeding library inclusions */
const foo = require('./routes/foo')
const other_route = require('./routes/other_route')
const startServer = async () => {
const server = Hapi.server({port, host})
server.route(other_route.routes())
server.route(foo.routes())
}
This is a bug with Hapi in node v16. I just opened an issue.
Your current solutions are either:
Upgrade to Hapi v20
Use n or another method to downgrade to node v14.16 for this project. I can confirm that POST requests do not hang in this version.

How to remove authentication for introspection query in Graphql

so may be this is very basic question so please bear with me. Let me explain what I am doing and what I really need.
EXPLANATION
I have created a graphql server by using ApolloGraphql (apollo-server-express npm module).
Here is the code snippet to give you an idea.
api.js
import express from 'express'
import rootSchema from './root-schema'
.... // some extra code
app = express.router()
app.use(jwtaAuthenticator) // --> this code authenticates Authorization header
.... // some more middleware's added
const graphQLServer = new ApolloServer({
schema: rootSchema, // --> this is root schema object
context: context => context,
introspection: true,
})
graphQLServer.applyMiddleware({ app, path: '/graphql' })
server.js
import http from 'http'
import express from 'express'
import apiRouter from './api' // --> the above file
const app = express()
app.use([some middlewares])
app.use('/', apiRouter)
....
....
export async function init () {
try {
const httpServer = http.createServer(app)
httpServer
.listen(PORT)
.on('error', (err) => { setTimeout(() => process.exit(1), 5000) })
} catch (err) {
setTimeout(() => process.exit(1), 5000)
}
console.log('Server started --- ', PORT)
}
export default app
index.js
require('babel-core')
require('babel-polyfill')
require = require('esm')(module/* , options */)
const server = require('./server.js') // --> the above file
server.init()
PROBLEM STATEMENT
I am using node index.js to start the app. So, the app is expecting Authorization header (JWT token) to be present all the times, even for the introspection query. But this is not what I want, I want that introspection query will be resolvable even without the token. So that anyone can see the documentation.
Please shed some light and please guide what is the best approach to do so. Happy coding :)
.startsWith('query Introspection') is insecure because any query can be named Introspection.
The better approach is to check the whole query.
First import graphql and prepare introspection query string:
const { parse, print, getIntrospectionQuery } = require('graphql');
// format introspection query same way as apollo tooling do
const introspectionQuery = print(parse(getIntrospectionQuery()));
Then in Apollo Server configuration check query:
context: ({ req }) => {
// allow introspection query
if (req.body.query === introspectionQuery) {
return {};
}
// continue
}
There's a ton of different ways to handle authorization in GraphQL, as illustrated in the docs:
Adding middleware for express (or some other framework like hapi or koa)
Checking for authorization inside individual resolvers
Checking for authorization inside your data models
Utilizing custom directives
Adding express middleware is great for preventing unauthorized access to your entire schema. If you want to allow unauthenticated access to some fields but not others, it's generally recommended you move your authorization logic from the framework layer to the GraphQL or data model layer using one of the methods above.
So finally I found the solution and here is what I did.
Let me first tell you that there were 2 middle-wares added on base path. Like this:
app //--> this is express.Router()
.use(jwtMw) // ---> these are middlewares
.use(otherMw)
The jwtMw is the one that checks the authentication of the user, and since even introspection query comes under this MW, it used to authenticate that as well. So, after some research I found this solution:
jwtMw.js
function addJWTMeta (req, res, next) {
// we can check for null OR undefined and all, then check for query Introspection, with better condition like with ignore case
if (req.body.query.trim().startsWith('query Introspection')) {
req.isIntrospection = true
return next()
}
...
...
// ---> extra code to do authentication of the USER based on the Authorization header
}
export default addJWTMeta
otherMw.js
function otherMw (req, res, next) {
if (req.isIntrospection) return next()
...
...
// ---> extra code to do some other context creation
}
export default otherMw
So here in jwtMw.js we are checking that if the query is Introspection just add a variable in req object and move forward, and in next middleware after the jwtMw.js whosoever wants to check for introspection query just check for that variable (isIntrospection, in this case) and if it is present and is true, please move on. We can add this code and scale to every middleware that if req.isIntrospection is there just carry on or do the actual processing otherwise.
Happy coding :)

Wrong TLS with RESTDataSource from Apollo and HttpsProxyAgent

I'm setting up a link between my Apollo Server (Node) and a REST API. My endpoint is https://app.myproject.local/api/v1 and is served via Hotel through a Pacfile available from http://localhost:2000/proxy.pac.
In reality this endpoint is also available from http://localhost:4000/api/v1 but I want to access it with Hotel.
I figured app.myproject.local wasn't resolved if I tried to access it directly in the node application, so I should go through HttpsProxyAgent and get it from there.
import { RESTDataSource } from 'apollo-datasource-rest'
import HttpsProxyAgent from 'https-proxy-agent'
import { restConfig } from '../config/restConfig'
export class RestAPI extends RESTDataSource {
constructor() {
super()
this.baseURL = restConfig.endpoint
}
public willSendRequest(request: any) {
request.agent = new HttpsProxyAgent({
host: 'localhost',
port: 2000,
secureProxy: false,
rejectUnauthorized: false,
})
}
public async test() {
return this.get('/status')
}
}
Despite having rejectUnauthorized it throws an error
(node:40593) UnhandledPromiseRejectionWarning: FetchError: request to https://app.myproject.local/api/v1/organizations/current failed, reason: write EPROTO 4474312128:error:1408F10B:SSL routines:ssl3_get_record:wrong version number:../deps/openssl/openssl/ssl/record/ssl3_record.c:332:
When I try to do the same kind of fetch with cURL it does work with
curl --insecure --proxy http://localhost:2000/proxy.pac https://app.myproject.local/api/v1/status
It may be a misunderstanding from my part but I thought rejectUnauthorized would bypass this SSL certificate problem. I'm actually using this in development environment so it does not matter so much, in production I won't need to go through all this.
I'm using the documentation of https://node.readthedocs.io/en/latest/api/tls/#tlsconnectport-host-options-callback to help me pass arguments to HttpsProxyAgent
const https = require('https')
...
willSendRequest(request) {
request.agent = new https.Agent({ rejectUnauthorized: false })
}
...
proxy.pac is a file that tells a web browser which proxy to use for a given request. You generally don't use it as a proxy itself.

Resources