mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Store reset password ID in the submit URL
This commit is contained in:
@@ -200,7 +200,7 @@ describe('A Solid server with IDP', (): void => {
|
||||
const match = /(http:.*)$/u.exec(mail.text);
|
||||
expect(match).toBeDefined();
|
||||
nextUrl = match![1];
|
||||
expect(nextUrl).toContain('resetpassword?rid=');
|
||||
expect(nextUrl).toMatch(/\/resetpassword\/[^/]+$/u);
|
||||
});
|
||||
|
||||
it('resets the password through the given link.', async(): Promise<void> => {
|
||||
@@ -209,14 +209,12 @@ describe('A Solid server with IDP', (): void => {
|
||||
expect(res.status).toBe(200);
|
||||
const text = await res.text();
|
||||
const relative = load(text)('form').attr('action');
|
||||
expect(typeof relative).toBe('string');
|
||||
// Reset password form has no action causing the current URL to be used
|
||||
expect(relative).toBeUndefined();
|
||||
|
||||
const recordId = load(text)('input[name="recordId"]').attr('value');
|
||||
expect(typeof recordId).toBe('string');
|
||||
|
||||
// POST the new password
|
||||
const formData = stringify({ password: password2, confirmPassword: password2, recordId });
|
||||
res = await fetch(new URL(relative!, baseUrl).href, {
|
||||
// POST the new password to the same URL
|
||||
const formData = stringify({ password: password2, confirmPassword: password2 });
|
||||
res = await fetch(nextUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': APPLICATION_X_WWW_FORM_URLENCODED },
|
||||
body: formData,
|
||||
|
||||
@@ -15,7 +15,7 @@ describe('A ForgotPasswordHandler', (): void => {
|
||||
const response: HttpResponse = {} as any;
|
||||
const email = 'test@test.email';
|
||||
const recordId = '123456';
|
||||
const html = `<a href="/base/idp/resetpassword?rid=${recordId}">Reset Password</a>`;
|
||||
const html = `<a href="/base/idp/resetpassword/${recordId}">Reset Password</a>`;
|
||||
const renderParams = { response, contents: { errorMessage: '', prefilled: { email }}};
|
||||
const provider: Provider = {} as any;
|
||||
let messageRenderHandler: IdpRenderHandler;
|
||||
@@ -76,7 +76,7 @@ describe('A ForgotPasswordHandler', (): void => {
|
||||
expect(emailSender.handleSafe).toHaveBeenLastCalledWith({
|
||||
recipient: email,
|
||||
subject: 'Reset your password',
|
||||
text: `To reset your password, go to this link: http://test.com/base/idp/resetpassword?rid=${recordId}`,
|
||||
text: `To reset your password, go to this link: http://test.com/base/idp/resetpassword/${recordId}`,
|
||||
html,
|
||||
});
|
||||
expect(messageRenderHandler.handleSafe).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import {
|
||||
ResetPasswordHandler,
|
||||
} from '../../../../../../src/identity/interaction/email-password/handler/ResetPasswordHandler';
|
||||
import type {
|
||||
ResetPasswordRenderHandler,
|
||||
} from '../../../../../../src/identity/interaction/email-password/handler/ResetPasswordRenderHandler';
|
||||
import type { AccountStore } from '../../../../../../src/identity/interaction/email-password/storage/AccountStore';
|
||||
import type { HttpRequest } from '../../../../../../src/server/HttpRequest';
|
||||
import type { HttpResponse } from '../../../../../../src/server/HttpResponse';
|
||||
@@ -14,9 +11,9 @@ describe('A ResetPasswordHandler', (): void => {
|
||||
let request: HttpRequest;
|
||||
const response: HttpResponse = {} as any;
|
||||
const recordId = '123456';
|
||||
const url = `/resetURL/${recordId}`;
|
||||
const email = 'alice@test.email';
|
||||
let accountStore: AccountStore;
|
||||
let renderHandler: ResetPasswordRenderHandler;
|
||||
let messageRenderHandler: TemplateHandler<{ message: string }>;
|
||||
let handler: ResetPasswordHandler;
|
||||
|
||||
@@ -27,52 +24,39 @@ describe('A ResetPasswordHandler', (): void => {
|
||||
changePassword: jest.fn(),
|
||||
} as any;
|
||||
|
||||
renderHandler = {
|
||||
handleSafe: jest.fn(),
|
||||
} as any;
|
||||
|
||||
messageRenderHandler = {
|
||||
handleSafe: jest.fn(),
|
||||
} as any;
|
||||
|
||||
handler = new ResetPasswordHandler({
|
||||
accountStore,
|
||||
renderHandler,
|
||||
messageRenderHandler,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders errors for non-string recordIds.', async(): Promise<void> => {
|
||||
it('errors for non-string recordIds.', async(): Promise<void> => {
|
||||
const errorMessage = 'Invalid request. Open the link from your email again';
|
||||
request = createPostFormRequest({});
|
||||
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
||||
expect(renderHandler.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(renderHandler.handleSafe).toHaveBeenLastCalledWith({ response, contents: { errorMessage, recordId: '' }});
|
||||
request = createPostFormRequest({ recordId: [ 'a', 'b' ]});
|
||||
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
||||
expect(renderHandler.handleSafe).toHaveBeenCalledTimes(2);
|
||||
expect(renderHandler.handleSafe).toHaveBeenLastCalledWith({ response, contents: { errorMessage, recordId: '' }});
|
||||
await expect(handler.handle({ request, response })).rejects.toThrow(errorMessage);
|
||||
request = createPostFormRequest({}, '');
|
||||
await expect(handler.handle({ request, response })).rejects.toThrow(errorMessage);
|
||||
});
|
||||
|
||||
it('renders errors for invalid passwords.', async(): Promise<void> => {
|
||||
it('errors for invalid passwords.', async(): Promise<void> => {
|
||||
const errorMessage = 'Password and confirmation do not match';
|
||||
request = createPostFormRequest({ recordId, password: 'password!', confirmPassword: 'otherPassword!' });
|
||||
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
||||
expect(renderHandler.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(renderHandler.handleSafe).toHaveBeenLastCalledWith({ response, contents: { errorMessage, recordId }});
|
||||
request = createPostFormRequest({ password: 'password!', confirmPassword: 'otherPassword!' }, url);
|
||||
await expect(handler.handle({ request, response })).rejects.toThrow(errorMessage);
|
||||
});
|
||||
|
||||
it('renders errors for invalid emails.', async(): Promise<void> => {
|
||||
it('errors for invalid emails.', async(): Promise<void> => {
|
||||
const errorMessage = 'This reset password link is no longer valid.';
|
||||
request = createPostFormRequest({ recordId, password: 'password!', confirmPassword: 'password!' });
|
||||
request = createPostFormRequest({ password: 'password!', confirmPassword: 'password!' }, url);
|
||||
(accountStore.getForgotPasswordRecord as jest.Mock).mockResolvedValueOnce(undefined);
|
||||
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
||||
expect(renderHandler.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(renderHandler.handleSafe).toHaveBeenLastCalledWith({ response, contents: { errorMessage, recordId }});
|
||||
await expect(handler.handle({ request, response })).rejects.toThrow(errorMessage);
|
||||
});
|
||||
|
||||
it('renders a message on success.', async(): Promise<void> => {
|
||||
request = createPostFormRequest({ recordId, password: 'password!', confirmPassword: 'password!' });
|
||||
request = createPostFormRequest({ password: 'password!', confirmPassword: 'password!' }, url);
|
||||
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
||||
expect(accountStore.getForgotPasswordRecord).toHaveBeenCalledTimes(1);
|
||||
expect(accountStore.getForgotPasswordRecord).toHaveBeenLastCalledWith(recordId);
|
||||
@@ -87,10 +71,8 @@ describe('A ResetPasswordHandler', (): void => {
|
||||
|
||||
it('has a default error for non-native errors.', async(): Promise<void> => {
|
||||
const errorMessage = 'Unknown error: not native';
|
||||
request = createPostFormRequest({ recordId, password: 'password!', confirmPassword: 'password!' });
|
||||
request = createPostFormRequest({ password: 'password!', confirmPassword: 'password!' }, url);
|
||||
(accountStore.getForgotPasswordRecord as jest.Mock).mockRejectedValueOnce('not native');
|
||||
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
||||
expect(renderHandler.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(renderHandler.handleSafe).toHaveBeenLastCalledWith({ response, contents: { errorMessage, recordId }});
|
||||
await expect(handler.handle({ request, response })).rejects.toThrow(errorMessage);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import type {
|
||||
ResetPasswordRenderHandler,
|
||||
} from '../../../../../../src/identity/interaction/email-password/handler/ResetPasswordRenderHandler';
|
||||
import {
|
||||
ResetPasswordViewHandler,
|
||||
} from '../../../../../../src/identity/interaction/email-password/handler/ResetPasswordViewHandler';
|
||||
import type { HttpRequest } from '../../../../../../src/server/HttpRequest';
|
||||
import type { HttpResponse } from '../../../../../../src/server/HttpResponse';
|
||||
|
||||
describe('A ResetPasswordViewHandler', (): void => {
|
||||
let request: HttpRequest;
|
||||
const response: HttpResponse = {} as any;
|
||||
let renderHandler: ResetPasswordRenderHandler;
|
||||
let handler: ResetPasswordViewHandler;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
request = {} as any;
|
||||
|
||||
renderHandler = {
|
||||
handleSafe: jest.fn(),
|
||||
} as any;
|
||||
|
||||
handler = new ResetPasswordViewHandler(renderHandler);
|
||||
});
|
||||
|
||||
it('requires a URL.', async(): Promise<void> => {
|
||||
await expect(handler.handle({ request, response })).rejects.toThrow('The request must have a URL');
|
||||
});
|
||||
|
||||
it('requires a record ID.', async(): Promise<void> => {
|
||||
request.url = '/foo';
|
||||
await expect(handler.handle({ request, response })).rejects
|
||||
.toThrow('A forgot password record ID must be provided. Use the link you have received by email.');
|
||||
request.url = '/foo?wrong=recordId';
|
||||
await expect(handler.handle({ request, response })).rejects
|
||||
.toThrow('A forgot password record ID must be provided. Use the link you have received by email.');
|
||||
});
|
||||
|
||||
it('renders the response.', async(): Promise<void> => {
|
||||
request.url = '/foo?rid=recordId';
|
||||
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
||||
expect(renderHandler.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(renderHandler.handleSafe).toHaveBeenLastCalledWith({
|
||||
response,
|
||||
contents: { errorMessage: '', recordId: 'recordId' },
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,9 +6,11 @@ import { guardedStreamFrom } from '../../../../../../src/util/StreamUtil';
|
||||
* Creates a mock HttpRequest which is a stream of an object encoded as application/x-www-form-urlencoded
|
||||
* and a matching content-type header.
|
||||
* @param data - Object to encode.
|
||||
* @param url - URL value of the request.
|
||||
*/
|
||||
export function createPostFormRequest(data: NodeJS.Dict<any>): HttpRequest {
|
||||
export function createPostFormRequest(data: NodeJS.Dict<any>, url?: string): HttpRequest {
|
||||
const request = guardedStreamFrom(stringify(data)) as HttpRequest;
|
||||
request.headers = { 'content-type': 'application/x-www-form-urlencoded' };
|
||||
request.url = url;
|
||||
return request;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user