Skip to content

Commit 98304cc

Browse files
authored
fix: trim/lowercase email on email change (#2886)
1 parent ed0d98a commit 98304cc

File tree

5 files changed

+61
-10
lines changed

5 files changed

+61
-10
lines changed

packages/services/src/Domain/User/UserService.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInt
4040
import { ReencryptTypeAItems } from '../Encryption/UseCase/TypeA/ReencryptTypeAItems'
4141
import { DecryptErroredPayloads } from '../Encryption/UseCase/DecryptErroredPayloads'
4242

43+
const cleanedEmailString = (email: string) => {
44+
return email.trim().toLowerCase()
45+
}
46+
4347
export class UserService
4448
extends AbstractService<AccountEvent, AccountEventData>
4549
implements UserServiceInterface, InternalEventHandlerInterface
@@ -593,13 +597,15 @@ export class UserService
593597
}
594598
}
595599

600+
const newEmail = parameters.newEmail ? cleanedEmailString(parameters.newEmail) : undefined
601+
596602
const user = this.sessions.getUser() as User
597603
const currentEmail = user.email
598604
const { currentRootKey, newRootKey } = await this.recomputeRootKeysForCredentialChange({
599605
currentPassword: parameters.currentPassword,
600606
currentEmail,
601607
origination: parameters.origination,
602-
newEmail: parameters.newEmail,
608+
newEmail: newEmail,
603609
newPassword: parameters.newPassword,
604610
})
605611

@@ -609,7 +615,7 @@ export class UserService
609615
currentServerPassword: currentRootKey.serverPassword as string,
610616
newRootKey: newRootKey,
611617
wrappingKey,
612-
newEmail: parameters.newEmail,
618+
newEmail: newEmail,
613619
})
614620

615621
this.unlockSyncing()

packages/snjs/lib/Services/Session/SessionManager.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,12 @@ import {
7070
UserApiServiceInterface,
7171
UserRegistrationResponseBody,
7272
} from '@standardnotes/api'
73+
import { cleanedEmailString } from './cleanedEmailString'
7374

7475
export const MINIMUM_PASSWORD_LENGTH = 8
7576
export const MissingAccountParams = 'missing-params'
7677
const ThirtyMinutes = 30 * 60 * 1000
7778

78-
const cleanedEmailString = (email: string) => {
79-
return email.trim().toLowerCase()
80-
}
81-
8279
/**
8380
* The session manager is responsible for loading initial user state, and any relevant
8481
* server credentials, such as the session token. It also exposes methods for registering
@@ -659,7 +656,7 @@ export class SessionManager
659656
currentServerPassword: parameters.currentServerPassword,
660657
newServerPassword: parameters.newRootKey.serverPassword as string,
661658
newKeyParams: parameters.newRootKey.keyParams,
662-
newEmail: parameters.newEmail,
659+
newEmail: parameters.newEmail ? cleanedEmailString(parameters.newEmail) : undefined,
663660
})
664661

665662
const oldKeys = this._getKeyPairs.execute()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const cleanedEmailString = (email: string) => {
2+
return email.trim().toLowerCase()
3+
}

packages/snjs/mocha/keys.test.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,37 @@ describe('keys', function () {
453453
await Factory.safeDeinit(application)
454454
})
455455

456+
it('changing email to all uppercase should allow sign in with lowercase', async function () {
457+
await Factory.safeDeinit(application)
458+
const realCryptoContext = await Factory.createAppContextWithRealCrypto()
459+
await realCryptoContext.launch()
460+
461+
application = realCryptoContext.application
462+
463+
await Factory.registerUserToApplication({
464+
application: application,
465+
email: email,
466+
password: password,
467+
})
468+
const mixedCaseEmail = `TheFooBar@bar.${UuidGenerator.GenerateUuid()}`
469+
470+
const changeEmailResponse = await application.changeEmail(mixedCaseEmail, password)
471+
472+
expect(changeEmailResponse.error).to.not.be.ok
473+
474+
application = await Factory.signOutApplicationAndReturnNew(application)
475+
const loginResponse = await Factory.loginToApplication({
476+
application: application,
477+
email: mixedCaseEmail.toLowerCase(),
478+
password: password,
479+
})
480+
481+
expect(loginResponse).to.be.ok
482+
expect(loginResponse.status).to.equal(200)
483+
484+
await Factory.safeDeinit(application)
485+
}).timeout(Factory.TwentySecondTimeout)
486+
456487
it('compares root keys', async function () {
457488
const keyParams = {}
458489
const a1 = await CreateNewRootKey({

packages/snjs/mocha/lib/factory.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,14 @@ export async function createAppContextWithRealCrypto(identifier) {
5151
return createAppContext({ identifier, crypto: new SNWebCrypto() })
5252
}
5353

54-
export async function createAppContext({ identifier, crypto, email, password, host, syncCallsThresholdPerMinute } = {}) {
54+
export async function createAppContext({
55+
identifier,
56+
crypto,
57+
email,
58+
password,
59+
host,
60+
syncCallsThresholdPerMinute,
61+
} = {}) {
5562
const context = new AppContext({ identifier, crypto, email, password, host, syncCallsThresholdPerMinute })
5663
await context.initialize()
5764
return context
@@ -250,7 +257,14 @@ export async function awaitFunctionInvokation(object, functionName) {
250257
* A new one must be created.
251258
*/
252259
export async function signOutApplicationAndReturnNew(application) {
253-
const isRealCrypto = application.crypto instanceof SNWebCrypto
260+
if (!application) {
261+
throw Error('[signOutApplicationAndReturnNew] Application is undefined')
262+
}
263+
if (!application.options.crypto) {
264+
throw Error('[signOutApplicationAndReturnNew] Application.options.crypto is undefined')
265+
}
266+
267+
const isRealCrypto = application.options.crypto instanceof SNWebCrypto
254268
await application.user.signOut()
255269
if (isRealCrypto) {
256270
return createInitAppWithRealCrypto()
@@ -260,7 +274,7 @@ export async function signOutApplicationAndReturnNew(application) {
260274
}
261275

262276
export async function signOutAndBackIn(application, email, password) {
263-
const isRealCrypto = application.crypto instanceof SNWebCrypto
277+
const isRealCrypto = application.options.crypto instanceof SNWebCrypto
264278
await application.user.signOut()
265279
const newApplication = isRealCrypto ? await createInitAppWithRealCrypto() : await createInitAppWithFakeCrypto()
266280
await this.loginToApplication({

0 commit comments

Comments
 (0)