diff --git a/src/identity/interaction/email-password/storage/AccountStore.ts b/src/identity/interaction/email-password/storage/AccountStore.ts index 16f94be6f..6a512b47e 100644 --- a/src/identity/interaction/email-password/storage/AccountStore.ts +++ b/src/identity/interaction/email-password/storage/AccountStore.ts @@ -19,6 +19,14 @@ export interface AccountStore { */ create: (email: string, webId: string, password: string) => Promise; + /** + * Verifies the account creation. This can be used with, for example, e-mail verification. + * The account can only be used after it is verified. + * In case verification is not required, this should be called immediately after the `create` call. + * @param email - the account email + */ + verify: (email: string) => Promise; + /** * Changes the password * @param email - the user's email diff --git a/src/identity/interaction/email-password/storage/BaseAccountStore.ts b/src/identity/interaction/email-password/storage/BaseAccountStore.ts index 7f3b5b4f8..8437c9282 100644 --- a/src/identity/interaction/email-password/storage/BaseAccountStore.ts +++ b/src/identity/interaction/email-password/storage/BaseAccountStore.ts @@ -11,6 +11,7 @@ export interface AccountPayload { webId: string; email: string; password: string; + verified: boolean; } /** @@ -72,6 +73,7 @@ export class BaseAccountStore implements AccountStore { public async authenticate(email: string, password: string): Promise { const { account } = await this.getAccountPayload(email); assert(account, 'No account by that email'); + assert(account.verified, 'Account still needs to be verified'); assert(await compare(password, account.password), 'Incorrect password'); return account.webId; } @@ -83,10 +85,18 @@ export class BaseAccountStore implements AccountStore { email, webId, password: await hash(password, this.saltRounds), + verified: false, }; await this.storage.set(key, payload); } + public async verify(email: string): Promise { + const { key, account } = await this.getAccountPayload(email); + assert(account, 'Account does not exist'); + account.verified = true; + await this.storage.set(key, account); + } + public async changePassword(email: string, password: string): Promise { const { key, account } = await this.getAccountPayload(email); assert(account, 'Account does not exist'); diff --git a/test/unit/identity/interaction/email-password/storage/BaseAccountStore.test.ts b/test/unit/identity/interaction/email-password/storage/BaseAccountStore.test.ts index 3ec216793..4e82931bb 100644 --- a/test/unit/identity/interaction/email-password/storage/BaseAccountStore.test.ts +++ b/test/unit/identity/interaction/email-password/storage/BaseAccountStore.test.ts @@ -37,13 +37,24 @@ describe('A BaseAccountStore', (): void => { await expect(store.authenticate(email, password)).rejects.toThrow('No account by that email'); }); + it('errors when authenticating an unverified account.', async(): Promise => { + await expect(store.create(email, webId, password)).resolves.toBeUndefined(); + await expect(store.authenticate(email, 'wrongPassword')).rejects.toThrow('Account still needs to be verified'); + }); + + it('errors when verifying a non-existent account.', async(): Promise => { + await expect(store.verify(email)).rejects.toThrow('Account does not exist'); + }); + it('errors when authenticating with the wrong password.', async(): Promise => { await expect(store.create(email, webId, password)).resolves.toBeUndefined(); + await expect(store.verify(email)).resolves.toBeUndefined(); await expect(store.authenticate(email, 'wrongPassword')).rejects.toThrow('Incorrect password'); }); it('can authenticate.', async(): Promise => { await expect(store.create(email, webId, password)).resolves.toBeUndefined(); + await expect(store.verify(email)).resolves.toBeUndefined(); await expect(store.authenticate(email, password)).resolves.toBe(webId); }); @@ -54,6 +65,7 @@ describe('A BaseAccountStore', (): void => { it('can change the password.', async(): Promise => { const newPassword = 'newPassword!'; await expect(store.create(email, webId, password)).resolves.toBeUndefined(); + await expect(store.verify(email)).resolves.toBeUndefined(); await expect(store.changePassword(email, newPassword)).resolves.toBeUndefined(); await expect(store.authenticate(email, newPassword)).resolves.toBe(webId); });