feat: Use URLs for channel identifiers

In the future these can potentially be used to dereference them
This commit is contained in:
Joachim Van Herwegen
2023-02-03 15:32:34 +01:00
parent 67d1ff4ac0
commit cbbb10afa1
13 changed files with 58 additions and 68 deletions

View File

@@ -14,6 +14,7 @@ import type { InteractionRoute } from '../../identity/interaction/routing/Intera
import { ContextDocumentLoader } from '../../storage/conversion/ConversionUtil';
import { UnprocessableEntityHttpError } from '../../util/errors/UnprocessableEntityHttpError';
import { IdentifierSetMultiMap } from '../../util/map/IdentifierMap';
import { joinUrl } from '../../util/PathUtil';
import { readableToQuads } from '../../util/StreamUtil';
import { msToDuration } from '../../util/StringUtil';
import { NOTIFY, RDF, XSD } from '../../util/Vocabularies';
@@ -201,7 +202,9 @@ export abstract class BaseChannelType implements NotificationChannelType {
/**
* Converts a set of quads to a {@link NotificationChannel}.
* Assumes the data is valid, so this should be called after {@link validateSubscription}
* Assumes the data is valid, so this should be called after {@link validateSubscription}.
*
* The generated identifier will be a URL made by combining the base URL of the channel type with a unique identifier.
*
* The values of the default features will be added to the resulting channel,
* subclasses with additional features that need to be added are responsible for parsing those quads.
@@ -216,7 +219,7 @@ export abstract class BaseChannelType implements NotificationChannelType {
const type = data.getObjects(subject, RDF.terms.type, null)[0] as NamedNode;
const channel: NotificationChannel = {
id: `${v4()}:${topic.value}`,
id: joinUrl(this.path, v4()),
type: type.value,
topic: topic.value,
};

View File

@@ -1,20 +0,0 @@
import { joinUrl } from '../../../util/PathUtil';
/**
* Generates a specific unsubscribe URL for a WebHookSubscription2021
* by combining the default unsubscribe URL with the given identifier.
* @param url - The default unsubscribe URL.
* @param id - The identifier.
*/
export function generateWebHookUnsubscribeUrl(url: string, id: string): string {
return joinUrl(url, encodeURIComponent(id));
}
/**
* Parses a WebHookSubscription2021 unsubscribe URL to extract the identifier.
* @param url - The unsubscribe URL that is being called.
*/
export function parseWebHookUnsubscribeUrl(url: string): string {
// Split always returns an array of at least length 1 so result can not be undefined
return decodeURIComponent(url.split(/\//u).pop()!);
}

View File

@@ -9,7 +9,6 @@ import { BaseChannelType, DEFAULT_NOTIFICATION_FEATURES } from '../BaseChannelTy
import type { NotificationChannel } from '../NotificationChannel';
import type { SubscriptionService } from '../NotificationChannelType';
import type { StateHandler } from '../StateHandler';
import { generateWebHookUnsubscribeUrl } from './WebHook2021Util';
/**
* A {@link NotificationChannel} containing the necessary fields for a WebHookSubscription2021 channel.
@@ -57,19 +56,17 @@ export function isWebHook2021Channel(channel: NotificationChannel): channel is W
export class WebHookSubscription2021 extends BaseChannelType {
protected readonly logger = getLoggerFor(this);
private readonly unsubscribePath: string;
private readonly stateHandler: StateHandler;
private readonly webId: string;
/**
* @param route - The route corresponding to the URL of the subscription service of this channel type.
* @param webIdRoute - The route to the WebID that needs to be used when generating DPoP tokens for notifications.
* @param unsubscribeRoute - The route where the request needs to be sent to unsubscribe.
* @param stateHandler - The {@link StateHandler} that will be called after a successful subscription.
* @param features - The features that need to be enabled for this channel type.
*/
public constructor(route: InteractionRoute, webIdRoute: InteractionRoute, unsubscribeRoute: InteractionRoute,
stateHandler: StateHandler, features: string[] = DEFAULT_NOTIFICATION_FEATURES) {
public constructor(route: InteractionRoute, webIdRoute: InteractionRoute, stateHandler: StateHandler,
features: string[] = DEFAULT_NOTIFICATION_FEATURES) {
super(NOTIFY.terms.WebHookSubscription2021,
route,
[ ...features, NOTIFY.webhookAuth ],
@@ -80,7 +77,6 @@ export class WebHookSubscription2021 extends BaseChannelType {
// which would make this more annoying so we are lenient here.
// Could change in the future once this field is updated and part of the context.
[{ path: NOTIFY.target, minCount: 1, maxCount: 1 }]);
this.unsubscribePath = unsubscribeRoute.getPath();
this.stateHandler = stateHandler;
this.webId = webIdRoute.getPath();
}
@@ -114,7 +110,7 @@ export class WebHookSubscription2021 extends BaseChannelType {
webId,
target: target.value,
// eslint-disable-next-line @typescript-eslint/naming-convention
unsubscribe_endpoint: generateWebHookUnsubscribeUrl(this.unsubscribePath, channel.id),
unsubscribe_endpoint: channel.id,
};
}

View File

@@ -7,7 +7,6 @@ import { NotFoundHttpError } from '../../../util/errors/NotFoundHttpError';
import type { OperationHttpHandlerInput } from '../../OperationHttpHandler';
import { OperationHttpHandler } from '../../OperationHttpHandler';
import type { NotificationChannelStorage } from '../NotificationChannelStorage';
import { parseWebHookUnsubscribeUrl } from './WebHook2021Util';
import { isWebHook2021Channel } from './WebHookSubscription2021';
/**
@@ -27,7 +26,7 @@ export class WebHookUnsubscriber extends OperationHttpHandler {
}
public async handle({ operation, request }: OperationHttpHandlerInput): Promise<ResponseDescription> {
const id = parseWebHookUnsubscribeUrl(operation.target.path);
const id = operation.target.path;
const channel = await this.storage.get(id);
if (!channel || !isWebHook2021Channel(channel)) {
throw new NotFoundHttpError();