diff --git a/config/identity/handler/interaction/routes.json b/config/identity/handler/interaction/routes.json index d1b92d5a6..07d40db70 100644 --- a/config/identity/handler/interaction/routes.json +++ b/config/identity/handler/interaction/routes.json @@ -30,11 +30,11 @@ "comment": "Converts redirect errors to location JSON responses.", "@id": "urn:solid-server:auth:password:LocationInteractionHandler", "@type": "LocationInteractionHandler", - "LocationInteractionHandler:_source" : { "@id": "urn:solid-server:auth:password:RouteInteractionHandler" } + "LocationInteractionHandler:_source" : { "@id": "urn:solid-server:auth:password:InteractionRouteHandler" } }, { "comment": "Handles every interaction based on their route.", - "@id": "urn:solid-server:auth:password:RouteInteractionHandler", + "@id": "urn:solid-server:auth:password:InteractionRouteHandler", "@type": "WaterfallHandler", "handlers": [ { @@ -44,12 +44,12 @@ ], "@type": "UnsupportedAsyncHandler" }, - { "@id": "urn:solid-server:auth:password:IndexRoute" }, - { "@id": "urn:solid-server:auth:password:PromptRoute" }, - { "@id": "urn:solid-server:auth:password:LoginRoute" }, - { "@id": "urn:solid-server:auth:password:ExistingLoginRoute" }, - { "@id": "urn:solid-server:auth:password:ForgotPasswordRoute" }, - { "@id": "urn:solid-server:auth:password:ResetPasswordRoute" } + { "@id": "urn:solid-server:auth:password:IndexRouteHandler" }, + { "@id": "urn:solid-server:auth:password:PromptRouteHandler" }, + { "@id": "urn:solid-server:auth:password:LoginRouteHandler" }, + { "@id": "urn:solid-server:auth:password:ExistingLoginRouteHandler" }, + { "@id": "urn:solid-server:auth:password:ForgotPasswordRouteHandler" }, + { "@id": "urn:solid-server:auth:password:ResetPasswordRouteHandler" } ] } ] diff --git a/config/identity/handler/interaction/routes/existing-login.json b/config/identity/handler/interaction/routes/existing-login.json index 17ad7da19..915373ff0 100644 --- a/config/identity/handler/interaction/routes/existing-login.json +++ b/config/identity/handler/interaction/routes/existing-login.json @@ -3,11 +3,16 @@ "@graph": [ { "comment": "Handles the interaction that occurs when a logged in user wants to authenticate with a new app.", - "@id": "urn:solid-server:auth:password:ExistingLoginRoute", - "@type": "RelativeInteractionRoute", - "base": { "@id": "urn:solid-server:auth:password:IndexRoute" }, - "relativePath": "/consent/", + "@id": "urn:solid-server:auth:password:ExistingLoginRouteHandler", + "@type":"InteractionRouteHandler", + "route": { + "@id": "urn:solid-server:auth:password:ExistingLoginRoute", + "@type": "RelativePathInteractionRoute", + "base": { "@id": "urn:solid-server:auth:password:IndexRoute" }, + "relativePath": "/consent/" + }, "source": { + "@id": "urn:solid-server:auth:password:ExistingLoginHandler", "@type": "ExistingLoginHandler", "interactionCompleter": { "@type": "BaseInteractionCompleter" } } diff --git a/config/identity/handler/interaction/routes/forgot-password.json b/config/identity/handler/interaction/routes/forgot-password.json index b3e32da5b..748d58a0a 100644 --- a/config/identity/handler/interaction/routes/forgot-password.json +++ b/config/identity/handler/interaction/routes/forgot-password.json @@ -3,11 +3,16 @@ "@graph": [ { "comment": "Handles the forgot password interaction", - "@id": "urn:solid-server:auth:password:ForgotPasswordRoute", - "@type": "RelativeInteractionRoute", - "base": { "@id": "urn:solid-server:auth:password:IndexRoute" }, - "relativePath": "/forgotpassword/", + "@id": "urn:solid-server:auth:password:ForgotPasswordRouteHandler", + "@type":"InteractionRouteHandler", + "route": { + "@id": "urn:solid-server:auth:password:ForgotPasswordRoute", + "@type": "RelativePathInteractionRoute", + "base": { "@id": "urn:solid-server:auth:password:IndexRoute" }, + "relativePath": "/forgotpassword/" + }, "source": { + "@id": "urn:solid-server:auth:password:ForgotPasswordHandler", "@type": "ForgotPasswordHandler", "args_accountStore": { "@id": "urn:solid-server:auth:password:AccountStore" }, "args_templateEngine": { diff --git a/config/identity/handler/interaction/routes/index.json b/config/identity/handler/interaction/routes/index.json index 419edd709..145b4fcf2 100644 --- a/config/identity/handler/interaction/routes/index.json +++ b/config/identity/handler/interaction/routes/index.json @@ -3,11 +3,16 @@ "@graph": [ { "comment": "Root API entry. Returns an empty body so we can add controls pointing to other interaction routes.", - "@id": "urn:solid-server:auth:password:IndexRoute", - "@type": "RelativeInteractionRoute", - "base": { "@id": "urn:solid-server:default:variable:baseUrl" }, - "relativePath": "/idp/", + "@id": "urn:solid-server:auth:password:IndexRouteHandler", + "@type": "InteractionRouteHandler", + "route": { + "@id": "urn:solid-server:auth:password:IndexRoute", + "@type": "RelativePathInteractionRoute", + "base": { "@id": "urn:solid-server:default:variable:baseUrl" }, + "relativePath": "/idp/" + }, "source": { + "@id": "urn:solid-server:auth:password:IndexHandler", "@type": "FixedInteractionHandler", "response": {} } diff --git a/config/identity/handler/interaction/routes/login.json b/config/identity/handler/interaction/routes/login.json index 294af8e49..b3bf1b108 100644 --- a/config/identity/handler/interaction/routes/login.json +++ b/config/identity/handler/interaction/routes/login.json @@ -3,11 +3,16 @@ "@graph": [ { "comment": "Handles the login interaction", - "@id": "urn:solid-server:auth:password:LoginRoute", - "@type": "RelativeInteractionRoute", - "base": { "@id": "urn:solid-server:auth:password:IndexRoute" }, - "relativePath": "/login/", + "@id": "urn:solid-server:auth:password:LoginRouteHandler", + "@type": "InteractionRouteHandler", + "route": { + "@id": "urn:solid-server:auth:password:LoginRoute", + "@type": "RelativePathInteractionRoute", + "base": { "@id": "urn:solid-server:auth:password:IndexRoute" }, + "relativePath": "/login/" + }, "source": { + "@id": "urn:solid-server:auth:password:LoginHandler", "@type": "LoginHandler", "accountStore": { "@id": "urn:solid-server:auth:password:AccountStore" }, "interactionCompleter": { "@type": "BaseInteractionCompleter" } diff --git a/config/identity/handler/interaction/routes/prompt.json b/config/identity/handler/interaction/routes/prompt.json index 0dba93633..98cecae96 100644 --- a/config/identity/handler/interaction/routes/prompt.json +++ b/config/identity/handler/interaction/routes/prompt.json @@ -3,10 +3,14 @@ "@graph": [ { "comment": "Handles OIDC redirects containing a prompt, such as login or consent.", - "@id": "urn:solid-server:auth:password:PromptRoute", - "@type": "RelativeInteractionRoute", - "base": { "@id": "urn:solid-server:auth:password:IndexRoute" }, - "relativePath": "/prompt/", + "@id": "urn:solid-server:auth:password:PromptRouteHandler", + "@type": "InteractionRouteHandler", + "route": { + "@id": "urn:solid-server:auth:password:PromptRoute", + "@type": "RelativePathInteractionRoute", + "base": { "@id": "urn:solid-server:auth:password:IndexRoute" }, + "relativePath": "/prompt/" + }, "source": { "@type": "PromptHandler", "@id": "urn:solid-server:auth:password:PromptHandler", diff --git a/config/identity/handler/interaction/routes/reset-password.json b/config/identity/handler/interaction/routes/reset-password.json index 5c1bc3b92..ef6aa9335 100644 --- a/config/identity/handler/interaction/routes/reset-password.json +++ b/config/identity/handler/interaction/routes/reset-password.json @@ -3,11 +3,16 @@ "@graph": [ { "comment": "Handles the reset password interaction", - "@id": "urn:solid-server:auth:password:ResetPasswordRoute", - "@type": "RelativeInteractionRoute", - "base": { "@id": "urn:solid-server:auth:password:IndexRoute" }, - "relativePath": "/resetpassword/", + "@id": "urn:solid-server:auth:password:ResetPasswordRouteHandler", + "@type": "InteractionRouteHandler", + "route": { + "@id": "urn:solid-server:auth:password:ResetPasswordRoute", + "@type": "RelativePathInteractionRoute", + "base": { "@id": "urn:solid-server:auth:password:IndexRoute" }, + "relativePath": "/resetpassword/" + }, "source": { + "@id": "urn:solid-server:auth:password:ResetPasswordHandler", "@type": "ResetPasswordHandler", "accountStore": { "@id": "urn:solid-server:auth:password:AccountStore" } } diff --git a/config/identity/registration/enabled.json b/config/identity/registration/enabled.json index db6579f17..5caedc4f4 100644 --- a/config/identity/registration/enabled.json +++ b/config/identity/registration/enabled.json @@ -5,7 +5,7 @@ ], "@graph": [ { - "@id": "urn:solid-server:auth:password:RouteInteractionHandler", + "@id": "urn:solid-server:auth:password:InteractionRouteHandler", "WaterfallHandler:_handlers": [ { "comment": [ @@ -14,7 +14,7 @@ ], "@type": "UnsupportedAsyncHandler" }, - { "@id": "urn:solid-server:auth:password:RegistrationRoute" } + { "@id": "urn:solid-server:auth:password:RegistrationRouteHandler" } ] }, { @@ -32,14 +32,6 @@ { "HtmlViewHandler:_templates_key": "@css:templates/identity/email-password/register.html.ejs", "HtmlViewHandler:_templates_value": { "@id": "urn:solid-server:auth:password:RegistrationRoute" } - }, - { - "HtmlViewHandler:_templates_key": "@css:templates/identity/email-password/reset-password-response.html.ejs", - "HtmlViewHandler:_templates_value": { - "@type": "RelativeInteractionRoute", - "base": { "@id": "urn:solid-server:auth:password:ResetPasswordRoute" }, - "relativePath": "/response/" - } } ] } diff --git a/config/identity/registration/route/registration.json b/config/identity/registration/route/registration.json index 9e2e684c1..693c9ba3e 100644 --- a/config/identity/registration/route/registration.json +++ b/config/identity/registration/route/registration.json @@ -3,11 +3,16 @@ "@graph": [ { "comment": "Handles the register interaction", - "@id": "urn:solid-server:auth:password:RegistrationRoute", - "@type": "RelativeInteractionRoute", - "base": { "@id": "urn:solid-server:auth:password:IndexRoute" }, - "relativePath": "/register/", + "@id": "urn:solid-server:auth:password:RegistrationRouteHandler", + "@type": "InteractionRouteHandler", + "route": { + "@id": "urn:solid-server:auth:password:RegistrationRoute", + "@type": "RelativePathInteractionRoute", + "base": { "@id": "urn:solid-server:auth:password:IndexRoute" }, + "relativePath": "/register/" + }, "source": { + "@id": "urn:solid-server:auth:password:RegistrationHandler", "@type": "RegistrationHandler", "registrationManager": { "@type": "RegistrationManager", diff --git a/src/identity/interaction/routing/AbsolutePathInteractionRoute.ts b/src/identity/interaction/routing/AbsolutePathInteractionRoute.ts new file mode 100644 index 000000000..7a7e0d897 --- /dev/null +++ b/src/identity/interaction/routing/AbsolutePathInteractionRoute.ts @@ -0,0 +1,16 @@ +import type { InteractionRoute } from './InteractionRoute'; + +/** + * A route that returns the input string as path. + */ +export class AbsolutePathInteractionRoute implements InteractionRoute { + private readonly path: string; + + public constructor(path: string) { + this.path = path; + } + + public getPath(): string { + return this.path; + } +} diff --git a/src/identity/interaction/routing/BasicInteractionRoute.ts b/src/identity/interaction/routing/InteractionRouteHandler.ts similarity index 51% rename from src/identity/interaction/routing/BasicInteractionRoute.ts rename to src/identity/interaction/routing/InteractionRouteHandler.ts index f726e687e..22573524d 100644 --- a/src/identity/interaction/routing/BasicInteractionRoute.ts +++ b/src/identity/interaction/routing/InteractionRouteHandler.ts @@ -1,36 +1,28 @@ import type { Representation } from '../../../http/representation/Representation'; import { NotFoundHttpError } from '../../../util/errors/NotFoundHttpError'; -import { UnsupportedAsyncHandler } from '../../../util/handlers/UnsupportedAsyncHandler'; -import { InteractionHandler } from '../InteractionHandler'; import type { InteractionHandlerInput } from '../InteractionHandler'; +import { InteractionHandler } from '../InteractionHandler'; import type { InteractionRoute } from './InteractionRoute'; /** - * Default implementation of an InteractionHandler with an InteractionRoute. + * InteractionHandler that only accepts operations with an expected path. * * Rejects operations that target a different path, - * otherwise the input parameters get passed to the source handler. - * - * In case no source handler is provided it defaults to an {@link UnsupportedAsyncHandler}. - * This can be useful if you want an object with just the route. + * otherwise the input parameters are passed to the source handler. */ -export class BasicInteractionRoute extends InteractionHandler implements InteractionRoute { - private readonly path: string; +export class InteractionRouteHandler extends InteractionHandler { + private readonly route: InteractionRoute; private readonly source: InteractionHandler; - public constructor(path: string, source?: InteractionHandler) { + public constructor(route: InteractionRoute, source: InteractionHandler) { super(); - this.path = path; - this.source = source ?? new UnsupportedAsyncHandler('This route has no associated handler.'); - } - - public getPath(): string { - return this.path; + this.route = route; + this.source = source; } public async canHandle(input: InteractionHandlerInput): Promise { const { target } = input.operation; - const path = this.getPath(); + const path = this.route.getPath(); if (target.path !== path) { throw new NotFoundHttpError(); } diff --git a/src/identity/interaction/routing/RelativeInteractionRoute.ts b/src/identity/interaction/routing/RelativePathInteractionRoute.ts similarity index 59% rename from src/identity/interaction/routing/RelativeInteractionRoute.ts rename to src/identity/interaction/routing/RelativePathInteractionRoute.ts index 6443b5ec3..70e7d0e93 100644 --- a/src/identity/interaction/routing/RelativeInteractionRoute.ts +++ b/src/identity/interaction/routing/RelativePathInteractionRoute.ts @@ -1,18 +1,16 @@ import { joinUrl } from '../../../util/PathUtil'; -import type { InteractionHandler } from '../InteractionHandler'; -import { BasicInteractionRoute } from './BasicInteractionRoute'; +import { AbsolutePathInteractionRoute } from './AbsolutePathInteractionRoute'; import type { InteractionRoute } from './InteractionRoute'; /** * A route that is relative to another route. * The relative path will be joined to the input base, * which can either be an absolute URL or an InteractionRoute of which the path will be used. - * The source handler will be called for all operation requests */ -export class RelativeInteractionRoute extends BasicInteractionRoute { - public constructor(base: InteractionRoute | string, relativePath: string, source?: InteractionHandler) { +export class RelativePathInteractionRoute extends AbsolutePathInteractionRoute { + public constructor(base: InteractionRoute | string, relativePath: string) { const url = typeof base === 'string' ? base : base.getPath(); const path = joinUrl(url, relativePath); - super(path, source); + super(path); } } diff --git a/src/index.ts b/src/index.ts index 2d68312c0..12dd3b8e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -145,9 +145,10 @@ export * from './identity/interaction/email-password/util/RegistrationManager'; export * from './identity/interaction/email-password/EmailPasswordUtil'; // Identity/Interaction/Routing -export * from './identity/interaction/routing/BasicInteractionRoute'; +export * from './identity/interaction/routing/AbsolutePathInteractionRoute'; export * from './identity/interaction/routing/InteractionRoute'; -export * from './identity/interaction/routing/RelativeInteractionRoute'; +export * from './identity/interaction/routing/InteractionRouteHandler'; +export * from './identity/interaction/routing/RelativePathInteractionRoute'; // Identity/Interaction/Util export * from './identity/interaction/util/BaseEmailSender'; diff --git a/test/unit/identity/interaction/routing/AbsolutePathInteractionRoute.test.ts b/test/unit/identity/interaction/routing/AbsolutePathInteractionRoute.test.ts new file mode 100644 index 000000000..bed58c07e --- /dev/null +++ b/test/unit/identity/interaction/routing/AbsolutePathInteractionRoute.test.ts @@ -0,0 +1,12 @@ +import { + AbsolutePathInteractionRoute, +} from '../../../../../src/identity/interaction/routing/AbsolutePathInteractionRoute'; + +describe('An AbsolutePathInteractionRoute', (): void => { + const path = 'http://example.com/idp/path/'; + const route = new AbsolutePathInteractionRoute(path); + + it('returns the given path.', async(): Promise => { + expect(route.getPath()).toBe('http://example.com/idp/path/'); + }); +}); diff --git a/test/unit/identity/interaction/routing/BasicInteractionRoute.test.ts b/test/unit/identity/interaction/routing/InteractionRouteHandler.test.ts similarity index 52% rename from test/unit/identity/interaction/routing/BasicInteractionRoute.test.ts rename to test/unit/identity/interaction/routing/InteractionRouteHandler.test.ts index 7ca15e559..cecc36cd9 100644 --- a/test/unit/identity/interaction/routing/BasicInteractionRoute.test.ts +++ b/test/unit/identity/interaction/routing/InteractionRouteHandler.test.ts @@ -1,59 +1,53 @@ import type { Operation } from '../../../../../src/http/Operation'; import { BasicRepresentation } from '../../../../../src/http/representation/BasicRepresentation'; import type { Representation } from '../../../../../src/http/representation/Representation'; -import type { - InteractionHandler, -} from '../../../../../src/identity/interaction/InteractionHandler'; -import { BasicInteractionRoute } from '../../../../../src/identity/interaction/routing/BasicInteractionRoute'; +import type { InteractionHandler } from '../../../../../src/identity/interaction/InteractionHandler'; +import type { InteractionRoute } from '../../../../../src/identity/interaction/routing/InteractionRoute'; +import { InteractionRouteHandler } from '../../../../../src/identity/interaction/routing/InteractionRouteHandler'; import { APPLICATION_JSON } from '../../../../../src/util/ContentTypes'; import { NotFoundHttpError } from '../../../../../src/util/errors/NotFoundHttpError'; import { createPostJsonOperation } from '../email-password/handler/Util'; -describe('A BasicInteractionRoute', (): void => { +describe('An InteractionRouteHandler', (): void => { const path = 'http://example.com/idp/path/'; let operation: Operation; let representation: Representation; + let route: InteractionRoute; let source: jest.Mocked; - let route: BasicInteractionRoute; + let handler: InteractionRouteHandler; beforeEach(async(): Promise => { - operation = createPostJsonOperation({}, 'http://example.com/idp/path/'); + operation = createPostJsonOperation({}, path); representation = new BasicRepresentation(JSON.stringify({}), APPLICATION_JSON); + route = { + getPath: jest.fn().mockReturnValue(path), + }; + source = { canHandle: jest.fn(), handle: jest.fn().mockResolvedValue(representation), } as any; - route = new BasicInteractionRoute(path, source); - }); - - it('returns the given path.', async(): Promise => { - expect(route.getPath()).toBe('http://example.com/idp/path/'); + handler = new InteractionRouteHandler(route, source); }); it('rejects other paths.', async(): Promise => { operation = createPostJsonOperation({}, 'http://example.com/idp/otherPath/'); - await expect(route.canHandle({ operation })).rejects.toThrow(NotFoundHttpError); + await expect(handler.canHandle({ operation })).rejects.toThrow(NotFoundHttpError); }); it('rejects input its source cannot handle.', async(): Promise => { source.canHandle.mockRejectedValueOnce(new Error('bad data')); - await expect(route.canHandle({ operation })).rejects.toThrow('bad data'); + await expect(handler.canHandle({ operation })).rejects.toThrow('bad data'); }); it('can handle requests its source can handle.', async(): Promise => { - await expect(route.canHandle({ operation })).resolves.toBeUndefined(); + await expect(handler.canHandle({ operation })).resolves.toBeUndefined(); }); it('lets its source handle requests.', async(): Promise => { - await expect(route.handle({ operation })).resolves.toBe(representation); - }); - - it('defaults to an UnsupportedAsyncHandler if no source is provided.', async(): Promise => { - route = new BasicInteractionRoute(path); - await expect(route.canHandle({ operation })).rejects.toThrow('This route has no associated handler.'); - await expect(route.handle({ operation })).rejects.toThrow('This route has no associated handler.'); + await expect(handler.handle({ operation })).resolves.toBe(representation); }); }); diff --git a/test/unit/identity/interaction/routing/RelativeInteractionRoute.test.ts b/test/unit/identity/interaction/routing/RelativeInteractionRoute.test.ts deleted file mode 100644 index 9d8bb8ba8..000000000 --- a/test/unit/identity/interaction/routing/RelativeInteractionRoute.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { - InteractionHandler, -} from '../../../../../src/identity/interaction/InteractionHandler'; -import type { InteractionRoute } from '../../../../../src/identity/interaction/routing/InteractionRoute'; -import { RelativeInteractionRoute } from '../../../../../src/identity/interaction/routing/RelativeInteractionRoute'; - -describe('A RelativeInteractionRoute', (): void => { - const relativePath = '/relative/'; - let route: jest.Mocked; - let source: jest.Mocked; - let relativeRoute: RelativeInteractionRoute; - - beforeEach(async(): Promise => { - route = { - getPath: jest.fn().mockReturnValue('http://example.com/'), - } as any; - - source = { - canHandle: jest.fn(), - } as any; - }); - - it('returns the joined path.', async(): Promise => { - relativeRoute = new RelativeInteractionRoute(route, relativePath, source); - expect(relativeRoute.getPath()).toBe('http://example.com/relative/'); - - relativeRoute = new RelativeInteractionRoute('http://example.com/', relativePath, source); - expect(relativeRoute.getPath()).toBe('http://example.com/relative/'); - }); -}); diff --git a/test/unit/identity/interaction/routing/RelativePathInteractionRoute.test.ts b/test/unit/identity/interaction/routing/RelativePathInteractionRoute.test.ts new file mode 100644 index 000000000..b8991202c --- /dev/null +++ b/test/unit/identity/interaction/routing/RelativePathInteractionRoute.test.ts @@ -0,0 +1,24 @@ +import type { InteractionRoute } from '../../../../../src/identity/interaction/routing/InteractionRoute'; +import { + RelativePathInteractionRoute, +} from '../../../../../src/identity/interaction/routing/RelativePathInteractionRoute'; + +describe('A RelativePathInteractionRoute', (): void => { + const relativePath = '/relative/'; + let route: jest.Mocked; + let relativeRoute: RelativePathInteractionRoute; + + beforeEach(async(): Promise => { + route = { + getPath: jest.fn().mockReturnValue('http://example.com/'), + }; + }); + + it('returns the joined path.', async(): Promise => { + relativeRoute = new RelativePathInteractionRoute(route, relativePath); + expect(relativeRoute.getPath()).toBe('http://example.com/relative/'); + + relativeRoute = new RelativePathInteractionRoute('http://example.com/test/', relativePath); + expect(relativeRoute.getPath()).toBe('http://example.com/test/relative/'); + }); +});