feat: sign in setting refactor (#472)

* fix(auth): refactor setting names into domain core value objects

* fix(auth): refactor specs with setting name value objects

* feat(auth): move mute sign in emails to a subscription kind of setting

* feat(auth): add migration script to change sign in email settings to subscription settings

* chore: fix setting name usage

* fix(auth): upper casing setting names

---------

Co-authored-by: Karol Sójko <[email protected]>
This commit is contained in:
Karol Sójko
2023-03-08 10:22:27 +01:00
committed by GitHub
co-authored by Karol Sójko
parent ec0fb7e0b9
commit 27cf093f85
67 changed files with 1450 additions and 809 deletions
@@ -14,6 +14,11 @@ import { User } from '../../User/User'
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { UpdateSetting } from './UpdateSetting'
import { SettingName } from '@standardnotes/settings'
import { SubscriptionSettingServiceInterface } from '../../Setting/SubscriptionSettingServiceInterface'
import { SubscriptionSettingProjector } from '../../../Projection/SubscriptionSettingProjector'
import { UserSubscriptionServiceInterface } from '../../Subscription/UserSubscriptionServiceInterface'
import { UserSubscriptionType } from '../../Subscription/UserSubscriptionType'
import { UserSubscription } from '../../Subscription/UserSubscription'
describe('UpdateSetting', () => {
let settingService: SettingServiceInterface
@@ -25,9 +30,24 @@ describe('UpdateSetting', () => {
let userRepository: UserRepositoryInterface
let roleService: RoleServiceInterface
let logger: Logger
let userSubscriptionService: UserSubscriptionServiceInterface
let subscriptionSettingProjector: SubscriptionSettingProjector
let subscriptionSettingService: SubscriptionSettingServiceInterface
let regularSubscription: UserSubscription
let sharedSubscription: UserSubscription
const createUseCase = () =>
new UpdateSetting(settingService, settingProjector, settingsAssociationService, userRepository, roleService, logger)
new UpdateSetting(
settingService,
subscriptionSettingService,
userSubscriptionService,
settingProjector,
subscriptionSettingProjector,
settingsAssociationService,
userRepository,
roleService,
logger,
)
beforeEach(() => {
setting = {} as jest.Mocked<Setting>
@@ -35,9 +55,32 @@ describe('UpdateSetting', () => {
settingService = {} as jest.Mocked<SettingServiceInterface>
settingService.createOrReplace = jest.fn().mockReturnValue({ status: 'created', setting })
subscriptionSettingService = {} as jest.Mocked<SubscriptionSettingServiceInterface>
subscriptionSettingService.createOrReplace = jest.fn().mockReturnValue({ status: 'created', setting })
settingProjector = {} as jest.Mocked<SettingProjector>
settingProjector.projectSimple = jest.fn().mockReturnValue(settingProjection)
regularSubscription = {
uuid: '1-2-3',
subscriptionType: UserSubscriptionType.Regular,
user: Promise.resolve(user),
} as jest.Mocked<UserSubscription>
sharedSubscription = {
uuid: '2-3-4',
subscriptionType: UserSubscriptionType.Shared,
user: Promise.resolve(user),
} as jest.Mocked<UserSubscription>
userSubscriptionService = {} as jest.Mocked<UserSubscriptionServiceInterface>
userSubscriptionService.findRegularSubscriptionForUserUuid = jest
.fn()
.mockReturnValue({ regularSubscription: null, sharedSubscription: null })
subscriptionSettingProjector = {} as jest.Mocked<SubscriptionSettingProjector>
subscriptionSettingProjector.projectSimple = jest.fn().mockReturnValue({ foo: 'sub-bar' })
user = {} as jest.Mocked<User>
userRepository = {} as jest.Mocked<UserRepositoryInterface>
@@ -57,124 +100,217 @@ describe('UpdateSetting', () => {
logger.error = jest.fn()
})
it('should create a setting', async () => {
const props = {
name: SettingName.ExtensionKey,
unencryptedValue: 'test-setting-value',
serverEncryptionVersion: EncryptionVersion.Default,
sensitive: false,
}
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
expect(settingService.createOrReplace).toHaveBeenCalledWith({
props: {
name: 'EXTENSION_KEY',
describe('no subscription', () => {
it('should create a setting', async () => {
const props = {
name: SettingName.NAMES.ExtensionKey,
unencryptedValue: 'test-setting-value',
serverEncryptionVersion: 1,
serverEncryptionVersion: EncryptionVersion.Default,
sensitive: false,
},
user,
}
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
expect(settingService.createOrReplace).toHaveBeenCalledWith({
props: {
name: 'EXTENSION_KEY',
unencryptedValue: 'test-setting-value',
serverEncryptionVersion: 1,
sensitive: false,
},
user,
})
expect(response).toEqual({
success: true,
setting: settingProjection,
statusCode: 201,
})
})
expect(response).toEqual({
success: true,
setting: settingProjection,
statusCode: 201,
it('should not create a setting if user does not exist', async () => {
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
const props = {
name: SettingName.NAMES.ExtensionKey,
unencryptedValue: 'test-setting-value',
serverEncryptionVersion: EncryptionVersion.Unencrypted,
sensitive: false,
}
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
expect(settingService.createOrReplace).not.toHaveBeenCalled()
expect(response).toEqual({
success: false,
error: {
message: 'User 1-2-3 not found.',
},
statusCode: 404,
})
})
it('should not create a subscription setting', async () => {
const props = {
name: SettingName.NAMES.MuteSignInEmails,
unencryptedValue: 'test-setting-value',
serverEncryptionVersion: EncryptionVersion.Unencrypted,
sensitive: false,
}
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
expect(settingService.createOrReplace).not.toHaveBeenCalled()
expect(response).toEqual({
success: false,
error: {
message: 'User 1-2-3 has no subscription to change a subscription setting.',
},
statusCode: 401,
})
})
it('should not create a setting if the setting name is invalid', async () => {
const props = {
name: 'random-setting',
unencryptedValue: 'test-setting-value',
serverEncryptionVersion: EncryptionVersion.Unencrypted,
sensitive: false,
}
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
expect(settingService.createOrReplace).not.toHaveBeenCalled()
expect(response).toEqual({
success: false,
error: {
message: 'Invalid setting name: random-setting',
},
statusCode: 400,
})
})
it('should not create a setting if user is not permitted to', async () => {
settingsAssociationService.getPermissionAssociatedWithSetting = jest
.fn()
.mockReturnValue(PermissionName.DailyEmailBackup)
roleService.userHasPermission = jest.fn().mockReturnValue(false)
const props = {
name: SettingName.NAMES.ExtensionKey,
unencryptedValue: 'test-setting-value',
serverEncryptionVersion: EncryptionVersion.Unencrypted,
sensitive: false,
}
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
expect(settingService.createOrReplace).not.toHaveBeenCalled()
expect(response).toEqual({
success: false,
error: {
message: 'User 1-2-3 is not permitted to change the setting.',
},
statusCode: 401,
})
})
it('should not create a setting if setting is not mutable by the client', async () => {
settingsAssociationService.isSettingMutableByClient = jest.fn().mockReturnValue(false)
const props = {
name: SettingName.NAMES.ExtensionKey,
unencryptedValue: 'test-setting-value',
serverEncryptionVersion: EncryptionVersion.Unencrypted,
sensitive: false,
}
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
expect(settingService.createOrReplace).not.toHaveBeenCalled()
expect(response).toEqual({
success: false,
error: {
message: 'User 1-2-3 is not permitted to change the setting.',
},
statusCode: 401,
})
})
})
it('should not create a setting if user does not exist', async () => {
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
describe('regular subscription', () => {
beforeEach(() => {
userSubscriptionService.findRegularSubscriptionForUserUuid = jest
.fn()
.mockReturnValue({ regularSubscription, sharedSubscription: null })
})
const props = {
name: SettingName.ExtensionKey,
unencryptedValue: 'test-setting-value',
serverEncryptionVersion: EncryptionVersion.Unencrypted,
sensitive: false,
}
it('should create a subscription setting', async () => {
const props = {
name: SettingName.NAMES.MuteSignInEmails,
unencryptedValue: 'test-setting-value',
serverEncryptionVersion: EncryptionVersion.Default,
sensitive: false,
}
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
expect(settingService.createOrReplace).not.toHaveBeenCalled()
expect(subscriptionSettingService.createOrReplace).toHaveBeenCalledWith({
props: {
name: 'MUTE_SIGN_IN_EMAILS',
unencryptedValue: 'test-setting-value',
serverEncryptionVersion: 1,
sensitive: false,
},
userSubscription: regularSubscription,
})
expect(response).toEqual({
success: false,
error: {
message: 'User 1-2-3 not found.',
},
statusCode: 404,
expect(response).toEqual({
success: true,
setting: { foo: 'sub-bar' },
statusCode: 201,
})
})
})
it('should not create a setting if the setting name is invalid', async () => {
const props = {
name: 'random-setting',
unencryptedValue: 'test-setting-value',
serverEncryptionVersion: EncryptionVersion.Unencrypted,
sensitive: false,
}
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
expect(settingService.createOrReplace).not.toHaveBeenCalled()
expect(response).toEqual({
success: false,
error: {
message: 'Setting name random-setting is invalid.',
},
statusCode: 400,
describe('shared subscription', () => {
beforeEach(() => {
userSubscriptionService.findRegularSubscriptionForUserUuid = jest
.fn()
.mockReturnValue({ regularSubscription, sharedSubscription })
})
})
it('should not create a setting if user is not permitted to', async () => {
settingsAssociationService.getPermissionAssociatedWithSetting = jest
.fn()
.mockReturnValue(PermissionName.DailyEmailBackup)
it('should create a subscription setting', async () => {
const props = {
name: SettingName.NAMES.MuteSignInEmails,
unencryptedValue: 'test-setting-value',
serverEncryptionVersion: EncryptionVersion.Default,
sensitive: false,
}
roleService.userHasPermission = jest.fn().mockReturnValue(false)
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
const props = {
name: SettingName.ExtensionKey,
unencryptedValue: 'test-setting-value',
serverEncryptionVersion: EncryptionVersion.Unencrypted,
sensitive: false,
}
expect(subscriptionSettingService.createOrReplace).toHaveBeenCalledWith({
props: {
name: 'MUTE_SIGN_IN_EMAILS',
unencryptedValue: 'test-setting-value',
serverEncryptionVersion: 1,
sensitive: false,
},
userSubscription: sharedSubscription,
})
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
expect(settingService.createOrReplace).not.toHaveBeenCalled()
expect(response).toEqual({
success: false,
error: {
message: 'User 1-2-3 is not permitted to change the setting.',
},
statusCode: 401,
})
})
it('should not create a setting if setting is not mutable by the client', async () => {
settingsAssociationService.isSettingMutableByClient = jest.fn().mockReturnValue(false)
const props = {
name: SettingName.ExtensionKey,
unencryptedValue: 'test-setting-value',
serverEncryptionVersion: EncryptionVersion.Unencrypted,
sensitive: false,
}
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
expect(settingService.createOrReplace).not.toHaveBeenCalled()
expect(response).toEqual({
success: false,
error: {
message: 'User 1-2-3 is not permitted to change the setting.',
},
statusCode: 401,
expect(response).toEqual({
success: true,
setting: { foo: 'sub-bar' },
statusCode: 201,
})
})
})
})