@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.
- package/.vscode/settings.json +6 -0
- package/README.md +202 -0
- package/bin/create-enclave.js +616 -0
- package/img/favicon.ico +0 -0
- package/img/logo.png +0 -0
- package/package.json +46 -0
- package/server/golang/go.mod +50 -0
- package/server/golang/go.sum +198 -0
- package/server/golang/server.go +488 -0
- package/server/golang/utils.go +7 -0
- package/server/node/server.ts +281 -0
- package/server/node/utils.ts +3 -0
- package/server/python/.python-version +1 -0
- package/server/python/pyproject.toml +14 -0
- package/server/python/server.py +446 -0
- package/server/python/utils.py +2 -0
- package/server/python/uv.lock +798 -0
- package/src/create-enclave.ts +752 -0
- package/src/package.ts +179 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"log"
|
|
7
|
+
"math/rand"
|
|
8
|
+
"net/http"
|
|
9
|
+
"os"
|
|
10
|
+
"path/filepath"
|
|
11
|
+
"strconv"
|
|
12
|
+
"strings"
|
|
13
|
+
"sync"
|
|
14
|
+
"time"
|
|
15
|
+
|
|
16
|
+
"github.com/gin-gonic/gin"
|
|
17
|
+
"github.com/go-redis/redis"
|
|
18
|
+
"github.com/scailo/go-sdk"
|
|
19
|
+
"google.golang.org/grpc"
|
|
20
|
+
"google.golang.org/grpc/credentials"
|
|
21
|
+
"google.golang.org/grpc/credentials/insecure"
|
|
22
|
+
"google.golang.org/grpc/metadata"
|
|
23
|
+
"google.golang.org/protobuf/proto"
|
|
24
|
+
|
|
25
|
+
"github.com/gorilla/securecookie"
|
|
26
|
+
|
|
27
|
+
"github.com/joho/godotenv"
|
|
28
|
+
_ "github.com/joho/godotenv/autoload"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
// Config holds all necessary environment variables
|
|
32
|
+
type Config struct {
|
|
33
|
+
EnclaveName string
|
|
34
|
+
ScailoAPI string
|
|
35
|
+
Port int
|
|
36
|
+
Username string
|
|
37
|
+
Password string
|
|
38
|
+
|
|
39
|
+
RedisUsername string
|
|
40
|
+
RedisPassword string
|
|
41
|
+
RedisURL string
|
|
42
|
+
|
|
43
|
+
WorkflowEventsChannel string
|
|
44
|
+
|
|
45
|
+
CookieSignatureSecret string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Global state variables
|
|
49
|
+
var (
|
|
50
|
+
GlobalConfig Config
|
|
51
|
+
AuthToken string
|
|
52
|
+
// Use this context for all Scailo API calls
|
|
53
|
+
ScailoAPICtx context.Context
|
|
54
|
+
// Mutex to protect shared state
|
|
55
|
+
mu sync.RWMutex
|
|
56
|
+
production bool = false
|
|
57
|
+
indexPage string // Cached version of index.html
|
|
58
|
+
enclavePrefix string
|
|
59
|
+
RedisClient *redis.Client
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
const (
|
|
63
|
+
// 12-hour interval (3600 * 12)
|
|
64
|
+
loginInterval = 3600 * 12 * time.Second
|
|
65
|
+
indexHTMLFile = "index.html"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
func init() {
|
|
69
|
+
// Initialize the random number generator
|
|
70
|
+
// rand.Seed(time.Now().UnixNano())
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// loadConfig reads and validates environment variables.
|
|
74
|
+
func loadConfig() {
|
|
75
|
+
// Try loading .env
|
|
76
|
+
godotenv.Load(".env")
|
|
77
|
+
// We'll respect the user's `production` flag.
|
|
78
|
+
if os.Getenv("PRODUCTION") == "true" {
|
|
79
|
+
production = true
|
|
80
|
+
} else {
|
|
81
|
+
// Assume development mode by default
|
|
82
|
+
production = false
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 1. Read environment variables
|
|
86
|
+
GlobalConfig.EnclaveName = os.Getenv("ENCLAVE_NAME")
|
|
87
|
+
GlobalConfig.ScailoAPI = os.Getenv("SCAILO_API")
|
|
88
|
+
GlobalConfig.Username = os.Getenv("USERNAME")
|
|
89
|
+
GlobalConfig.Password = os.Getenv("PASSWORD")
|
|
90
|
+
|
|
91
|
+
GlobalConfig.RedisUsername = os.Getenv("REDIS_USERNAME")
|
|
92
|
+
GlobalConfig.RedisPassword = os.Getenv("REDIS_PASSWORD")
|
|
93
|
+
GlobalConfig.RedisURL = os.Getenv("REDIS_URL")
|
|
94
|
+
|
|
95
|
+
GlobalConfig.WorkflowEventsChannel = os.Getenv("WORKFLOW_EVENTS_CHANNEL")
|
|
96
|
+
GlobalConfig.CookieSignatureSecret = os.Getenv("COOKIE_SIGNATURE_SECRET")
|
|
97
|
+
|
|
98
|
+
portStr := os.Getenv("PORT")
|
|
99
|
+
if portStr != "" {
|
|
100
|
+
p, err := strconv.Atoi(portStr)
|
|
101
|
+
if err == nil {
|
|
102
|
+
GlobalConfig.Port = p
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 2. Validate environment variables
|
|
107
|
+
var exitCode = 0
|
|
108
|
+
if GlobalConfig.EnclaveName == "" {
|
|
109
|
+
log.Println("ENCLAVE_NAME not set")
|
|
110
|
+
exitCode = 1
|
|
111
|
+
}
|
|
112
|
+
if GlobalConfig.ScailoAPI == "" {
|
|
113
|
+
log.Println("SCAILO_API not set")
|
|
114
|
+
exitCode = 1
|
|
115
|
+
}
|
|
116
|
+
if GlobalConfig.Port == 0 {
|
|
117
|
+
log.Println("PORT not set or is 0")
|
|
118
|
+
exitCode = 1
|
|
119
|
+
}
|
|
120
|
+
if GlobalConfig.Username == "" {
|
|
121
|
+
log.Println("USERNAME not set")
|
|
122
|
+
exitCode = 1
|
|
123
|
+
}
|
|
124
|
+
if GlobalConfig.Password == "" {
|
|
125
|
+
log.Println("PASSWORD not set")
|
|
126
|
+
exitCode = 1
|
|
127
|
+
}
|
|
128
|
+
if GlobalConfig.RedisURL == "" {
|
|
129
|
+
log.Println("REDIS_URL not set")
|
|
130
|
+
exitCode = 1
|
|
131
|
+
}
|
|
132
|
+
if GlobalConfig.WorkflowEventsChannel == "" {
|
|
133
|
+
log.Println("WORKFLOW_EVENTS_CHANNEL not set")
|
|
134
|
+
exitCode = 1
|
|
135
|
+
}
|
|
136
|
+
if GlobalConfig.CookieSignatureSecret == "" {
|
|
137
|
+
log.Println("COOKIE_SIGNATURE_SECRET not set")
|
|
138
|
+
exitCode = 1
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
enclavePrefix = getEnclavePrefix(GlobalConfig.EnclaveName)
|
|
142
|
+
|
|
143
|
+
if exitCode != 0 {
|
|
144
|
+
os.Exit(exitCode)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// loginToAPI logs into the Scailo API
|
|
149
|
+
func loginToAPI(conn *grpc.ClientConn) {
|
|
150
|
+
// This function uses a goroutine to run asynchronously and recursively.
|
|
151
|
+
RedisClient = redis.NewClient(&redis.Options{
|
|
152
|
+
Addr: GlobalConfig.RedisURL,
|
|
153
|
+
Password: GlobalConfig.RedisPassword,
|
|
154
|
+
MaxRetries: 5,
|
|
155
|
+
DialTimeout: 10 * time.Second,
|
|
156
|
+
ReadTimeout: 30 * time.Second,
|
|
157
|
+
WriteTimeout: 30 * time.Second,
|
|
158
|
+
PoolSize: 10,
|
|
159
|
+
MinIdleConns: 5,
|
|
160
|
+
PoolTimeout: 30 * time.Second,
|
|
161
|
+
IdleTimeout: 15 * time.Minute,
|
|
162
|
+
IdleCheckFrequency: 1 * time.Minute,
|
|
163
|
+
})
|
|
164
|
+
go handleWorkflowEvents(RedisClient)
|
|
165
|
+
|
|
166
|
+
// Create a Ticker for the recurring job (1 hour)
|
|
167
|
+
ticker := time.NewTicker(loginInterval)
|
|
168
|
+
|
|
169
|
+
// Start the initial login immediately, then wait for the ticker.
|
|
170
|
+
performLogin(conn)
|
|
171
|
+
|
|
172
|
+
// Wait for the ticker events in a separate goroutine
|
|
173
|
+
go func() {
|
|
174
|
+
for range ticker.C {
|
|
175
|
+
performLogin(conn)
|
|
176
|
+
}
|
|
177
|
+
}()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
func getServerURL() string {
|
|
181
|
+
if strings.HasPrefix(GlobalConfig.ScailoAPI, "http") || strings.Contains(GlobalConfig.ScailoAPI, "//") {
|
|
182
|
+
var split = strings.Split(GlobalConfig.ScailoAPI, "//")
|
|
183
|
+
if len(split) > 1 {
|
|
184
|
+
return split[1]
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return GlobalConfig.ScailoAPI
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
func handleWorkflowEvents(redisClient *redis.Client) error {
|
|
192
|
+
subscription := redisClient.Subscribe(GlobalConfig.WorkflowEventsChannel)
|
|
193
|
+
for message := range subscription.Channel() {
|
|
194
|
+
fmt.Println("Received message on workflow channel")
|
|
195
|
+
var workflowEvent = new(sdk.WorkflowEvent)
|
|
196
|
+
|
|
197
|
+
err := proto.Unmarshal([]byte(message.Payload), workflowEvent)
|
|
198
|
+
if err != nil {
|
|
199
|
+
fmt.Printf("Unable to unmarshal workflow event: %s\n", err.Error())
|
|
200
|
+
continue
|
|
201
|
+
}
|
|
202
|
+
fmt.Println("Unmarshalled the message with rule code: ", workflowEvent.RuleCode, " for service: ", workflowEvent.ServiceName.String())
|
|
203
|
+
|
|
204
|
+
// Sample code to handle a sales order related workflow rule
|
|
205
|
+
if workflowEvent.RuleCode == "" {
|
|
206
|
+
// Handle sales order
|
|
207
|
+
var salesorder = new(sdk.SalesOrder)
|
|
208
|
+
err = proto.Unmarshal(workflowEvent.TransactionPayload, salesorder)
|
|
209
|
+
if err != nil {
|
|
210
|
+
fmt.Println("Unable to unmarshal sales order: ", err)
|
|
211
|
+
continue
|
|
212
|
+
}
|
|
213
|
+
fmt.Println("Unmarshalled transaction payload for rule code: ", workflowEvent.RuleCode)
|
|
214
|
+
// Use the ScailoAPICtx to further process the sales order
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return nil
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
func getGRPCConnection() *grpc.ClientConn {
|
|
221
|
+
var creds grpc.DialOption
|
|
222
|
+
if strings.HasPrefix(GlobalConfig.ScailoAPI, "http://") {
|
|
223
|
+
// Without TLS
|
|
224
|
+
creds = grpc.WithTransportCredentials(insecure.NewCredentials())
|
|
225
|
+
} else {
|
|
226
|
+
// With TLS
|
|
227
|
+
creds = grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, getServerURL()))
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
conn, err := grpc.NewClient(getServerURL(), creds)
|
|
231
|
+
if err != nil {
|
|
232
|
+
log.Fatalf("did not connect: %v", err)
|
|
233
|
+
}
|
|
234
|
+
return conn
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
func performLogin(conn *grpc.ClientConn) {
|
|
238
|
+
log.Println("About to login to API")
|
|
239
|
+
|
|
240
|
+
ctx := context.Background()
|
|
241
|
+
|
|
242
|
+
loginClient := sdk.NewLoginServiceClient(conn)
|
|
243
|
+
loginResp, err := loginClient.LoginAsEmployeePrimary(ctx, &sdk.UserLoginRequest{
|
|
244
|
+
Username: GlobalConfig.Username,
|
|
245
|
+
PlainTextPassword: GlobalConfig.Password,
|
|
246
|
+
})
|
|
247
|
+
if err != nil {
|
|
248
|
+
panic(err)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
md := metadata.Pairs(
|
|
252
|
+
"auth_token", loginResp.AuthToken,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
// 4. Create a new context with the metadata attached.
|
|
256
|
+
ScailoAPICtx = metadata.NewOutgoingContext(ctx, md)
|
|
257
|
+
|
|
258
|
+
mu.Lock()
|
|
259
|
+
AuthToken = loginResp.AuthToken
|
|
260
|
+
mu.Unlock()
|
|
261
|
+
|
|
262
|
+
log.Printf("Logged in with auth token: %s", AuthToken)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// replaceBundleCaches implements the cache-busting logic
|
|
266
|
+
func replaceBundleCaches(page string) string {
|
|
267
|
+
version := time.Now().Format("20060102150405") // YYYYMMDDhhmmss format
|
|
268
|
+
|
|
269
|
+
// Replace script preload
|
|
270
|
+
page = IndexPageReplacer(page,
|
|
271
|
+
fmt.Sprintf(`<link rel="preload" as="script" href="%s/resources/dist/js/bundle.src.min.js">`, enclavePrefix),
|
|
272
|
+
fmt.Sprintf(`<link rel="preload" as="script" href="%s/resources/dist/js/bundle.src.min.js?v=%s">`, enclavePrefix, version))
|
|
273
|
+
|
|
274
|
+
// Replace script src
|
|
275
|
+
page = IndexPageReplacer(page,
|
|
276
|
+
fmt.Sprintf(`<script src="%s/resources/dist/js/bundle.src.min.js"></script>`, enclavePrefix),
|
|
277
|
+
fmt.Sprintf(`<script src="%s/resources/dist/js/bundle.src.min.js?v=%s"></script>`, enclavePrefix, version))
|
|
278
|
+
|
|
279
|
+
// Replace stylesheet link
|
|
280
|
+
page = IndexPageReplacer(page,
|
|
281
|
+
fmt.Sprintf(`<link rel="stylesheet" href="%s/resources/dist/css/bundle.css">`, enclavePrefix),
|
|
282
|
+
fmt.Sprintf(`<link rel="stylesheet" href="%s/resources/dist/css/bundle.css?v=%s">`, enclavePrefix, version))
|
|
283
|
+
|
|
284
|
+
return page
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// IndexPageReplacer is a helper to centralize string replacement with logging.
|
|
288
|
+
func IndexPageReplacer(s, old, new string) string {
|
|
289
|
+
return strings.ReplaceAll(s, old, new)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// indexHandler is the single handler for all root/SPA routes.
|
|
293
|
+
func indexHandler(c *gin.Context) {
|
|
294
|
+
// 1. Read index.html logic
|
|
295
|
+
if !production || indexPage == "" {
|
|
296
|
+
content, err := os.ReadFile(indexHTMLFile)
|
|
297
|
+
if err != nil {
|
|
298
|
+
log.Printf("Error reading index.html: %v", err)
|
|
299
|
+
c.String(http.StatusInternalServerError, "Index page not found.")
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
indexPage = string(content)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// 2. Cache busting logic
|
|
306
|
+
pageWithCache := replaceBundleCaches(indexPage)
|
|
307
|
+
|
|
308
|
+
// 3. Set headers and send response
|
|
309
|
+
c.Header("Content-Type", "text/html")
|
|
310
|
+
c.String(http.StatusOK, pageWithCache)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// AuthMiddleware is our "Gatekeeper"
|
|
314
|
+
func AuthMiddleware(cookieCoder *securecookie.SecureCookie, cookieAuthTokenName string) gin.HandlerFunc {
|
|
315
|
+
return func(c *gin.Context) {
|
|
316
|
+
val, err := c.Cookie(cookieAuthTokenName)
|
|
317
|
+
if err != nil {
|
|
318
|
+
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
|
319
|
+
return
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Decode the value
|
|
323
|
+
var authToken string
|
|
324
|
+
cookieCoder.Decode(cookieAuthTokenName, val, &authToken)
|
|
325
|
+
|
|
326
|
+
if authToken == "" {
|
|
327
|
+
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Store the unsigned value in the context for the next handler
|
|
332
|
+
c.Set(cookieAuthTokenName, authToken)
|
|
333
|
+
c.Next()
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
func getNewContextForAuthToken(authToken string) context.Context {
|
|
338
|
+
ctx := context.Background()
|
|
339
|
+
md := metadata.Pairs(
|
|
340
|
+
"auth_token", authToken,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
// 4. Create a new context with the metadata attached.
|
|
344
|
+
return metadata.NewOutgoingContext(ctx, md)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// main entry point
|
|
348
|
+
func main() {
|
|
349
|
+
loadConfig()
|
|
350
|
+
|
|
351
|
+
conn := getGRPCConnection()
|
|
352
|
+
defer conn.Close()
|
|
353
|
+
|
|
354
|
+
var cookieCoder = securecookie.New([]byte(GlobalConfig.CookieSignatureSecret), nil)
|
|
355
|
+
|
|
356
|
+
// Start the recurring login process asynchronously
|
|
357
|
+
go loginToAPI(conn)
|
|
358
|
+
|
|
359
|
+
// Set Gin to release mode if in production
|
|
360
|
+
if production {
|
|
361
|
+
gin.SetMode(gin.ReleaseMode)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
vaultClient := sdk.NewVaultServiceClient(conn)
|
|
365
|
+
purchaseOrdersClient := sdk.NewPurchasesOrdersServiceClient(conn)
|
|
366
|
+
vendorsClient := sdk.NewVendorsServiceClient(conn)
|
|
367
|
+
|
|
368
|
+
var cookieAuthTokenName = fmt.Sprintf("%s_auth_token", GlobalConfig.EnclaveName)
|
|
369
|
+
|
|
370
|
+
// Initialize Gin
|
|
371
|
+
router := gin.Default()
|
|
372
|
+
|
|
373
|
+
// --- 1. Register Static Routes ---
|
|
374
|
+
router.Static(fmt.Sprintf("%s/resources/dist", enclavePrefix), filepath.Join("resources", "dist"))
|
|
375
|
+
|
|
376
|
+
// --- 2. Health Checks ---
|
|
377
|
+
router.GET(fmt.Sprintf("%s/health/startup", enclavePrefix), func(c *gin.Context) {
|
|
378
|
+
c.JSON(http.StatusOK, gin.H{"status": "OK"})
|
|
379
|
+
})
|
|
380
|
+
router.GET(fmt.Sprintf("%s/health/liveliness", enclavePrefix), func(c *gin.Context) {
|
|
381
|
+
c.JSON(http.StatusOK, gin.H{"status": "OK"})
|
|
382
|
+
})
|
|
383
|
+
router.GET(fmt.Sprintf("%s/health/readiness", enclavePrefix), func(c *gin.Context) {
|
|
384
|
+
c.JSON(http.StatusOK, gin.H{"status": "OK"})
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
// --- 3. API Endpoint ---
|
|
388
|
+
// Using a parameter for enclaveName so it matches the route pattern exactly
|
|
389
|
+
router.GET(fmt.Sprintf("%s/api/random", enclavePrefix), func(c *gin.Context) {
|
|
390
|
+
// Generate a random float between 0.0 and 1.0 (like Math.random())
|
|
391
|
+
randomNumber := rand.Float64()
|
|
392
|
+
c.JSON(http.StatusOK, gin.H{"random": randomNumber})
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
// --- 4. Index Page / SPA Routes (all pointing to the same handler) ---
|
|
396
|
+
// Specific UI routes
|
|
397
|
+
uiPath1 := fmt.Sprintf("%s/ui", enclavePrefix)
|
|
398
|
+
uiPath2 := fmt.Sprintf("%s/ui/*path", enclavePrefix)
|
|
399
|
+
|
|
400
|
+
router.GET(uiPath1, indexHandler)
|
|
401
|
+
router.GET(uiPath2, indexHandler)
|
|
402
|
+
|
|
403
|
+
// Implicit redirect for entry_point_management = direct_url
|
|
404
|
+
router.GET("/", func(c *gin.Context) {
|
|
405
|
+
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/ui", enclavePrefix))
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
// ------------------------------------------------------------------------------------------------------------------------
|
|
409
|
+
// Enclave Ingress handler
|
|
410
|
+
router.GET(fmt.Sprintf("%s/ingress/:token", enclavePrefix), func(c *gin.Context) {
|
|
411
|
+
var cookieToSet string
|
|
412
|
+
var expiresIn int
|
|
413
|
+
if !production {
|
|
414
|
+
// In dev, use the default auth token
|
|
415
|
+
expiresIn = 3600
|
|
416
|
+
cookieToSet = AuthToken
|
|
417
|
+
} else {
|
|
418
|
+
token := c.Param("token")
|
|
419
|
+
if token == "" {
|
|
420
|
+
c.JSON(http.StatusBadRequest, gin.H{"error": "Missing token"})
|
|
421
|
+
return
|
|
422
|
+
}
|
|
423
|
+
// Correctly verify the ingress token
|
|
424
|
+
ingress, err := vaultClient.VerifyEnclaveIngress(ScailoAPICtx, &sdk.VerifyEnclaveIngressRequest{Token: token})
|
|
425
|
+
if err != nil {
|
|
426
|
+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
427
|
+
return
|
|
428
|
+
}
|
|
429
|
+
expiresIn = int(ingress.ExpiresAt) - int(time.Now().Unix())
|
|
430
|
+
cookieToSet = ingress.AuthToken
|
|
431
|
+
|
|
432
|
+
}
|
|
433
|
+
securedCookieValue, err := cookieCoder.Encode(cookieAuthTokenName, cookieToSet)
|
|
434
|
+
if err != nil {
|
|
435
|
+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
436
|
+
return
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
c.SetCookie(cookieAuthTokenName, securedCookieValue, expiresIn, "/", "", false, true)
|
|
440
|
+
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/ui", enclavePrefix))
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
// Protected routes
|
|
444
|
+
protected := router.Group(fmt.Sprintf("%s/protected", enclavePrefix))
|
|
445
|
+
protected.Use(AuthMiddleware(cookieCoder, cookieAuthTokenName))
|
|
446
|
+
{
|
|
447
|
+
protected.GET("/api/random", func(c *gin.Context) {
|
|
448
|
+
// Generate a random float between 0.0 and 1.0 (like Math.random())
|
|
449
|
+
|
|
450
|
+
authToken, exists := c.Get(cookieAuthTokenName)
|
|
451
|
+
if !exists {
|
|
452
|
+
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
|
453
|
+
return
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
ctx := getNewContextForAuthToken(authToken.(string))
|
|
457
|
+
purchaseOrdersList, err := purchaseOrdersClient.Filter(ctx, &sdk.PurchasesOrdersServiceFilterReq{IsActive: sdk.BOOL_FILTER_BOOL_FILTER_TRUE, Count: -1})
|
|
458
|
+
if err != nil {
|
|
459
|
+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
460
|
+
return
|
|
461
|
+
}
|
|
462
|
+
vendorsList, err := vendorsClient.Filter(ctx, &sdk.VendorsServiceFilterReq{IsActive: sdk.BOOL_FILTER_BOOL_FILTER_TRUE, Count: -1})
|
|
463
|
+
if err != nil {
|
|
464
|
+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
465
|
+
return
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
randomNumber := rand.Float64()
|
|
469
|
+
c.JSON(http.StatusOK, gin.H{"random": randomNumber, "purchaseOrders": purchaseOrdersList.List, "vendors": vendorsList.List})
|
|
470
|
+
})
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// ------------------------------------------------------------------------------------------------------------------------
|
|
474
|
+
|
|
475
|
+
// --- 5. Not Found Handler ---
|
|
476
|
+
router.NoRoute(func(c *gin.Context) {
|
|
477
|
+
// Only redirect if the request is not for a resource that failed to load (e.g., /resources/dist/404)
|
|
478
|
+
c.Redirect(http.StatusTemporaryRedirect, uiPath1)
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
// --- 6. Start Server ---
|
|
482
|
+
address := fmt.Sprintf("0.0.0.0:%d", GlobalConfig.Port)
|
|
483
|
+
log.Printf("Listening on address %s with Production: %t", address, production)
|
|
484
|
+
|
|
485
|
+
if err := router.Run(address); err != nil {
|
|
486
|
+
log.Fatalf("Server failed to start: %v", err)
|
|
487
|
+
}
|
|
488
|
+
}
|