mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Send reset password recordId as query parameter
This is a revert of a previous change but is now possible due to the use of JSON bodies. This does mean JavaScript is required in the HTML page, but that will be required for future changes anyway.
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
"comment": "Handles the reset password page submission",
|
||||
"@id": "urn:solid-server:auth:password:ResetPasswordRoute",
|
||||
"@type": "BasicInteractionRoute",
|
||||
"route": "^/resetpassword/[^/]*$",
|
||||
"route": "^/resetpassword/$",
|
||||
"viewTemplates": {
|
||||
"BasicInteractionRoute:_viewTemplates_key": "text/html",
|
||||
"BasicInteractionRoute:_viewTemplates_value": "@css:templates/identity/email-password/reset-password.html.ejs"
|
||||
|
||||
@@ -68,7 +68,8 @@ export class ForgotPasswordHandler extends InteractionHandler {
|
||||
*/
|
||||
private async sendResetMail(recordId: string, email: string): Promise<void> {
|
||||
this.logger.info(`Sending password reset to ${email}`);
|
||||
const resetLink = joinUrl(this.baseUrl, this.idpPath, `resetpassword/${recordId}`);
|
||||
// `joinUrl` strips trailing slash when query parameter gets added
|
||||
const resetLink = `${joinUrl(this.baseUrl, this.idpPath, 'resetpassword/')}?rid=${recordId}`;
|
||||
const renderedEmail = await this.templateEngine.render({ resetLink });
|
||||
await this.emailSender.handleSafe({
|
||||
recipient: email,
|
||||
|
||||
@@ -21,10 +21,8 @@ export class ResetPasswordHandler extends InteractionHandler {
|
||||
}
|
||||
|
||||
public async handle({ operation }: InteractionHandlerInput): Promise<InteractionResponseResult> {
|
||||
// Extract record ID from request URL
|
||||
const recordId = /\/([^/]+)$/u.exec(operation.target.path)?.[1];
|
||||
// Validate input data
|
||||
const { password, confirmPassword } = await readJsonStream(operation.body.data);
|
||||
const { password, confirmPassword, recordId } = await readJsonStream(operation.body.data);
|
||||
assert(
|
||||
typeof recordId === 'string' && recordId.length > 0,
|
||||
'Invalid request. Open the link from your email again',
|
||||
|
||||
@@ -15,7 +15,14 @@
|
||||
<input id="confirmPassword" type="password" name="confirmPassword" placeholder="">
|
||||
</li>
|
||||
</ol>
|
||||
<input type="hidden" id="recordId" name="recordId" value="">
|
||||
</fieldset>
|
||||
|
||||
<p class="actions"><button type="submit" name="submit">Reset password</button></p>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
const hidden = document.getElementById('recordId');
|
||||
const recordId = new URLSearchParams(window.location.search).get('rid');
|
||||
hidden.value = recordId;
|
||||
</script>
|
||||
|
||||
@@ -206,8 +206,12 @@ describe('A Solid server with IDP', (): void => {
|
||||
// Reset password form has no action causing the current URL to be used
|
||||
expect(relative).toBeUndefined();
|
||||
|
||||
// Extract recordId from URL since JS is used to add it
|
||||
const recordId = /\?rid=([^/]+)$/u.exec(nextUrl)?.[1];
|
||||
expect(typeof recordId).toBe('string');
|
||||
|
||||
// POST the new password to the same URL
|
||||
const formData = stringify({ password: password2, confirmPassword: password2 });
|
||||
const formData = stringify({ password: password2, confirmPassword: password2, recordId });
|
||||
res = await fetch(nextUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': APPLICATION_X_WWW_FORM_URLENCODED },
|
||||
|
||||
@@ -64,7 +64,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/${recordId}`,
|
||||
text: `To reset your password, go to this link: http://test.com/base/idp/resetpassword/?rid=${recordId}`,
|
||||
html,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,25 +27,25 @@ describe('A ResetPasswordHandler', (): void => {
|
||||
const errorMessage = 'Invalid request. Open the link from your email again';
|
||||
operation = createPostJsonOperation({});
|
||||
await expect(handler.handle({ operation })).rejects.toThrow(errorMessage);
|
||||
operation = createPostJsonOperation({}, '');
|
||||
operation = createPostJsonOperation({ recordId: 5 });
|
||||
await expect(handler.handle({ operation })).rejects.toThrow(errorMessage);
|
||||
});
|
||||
|
||||
it('errors for invalid passwords.', async(): Promise<void> => {
|
||||
const errorMessage = 'Your password and confirmation did not match.';
|
||||
operation = createPostJsonOperation({ password: 'password!', confirmPassword: 'otherPassword!' }, url);
|
||||
operation = createPostJsonOperation({ password: 'password!', confirmPassword: 'otherPassword!', recordId }, url);
|
||||
await expect(handler.handle({ operation })).rejects.toThrow(errorMessage);
|
||||
});
|
||||
|
||||
it('errors for invalid emails.', async(): Promise<void> => {
|
||||
const errorMessage = 'This reset password link is no longer valid.';
|
||||
operation = createPostJsonOperation({ password: 'password!', confirmPassword: 'password!' }, url);
|
||||
operation = createPostJsonOperation({ password: 'password!', confirmPassword: 'password!', recordId }, url);
|
||||
(accountStore.getForgotPasswordRecord as jest.Mock).mockResolvedValueOnce(undefined);
|
||||
await expect(handler.handle({ operation })).rejects.toThrow(errorMessage);
|
||||
});
|
||||
|
||||
it('renders a message on success.', async(): Promise<void> => {
|
||||
operation = createPostJsonOperation({ password: 'password!', confirmPassword: 'password!' }, url);
|
||||
operation = createPostJsonOperation({ password: 'password!', confirmPassword: 'password!', recordId }, url);
|
||||
await expect(handler.handle({ operation })).resolves.toEqual({ type: 'response' });
|
||||
expect(accountStore.getForgotPasswordRecord).toHaveBeenCalledTimes(1);
|
||||
expect(accountStore.getForgotPasswordRecord).toHaveBeenLastCalledWith(recordId);
|
||||
|
||||
Reference in New Issue
Block a user