@mudrichenkoevgeny/web-platform-sdk-core-security 0.0.1 → 0.1.0
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 +12255 -5155
- 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,33 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
WebSocketServiceMock,
|
|
4
|
-
HttpClient,
|
|
5
|
-
EncryptedSettingsMock,
|
|
6
|
-
DeviceInfoProviderMock
|
|
7
|
-
} from '@mudrichenkoevgeny/web-platform-sdk-core-common'
|
|
8
|
-
import { SecurityComponent } from '@/di/security-component'
|
|
9
|
-
|
|
10
|
-
describe('SecurityComponent', () => {
|
|
11
|
-
it('initializes all modules and exposes dependencies', () => {
|
|
12
|
-
const ws = new WebSocketServiceMock()
|
|
13
|
-
const httpClient = new HttpClient({
|
|
14
|
-
baseUrl: 'https://api.example.com',
|
|
15
|
-
deviceInfoProvider: new DeviceInfoProviderMock()
|
|
16
|
-
})
|
|
17
|
-
const encryptedSettings = new EncryptedSettingsMock()
|
|
18
|
-
|
|
19
|
-
const component = new SecurityComponent({
|
|
20
|
-
webSocketService: ws,
|
|
21
|
-
httpClient,
|
|
22
|
-
encryptedSettings
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
expect(component.securitySettingsStorage).toBeDefined()
|
|
26
|
-
expect(component.openSecuritySettingsApi).toBeDefined()
|
|
27
|
-
expect(component.securitySettingsRepository).toBeDefined()
|
|
28
|
-
expect(component.passwordPolicyValidator).toBeDefined()
|
|
29
|
-
expect(component.refreshSecuritySettingsUseCase).toBeDefined()
|
|
30
|
-
expect(component.validatePasswordUseCase).toBeDefined()
|
|
31
|
-
expect(component.securityWebSocketMessageHandler).toBeDefined()
|
|
32
|
-
})
|
|
33
|
-
})
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import type { EncryptedSettings, HttpClient, WebSocketService } from '@mudrichenkoevgeny/web-platform-sdk-core-common'
|
|
2
|
-
import type { OpenSecuritySettingsStorage } from '@/storage/security-settings/open-security-settings-storage'
|
|
3
|
-
import { EncryptedOpenSecuritySettingsStorage } from '@/storage/security-settings/encrypted-open-security-settings-storage'
|
|
4
|
-
import type { OpenSecuritySettingsApi } from '@/network/security-settings/open-security-settings-api'
|
|
5
|
-
import { FetchOpenSecuritySettingsApi } from '@/network/security-settings/fetch-open-security-settings-api'
|
|
6
|
-
import type { OpenSecuritySettingsRepository } from '@/repository/open-security-settings-repository'
|
|
7
|
-
import { OpenSecuritySettingsRepositoryImpl } from '@/repository/open-security-settings-repository-impl'
|
|
8
|
-
import { PasswordPolicyValidator } from '@/domain/model/password-policy-validator'
|
|
9
|
-
import { RefreshOpenSecuritySettingsUseCase } from '@/usecase/refresh-open-security-settings-use-case'
|
|
10
|
-
import { ValidatePasswordUseCase } from '@/usecase/validate-password-use-case'
|
|
11
|
-
import { SecurityWebSocketMessageHandler } from '@/network/websocket/message-handler/security-web-socket-message-handler'
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Configuration options required to instantiate {@link SecurityComponent}.
|
|
15
|
-
*/
|
|
16
|
-
export interface SecurityComponentConfig {
|
|
17
|
-
/** Global WebSocket service. */
|
|
18
|
-
webSocketService: WebSocketService
|
|
19
|
-
/** Pre-configured HTTP client for REST calls. */
|
|
20
|
-
httpClient: HttpClient
|
|
21
|
-
/** Encrypted storage backend. */
|
|
22
|
-
encryptedSettings: EncryptedSettings
|
|
23
|
-
/** Optional mock overrides for tests. */
|
|
24
|
-
openSecuritySettingsApi?: OpenSecuritySettingsApi
|
|
25
|
-
/** Optional mock overrides for tests. */
|
|
26
|
-
openSecuritySettingsStorage?: OpenSecuritySettingsStorage
|
|
27
|
-
/** Optional mock overrides for tests. */
|
|
28
|
-
passwordPolicyValidator?: PasswordPolicyValidator
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Root dependency injection container for `core-security` module.
|
|
33
|
-
*
|
|
34
|
-
* Assembles storage, networking, repositories, and password policy validation.
|
|
35
|
-
*/
|
|
36
|
-
export class SecurityComponent {
|
|
37
|
-
/** Core persistence interface for open security settings. */
|
|
38
|
-
public readonly securitySettingsStorage: OpenSecuritySettingsStorage
|
|
39
|
-
/** HTTP REST client API for open security settings. */
|
|
40
|
-
public readonly openSecuritySettingsApi: OpenSecuritySettingsApi
|
|
41
|
-
/** Central repository for open security settings. */
|
|
42
|
-
public readonly securitySettingsRepository: OpenSecuritySettingsRepository
|
|
43
|
-
/** Pure domain logic password policy validator. */
|
|
44
|
-
public readonly passwordPolicyValidator: PasswordPolicyValidator
|
|
45
|
-
/** Use case for forcing a network refresh of settings. */
|
|
46
|
-
public readonly refreshSecuritySettingsUseCase: RefreshOpenSecuritySettingsUseCase
|
|
47
|
-
/** Use case for validating password candidate strings against active policy. */
|
|
48
|
-
public readonly validatePasswordUseCase: ValidatePasswordUseCase
|
|
49
|
-
/** Handler offered to the shared WebSocket pipeline. */
|
|
50
|
-
public readonly securityWebSocketMessageHandler: SecurityWebSocketMessageHandler
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Constructs a new {@link SecurityComponent}.
|
|
54
|
-
*
|
|
55
|
-
* @param config - Container configuration holding global SDK dependencies
|
|
56
|
-
*/
|
|
57
|
-
public constructor(config: SecurityComponentConfig) {
|
|
58
|
-
this.securitySettingsStorage =
|
|
59
|
-
config.openSecuritySettingsStorage ??
|
|
60
|
-
new EncryptedOpenSecuritySettingsStorage(config.encryptedSettings)
|
|
61
|
-
|
|
62
|
-
this.passwordPolicyValidator =
|
|
63
|
-
config.passwordPolicyValidator ?? new PasswordPolicyValidator()
|
|
64
|
-
|
|
65
|
-
this.openSecuritySettingsApi =
|
|
66
|
-
config.openSecuritySettingsApi ??
|
|
67
|
-
new FetchOpenSecuritySettingsApi(config.httpClient)
|
|
68
|
-
|
|
69
|
-
this.securitySettingsRepository = new OpenSecuritySettingsRepositoryImpl(
|
|
70
|
-
this.openSecuritySettingsApi,
|
|
71
|
-
this.securitySettingsStorage,
|
|
72
|
-
config.webSocketService
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
this.refreshSecuritySettingsUseCase = new RefreshOpenSecuritySettingsUseCase(
|
|
76
|
-
this.securitySettingsRepository
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
this.validatePasswordUseCase = new ValidatePasswordUseCase(
|
|
80
|
-
this.securitySettingsRepository,
|
|
81
|
-
this.passwordPolicyValidator
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
this.securityWebSocketMessageHandler = new SecurityWebSocketMessageHandler()
|
|
85
|
-
}
|
|
86
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import type { OpenSecuritySettingsPayload } from '@mudrichenkoevgeny/shared-foundation'
|
|
2
|
-
|
|
3
|
-
/** Password policy requirement constraints. */
|
|
4
|
-
export interface OpenPasswordPolicy {
|
|
5
|
-
/** Minimum required password character length. */
|
|
6
|
-
readonly minLength: number
|
|
7
|
-
/** Whether password must contain at least one letter. */
|
|
8
|
-
readonly requireLetter: boolean
|
|
9
|
-
/** Whether password must contain at least one uppercase letter. */
|
|
10
|
-
readonly requireUpperCase: boolean
|
|
11
|
-
/** Whether password must contain at least one lowercase letter. */
|
|
12
|
-
readonly requireLowerCase: boolean
|
|
13
|
-
/** Whether password must contain at least one digit character. */
|
|
14
|
-
readonly requireDigit: boolean
|
|
15
|
-
/** Whether password must contain at least one special character. */
|
|
16
|
-
readonly requireSpecialChar: boolean
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** One-time password confirmation constraints. */
|
|
20
|
-
export interface OtpConfirmation {
|
|
21
|
-
/** Delay duration in seconds before requesting OTP resend. */
|
|
22
|
-
readonly retryAfterSeconds: number
|
|
23
|
-
/** Number of digit/symbol characters in OTP code. */
|
|
24
|
-
readonly numberOfSymbols: number
|
|
25
|
-
/** Validity duration in seconds before OTP code expires. */
|
|
26
|
-
readonly expirationSeconds: number
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Active open security settings domain model. */
|
|
30
|
-
export interface OpenSecuritySettings {
|
|
31
|
-
/** Password policy validation configuration. */
|
|
32
|
-
readonly openPasswordPolicy: OpenPasswordPolicy
|
|
33
|
-
/** OTP code confirmation parameters. */
|
|
34
|
-
readonly otpConfirmation: OtpConfirmation
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Maps an {@link OpenSecuritySettingsPayload} server response object into an {@link OpenSecuritySettings} domain model.
|
|
39
|
-
*
|
|
40
|
-
* @param payload - Raw API response payload
|
|
41
|
-
* @returns Mapped domain model
|
|
42
|
-
*/
|
|
43
|
-
export const toOpenSecuritySettings = (payload: OpenSecuritySettingsPayload): OpenSecuritySettings => ({
|
|
44
|
-
openPasswordPolicy: {
|
|
45
|
-
minLength: payload.open_password_policy.min_length,
|
|
46
|
-
requireLetter: payload.open_password_policy.require_letter,
|
|
47
|
-
requireUpperCase: payload.open_password_policy.require_upper_case,
|
|
48
|
-
requireLowerCase: payload.open_password_policy.require_lower_case,
|
|
49
|
-
requireDigit: payload.open_password_policy.require_digit,
|
|
50
|
-
requireSpecialChar: payload.open_password_policy.require_special_char
|
|
51
|
-
},
|
|
52
|
-
otpConfirmation: {
|
|
53
|
-
retryAfterSeconds: payload.otp_confirmation.retry_after_seconds,
|
|
54
|
-
numberOfSymbols: payload.otp_confirmation.number_of_symbols,
|
|
55
|
-
expirationSeconds: payload.otp_confirmation.expiration_seconds
|
|
56
|
-
}
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Maps an {@link OpenSecuritySettings} domain model into an {@link OpenSecuritySettingsPayload} server payload.
|
|
61
|
-
*
|
|
62
|
-
* @param settings - Security settings domain model
|
|
63
|
-
* @returns Serialized API payload
|
|
64
|
-
*/
|
|
65
|
-
export const toOpenSecuritySettingsPayload = (settings: OpenSecuritySettings): OpenSecuritySettingsPayload => ({
|
|
66
|
-
open_password_policy: {
|
|
67
|
-
min_length: settings.openPasswordPolicy.minLength,
|
|
68
|
-
require_letter: settings.openPasswordPolicy.requireLetter,
|
|
69
|
-
require_upper_case: settings.openPasswordPolicy.requireUpperCase,
|
|
70
|
-
require_lower_case: settings.openPasswordPolicy.requireLowerCase,
|
|
71
|
-
require_digit: settings.openPasswordPolicy.requireDigit,
|
|
72
|
-
require_special_char: settings.openPasswordPolicy.requireSpecialChar
|
|
73
|
-
},
|
|
74
|
-
otp_confirmation: {
|
|
75
|
-
retry_after_seconds: settings.otpConfirmation.retryAfterSeconds,
|
|
76
|
-
number_of_symbols: settings.otpConfirmation.numberOfSymbols,
|
|
77
|
-
expiration_seconds: settings.otpConfirmation.expirationSeconds
|
|
78
|
-
}
|
|
79
|
-
})
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { PasswordPolicyValidator } from '@/domain/model/password-policy-validator'
|
|
3
|
-
import type { OpenPasswordPolicy } from '@/domain/model/open-security-settings'
|
|
4
|
-
|
|
5
|
-
describe('PasswordPolicyValidator', () => {
|
|
6
|
-
const validator = new PasswordPolicyValidator()
|
|
7
|
-
|
|
8
|
-
const strictPolicy: OpenPasswordPolicy = {
|
|
9
|
-
minLength: 8,
|
|
10
|
-
requireLetter: true,
|
|
11
|
-
requireUpperCase: true,
|
|
12
|
-
requireLowerCase: true,
|
|
13
|
-
requireDigit: true,
|
|
14
|
-
requireSpecialChar: true
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const relaxedPolicy: OpenPasswordPolicy = {
|
|
18
|
-
minLength: 6,
|
|
19
|
-
requireLetter: false,
|
|
20
|
-
requireUpperCase: false,
|
|
21
|
-
requireLowerCase: false,
|
|
22
|
-
requireDigit: false,
|
|
23
|
-
requireSpecialChar: false
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
it('validates successful password', () => {
|
|
27
|
-
const result = validator.validate(strictPolicy, 'Valid123!')
|
|
28
|
-
expect(result.success).toBe(true)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('fails TOO_SHORT', () => {
|
|
32
|
-
const result = validator.validate(strictPolicy, 'Val1!')
|
|
33
|
-
expect(result.success).toBe(false)
|
|
34
|
-
if (!result.success) {
|
|
35
|
-
expect(result.reasons).toContain('TOO_SHORT')
|
|
36
|
-
}
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('fails NO_LETTER', () => {
|
|
40
|
-
const result = validator.validate(strictPolicy, '12345678!')
|
|
41
|
-
expect(result.success).toBe(false)
|
|
42
|
-
if (!result.success) {
|
|
43
|
-
expect(result.reasons).toContain('NO_LETTER')
|
|
44
|
-
expect(result.reasons).toContain('NO_UPPERCASE')
|
|
45
|
-
expect(result.reasons).toContain('NO_LOWERCASE')
|
|
46
|
-
}
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('fails NO_UPPERCASE', () => {
|
|
50
|
-
const result = validator.validate(strictPolicy, 'valid123!')
|
|
51
|
-
expect(result.success).toBe(false)
|
|
52
|
-
if (!result.success) {
|
|
53
|
-
expect(result.reasons).toContain('NO_UPPERCASE')
|
|
54
|
-
}
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('fails NO_LOWERCASE', () => {
|
|
58
|
-
const result = validator.validate(strictPolicy, 'VALID123!')
|
|
59
|
-
expect(result.success).toBe(false)
|
|
60
|
-
if (!result.success) {
|
|
61
|
-
expect(result.reasons).toContain('NO_LOWERCASE')
|
|
62
|
-
}
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('fails NO_DIGIT', () => {
|
|
66
|
-
const result = validator.validate(strictPolicy, 'ValidPassword!')
|
|
67
|
-
expect(result.success).toBe(false)
|
|
68
|
-
if (!result.success) {
|
|
69
|
-
expect(result.reasons).toContain('NO_DIGIT')
|
|
70
|
-
}
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
it('fails NO_SPECIAL_CHAR', () => {
|
|
74
|
-
const result = validator.validate(strictPolicy, 'Valid123')
|
|
75
|
-
expect(result.success).toBe(false)
|
|
76
|
-
if (!result.success) {
|
|
77
|
-
expect(result.reasons).toContain('NO_SPECIAL_CHAR')
|
|
78
|
-
}
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('passes relaxed policy with weak password', () => {
|
|
82
|
-
const result = validator.validate(relaxedPolicy, '123456')
|
|
83
|
-
expect(result.success).toBe(true)
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
it('returns multiple failure reasons simultaneously', () => {
|
|
87
|
-
const result = validator.validate(strictPolicy, 'abc')
|
|
88
|
-
expect(result.success).toBe(false)
|
|
89
|
-
if (!result.success) {
|
|
90
|
-
expect(result.reasons).toContain('TOO_SHORT')
|
|
91
|
-
expect(result.reasons).toContain('NO_UPPERCASE')
|
|
92
|
-
expect(result.reasons).toContain('NO_DIGIT')
|
|
93
|
-
expect(result.reasons).toContain('NO_SPECIAL_CHAR')
|
|
94
|
-
}
|
|
95
|
-
})
|
|
96
|
-
})
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import type { OpenPasswordPolicy } from '@/domain/model/open-security-settings'
|
|
2
|
-
|
|
3
|
-
/** Represents individual reasons why a password might fail policy validation. */
|
|
4
|
-
export type PasswordPolicyFailReason =
|
|
5
|
-
| 'TOO_SHORT'
|
|
6
|
-
| 'NO_LETTER'
|
|
7
|
-
| 'NO_UPPERCASE'
|
|
8
|
-
| 'NO_LOWERCASE'
|
|
9
|
-
| 'NO_DIGIT'
|
|
10
|
-
| 'NO_SPECIAL_CHAR'
|
|
11
|
-
| 'TOO_COMMON'
|
|
12
|
-
|
|
13
|
-
/** Result of a password policy validation check. */
|
|
14
|
-
export type PasswordPolicyValidatorResult =
|
|
15
|
-
| { readonly success: true }
|
|
16
|
-
| { readonly success: false; readonly reasons: PasswordPolicyFailReason[] }
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Validates a candidate password string against an {@link OpenPasswordPolicy}.
|
|
20
|
-
*/
|
|
21
|
-
export class PasswordPolicyValidator {
|
|
22
|
-
/**
|
|
23
|
-
* Evaluates a password string against the rules defined in a policy.
|
|
24
|
-
*
|
|
25
|
-
* @param policy - Target password policy
|
|
26
|
-
* @param password - Candidate string
|
|
27
|
-
* @returns Validation result indicating success or failure reasons
|
|
28
|
-
*/
|
|
29
|
-
public validate(policy: OpenPasswordPolicy, password: string): PasswordPolicyValidatorResult {
|
|
30
|
-
const reasons: PasswordPolicyFailReason[] = []
|
|
31
|
-
|
|
32
|
-
if (password.length < policy.minLength) {
|
|
33
|
-
reasons.push('TOO_SHORT')
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (policy.requireLetter && !/[a-zA-Z]/.test(password)) {
|
|
37
|
-
reasons.push('NO_LETTER')
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (policy.requireUpperCase && !/[A-Z]/.test(password)) {
|
|
41
|
-
reasons.push('NO_UPPERCASE')
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (policy.requireLowerCase && !/[a-z]/.test(password)) {
|
|
45
|
-
reasons.push('NO_LOWERCASE')
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (policy.requireDigit && !/[0-9]/.test(password)) {
|
|
49
|
-
reasons.push('NO_DIGIT')
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Special characters include standard symbols; adjust regex based on backend constraints if needed.
|
|
53
|
-
if (policy.requireSpecialChar && !/[^a-zA-Z0-9]/.test(password)) {
|
|
54
|
-
reasons.push('NO_SPECIAL_CHAR')
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (reasons.length > 0) {
|
|
58
|
-
return { success: false, reasons }
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return { success: true }
|
|
62
|
-
}
|
|
63
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { SecurityError } from '@/error/model/security-error'
|
|
3
|
-
import { ClientSecurityErrorCodes } from '@/error/naming/client-security-error-codes'
|
|
4
|
-
|
|
5
|
-
describe('SecurityError', () => {
|
|
6
|
-
it('creates passwordPolicyUnavailable error as a plain object', () => {
|
|
7
|
-
const error = SecurityError.passwordPolicyUnavailable()
|
|
8
|
-
expect(error.code).toBe(ClientSecurityErrorCodes.PASSWORD_POLICY_UNAVAILABLE)
|
|
9
|
-
expect(error.isRetryable).toBe(true)
|
|
10
|
-
expect(error.args).toBeNull()
|
|
11
|
-
expect(error.id).toBeDefined()
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
it('creates passwordTooShort error with arguments', () => {
|
|
15
|
-
const error = SecurityError.passwordTooShort(8)
|
|
16
|
-
expect(error.code).toBe(ClientSecurityErrorCodes.PASSWORD_TOO_SHORT)
|
|
17
|
-
expect(error.isRetryable).toBe(false)
|
|
18
|
-
expect(error.args).toEqual({ passwordMinLength: '8' })
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('creates passwordTooShort error without minLength', () => {
|
|
22
|
-
const error = SecurityError.passwordTooShort()
|
|
23
|
-
expect(error.args).toBeNull()
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('creates passwordNoLetter error', () => {
|
|
27
|
-
const error = SecurityError.passwordNoLetter()
|
|
28
|
-
expect(error.code).toBe(ClientSecurityErrorCodes.PASSWORD_NO_LETTER)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('creates passwordNoUpperCase error', () => {
|
|
32
|
-
const error = SecurityError.passwordNoUpperCase()
|
|
33
|
-
expect(error.code).toBe(ClientSecurityErrorCodes.PASSWORD_NO_UPPERCASE)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('creates passwordNoLowerCase error', () => {
|
|
37
|
-
const error = SecurityError.passwordNoLowerCase()
|
|
38
|
-
expect(error.code).toBe(ClientSecurityErrorCodes.PASSWORD_NO_LOWERCASE)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('creates passwordNoDigit error', () => {
|
|
42
|
-
const error = SecurityError.passwordNoDigit()
|
|
43
|
-
expect(error.code).toBe(ClientSecurityErrorCodes.PASSWORD_NO_DIGIT)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('creates passwordNoSpecialChar error', () => {
|
|
47
|
-
const error = SecurityError.passwordNoSpecialChar()
|
|
48
|
-
expect(error.code).toBe(ClientSecurityErrorCodes.PASSWORD_NO_SPECIAL_CHAR)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('creates passwordTooCommon error', () => {
|
|
52
|
-
const error = SecurityError.passwordTooCommon()
|
|
53
|
-
expect(error.code).toBe(ClientSecurityErrorCodes.PASSWORD_TOO_COMMON)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('supports JSON serialization / deserialization without losing structure', () => {
|
|
57
|
-
const original = SecurityError.passwordTooShort(12)
|
|
58
|
-
const serialized = JSON.stringify(original)
|
|
59
|
-
const deserialized = JSON.parse(serialized) as typeof original
|
|
60
|
-
|
|
61
|
-
expect(deserialized).toEqual(original)
|
|
62
|
-
expect(deserialized.code).toBe('PASSWORD_TOO_SHORT')
|
|
63
|
-
expect(deserialized.args).toEqual({ passwordMinLength: '12' })
|
|
64
|
-
})
|
|
65
|
-
})
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import { SecurityErrorArgs } from '@mudrichenkoevgeny/shared-foundation'
|
|
2
|
-
import { generateErrorId } from '@mudrichenkoevgeny/web-platform-sdk-core-common'
|
|
3
|
-
import type { ErrorId } from "@mudrichenkoevgeny/web-platform-sdk-core-common";
|
|
4
|
-
import { ClientSecurityErrorCodes } from '@/error/naming/client-security-error-codes'
|
|
5
|
-
/** Error indicating security/password policy settings could not be fetched. */
|
|
6
|
-
export type SecurityErrorPasswordPolicyUnavailable = {
|
|
7
|
-
readonly id: ErrorId
|
|
8
|
-
readonly code: typeof ClientSecurityErrorCodes.PASSWORD_POLICY_UNAVAILABLE
|
|
9
|
-
readonly args: null
|
|
10
|
-
readonly isRetryable: true
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** Error indicating candidate password is shorter than minimum required length. */
|
|
14
|
-
export type SecurityErrorPasswordTooShort = {
|
|
15
|
-
readonly id: ErrorId
|
|
16
|
-
readonly code: typeof ClientSecurityErrorCodes.PASSWORD_TOO_SHORT
|
|
17
|
-
readonly args: Record<string, string> | null
|
|
18
|
-
readonly isRetryable: false
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Error indicating candidate password contains no letter character. */
|
|
22
|
-
export type SecurityErrorPasswordNoLetter = {
|
|
23
|
-
readonly id: ErrorId
|
|
24
|
-
readonly code: typeof ClientSecurityErrorCodes.PASSWORD_NO_LETTER
|
|
25
|
-
readonly args: null
|
|
26
|
-
readonly isRetryable: false
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Error indicating candidate password contains no uppercase letter. */
|
|
30
|
-
export type SecurityErrorPasswordNoUpperCase = {
|
|
31
|
-
readonly id: ErrorId
|
|
32
|
-
readonly code: typeof ClientSecurityErrorCodes.PASSWORD_NO_UPPERCASE
|
|
33
|
-
readonly args: null
|
|
34
|
-
readonly isRetryable: false
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** Error indicating candidate password contains no lowercase letter. */
|
|
38
|
-
export type SecurityErrorPasswordNoLowerCase = {
|
|
39
|
-
readonly id: ErrorId
|
|
40
|
-
readonly code: typeof ClientSecurityErrorCodes.PASSWORD_NO_LOWERCASE
|
|
41
|
-
readonly args: null
|
|
42
|
-
readonly isRetryable: false
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** Error indicating candidate password contains no digit character. */
|
|
46
|
-
export type SecurityErrorPasswordNoDigit = {
|
|
47
|
-
readonly id: ErrorId
|
|
48
|
-
readonly code: typeof ClientSecurityErrorCodes.PASSWORD_NO_DIGIT
|
|
49
|
-
readonly args: null
|
|
50
|
-
readonly isRetryable: false
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Error indicating candidate password contains no special character. */
|
|
54
|
-
export type SecurityErrorPasswordNoSpecialChar = {
|
|
55
|
-
readonly id: ErrorId
|
|
56
|
-
readonly code: typeof ClientSecurityErrorCodes.PASSWORD_NO_SPECIAL_CHAR
|
|
57
|
-
readonly args: null
|
|
58
|
-
readonly isRetryable: false
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Error indicating candidate password is on the forbidden common passwords list. */
|
|
62
|
-
export type SecurityErrorPasswordTooCommon = {
|
|
63
|
-
readonly id: ErrorId
|
|
64
|
-
readonly code: typeof ClientSecurityErrorCodes.PASSWORD_TOO_COMMON
|
|
65
|
-
readonly args: null
|
|
66
|
-
readonly isRetryable: false
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Discriminated union type representing security domain errors.
|
|
71
|
-
*/
|
|
72
|
-
export type SecurityError =
|
|
73
|
-
| SecurityErrorPasswordPolicyUnavailable
|
|
74
|
-
| SecurityErrorPasswordTooShort
|
|
75
|
-
| SecurityErrorPasswordNoLetter
|
|
76
|
-
| SecurityErrorPasswordNoUpperCase
|
|
77
|
-
| SecurityErrorPasswordNoLowerCase
|
|
78
|
-
| SecurityErrorPasswordNoDigit
|
|
79
|
-
| SecurityErrorPasswordNoSpecialChar
|
|
80
|
-
| SecurityErrorPasswordTooCommon
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Factory helper object for constructing {@link SecurityError} plain objects.
|
|
84
|
-
*/
|
|
85
|
-
export const SecurityError = {
|
|
86
|
-
/**
|
|
87
|
-
* Creates a password policy unavailable error.
|
|
88
|
-
*
|
|
89
|
-
* @returns {@link SecurityErrorPasswordPolicyUnavailable}
|
|
90
|
-
*/
|
|
91
|
-
passwordPolicyUnavailable: (): SecurityErrorPasswordPolicyUnavailable => ({
|
|
92
|
-
id: generateErrorId(),
|
|
93
|
-
code: ClientSecurityErrorCodes.PASSWORD_POLICY_UNAVAILABLE,
|
|
94
|
-
args: null,
|
|
95
|
-
isRetryable: true
|
|
96
|
-
}),
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Creates a password too short validation error.
|
|
100
|
-
*
|
|
101
|
-
* @param minLength - Optional minimum length requirement
|
|
102
|
-
* @returns {@link SecurityErrorPasswordTooShort}
|
|
103
|
-
*/
|
|
104
|
-
passwordTooShort: (minLength?: number | null): SecurityErrorPasswordTooShort => ({
|
|
105
|
-
id: generateErrorId(),
|
|
106
|
-
code: ClientSecurityErrorCodes.PASSWORD_TOO_SHORT,
|
|
107
|
-
args: minLength != null ? { [SecurityErrorArgs.PASSWORD_MIN_LENGTH]: String(minLength) } : null,
|
|
108
|
-
isRetryable: false
|
|
109
|
-
}),
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Creates a password missing letter validation error.
|
|
113
|
-
*
|
|
114
|
-
* @returns {@link SecurityErrorPasswordNoLetter}
|
|
115
|
-
*/
|
|
116
|
-
passwordNoLetter: (): SecurityErrorPasswordNoLetter => ({
|
|
117
|
-
id: generateErrorId(),
|
|
118
|
-
code: ClientSecurityErrorCodes.PASSWORD_NO_LETTER,
|
|
119
|
-
args: null,
|
|
120
|
-
isRetryable: false
|
|
121
|
-
}),
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Creates a password missing uppercase letter validation error.
|
|
125
|
-
*
|
|
126
|
-
* @returns {@link SecurityErrorPasswordNoUpperCase}
|
|
127
|
-
*/
|
|
128
|
-
passwordNoUpperCase: (): SecurityErrorPasswordNoUpperCase => ({
|
|
129
|
-
id: generateErrorId(),
|
|
130
|
-
code: ClientSecurityErrorCodes.PASSWORD_NO_UPPERCASE,
|
|
131
|
-
args: null,
|
|
132
|
-
isRetryable: false
|
|
133
|
-
}),
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Creates a password missing lowercase letter validation error.
|
|
137
|
-
*
|
|
138
|
-
* @returns {@link SecurityErrorPasswordNoLowerCase}
|
|
139
|
-
*/
|
|
140
|
-
passwordNoLowerCase: (): SecurityErrorPasswordNoLowerCase => ({
|
|
141
|
-
id: generateErrorId(),
|
|
142
|
-
code: ClientSecurityErrorCodes.PASSWORD_NO_LOWERCASE,
|
|
143
|
-
args: null,
|
|
144
|
-
isRetryable: false
|
|
145
|
-
}),
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Creates a password missing digit validation error.
|
|
149
|
-
*
|
|
150
|
-
* @returns {@link SecurityErrorPasswordNoDigit}
|
|
151
|
-
*/
|
|
152
|
-
passwordNoDigit: (): SecurityErrorPasswordNoDigit => ({
|
|
153
|
-
id: generateErrorId(),
|
|
154
|
-
code: ClientSecurityErrorCodes.PASSWORD_NO_DIGIT,
|
|
155
|
-
args: null,
|
|
156
|
-
isRetryable: false
|
|
157
|
-
}),
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Creates a password missing special character validation error.
|
|
161
|
-
*
|
|
162
|
-
* @returns {@link SecurityErrorPasswordNoSpecialChar}
|
|
163
|
-
*/
|
|
164
|
-
passwordNoSpecialChar: (): SecurityErrorPasswordNoSpecialChar => ({
|
|
165
|
-
id: generateErrorId(),
|
|
166
|
-
code: ClientSecurityErrorCodes.PASSWORD_NO_SPECIAL_CHAR,
|
|
167
|
-
args: null,
|
|
168
|
-
isRetryable: false
|
|
169
|
-
}),
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Creates a password too common validation error.
|
|
173
|
-
*
|
|
174
|
-
* @returns {@link SecurityErrorPasswordTooCommon}
|
|
175
|
-
*/
|
|
176
|
-
passwordTooCommon: (): SecurityErrorPasswordTooCommon => ({
|
|
177
|
-
id: generateErrorId(),
|
|
178
|
-
code: ClientSecurityErrorCodes.PASSWORD_TOO_COMMON,
|
|
179
|
-
args: null,
|
|
180
|
-
isRetryable: false
|
|
181
|
-
})
|
|
182
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Machine-readable error codes for security client errors and password policy validation.
|
|
3
|
-
*/
|
|
4
|
-
export const ClientSecurityErrorCodes = {
|
|
5
|
-
/** Password policy is missing and cannot be fetched. */
|
|
6
|
-
PASSWORD_POLICY_UNAVAILABLE: 'PASSWORD_POLICY_UNAVAILABLE',
|
|
7
|
-
/** Password does not meet the minimum length requirement. */
|
|
8
|
-
PASSWORD_TOO_SHORT: 'PASSWORD_TOO_SHORT',
|
|
9
|
-
/** Password must contain at least one letter. */
|
|
10
|
-
PASSWORD_NO_LETTER: 'PASSWORD_NO_LETTER',
|
|
11
|
-
/** Password must contain at least one uppercase letter. */
|
|
12
|
-
PASSWORD_NO_UPPERCASE: 'PASSWORD_NO_UPPERCASE',
|
|
13
|
-
/** Password must contain at least one lowercase letter. */
|
|
14
|
-
PASSWORD_NO_LOWERCASE: 'PASSWORD_NO_LOWERCASE',
|
|
15
|
-
/** Password must contain at least one digit. */
|
|
16
|
-
PASSWORD_NO_DIGIT: 'PASSWORD_NO_DIGIT',
|
|
17
|
-
/** Password must contain at least one special character. */
|
|
18
|
-
PASSWORD_NO_SPECIAL_CHAR: 'PASSWORD_NO_SPECIAL_CHAR',
|
|
19
|
-
/** Password is in the list of forbidden common passwords. */
|
|
20
|
-
PASSWORD_TOO_COMMON: 'PASSWORD_TOO_COMMON'
|
|
21
|
-
} as const
|