From a579da0f70a8b8ab4aa6e0f796bd45ad6391b745 Mon Sep 17 00:00:00 2001 From: KavyaCSH Date: Wed, 2 Sep 2026 18:19:34 +0530 Subject: [PATCH] Fix API authentication and identity service updates --- fix_interceptor.ps1 | 25 + generate-collection.js | 176 ++++++ postman_collection.json | 948 ++++++++++++++++++++++++++++++++ src/index.ts | 5 + src/middleware/auth.ts | 25 +- src/modules/identity/router.ts | 4 +- src/modules/identity/service.ts | 4 +- update_api.py | 63 +++ 8 files changed, 1244 insertions(+), 6 deletions(-) create mode 100644 fix_interceptor.ps1 create mode 100644 generate-collection.js create mode 100644 postman_collection.json create mode 100644 update_api.py diff --git a/fix_interceptor.ps1 b/fix_interceptor.ps1 new file mode 100644 index 0000000..71ac3d2 --- /dev/null +++ b/fix_interceptor.ps1 @@ -0,0 +1,25 @@ +$content = Get-Content -Raw "c:\Users\Lenovo\Desktop\ai_pharma_frontend\src\services\api.ts" +$target = "}, (error) => {`r`n return Promise.reject(error);`r`n});" +$replacement = @" +}, (error) => { + return Promise.reject(error); +}); + +// Response interceptor to handle global errors like 401 Unauthorized +api.interceptors.response.use( + (response) => response, + (error) => { + if (error.response && error.response.status === 401) { + // Token is invalid or expired + localStorage.removeItem('auth_token'); + localStorage.removeItem('user'); + localStorage.removeItem('actor_id'); + window.location.href = '/login'; + } + return Promise.reject(error); + } +); +"@ + +$content = $content.Replace($target, $replacement) +Set-Content -Path "c:\Users\Lenovo\Desktop\ai_pharma_frontend\src\services\api.ts" -Value $content -NoNewline diff --git a/generate-collection.js b/generate-collection.js new file mode 100644 index 0000000..ae58407 --- /dev/null +++ b/generate-collection.js @@ -0,0 +1,176 @@ +const fs = require('fs'); + +const collection = { + info: { + name: "Pharmacy Core API", + description: "API collection for Pharmacy Core API", + schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + variable: [ + { + key: "baseUrl", + value: "http://localhost:8080", + type: "string" + }, + { + key: "token", + value: "", + type: "string" + } + ], + auth: { + type: "bearer", + bearer: [ + { + key: "token", + value: "{{token}}", + type: "string" + } + ] + }, + item: [] +}; + +function createFolder(name, endpoints, prefix) { + const items = endpoints.map(ep => { + const method = ep.method.toUpperCase(); + const fullPath = (prefix + ep.path).replace(/\/$/, ""); + const urlParts = fullPath.split('/').filter(p => p !== ''); + + const request = { + method: method, + header: [], + url: { + raw: `{{baseUrl}}${fullPath}`, + host: ["{{baseUrl}}"], + path: urlParts + } + }; + + if (['POST', 'PUT', 'PATCH'].includes(method)) { + request.body = { + mode: "raw", + raw: "{}", + options: { + raw: { + language: "json" + } + } + }; + } + + return { + name: ep.name || `${method} ${ep.path}`, + request: request + }; + }); + + return { + name: name, + item: items + }; +} + +const modules = [ + { + name: "Auth / Identity", + prefix: "/v1/auth", + endpoints: [ + { method: "GET", path: "/me", name: "Get Current User" }, + { method: "PUT", path: "/me", name: "Update Current User" }, + { method: "POST", path: "/register", name: "Register" }, + { method: "POST", path: "/login", name: "Login" }, + { method: "POST", path: "/logout", name: "Logout" }, + { method: "POST", path: "/token", name: "Refresh Token" } + ] + }, + { + name: "Catalogue", + prefix: "/v1", + endpoints: [ + { method: "GET", path: "/drugs", name: "Get Drugs" }, + { method: "POST", path: "/drugs", name: "Create Drug" }, + { method: "GET", path: "/drugs/search", name: "Search Drugs" }, + { method: "GET", path: "/products/:product_id/batches", name: "Get Product Batches" } + ] + }, + { + name: "Inventory", + prefix: "/v1/inventory", + endpoints: [ + { method: "POST", path: "/grn", name: "Create GRN" }, + { method: "GET", path: "/expiring", name: "Get Expiring Stock" }, + { method: "GET", path: "/low-stock", name: "Get Low Stock" }, + { method: "POST", path: "/adjustments", name: "Stock Adjustment" }, + { method: "GET", path: "/recall/:batch_no", name: "Recall Batch" } + ] + }, + { + name: "Dispensing (Orders)", + prefix: "/v1/orders", + endpoints: [ + { method: "GET", path: "/history", name: "Get Order History" }, + { method: "GET", path: "/h1-register", name: "Get H1 Register" }, + { method: "GET", path: "/:id/invoice/pdf", name: "Get Invoice PDF" }, + { method: "POST", path: "/", name: "Create Order" }, + { method: "PATCH", path: "/:id/items", name: "Update Order Items" }, + { method: "POST", path: "/:id/precheck", name: "Precheck Order" }, + { method: "POST", path: "/:id/dispense", name: "Dispense Order" } + ] + }, + { + name: "Audit", + prefix: "/v1/audit", + endpoints: [ + { method: "GET", path: "/ai-calls", name: "Get AI Calls" }, + { method: "GET", path: "/overrides", name: "Get Overrides" }, + { method: "GET", path: "/h1-register", name: "Get H1 Register Audit" }, + { method: "GET", path: "/sales-register", name: "Get Sales Register" }, + { method: "GET", path: "/dashboard-metrics", name: "Get Dashboard Metrics" } + ] + }, + { + name: "Prescription", + prefix: "/v1/rx", + endpoints: [ + { method: "POST", path: "/", name: "Upload Prescription" }, + { method: "GET", path: "/", name: "Get Prescriptions" }, + { method: "GET", path: "/:id", name: "Get Prescription by ID" }, + { method: "POST", path: "/:id/confirm", name: "Confirm Prescription" } + ] + }, + { + name: "AI", + prefix: "/v1/ai", + endpoints: [ + { method: "POST", path: "/voice", name: "Voice AI" } + ] + }, + { + name: "Integration", + prefix: "/v1/integration", + endpoints: [ + { method: "POST", path: "/hospital/patient-sync", name: "Sync Hospital Patient" }, + { method: "GET", path: "/hospital/patients", name: "Get Hospital Patients" }, + { method: "POST", path: "/telegaruda/prescription", name: "Receive Telegaruda Rx" }, + { method: "GET", path: "/telegaruda/patient/:patientId", name: "Fetch Telegaruda Patient" }, + { method: "GET", path: "/hospital/prescriptions", name: "Get Hospital Prescriptions" } + ] + }, + { + name: "Upload", + prefix: "/v1/upload", + endpoints: [ + { method: "POST", path: "/", name: "Upload File" }, + { method: "GET", path: "/presigned", name: "Get Presigned URL" } + ] + } +]; + +modules.forEach(mod => { + collection.item.push(createFolder(mod.name, mod.endpoints, mod.prefix)); +}); + +// Write to file +fs.writeFileSync('postman_collection.json', JSON.stringify(collection, null, 2)); +console.log('Collection generated at postman_collection.json'); diff --git a/postman_collection.json b/postman_collection.json new file mode 100644 index 0000000..34114aa --- /dev/null +++ b/postman_collection.json @@ -0,0 +1,948 @@ +{ + "info": { + "name": "Pharmacy Core API", + "description": "API collection for Pharmacy Core API", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "variable": [ + { + "key": "baseUrl", + "value": "http://localhost:8080", + "type": "string" + }, + { + "key": "token", + "value": "", + "type": "string" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token}}", + "type": "string" + } + ] + }, + "item": [ + { + "name": "Auth / Identity", + "item": [ + { + "name": "Get Current User", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/auth/me", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "auth", + "me" + ] + } + } + }, + { + "name": "Update Current User", + "request": { + "method": "PUT", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/auth/me", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "auth", + "me" + ] + }, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Register", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/auth/register", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "auth", + "register" + ] + }, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Login", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/auth/login", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "auth", + "login" + ] + }, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Logout", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/auth/logout", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "auth", + "logout" + ] + }, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Refresh Token", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/auth/token", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "auth", + "token" + ] + }, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + }, + { + "name": "Catalogue", + "item": [ + { + "name": "Get Drugs", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/drugs", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "drugs" + ] + } + } + }, + { + "name": "Create Drug", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/drugs", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "drugs" + ] + }, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Search Drugs", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/drugs/search", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "drugs", + "search" + ] + } + } + }, + { + "name": "Get Product Batches", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/products/:product_id/batches", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "products", + ":product_id", + "batches" + ] + } + } + } + ] + }, + { + "name": "Inventory", + "item": [ + { + "name": "Create GRN", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/inventory/grn", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "inventory", + "grn" + ] + }, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Get Expiring Stock", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/inventory/expiring", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "inventory", + "expiring" + ] + } + } + }, + { + "name": "Get Low Stock", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/inventory/low-stock", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "inventory", + "low-stock" + ] + } + } + }, + { + "name": "Stock Adjustment", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/inventory/adjustments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "inventory", + "adjustments" + ] + }, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Recall Batch", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/inventory/recall/:batch_no", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "inventory", + "recall", + ":batch_no" + ] + } + } + } + ] + }, + { + "name": "Dispensing (Orders)", + "item": [ + { + "name": "Get Order History", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/orders/history", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "orders", + "history" + ] + } + } + }, + { + "name": "Get H1 Register", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/orders/h1-register", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "orders", + "h1-register" + ] + } + } + }, + { + "name": "Get Invoice PDF", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/orders/:id/invoice/pdf", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "orders", + ":id", + "invoice", + "pdf" + ] + } + } + }, + { + "name": "Create Order", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/orders", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "orders" + ] + }, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Update Order Items", + "request": { + "method": "PATCH", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/orders/:id/items", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "orders", + ":id", + "items" + ] + }, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Precheck Order", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/orders/:id/precheck", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "orders", + ":id", + "precheck" + ] + }, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Dispense Order", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/orders/:id/dispense", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "orders", + ":id", + "dispense" + ] + }, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + }, + { + "name": "Audit", + "item": [ + { + "name": "Get AI Calls", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/audit/ai-calls", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "audit", + "ai-calls" + ] + } + } + }, + { + "name": "Get Overrides", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/audit/overrides", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "audit", + "overrides" + ] + } + } + }, + { + "name": "Get H1 Register Audit", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/audit/h1-register", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "audit", + "h1-register" + ] + } + } + }, + { + "name": "Get Sales Register", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/audit/sales-register", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "audit", + "sales-register" + ] + } + } + }, + { + "name": "Get Dashboard Metrics", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/audit/dashboard-metrics", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "audit", + "dashboard-metrics" + ] + } + } + } + ] + }, + { + "name": "Prescription", + "item": [ + { + "name": "Upload Prescription", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/rx", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "rx" + ] + }, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Get Prescriptions", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/rx", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "rx" + ] + } + } + }, + { + "name": "Get Prescription by ID", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/rx/:id", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "rx", + ":id" + ] + } + } + }, + { + "name": "Confirm Prescription", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/rx/:id/confirm", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "rx", + ":id", + "confirm" + ] + }, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + }, + { + "name": "AI", + "item": [ + { + "name": "Voice AI", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/ai/voice", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "ai", + "voice" + ] + }, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + }, + { + "name": "Integration", + "item": [ + { + "name": "Sync Hospital Patient", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/integration/hospital/patient-sync", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "integration", + "hospital", + "patient-sync" + ] + }, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Get Hospital Patients", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/integration/hospital/patients", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "integration", + "hospital", + "patients" + ] + } + } + }, + { + "name": "Receive Telegaruda Rx", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/integration/telegaruda/prescription", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "integration", + "telegaruda", + "prescription" + ] + }, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Fetch Telegaruda Patient", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/integration/telegaruda/patient/:patientId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "integration", + "telegaruda", + "patient", + ":patientId" + ] + } + } + }, + { + "name": "Get Hospital Prescriptions", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/integration/hospital/prescriptions", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "integration", + "hospital", + "prescriptions" + ] + } + } + } + ] + }, + { + "name": "Upload", + "item": [ + { + "name": "Upload File", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/upload", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "upload" + ] + }, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Get Presigned URL", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/v1/upload/presigned", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "upload", + "presigned" + ] + } + } + } + ] + } + ] +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 4ba1fa6..6f55b9b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import express from 'express'; import cors from 'cors'; import helmet from 'helmet'; import { errorHandler } from './middleware/error'; +import { requireApiKey } from './middleware/auth'; // Import routers import identityRouter from './modules/identity/router'; @@ -39,6 +40,9 @@ app.use((req, res, next) => { next(); }); +// Global API Key Check +app.use(requireApiKey); + // API Routes app.use('/v1/auth', identityRouter); app.use('/v1', catalogueRouter); @@ -61,3 +65,4 @@ app.use(errorHandler); app.listen(PORT, () => { console.log(`Pharmacy Core API listening on port ${PORT}`); }); + diff --git a/src/middleware/auth.ts b/src/middleware/auth.ts index f88369f..8aeb2b6 100644 --- a/src/middleware/auth.ts +++ b/src/middleware/auth.ts @@ -1,7 +1,8 @@ import { Request, Response, NextFunction, RequestHandler } from 'express'; import jwt from 'jsonwebtoken'; -const JWT_SECRET = process.env.JWT_SECRET || 'super_secret_pharmacy_key_2026'; +// JWT_SECRET will be evaluated at runtime to avoid loading order issues +const API_KEY = process.env.API_KEY; declare global { namespace Express { @@ -12,7 +13,26 @@ declare global { } } +export const requireApiKey: RequestHandler = (req: Request, res: Response, next: NextFunction): void => { + const apiKey = req.headers['x-api-key']; + const expectedApiKey = process.env.API_KEY; + + // If API_KEY is set in .env, enforce it + if (expectedApiKey && apiKey !== expectedApiKey) { + res.status(401).json({ + error: 'UNAUTHORIZED', + message: 'Missing or invalid API Key.', + details: {}, + retryable: false + }); + return; + } + + next(); +}; + export const requireAuth: RequestHandler = (req: Request, res: Response, next: NextFunction): void => { + const authHeader = req.headers.authorization; const actorId = req.headers['x-actor-id'] as string; @@ -29,8 +49,9 @@ export const requireAuth: RequestHandler = (req: Request, res: Response, next: N const token = authHeader.split(' ')[1]; try { + const secret = process.env.JWT_SECRET || 'super_secret_pharmacy_key_2026'; // Cryptographically verify the token using our secret key - const payload = jwt.verify(token, JWT_SECRET) as any; + const payload = jwt.verify(token, secret) as any; if (payload.userId) { // User JWT Token diff --git a/src/modules/identity/router.ts b/src/modules/identity/router.ts index 40dd628..253ebf9 100644 --- a/src/modules/identity/router.ts +++ b/src/modules/identity/router.ts @@ -9,7 +9,7 @@ const router = Router(); const upload = multer({ storage: multer.memoryStorage() }); const s3Service = new S3Service(); const identityService = new IdentityService(); -const JWT_SECRET = process.env.JWT_SECRET || 'super_secret_pharmacy_key_2026'; +// JWT_SECRET evaluated at runtime // GET /v1/auth/me (Get Profile) router.get('/me', requireAuth, async (req, res) => { @@ -123,7 +123,7 @@ router.post('/token', (req, res) => { tenantId: client_id, scopes: ['rx:read', 'rx:write', 'dispense:human', 'catalogue:read'] }, - JWT_SECRET, + process.env.JWT_SECRET || 'super_secret_pharmacy_key_2026', { expiresIn: '8h' } ); diff --git a/src/modules/identity/service.ts b/src/modules/identity/service.ts index 64fe953..0bceadf 100644 --- a/src/modules/identity/service.ts +++ b/src/modules/identity/service.ts @@ -2,7 +2,7 @@ import { pool } from '../../db/pool'; import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; -const JWT_SECRET = process.env.JWT_SECRET || 'super_secret_pharmacy_key_2026'; +// JWT_SECRET evaluated at runtime export class IdentityService { async registerUser(payload: { @@ -63,7 +63,7 @@ export class IdentityService { role: user.role, tenant_id: user.tenant_id }, - JWT_SECRET, + process.env.JWT_SECRET || 'super_secret_pharmacy_key_2026', { expiresIn: '12h' } // Usually 8-12 hours for a shift ); diff --git a/update_api.py b/update_api.py new file mode 100644 index 0000000..2d2556e --- /dev/null +++ b/update_api.py @@ -0,0 +1,63 @@ +import os + +filepath = r"c:\Users\Lenovo\Desktop\ai_pharma_frontend\src\services\api.ts" + +with open(filepath, "r", encoding="utf-8") as f: + content = f.read() + +# Replacement 1: Add API_KEY to axios.create +old_axios_create = """export const api = axios.create({ + baseURL: API_BASE_URL, + headers: { + 'Content-Type': 'application/json', + }, +});""" + +new_axios_create = """export const api = axios.create({ + baseURL: API_BASE_URL, + headers: { + 'Content-Type': 'application/json', + 'x-api-key': import.meta.env.VITE_API_KEY || '', + }, +});""" + +content = content.replace(old_axios_create, new_axios_create) + +# Replacement 2: update authenticate +old_auth = """ const response = await axios.post(`${API_BASE_URL}/auth/token`, { + grant_type: 'client_credentials', + client_id: 'jayam_web_ai', + client_secret: 'my_secret_key' + });""" + +new_auth = """ const response = await axios.post(`${API_BASE_URL}/auth/token`, { + grant_type: 'client_credentials', + client_id: 'jayam_web_ai', + client_secret: 'my_secret_key' + }, { + headers: { 'x-api-key': import.meta.env.VITE_API_KEY || '' } + });""" + +content = content.replace(old_auth, new_auth) + +# Replacement 3: update loginPharmacist +old_login = """ const response = await axios.post(`${API_BASE_URL}/auth/login`, credentials);""" + +new_login = """ const response = await axios.post(`${API_BASE_URL}/auth/login`, credentials, { + headers: { 'x-api-key': import.meta.env.VITE_API_KEY || '' } + });""" + +content = content.replace(old_login, new_login) + +# Replacement 4: update logoutPharmacist +old_logout = """ const response = await axios.post(`${API_BASE_URL}/auth/logout`);""" +new_logout = """ const response = await axios.post(`${API_BASE_URL}/auth/logout`, {}, { + headers: { 'x-api-key': import.meta.env.VITE_API_KEY || '' } + });""" + +content = content.replace(old_logout, new_logout) + +with open(filepath, "w", encoding="utf-8") as f: + f.write(content) + +print("Updated api.ts")