mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Move OIDC library behaviour to separate path
This commit is contained in:
@@ -10,6 +10,7 @@ import { OperationHttpHandler } from '../server/OperationHttpHandler';
|
||||
import type { RepresentationConverter } from '../storage/conversion/RepresentationConverter';
|
||||
import { APPLICATION_JSON } from '../util/ContentTypes';
|
||||
import { BadRequestHttpError } from '../util/errors/BadRequestHttpError';
|
||||
import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
|
||||
import { joinUrl, trimTrailingSlashes } from '../util/PathUtil';
|
||||
import { addTemplateMetadata, cloneRepresentation } from '../util/ResourceUtil';
|
||||
import { readJsonStream } from '../util/StreamUtil';
|
||||
@@ -77,8 +78,6 @@ export class IdentityProviderHttpHandler extends OperationHttpHandler {
|
||||
private readonly controls: Record<string, string>;
|
||||
|
||||
public constructor(args: IdentityProviderHttpHandlerArgs) {
|
||||
// It is important that the RequestParser does not read out the Request body stream.
|
||||
// Otherwise we can't pass it anymore to the OIDC library when needed.
|
||||
super();
|
||||
// Trimming trailing slashes so the relative URL starts with a slash after slicing this off
|
||||
this.baseUrl = trimTrailingSlashes(joinUrl(args.baseUrl, args.idpPath));
|
||||
@@ -97,29 +96,19 @@ export class IdentityProviderHttpHandler extends OperationHttpHandler {
|
||||
/**
|
||||
* Finds the matching route and resolves the operation.
|
||||
*/
|
||||
public async handle({ operation, request, response }: OperationHttpHandlerInput):
|
||||
Promise<ResponseDescription | undefined> {
|
||||
public async handle({ operation, request, response }: OperationHttpHandlerInput): Promise<ResponseDescription> {
|
||||
// This being defined means we're in an OIDC session
|
||||
let oidcInteraction: Interaction | undefined;
|
||||
try {
|
||||
const provider = await this.providerFactory.getProvider();
|
||||
// This being defined means we're in an OIDC session
|
||||
oidcInteraction = await provider.interactionDetails(request, response);
|
||||
} catch {
|
||||
// Just a regular request
|
||||
}
|
||||
|
||||
// If our own interaction handler does not support the input, it is either invalid or a request for the OIDC library
|
||||
const route = await this.findRoute(operation, oidcInteraction);
|
||||
|
||||
if (!route) {
|
||||
const provider = await this.providerFactory.getProvider();
|
||||
this.logger.debug(`Sending request to oidc-provider: ${request.url}`);
|
||||
// Even though the typings do not indicate this, this is a Promise that needs to be awaited.
|
||||
// Otherwise the `BaseHttpServerFactory` will write a 404 before the OIDC library could handle the response.
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
await provider.callback(request, response);
|
||||
return;
|
||||
throw new NotFoundHttpError();
|
||||
}
|
||||
|
||||
// Cloning input data so it can be sent back in case of errors
|
||||
@@ -149,7 +138,7 @@ export class IdentityProviderHttpHandler extends OperationHttpHandler {
|
||||
*/
|
||||
private async findRoute(operation: Operation, oidcInteraction?: Interaction): Promise<InteractionRoute | undefined> {
|
||||
if (!operation.target.path.startsWith(this.baseUrl)) {
|
||||
// This is either an invalid request or a call to the .well-known configuration
|
||||
// This is an invalid request
|
||||
return;
|
||||
}
|
||||
const pathName = operation.target.path.slice(this.baseUrl.length);
|
||||
|
||||
27
src/identity/OidcHttpHandler.ts
Normal file
27
src/identity/OidcHttpHandler.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { getLoggerFor } from '../logging/LogUtil';
|
||||
import type { HttpHandlerInput } from '../server/HttpHandler';
|
||||
import { HttpHandler } from '../server/HttpHandler';
|
||||
import type { ProviderFactory } from './configuration/ProviderFactory';
|
||||
|
||||
/**
|
||||
* HTTP handler that redirects all requests to the OIDC library.
|
||||
*/
|
||||
export class OidcHttpHandler extends HttpHandler {
|
||||
protected readonly logger = getLoggerFor(this);
|
||||
|
||||
private readonly providerFactory: ProviderFactory;
|
||||
|
||||
public constructor(providerFactory: ProviderFactory) {
|
||||
super();
|
||||
this.providerFactory = providerFactory;
|
||||
}
|
||||
|
||||
public async handle({ request, response }: HttpHandlerInput): Promise<void> {
|
||||
const provider = await this.providerFactory.getProvider();
|
||||
this.logger.debug(`Sending request to oidc-provider: ${request.url}`);
|
||||
// Even though the typings do not indicate this, this is a Promise that needs to be awaited.
|
||||
// Otherwise the `BaseHttpServerFactory` will write a 404 before the OIDC library could handle the response.
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
await provider.callback(request, response);
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,11 @@ export interface IdentityProviderFactoryArgs {
|
||||
*/
|
||||
baseUrl: string;
|
||||
/**
|
||||
* Path of the IDP component in the server.
|
||||
* Path for all requests targeting the OIDC library.
|
||||
*/
|
||||
oidcPath: string;
|
||||
/**
|
||||
* The entry point for the custom IDP handlers of the server.
|
||||
* Should start with a slash.
|
||||
*/
|
||||
idpPath: string;
|
||||
@@ -62,6 +66,7 @@ export class IdentityProviderFactory implements ProviderFactory {
|
||||
private readonly config: Configuration;
|
||||
private readonly adapterFactory!: AdapterFactory;
|
||||
private readonly baseUrl!: string;
|
||||
private readonly oidcPath!: string;
|
||||
private readonly idpPath!: string;
|
||||
private readonly storage!: KeyValueStorage<string, unknown>;
|
||||
private readonly errorHandler!: ErrorHandler;
|
||||
@@ -107,6 +112,7 @@ export class IdentityProviderFactory implements ProviderFactory {
|
||||
// Allow provider to interpret reverse proxy headers
|
||||
const provider = new Provider(this.baseUrl, config);
|
||||
provider.proxy = true;
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
@@ -210,11 +216,11 @@ export class IdentityProviderFactory implements ProviderFactory {
|
||||
|
||||
/**
|
||||
* Creates the route string as required by the `oidc-provider` library.
|
||||
* In case base URL is `http://test.com/foo/`, `idpPath` is `/idp` and `relative` is `device/auth`,
|
||||
* In case base URL is `http://test.com/foo/`, `oidcPath` is `/idp` and `relative` is `device/auth`,
|
||||
* this would result in `/foo/idp/device/auth`.
|
||||
*/
|
||||
private createRoute(relative: string): string {
|
||||
return new URL(joinUrl(this.baseUrl, this.idpPath, relative)).pathname;
|
||||
return new URL(joinUrl(this.baseUrl, this.oidcPath, relative)).pathname;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -165,6 +165,7 @@ export * from './identity/storage/WebIdAdapterFactory';
|
||||
|
||||
// Identity
|
||||
export * from './identity/IdentityProviderHttpHandler';
|
||||
export * from './identity/OidcHttpHandler';
|
||||
|
||||
// Init/Final
|
||||
export * from './init/final/Finalizable';
|
||||
|
||||
@@ -58,7 +58,7 @@ export class AuthorizingHttpHandler extends OperationHttpHandler {
|
||||
this.operationHandler = args.operationHandler;
|
||||
}
|
||||
|
||||
public async handle(input: OperationHttpHandlerInput): Promise<ResponseDescription | undefined> {
|
||||
public async handle(input: OperationHttpHandlerInput): Promise<ResponseDescription> {
|
||||
const { request, operation } = input;
|
||||
const credentials: CredentialSet = await this.credentialsExtractor.handleSafe(request);
|
||||
this.logger.verbose(`Extracted credentials: ${JSON.stringify(credentials)}`);
|
||||
|
||||
@@ -9,8 +9,6 @@ export interface OperationHttpHandlerInput extends HttpHandlerInput {
|
||||
|
||||
/**
|
||||
* An HTTP handler that makes use of an already parsed Operation.
|
||||
* Can either return a ResponseDescription to be resolved by the calling class,
|
||||
* or undefined if this class handles the response itself.
|
||||
*/
|
||||
export abstract class OperationHttpHandler
|
||||
extends AsyncHandler<OperationHttpHandlerInput, ResponseDescription | undefined> {}
|
||||
extends AsyncHandler<OperationHttpHandlerInput, ResponseDescription> {}
|
||||
|
||||
Reference in New Issue
Block a user