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