@friggframework/core 2.0.0--canary.405.1f6792c.0 → 2.0.0--canary.396.6862738.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/credential/credential-repository.js +9 -0
- package/credential/use-cases/get-credential-for-user.js +21 -0
- package/encrypt/encrypt.js +27 -4
- package/handlers/app-definition-loader.js +38 -0
- package/handlers/app-handler-helpers.js +0 -3
- package/handlers/backend-utils.js +29 -34
- package/handlers/routers/auth.js +14 -11
- package/handlers/routers/integration-defined-routers.js +8 -5
- package/handlers/routers/user.js +25 -5
- package/handlers/workers/integration-defined-workers.js +6 -3
- package/index.js +0 -11
- package/integrations/index.js +0 -5
- package/integrations/integration-base.js +10 -7
- package/integrations/integration-repository.js +44 -0
- package/integrations/integration-router.js +230 -132
- package/integrations/integration.js +233 -0
- package/integrations/options.js +1 -1
- package/integrations/use-cases/create-integration.js +58 -0
- package/integrations/use-cases/delete-integration-for-user.js +53 -0
- package/integrations/use-cases/get-integration-for-user.js +63 -0
- package/integrations/use-cases/get-integration-instance.js +73 -0
- package/integrations/use-cases/get-integrations-for-user.js +64 -0
- package/integrations/use-cases/index.js +11 -0
- package/integrations/use-cases/update-integration.js +81 -0
- package/integrations/utils/map-integration-dto.js +37 -0
- package/module-plugin/index.js +0 -4
- package/module-plugin/module-factory.js +13 -32
- package/module-plugin/module-repository.js +70 -0
- package/module-plugin/module-service.js +50 -0
- package/module-plugin/{auther.js → module.js} +109 -173
- package/module-plugin/test/mock-api/api.js +8 -3
- package/module-plugin/test/mock-api/definition.js +12 -8
- package/module-plugin/use-cases/get-entities-for-user.js +32 -0
- package/module-plugin/utils/map-module-dto.js +18 -0
- package/package.json +5 -5
- package/types/integrations/index.d.ts +2 -6
- package/types/module-plugin/index.d.ts +4 -21
- package/user/tests/doubles/test-user-repository.js +72 -0
- package/user/tests/use-cases/create-individual-user.test.js +24 -0
- package/user/tests/use-cases/create-organization-user.test.js +28 -0
- package/user/tests/use-cases/create-token-for-user-id.test.js +19 -0
- package/user/tests/use-cases/get-user-from-bearer-token.test.js +64 -0
- package/user/tests/use-cases/login-user.test.js +140 -0
- package/user/use-cases/create-individual-user.js +61 -0
- package/user/use-cases/create-organization-user.js +47 -0
- package/user/use-cases/create-token-for-user-id.js +30 -0
- package/user/use-cases/get-user-from-bearer-token.js +77 -0
- package/user/use-cases/login-user.js +122 -0
- package/user/user-repository.js +62 -0
- package/user/user.js +77 -0
- package/handlers/routers/HEALTHCHECK.md +0 -240
- package/handlers/routers/health.js +0 -459
- package/handlers/routers/health.test.js +0 -203
- package/handlers/routers/middleware/loadUser.js +0 -15
- package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
- package/integrations/create-frigg-backend.js +0 -31
- package/integrations/integration-factory.js +0 -251
- package/integrations/integration-user.js +0 -144
- package/module-plugin/entity-manager.js +0 -70
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
const Boom = require('@hapi/boom');
|
|
2
|
+
const {
|
|
3
|
+
RequiredPropertyError,
|
|
4
|
+
} = require('../../errors');
|
|
5
|
+
const { User } = require('../user');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Use case for logging in a user.
|
|
9
|
+
* @class LoginUser
|
|
10
|
+
*/
|
|
11
|
+
class LoginUser {
|
|
12
|
+
/**
|
|
13
|
+
* Creates a new LoginUser instance.
|
|
14
|
+
* @param {Object} params - Configuration parameters.
|
|
15
|
+
* @param {import('../user-repository').UserRepository} params.userRepository - Repository for user data operations.
|
|
16
|
+
* @param {Object} params.userConfig - The user properties inside of the app definition.
|
|
17
|
+
*/
|
|
18
|
+
constructor({ userRepository, userConfig }) {
|
|
19
|
+
this.userRepository = userRepository;
|
|
20
|
+
this.userConfig = userConfig;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Executes the use case.
|
|
25
|
+
* @async
|
|
26
|
+
* @param {Object} userCredentials - The user's credentials for authentication.
|
|
27
|
+
* @param {string} [userCredentials.username] - The username for authentication.
|
|
28
|
+
* @param {string} [userCredentials.password] - The password for authentication.
|
|
29
|
+
* @param {string} [userCredentials.appUserId] - The app user id for authentication if no username and password are provided.
|
|
30
|
+
* @param {string} [userCredentials.appOrgId] - The app organization id for authentication if no username and password are provided.
|
|
31
|
+
* @returns {Promise<import('../user').User>} The authenticated user object.
|
|
32
|
+
*/
|
|
33
|
+
async execute(userCredentials) {
|
|
34
|
+
const { username, password, appUserId, appOrgId } = userCredentials;
|
|
35
|
+
if (this.userConfig.individualUserRequired) {
|
|
36
|
+
if (this.userConfig.usePassword) {
|
|
37
|
+
if (!username) {
|
|
38
|
+
throw new RequiredPropertyError({
|
|
39
|
+
parent: this,
|
|
40
|
+
key: 'username',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (!password) {
|
|
44
|
+
throw new RequiredPropertyError({
|
|
45
|
+
parent: this,
|
|
46
|
+
key: 'password',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const individualUserData =
|
|
51
|
+
await this.userRepository.findIndividualUserByUsername(
|
|
52
|
+
username
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
if (!individualUserData) {
|
|
56
|
+
throw Boom.unauthorized('user not found');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const individualUser = new User(
|
|
60
|
+
individualUserData,
|
|
61
|
+
null,
|
|
62
|
+
this.userConfig.usePassword,
|
|
63
|
+
this.userConfig.primary,
|
|
64
|
+
this.userConfig.individualUserRequired,
|
|
65
|
+
this.userConfig.organizationUserRequired
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
if (!individualUser.isPasswordValid(password)) {
|
|
69
|
+
throw Boom.unauthorized('Incorrect username or password');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return individualUser;
|
|
73
|
+
} else {
|
|
74
|
+
const individualUserData =
|
|
75
|
+
await this.userRepository.findIndividualUserByAppUserId(
|
|
76
|
+
appUserId
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (!individualUserData) {
|
|
80
|
+
throw Boom.unauthorized('user not found');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const individualUser = new User(
|
|
84
|
+
individualUserData,
|
|
85
|
+
null,
|
|
86
|
+
this.userConfig.usePassword,
|
|
87
|
+
this.userConfig.primary,
|
|
88
|
+
this.userConfig.individualUserRequired,
|
|
89
|
+
this.userConfig.organizationUserRequired
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return individualUser;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if (this.userConfig.organizationUserRequired) {
|
|
98
|
+
|
|
99
|
+
const organizationUserData =
|
|
100
|
+
await this.userRepository.findOrganizationUserByAppOrgId(appOrgId);
|
|
101
|
+
|
|
102
|
+
if (!organizationUserData) {
|
|
103
|
+
throw Boom.unauthorized(`org user ${appOrgId} not found`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const organizationUser = new User(
|
|
107
|
+
null,
|
|
108
|
+
organizationUserData,
|
|
109
|
+
this.userConfig.usePassword,
|
|
110
|
+
this.userConfig.primary,
|
|
111
|
+
this.userConfig.individualUserRequired,
|
|
112
|
+
this.userConfig.organizationUserRequired
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
return organizationUser;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = { LoginUser };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const { Token } = require('../database/models/Token');
|
|
2
|
+
const { IndividualUser } = require('../database/models/IndividualUser');
|
|
3
|
+
const { OrganizationUser } = require('../database/models/OrganizationUser');
|
|
4
|
+
|
|
5
|
+
class UserRepository {
|
|
6
|
+
/**
|
|
7
|
+
* @param {Object} userConfig - The user config in the app definition.
|
|
8
|
+
*/
|
|
9
|
+
constructor({ userConfig }) {
|
|
10
|
+
this.IndividualUser = IndividualUser;
|
|
11
|
+
this.OrganizationUser = OrganizationUser;
|
|
12
|
+
this.Token = Token;
|
|
13
|
+
this.userConfig = userConfig;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async getSessionToken(token) {
|
|
17
|
+
const jsonToken =
|
|
18
|
+
this.Token.getJSONTokenFromBase64BufferToken(token);
|
|
19
|
+
const sessionToken =
|
|
20
|
+
await this.Token.validateAndGetTokenFromJSONToken(jsonToken);
|
|
21
|
+
return sessionToken;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async findOrganizationUserById(userId) {
|
|
25
|
+
return this.OrganizationUser.findById(userId);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async findIndividualUserById(userId) {
|
|
29
|
+
return this.IndividualUser.findById(userId);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async createToken(userId, rawToken, minutes = 120) {
|
|
33
|
+
const createdToken = await this.Token.createTokenWithExpire(
|
|
34
|
+
userId,
|
|
35
|
+
rawToken,
|
|
36
|
+
minutes
|
|
37
|
+
);
|
|
38
|
+
return this.Token.createBase64BufferToken(createdToken, rawToken);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async createIndividualUser(params) {
|
|
42
|
+
return this.IndividualUser.create(params);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async createOrganizationUser(params) {
|
|
46
|
+
return this.OrganizationUser.create(params);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async findIndividualUserByUsername(username) {
|
|
50
|
+
return this.IndividualUser.findOne({ username });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async findIndividualUserByAppUserId(appUserId) {
|
|
54
|
+
return this.IndividualUser.getUserByAppUserId(appUserId);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async findOrganizationUserByAppOrgId(appOrgId) {
|
|
58
|
+
return this.OrganizationUser.getUserByAppOrgId(appOrgId);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = { UserRepository };
|
package/user/user.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const bcrypt = require('bcryptjs');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a user in the system. The User class is a domain entity,
|
|
5
|
+
* @class User
|
|
6
|
+
*/
|
|
7
|
+
class User {
|
|
8
|
+
/**
|
|
9
|
+
* Creates a new User instance.
|
|
10
|
+
* @param {import('../database/models/IndividualUser').IndividualUser} [individualUser=null] - The individual user for the user.
|
|
11
|
+
* @param {import('../database/models/OrganizationUser').OrganizationUser} [organizationUser=null] - The organization user for the user.
|
|
12
|
+
* @param {boolean} [usePassword=false] - Whether the user has a password.
|
|
13
|
+
* @param {string} [primary='individual'] - The primary user type.
|
|
14
|
+
* @param {boolean} [individualUserRequired=true] - Whether the user is required to have an individual user.
|
|
15
|
+
* @param {boolean} [organizationUserRequired=false] - Whether the user is required to have an organization user.
|
|
16
|
+
*/
|
|
17
|
+
constructor(individualUser = null, organizationUser = null, usePassword = false, primary = 'individual', individualUserRequired = true, organizationUserRequired = false) {
|
|
18
|
+
this.individualUser = individualUser;
|
|
19
|
+
this.organizationUser = organizationUser;
|
|
20
|
+
this.usePassword = usePassword;
|
|
21
|
+
|
|
22
|
+
this.config = {
|
|
23
|
+
primary,
|
|
24
|
+
individualUserRequired,
|
|
25
|
+
organizationUserRequired,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getPrimaryUser() {
|
|
30
|
+
if (this.config.primary === 'organization') {
|
|
31
|
+
return this.organizationUser;
|
|
32
|
+
}
|
|
33
|
+
return this.individualUser;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getId() {
|
|
37
|
+
return this.getPrimaryUser()?.id;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
isPasswordRequired() {
|
|
41
|
+
return this.usePassword;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
isPasswordValid(password) {
|
|
45
|
+
if (!this.isPasswordRequired()) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return bcrypt.compareSync(password, this.getPrimaryUser().hashword);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
setIndividualUser(individualUser) {
|
|
53
|
+
this.individualUser = individualUser;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setOrganizationUser(organizationUser) {
|
|
57
|
+
this.organizationUser = organizationUser;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
isOrganizationUserRequired() {
|
|
61
|
+
return this.config.organizationUserRequired;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
isIndividualUserRequired() {
|
|
65
|
+
return this.config.individualUserRequired;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getIndividualUser() {
|
|
69
|
+
return this.individualUser;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getOrganizationUser() {
|
|
73
|
+
return this.organizationUser;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { User };
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
# Frigg Healthcheck Endpoint Documentation
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
The Frigg service includes comprehensive healthcheck endpoints to monitor service health, connectivity, and readiness. These endpoints follow industry best practices and are designed for use with monitoring systems, load balancers, and container orchestration platforms.
|
|
6
|
-
|
|
7
|
-
## Endpoints
|
|
8
|
-
|
|
9
|
-
### 1. Basic Health Check
|
|
10
|
-
**GET** `/health`
|
|
11
|
-
|
|
12
|
-
Simple health check endpoint that returns basic service information. No authentication required. This endpoint is rate-limited at the API Gateway level.
|
|
13
|
-
|
|
14
|
-
**Response:**
|
|
15
|
-
```json
|
|
16
|
-
{
|
|
17
|
-
"status": "ok",
|
|
18
|
-
"timestamp": "2024-01-10T12:00:00.000Z",
|
|
19
|
-
"service": "frigg-core-api"
|
|
20
|
-
}
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
**Status Codes:**
|
|
24
|
-
- `200 OK` - Service is running
|
|
25
|
-
|
|
26
|
-
### 2. Detailed Health Check
|
|
27
|
-
**GET** `/health/detailed`
|
|
28
|
-
|
|
29
|
-
Comprehensive health check that tests all service components and dependencies.
|
|
30
|
-
|
|
31
|
-
**Authentication Required:**
|
|
32
|
-
- Header: `x-api-key: YOUR_API_KEY`
|
|
33
|
-
- The API key must match the `HEALTH_API_KEY` environment variable
|
|
34
|
-
|
|
35
|
-
**Response:**
|
|
36
|
-
```json
|
|
37
|
-
{
|
|
38
|
-
"service": "frigg-core-api",
|
|
39
|
-
"status": "healthy", // "healthy" or "unhealthy"
|
|
40
|
-
"timestamp": "2024-01-10T12:00:00.000Z",
|
|
41
|
-
"checks": {
|
|
42
|
-
"database": {
|
|
43
|
-
"status": "healthy",
|
|
44
|
-
"state": "connected",
|
|
45
|
-
"responseTime": 5 // milliseconds
|
|
46
|
-
},
|
|
47
|
-
"externalApis": {
|
|
48
|
-
"github": {
|
|
49
|
-
"status": "healthy",
|
|
50
|
-
"statusCode": 200,
|
|
51
|
-
"responseTime": 150,
|
|
52
|
-
"reachable": true
|
|
53
|
-
},
|
|
54
|
-
"npm": {
|
|
55
|
-
"status": "healthy",
|
|
56
|
-
"statusCode": 200,
|
|
57
|
-
"responseTime": 200,
|
|
58
|
-
"reachable": true
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
"integrations": {
|
|
62
|
-
"status": "healthy",
|
|
63
|
-
"modules": {
|
|
64
|
-
"count": 10,
|
|
65
|
-
"available": ["module1", "module2", "..."]
|
|
66
|
-
},
|
|
67
|
-
"integrations": {
|
|
68
|
-
"count": 5,
|
|
69
|
-
"available": ["integration1", "integration2", "..."]
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
"responseTime": 250 // total endpoint response time in milliseconds
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
**Status Codes:**
|
|
78
|
-
- `200 OK` - Service is healthy (all components operational)
|
|
79
|
-
- `503 Service Unavailable` - Service is unhealthy (any component failure)
|
|
80
|
-
- `401 Unauthorized` - Missing or invalid x-api-key header
|
|
81
|
-
|
|
82
|
-
### 3. Liveness Probe
|
|
83
|
-
**GET** `/health/live`
|
|
84
|
-
|
|
85
|
-
Kubernetes-style liveness probe. Returns whether the service process is alive.
|
|
86
|
-
|
|
87
|
-
**Authentication Required:**
|
|
88
|
-
- Header: `x-api-key: YOUR_API_KEY`
|
|
89
|
-
|
|
90
|
-
**Response:**
|
|
91
|
-
```json
|
|
92
|
-
{
|
|
93
|
-
"status": "alive",
|
|
94
|
-
"timestamp": "2024-01-10T12:00:00.000Z"
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
**Status Codes:**
|
|
99
|
-
- `200 OK` - Service process is alive
|
|
100
|
-
|
|
101
|
-
### 4. Readiness Probe
|
|
102
|
-
**GET** `/health/ready`
|
|
103
|
-
|
|
104
|
-
Kubernetes-style readiness probe. Returns whether the service is ready to receive traffic.
|
|
105
|
-
|
|
106
|
-
**Authentication Required:**
|
|
107
|
-
- Header: `x-api-key: YOUR_API_KEY`
|
|
108
|
-
|
|
109
|
-
**Response:**
|
|
110
|
-
```json
|
|
111
|
-
{
|
|
112
|
-
"ready": true,
|
|
113
|
-
"timestamp": "2024-01-10T12:00:00.000Z",
|
|
114
|
-
"checks": {
|
|
115
|
-
"database": true,
|
|
116
|
-
"modules": true
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
**Status Codes:**
|
|
122
|
-
- `200 OK` - Service is ready
|
|
123
|
-
- `503 Service Unavailable` - Service is not ready
|
|
124
|
-
|
|
125
|
-
## Health Status Definitions
|
|
126
|
-
|
|
127
|
-
- **healthy**: All components are functioning normally
|
|
128
|
-
- **unhealthy**: Any component is failing, service may not function properly
|
|
129
|
-
|
|
130
|
-
## Component Checks
|
|
131
|
-
|
|
132
|
-
### Database Connectivity
|
|
133
|
-
- Checks database connection state
|
|
134
|
-
- Performs ping test with 2-second timeout if connected
|
|
135
|
-
- Reports connection state and response time
|
|
136
|
-
- Database type is not exposed for security reasons
|
|
137
|
-
|
|
138
|
-
### External API Connectivity
|
|
139
|
-
- Tests connectivity to external services (GitHub, npm registry)
|
|
140
|
-
- Configurable timeout (default: 5 seconds)
|
|
141
|
-
- Reports reachability and response times
|
|
142
|
-
- Uses Promise.all for parallel checking
|
|
143
|
-
|
|
144
|
-
### Integration Status
|
|
145
|
-
- Verifies available modules and integrations are loaded
|
|
146
|
-
- Reports counts and lists of available components
|
|
147
|
-
|
|
148
|
-
## Usage Examples
|
|
149
|
-
|
|
150
|
-
### Monitoring Systems
|
|
151
|
-
Configure your monitoring system to poll `/health/detailed` every 30-60 seconds:
|
|
152
|
-
```bash
|
|
153
|
-
curl -H "x-api-key: YOUR_API_KEY" https://your-frigg-instance.com/health/detailed
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### Load Balancer Health Checks
|
|
157
|
-
Configure load balancers to use the simple `/health` endpoint:
|
|
158
|
-
```bash
|
|
159
|
-
curl https://your-frigg-instance.com/health
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
### Kubernetes Configuration
|
|
163
|
-
```yaml
|
|
164
|
-
livenessProbe:
|
|
165
|
-
httpGet:
|
|
166
|
-
path: /health/live
|
|
167
|
-
port: 8080
|
|
168
|
-
httpHeaders:
|
|
169
|
-
- name: x-api-key
|
|
170
|
-
value: YOUR_API_KEY
|
|
171
|
-
periodSeconds: 10
|
|
172
|
-
timeoutSeconds: 5
|
|
173
|
-
|
|
174
|
-
readinessProbe:
|
|
175
|
-
httpGet:
|
|
176
|
-
path: /health/ready
|
|
177
|
-
port: 8080
|
|
178
|
-
httpHeaders:
|
|
179
|
-
- name: x-api-key
|
|
180
|
-
value: YOUR_API_KEY
|
|
181
|
-
initialDelaySeconds: 30
|
|
182
|
-
periodSeconds: 10
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
## Customization
|
|
186
|
-
|
|
187
|
-
### Adding External API Checks
|
|
188
|
-
To add more external API checks, modify the `externalAPIs` array in the health router:
|
|
189
|
-
```javascript
|
|
190
|
-
const externalAPIs = [
|
|
191
|
-
{ name: 'github', url: 'https://api.github.com/status' },
|
|
192
|
-
{ name: 'npm', url: 'https://registry.npmjs.org' },
|
|
193
|
-
{ name: 'your-api', url: 'https://your-api.com/health' }
|
|
194
|
-
];
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Adjusting Timeouts
|
|
198
|
-
The default timeout for external API checks is 5 seconds. Database ping timeout is set to 2 seconds:
|
|
199
|
-
```javascript
|
|
200
|
-
const checkExternalAPI = (url, timeout = 5000) => {
|
|
201
|
-
// ...
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
await mongoose.connection.db.admin().ping({ maxTimeMS: 2000 });
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
## Best Practices
|
|
208
|
-
|
|
209
|
-
1. **Authentication**: Basic `/health` endpoint requires no authentication, but detailed endpoints require `x-api-key` header
|
|
210
|
-
2. **Rate Limiting**: Configure rate limiting at the API Gateway level to prevent abuse
|
|
211
|
-
3. **Fast Response**: Health checks should respond quickly (< 1 second)
|
|
212
|
-
4. **Strict Status Codes**: Return 503 for any non-healthy state to ensure proper alerting
|
|
213
|
-
5. **Detailed Logging**: Failed health checks are logged for debugging
|
|
214
|
-
6. **Security**: No sensitive information (DB types, versions) exposed in responses
|
|
215
|
-
7. **Lambda Considerations**: Uptime and memory metrics not included as they're not relevant in serverless
|
|
216
|
-
|
|
217
|
-
## Troubleshooting
|
|
218
|
-
|
|
219
|
-
### Database Connection Issues
|
|
220
|
-
- Check `MONGO_URI` environment variable
|
|
221
|
-
- Verify network connectivity to MongoDB
|
|
222
|
-
- Check MongoDB server status
|
|
223
|
-
|
|
224
|
-
### External API Failures
|
|
225
|
-
- May indicate network issues or external service downtime
|
|
226
|
-
- Service reports "unhealthy" status if any external API is unreachable
|
|
227
|
-
|
|
228
|
-
## Security Considerations
|
|
229
|
-
|
|
230
|
-
- Basic health endpoint requires no authentication for monitoring compatibility
|
|
231
|
-
- Detailed endpoints require `x-api-key` header authentication
|
|
232
|
-
- Health endpoints do not expose sensitive information
|
|
233
|
-
- Database connection strings and credentials are never included in responses
|
|
234
|
-
- External API checks use read-only endpoints
|
|
235
|
-
- Rate limiting should be configured at the API Gateway level
|
|
236
|
-
- Consider IP whitelisting for health endpoints in production
|
|
237
|
-
|
|
238
|
-
## Environment Variables
|
|
239
|
-
|
|
240
|
-
- `HEALTH_API_KEY`: Required API key for accessing detailed health endpoints
|