@heliyos/heliyos-api-core 1.0.70 → 1.0.72
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/dist/authentication.d.ts +1 -0
- package/dist/authentication.js +65 -7
- package/dist/authorization.d.ts +12 -2
- package/dist/authorization.js +31 -5
- package/dist/middleware.js +7 -13
- package/dist/static/authPolicyFile.d.ts +2 -0
- package/dist/static/authPolicyFile.js +6 -0
- package/dist/static/authPolicyFile.ts +14 -0
- package/package.json +1 -1
package/dist/authentication.d.ts
CHANGED
package/dist/authentication.js
CHANGED
|
@@ -23,6 +23,37 @@ exports.authentication = void 0;
|
|
|
23
23
|
const basic_auth_1 = __importDefault(require("basic-auth"));
|
|
24
24
|
const customError_1 = require("./@types/globals/customError");
|
|
25
25
|
const _1 = require(".");
|
|
26
|
+
const BASIC_AUTH_ALLOWED_PATH_PREFIXES = [
|
|
27
|
+
"/v1/auth/",
|
|
28
|
+
"/v1/platform/billing/internal/",
|
|
29
|
+
"/v1/platform/usage/internal/log",
|
|
30
|
+
];
|
|
31
|
+
const canAccessWithBasicAuth = (path) => {
|
|
32
|
+
if (!path) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return BASIC_AUTH_ALLOWED_PATH_PREFIXES.some((prefix) => path.startsWith(prefix));
|
|
36
|
+
};
|
|
37
|
+
const getCandidateSessionCookieNames = () => {
|
|
38
|
+
const explicitNodeEnv = String(process.env.NODE_ENV || "").trim();
|
|
39
|
+
const explicitPublicEnv = String(process.env.NEXT_PUBLIC_ENV || "").trim();
|
|
40
|
+
const names = new Set(["user_session"]);
|
|
41
|
+
[explicitNodeEnv, explicitPublicEnv, "production", "development", "local"]
|
|
42
|
+
.filter(Boolean)
|
|
43
|
+
.forEach((envName) => names.add(`${envName}_user_session`));
|
|
44
|
+
return Array.from(names);
|
|
45
|
+
};
|
|
46
|
+
const getUserSessionCookieFromRequest = (req) => {
|
|
47
|
+
const cookies = req.cookies || {};
|
|
48
|
+
const cookieNames = getCandidateSessionCookieNames();
|
|
49
|
+
for (const name of cookieNames) {
|
|
50
|
+
const value = cookies[name];
|
|
51
|
+
if (value) {
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
};
|
|
26
57
|
/**
|
|
27
58
|
* Function to check internal and external authentication
|
|
28
59
|
* @param req
|
|
@@ -36,7 +67,7 @@ const authentication = (req, res, next) => __awaiter(void 0, void 0, void 0, fun
|
|
|
36
67
|
const container = {
|
|
37
68
|
input: {
|
|
38
69
|
authentication_header: req.headers.authorization,
|
|
39
|
-
user_session_cookie: req
|
|
70
|
+
user_session_cookie: getUserSessionCookieFromRequest(req),
|
|
40
71
|
ip: getIp(req),
|
|
41
72
|
auth_type: undefined,
|
|
42
73
|
},
|
|
@@ -46,7 +77,10 @@ const authentication = (req, res, next) => __awaiter(void 0, void 0, void 0, fun
|
|
|
46
77
|
},
|
|
47
78
|
};
|
|
48
79
|
// Check for the type of authentication
|
|
49
|
-
checkAuthType(container, res);
|
|
80
|
+
const authTypeCheckResponse = checkAuthType(container, res);
|
|
81
|
+
if (authTypeCheckResponse) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
50
84
|
// Based on type, do further authentication
|
|
51
85
|
// Either of BASIC / COOKIE / BEARER
|
|
52
86
|
const authenticationResponse = yield authenticateRequest(container);
|
|
@@ -62,6 +96,16 @@ const authentication = (req, res, next) => __awaiter(void 0, void 0, void 0, fun
|
|
|
62
96
|
message: "User not authenticated, Invalid token",
|
|
63
97
|
});
|
|
64
98
|
}
|
|
99
|
+
if (container.output.isBasicAuth &&
|
|
100
|
+
!canAccessWithBasicAuth(req.path || req.originalUrl || "")) {
|
|
101
|
+
return res.status(403).json({
|
|
102
|
+
error: {
|
|
103
|
+
status: 403,
|
|
104
|
+
err_msg: "FORBIDDEN",
|
|
105
|
+
},
|
|
106
|
+
message: "Basic authentication is restricted to internal service endpoints.",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
65
109
|
// Set logged in user data which can be used later on
|
|
66
110
|
setLoggedInUser(container, req);
|
|
67
111
|
// Move to next chain
|
|
@@ -137,7 +181,15 @@ const isJwtToken = (token) => {
|
|
|
137
181
|
try {
|
|
138
182
|
if (token.indexOf(".") > -1) {
|
|
139
183
|
const token_parts = token.split(".");
|
|
140
|
-
|
|
184
|
+
if (!token_parts[0]) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
const normalizedHeader = token_parts[0]
|
|
188
|
+
.replace(/-/g, "+")
|
|
189
|
+
.replace(/_/g, "/");
|
|
190
|
+
const padding = (4 - (normalizedHeader.length % 4)) % 4;
|
|
191
|
+
const paddedHeader = normalizedHeader + "=".repeat(padding);
|
|
192
|
+
const token_detail_string = Buffer.from(paddedHeader, "base64");
|
|
141
193
|
const token_detail = JSON.parse(token_detail_string.toString());
|
|
142
194
|
if (token_detail.typ && token_detail.typ.toUpperCase() === "JWT") {
|
|
143
195
|
return true;
|
|
@@ -233,7 +285,7 @@ const setLoggedInUser = function (container, req) {
|
|
|
233
285
|
//
|
|
234
286
|
// Handle Cookie and Bearer token
|
|
235
287
|
const { output: { loggedInUser }, } = container;
|
|
236
|
-
const { token, userId, organizationId, role, userFullName } = loggedInUser;
|
|
288
|
+
const { token, userId, organizationId, role, userFullName, organizationBillingStatus } = loggedInUser;
|
|
237
289
|
// Modify req object with logged in user data
|
|
238
290
|
req.loggedInUser = {
|
|
239
291
|
token,
|
|
@@ -241,6 +293,7 @@ const setLoggedInUser = function (container, req) {
|
|
|
241
293
|
organizationId,
|
|
242
294
|
role,
|
|
243
295
|
userFullName,
|
|
296
|
+
organizationBillingStatus,
|
|
244
297
|
auth_type,
|
|
245
298
|
};
|
|
246
299
|
return undefined;
|
|
@@ -266,6 +319,7 @@ const callAuthApiServer = (token) => __awaiter(void 0, void 0, void 0, function*
|
|
|
266
319
|
organizationId: authResult.data.data.payload.organizationId,
|
|
267
320
|
role: authResult.data.data.payload.role,
|
|
268
321
|
userFullName: authResult.data.data.payload.userFullName,
|
|
322
|
+
organizationBillingStatus: authResult.data.data.payload.organizationBillingStatus,
|
|
269
323
|
};
|
|
270
324
|
}
|
|
271
325
|
else {
|
|
@@ -273,7 +327,9 @@ const callAuthApiServer = (token) => __awaiter(void 0, void 0, void 0, function*
|
|
|
273
327
|
}
|
|
274
328
|
}
|
|
275
329
|
catch (error) {
|
|
276
|
-
|
|
330
|
+
_1.logger.error("Error while verifying bearer token with auth service", {
|
|
331
|
+
error: (error === null || error === void 0 ? void 0 : error.message) || error,
|
|
332
|
+
});
|
|
277
333
|
throw error;
|
|
278
334
|
}
|
|
279
335
|
});
|
|
@@ -286,7 +342,7 @@ const verifyApiKey = (apiKey) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
286
342
|
var _a, _b, _c, _d;
|
|
287
343
|
try {
|
|
288
344
|
// Call api key service to verify api key
|
|
289
|
-
const apiRes = yield _1.axios.authServer.post(`/
|
|
345
|
+
const apiRes = yield _1.axios.authServer.post(`/v1/auth/api_key/verify`, {
|
|
290
346
|
apiKey,
|
|
291
347
|
});
|
|
292
348
|
if ((_b = (_a = apiRes.data) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.isValid) {
|
|
@@ -298,7 +354,9 @@ const verifyApiKey = (apiKey) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
298
354
|
}
|
|
299
355
|
}
|
|
300
356
|
catch (err) {
|
|
301
|
-
|
|
357
|
+
_1.logger.error("Error while verifying API key with auth service", {
|
|
358
|
+
error: (err === null || err === void 0 ? void 0 : err.message) || err,
|
|
359
|
+
});
|
|
302
360
|
// If verified api key not found then throw error
|
|
303
361
|
const error = new customError_1.HttpError("Invalid api key.");
|
|
304
362
|
error.status = "403";
|
package/dist/authorization.d.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Authorize the user with the resource action
|
|
2
|
+
* Authorize the user with the resource action.
|
|
3
|
+
* Also enforces billing gate: write actions (ADD/EDIT/DELETE/CREATE)
|
|
4
|
+
* are blocked when the org's billingStatus is canceled or expired.
|
|
5
|
+
* Read actions (VIEW_*) always pass through.
|
|
6
|
+
*
|
|
3
7
|
* @param organizationId
|
|
4
8
|
* @param userId
|
|
5
9
|
* @param resourceAction
|
|
10
|
+
* @param options.allowInactive - bypass inactive org check in auth service
|
|
11
|
+
* @param options.billingStatus - org billing status from session; used for write-gate
|
|
6
12
|
* @returns
|
|
7
13
|
*/
|
|
8
|
-
export declare const authorizeUser: <T = string, U = string>(organizationId: T, userId: U, resourceAction: string
|
|
14
|
+
export declare const authorizeUser: <T = string, U = string>(organizationId: T, userId: U, resourceAction: string, options?: {
|
|
15
|
+
allowInactive?: boolean;
|
|
16
|
+
billingStatus?: string;
|
|
17
|
+
userRole?: string;
|
|
18
|
+
}) => Promise<{
|
|
9
19
|
isAllowed: string;
|
|
10
20
|
userRole: string;
|
|
11
21
|
}>;
|
package/dist/authorization.js
CHANGED
|
@@ -12,25 +12,51 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.authorizeUser = void 0;
|
|
13
13
|
const customError_1 = require("./@types/globals/customError");
|
|
14
14
|
const axios_1 = require("./axios");
|
|
15
|
+
const BILLING_BLOCKED_STATUSES = ["canceled", "incomplete_expired"];
|
|
15
16
|
/**
|
|
16
|
-
* Authorize the user with the resource action
|
|
17
|
+
* Authorize the user with the resource action.
|
|
18
|
+
* Also enforces billing gate: write actions (ADD/EDIT/DELETE/CREATE)
|
|
19
|
+
* are blocked when the org's billingStatus is canceled or expired.
|
|
20
|
+
* Read actions (VIEW_*) always pass through.
|
|
21
|
+
*
|
|
17
22
|
* @param organizationId
|
|
18
23
|
* @param userId
|
|
19
24
|
* @param resourceAction
|
|
25
|
+
* @param options.allowInactive - bypass inactive org check in auth service
|
|
26
|
+
* @param options.billingStatus - org billing status from session; used for write-gate
|
|
20
27
|
* @returns
|
|
21
28
|
*/
|
|
22
29
|
// eslint-disable-next-line import/prefer-default-export, @typescript-eslint/naming-convention
|
|
23
|
-
const authorizeUser = (organizationId, userId, resourceAction) => __awaiter(void 0, void 0, void 0, function* () {
|
|
30
|
+
const authorizeUser = (organizationId, userId, resourceAction, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
24
31
|
try {
|
|
25
|
-
|
|
32
|
+
// Platform admin roles bypass billing gate entirely
|
|
33
|
+
const PLATFORM_ROLES = ["SUPER_ADMIN", "STAFF", "SUPPORT"];
|
|
34
|
+
const isPlatformAdmin = PLATFORM_ROLES.includes(String((options === null || options === void 0 ? void 0 : options.userRole) || "").toUpperCase());
|
|
35
|
+
// Billing gate: block write actions for orgs with canceled/expired billing
|
|
36
|
+
// Platform admins (SUPER_ADMIN etc.) are exempt
|
|
37
|
+
const isWriteAction = !resourceAction.startsWith("VIEW_");
|
|
38
|
+
if (!isPlatformAdmin &&
|
|
39
|
+
isWriteAction &&
|
|
40
|
+
(options === null || options === void 0 ? void 0 : options.billingStatus) &&
|
|
41
|
+
BILLING_BLOCKED_STATUSES.includes(options.billingStatus)) {
|
|
42
|
+
const error = new customError_1.HttpError("Your subscription is inactive. Please renew to continue.");
|
|
43
|
+
error.status = "403";
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
const authenticationResponse = yield axios_1.coreAxios.authServer.post(`/v1/auth/user/${userId}`, {
|
|
47
|
+
resourceAction,
|
|
48
|
+
organizationId,
|
|
49
|
+
allowInactive: Boolean(options === null || options === void 0 ? void 0 : options.allowInactive),
|
|
50
|
+
});
|
|
26
51
|
return authenticationResponse.data.data;
|
|
27
52
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
|
|
28
53
|
}
|
|
29
54
|
catch (err) {
|
|
30
|
-
|
|
55
|
+
const status = String((err === null || err === void 0 ? void 0 : err.status) || "");
|
|
56
|
+
if (status === "401") {
|
|
31
57
|
throw err;
|
|
32
58
|
}
|
|
33
|
-
if (
|
|
59
|
+
if (status === "403") {
|
|
34
60
|
throw err;
|
|
35
61
|
}
|
|
36
62
|
const error = new customError_1.HttpError("Something went wrong with Authentication");
|
package/dist/middleware.js
CHANGED
|
@@ -19,6 +19,7 @@ const serve_static_1 = __importDefault(require("serve-static"));
|
|
|
19
19
|
const authentication_1 = require("./authentication");
|
|
20
20
|
const allowedOrigin_1 = require("./allowedOrigin");
|
|
21
21
|
const customError_1 = require("./@types/globals/customError");
|
|
22
|
+
const logger_1 = require("./logger");
|
|
22
23
|
const genericErrorMessage = "Something went wrong";
|
|
23
24
|
const defaultErrorStatusCode = 500;
|
|
24
25
|
const nonProductionEnvironments = new Set(["development", "local", "test"]);
|
|
@@ -189,19 +190,12 @@ const handle_errors = (error, _, res, __) => {
|
|
|
189
190
|
description: sanitizeErrorDescription(description),
|
|
190
191
|
};
|
|
191
192
|
// Log original error
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
"\n" +
|
|
199
|
-
"Desc: " +
|
|
200
|
-
description +
|
|
201
|
-
"\n" +
|
|
202
|
-
"Stack: " +
|
|
203
|
-
stack +
|
|
204
|
-
"\n---\n=== End Error ===");
|
|
193
|
+
logger_1.logger.error("Unhandled middleware error", {
|
|
194
|
+
message,
|
|
195
|
+
status,
|
|
196
|
+
description,
|
|
197
|
+
stack,
|
|
198
|
+
});
|
|
205
199
|
// Provide stack track in env development and local
|
|
206
200
|
if (!isProductionEnvironment()) {
|
|
207
201
|
response.error = Object.assign({ stack }, error);
|
|
@@ -700,3 +700,9 @@ exports.authPolicy = {
|
|
|
700
700
|
],
|
|
701
701
|
},
|
|
702
702
|
};
|
|
703
|
+
// MEMBER is the canonical name (renamed from TEAM_MEMBER)
|
|
704
|
+
const memberRolePermissions = Array.from(new Set(exports.authPolicy.ROLES_PERMISSIONS.TEAM_MEMBER));
|
|
705
|
+
exports.authPolicy.ROLES_PERMISSIONS.MEMBER = memberRolePermissions;
|
|
706
|
+
// AGENCY has the same permissions as OWNER
|
|
707
|
+
const agencyRolePermissions = Array.from(new Set(exports.authPolicy.ROLES_PERMISSIONS.OWNER));
|
|
708
|
+
exports.authPolicy.ROLES_PERMISSIONS.AGENCY = agencyRolePermissions;
|
|
@@ -773,12 +773,26 @@ export const authPolicy: IAuthPolicy = {
|
|
|
773
773
|
},
|
|
774
774
|
};
|
|
775
775
|
|
|
776
|
+
// MEMBER is the canonical name (renamed from TEAM_MEMBER)
|
|
777
|
+
const memberRolePermissions = Array.from(
|
|
778
|
+
new Set(authPolicy.ROLES_PERMISSIONS.TEAM_MEMBER)
|
|
779
|
+
);
|
|
780
|
+
authPolicy.ROLES_PERMISSIONS.MEMBER = memberRolePermissions;
|
|
781
|
+
|
|
782
|
+
// AGENCY has the same permissions as OWNER
|
|
783
|
+
const agencyRolePermissions = Array.from(
|
|
784
|
+
new Set(authPolicy.ROLES_PERMISSIONS.OWNER)
|
|
785
|
+
);
|
|
786
|
+
authPolicy.ROLES_PERMISSIONS.AGENCY = agencyRolePermissions;
|
|
787
|
+
|
|
776
788
|
interface IAuthPolicy {
|
|
777
789
|
RESOURCES_ACTIONS: ResourcePolicyActionsType;
|
|
778
790
|
ROLES_PERMISSIONS: RolesPermissionsType;
|
|
779
791
|
}
|
|
780
792
|
|
|
781
793
|
export type RolesPermissionsType = {
|
|
794
|
+
MEMBER?: string[];
|
|
795
|
+
AGENCY?: string[];
|
|
782
796
|
TEAM_MEMBER: string[];
|
|
783
797
|
ADMIN: string[];
|
|
784
798
|
OWNER: string[];
|