in src/app.ts [51:226]
export function generateServerlessRouter(
fhirConfig: FhirConfig,
supportedGenericResources: string[],
corsOptions?: CorsOptions,
): Express {
if (configVersionSupported !== fhirConfig.configVersion) {
throw new Error(`This router does not support ${fhirConfig.configVersion} version`);
}
const configHandler: ConfigHandler = new ConfigHandler(fhirConfig, supportedGenericResources);
const { fhirVersion, genericResource, compiledImplementationGuides } = fhirConfig.profile;
const serverUrl: string = fhirConfig.server.url;
let hasCORSEnabled: boolean = false;
const registry = new FHIRStructureDefinitionRegistry(compiledImplementationGuides);
const operationRegistry = initializeOperationRegistry(configHandler);
const app = express();
app.disable('x-powered-by');
const mainRouter = express.Router({ mergeParams: true });
mainRouter.use(express.urlencoded({ extended: true }));
mainRouter.use(
express.json({
type: ['application/json', 'application/fhir+json', 'application/json-patch+json'],
// 6MB is the maximum payload that Lambda accepts
limit: '6mb',
}),
);
// Add cors handler before auth to allow pre-flight requests without auth.
if (corsOptions) {
mainRouter.use(cors(corsOptions));
hasCORSEnabled = true;
}
mainRouter.use(setServerUrlMiddleware(fhirConfig));
mainRouter.use(setContentTypeMiddleware);
// Metadata
const metadataRoute: MetadataRoute = new MetadataRoute(
fhirVersion,
configHandler,
registry,
operationRegistry,
hasCORSEnabled,
);
mainRouter.use('/metadata', metadataRoute.router);
if (fhirConfig.auth.strategy.service === 'SMART-on-FHIR') {
// well-known URI http://www.hl7.org/fhir/smart-app-launch/conformance/index.html#using-well-known
const smartStrat: SmartStrategy = fhirConfig.auth.strategy.oauthPolicy as SmartStrategy;
if (smartStrat.capabilities) {
const wellKnownUriRoute = new WellKnownUriRouteRoute(smartStrat);
mainRouter.use('/.well-known/smart-configuration', wellKnownUriRoute.router);
}
}
// AuthZ
mainRouter.use(async (req: express.Request, res: express.Response, next: express.NextFunction) => {
try {
const requestInformation =
operationRegistry.getOperation(req.method, req.path)?.requestInformation ??
getRequestInformation(req.method, req.path);
// Clean auth header (remove 'Bearer ')
req.headers.authorization = cleanAuthHeader(req.headers.authorization);
res.locals.requestContext = prepareRequestContext(req);
res.locals.userIdentity = await fhirConfig.auth.authorization.verifyAccessToken({
...requestInformation,
requestContext: res.locals.requestContext,
accessToken: req.headers.authorization,
fhirServiceBaseUrl: res.locals.serverUrl,
});
next();
} catch (e) {
next(e);
}
});
if (fhirConfig.multiTenancyConfig?.enableMultiTenancy) {
mainRouter.use(setTenantIdMiddleware(fhirConfig));
}
// Export
if (fhirConfig.profile.bulkDataAccess) {
const exportRoute = new ExportRoute(
fhirConfig.profile.bulkDataAccess,
fhirConfig.auth.authorization,
fhirConfig.profile.fhirVersion,
);
mainRouter.use('/', exportRoute.router);
}
// Operations defined by OperationDefinition resources
operationRegistry.getAllRouters().forEach((router) => {
mainRouter.use('/', router);
});
// Special Resources
if (fhirConfig.profile.resources) {
Object.entries(fhirConfig.profile.resources).forEach(async (resourceEntry) => {
const { operations, persistence, typeSearch, typeHistory, fhirVersions } = resourceEntry[1];
if (fhirVersions.includes(fhirVersion)) {
const resourceHandler: ResourceHandler = new ResourceHandler(
persistence,
typeSearch,
typeHistory,
fhirConfig.auth.authorization,
serverUrl,
fhirConfig.validators,
);
const route: GenericResourceRoute = new GenericResourceRoute(
operations,
resourceHandler,
fhirConfig.auth.authorization,
);
mainRouter.use(`/:resourceType(${resourceEntry[0]})`, route.router);
}
});
}
// Generic Resource Support
// Make a list of resources to make
const genericFhirResources: string[] = configHandler.getGenericResources(fhirVersion);
if (genericResource && genericResource.fhirVersions.includes(fhirVersion)) {
const genericOperations: TypeOperation[] = configHandler.getGenericOperations(fhirVersion);
const genericResourceHandler: ResourceHandler = new ResourceHandler(
genericResource.persistence,
genericResource.typeSearch,
genericResource.typeHistory,
fhirConfig.auth.authorization,
serverUrl,
fhirConfig.validators,
);
const genericRoute: GenericResourceRoute = new GenericResourceRoute(
genericOperations,
genericResourceHandler,
fhirConfig.auth.authorization,
);
// Set up Resource for each generic resource
genericFhirResources.forEach(async (resourceType: string) => {
mainRouter.use(`/:resourceType(${resourceType})`, genericRoute.router);
});
}
// Root Post (Bundle/Global Search)
if (fhirConfig.profile.systemOperations.length > 0) {
const rootRoute = new RootRoute(
fhirConfig.profile.systemOperations,
fhirConfig.validators,
serverUrl,
fhirConfig.profile.bundle,
fhirConfig.profile.systemSearch,
fhirConfig.profile.systemHistory,
fhirConfig.auth.authorization,
genericFhirResources,
genericResource,
fhirConfig.profile.resources,
);
mainRouter.use('/', rootRoute.router);
}
mainRouter.use(applicationErrorMapper);
mainRouter.use(httpErrorHandler);
mainRouter.use(unknownErrorHandler);
if (fhirConfig.multiTenancyConfig?.enableMultiTenancy && fhirConfig.multiTenancyConfig?.useTenantSpecificUrl) {
app.use('/tenant/:tenantIdFromPath([a-zA-Z0-9\\-_]{1,64})', mainRouter);
} else {
app.use('/', mainRouter);
}
return app;
}