@mudrichenkoevgeny/web-platform-sdk-core-security 0.0.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/README.md +110 -110
- package/dist/di/security-component.d.ts +56 -0
- package/dist/di/security-component.d.ts.map +1 -0
- package/dist/di/security-component.test.d.ts +2 -0
- package/dist/di/security-component.test.d.ts.map +1 -0
- package/dist/error/model/security-error.d.ts +117 -0
- package/dist/error/model/security-error.d.ts.map +1 -0
- package/dist/error/model/security-error.test.d.ts +2 -0
- package/dist/error/model/security-error.test.d.ts.map +1 -0
- package/dist/error/parser/security-error-parser.d.ts +37 -0
- package/dist/error/parser/security-error-parser.d.ts.map +1 -0
- package/dist/error/parser/security-error-parser.test.d.ts +2 -0
- package/dist/error/parser/security-error-parser.test.d.ts.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11946 -4898
- package/dist/integration/SecuritySettingsIntegration.test.d.ts +2 -0
- package/dist/integration/SecuritySettingsIntegration.test.d.ts.map +1 -0
- package/dist/mock/di/security-component-mock.d.ts +9 -0
- package/dist/mock/di/security-component-mock.d.ts.map +1 -0
- package/dist/mock/network/open-security-settings-api-mock.d.ts +25 -0
- package/dist/mock/network/open-security-settings-api-mock.d.ts.map +1 -0
- package/dist/mock/repository/open-security-settings-repository-mock.d.ts +28 -0
- package/dist/mock/repository/open-security-settings-repository-mock.d.ts.map +1 -0
- package/dist/mock/storage/open-security-settings-storage-mock.d.ts +31 -0
- package/dist/mock/storage/open-security-settings-storage-mock.d.ts.map +1 -0
- package/dist/mock/usecase/validate-password-use-case-mock.d.ts +22 -0
- package/dist/mock/usecase/validate-password-use-case-mock.d.ts.map +1 -0
- package/dist/network/api/security-settings/fetch-open-security-settings-api.d.ts +22 -0
- package/dist/network/api/security-settings/fetch-open-security-settings-api.d.ts.map +1 -0
- package/dist/network/api/security-settings/fetch-open-security-settings-api.test.d.ts +2 -0
- package/dist/network/api/security-settings/fetch-open-security-settings-api.test.d.ts.map +1 -0
- package/dist/network/api/security-settings/open-security-settings-api.d.ts +14 -0
- package/dist/network/api/security-settings/open-security-settings-api.d.ts.map +1 -0
- package/dist/network/api/security-settings/security-settings-dto-mapping.test.d.ts +2 -0
- package/dist/network/api/security-settings/security-settings-dto-mapping.test.d.ts.map +1 -0
- package/dist/network/contract/security-web-socket-event-types.d.ts +2 -0
- package/dist/network/contract/security-web-socket-event-types.d.ts.map +1 -0
- package/dist/network/websocket/message-handler/security-web-socket-message-handler.d.ts +15 -0
- package/dist/network/websocket/message-handler/security-web-socket-message-handler.d.ts.map +1 -0
- package/dist/network/websocket/message-handler/security-web-socket-message-handler.test.d.ts +2 -0
- package/dist/network/websocket/message-handler/security-web-socket-message-handler.test.d.ts.map +1 -0
- package/dist/repository/open-security-settings-repository-impl.d.ts +57 -0
- package/dist/repository/open-security-settings-repository-impl.d.ts.map +1 -0
- package/dist/repository/open-security-settings-repository-impl.test.d.ts +2 -0
- package/dist/repository/open-security-settings-repository-impl.test.d.ts.map +1 -0
- package/dist/repository/open-security-settings-repository.d.ts +35 -0
- package/dist/repository/open-security-settings-repository.d.ts.map +1 -0
- package/dist/storage/security-settings/encrypted-open-security-settings-storage.d.ts +32 -0
- package/dist/storage/security-settings/encrypted-open-security-settings-storage.d.ts.map +1 -0
- package/dist/storage/security-settings/encrypted-open-security-settings-storage.test.d.ts +2 -0
- package/dist/storage/security-settings/encrypted-open-security-settings-storage.test.d.ts.map +1 -0
- package/dist/storage/security-settings/open-security-settings-storage.d.ts +23 -0
- package/dist/storage/security-settings/open-security-settings-storage.d.ts.map +1 -0
- package/dist/usecase/observe-open-security-settings-use-case.d.ts +19 -0
- package/dist/usecase/observe-open-security-settings-use-case.d.ts.map +1 -0
- package/dist/usecase/observe-open-security-settings-use-case.test.d.ts +2 -0
- package/dist/usecase/observe-open-security-settings-use-case.test.d.ts.map +1 -0
- package/dist/usecase/refresh-open-security-settings-use-case.d.ts +22 -0
- package/dist/usecase/refresh-open-security-settings-use-case.d.ts.map +1 -0
- package/dist/usecase/refresh-open-security-settings-use-case.test.d.ts +2 -0
- package/dist/usecase/refresh-open-security-settings-use-case.test.d.ts.map +1 -0
- package/dist/usecase/validate-password-use-case.d.ts +26 -0
- package/dist/usecase/validate-password-use-case.d.ts.map +1 -0
- package/dist/usecase/validate-password-use-case.test.d.ts +2 -0
- package/dist/usecase/validate-password-use-case.test.d.ts.map +1 -0
- package/package.json +16 -7
- package/src/di/security-component.test.ts +0 -33
- package/src/di/security-component.ts +0 -86
- package/src/domain/model/open-security-settings.ts +0 -79
- package/src/domain/model/password-policy-validator.test.ts +0 -96
- package/src/domain/model/password-policy-validator.ts +0 -63
- package/src/error/model/security-error.test.ts +0 -65
- package/src/error/model/security-error.ts +0 -182
- package/src/error/naming/client-security-error-codes.ts +0 -21
- package/src/error/parser/security-error-parser.test.ts +0 -87
- package/src/error/parser/security-error-parser.ts +0 -138
- package/src/index.ts +0 -24
- package/src/locales/en/strings.ts +0 -67
- package/src/locales/index.ts +0 -2
- package/src/locales/ru/strings.ts +0 -35
- package/src/mock/di/security-component-mock.ts +0 -35
- package/src/mock/domain/model/open-security-settings-mock.ts +0 -52
- package/src/mock/network/model/open-security-settings-payload-mock.ts +0 -28
- package/src/mock/network/open-security-settings-api-mock.ts +0 -38
- package/src/mock/repository/open-security-settings-repository-mock.ts +0 -62
- package/src/mock/storage/open-security-settings-storage-mock.ts +0 -45
- package/src/mock/usecase/validate-password-use-case-mock.ts +0 -31
- package/src/network/contract/security-web-socket-event-types.ts +0 -7
- package/src/network/security-settings/fetch-open-security-settings-api.test.ts +0 -51
- package/src/network/security-settings/fetch-open-security-settings-api.ts +0 -30
- package/src/network/security-settings/open-security-settings-api.ts +0 -15
- package/src/network/websocket/message-handler/security-web-socket-message-handler.test.ts +0 -19
- package/src/network/websocket/message-handler/security-web-socket-message-handler.ts +0 -19
- package/src/repository/open-security-settings-repository-impl.test.ts +0 -112
- package/src/repository/open-security-settings-repository-impl.ts +0 -151
- package/src/repository/open-security-settings-repository.ts +0 -39
- package/src/storage/security-settings/encrypted-open-security-settings-storage.test.ts +0 -71
- package/src/storage/security-settings/encrypted-open-security-settings-storage.ts +0 -65
- package/src/storage/security-settings/open-security-settings-storage.ts +0 -25
- package/src/usecase/refresh-open-security-settings-use-case.ts +0 -26
- package/src/usecase/security-use-cases.test.ts +0 -67
- package/src/usecase/validate-password-use-case.ts +0 -76
- package/tsconfig.json +0 -16
- package/vite.config.ts +0 -50
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
HttpClient,
|
|
4
|
-
DeviceInfoProviderMock,
|
|
5
|
-
isSuccess
|
|
6
|
-
} from '@mudrichenkoevgeny/web-platform-sdk-core-common'
|
|
7
|
-
import { FetchOpenSecuritySettingsApi } from '@/network/security-settings/fetch-open-security-settings-api'
|
|
8
|
-
import type { OpenSecuritySettingsPayload } from '@mudrichenkoevgeny/shared-foundation'
|
|
9
|
-
|
|
10
|
-
describe('FetchOpenSecuritySettingsApi', () => {
|
|
11
|
-
const samplePayload: OpenSecuritySettingsPayload = {
|
|
12
|
-
open_password_policy: {
|
|
13
|
-
min_length: 8,
|
|
14
|
-
require_letter: true,
|
|
15
|
-
require_upper_case: true,
|
|
16
|
-
require_lower_case: true,
|
|
17
|
-
require_digit: true,
|
|
18
|
-
require_special_char: false
|
|
19
|
-
},
|
|
20
|
-
otp_confirmation: {
|
|
21
|
-
retry_after_seconds: 60,
|
|
22
|
-
number_of_symbols: 6,
|
|
23
|
-
expiration_seconds: 300
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
it('fetches open security settings successfully', async () => {
|
|
28
|
-
const mockResponse = new Response(JSON.stringify(samplePayload), { status: 200 })
|
|
29
|
-
const customFetch = vi.fn().mockResolvedValue(mockResponse)
|
|
30
|
-
const deviceInfoProvider = new DeviceInfoProviderMock()
|
|
31
|
-
|
|
32
|
-
const httpClient = new HttpClient({
|
|
33
|
-
baseUrl: 'https://api.example.com',
|
|
34
|
-
deviceInfoProvider,
|
|
35
|
-
customFetch
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
const api = new FetchOpenSecuritySettingsApi(httpClient)
|
|
39
|
-
const result = await api.getSecuritySettings()
|
|
40
|
-
|
|
41
|
-
expect(isSuccess(result)).toBe(true)
|
|
42
|
-
if (isSuccess(result)) {
|
|
43
|
-
expect(result.data).toEqual(samplePayload)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
expect(customFetch).toHaveBeenCalledWith(
|
|
47
|
-
'https://api.example.com/security/settings',
|
|
48
|
-
expect.any(Object)
|
|
49
|
-
)
|
|
50
|
-
})
|
|
51
|
-
})
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { OpenSecuritySettingsRoutes } from '@mudrichenkoevgeny/shared-foundation'
|
|
2
|
-
import type { OpenSecuritySettingsPayload } from '@mudrichenkoevgeny/shared-foundation'
|
|
3
|
-
import { callResult } from '@mudrichenkoevgeny/web-platform-sdk-core-common'
|
|
4
|
-
import type { HttpClient, AppResult, AppError } from "@mudrichenkoevgeny/web-platform-sdk-core-common";
|
|
5
|
-
import type { OpenSecuritySettingsApi } from '@/network/security-settings/open-security-settings-api'
|
|
6
|
-
/**
|
|
7
|
-
* Fetch HTTP client implementation of {@link OpenSecuritySettingsApi}.
|
|
8
|
-
*/
|
|
9
|
-
export class FetchOpenSecuritySettingsApi implements OpenSecuritySettingsApi {
|
|
10
|
-
/**
|
|
11
|
-
* Constructs a new {@link FetchOpenSecuritySettingsApi}.
|
|
12
|
-
*
|
|
13
|
-
* @param httpClient - Configured SDK HTTP client instance
|
|
14
|
-
*/
|
|
15
|
-
public constructor(private readonly httpClient: HttpClient) {}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Executes HTTP GET request to retrieve open security settings payload.
|
|
19
|
-
*
|
|
20
|
-
* @returns AppResult wrapping {@link OpenSecuritySettingsPayload}
|
|
21
|
-
*/
|
|
22
|
-
public async getSecuritySettings(): Promise<AppResult<OpenSecuritySettingsPayload, AppError>> {
|
|
23
|
-
return callResult(() =>
|
|
24
|
-
this.httpClient.request<OpenSecuritySettingsPayload>(
|
|
25
|
-
OpenSecuritySettingsRoutes.GET_OPEN_SECURITY_SETTINGS,
|
|
26
|
-
{ method: 'GET' }
|
|
27
|
-
)
|
|
28
|
-
)
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { OpenSecuritySettingsPayload } from '@mudrichenkoevgeny/shared-foundation'
|
|
2
|
-
import type { AppError } from '@mudrichenkoevgeny/web-platform-sdk-core-common'
|
|
3
|
-
import type { AppResult } from "@mudrichenkoevgeny/web-platform-sdk-core-common";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Direct HTTP REST API client interface for fetching open security settings.
|
|
7
|
-
*/
|
|
8
|
-
export interface OpenSecuritySettingsApi {
|
|
9
|
-
/**
|
|
10
|
-
* Fetches active open security settings payload from the server.
|
|
11
|
-
*
|
|
12
|
-
* @returns AppResult containing {@link OpenSecuritySettingsPayload} on success or {@link AppError} on failure
|
|
13
|
-
*/
|
|
14
|
-
getSecuritySettings(): Promise<AppResult<OpenSecuritySettingsPayload, AppError>>
|
|
15
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import type { SocketFrame } from '@mudrichenkoevgeny/web-platform-sdk-core-common'
|
|
3
|
-
import { SecurityWebSocketMessageHandler } from '@/network/websocket/message-handler/security-web-socket-message-handler'
|
|
4
|
-
|
|
5
|
-
describe('SecurityWebSocketMessageHandler', () => {
|
|
6
|
-
it('returns NotHandled for unhandled frames', () => {
|
|
7
|
-
const handler = new SecurityWebSocketMessageHandler()
|
|
8
|
-
const frame: SocketFrame = {
|
|
9
|
-
id: 'frame-1',
|
|
10
|
-
type: 'UNKNOWN_TYPE',
|
|
11
|
-
payload: null,
|
|
12
|
-
metadata: {},
|
|
13
|
-
timestamp: Date.now()
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const result = handler.handle(frame)
|
|
17
|
-
expect(result.kind).toBe('NotHandled')
|
|
18
|
-
})
|
|
19
|
-
})
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
webSocketMessageHandlerResultNotHandled
|
|
3
|
-
} from '@mudrichenkoevgeny/web-platform-sdk-core-common'
|
|
4
|
-
import type { WebSocketMessageHandler, WebSocketMessageHandlerResult, SocketFrame } from "@mudrichenkoevgeny/web-platform-sdk-core-common";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* WebSocket message handler for security domain frame types.
|
|
8
|
-
*/
|
|
9
|
-
export class SecurityWebSocketMessageHandler implements WebSocketMessageHandler {
|
|
10
|
-
/**
|
|
11
|
-
* Processes incoming security WebSocket message frame.
|
|
12
|
-
*
|
|
13
|
-
* @param frame - Incoming socket frame
|
|
14
|
-
* @returns {@link WebSocketMessageHandlerResult}
|
|
15
|
-
*/
|
|
16
|
-
public handle(_frame: SocketFrame): WebSocketMessageHandlerResult {
|
|
17
|
-
return webSocketMessageHandlerResultNotHandled()
|
|
18
|
-
}
|
|
19
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
-
import { WebSocketServiceMock, EncryptedSettingsMock } from '@mudrichenkoevgeny/web-platform-sdk-core-common'
|
|
3
|
-
import type { AppResult, AppError } from "@mudrichenkoevgeny/web-platform-sdk-core-common";
|
|
4
|
-
import type { OpenSecuritySettingsPayload } from '@mudrichenkoevgeny/shared-foundation'
|
|
5
|
-
import { OpenSecuritySettingsRepositoryImpl } from '@/repository/open-security-settings-repository-impl'
|
|
6
|
-
import type { OpenSecuritySettingsApi } from '@/network/security-settings/open-security-settings-api'
|
|
7
|
-
import { EncryptedOpenSecuritySettingsStorage } from '@/storage/security-settings/encrypted-open-security-settings-storage'
|
|
8
|
-
import { toOpenSecuritySettings } from '@/domain/model/open-security-settings'
|
|
9
|
-
import { SecurityWebSocketEventTypes } from '@/network/contract/security-web-socket-event-types'
|
|
10
|
-
describe('OpenSecuritySettingsRepositoryImpl', () => {
|
|
11
|
-
const samplePayload: OpenSecuritySettingsPayload = {
|
|
12
|
-
open_password_policy: {
|
|
13
|
-
min_length: 8,
|
|
14
|
-
require_letter: true,
|
|
15
|
-
require_upper_case: true,
|
|
16
|
-
require_lower_case: true,
|
|
17
|
-
require_digit: true,
|
|
18
|
-
require_special_char: false
|
|
19
|
-
},
|
|
20
|
-
otp_confirmation: {
|
|
21
|
-
retry_after_seconds: 60,
|
|
22
|
-
number_of_symbols: 6,
|
|
23
|
-
expiration_seconds: 300
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const sampleSettings = toOpenSecuritySettings(samplePayload)
|
|
28
|
-
|
|
29
|
-
class MockApi implements OpenSecuritySettingsApi {
|
|
30
|
-
public getSecuritySettingsCallCount = 0
|
|
31
|
-
public async getSecuritySettings(): Promise<AppResult<OpenSecuritySettingsPayload, AppError>> {
|
|
32
|
-
this.getSecuritySettingsCallCount++
|
|
33
|
-
return { success: true, data: samplePayload }
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
it('loads from network if cache is empty', async () => {
|
|
38
|
-
const api = new MockApi()
|
|
39
|
-
const storage = new EncryptedOpenSecuritySettingsStorage(new EncryptedSettingsMock())
|
|
40
|
-
const ws = new WebSocketServiceMock()
|
|
41
|
-
|
|
42
|
-
const repo = new OpenSecuritySettingsRepositoryImpl(api, storage, ws)
|
|
43
|
-
const result = await repo.getOpenSecuritySettings()
|
|
44
|
-
|
|
45
|
-
expect(result.success).toBe(true)
|
|
46
|
-
if (result.success) {
|
|
47
|
-
expect(result.data).toEqual(sampleSettings)
|
|
48
|
-
}
|
|
49
|
-
expect(api.getSecuritySettingsCallCount).toBe(1)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('loads from storage without calling network if cache exists', async () => {
|
|
53
|
-
const api = new MockApi()
|
|
54
|
-
const storage = new EncryptedOpenSecuritySettingsStorage(new EncryptedSettingsMock())
|
|
55
|
-
await storage.updateOpenSecuritySettings(sampleSettings)
|
|
56
|
-
const ws = new WebSocketServiceMock()
|
|
57
|
-
|
|
58
|
-
const repo = new OpenSecuritySettingsRepositoryImpl(api, storage, ws)
|
|
59
|
-
const result = await repo.getOpenSecuritySettings()
|
|
60
|
-
|
|
61
|
-
expect(result.success).toBe(true)
|
|
62
|
-
expect(api.getSecuritySettingsCallCount).toBe(0)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('forces network refresh when refreshOpenSecuritySettings is called', async () => {
|
|
66
|
-
const api = new MockApi()
|
|
67
|
-
const storage = new EncryptedOpenSecuritySettingsStorage(new EncryptedSettingsMock())
|
|
68
|
-
await storage.updateOpenSecuritySettings(sampleSettings)
|
|
69
|
-
const ws = new WebSocketServiceMock()
|
|
70
|
-
|
|
71
|
-
const repo = new OpenSecuritySettingsRepositoryImpl(api, storage, ws)
|
|
72
|
-
await repo.refreshOpenSecuritySettings()
|
|
73
|
-
|
|
74
|
-
expect(api.getSecuritySettingsCallCount).toBe(1)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('updates state and storage when WebSocket update is received', async () => {
|
|
78
|
-
const api = new MockApi()
|
|
79
|
-
const storage = new EncryptedOpenSecuritySettingsStorage(new EncryptedSettingsMock())
|
|
80
|
-
const ws = new WebSocketServiceMock()
|
|
81
|
-
|
|
82
|
-
const repo = new OpenSecuritySettingsRepositoryImpl(api, storage, ws)
|
|
83
|
-
|
|
84
|
-
const observer = vi.fn()
|
|
85
|
-
repo.observeOpenSecuritySettings(observer)
|
|
86
|
-
|
|
87
|
-
// Wait for potential preload to finish so observer stabilizes
|
|
88
|
-
await repo.getOpenSecuritySettings()
|
|
89
|
-
|
|
90
|
-
const wsPayload: OpenSecuritySettingsPayload = {
|
|
91
|
-
open_password_policy: { ...samplePayload.open_password_policy, min_length: 12 },
|
|
92
|
-
otp_confirmation: samplePayload.otp_confirmation
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
ws.emitFrameLocally({
|
|
96
|
-
id: 'frame1',
|
|
97
|
-
type: SecurityWebSocketEventTypes.SECURITY_SETTINGS_UPDATED,
|
|
98
|
-
payload: wsPayload,
|
|
99
|
-
timestamp: Date.now()
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
const updatedSettings = await storage.getOpenSecuritySettings()
|
|
103
|
-
expect(updatedSettings?.openPasswordPolicy.minLength).toBe(12)
|
|
104
|
-
|
|
105
|
-
const currentResult = await repo.getOpenSecuritySettings()
|
|
106
|
-
if (currentResult.success) {
|
|
107
|
-
expect(currentResult.data.openPasswordPolicy.minLength).toBe(12)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
expect(observer).toHaveBeenCalled()
|
|
111
|
-
})
|
|
112
|
-
})
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { mapSuccess } from '@mudrichenkoevgeny/web-platform-sdk-core-common'
|
|
2
|
-
import type { AppResult, AppError, WebSocketService, SocketFrame } from "@mudrichenkoevgeny/web-platform-sdk-core-common";
|
|
3
|
-
import { openSecuritySettingsPayloadSchema } from '@mudrichenkoevgeny/shared-foundation'
|
|
4
|
-
import { toOpenSecuritySettings } from '@/domain/model/open-security-settings'
|
|
5
|
-
import type { OpenSecuritySettings } from '@/domain/model/open-security-settings'
|
|
6
|
-
import type { OpenSecuritySettingsRepository } from '@/repository/open-security-settings-repository'
|
|
7
|
-
import type { OpenSecuritySettingsObserver } from '@/repository/open-security-settings-repository'
|
|
8
|
-
import type { OpenSecuritySettingsApi } from '@/network/security-settings/open-security-settings-api'
|
|
9
|
-
import type { OpenSecuritySettingsStorage } from '@/storage/security-settings/open-security-settings-storage'
|
|
10
|
-
import { SecurityWebSocketEventTypes } from '@/network/contract/security-web-socket-event-types'
|
|
11
|
-
/**
|
|
12
|
-
* Implementation of {@link OpenSecuritySettingsRepository} handling API fetch, encrypted storage, and WebSocket updates.
|
|
13
|
-
*/
|
|
14
|
-
export class OpenSecuritySettingsRepositoryImpl implements OpenSecuritySettingsRepository {
|
|
15
|
-
private inMemorySettings: OpenSecuritySettings | null = null
|
|
16
|
-
private readonly observers = new Set<OpenSecuritySettingsObserver>()
|
|
17
|
-
private initializationPromise: Promise<void> | null = null
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Constructs a new {@link OpenSecuritySettingsRepositoryImpl}.
|
|
21
|
-
*
|
|
22
|
-
* @param api - REST API client for open security settings
|
|
23
|
-
* @param storage - Encrypted storage backend for settings persistence
|
|
24
|
-
* @param webSocketService - WebSocket client for receiving real-time setting updates
|
|
25
|
-
* @param logger - Optional diagnostic logger
|
|
26
|
-
*/
|
|
27
|
-
public constructor(
|
|
28
|
-
private readonly api: OpenSecuritySettingsApi,
|
|
29
|
-
private readonly storage: OpenSecuritySettingsStorage,
|
|
30
|
-
private readonly webSocketService: WebSocketService,
|
|
31
|
-
private readonly logger?: (msg: string) => void
|
|
32
|
-
) {
|
|
33
|
-
this.startWebSocketObservation()
|
|
34
|
-
this.initializationPromise = this.preloadCache()
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Retrieves security settings from memory, storage, or network.
|
|
39
|
-
*
|
|
40
|
-
* @returns Resolved security settings result
|
|
41
|
-
*/
|
|
42
|
-
public async getOpenSecuritySettings(): Promise<AppResult<OpenSecuritySettings, AppError>> {
|
|
43
|
-
if (this.inMemorySettings) {
|
|
44
|
-
return { success: true, data: this.inMemorySettings }
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (this.initializationPromise) {
|
|
48
|
-
await this.initializationPromise
|
|
49
|
-
if (this.inMemorySettings) {
|
|
50
|
-
return { success: true, data: this.inMemorySettings }
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return this.refreshOpenSecuritySettingsInternal()
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Forces a refresh from the network and persists result.
|
|
59
|
-
*
|
|
60
|
-
* @returns Updated security settings result
|
|
61
|
-
*/
|
|
62
|
-
public async refreshOpenSecuritySettings(): Promise<AppResult<OpenSecuritySettings, AppError>> {
|
|
63
|
-
return this.refreshOpenSecuritySettingsInternal()
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Overwrites the active security settings (e.g. from WebSocket update).
|
|
68
|
-
*
|
|
69
|
-
* @param securitySettings - New active settings
|
|
70
|
-
*/
|
|
71
|
-
public async updateOpenSecuritySettings(securitySettings: OpenSecuritySettings): Promise<void> {
|
|
72
|
-
await this.applySettingsUpdate(securitySettings)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Subscribes to updates of the security settings.
|
|
77
|
-
*
|
|
78
|
-
* @param observer - Callback
|
|
79
|
-
* @returns Unsubscribe function
|
|
80
|
-
*/
|
|
81
|
-
public observeOpenSecuritySettings(observer: OpenSecuritySettingsObserver): () => void {
|
|
82
|
-
this.observers.add(observer)
|
|
83
|
-
observer(this.inMemorySettings)
|
|
84
|
-
return () => {
|
|
85
|
-
this.observers.delete(observer)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
private async preloadCache(): Promise<void> {
|
|
90
|
-
try {
|
|
91
|
-
const cached = await this.storage.getOpenSecuritySettings()
|
|
92
|
-
if (cached && !this.inMemorySettings) {
|
|
93
|
-
this.inMemorySettings = cached
|
|
94
|
-
this.notifyObservers(cached)
|
|
95
|
-
}
|
|
96
|
-
} catch (e) {
|
|
97
|
-
this.logger?.(`OpenSecuritySettingsRepositoryImpl: Preload failed - ${String(e)}`)
|
|
98
|
-
} finally {
|
|
99
|
-
this.initializationPromise = null
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
private async refreshOpenSecuritySettingsInternal(): Promise<AppResult<OpenSecuritySettings, AppError>> {
|
|
104
|
-
const response = await this.api.getSecuritySettings()
|
|
105
|
-
return mapSuccess(response, (payload) => {
|
|
106
|
-
const settings = toOpenSecuritySettings(payload)
|
|
107
|
-
void this.applySettingsUpdate(settings)
|
|
108
|
-
return settings
|
|
109
|
-
})
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
private async applySettingsUpdate(settings: OpenSecuritySettings): Promise<void> {
|
|
113
|
-
this.inMemorySettings = settings
|
|
114
|
-
this.notifyObservers(settings)
|
|
115
|
-
try {
|
|
116
|
-
await this.storage.updateOpenSecuritySettings(settings)
|
|
117
|
-
} catch (e) {
|
|
118
|
-
this.logger?.(`OpenSecuritySettingsRepositoryImpl: Storage update failed - ${String(e)}`)
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
private notifyObservers(settings: OpenSecuritySettings | null): void {
|
|
123
|
-
for (const observer of this.observers) {
|
|
124
|
-
try {
|
|
125
|
-
observer(settings)
|
|
126
|
-
} catch (e) {
|
|
127
|
-
this.logger?.(`OpenSecuritySettingsRepositoryImpl: Observer threw error - ${String(e)}`)
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
private startWebSocketObservation(): void {
|
|
133
|
-
this.webSocketService.observeEvents((frame: SocketFrame) => {
|
|
134
|
-
if (frame.type === SecurityWebSocketEventTypes.SECURITY_SETTINGS_UPDATED) {
|
|
135
|
-
try {
|
|
136
|
-
if (frame.payload) {
|
|
137
|
-
const validationResult = openSecuritySettingsPayloadSchema.safeParse(frame.payload)
|
|
138
|
-
if (validationResult.success) {
|
|
139
|
-
const settings = toOpenSecuritySettings(validationResult.data)
|
|
140
|
-
void this.applySettingsUpdate(settings)
|
|
141
|
-
} else {
|
|
142
|
-
this.logger?.('OpenSecuritySettingsRepositoryImpl: Invalid WS payload schema')
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
} catch (e) {
|
|
146
|
-
this.logger?.(`OpenSecuritySettingsRepositoryImpl: WS payload parsing failed - ${String(e)}`)
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
})
|
|
150
|
-
}
|
|
151
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import type { AppResult, AppError } from '@mudrichenkoevgeny/web-platform-sdk-core-common'
|
|
2
|
-
import type { OpenSecuritySettings } from '@/domain/model/open-security-settings'
|
|
3
|
-
|
|
4
|
-
/** Observer callback for security settings changes. */
|
|
5
|
-
export type OpenSecuritySettingsObserver = (settings: OpenSecuritySettings | null) => void
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Repository interface for retrieving, updating, and observing open security settings.
|
|
9
|
-
*/
|
|
10
|
-
export interface OpenSecuritySettingsRepository {
|
|
11
|
-
/**
|
|
12
|
-
* Retrieves in-memory or persisted settings. If unavailable, fetches from network.
|
|
13
|
-
*
|
|
14
|
-
* @returns AppResult containing current settings or error
|
|
15
|
-
*/
|
|
16
|
-
getOpenSecuritySettings(): Promise<AppResult<OpenSecuritySettings, AppError>>
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Forces a network refresh of the open security settings and persists them.
|
|
20
|
-
*
|
|
21
|
-
* @returns AppResult containing updated settings or error
|
|
22
|
-
*/
|
|
23
|
-
refreshOpenSecuritySettings(): Promise<AppResult<OpenSecuritySettings, AppError>>
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Applies an external update to the settings and persists them.
|
|
27
|
-
*
|
|
28
|
-
* @param securitySettings - New settings snapshot
|
|
29
|
-
*/
|
|
30
|
-
updateOpenSecuritySettings(securitySettings: OpenSecuritySettings): Promise<void>
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Registers an observer for live updates to the security settings.
|
|
34
|
-
*
|
|
35
|
-
* @param observer - Callback receiving updated settings or null
|
|
36
|
-
* @returns Unsubscribe function
|
|
37
|
-
*/
|
|
38
|
-
observeOpenSecuritySettings(observer: OpenSecuritySettingsObserver): () => void
|
|
39
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { EncryptedSettingsMock } from '@mudrichenkoevgeny/web-platform-sdk-core-common'
|
|
3
|
-
import { EncryptedOpenSecuritySettingsStorage } from '@/storage/security-settings/encrypted-open-security-settings-storage'
|
|
4
|
-
import type { OpenSecuritySettings } from '@/domain/model/open-security-settings'
|
|
5
|
-
|
|
6
|
-
describe('EncryptedOpenSecuritySettingsStorage', () => {
|
|
7
|
-
const sampleSettings: OpenSecuritySettings = {
|
|
8
|
-
openPasswordPolicy: {
|
|
9
|
-
minLength: 8,
|
|
10
|
-
requireLetter: true,
|
|
11
|
-
requireUpperCase: true,
|
|
12
|
-
requireLowerCase: true,
|
|
13
|
-
requireDigit: true,
|
|
14
|
-
requireSpecialChar: false
|
|
15
|
-
},
|
|
16
|
-
otpConfirmation: {
|
|
17
|
-
retryAfterSeconds: 60,
|
|
18
|
-
numberOfSymbols: 6,
|
|
19
|
-
expirationSeconds: 300
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
it('returns null when settings are not present in storage', async () => {
|
|
24
|
-
const encryptedSettings = new EncryptedSettingsMock()
|
|
25
|
-
const storage = new EncryptedOpenSecuritySettingsStorage(encryptedSettings)
|
|
26
|
-
|
|
27
|
-
const result = await storage.getOpenSecuritySettings()
|
|
28
|
-
expect(result).toBeNull()
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('updates and retrieves settings correctly', async () => {
|
|
32
|
-
const encryptedSettings = new EncryptedSettingsMock()
|
|
33
|
-
const storage = new EncryptedOpenSecuritySettingsStorage(encryptedSettings)
|
|
34
|
-
|
|
35
|
-
await storage.updateOpenSecuritySettings(sampleSettings)
|
|
36
|
-
const result = await storage.getOpenSecuritySettings()
|
|
37
|
-
|
|
38
|
-
expect(result).toEqual(sampleSettings)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('clears persisted settings', async () => {
|
|
42
|
-
const encryptedSettings = new EncryptedSettingsMock()
|
|
43
|
-
const storage = new EncryptedOpenSecuritySettingsStorage(encryptedSettings)
|
|
44
|
-
|
|
45
|
-
await storage.updateOpenSecuritySettings(sampleSettings)
|
|
46
|
-
await storage.clearOpenSecuritySettings()
|
|
47
|
-
|
|
48
|
-
const result = await storage.getOpenSecuritySettings()
|
|
49
|
-
expect(result).toBeNull()
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('handles invalid stored JSON gracefully and cleans up entry', async () => {
|
|
53
|
-
const encryptedSettings = new EncryptedSettingsMock()
|
|
54
|
-
await encryptedSettings.put('security_settings', 'invalid-json-{')
|
|
55
|
-
const storage = new EncryptedOpenSecuritySettingsStorage(encryptedSettings)
|
|
56
|
-
|
|
57
|
-
const result = await storage.getOpenSecuritySettings()
|
|
58
|
-
expect(result).toBeNull()
|
|
59
|
-
expect(await encryptedSettings.get('security_settings')).toBeNull()
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
it('handles schema-mismatched JSON gracefully and cleans up entry', async () => {
|
|
63
|
-
const encryptedSettings = new EncryptedSettingsMock()
|
|
64
|
-
await encryptedSettings.put('security_settings', JSON.stringify({ unknown_field: 123 }))
|
|
65
|
-
const storage = new EncryptedOpenSecuritySettingsStorage(encryptedSettings)
|
|
66
|
-
|
|
67
|
-
const result = await storage.getOpenSecuritySettings()
|
|
68
|
-
expect(result).toBeNull()
|
|
69
|
-
expect(await encryptedSettings.get('security_settings')).toBeNull()
|
|
70
|
-
})
|
|
71
|
-
})
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { openSecuritySettingsPayloadSchema } from '@mudrichenkoevgeny/shared-foundation'
|
|
2
|
-
import type { EncryptedSettings } from '@mudrichenkoevgeny/web-platform-sdk-core-common'
|
|
3
|
-
import type { OpenSecuritySettingsStorage } from '@/storage/security-settings/open-security-settings-storage'
|
|
4
|
-
import {
|
|
5
|
-
toOpenSecuritySettings,
|
|
6
|
-
toOpenSecuritySettingsPayload
|
|
7
|
-
} from '@/domain/model/open-security-settings'
|
|
8
|
-
import type { OpenSecuritySettings } from '@/domain/model/open-security-settings'
|
|
9
|
-
|
|
10
|
-
const KEY_SECURITY_SETTINGS = 'security_settings'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Storage implementation of {@link OpenSecuritySettingsStorage} backed by {@link EncryptedSettings}.
|
|
14
|
-
*/
|
|
15
|
-
export class EncryptedOpenSecuritySettingsStorage implements OpenSecuritySettingsStorage {
|
|
16
|
-
/**
|
|
17
|
-
* Constructs a new {@link EncryptedOpenSecuritySettingsStorage}.
|
|
18
|
-
*
|
|
19
|
-
* @param encryptedSettings - Encrypted settings key-value store instance
|
|
20
|
-
*/
|
|
21
|
-
public constructor(private readonly encryptedSettings: EncryptedSettings) {}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Retrieves and deserializes encrypted open security settings.
|
|
25
|
-
*
|
|
26
|
-
* @returns Deserialized settings or null if unreadable or absent
|
|
27
|
-
*/
|
|
28
|
-
public async getOpenSecuritySettings(): Promise<OpenSecuritySettings | null> {
|
|
29
|
-
const rawData = await this.encryptedSettings.get(KEY_SECURITY_SETTINGS)
|
|
30
|
-
if (!rawData) {
|
|
31
|
-
return null
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
const parsedJson = JSON.parse(rawData)
|
|
36
|
-
const validationResult = openSecuritySettingsPayloadSchema.safeParse(parsedJson)
|
|
37
|
-
if (!validationResult.success) {
|
|
38
|
-
await this.encryptedSettings.remove(KEY_SECURITY_SETTINGS)
|
|
39
|
-
return null
|
|
40
|
-
}
|
|
41
|
-
return toOpenSecuritySettings(validationResult.data)
|
|
42
|
-
} catch {
|
|
43
|
-
await this.encryptedSettings.remove(KEY_SECURITY_SETTINGS)
|
|
44
|
-
return null
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Serializes and persists open security settings.
|
|
50
|
-
*
|
|
51
|
-
* @param securitySettings - Security settings snapshot to store
|
|
52
|
-
*/
|
|
53
|
-
public async updateOpenSecuritySettings(securitySettings: OpenSecuritySettings): Promise<void> {
|
|
54
|
-
const payload = toOpenSecuritySettingsPayload(securitySettings)
|
|
55
|
-
const jsonString = JSON.stringify(payload)
|
|
56
|
-
await this.encryptedSettings.put(KEY_SECURITY_SETTINGS, jsonString)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Removes persisted open security settings from storage.
|
|
61
|
-
*/
|
|
62
|
-
public async clearOpenSecuritySettings(): Promise<void> {
|
|
63
|
-
await this.encryptedSettings.remove(KEY_SECURITY_SETTINGS)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { OpenSecuritySettings } from '@/domain/model/open-security-settings'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Persistence port for {@link OpenSecuritySettings} storage implementations.
|
|
5
|
-
*/
|
|
6
|
-
export interface OpenSecuritySettingsStorage {
|
|
7
|
-
/**
|
|
8
|
-
* Retrieves the stored {@link OpenSecuritySettings}, or null if none are persisted yet.
|
|
9
|
-
*
|
|
10
|
-
* @returns Promise resolving to stored settings or null
|
|
11
|
-
*/
|
|
12
|
-
getOpenSecuritySettings(): Promise<OpenSecuritySettings | null>
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Persists a new snapshot of {@link OpenSecuritySettings}.
|
|
16
|
-
*
|
|
17
|
-
* @param securitySettings - Snapshot to serialize and persist
|
|
18
|
-
*/
|
|
19
|
-
updateOpenSecuritySettings(securitySettings: OpenSecuritySettings): Promise<void>
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Removes persisted open security settings from storage, if present.
|
|
23
|
-
*/
|
|
24
|
-
clearOpenSecuritySettings(): Promise<void>
|
|
25
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { AppResult, AppError } from '@mudrichenkoevgeny/web-platform-sdk-core-common'
|
|
2
|
-
import type { OpenSecuritySettings } from '@/domain/model/open-security-settings'
|
|
3
|
-
import type { OpenSecuritySettingsRepository } from '@/repository/open-security-settings-repository'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Use case that delegates to {@link OpenSecuritySettingsRepository.refreshOpenSecuritySettings}.
|
|
7
|
-
*/
|
|
8
|
-
export class RefreshOpenSecuritySettingsUseCase {
|
|
9
|
-
/**
|
|
10
|
-
* Constructs a new {@link RefreshOpenSecuritySettingsUseCase}.
|
|
11
|
-
*
|
|
12
|
-
* @param openSecuritySettingsRepository - Repository performing the network refresh and persistence
|
|
13
|
-
*/
|
|
14
|
-
public constructor(
|
|
15
|
-
private readonly openSecuritySettingsRepository: OpenSecuritySettingsRepository
|
|
16
|
-
) {}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Executes the refresh operation.
|
|
20
|
-
*
|
|
21
|
-
* @returns Promise resolving to AppResult with refreshed settings or error
|
|
22
|
-
*/
|
|
23
|
-
public async execute(): Promise<AppResult<OpenSecuritySettings, AppError>> {
|
|
24
|
-
return this.openSecuritySettingsRepository.refreshOpenSecuritySettings()
|
|
25
|
-
}
|
|
26
|
-
}
|