@kernelminds/create-enclave 0.0.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.
@@ -0,0 +1,281 @@
1
+ import Fastify, { FastifyReply, FastifyRequest } from "fastify";
2
+ import fastifyStatic from "@fastify/static";
3
+ import * as path from "path";
4
+ import * as fs from "fs";
5
+ import { createConnectTransport } from "@connectrpc/connect-node";
6
+ import { BOOL_FILTER, getScailoClientForLoginService, getScailoClientForPurchasesOrdersService, getScailoClientForVaultService, getScailoClientForVendorsService, WorkflowEvent } from "@kernelminds/scailo-sdk";
7
+ import { createClient } from "redis";
8
+ import type { FastifyCookieOptions } from "@fastify/cookie";
9
+ import cookie from "@fastify/cookie";
10
+ import { getEnclavePrefix } from "./utils";
11
+
12
+ const ENCLAVE_NAME = (process.env.ENCLAVE_NAME || "").trim();
13
+ const SCAILO_API = (process.env.SCAILO_API || "").trim();
14
+ const PORT = parseInt(process.env.PORT || "0");
15
+ const USERNAME = (process.env.USERNAME || "").trim();
16
+ const PASSWORD = (process.env.PASSWORD || "").trim();
17
+ const REDIS_USERNAME = (process.env.REDIS_USERNAME || "").trim();
18
+ const REDIS_PASSWORD = (process.env.REDIS_PASSWORD || "").trim();
19
+ const REDIS_URL = (process.env.REDIS_URL || "").trim();
20
+ const WORKFLOW_EVENTS_CHANNEL = (process.env.WORKFLOW_EVENTS_CHANNEL || "").trim();
21
+ /**If this has to be a constant, this can be overridden by creating an environment variable */
22
+ const COOKIE_SIGNATURE_SECRET = (process.env.COOKIE_SIGNATURE_SECRET || "").trim();
23
+
24
+ if (ENCLAVE_NAME == undefined || ENCLAVE_NAME == null || ENCLAVE_NAME == "") {
25
+ console.log("ENCLAVE_NAME not set");
26
+ process.exit(1);
27
+ }
28
+
29
+ if (SCAILO_API == undefined || SCAILO_API == null || SCAILO_API == "") {
30
+ console.log("SCAILO_API not set");
31
+ process.exit(1);
32
+ }
33
+
34
+ if (PORT == undefined || PORT == null || PORT == 0) {
35
+ console.log("PORT not set");
36
+ process.exit(1);
37
+ }
38
+
39
+ if (USERNAME == undefined || USERNAME == null || USERNAME == "") {
40
+ console.log("USERNAME not set");
41
+ process.exit(1);
42
+ }
43
+
44
+ if (PASSWORD == undefined || PASSWORD == null || PASSWORD == "") {
45
+ console.log("PASSWORD not set");
46
+ process.exit(1);
47
+ }
48
+
49
+ if (REDIS_URL == undefined || REDIS_URL == null || REDIS_URL == "") {
50
+ console.log("REDIS_URL not set");
51
+ process.exit(1);
52
+ }
53
+
54
+ if (WORKFLOW_EVENTS_CHANNEL == undefined || WORKFLOW_EVENTS_CHANNEL == null || WORKFLOW_EVENTS_CHANNEL == "") {
55
+ console.log("WORKFLOW_EVENTS_CHANNEL not set");
56
+ process.exit(1);
57
+ }
58
+
59
+ if (COOKIE_SIGNATURE_SECRET == undefined || COOKIE_SIGNATURE_SECRET == null || COOKIE_SIGNATURE_SECRET == "") {
60
+ console.log("COOKIE_SIGNATURE_SECRET not set");
61
+ process.exit(1);
62
+ }
63
+
64
+ const redisConnectionString = `redis://${REDIS_USERNAME}:${REDIS_PASSWORD}@${REDIS_URL}`;
65
+
66
+ async function setupWorkflowEventsCapture() {
67
+ const client = await createClient({ url: redisConnectionString })
68
+ .on('error', err => server.log.info('Redis Client Error', err))
69
+ .connect();
70
+ client.subscribe(WORKFLOW_EVENTS_CHANNEL, (incomingMessage) => {
71
+ const message = JSON.parse(incomingMessage) as WorkflowEvent;
72
+ console.log("Received Workflow Event: " + JSON.stringify(message));
73
+ });
74
+ }
75
+
76
+ function getTransport(apiEndPoint: string) {
77
+ return createConnectTransport({
78
+ baseUrl: apiEndPoint, httpVersion: "1.1", useBinaryFormat: false, interceptors: []
79
+ });
80
+ }
81
+
82
+ const transport = getTransport(SCAILO_API);
83
+ const server = Fastify({
84
+ trustProxy: true,
85
+ logger: {
86
+ level: 'info',
87
+ transport: {
88
+ target: 'pino-pretty'
89
+ }
90
+ }
91
+ });
92
+ const loginClient = getScailoClientForLoginService(transport);
93
+ const vaultClient = getScailoClientForVaultService(transport);
94
+ const purchaseOrdersClient = getScailoClientForPurchasesOrdersService(transport);
95
+ const vendorsClient = getScailoClientForVendorsService(transport);
96
+
97
+ let authToken = "";
98
+ let production = false;
99
+ if (process.env.PRODUCTION && process.env.PRODUCTION == "true") {
100
+ production = true;
101
+ }
102
+
103
+ const enclavePrefix = getEnclavePrefix(ENCLAVE_NAME);
104
+
105
+ async function loginToAPI() {
106
+ console.log("About to login to API")
107
+ try {
108
+ loginClient.loginAsEmployeePrimary({ username: USERNAME, plainTextPassword: PASSWORD }).then(response => {
109
+ authToken = response.authToken;
110
+ console.log("Logged in with auth token: " + authToken);
111
+ });
112
+ } catch (e) {
113
+ console.error(e);
114
+ } finally {
115
+ setTimeout(() => {
116
+ loginToAPI();
117
+ }, 3600 * 12 * 1000);
118
+ }
119
+ }
120
+
121
+ /**Register the cookie plugin */
122
+ server.register(cookie, {
123
+ secret: COOKIE_SIGNATURE_SECRET, // for cookies signature
124
+ parseOptions: {} // options for parsing cookies
125
+ } as FastifyCookieOptions)
126
+
127
+ // ------------------------------------------------------------------------------------------
128
+ // Register static routes here (this will serve the correct favicon from any route)
129
+ server.register(require('fastify-favicon'), { path: `./resources/dist/img`, name: 'favicon.ico', maxAge: 3600 })
130
+ // Setup static handler for web/
131
+ server.register(fastifyStatic, {
132
+ root: path.join(process.cwd(), 'resources', 'dist'),
133
+ prefix: `${enclavePrefix}/resources/dist`, // optional: default '/'
134
+ decorateReply: false,
135
+ constraints: {} // optional: default {}
136
+ });
137
+
138
+ let indexPage = fs.readFileSync(path.join(process.cwd(), 'index.html'), { encoding: 'utf-8' });
139
+ server.get("/*", async (request, reply) => {
140
+ if (!production) {
141
+ indexPage = fs.readFileSync(path.join(process.cwd(), 'index.html'), { encoding: 'utf-8' });
142
+ }
143
+ reply.header("Content-Type", "text/html");
144
+ reply.send(replaceBundleCaches(indexPage));
145
+ });
146
+
147
+ // Implicit redirect for entry_point_management = direct_url
148
+ server.get(`/`, async (request, reply) => {
149
+ reply.redirect(`${enclavePrefix}/ui`);
150
+ });
151
+
152
+ server.get(`${enclavePrefix}/ui`, async (request, reply) => {
153
+ if (!production) {
154
+ indexPage = fs.readFileSync(path.join(process.cwd(), 'index.html'), { encoding: 'utf-8' });
155
+ }
156
+ reply.header("Content-Type", "text/html");
157
+ reply.send(replaceBundleCaches(indexPage));
158
+ });
159
+
160
+ server.get(`${enclavePrefix}/ui/*`, async (request, reply) => {
161
+ if (!production) {
162
+ indexPage = fs.readFileSync(path.join(process.cwd(), 'index.html'), { encoding: 'utf-8' });
163
+ }
164
+ reply.header("Content-Type", "text/html");
165
+ reply.send(replaceBundleCaches(indexPage));
166
+ });
167
+
168
+ function appendDefaultHeader({ authTokenToAdd }: { authTokenToAdd?: string }) {
169
+ if (!authTokenToAdd) {
170
+ authTokenToAdd = authToken;
171
+ }
172
+ return {
173
+ "auth_token": authTokenToAdd
174
+ }
175
+ }
176
+
177
+ /**Handle the ingress -> sets the auth token and redirects to the entry point */
178
+ server.get(`${enclavePrefix}/ingress/:token`, async (request, reply) => {
179
+ try {
180
+ if (!production) {
181
+ // In dev, use the default auth token
182
+ reply.setCookie(`${ENCLAVE_NAME}_auth_token`, authToken, {
183
+ path: "/",
184
+ signed: true,
185
+ expires: new Date(Date.now() + 3600 * 1000)
186
+ });
187
+ } else {
188
+ const token = (<any>request.params).token;
189
+ if (!token) {
190
+ reply.status(400).send("Missing token");
191
+ return;
192
+ }
193
+ // Correctly verify the ingress token
194
+ const ingress = await vaultClient.verifyEnclaveIngress({ token }, { headers: appendDefaultHeader({ authTokenToAdd: authToken }) });
195
+ reply.setCookie(`${ENCLAVE_NAME}_auth_token`, ingress.authToken, {
196
+ path: "/",
197
+ signed: true,
198
+ expires: new Date(parseInt(ingress.expiresAt.toString()) * 1000)
199
+ });
200
+ }
201
+
202
+ reply.redirect(`${enclavePrefix}/ui`);
203
+ } catch (e) {
204
+ reply.code(500).send(e);
205
+ }
206
+ });
207
+
208
+ /**Handles the protected routes -> verifies the auth token and calls the handler */
209
+ async function handleProtectedRoute(request: FastifyRequest, reply: FastifyReply, handler: (userAuthToken: string) => void) {
210
+ try {
211
+ let cookieValue = request.cookies[`${ENCLAVE_NAME}_auth_token`];
212
+ if (!cookieValue || cookieValue == "") {
213
+ server.log.error("No auth token found");
214
+ reply.redirect(`${enclavePrefix}/ui`);
215
+ }
216
+ const userAuthToken = request.unsignCookie(cookieValue!).value;
217
+ if (!userAuthToken || userAuthToken == "") {
218
+ server.log.error("No auth token found");
219
+ reply.redirect(`${enclavePrefix}/ui`);
220
+ }
221
+
222
+ handler(userAuthToken!);
223
+ } catch (e) {
224
+ reply.code(500).send(e);
225
+ }
226
+ }
227
+
228
+ server.get(`${enclavePrefix}/protected/api/random`, async (request, reply) => {
229
+ handleProtectedRoute(request, reply, async (userAuthToken) => {
230
+ const [purchaseOrdersList, vendorsList] = await Promise.all([
231
+ purchaseOrdersClient.filter({
232
+ isActive: BOOL_FILTER.BOOL_FILTER_TRUE,
233
+ count: BigInt(5),
234
+ }, { headers: appendDefaultHeader({ authTokenToAdd: userAuthToken! }) }),
235
+
236
+ vendorsClient.filter({
237
+ isActive: BOOL_FILTER.BOOL_FILTER_TRUE,
238
+ count: BigInt(5),
239
+ }, { headers: appendDefaultHeader({ authTokenToAdd: userAuthToken! }) })
240
+ ]);
241
+
242
+ reply.send({ random: Math.random(), purchaseOrders: purchaseOrdersList.list, vendors: vendorsList.list });
243
+ });
244
+ })
245
+
246
+ server.get(`${enclavePrefix}/api/random`, async (request, reply) => {
247
+ reply.header("Content-Type", "application/json");
248
+ reply.send({ random: Math.random() });
249
+ });
250
+
251
+ server.get(`${enclavePrefix}/health/startup`, async (request, reply) => {
252
+ reply.send({ status: "OK" });
253
+ });
254
+
255
+ server.get(`${enclavePrefix}/health/liveliness`, async (request, reply) => {
256
+ reply.send({ status: "OK" });
257
+ });
258
+
259
+ server.get(`${enclavePrefix}/health/readiness`, async (request, reply) => {
260
+ reply.send({ status: "OK" });
261
+ });
262
+
263
+ function replaceBundleCaches(page: string) {
264
+ const version = new Date().toISOString();
265
+ page = page.replace(`<link rel="preload" as="script" href="${enclavePrefix}/resources/dist/js/bundle.src.min.js">`, `<link rel="preload" as="script" href="${enclavePrefix}/resources/dist/js/bundle.src.min.js?v=${version}">`)
266
+ page = page.replace(`<script src="${enclavePrefix}/resources/dist/js/bundle.src.min.js"></script>`, `<script src="${enclavePrefix}/resources/dist/js/bundle.src.min.js?v=${version}"></script>`)
267
+ page = page.replace(`<link rel="stylesheet" href="${enclavePrefix}/resources/dist/css/bundle.css">`, `<link rel="stylesheet" href="${enclavePrefix}/resources/dist/css/bundle.css?v=${version}">`)
268
+ return page
269
+ }
270
+
271
+ // ------------------------------------------------------------------------------------------
272
+ server.setNotFoundHandler((request, reply) => {
273
+ reply.redirect("/");
274
+ })
275
+ // ------------------------------------------------------------------------------------------
276
+
277
+ // ------------------------------------------------------------------------------------------
278
+ setupWorkflowEventsCapture();
279
+ console.log(`Listening on port ${PORT} with Production: ${production}`);
280
+ loginToAPI();
281
+ server.listen({ port: PORT, host: '0.0.0.0' });
@@ -0,0 +1,3 @@
1
+ export function getEnclavePrefix(enclaveName: string): string {
2
+ return `/enclave/${enclaveName}`;
3
+ }
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,14 @@
1
+ [project]
2
+ name = "python"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = [
8
+ "aiohttp>=3.13.2",
9
+ "aiohttp-session>=2.12.1",
10
+ "cryptography>=46.0.3",
11
+ "dotenv>=0.9.9",
12
+ "redis>=7.1.0",
13
+ "scailo-sdk>=0.2.9",
14
+ ]