mirror of
https://github.com/standardnotes/server
synced 2026-10-17 03:03:10 -04:00
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:
co-authored by
Karol Sójko
parent
ec0fb7e0b9
commit
27cf093f85
@@ -11,36 +11,84 @@ import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
|
||||
import { User } from '../../User/User'
|
||||
import { CrypterInterface } from '../../Encryption/CrypterInterface'
|
||||
import { EncryptionVersion } from '../../Encryption/EncryptionVersion'
|
||||
import { SubscriptionSettingRepositoryInterface } from '../../Setting/SubscriptionSettingRepositoryInterface'
|
||||
import { UserSubscriptionServiceInterface } from '../../Subscription/UserSubscriptionServiceInterface'
|
||||
import { SubscriptionSettingProjector } from '../../../Projection/SubscriptionSettingProjector'
|
||||
import { SubscriptionSetting } from '../../Setting/SubscriptionSetting'
|
||||
import { UserSubscription } from '../../Subscription/UserSubscription'
|
||||
import { UserSubscriptionType } from '../../Subscription/UserSubscriptionType'
|
||||
|
||||
describe('GetSettings', () => {
|
||||
let settingRepository: SettingRepositoryInterface
|
||||
let subscriptionSettingRepository: SubscriptionSettingRepositoryInterface
|
||||
let userSubscriptionService: UserSubscriptionServiceInterface
|
||||
let settingProjector: SettingProjector
|
||||
let subscriptionSettingProjector: SubscriptionSettingProjector
|
||||
let setting: Setting
|
||||
let mfaSetting: Setting
|
||||
let signInEmailsSetting: SubscriptionSetting
|
||||
let userRepository: UserRepositoryInterface
|
||||
let user: User
|
||||
let crypter: CrypterInterface
|
||||
let regularSubscription: UserSubscription
|
||||
let sharedSubscription: UserSubscription
|
||||
|
||||
const createUseCase = () => new GetSettings(settingRepository, settingProjector, userRepository, crypter)
|
||||
const createUseCase = () =>
|
||||
new GetSettings(
|
||||
settingRepository,
|
||||
subscriptionSettingRepository,
|
||||
userSubscriptionService,
|
||||
settingProjector,
|
||||
subscriptionSettingProjector,
|
||||
userRepository,
|
||||
crypter,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
setting = {
|
||||
name: 'test',
|
||||
updatedAt: 345,
|
||||
sensitive: false,
|
||||
} as jest.Mocked<Setting>
|
||||
setting = new Setting()
|
||||
setting.name = 'test'
|
||||
setting.updatedAt = 345
|
||||
setting.sensitive = false
|
||||
|
||||
mfaSetting = {
|
||||
name: SettingName.MfaSecret,
|
||||
updatedAt: 122,
|
||||
sensitive: true,
|
||||
} as jest.Mocked<Setting>
|
||||
mfaSetting = new Setting()
|
||||
mfaSetting.name = SettingName.NAMES.MfaSecret
|
||||
mfaSetting.updatedAt = 122
|
||||
mfaSetting.sensitive = true
|
||||
|
||||
signInEmailsSetting = new SubscriptionSetting()
|
||||
signInEmailsSetting.name = SettingName.NAMES.MuteSignInEmails
|
||||
signInEmailsSetting.updatedAt = 122
|
||||
signInEmailsSetting.sensitive = false
|
||||
signInEmailsSetting.value = 'not_muted'
|
||||
|
||||
settingRepository = {} as jest.Mocked<SettingRepositoryInterface>
|
||||
settingRepository.findAllByUserUuid = jest.fn().mockReturnValue([setting, mfaSetting])
|
||||
|
||||
subscriptionSettingRepository = {} as jest.Mocked<SubscriptionSettingRepositoryInterface>
|
||||
subscriptionSettingRepository.findAllBySubscriptionUuid = jest.fn().mockReturnValue([signInEmailsSetting])
|
||||
|
||||
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 })
|
||||
|
||||
settingProjector = {} as jest.Mocked<SettingProjector>
|
||||
settingProjector.projectManySimple = jest.fn().mockReturnValue([{ foo: 'bar' }])
|
||||
settingProjector.projectSimple = jest.fn().mockReturnValue({ foo: 'bar' })
|
||||
|
||||
subscriptionSettingProjector = {} as jest.Mocked<SubscriptionSettingProjector>
|
||||
subscriptionSettingProjector.projectSimple = jest.fn().mockReturnValue({ foo: 'sub-bar' })
|
||||
|
||||
user = {} as jest.Mocked<User>
|
||||
|
||||
@@ -51,83 +99,126 @@ describe('GetSettings', () => {
|
||||
crypter.decryptForUser = jest.fn().mockReturnValue('decrypted')
|
||||
})
|
||||
|
||||
it('should fail if a user is not found', async () => {
|
||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
||||
describe('no subscription', () => {
|
||||
it('should fail if a user is not found', async () => {
|
||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
expect(await createUseCase().execute({ userUuid: '1-2-3' })).toEqual({
|
||||
success: false,
|
||||
error: {
|
||||
message: 'User 1-2-3 not found.',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should return all user settings except mfa', async () => {
|
||||
expect(await createUseCase().execute({ userUuid: '1-2-3' })).toEqual({
|
||||
success: true,
|
||||
userUuid: '1-2-3',
|
||||
settings: [{ foo: 'bar' }],
|
||||
expect(await createUseCase().execute({ userUuid: '1-2-3' })).toEqual({
|
||||
success: false,
|
||||
error: {
|
||||
message: 'User 1-2-3 not found.',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
expect(settingProjector.projectManySimple).toHaveBeenCalledWith([setting])
|
||||
})
|
||||
it('should return all user settings except mfa', async () => {
|
||||
expect(await createUseCase().execute({ userUuid: '1-2-3' })).toEqual({
|
||||
success: true,
|
||||
userUuid: '1-2-3',
|
||||
settings: [{ foo: 'bar' }],
|
||||
})
|
||||
|
||||
it('should return all setting with decrypted values', async () => {
|
||||
setting = {
|
||||
name: 'test',
|
||||
updatedAt: 345,
|
||||
value: 'encrypted',
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
} as jest.Mocked<Setting>
|
||||
settingRepository.findAllByUserUuid = jest.fn().mockReturnValue([setting])
|
||||
|
||||
expect(await createUseCase().execute({ userUuid: '1-2-3' })).toEqual({
|
||||
success: true,
|
||||
userUuid: '1-2-3',
|
||||
settings: [{ foo: 'bar' }],
|
||||
expect(settingProjector.projectSimple).toHaveBeenCalledWith(setting)
|
||||
expect(subscriptionSettingProjector.projectSimple).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
expect(settingProjector.projectManySimple).toHaveBeenCalledWith([
|
||||
{
|
||||
it('should return all setting with decrypted values', async () => {
|
||||
setting = {
|
||||
name: 'test',
|
||||
updatedAt: 345,
|
||||
value: 'encrypted',
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
} as jest.Mocked<Setting>
|
||||
settingRepository.findAllByUserUuid = jest.fn().mockReturnValue([setting])
|
||||
|
||||
expect(await createUseCase().execute({ userUuid: '1-2-3' })).toEqual({
|
||||
success: true,
|
||||
userUuid: '1-2-3',
|
||||
settings: [{ foo: 'bar' }],
|
||||
})
|
||||
|
||||
expect(settingProjector.projectSimple).toHaveBeenCalledWith({
|
||||
name: 'test',
|
||||
updatedAt: 345,
|
||||
value: 'decrypted',
|
||||
serverEncryptionVersion: 1,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should return all user settings of certain name', async () => {
|
||||
expect(
|
||||
await createUseCase().execute({ userUuid: '1-2-3', settingName: 'test', allowSensitiveRetrieval: true }),
|
||||
).toEqual({
|
||||
success: true,
|
||||
userUuid: '1-2-3',
|
||||
settings: [{ foo: 'bar' }],
|
||||
})
|
||||
})
|
||||
|
||||
expect(settingProjector.projectManySimple).toHaveBeenCalledWith([setting])
|
||||
})
|
||||
it('should return all user settings of certain name', async () => {
|
||||
expect(
|
||||
await createUseCase().execute({ userUuid: '1-2-3', settingName: 'test', allowSensitiveRetrieval: true }),
|
||||
).toEqual({
|
||||
success: true,
|
||||
userUuid: '1-2-3',
|
||||
settings: [{ foo: 'bar' }],
|
||||
})
|
||||
|
||||
it('should return all user settings updated after', async () => {
|
||||
expect(
|
||||
await createUseCase().execute({ userUuid: '1-2-3', allowSensitiveRetrieval: true, updatedAfter: 123 }),
|
||||
).toEqual({
|
||||
success: true,
|
||||
userUuid: '1-2-3',
|
||||
settings: [{ foo: 'bar' }],
|
||||
expect(settingProjector.projectSimple).toHaveBeenCalledWith(setting)
|
||||
})
|
||||
|
||||
expect(settingProjector.projectManySimple).toHaveBeenCalledWith([setting])
|
||||
})
|
||||
it('should return all user settings updated after', async () => {
|
||||
expect(
|
||||
await createUseCase().execute({ userUuid: '1-2-3', allowSensitiveRetrieval: true, updatedAfter: 123 }),
|
||||
).toEqual({
|
||||
success: true,
|
||||
userUuid: '1-2-3',
|
||||
settings: [{ foo: 'bar' }],
|
||||
})
|
||||
|
||||
it('should return all sensitive user settings if explicit', async () => {
|
||||
expect(await createUseCase().execute({ userUuid: '1-2-3', allowSensitiveRetrieval: true })).toEqual({
|
||||
success: true,
|
||||
userUuid: '1-2-3',
|
||||
settings: [{ foo: 'bar' }],
|
||||
expect(settingProjector.projectSimple).toHaveBeenCalledWith(setting)
|
||||
})
|
||||
|
||||
expect(settingProjector.projectManySimple).toHaveBeenCalledWith([setting, mfaSetting])
|
||||
it('should return all sensitive user settings if explicit', async () => {
|
||||
expect(await createUseCase().execute({ userUuid: '1-2-3', allowSensitiveRetrieval: true })).toEqual({
|
||||
success: true,
|
||||
userUuid: '1-2-3',
|
||||
settings: [{ foo: 'bar' }, { foo: 'bar' }],
|
||||
})
|
||||
|
||||
expect(settingProjector.projectSimple).toHaveBeenCalledTimes(2)
|
||||
expect(settingProjector.projectSimple).toHaveBeenNthCalledWith(1, setting)
|
||||
expect(settingProjector.projectSimple).toHaveBeenNthCalledWith(2, mfaSetting)
|
||||
})
|
||||
})
|
||||
|
||||
describe('regular subscription', () => {
|
||||
beforeEach(() => {
|
||||
userSubscriptionService.findRegularSubscriptionForUserUuid = jest
|
||||
.fn()
|
||||
.mockReturnValue({ regularSubscription, sharedSubscription: null })
|
||||
})
|
||||
|
||||
it('should return all user settings except mfa', async () => {
|
||||
expect(await createUseCase().execute({ userUuid: '1-2-3' })).toEqual({
|
||||
success: true,
|
||||
userUuid: '1-2-3',
|
||||
settings: [{ foo: 'bar' }, { foo: 'sub-bar' }],
|
||||
})
|
||||
|
||||
expect(subscriptionSettingRepository.findAllBySubscriptionUuid).toHaveBeenCalledWith('1-2-3')
|
||||
expect(settingProjector.projectSimple).toHaveBeenCalledWith(setting)
|
||||
expect(subscriptionSettingProjector.projectSimple).toHaveBeenCalledWith(signInEmailsSetting)
|
||||
})
|
||||
})
|
||||
|
||||
describe('shared subscription', () => {
|
||||
beforeEach(() => {
|
||||
userSubscriptionService.findRegularSubscriptionForUserUuid = jest
|
||||
.fn()
|
||||
.mockReturnValue({ regularSubscription, sharedSubscription })
|
||||
})
|
||||
|
||||
it('should return all user settings except mfa', async () => {
|
||||
expect(await createUseCase().execute({ userUuid: '1-2-3' })).toEqual({
|
||||
success: true,
|
||||
userUuid: '1-2-3',
|
||||
settings: [{ foo: 'bar' }, { foo: 'sub-bar' }],
|
||||
})
|
||||
|
||||
expect(subscriptionSettingRepository.findAllBySubscriptionUuid).toHaveBeenCalledWith('2-3-4')
|
||||
expect(settingProjector.projectSimple).toHaveBeenCalledWith(setting)
|
||||
expect(subscriptionSettingProjector.projectSimple).toHaveBeenCalledWith(signInEmailsSetting)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,12 +9,22 @@ import { Setting } from '../../Setting/Setting'
|
||||
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
|
||||
import { CrypterInterface } from '../../Encryption/CrypterInterface'
|
||||
import { EncryptionVersion } from '../../Encryption/EncryptionVersion'
|
||||
import { UserSubscriptionServiceInterface } from '../../Subscription/UserSubscriptionServiceInterface'
|
||||
import { SubscriptionSettingRepositoryInterface } from '../../Setting/SubscriptionSettingRepositoryInterface'
|
||||
import { SubscriptionSetting } from '../../Setting/SubscriptionSetting'
|
||||
import { SimpleSetting } from '../../Setting/SimpleSetting'
|
||||
import { SimpleSubscriptionSetting } from '../../Setting/SimpleSubscriptionSetting'
|
||||
import { SubscriptionSettingProjector } from '../../../Projection/SubscriptionSettingProjector'
|
||||
|
||||
@injectable()
|
||||
export class GetSettings implements UseCaseInterface {
|
||||
constructor(
|
||||
@inject(TYPES.SettingRepository) private settingRepository: SettingRepositoryInterface,
|
||||
@inject(TYPES.SubscriptionSettingRepository)
|
||||
private subscriptionSettingRepository: SubscriptionSettingRepositoryInterface,
|
||||
@inject(TYPES.UserSubscriptionService) private userSubscriptionService: UserSubscriptionServiceInterface,
|
||||
@inject(TYPES.SettingProjector) private settingProjector: SettingProjector,
|
||||
@inject(TYPES.SubscriptionSettingProjector) private subscriptionSettingProjector: SubscriptionSettingProjector,
|
||||
@inject(TYPES.UserRepository) private userRepository: UserRepositoryInterface,
|
||||
@inject(TYPES.Crypter) private crypter: CrypterInterface,
|
||||
) {}
|
||||
@@ -33,27 +43,43 @@ export class GetSettings implements UseCaseInterface {
|
||||
}
|
||||
}
|
||||
|
||||
let settings = await this.settingRepository.findAllByUserUuid(userUuid)
|
||||
let settings: Array<Setting | SubscriptionSetting>
|
||||
settings = await this.settingRepository.findAllByUserUuid(userUuid)
|
||||
|
||||
const { regularSubscription, sharedSubscription } =
|
||||
await this.userSubscriptionService.findRegularSubscriptionForUserUuid(user.uuid)
|
||||
const subscription = sharedSubscription ?? regularSubscription
|
||||
if (subscription) {
|
||||
const subscriptionSettings = await this.subscriptionSettingRepository.findAllBySubscriptionUuid(subscription.uuid)
|
||||
settings = settings.concat(subscriptionSettings)
|
||||
}
|
||||
|
||||
if (dto.settingName !== undefined) {
|
||||
settings = settings.filter((setting: Setting) => setting.name === dto.settingName)
|
||||
settings = settings.filter((setting: Setting | SubscriptionSetting) => setting.name === dto.settingName)
|
||||
}
|
||||
|
||||
if (dto.updatedAfter !== undefined) {
|
||||
settings = settings.filter((setting: Setting) => setting.updatedAt >= (dto.updatedAfter as number))
|
||||
settings = settings.filter(
|
||||
(setting: Setting | SubscriptionSetting) => setting.updatedAt >= (dto.updatedAfter as number),
|
||||
)
|
||||
}
|
||||
|
||||
if (!dto.allowSensitiveRetrieval) {
|
||||
settings = settings.filter((setting: Setting) => !setting.sensitive)
|
||||
settings = settings.filter((setting: Setting | SubscriptionSetting) => !setting.sensitive)
|
||||
}
|
||||
|
||||
const simpleSettings: Array<SimpleSetting | SimpleSubscriptionSetting> = []
|
||||
for (const setting of settings) {
|
||||
if (setting.value !== null && setting.serverEncryptionVersion === EncryptionVersion.Default) {
|
||||
setting.value = await this.crypter.decryptForUser(setting.value, user)
|
||||
}
|
||||
}
|
||||
|
||||
const simpleSettings = await this.settingProjector.projectManySimple(settings)
|
||||
if (setting instanceof SubscriptionSetting) {
|
||||
simpleSettings.push(await this.subscriptionSettingProjector.projectSimple(setting))
|
||||
} else {
|
||||
simpleSettings.push(await this.settingProjector.projectSimple(setting))
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { SimpleSetting } from '../../Setting/SimpleSetting'
|
||||
import { SimpleSubscriptionSetting } from '../../Setting/SimpleSubscriptionSetting'
|
||||
|
||||
export type GetSettingsResponse =
|
||||
| {
|
||||
success: true
|
||||
userUuid: string
|
||||
settings: SimpleSetting[]
|
||||
settings: Array<SimpleSetting | SimpleSubscriptionSetting>
|
||||
}
|
||||
| {
|
||||
success: false
|
||||
|
||||
Reference in New Issue
Block a user