diff --git a/src/identity/interaction/email-password/handler/RegistrationHandler.ts b/src/identity/interaction/email-password/handler/RegistrationHandler.ts index c396f0fcc..0b125752d 100644 --- a/src/identity/interaction/email-password/handler/RegistrationHandler.ts +++ b/src/identity/interaction/email-password/handler/RegistrationHandler.ts @@ -4,6 +4,7 @@ import type { ResourceIdentifier } from '../../../../ldp/representation/Resource import { getLoggerFor } from '../../../../logging/LogUtil'; import type { IdentifierGenerator } from '../../../../pods/generate/IdentifierGenerator'; import type { PodManager } from '../../../../pods/PodManager'; +import type { PodSettings } from '../../../../pods/settings/PodSettings'; import type { HttpHandlerInput } from '../../../../server/HttpHandler'; import { HttpHandler } from '../../../../server/HttpHandler'; import type { HttpRequest } from '../../../../server/HttpRequest'; @@ -48,17 +49,29 @@ export interface RegistrationHandlerArgs { /** * All the parameters that will be parsed from a request. - * `data` contains all the raw values to potentially be used by pod templates. */ -interface ParseResult { +interface ParsedInput { email: string; + webId?: string; password?: string; podName?: string; - webId?: string; + template?: string; + createWebId: boolean; + register: boolean; + createPod: boolean; +} + +/** + * The results that will be applied to the response template. + */ +interface RegistrationResponse { + email: string; + webId?: string; + oidcIssuer?: string; + podBaseUrl?: string; createWebId: boolean; register: boolean; createPod: boolean; - data: NodeJS.Dict; } /** @@ -103,7 +116,9 @@ export class RegistrationHandler extends HttpHandler { const contents = await this.register(result); await this.responseHandler.handleSafe({ response, contents }); } catch (error: unknown) { - throwIdpInteractionError(error, result.data as Record); + // Don't expose the password field + delete result.password; + throwIdpInteractionError(error, result as Record); } } @@ -111,14 +126,16 @@ export class RegistrationHandler extends HttpHandler { * Does the full registration and pod creation process, * with the steps chosen by the values in the `ParseResult`. */ - private async register(result: ParseResult): Promise> { + private async register(result: ParsedInput): Promise { // This is only used when createWebId and/or createPod are true let podBaseUrl: ResourceIdentifier | undefined; + if (result.createWebId || result.createPod) { + podBaseUrl = this.identifierGenerator.generate(result.podName!); + } // Create or verify the WebID if (result.createWebId) { - podBaseUrl = this.identifierGenerator.generate(result.podName!); - result.webId = urljoin(podBaseUrl.path, this.webIdSuffix); + result.webId = urljoin(podBaseUrl!.path, this.webIdSuffix); } else { await this.ownershipValidator.handleSafe({ webId: result.webId! }); } @@ -126,20 +143,24 @@ export class RegistrationHandler extends HttpHandler { // Register the account if (result.register) { await this.accountStore.create(result.email, result.webId!, result.password!); - - // Add relevant data for the templates - result.data.oidcIssuer = this.baseUrl; } // Create the pod if (result.createPod) { - podBaseUrl = podBaseUrl ?? this.identifierGenerator.generate(result.podName!); + const podSettings: PodSettings = { + email: result.email, + webId: result.webId!, + template: result.template, + podBaseUrl: podBaseUrl!.path, + }; + + // Set the OIDC issuer to our server when registering with the IDP + if (result.register) { + podSettings.oidcIssuer = this.baseUrl; + } + try { - await this.podManager.createPod(podBaseUrl, { - ...result.data, - podBaseUrl: podBaseUrl.path, - webId: result.webId!, - }); + await this.podManager.createPod(podBaseUrl!, podSettings); } catch (error: unknown) { // In case pod creation errors we don't want to keep the account if (result.register) { @@ -170,7 +191,7 @@ export class RegistrationHandler extends HttpHandler { /** * Parses the input request into a `ParseResult`. */ - private async parseInput(request: HttpRequest): Promise { + private async parseInput(request: HttpRequest): Promise { const parsed = await getFormDataRequestBody(request); let prefilled: Record = {}; try { @@ -190,18 +211,18 @@ export class RegistrationHandler extends HttpHandler { * Converts the raw input date into a `ParseResult`. * Verifies that all the data combinations make sense. */ - private validateInput(parsed: NodeJS.Dict): ParseResult { - const { email, password, confirmPassword, podName, webId, createWebId, register, createPod } = parsed; + private validateInput(parsed: NodeJS.Dict): ParsedInput { + const { email, password, confirmPassword, podName, webId, template, createWebId, register, createPod } = parsed; assert(typeof email === 'string' && email.length > 0 && emailRegex.test(email), 'A valid e-mail address is required'); - const result: ParseResult = { + const result: ParsedInput = { email, + template, createWebId: Boolean(createWebId), register: Boolean(register), createPod: Boolean(createPod), - data: parsed, }; const validWebId = typeof webId === 'string' && webId.length > 0; diff --git a/test/unit/identity/interaction/email-password/handler/RegistrationHandler.test.ts b/test/unit/identity/interaction/email-password/handler/RegistrationHandler.test.ts index d84f6be1c..278ca1a1f 100644 --- a/test/unit/identity/interaction/email-password/handler/RegistrationHandler.test.ts +++ b/test/unit/identity/interaction/email-password/handler/RegistrationHandler.test.ts @@ -8,6 +8,7 @@ import type { OwnershipValidator } from '../../../../../../src/identity/ownershi import type { ResourceIdentifier } from '../../../../../../src/ldp/representation/ResourceIdentifier'; import type { IdentifierGenerator } from '../../../../../../src/pods/generate/IdentifierGenerator'; import type { PodManager } from '../../../../../../src/pods/PodManager'; +import type { PodSettings } from '../../../../../../src/pods/settings/PodSettings'; import type { HttpRequest } from '../../../../../../src/server/HttpRequest'; import type { HttpResponse } from '../../../../../../src/server/HttpResponse'; import type { TemplateHandler } from '../../../../../../src/server/util/TemplateHandler'; @@ -31,6 +32,7 @@ describe('A RegistrationHandler', (): void => { const baseUrl = 'http://test.com/'; const webIdSuffix = '/profile/card'; + let podSettings: PodSettings; let identifierGenerator: IdentifierGenerator; let ownershipValidator: OwnershipValidator; let accountStore: AccountStore; @@ -39,6 +41,8 @@ describe('A RegistrationHandler', (): void => { let handler: RegistrationHandler; beforeEach(async(): Promise => { + podSettings = { email, webId, podBaseUrl }; + identifierGenerator = { generate: jest.fn((name: string): ResourceIdentifier => ({ path: `${baseUrl}${name}/` })), }; @@ -179,9 +183,7 @@ describe('A RegistrationHandler', (): void => { expect(identifierGenerator.generate).toHaveBeenCalledTimes(1); expect(identifierGenerator.generate).toHaveBeenLastCalledWith(podName); expect(podManager.createPod).toHaveBeenCalledTimes(1); - expect(podManager.createPod).toHaveBeenLastCalledWith( - { path: `${baseUrl}${podName}/` }, { podBaseUrl, ...params }, - ); + expect(podManager.createPod).toHaveBeenLastCalledWith({ path: `${baseUrl}${podName}/` }, podSettings); expect(accountStore.create).toHaveBeenCalledTimes(0); expect(accountStore.verify).toHaveBeenCalledTimes(0); @@ -190,6 +192,7 @@ describe('A RegistrationHandler', (): void => { it('adds an oidcIssuer to the data when doing both IDP registration and pod creation.', async(): Promise => { const params = { email, webId, password, confirmPassword, podName, register, createPod }; + podSettings.oidcIssuer = baseUrl; request = createPostFormRequest(params); await expect(handler.handle({ request, response })).resolves.toBeUndefined(); @@ -199,11 +202,8 @@ describe('A RegistrationHandler', (): void => { expect(accountStore.create).toHaveBeenLastCalledWith(email, webId, password); expect(identifierGenerator.generate).toHaveBeenCalledTimes(1); expect(identifierGenerator.generate).toHaveBeenLastCalledWith(podName); - (params as any).oidcIssuer = baseUrl; expect(podManager.createPod).toHaveBeenCalledTimes(1); - expect(podManager.createPod).toHaveBeenLastCalledWith( - { path: `${baseUrl}${podName}/` }, { podBaseUrl, ...params }, - ); + expect(podManager.createPod).toHaveBeenLastCalledWith({ path: `${baseUrl}${podName}/` }, podSettings); expect(accountStore.verify).toHaveBeenCalledTimes(1); expect(accountStore.verify).toHaveBeenLastCalledWith(email); @@ -212,6 +212,7 @@ describe('A RegistrationHandler', (): void => { it('deletes the created account if pod generation fails.', async(): Promise => { const params = { email, webId, password, confirmPassword, podName, register, createPod }; + podSettings.oidcIssuer = baseUrl; request = createPostFormRequest(params); (podManager.createPod as jest.Mock).mockRejectedValueOnce(new Error('pod error')); await expect(handler.handle({ request, response })).rejects.toThrow('pod error'); @@ -222,11 +223,8 @@ describe('A RegistrationHandler', (): void => { expect(accountStore.create).toHaveBeenLastCalledWith(email, webId, password); expect(identifierGenerator.generate).toHaveBeenCalledTimes(1); expect(identifierGenerator.generate).toHaveBeenLastCalledWith(podName); - (params as any).oidcIssuer = baseUrl; expect(podManager.createPod).toHaveBeenCalledTimes(1); - expect(podManager.createPod).toHaveBeenLastCalledWith( - { path: `${baseUrl}${podName}/` }, { podBaseUrl, ...params }, - ); + expect(podManager.createPod).toHaveBeenLastCalledWith({ path: `${baseUrl}${podName}/` }, podSettings); expect(accountStore.deleteAccount).toHaveBeenCalledTimes(1); expect(accountStore.deleteAccount).toHaveBeenLastCalledWith(email); @@ -235,10 +233,12 @@ describe('A RegistrationHandler', (): void => { it('can create a WebID with an account and pod.', async(): Promise => { const params = { email, password, confirmPassword, podName, createWebId, register, createPod }; + podSettings.oidcIssuer = baseUrl; request = createPostFormRequest(params); await expect(handler.handle({ request, response })).resolves.toBeUndefined(); const generatedWebID = urljoin(baseUrl, podName, webIdSuffix); + podSettings.webId = generatedWebID; expect(identifierGenerator.generate).toHaveBeenCalledTimes(1); expect(identifierGenerator.generate).toHaveBeenLastCalledWith(podName); @@ -247,9 +247,7 @@ describe('A RegistrationHandler', (): void => { expect(accountStore.verify).toHaveBeenCalledTimes(1); expect(accountStore.verify).toHaveBeenLastCalledWith(email); expect(podManager.createPod).toHaveBeenCalledTimes(1); - expect(podManager.createPod).toHaveBeenLastCalledWith( - { path: `${baseUrl}${podName}/` }, { ...params, podBaseUrl, oidcIssuer: baseUrl, webId: generatedWebID }, - ); + expect(podManager.createPod).toHaveBeenLastCalledWith({ path: `${baseUrl}${podName}/` }, podSettings); expect(ownershipValidator.handleSafe).toHaveBeenCalledTimes(0); expect(accountStore.deleteAccount).toHaveBeenCalledTimes(0); @@ -262,7 +260,13 @@ describe('A RegistrationHandler', (): void => { const prom = handler.handle({ request, response }); await expect(prom).rejects.toThrow('pod error'); await expect(prom).rejects.toThrow(IdpInteractionError); - await expect(prom).rejects.toThrow(expect.objectContaining({ prefilled: params })); + // Using the cleaned input for prefilled + await expect(prom).rejects.toThrow(expect.objectContaining({ prefilled: { + ...params, + createWebId: false, + register: false, + createPod: true, + }})); }); }); });