diff --git a/packages/services/src/Domain/User/UserService.ts b/packages/services/src/Domain/User/UserService.ts index 6d137546a..1eb8b4b96 100644 --- a/packages/services/src/Domain/User/UserService.ts +++ b/packages/services/src/Domain/User/UserService.ts @@ -40,6 +40,10 @@ import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInt import { ReencryptTypeAItems } from '../Encryption/UseCase/TypeA/ReencryptTypeAItems' import { DecryptErroredPayloads } from '../Encryption/UseCase/DecryptErroredPayloads' +const cleanedEmailString = (email: string) => { + return email.trim().toLowerCase() +} + export class UserService extends AbstractService implements UserServiceInterface, InternalEventHandlerInterface @@ -593,13 +597,15 @@ export class UserService } } + const newEmail = parameters.newEmail ? cleanedEmailString(parameters.newEmail) : undefined + const user = this.sessions.getUser() as User const currentEmail = user.email const { currentRootKey, newRootKey } = await this.recomputeRootKeysForCredentialChange({ currentPassword: parameters.currentPassword, currentEmail, origination: parameters.origination, - newEmail: parameters.newEmail, + newEmail: newEmail, newPassword: parameters.newPassword, }) @@ -609,7 +615,7 @@ export class UserService currentServerPassword: currentRootKey.serverPassword as string, newRootKey: newRootKey, wrappingKey, - newEmail: parameters.newEmail, + newEmail: newEmail, }) this.unlockSyncing() diff --git a/packages/snjs/lib/Services/Session/SessionManager.ts b/packages/snjs/lib/Services/Session/SessionManager.ts index 00550a86f..42de925cf 100644 --- a/packages/snjs/lib/Services/Session/SessionManager.ts +++ b/packages/snjs/lib/Services/Session/SessionManager.ts @@ -70,15 +70,12 @@ import { UserApiServiceInterface, UserRegistrationResponseBody, } from '@standardnotes/api' +import { cleanedEmailString } from './cleanedEmailString' export const MINIMUM_PASSWORD_LENGTH = 8 export const MissingAccountParams = 'missing-params' const ThirtyMinutes = 30 * 60 * 1000 -const cleanedEmailString = (email: string) => { - return email.trim().toLowerCase() -} - /** * The session manager is responsible for loading initial user state, and any relevant * server credentials, such as the session token. It also exposes methods for registering @@ -659,7 +656,7 @@ export class SessionManager currentServerPassword: parameters.currentServerPassword, newServerPassword: parameters.newRootKey.serverPassword as string, newKeyParams: parameters.newRootKey.keyParams, - newEmail: parameters.newEmail, + newEmail: parameters.newEmail ? cleanedEmailString(parameters.newEmail) : undefined, }) const oldKeys = this._getKeyPairs.execute() diff --git a/packages/snjs/lib/Services/Session/cleanedEmailString.ts b/packages/snjs/lib/Services/Session/cleanedEmailString.ts new file mode 100644 index 000000000..129c83a8d --- /dev/null +++ b/packages/snjs/lib/Services/Session/cleanedEmailString.ts @@ -0,0 +1,3 @@ +export const cleanedEmailString = (email: string) => { + return email.trim().toLowerCase() +} diff --git a/packages/snjs/mocha/keys.test.js b/packages/snjs/mocha/keys.test.js index 9e7e87677..49331f2a9 100644 --- a/packages/snjs/mocha/keys.test.js +++ b/packages/snjs/mocha/keys.test.js @@ -453,6 +453,37 @@ describe('keys', function () { await Factory.safeDeinit(application) }) + it('changing email to all uppercase should allow sign in with lowercase', async function () { + await Factory.safeDeinit(application) + const realCryptoContext = await Factory.createAppContextWithRealCrypto() + await realCryptoContext.launch() + + application = realCryptoContext.application + + await Factory.registerUserToApplication({ + application: application, + email: email, + password: password, + }) + const mixedCaseEmail = `TheFooBar@bar.${UuidGenerator.GenerateUuid()}` + + const changeEmailResponse = await application.changeEmail(mixedCaseEmail, password) + + expect(changeEmailResponse.error).to.not.be.ok + + application = await Factory.signOutApplicationAndReturnNew(application) + const loginResponse = await Factory.loginToApplication({ + application: application, + email: mixedCaseEmail.toLowerCase(), + password: password, + }) + + expect(loginResponse).to.be.ok + expect(loginResponse.status).to.equal(200) + + await Factory.safeDeinit(application) + }).timeout(Factory.TwentySecondTimeout) + it('compares root keys', async function () { const keyParams = {} const a1 = await CreateNewRootKey({ diff --git a/packages/snjs/mocha/lib/factory.js b/packages/snjs/mocha/lib/factory.js index 81483cac9..14b5d15cb 100644 --- a/packages/snjs/mocha/lib/factory.js +++ b/packages/snjs/mocha/lib/factory.js @@ -51,7 +51,14 @@ export async function createAppContextWithRealCrypto(identifier) { return createAppContext({ identifier, crypto: new SNWebCrypto() }) } -export async function createAppContext({ identifier, crypto, email, password, host, syncCallsThresholdPerMinute } = {}) { +export async function createAppContext({ + identifier, + crypto, + email, + password, + host, + syncCallsThresholdPerMinute, +} = {}) { const context = new AppContext({ identifier, crypto, email, password, host, syncCallsThresholdPerMinute }) await context.initialize() return context @@ -250,7 +257,14 @@ export async function awaitFunctionInvokation(object, functionName) { * A new one must be created. */ export async function signOutApplicationAndReturnNew(application) { - const isRealCrypto = application.crypto instanceof SNWebCrypto + if (!application) { + throw Error('[signOutApplicationAndReturnNew] Application is undefined') + } + if (!application.options.crypto) { + throw Error('[signOutApplicationAndReturnNew] Application.options.crypto is undefined') + } + + const isRealCrypto = application.options.crypto instanceof SNWebCrypto await application.user.signOut() if (isRealCrypto) { return createInitAppWithRealCrypto() @@ -260,7 +274,7 @@ export async function signOutApplicationAndReturnNew(application) { } export async function signOutAndBackIn(application, email, password) { - const isRealCrypto = application.crypto instanceof SNWebCrypto + const isRealCrypto = application.options.crypto instanceof SNWebCrypto await application.user.signOut() const newApplication = isRealCrypto ? await createInitAppWithRealCrypto() : await createInitAppWithFakeCrypto() await this.loginToApplication({