feat(auth): make cors aware of authenticated sessions

This commit is contained in:
Pujit Mehrotra
2024-10-04 17:33:37 -04:00
parent d8656cc6b3
commit 287aabfda7
2 changed files with 83 additions and 24 deletions

View File

@@ -0,0 +1,78 @@
import { type NestFastifyApplication } from '@nestjs/platform-fastify';
import { type CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface';
import { apiLogger } from '@app/core/log';
import { getAllowedOrigins } from '@app/common/allowed-origins';
import { BYPASS_PERMISSION_CHECKS } from '@app/environment';
import { GraphQLError } from 'graphql';
import { CookieService } from '../auth/cookie.service';
/**
* Returns whether the origin is allowed to access the API.
*
* @throws GraphQLError if the origin is not in the list of allowed origins
* and `BYPASS_PERMISSION_CHECKS` flag is not set.
*/
// note: don't make this function synchronous. throwing will then crash the server.
export async function isOriginAllowed(origin: string | undefined) {
const allowedOrigins = getAllowedOrigins();
if (origin && allowedOrigins.includes(origin)) {
return true;
} else {
apiLogger.debug(`Origin not in allowed origins: ${origin}`);
if (BYPASS_PERMISSION_CHECKS) {
return true;
}
throw new GraphQLError(
'The CORS policy for this site does not allow access from the specified Origin.'
);
}
}
/**
* Dynamically determines the CORS config for a request.
*
* - Expects any cookies to be parsed & available on the `cookies` property of the request.
*
* If the request contains a valid unraid session cookie, it is allowed to access
* the API from any origin. Otherwise, the origin must be explicitly listed in
* the `allowedOrigins` config option, or the `BYPASS_PERMISSION_CHECKS` flag
* must be set.
*
* @param req the request object
* @param callback the callback to call with the CORS options
*/
function dynamicCors(req: any, callback: (error: Error | null, options: CorsOptions) => void) {
const { cookies } = req;
const service = new CookieService();
if (typeof cookies === 'object') {
service.hasValidAuthCookie(cookies).then((isValid) => {
if (isValid) {
callback(null, { origin: true });
} else {
callback(null, { origin: isOriginAllowed });
}
});
} else {
callback(null, { origin: isOriginAllowed });
}
}
/**------------------------------------------------------------------------
* ? Fastify Cors Config
*
* The fastify cors configuration function is very different from express,
* but Nest.js doesn't have clear docs or types describing this so I'm
* documenting it here.
*
* This takes a fastify app instance and returns a cors config function, instead
* of just the cors config function (which is nest's default behavior).
*------------------------------------------------------------------------**/
/** A wrapper function for the fastify CORS configuration. */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function configureFastifyCors(app: NestFastifyApplication) {
return dynamicCors;
}

View File

@@ -3,34 +3,15 @@ import { LoggerErrorInterceptor, Logger as PinoLogger } from 'nestjs-pino';
import { AppModule } from './app/app.module';
import Fastify from 'fastify';
import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify';
import { type CorsOptionsDelegate } from 'cors';
import { getAllowedOrigins } from '@app/common/allowed-origins';
import { HttpExceptionFilter } from '@app/unraid-api/exceptions/http-exceptions.filter';
import { GraphQLError } from 'graphql';
import { GraphQLExceptionsFilter } from '@app/unraid-api/exceptions/graphql-exceptions.filter';
import { BYPASS_PERMISSION_CHECKS, PORT } from '@app/environment';
import { PORT } from '@app/environment';
import { type FastifyInstance } from 'fastify';
import { type Server, type IncomingMessage, type ServerResponse } from 'http';
import { apiLogger } from '@app/core/log';
import cookieParser from 'cookie-parser';
export const corsOptionsDelegate: CorsOptionsDelegate = async (origin: string | undefined) => {
const allowedOrigins = getAllowedOrigins();
if (origin && allowedOrigins.includes(origin)) {
return true;
} else {
apiLogger.debug(`Origin not in allowed origins: ${origin}`);
if (BYPASS_PERMISSION_CHECKS) {
return true;
}
throw new GraphQLError(
'The CORS policy for this site does not allow access from the specified Origin.'
);
}
};
import fastifyCookie from '@fastify/cookie';
import { configureFastifyCors } from './app/cors';
export async function bootstrapNestServer(): Promise<NestFastifyApplication> {
const server: FastifyInstance<Server, IncomingMessage, ServerResponse> = Fastify({
@@ -38,11 +19,11 @@ export async function bootstrapNestServer(): Promise<NestFastifyApplication> {
});
const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter(server), {
cors: { origin: corsOptionsDelegate },
bufferLogs: true,
});
app.use(cookieParser());
app.register(fastifyCookie); // parse cookies before cors
app.enableCors(configureFastifyCors);
// Setup Nestjs Pino Logger
app.useLogger(app.get(PinoLogger));