c8y-nitro 0.3.0 → 0.4.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/dist/{cli/commands/bootstrap.mjs → bootstrap-BqWPkH8q.mjs} +5 -8
- package/dist/{cli/utils/c8y-api.mjs → c8y-api-BBSKRwKs.mjs} +73 -3
- package/dist/cli/index.mjs +5 -7
- package/dist/{cli/utils/config.mjs → config-Dqi-ttQi.mjs} +1 -3
- package/dist/{cli/utils/env-file.mjs → env-file-B0BK-uZW.mjs} +1 -3
- package/dist/{types/manifest.d.mts → index-CzUqbp5C.d.mts} +94 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +549 -14
- package/dist/{cli/commands/options.mjs → options-BDDJWdph.mjs} +3 -6
- package/dist/{package.mjs → package-CobwNpP9.mjs} +2 -3
- package/dist/{cli/commands/roles.mjs → roles-DJxp2d8p.mjs} +3 -6
- package/dist/runtime/handlers/liveness-readiness.mjs +7 -0
- package/dist/runtime/middlewares/dev-user.mjs +23 -0
- package/dist/runtime/plugins/c8y-variables.mjs +17 -0
- package/dist/runtime/plugins/enrich-logs.mjs +15 -0
- package/dist/types.d.mts +2 -25
- package/dist/types.mjs +1 -1
- package/dist/utils.d.mts +292 -6
- package/dist/utils.mjs +444 -8
- package/package.json +12 -11
- package/dist/module/apiClient.mjs +0 -207
- package/dist/module/autoBootstrap.mjs +0 -54
- package/dist/module/c8yzip.mjs +0 -66
- package/dist/module/constants.mjs +0 -6
- package/dist/module/docker.mjs +0 -101
- package/dist/module/manifest.mjs +0 -72
- package/dist/module/probeCheck.mjs +0 -30
- package/dist/module/register.mjs +0 -58
- package/dist/module/runtime/handlers/liveness-readiness.ts +0 -7
- package/dist/module/runtime/middlewares/dev-user.ts +0 -25
- package/dist/module/runtime/plugins/c8y-variables.ts +0 -24
- package/dist/module/runtime.mjs +0 -38
- package/dist/module/runtimeConfig.mjs +0 -20
- package/dist/types/apiClient.d.mts +0 -16
- package/dist/types/cache.d.mts +0 -28
- package/dist/types/roles.d.mts +0 -4
- package/dist/types/tenantOptions.d.mts +0 -13
- package/dist/types/zip.d.mts +0 -22
- package/dist/utils/client.d.mts +0 -52
- package/dist/utils/client.mjs +0 -90
- package/dist/utils/credentials.d.mts +0 -71
- package/dist/utils/credentials.mjs +0 -120
- package/dist/utils/internal/common.mjs +0 -26
- package/dist/utils/logging.d.mts +0 -3
- package/dist/utils/logging.mjs +0 -4
- package/dist/utils/middleware.d.mts +0 -89
- package/dist/utils/middleware.mjs +0 -62
- package/dist/utils/resources.d.mts +0 -30
- package/dist/utils/resources.mjs +0 -49
- package/dist/utils/tenantOptions.d.mts +0 -65
- package/dist/utils/tenantOptions.mjs +0 -127
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { useUserClient } from "./client.mjs";
|
|
2
|
-
import { useUserRoles } from "./resources.mjs";
|
|
3
|
-
import process from "node:process";
|
|
4
|
-
import { HTTPError, defineHandler } from "nitro/h3";
|
|
5
|
-
|
|
6
|
-
//#region src/utils/middleware.ts
|
|
7
|
-
function hasUserRequiredRole(roleOrRoles) {
|
|
8
|
-
return defineHandler(async (event) => {
|
|
9
|
-
const requiredRoles = Array.isArray(roleOrRoles) ? roleOrRoles : [roleOrRoles];
|
|
10
|
-
const userRoles = await useUserRoles(event);
|
|
11
|
-
if (!requiredRoles.some((role) => userRoles.includes(role))) throw new HTTPError({
|
|
12
|
-
status: 403,
|
|
13
|
-
statusText: "Forbidden",
|
|
14
|
-
message: `User does not have required role(s) to access this resource: ${requiredRoles.join(", ")}`
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
function isUserFromAllowedTenant(tenantIdOrIds) {
|
|
19
|
-
return defineHandler(async (event) => {
|
|
20
|
-
const allowedTenants = Array.isArray(tenantIdOrIds) ? tenantIdOrIds : [tenantIdOrIds];
|
|
21
|
-
const userTenantId = useUserClient(event).core.tenant;
|
|
22
|
-
if (!allowedTenants.includes(userTenantId)) throw new HTTPError({
|
|
23
|
-
status: 403,
|
|
24
|
-
statusText: "Forbidden",
|
|
25
|
-
message: `User's tenant '${userTenantId}' is not allowed to access this resource. Allowed tenants: ${allowedTenants.join(", ")}`
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Middleware to check if the current user belongs to the deployed tenant.\
|
|
31
|
-
* The deployed tenant is where this microservice is hosted (C8Y_BOOTSTRAP_TENANT).\
|
|
32
|
-
* If the user's tenant doesn't match the deployed tenant, throws a 403 Forbidden error.\
|
|
33
|
-
* Must be used within a request handler context.\
|
|
34
|
-
* @returns Event handler that validates user is from deployed tenant
|
|
35
|
-
* @example
|
|
36
|
-
* // Only allow users from the deployed tenant:
|
|
37
|
-
* export default defineHandler({
|
|
38
|
-
* middleware: [isUserFromDeployedTenant()],
|
|
39
|
-
* handler: async () => {
|
|
40
|
-
* return { message: 'You have access' }
|
|
41
|
-
* }
|
|
42
|
-
* })
|
|
43
|
-
*/
|
|
44
|
-
function isUserFromDeployedTenant() {
|
|
45
|
-
return defineHandler(async (event) => {
|
|
46
|
-
const userTenantId = useUserClient(event).core.tenant;
|
|
47
|
-
const deployedTenantId = process.env.C8Y_BOOTSTRAP_TENANT;
|
|
48
|
-
if (!deployedTenantId) throw new HTTPError({
|
|
49
|
-
status: 500,
|
|
50
|
-
statusText: "Internal Server Error",
|
|
51
|
-
message: "C8Y_BOOTSTRAP_TENANT environment variable is not set"
|
|
52
|
-
});
|
|
53
|
-
if (userTenantId !== deployedTenantId) throw new HTTPError({
|
|
54
|
-
status: 403,
|
|
55
|
-
statusText: "Forbidden",
|
|
56
|
-
message: `Only users from tenant '${deployedTenantId}' can access this resource.`
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
//#endregion
|
|
62
|
-
export { hasUserRequiredRole, isUserFromAllowedTenant, isUserFromDeployedTenant };
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { ICurrentUser } from "@c8y/client";
|
|
2
|
-
import { H3Event } from "nitro/h3";
|
|
3
|
-
import { ServerRequest } from "nitro/types";
|
|
4
|
-
|
|
5
|
-
//#region src/utils/resources.d.ts
|
|
6
|
-
/**
|
|
7
|
-
* Fetches the current user from Cumulocity using credentials extracted from the current request's headers.
|
|
8
|
-
* This is a non-cached version - fetches fresh data on every call.
|
|
9
|
-
* @param requestOrEvent - The H3Event or ServerRequest from the current request
|
|
10
|
-
* @returns The current user object from Cumulocity
|
|
11
|
-
* @example
|
|
12
|
-
* // In a request handler:
|
|
13
|
-
* const user = await useUser(event)
|
|
14
|
-
* console.log(user.userName, user.email)
|
|
15
|
-
*/
|
|
16
|
-
declare function useUser(requestOrEvent: ServerRequest | H3Event): Promise<ICurrentUser>;
|
|
17
|
-
/**
|
|
18
|
-
* Fetches the roles of the current user from Cumulocity.
|
|
19
|
-
* Internally calls `useUser()` and extracts role IDs from the user object.
|
|
20
|
-
* This is a non-cached version - fetches fresh data on every call.
|
|
21
|
-
* @param requestOrEvent - The H3Event or ServerRequest from the current request
|
|
22
|
-
* @returns Array of role ID strings assigned to the current user
|
|
23
|
-
* @example
|
|
24
|
-
* // In a request handler:
|
|
25
|
-
* const roles = await useUserRoles(event)
|
|
26
|
-
* console.log(roles) // ['ROLE_INVENTORY_READ', 'ROLE_INVENTORY_ADMIN']
|
|
27
|
-
*/
|
|
28
|
-
declare function useUserRoles(requestOrEvent: ServerRequest | H3Event): Promise<string[]>;
|
|
29
|
-
//#endregion
|
|
30
|
-
export { useUser, useUserRoles };
|
package/dist/utils/resources.mjs
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { useUserClient } from "./client.mjs";
|
|
2
|
-
import { HTTPError } from "nitro/h3";
|
|
3
|
-
|
|
4
|
-
//#region src/utils/resources.ts
|
|
5
|
-
/**
|
|
6
|
-
* Fetches the current user from Cumulocity using credentials extracted from the current request's headers.
|
|
7
|
-
* This is a non-cached version - fetches fresh data on every call.
|
|
8
|
-
* @param requestOrEvent - The H3Event or ServerRequest from the current request
|
|
9
|
-
* @returns The current user object from Cumulocity
|
|
10
|
-
* @example
|
|
11
|
-
* // In a request handler:
|
|
12
|
-
* const user = await useUser(event)
|
|
13
|
-
* console.log(user.userName, user.email)
|
|
14
|
-
*/
|
|
15
|
-
async function useUser(requestOrEvent) {
|
|
16
|
-
const request = "req" in requestOrEvent ? requestOrEvent.req : requestOrEvent;
|
|
17
|
-
if (request.context?.["c8y_user"]) return request.context["c8y_user"];
|
|
18
|
-
const { res, data: user } = await useUserClient(requestOrEvent).user.currentWithEffectiveRoles();
|
|
19
|
-
if (!res.ok) throw new HTTPError({
|
|
20
|
-
message: `Failed to fetch current user`,
|
|
21
|
-
status: res.status,
|
|
22
|
-
statusText: res.statusText
|
|
23
|
-
});
|
|
24
|
-
request.context ??= {};
|
|
25
|
-
request.context["c8y_user"] = user;
|
|
26
|
-
return user;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Fetches the roles of the current user from Cumulocity.
|
|
30
|
-
* Internally calls `useUser()` and extracts role IDs from the user object.
|
|
31
|
-
* This is a non-cached version - fetches fresh data on every call.
|
|
32
|
-
* @param requestOrEvent - The H3Event or ServerRequest from the current request
|
|
33
|
-
* @returns Array of role ID strings assigned to the current user
|
|
34
|
-
* @example
|
|
35
|
-
* // In a request handler:
|
|
36
|
-
* const roles = await useUserRoles(event)
|
|
37
|
-
* console.log(roles) // ['ROLE_INVENTORY_READ', 'ROLE_INVENTORY_ADMIN']
|
|
38
|
-
*/
|
|
39
|
-
async function useUserRoles(requestOrEvent) {
|
|
40
|
-
const request = "req" in requestOrEvent ? requestOrEvent.req : requestOrEvent;
|
|
41
|
-
if (request.context?.["c8y_user_roles"]) return request.context["c8y_user_roles"];
|
|
42
|
-
const userRoles = (await useUser(requestOrEvent)).effectiveRoles?.map((role) => role.name) ?? [];
|
|
43
|
-
request.context ??= {};
|
|
44
|
-
request.context["c8y_user_roles"] = userRoles;
|
|
45
|
-
return userRoles;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
//#endregion
|
|
49
|
-
export { useUser, useUserRoles };
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { C8YTenantOptionKey } from "c8y-nitro/types";
|
|
2
|
-
|
|
3
|
-
//#region src/utils/tenantOptions.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Fetches a tenant option value by key.\
|
|
6
|
-
* Uses the deployed tenant's service user credentials to access the Options API.\
|
|
7
|
-
* Results are cached based on the configured TTL (default: 10 minutes).
|
|
8
|
-
*
|
|
9
|
-
* @param key - The tenant option key to fetch (as defined in `manifest.settings`)
|
|
10
|
-
* @returns The option value as a string
|
|
11
|
-
*
|
|
12
|
-
* @config Cache TTL can be configured via:
|
|
13
|
-
* - `c8y.cache.defaultTenantOptionsTTL` — Default TTL for all keys (in seconds)
|
|
14
|
-
* - `c8y.cache.tenantOptions` — Per-key TTL overrides
|
|
15
|
-
* - `NITRO_C8Y_DEFAULT_TENANT_OPTIONS_TTL` — Environment variable for default TTL
|
|
16
|
-
*
|
|
17
|
-
* @note For encrypted options (keys starting with `credentials.`), the value is automatically
|
|
18
|
-
* decrypted by Cumulocity if this microservice is the owner of the option (category matches
|
|
19
|
-
* the microservice's settingsCategory/contextPath/name). The `credentials.` prefix is
|
|
20
|
-
* automatically stripped when calling the API.
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* // Fetch a tenant option:
|
|
24
|
-
* const value = await useTenantOption('myOption')
|
|
25
|
-
*
|
|
26
|
-
* // Fetch an encrypted secret:
|
|
27
|
-
* const secret = await useTenantOption('credentials.apiKey')
|
|
28
|
-
*
|
|
29
|
-
* // Invalidate cache for a specific key:
|
|
30
|
-
* await useTenantOption.invalidate('myOption')
|
|
31
|
-
*
|
|
32
|
-
* // Force refresh a specific key:
|
|
33
|
-
* const fresh = await useTenantOption.refresh('myOption')
|
|
34
|
-
*
|
|
35
|
-
* // Invalidate all tenant option caches:
|
|
36
|
-
* await useTenantOption.invalidateAll()
|
|
37
|
-
*
|
|
38
|
-
* // Refresh all tenant options:
|
|
39
|
-
* const all = await useTenantOption.refreshAll()
|
|
40
|
-
*/
|
|
41
|
-
declare const useTenantOption: ((key: C8YTenantOptionKey) => Promise<string | undefined>) & {
|
|
42
|
-
/**
|
|
43
|
-
* Invalidate the cache for a specific tenant option key.
|
|
44
|
-
* @param key - The tenant option key to invalidate
|
|
45
|
-
*/
|
|
46
|
-
invalidate: (key: C8YTenantOptionKey) => Promise<void>;
|
|
47
|
-
/**
|
|
48
|
-
* Force refresh a specific tenant option key (invalidates and re-fetches).
|
|
49
|
-
* @param key - The tenant option key to refresh
|
|
50
|
-
*/
|
|
51
|
-
refresh: (key: C8YTenantOptionKey) => Promise<string | undefined>;
|
|
52
|
-
/**
|
|
53
|
-
* Invalidate all tenant option caches that have been accessed.
|
|
54
|
-
* Only invalidates keys that have been fetched at least once.
|
|
55
|
-
*/
|
|
56
|
-
invalidateAll: () => Promise<void>;
|
|
57
|
-
/**
|
|
58
|
-
* Refresh all tenant options that have been accessed.
|
|
59
|
-
* Only refreshes keys that have been fetched at least once.
|
|
60
|
-
* @returns Object mapping keys to their refreshed values
|
|
61
|
-
*/
|
|
62
|
-
refreshAll: () => Promise<Record<string, string | undefined>>;
|
|
63
|
-
};
|
|
64
|
-
//#endregion
|
|
65
|
-
export { useTenantOption };
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { useDeployedTenantClient } from "./client.mjs";
|
|
2
|
-
import { defineCachedFunction } from "nitro/cache";
|
|
3
|
-
import { useStorage } from "nitro/storage";
|
|
4
|
-
import { useRuntimeConfig } from "nitro/runtime-config";
|
|
5
|
-
|
|
6
|
-
//#region src/utils/tenantOptions.ts
|
|
7
|
-
/**
|
|
8
|
-
* Gets the cache TTL for a specific tenant option key.
|
|
9
|
-
* Uses per-key override if defined, otherwise falls back to default TTL.
|
|
10
|
-
* @param key - The tenant option key
|
|
11
|
-
*/
|
|
12
|
-
function getTenantOptionCacheTTL(key) {
|
|
13
|
-
const config = useRuntimeConfig();
|
|
14
|
-
return config.c8yTenantOptionsPerKeyTTL?.[key] ?? config.c8yDefaultTenantOptionsTTL ?? 600;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Internal storage for cached functions per key
|
|
18
|
-
*/
|
|
19
|
-
const tenantOptionFetchers = {};
|
|
20
|
-
/**
|
|
21
|
-
* Factory function that creates a cached fetcher for a specific tenant option key.
|
|
22
|
-
* @param key - The tenant option key
|
|
23
|
-
*/
|
|
24
|
-
function createCachedTenantOptionFetcher(key) {
|
|
25
|
-
const cacheName = `_c8y_nitro_tenant_option_${key.replace(/\./g, "_")}`;
|
|
26
|
-
const fetcher = defineCachedFunction(async () => {
|
|
27
|
-
const client = await useDeployedTenantClient();
|
|
28
|
-
const category = useRuntimeConfig().c8ySettingsCategory;
|
|
29
|
-
const apiKey = key.replace(/^credentials\./, "");
|
|
30
|
-
try {
|
|
31
|
-
return (await client.options.tenant.detail({
|
|
32
|
-
key: apiKey,
|
|
33
|
-
category
|
|
34
|
-
})).data.value;
|
|
35
|
-
} catch (error) {
|
|
36
|
-
if (error?.res?.status === 404 || error?.status === 404) return;
|
|
37
|
-
throw error;
|
|
38
|
-
}
|
|
39
|
-
}, {
|
|
40
|
-
maxAge: getTenantOptionCacheTTL(key),
|
|
41
|
-
name: cacheName,
|
|
42
|
-
group: "c8y_nitro",
|
|
43
|
-
swr: false
|
|
44
|
-
});
|
|
45
|
-
return Object.assign(fetcher, {
|
|
46
|
-
invalidate: async () => {
|
|
47
|
-
const completeKey = `c8y_nitro:functions:${cacheName}.json`;
|
|
48
|
-
await useStorage("cache").removeItem(completeKey);
|
|
49
|
-
},
|
|
50
|
-
refresh: async () => {
|
|
51
|
-
const completeKey = `c8y_nitro:functions:${cacheName}.json`;
|
|
52
|
-
await useStorage("cache").removeItem(completeKey);
|
|
53
|
-
return await fetcher();
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Gets or creates a cached fetcher for a specific tenant option key.
|
|
59
|
-
* @param key - The tenant option key
|
|
60
|
-
*/
|
|
61
|
-
function getOrCreateFetcher(key) {
|
|
62
|
-
let fetcher = tenantOptionFetchers[key];
|
|
63
|
-
if (!fetcher) {
|
|
64
|
-
fetcher = createCachedTenantOptionFetcher(key);
|
|
65
|
-
tenantOptionFetchers[key] = fetcher;
|
|
66
|
-
}
|
|
67
|
-
return fetcher;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Fetches a tenant option value by key.\
|
|
71
|
-
* Uses the deployed tenant's service user credentials to access the Options API.\
|
|
72
|
-
* Results are cached based on the configured TTL (default: 10 minutes).
|
|
73
|
-
*
|
|
74
|
-
* @param key - The tenant option key to fetch (as defined in `manifest.settings`)
|
|
75
|
-
* @returns The option value as a string
|
|
76
|
-
*
|
|
77
|
-
* @config Cache TTL can be configured via:
|
|
78
|
-
* - `c8y.cache.defaultTenantOptionsTTL` — Default TTL for all keys (in seconds)
|
|
79
|
-
* - `c8y.cache.tenantOptions` — Per-key TTL overrides
|
|
80
|
-
* - `NITRO_C8Y_DEFAULT_TENANT_OPTIONS_TTL` — Environment variable for default TTL
|
|
81
|
-
*
|
|
82
|
-
* @note For encrypted options (keys starting with `credentials.`), the value is automatically
|
|
83
|
-
* decrypted by Cumulocity if this microservice is the owner of the option (category matches
|
|
84
|
-
* the microservice's settingsCategory/contextPath/name). The `credentials.` prefix is
|
|
85
|
-
* automatically stripped when calling the API.
|
|
86
|
-
*
|
|
87
|
-
* @example
|
|
88
|
-
* // Fetch a tenant option:
|
|
89
|
-
* const value = await useTenantOption('myOption')
|
|
90
|
-
*
|
|
91
|
-
* // Fetch an encrypted secret:
|
|
92
|
-
* const secret = await useTenantOption('credentials.apiKey')
|
|
93
|
-
*
|
|
94
|
-
* // Invalidate cache for a specific key:
|
|
95
|
-
* await useTenantOption.invalidate('myOption')
|
|
96
|
-
*
|
|
97
|
-
* // Force refresh a specific key:
|
|
98
|
-
* const fresh = await useTenantOption.refresh('myOption')
|
|
99
|
-
*
|
|
100
|
-
* // Invalidate all tenant option caches:
|
|
101
|
-
* await useTenantOption.invalidateAll()
|
|
102
|
-
*
|
|
103
|
-
* // Refresh all tenant options:
|
|
104
|
-
* const all = await useTenantOption.refreshAll()
|
|
105
|
-
*/
|
|
106
|
-
const useTenantOption = Object.assign(async (key) => {
|
|
107
|
-
return await getOrCreateFetcher(key)();
|
|
108
|
-
}, {
|
|
109
|
-
invalidate: async (key) => {
|
|
110
|
-
const fetcher = tenantOptionFetchers[key];
|
|
111
|
-
if (fetcher) await fetcher.invalidate();
|
|
112
|
-
},
|
|
113
|
-
refresh: async (key) => {
|
|
114
|
-
return await getOrCreateFetcher(key).refresh();
|
|
115
|
-
},
|
|
116
|
-
invalidateAll: async () => {
|
|
117
|
-
await Promise.all(Object.values(tenantOptionFetchers).map((fetcher) => fetcher?.invalidate()));
|
|
118
|
-
},
|
|
119
|
-
refreshAll: async () => {
|
|
120
|
-
const entries = Object.entries(tenantOptionFetchers);
|
|
121
|
-
const values = await Promise.all(entries.map(([, fetcher]) => fetcher?.refresh()));
|
|
122
|
-
return Object.fromEntries(entries.map(([key], i) => [key, values[i]]));
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
//#endregion
|
|
127
|
-
export { useTenantOption };
|