@alacard-project/shared 1.0.10 → 1.1.1
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/README.md +24 -0
- package/dist/constants/auth-config.constants.d.ts +7 -0
- package/dist/constants/auth-config.constants.js +8 -1
- package/dist/constants/auth-config.constants.js.map +1 -1
- package/dist/constants/auth.constants.d.ts +25 -0
- package/dist/constants/auth.constants.js +15 -1
- package/dist/constants/auth.constants.js.map +1 -1
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.js +1 -0
- package/dist/constants/index.js.map +1 -1
- package/dist/constants/queue.constants.d.ts +8 -0
- package/dist/constants/queue.constants.js +12 -0
- package/dist/constants/queue.constants.js.map +1 -0
- package/dist/contracts/auth.contract.d.ts +12 -9
- package/dist/dto/account.dto.d.ts +10 -0
- package/dist/dto/account.dto.js +49 -0
- package/dist/dto/account.dto.js.map +1 -0
- package/dist/dto/auth.dto.d.ts +43 -0
- package/dist/dto/auth.dto.js +136 -1
- package/dist/dto/auth.dto.js.map +1 -1
- package/dist/dto/card.dto.js.map +1 -1
- package/dist/dto/client.dto.js.map +1 -1
- package/dist/dto/index.d.ts +2 -0
- package/dist/dto/index.js +2 -0
- package/dist/dto/index.js.map +1 -1
- package/dist/dto/logging.dto.js.map +1 -1
- package/dist/dto/partner.dto.d.ts +1 -1
- package/dist/dto/partner.dto.js +7 -9
- package/dist/dto/partner.dto.js.map +1 -1
- package/dist/dto/transaction.dto.d.ts +2 -1
- package/dist/dto/transaction.dto.js.map +1 -1
- package/dist/dto/user.dto.d.ts +11 -0
- package/dist/dto/user.dto.js +45 -0
- package/dist/dto/user.dto.js.map +1 -0
- package/dist/enums/auth.enum.d.ts +12 -0
- package/dist/enums/auth.enum.js +18 -0
- package/dist/enums/auth.enum.js.map +1 -0
- package/dist/enums/iam.enum.d.ts +14 -0
- package/dist/enums/iam.enum.js +21 -0
- package/dist/enums/iam.enum.js.map +1 -0
- package/dist/enums/index.d.ts +2 -0
- package/dist/enums/index.js +2 -0
- package/dist/enums/index.js.map +1 -1
- package/dist/errors/app.error.d.ts +1 -1
- package/dist/filters/http-exception.filter.d.ts +5 -0
- package/dist/filters/http-exception.filter.js +48 -0
- package/dist/filters/http-exception.filter.js.map +1 -0
- package/dist/filters/index.d.ts +1 -0
- package/dist/filters/index.js +18 -0
- package/dist/filters/index.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/interceptors/index.d.ts +2 -0
- package/dist/interceptors/index.js +19 -0
- package/dist/interceptors/index.js.map +1 -0
- package/dist/interceptors/logging.interceptor.d.ts +6 -0
- package/dist/interceptors/logging.interceptor.js +41 -0
- package/dist/interceptors/logging.interceptor.js.map +1 -0
- package/dist/interceptors/transform.interceptor.d.ts +9 -0
- package/dist/interceptors/transform.interceptor.js +24 -0
- package/dist/interceptors/transform.interceptor.js.map +1 -0
- package/dist/observability/index.d.ts +2 -0
- package/dist/observability/index.js +19 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/metrics/metrics.module.d.ts +2 -0
- package/dist/observability/metrics/metrics.module.js +28 -0
- package/dist/observability/metrics/metrics.module.js.map +1 -0
- package/dist/observability/tracing.d.ts +1 -0
- package/dist/observability/tracing.js +28 -0
- package/dist/observability/tracing.js.map +1 -0
- package/dist/resilience/circuit-breaker.service.d.ts +7 -0
- package/dist/resilience/circuit-breaker.service.js +45 -0
- package/dist/resilience/circuit-breaker.service.js.map +1 -0
- package/dist/resilience/resilience.module.d.ts +2 -0
- package/dist/resilience/resilience.module.js +22 -0
- package/dist/resilience/resilience.module.js.map +1 -0
- package/dist/types/auth.types.d.ts +22 -4
- package/dist/types/common.types.d.ts +11 -0
- package/dist/types/common.types.js +3 -0
- package/dist/types/common.types.js.map +1 -0
- package/dist/types/iam.types.d.ts +46 -0
- package/dist/types/iam.types.js +3 -0
- package/dist/types/iam.types.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/monitoring.types.d.ts +2 -1
- package/dist/types/user.types.d.ts +7 -3
- package/dist/utils/app.utils.d.ts +1 -0
- package/dist/utils/app.utils.js +7 -1
- package/dist/utils/app.utils.js.map +1 -1
- package/dist/utils/health.utils.d.ts +6 -2
- package/dist/utils/health.utils.js +2 -1
- package/dist/utils/health.utils.js.map +1 -1
- package/package.json +27 -14
- package/proto/dbf.proto +1 -1
- package/src/constants/auth-config.constants.ts +7 -0
- package/src/constants/auth.constants.ts +16 -0
- package/src/constants/index.ts +1 -0
- package/src/constants/queue.constants.ts +8 -0
- package/src/contracts/auth.contract.ts +10 -9
- package/src/dto/account.dto.ts +28 -0
- package/src/dto/auth.dto.ts +126 -0
- package/src/dto/card.dto.ts +1 -1
- package/src/dto/client.dto.ts +7 -7
- package/src/dto/index.ts +2 -0
- package/src/dto/logging.dto.ts +3 -3
- package/src/dto/partner.dto.ts +3 -6
- package/src/dto/transaction.dto.ts +4 -3
- package/src/dto/user.dto.ts +28 -0
- package/src/enums/auth.enum.ts +13 -0
- package/src/enums/iam.enum.ts +28 -0
- package/src/enums/index.ts +2 -0
- package/src/filters/http-exception.filter.ts +49 -0
- package/src/filters/index.ts +2 -0
- package/src/index.ts +5 -0
- package/src/interceptors/index.ts +3 -0
- package/src/interceptors/logging.interceptor.ts +37 -0
- package/src/interceptors/transform.interceptor.ts +21 -0
- package/src/observability/index.ts +3 -0
- package/src/observability/metrics/metrics.module.ts +16 -0
- package/src/observability/tracing.ts +33 -0
- package/src/resilience/circuit-breaker.service.ts +46 -0
- package/src/resilience/resilience.module.ts +9 -0
- package/src/types/auth.types.ts +23 -4
- package/src/types/common.types.ts +18 -0
- package/src/types/iam.types.ts +78 -0
- package/src/types/index.ts +2 -0
- package/src/types/monitoring.types.ts +2 -4
- package/src/types/user.types.ts +3 -3
- package/src/utils/app.utils.ts +9 -0
- package/src/utils/health.utils.ts +11 -4
- package/tsconfig.json +5 -1
package/src/dto/auth.dto.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IsEmail, IsString, MinLength, IsEnum, IsOptional } from 'class-validator';
|
|
2
2
|
import { UserRole } from '../enums/user.enum';
|
|
3
|
+
import { ServiceTokenType, IdentifierType } from '../enums';
|
|
3
4
|
|
|
4
5
|
export class RegisterDto {
|
|
5
6
|
@IsEmail()
|
|
@@ -34,6 +35,32 @@ export class LoginDto {
|
|
|
34
35
|
password!: string;
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Employee Login DTO
|
|
40
|
+
* Used for employee authentication via email
|
|
41
|
+
*/
|
|
42
|
+
export class EmployeeLoginDto {
|
|
43
|
+
@IsEmail()
|
|
44
|
+
email!: string;
|
|
45
|
+
|
|
46
|
+
@IsString()
|
|
47
|
+
@MinLength(8)
|
|
48
|
+
password!: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Public Login DTO
|
|
53
|
+
* Used for client and partner authentication via username
|
|
54
|
+
*/
|
|
55
|
+
export class PublicLoginDto {
|
|
56
|
+
@IsString()
|
|
57
|
+
username!: string;
|
|
58
|
+
|
|
59
|
+
@IsString()
|
|
60
|
+
@MinLength(8)
|
|
61
|
+
password!: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
37
64
|
export interface UserResponse {
|
|
38
65
|
id: string;
|
|
39
66
|
email: string;
|
|
@@ -105,3 +132,102 @@ export class ResetPasswordConfirmBody {
|
|
|
105
132
|
@MinLength(8)
|
|
106
133
|
newPassword!: string;
|
|
107
134
|
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Logout DTO
|
|
138
|
+
*/
|
|
139
|
+
export class LogoutDto {
|
|
140
|
+
@IsString()
|
|
141
|
+
@IsOptional()
|
|
142
|
+
refreshToken?: string;
|
|
143
|
+
|
|
144
|
+
@IsString()
|
|
145
|
+
@IsOptional()
|
|
146
|
+
sessionId?: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export class RefreshTokenDto {
|
|
150
|
+
@IsString()
|
|
151
|
+
refreshToken!: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Service Token DTOs
|
|
156
|
+
*/
|
|
157
|
+
export class CreateServiceTokenDto {
|
|
158
|
+
@IsString()
|
|
159
|
+
serviceName!: string;
|
|
160
|
+
|
|
161
|
+
@IsEnum(ServiceTokenType)
|
|
162
|
+
type!: ServiceTokenType;
|
|
163
|
+
|
|
164
|
+
@IsOptional()
|
|
165
|
+
@IsString()
|
|
166
|
+
description?: string;
|
|
167
|
+
|
|
168
|
+
@IsOptional()
|
|
169
|
+
@IsString()
|
|
170
|
+
name?: string;
|
|
171
|
+
|
|
172
|
+
@IsString()
|
|
173
|
+
identityId!: string;
|
|
174
|
+
|
|
175
|
+
@IsOptional()
|
|
176
|
+
@IsString()
|
|
177
|
+
expiresInDays?: number;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export class ValidateServiceTokenDto {
|
|
181
|
+
@IsString()
|
|
182
|
+
token!: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Credential DTOs
|
|
187
|
+
*/
|
|
188
|
+
export class CreateCredentialDto {
|
|
189
|
+
@IsEnum(IdentifierType)
|
|
190
|
+
identifierType!: IdentifierType;
|
|
191
|
+
|
|
192
|
+
@IsString()
|
|
193
|
+
identifierValue!: string;
|
|
194
|
+
|
|
195
|
+
@IsString()
|
|
196
|
+
password!: string;
|
|
197
|
+
|
|
198
|
+
@IsString()
|
|
199
|
+
identityId!: string;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Session DTOs
|
|
204
|
+
*/
|
|
205
|
+
export class CreateSessionDto {
|
|
206
|
+
@IsString()
|
|
207
|
+
identityId!: string;
|
|
208
|
+
|
|
209
|
+
@IsOptional()
|
|
210
|
+
@IsString()
|
|
211
|
+
ipAddress?: string;
|
|
212
|
+
|
|
213
|
+
@IsOptional()
|
|
214
|
+
@IsString()
|
|
215
|
+
userAgent?: string;
|
|
216
|
+
|
|
217
|
+
@IsOptional()
|
|
218
|
+
@IsString()
|
|
219
|
+
deviceInfo?: string;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 2FA Verification DTO
|
|
224
|
+
*/
|
|
225
|
+
export class Verify2faDto {
|
|
226
|
+
@IsString()
|
|
227
|
+
challengeId!: string;
|
|
228
|
+
|
|
229
|
+
@IsString()
|
|
230
|
+
@MinLength(6)
|
|
231
|
+
code!: string;
|
|
232
|
+
}
|
|
233
|
+
|
package/src/dto/card.dto.ts
CHANGED
package/src/dto/client.dto.ts
CHANGED
|
@@ -4,10 +4,10 @@ import { RegisterDto } from './auth.dto';
|
|
|
4
4
|
|
|
5
5
|
export class CreateClientContactDto {
|
|
6
6
|
@IsString()
|
|
7
|
-
contactType
|
|
7
|
+
contactType!: string;
|
|
8
8
|
|
|
9
9
|
@IsString()
|
|
10
|
-
value
|
|
10
|
+
value!: string;
|
|
11
11
|
|
|
12
12
|
@IsOptional()
|
|
13
13
|
@IsBoolean()
|
|
@@ -16,10 +16,10 @@ export class CreateClientContactDto {
|
|
|
16
16
|
|
|
17
17
|
export class CreateClientDetailDto {
|
|
18
18
|
@IsString()
|
|
19
|
-
detailType
|
|
19
|
+
detailType!: string;
|
|
20
20
|
|
|
21
21
|
@IsString()
|
|
22
|
-
value
|
|
22
|
+
value!: string;
|
|
23
23
|
|
|
24
24
|
@IsOptional()
|
|
25
25
|
@IsBoolean()
|
|
@@ -28,21 +28,21 @@ export class CreateClientDetailDto {
|
|
|
28
28
|
|
|
29
29
|
export class CreateClientDto {
|
|
30
30
|
@IsString()
|
|
31
|
-
clientNumber
|
|
31
|
+
clientNumber!: string;
|
|
32
32
|
|
|
33
33
|
@IsOptional()
|
|
34
34
|
@IsString()
|
|
35
35
|
clientNumberFormatted?: string;
|
|
36
36
|
|
|
37
37
|
@IsString()
|
|
38
|
-
clientType
|
|
38
|
+
clientType!: string;
|
|
39
39
|
|
|
40
40
|
@IsOptional()
|
|
41
41
|
@IsString()
|
|
42
42
|
shortName?: string;
|
|
43
43
|
|
|
44
44
|
@IsString()
|
|
45
|
-
fullName
|
|
45
|
+
fullName!: string;
|
|
46
46
|
|
|
47
47
|
@IsOptional()
|
|
48
48
|
@IsBoolean()
|
package/src/dto/index.ts
CHANGED
package/src/dto/logging.dto.ts
CHANGED
|
@@ -43,10 +43,10 @@ export class LogFilterDto {
|
|
|
43
43
|
|
|
44
44
|
export class LogCleanupDto {
|
|
45
45
|
@IsDateString()
|
|
46
|
-
startDate
|
|
46
|
+
startDate!: string;
|
|
47
47
|
|
|
48
48
|
@IsDateString()
|
|
49
|
-
endDate
|
|
49
|
+
endDate!: string;
|
|
50
50
|
|
|
51
51
|
@IsOptional()
|
|
52
52
|
@IsString()
|
|
@@ -62,7 +62,7 @@ export class RetentionPeriodDto {
|
|
|
62
62
|
@IsInt()
|
|
63
63
|
@Min(1)
|
|
64
64
|
@Max(365)
|
|
65
|
-
retentionDays
|
|
65
|
+
retentionDays!: number;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
export class UserActionFilterDto {
|
package/src/dto/partner.dto.ts
CHANGED
|
@@ -2,19 +2,16 @@ import { IsString, IsOptional, IsBoolean, ValidateNested, IsObject } from 'class
|
|
|
2
2
|
import { Type } from 'class-transformer';
|
|
3
3
|
import { RegisterDto } from './auth.dto';
|
|
4
4
|
|
|
5
|
-
export class
|
|
5
|
+
export class PartnerDto {
|
|
6
6
|
@IsString()
|
|
7
|
-
name
|
|
7
|
+
name!: string;
|
|
8
8
|
|
|
9
9
|
@IsString()
|
|
10
|
-
type
|
|
10
|
+
type!: string;
|
|
11
11
|
|
|
12
12
|
@IsOptional()
|
|
13
13
|
@IsBoolean()
|
|
14
14
|
isActive?: boolean;
|
|
15
|
-
|
|
16
|
-
@IsOptional()
|
|
17
|
-
@ValidateNested()
|
|
18
15
|
@IsObject()
|
|
19
16
|
@Type(() => RegisterDto)
|
|
20
17
|
adminUser?: RegisterDto;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IsString, IsOptional, IsNumber, Min, IsUUID } from 'class-validator';
|
|
2
2
|
import { Type } from 'class-transformer';
|
|
3
|
+
import type { JsonValue } from '../types/common.types';
|
|
3
4
|
|
|
4
5
|
export class CreateTransactionDto {
|
|
5
6
|
@IsOptional()
|
|
@@ -7,11 +8,11 @@ export class CreateTransactionDto {
|
|
|
7
8
|
externalId?: string;
|
|
8
9
|
|
|
9
10
|
@IsString()
|
|
10
|
-
cardId
|
|
11
|
+
cardId!: string;
|
|
11
12
|
|
|
12
13
|
@IsNumber()
|
|
13
14
|
@Min(0)
|
|
14
|
-
amount
|
|
15
|
+
amount!: number;
|
|
15
16
|
|
|
16
17
|
@IsOptional()
|
|
17
18
|
@IsString()
|
|
@@ -47,5 +48,5 @@ export class UpdateTransactionDto {
|
|
|
47
48
|
status?: string;
|
|
48
49
|
|
|
49
50
|
@IsOptional()
|
|
50
|
-
metadata?:
|
|
51
|
+
metadata?: JsonValue;
|
|
51
52
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { IsEmail, IsEnum, IsOptional, IsString, IsUUID } from 'class-validator';
|
|
2
|
+
import { UserRole } from '../enums';
|
|
3
|
+
|
|
4
|
+
export class UserDto {
|
|
5
|
+
@IsUUID()
|
|
6
|
+
id!: string;
|
|
7
|
+
|
|
8
|
+
@IsEmail()
|
|
9
|
+
email!: string;
|
|
10
|
+
|
|
11
|
+
@IsString()
|
|
12
|
+
@IsOptional()
|
|
13
|
+
firstName?: string;
|
|
14
|
+
|
|
15
|
+
@IsString()
|
|
16
|
+
@IsOptional()
|
|
17
|
+
lastName?: string;
|
|
18
|
+
|
|
19
|
+
@IsEnum(UserRole)
|
|
20
|
+
role!: UserRole;
|
|
21
|
+
|
|
22
|
+
@IsString()
|
|
23
|
+
@IsOptional()
|
|
24
|
+
phoneNumber?: string;
|
|
25
|
+
|
|
26
|
+
createdAt!: Date;
|
|
27
|
+
updatedAt!: Date;
|
|
28
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export enum CredentialStatus {
|
|
2
|
+
ACTIVE = 'ACTIVE',
|
|
3
|
+
INACTIVE = 'INACTIVE',
|
|
4
|
+
LOCKED = 'LOCKED',
|
|
5
|
+
SUSPENDED = 'SUSPENDED',
|
|
6
|
+
DELETED = 'DELETED',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export enum ServiceTokenType {
|
|
10
|
+
API = 'API',
|
|
11
|
+
INTERNAL = 'INTERNAL',
|
|
12
|
+
WEBHOOK = 'WEBHOOK',
|
|
13
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subject Type Enum
|
|
3
|
+
* Defines the type of subject in the system (immutable after creation)
|
|
4
|
+
*/
|
|
5
|
+
export enum SubjectType {
|
|
6
|
+
EMPLOYEE = 'EMPLOYEE',
|
|
7
|
+
CLIENT = 'CLIENT',
|
|
8
|
+
PARTNER = 'PARTNER',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Identifier Type Enum
|
|
13
|
+
* Defines the type of login identifier used for authentication
|
|
14
|
+
*/
|
|
15
|
+
export enum IdentifierType {
|
|
16
|
+
EMAIL = 'EMAIL',
|
|
17
|
+
USERNAME = 'USERNAME',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Identity Status Enum
|
|
22
|
+
* Defines the status of an identity in the IAM system
|
|
23
|
+
*/
|
|
24
|
+
export enum IdentityStatus {
|
|
25
|
+
ACTIVE = 'ACTIVE',
|
|
26
|
+
BLOCKED = 'BLOCKED',
|
|
27
|
+
DELETED = 'DELETED',
|
|
28
|
+
}
|
package/src/enums/index.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export * from './env.enum';
|
|
2
2
|
export * from './user.enum';
|
|
3
|
+
export * from './iam.enum';
|
|
3
4
|
export * from './health.enum';
|
|
4
5
|
export * from './logging.enum';
|
|
5
6
|
export * from './notification.enum';
|
|
6
7
|
export * from './error.enum';
|
|
8
|
+
export * from './auth.enum';
|
|
7
9
|
export * from './permission.enum';
|
|
8
10
|
export * from './events.enum';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
|
|
2
|
+
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Logger } from '@nestjs/common';
|
|
3
|
+
import { Request, Response } from 'express';
|
|
4
|
+
|
|
5
|
+
@Catch()
|
|
6
|
+
export class HttpExceptionFilter implements ExceptionFilter {
|
|
7
|
+
private readonly logger = new Logger(HttpExceptionFilter.name);
|
|
8
|
+
|
|
9
|
+
catch(exception: unknown, host: ArgumentsHost) {
|
|
10
|
+
const ctx = host.switchToHttp();
|
|
11
|
+
const response = ctx.getResponse<Response>();
|
|
12
|
+
const request = ctx.getRequest<Request>();
|
|
13
|
+
|
|
14
|
+
const status =
|
|
15
|
+
exception instanceof HttpException
|
|
16
|
+
? exception.getStatus()
|
|
17
|
+
: HttpStatus.INTERNAL_SERVER_ERROR;
|
|
18
|
+
|
|
19
|
+
const message =
|
|
20
|
+
exception instanceof HttpException
|
|
21
|
+
? exception.getResponse()
|
|
22
|
+
: 'Internal server error';
|
|
23
|
+
|
|
24
|
+
// Determine the actual error message string
|
|
25
|
+
const errorMessage = typeof message === 'string'
|
|
26
|
+
? message
|
|
27
|
+
: (message as any).message || message;
|
|
28
|
+
|
|
29
|
+
// Log the error
|
|
30
|
+
if (status >= 500) {
|
|
31
|
+
this.logger.error(
|
|
32
|
+
`[${request.method}] ${request.url} - ${status} - ${JSON.stringify(errorMessage)}`,
|
|
33
|
+
exception instanceof Error ? exception.stack : '',
|
|
34
|
+
);
|
|
35
|
+
} else {
|
|
36
|
+
this.logger.warn(
|
|
37
|
+
`[${request.method}] ${request.url} - ${status} - ${JSON.stringify(errorMessage)}`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
response.status(status).json({
|
|
42
|
+
statusCode: status,
|
|
43
|
+
timestamp: new Date().toISOString(),
|
|
44
|
+
path: request.url,
|
|
45
|
+
method: request.method,
|
|
46
|
+
error: errorMessage,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -10,3 +10,8 @@ export * from './utils/proto-path';
|
|
|
10
10
|
export * from './utils/user.utils';
|
|
11
11
|
export * from './utils/app.utils';
|
|
12
12
|
export * from './utils/health.utils';
|
|
13
|
+
export * from './filters';
|
|
14
|
+
export * from './interceptors';
|
|
15
|
+
export * from './observability';
|
|
16
|
+
export * from './resilience/resilience.module';
|
|
17
|
+
export * from './resilience/circuit-breaker.service';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
|
|
2
|
+
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common';
|
|
3
|
+
import { Observable } from 'rxjs';
|
|
4
|
+
import { tap } from 'rxjs/operators';
|
|
5
|
+
import { randomUUID } from 'crypto';
|
|
6
|
+
import { Request, Response } from 'express';
|
|
7
|
+
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class LoggingInterceptor implements NestInterceptor {
|
|
10
|
+
private readonly logger = new Logger(LoggingInterceptor.name);
|
|
11
|
+
|
|
12
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
|
13
|
+
const httpContext = context.switchToHttp();
|
|
14
|
+
const request = httpContext.getRequest<Request>();
|
|
15
|
+
const response = httpContext.getResponse<Response>();
|
|
16
|
+
|
|
17
|
+
const userAgent = request.get('user-agent') || '';
|
|
18
|
+
const { ip, method, path: url } = request;
|
|
19
|
+
|
|
20
|
+
// Generate or retrieve Trace ID
|
|
21
|
+
const traceId = request.headers['x-request-id'] || randomUUID();
|
|
22
|
+
request.headers['x-request-id'] = traceId as string;
|
|
23
|
+
response.setHeader('x-request-id', traceId);
|
|
24
|
+
|
|
25
|
+
this.logger.log(`[${traceId}] Incoming Request: ${method} ${url} - ${userAgent} ${ip}`);
|
|
26
|
+
|
|
27
|
+
const now = Date.now();
|
|
28
|
+
return next
|
|
29
|
+
.handle()
|
|
30
|
+
.pipe(
|
|
31
|
+
tap(() => {
|
|
32
|
+
const duration = Date.now() - now;
|
|
33
|
+
this.logger.log(`[${traceId}] Response Sent: ${method} ${url} ${response.statusCode} - ${duration}ms`);
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
|
3
|
+
import { Observable } from 'rxjs';
|
|
4
|
+
import { map } from 'rxjs/operators';
|
|
5
|
+
|
|
6
|
+
export interface Response<T> {
|
|
7
|
+
statusCode: number;
|
|
8
|
+
data: T;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@Injectable()
|
|
12
|
+
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
|
|
13
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
|
|
14
|
+
return next.handle().pipe(
|
|
15
|
+
map(data => ({
|
|
16
|
+
statusCode: context.switchToHttp().getResponse().statusCode,
|
|
17
|
+
data,
|
|
18
|
+
})),
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
import { Module } from '@nestjs/common';
|
|
3
|
+
import { PrometheusModule } from '@willsoto/nestjs-prometheus';
|
|
4
|
+
|
|
5
|
+
@Module({
|
|
6
|
+
imports: [
|
|
7
|
+
PrometheusModule.register({
|
|
8
|
+
path: '/metrics',
|
|
9
|
+
defaultMetrics: {
|
|
10
|
+
enabled: true,
|
|
11
|
+
},
|
|
12
|
+
}),
|
|
13
|
+
],
|
|
14
|
+
exports: [PrometheusModule],
|
|
15
|
+
})
|
|
16
|
+
export class MetricsModule { }
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
2
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
3
|
+
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
|
|
4
|
+
import { Logger } from '@nestjs/common';
|
|
5
|
+
|
|
6
|
+
const logger = new Logger('Tracing');
|
|
7
|
+
|
|
8
|
+
export function initTracing(serviceName: string) {
|
|
9
|
+
const otelExporterOtlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://jaeger:4318/v1/traces';
|
|
10
|
+
|
|
11
|
+
const traceExporter = new OTLPTraceExporter({
|
|
12
|
+
url: otelExporterOtlpEndpoint,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Set service name via env var so auto-detection picks it up
|
|
16
|
+
process.env.OTEL_SERVICE_NAME = serviceName;
|
|
17
|
+
|
|
18
|
+
const sdk = new NodeSDK({
|
|
19
|
+
traceExporter,
|
|
20
|
+
instrumentations: [getNodeAutoInstrumentations()],
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
sdk.start();
|
|
24
|
+
|
|
25
|
+
logger.log(`Initialized tracing for ${serviceName} sending to ${otelExporterOtlpEndpoint}`);
|
|
26
|
+
|
|
27
|
+
process.on('SIGTERM', () => {
|
|
28
|
+
sdk.shutdown()
|
|
29
|
+
.then(() => console.log('Tracing terminated'))
|
|
30
|
+
.catch((error) => console.log('Error terminating tracing', error))
|
|
31
|
+
.finally(() => process.exit(0));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
2
|
+
import CircuitBreaker = require('opossum');
|
|
3
|
+
|
|
4
|
+
@Injectable()
|
|
5
|
+
export class CircuitBreakerService {
|
|
6
|
+
private readonly logger = new Logger(CircuitBreakerService.name);
|
|
7
|
+
private readonly breakers = new Map<string, CircuitBreaker>();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Execute an async action through a circuit breaker.
|
|
11
|
+
* @param key Unique key for the breaker (e.g., service URL)
|
|
12
|
+
* @param action The async action to execute
|
|
13
|
+
* @param options Circuit Breaker options
|
|
14
|
+
*/
|
|
15
|
+
async execute<T>(
|
|
16
|
+
key: string,
|
|
17
|
+
action: () => Promise<T>,
|
|
18
|
+
options?: CircuitBreaker.Options
|
|
19
|
+
): Promise<T> {
|
|
20
|
+
let breaker = this.breakers.get(key);
|
|
21
|
+
|
|
22
|
+
if (!breaker) {
|
|
23
|
+
const defaultOptions: CircuitBreaker.Options = {
|
|
24
|
+
timeout: 5000,
|
|
25
|
+
errorThresholdPercentage: 50,
|
|
26
|
+
resetTimeout: 10000,
|
|
27
|
+
...options,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Wrap a pass-through function to allow dynamic actions
|
|
31
|
+
const passThrough = async (promiseFactory: () => Promise<T>) => promiseFactory();
|
|
32
|
+
breaker = new CircuitBreaker(passThrough, defaultOptions);
|
|
33
|
+
this.bindEvents(breaker, key);
|
|
34
|
+
this.breakers.set(key, breaker);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return breaker.fire(action) as Promise<T>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private bindEvents(breaker: CircuitBreaker, key: string) {
|
|
41
|
+
breaker.on('open', () => this.logger.warn(`Circuit Breaker OPEN for ${key}`));
|
|
42
|
+
breaker.on('halfOpen', () => this.logger.log(`Circuit Breaker HALF-OPEN for ${key}`));
|
|
43
|
+
breaker.on('close', () => this.logger.log(`Circuit Breaker CLOSED for ${key}`));
|
|
44
|
+
breaker.on('fallback', () => this.logger.warn(`Circuit Breaker FALLBACK for ${key}`));
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/types/auth.types.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import { JsonObject, JsonValue } from './common.types';
|
|
2
|
+
import { SubjectType } from '../enums/iam.enum';
|
|
3
|
+
export interface JwtPayload extends JsonObject {
|
|
2
4
|
sub: string;
|
|
3
|
-
email
|
|
5
|
+
email?: string;
|
|
4
6
|
role: string;
|
|
7
|
+
subjectType: SubjectType;
|
|
5
8
|
scopes: string[];
|
|
6
9
|
iat?: number;
|
|
7
10
|
exp?: number;
|
|
@@ -14,6 +17,22 @@ export interface JwtTokens {
|
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
export interface AuthenticatedRequest {
|
|
17
|
-
user:
|
|
18
|
-
[key: string]:
|
|
20
|
+
user: JwtPayload;
|
|
21
|
+
[key: string]: JsonValue;
|
|
22
|
+
}
|
|
23
|
+
export interface ValidateServiceTokenResponse {
|
|
24
|
+
isValid: boolean;
|
|
25
|
+
identityId?: string;
|
|
26
|
+
tokenId?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Session {
|
|
30
|
+
sessionId: string;
|
|
31
|
+
identityId: string;
|
|
32
|
+
ipAddress?: string;
|
|
33
|
+
userAgent?: string;
|
|
34
|
+
deviceInfo?: string;
|
|
35
|
+
createdAt: Date;
|
|
36
|
+
expiresAt: Date;
|
|
37
|
+
isRevoked?: boolean;
|
|
19
38
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type JsonPrimitive = string | number | boolean | null | undefined;
|
|
2
|
+
|
|
3
|
+
export interface JsonObject {
|
|
4
|
+
[key: string]: JsonValue;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface JsonArray extends Array<JsonValue> { }
|
|
8
|
+
|
|
9
|
+
export type JsonValue =
|
|
10
|
+
| JsonPrimitive
|
|
11
|
+
| JsonObject
|
|
12
|
+
| JsonArray;
|
|
13
|
+
|
|
14
|
+
export interface Metadata {
|
|
15
|
+
[key: string]: JsonValue;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type MetricType = 'counter' | 'gauge' | 'histogram' | 'summary';
|