Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions infrastructure/eid-wallet/src/env.d.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,4 +5,5 @@ declare namespace App {}
declare module "$env/static/public" {
export const PUBLIC_REGISTRY_URL: string;
export const PUBLIC_PROVISIONER_URL: string;
export const PUBLIC_EID_WALLET_TOKEN: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

PUBLIC_EID_WALLET_TOKEN in $env/static/public will be exposed to the browser

Declaring PUBLIC_EID_WALLET_TOKEN in the $env/static/public module means its value is statically inlined into client bundles and is not secret. If this token is used for authenticating sensitive operations (e.g., calling eVault /public-key), exposing it to all client code (and, transitively, attackers running JS in the browser) significantly weakens your security model.

Please:

  • Confirm that this token is truly safe to be public, or
  • Move it to a private env module ($env/static/private/dynamic private) and adjust the flow so that only trusted server-side code uses it.

Based on prior learnings about avoiding PUBLIC_* envs for critical configuration (e.g., PUBLIC_PLATFORM_URL), this is worth a careful threat-model review rather than treating it as just another public constant.


🏁 Script executed:

#!/bin/bash# Examine env.d.ts file content to see token declaration context
cat infrastructure/eid-wallet/src/env.d.ts

Length of output: 251


🏁 Script executed:

#!/bin/bash# Search for how PUBLIC_EID_WALLET_TOKEN is used in the codebase
rg -n "PUBLIC_EID_WALLET_TOKEN" infrastructure/eid-wallet/

Length of output: 469


🏁 Script executed:

#!/bin/bash# Check for .env.example or .env template files
fd -t f "\\.env" infrastructure/eid-wallet/ | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash# Search for eVault API calls and /public-key references to understand token usage
rg -n "public-key|eVault|PUBLIC_EID_WALLET" infrastructure/eid-wallet/ -A 3 -B 3

Length of output: 47537


}
71 changes: 49 additions & 22 deletions infrastructure/eid-wallet/src/lib/global/controllers/evault.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
import { PUBLIC_REGISTRY_URL, PUBLIC_PROVISIONER_URL } from "$env/static/public";
import {
PUBLIC_REGISTRY_URL,
PUBLIC_PROVISIONER_URL,
PUBLIC_EID_WALLET_TOKEN,
} from "$env/static/public";
import type { Store } from "@tauri-apps/plugin-store";
import axios from "axios";
import { GraphQLClient } from "graphql-request";
Expand DownExpand Up@@ -52,7 +56,11 @@ export class VaultController {
#profileCreationStatus: "idle" | "loading" | "success" | "failed" = "idle";
#notificationService: NotificationService;

constructor(store: Store, userController: UserController, keyService?: KeyService) {
constructor(
store: Store,
userController: UserController,
keyService?: KeyService,
) {
this.#store = store;
this.#userController = userController;
this.#keyService = keyService || null;
Expand DownExpand Up@@ -81,17 +89,21 @@ export class VaultController {
* Sync public key to eVault core
* Checks if public key was already saved, calls /whois, and PATCH if needed
*/
private async syncPublicKey(eName: string): Promise<void> {
async syncPublicKey(eName: string): Promise<void> {
try {
// Check if we've already saved the public key
const savedKey = localStorage.getItem(`publicKeySaved_${eName}`);
if (savedKey === "true") {
console.log(`Public key already saved for ${eName}, skipping sync`);
console.log(
`Public key already saved for ${eName}, skipping sync`,
);
return;
}

if (!this.#keyService) {
console.warn("KeyService not available, cannot sync public key");
console.warn(
"KeyService not available, cannot sync public key",
);
return;
}

Expand All@@ -118,23 +130,42 @@ export class VaultController {
return;
}

// Get public key from KeyService
const publicKey = await this.#keyService.getPublicKey(eName, "signing");
// Get public key using the exact same logic as onboarding/verification flow
// KEY_ID is always "default", context depends on whether user is pre-verification
const KEY_ID = "default";

// Determine context: check if user is pre-verification (fake/demo user)
const isFake = await this.#userController.isFake;
const context = isFake ? "pre-verification" : "onboarding";

// Get public key using the same method as getApplicationPublicKey() in onboarding/verify
let publicKey: string | undefined;
try {
publicKey = await this.#keyService.getPublicKey(
KEY_ID,
context,
);
} catch (error) {
console.error(
`Failed to get public key for ${KEY_ID} with context ${context}:`,
error,
);
return;
}

if (!publicKey) {
console.warn(`No public key found for ${eName}, cannot sync`);
console.warn(
`No public key found for ${KEY_ID} with context ${context}, cannot sync`,
);
return;
}

// Get authentication token from registry
let authToken: string | null = null;
try {
const entropyResponse = await axios.get(
new URL("/entropy", PUBLIC_REGISTRY_URL).toString()
// Get authentication token from environment variable
const authToken = PUBLIC_EID_WALLET_TOKEN || null;
if (!authToken) {
console.warn(
"PUBLIC_EID_WALLET_TOKEN not set, request may fail authentication",
);
authToken = entropyResponse.data?.token || null;
} catch (error) {
console.error("Failed to get auth token from registry:", error);
// Continue without token - server will reject if auth is required
}

// Call PATCH /public-key to save the public key
Expand All@@ -148,11 +179,7 @@ export class VaultController {
headers["Authorization"] = `Bearer ${authToken}`;
}

await axios.patch(
patchUrl,
{ publicKey },
{ headers }
);
await axios.patch(patchUrl, { publicKey }, { headers });

// Mark as saved
localStorage.setItem(`publicKeySaved_${eName}`, "true");
Expand Down
6 changes: 5 additions & 1 deletion infrastructure/eid-wallet/src/lib/global/state.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,11 @@ export class GlobalState {
this.securityController = new SecurityController(store);
this.userController = new UserController(store);
this.keyService = keyService;
this.vaultController = new VaultController(store, this.userController, keyService);
this.vaultController = new VaultController(
store,
this.userController,
keyService,
);
this.notificationService = NotificationService.getInstance();
}

Expand Down
20 changes: 20 additions & 0 deletions infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,6 +98,16 @@ onMount(async () => {
}
// For other errors, continue to app - non-blocking
}

// Sync public key to eVault core
try {
await globalState.vaultController.syncPublicKey(
vault.ename,
);
} catch (error) {
console.error("Error syncing public key:", error);
// Continue to app even if sync fails - non-blocking
}
}
} catch (error) {
console.error("Error during eVault health check:", error);
Expand DownExpand Up@@ -170,6 +180,16 @@ onMount(async () => {
}
// For other errors, continue to app - non-blocking
}

// Sync public key to eVault core
try {
await globalState.vaultController.syncPublicKey(
vault.ename,
);
} catch (error) {
console.error("Error syncing public key:", error);
// Continue to app even if sync fails - non-blocking
}
}
} catch (error) {
console.error("Error during eVault health check:", error);
Expand Down
68 changes: 68 additions & 0 deletions infrastructure/evault-core/generate-test-token.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env node

/**
* Script to generate a test JWT token for evault-core authentication
*
* Usage:
* REGISTRY_ENTROPY_KEY_JWK='{"kty":"EC",...}' node generate-test-token.js
*
* Or set the environment variable in a .env file
*
* You can also use tsx to run it:
* REGISTRY_ENTROPY_KEY_JWK='...' npx tsx generate-test-token.js
*/

const { importJWK, SignJWT } = require("jose");
const dotenv = require("dotenv");
const path = require("path");

// Load .env file if it exists (try both root and current directory)
dotenv.config({ path: path.resolve(__dirname, "../../../.env") });
dotenv.config({ path: path.resolve(__dirname, ".env") });

async function generateTestToken() {
const jwkString = '{"kty":"EC","use":"sig","alg":"ES256","kid":"entropy-key-1","crv":"P-256","x":"POWUVJwOulAW0gheTVUHF4nXUenMCg0jxhGKI8M1LLU","y":"Cb4GC8Tt0gW7zMr-DhsDJisGVNgWttwjnyQl1HyU7hg","d":"FWqvWBzoiZcD0hh4JAMdJ7foxFRGqyV2Ei_eWvr1Si4"}';

if (!jwkString) {
console.error("Error: REGISTRY_ENTROPY_KEY_JWK environment variable is required");
console.error("\nUsage:");
console.error(" REGISTRY_ENTROPY_KEY_JWK='<your-jwk-json>' node generate-test-token.js");
console.error("\nOr set it in your .env file");
process.exit(1);
}

try {
const jwk = JSON.parse(jwkString);
const privateKey = await importJWK(jwk, "ES256");

// Generate token valid for 1 year
const token = await new SignJWT({
// Add any custom claims you want
platform: "eid-wallet",
purpose: "public-key-sync",
})
.setProtectedHeader({
alg: "ES256",
kid: jwk.kid || "entropy-key-1",
})
.setIssuedAt()
.setExpirationTime("1y") // Valid for 1 year
.sign(privateKey);

console.log("\n✅ Generated JWT Token (valid for 1 year):\n");
console.log(token);
console.log("\n📋 Use this token in the Authorization header:");
console.log(` Authorization: Bearer ${token}\n`);

return token;
} catch (error) {
console.error("Error generating token:", error.message);
if (error.message.includes("JSON")) {
console.error("\nMake sure REGISTRY_ENTROPY_KEY_JWK is valid JSON");
}
process.exit(1);
}
}

generateTestToken();

1 change: 1 addition & 0 deletions infrastructure/evault-core/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@
"migration:revert": "npm run typeorm migration:revert -- -d dist/config/database.js"
},
"dependencies": {
"@fastify/cors": "^8.5.0",
"@fastify/formbody": "^8.0.2",
"@fastify/swagger": "^8.14.0",
"@fastify/swagger-ui": "^3.0.0",
Expand Down
42 changes: 31 additions & 11 deletions infrastructure/evault-core/src/core/http/server.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -286,30 +286,50 @@ export async function registerHttpRoutes(
// Helper function to validate JWT token
async function validateToken(authHeader: string | null): Promise<any | null> {
if (!authHeader || !authHeader.startsWith("Bearer ")) {
console.error("Token validation: Missing or invalid Authorization header format");
return null;
}

const token = authHeader.substring(7); // Remove 'Bearer ' prefix

try {
if (!process.env.REGISTRY_URL) {
console.error("REGISTRY_URL is not set");
// Try REGISTRY_URL first, fallback to PUBLIC_REGISTRY_URL
const registryUrl = process.env.REGISTRY_URL || process.env.PUBLIC_REGISTRY_URL;
if (!registryUrl) {
console.error("Token validation: REGISTRY_URL or PUBLIC_REGISTRY_URL is not set");
return null;
}

const jwksResponse = await axios.get(
new URL(
`/.well-known/jwks.json`,
process.env.REGISTRY_URL
).toString()
);
const jwksUrl = new URL(`/.well-known/jwks.json`, registryUrl).toString();
console.log(`Token validation: Fetching JWKS from ${jwksUrl}`);

const jwksResponse = await axios.get(jwksUrl, {
timeout: 5000,
});

console.log(`Token validation: JWKS response keys count: ${jwksResponse.data?.keys?.length || 0}`);

const JWKS = jose.createLocalJWKSet(jwksResponse.data);

// Decode token header to see what kid it's using
const decodedHeader = jose.decodeProtectedHeader(token);
console.log(`Token validation: Token header - alg: ${decodedHeader.alg}, kid: ${decodedHeader.kid}`);

const { payload } = await jose.jwtVerify(token, JWKS);


console.log(`Token validation: Token verified successfully, payload:`, payload);
return payload;
} catch (error) {
console.error("Token validation failed:", error);
} catch (error: any) {
console.error("Token validation failed:", error.message || error);
if (error.code) {
console.error(`Token validation error code: ${error.code}`);
}
if (error.response) {
console.error(`Token validation HTTP error: ${error.response.status} - ${error.response.statusText}`);
}
if (error.cause) {
console.error(`Token validation error cause:`, error.cause);
}
return null;
}
}
Expand Down
9 changes: 9 additions & 0 deletions infrastructure/evault-core/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@ import fastify, {
FastifyRequest,
FastifyReply,
} from "fastify";
import fastifyCors from "@fastify/cors";
import { renderVoyagerPage } from "graphql-voyager/middleware";
import { connectWithRetry } from "./core/db/retry-neo4j";
import neo4j, { Driver } from "neo4j-driver";
Expand DownExpand Up@@ -116,6 +117,14 @@ const initializeEVault = async (provisioningServiceInstance?: ProvisioningServic
logger: true,
});

// Register CORS plugin with relaxed settings
await fastifyServer.register(fastifyCors, {
origin: true, // Allow all origins
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization", "X-ENAME"],
credentials: true,
});

// Register HTTP routes with provisioning service if available
await registerHttpRoutes(fastifyServer, evaultInstance, provisioningServiceInstance, dbService);

Expand Down
5 changes: 5 additions & 0 deletions infrastructure/signature-validator/.gitignore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
node_modules/
dist/
*.log
.DS_Store

Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions infrastructure/eid-wallet/src/env.d.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,4 +5,5 @@ declare namespace App {}
declare module "$env/static/public" {
export const PUBLIC_REGISTRY_URL: string;
export const PUBLIC_PROVISIONER_URL: string;
export const PUBLIC_EID_WALLET_TOKEN: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

PUBLIC_EID_WALLET_TOKEN in $env/static/public will be exposed to the browser

Declaring PUBLIC_EID_WALLET_TOKEN in the $env/static/public module means its value is statically inlined into client bundles and is not secret. If this token is used for authenticating sensitive operations (e.g., calling eVault /public-key), exposing it to all client code (and, transitively, attackers running JS in the browser) significantly weakens your security model.

Please:

  • Confirm that this token is truly safe to be public, or
  • Move it to a private env module ($env/static/private/dynamic private) and adjust the flow so that only trusted server-side code uses it.

Based on prior learnings about avoiding PUBLIC_* envs for critical configuration (e.g., PUBLIC_PLATFORM_URL), this is worth a careful threat-model review rather than treating it as just another public constant.


🏁 Script executed:

#!/bin/bash# Examine env.d.ts file content to see token declaration context
cat infrastructure/eid-wallet/src/env.d.ts

Length of output: 251


🏁 Script executed:

#!/bin/bash# Search for how PUBLIC_EID_WALLET_TOKEN is used in the codebase
rg -n "PUBLIC_EID_WALLET_TOKEN" infrastructure/eid-wallet/

Length of output: 469


🏁 Script executed:

#!/bin/bash# Check for .env.example or .env template files
fd -t f "\\.env" infrastructure/eid-wallet/ | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash# Search for eVault API calls and /public-key references to understand token usage
rg -n "public-key|eVault|PUBLIC_EID_WALLET" infrastructure/eid-wallet/ -A 3 -B 3

Length of output: 47537


}
71 changes: 49 additions & 22 deletions infrastructure/eid-wallet/src/lib/global/controllers/evault.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
import { PUBLIC_REGISTRY_URL, PUBLIC_PROVISIONER_URL } from "$env/static/public";
import {
PUBLIC_REGISTRY_URL,
PUBLIC_PROVISIONER_URL,
PUBLIC_EID_WALLET_TOKEN,
} from "$env/static/public";
import type { Store } from "@tauri-apps/plugin-store";
import axios from "axios";
import { GraphQLClient } from "graphql-request";
Expand DownExpand Up@@ -52,7 +56,11 @@ export class VaultController {
#profileCreationStatus: "idle" | "loading" | "success" | "failed" = "idle";
#notificationService: NotificationService;

constructor(store: Store, userController: UserController, keyService?: KeyService) {
constructor(
store: Store,
userController: UserController,
keyService?: KeyService,
) {
this.#store = store;
this.#userController = userController;
this.#keyService = keyService || null;
Expand DownExpand Up@@ -81,17 +89,21 @@ export class VaultController {
* Sync public key to eVault core
* Checks if public key was already saved, calls /whois, and PATCH if needed
*/
private async syncPublicKey(eName: string): Promise<void> {
async syncPublicKey(eName: string): Promise<void> {
try {
// Check if we've already saved the public key
const savedKey = localStorage.getItem(`publicKeySaved_${eName}`);
if (savedKey === "true") {
console.log(`Public key already saved for ${eName}, skipping sync`);
console.log(
`Public key already saved for ${eName}, skipping sync`,
);
return;
}

if (!this.#keyService) {
console.warn("KeyService not available, cannot sync public key");
console.warn(
"KeyService not available, cannot sync public key",
);
return;
}

Expand All@@ -118,23 +130,42 @@ export class VaultController {
return;
}

// Get public key from KeyService
const publicKey = await this.#keyService.getPublicKey(eName, "signing");
// Get public key using the exact same logic as onboarding/verification flow
// KEY_ID is always "default", context depends on whether user is pre-verification
const KEY_ID = "default";

// Determine context: check if user is pre-verification (fake/demo user)
const isFake = await this.#userController.isFake;
const context = isFake ? "pre-verification" : "onboarding";

// Get public key using the same method as getApplicationPublicKey() in onboarding/verify
let publicKey: string | undefined;
try {
publicKey = await this.#keyService.getPublicKey(
KEY_ID,
context,
);
} catch (error) {
console.error(
`Failed to get public key for ${KEY_ID} with context ${context}:`,
error,
);
return;
}

if (!publicKey) {
console.warn(`No public key found for ${eName}, cannot sync`);
console.warn(
`No public key found for ${KEY_ID} with context ${context}, cannot sync`,
);
return;
}

// Get authentication token from registry
let authToken: string | null = null;
try {
const entropyResponse = await axios.get(
new URL("/entropy", PUBLIC_REGISTRY_URL).toString()
// Get authentication token from environment variable
const authToken = PUBLIC_EID_WALLET_TOKEN || null;
if (!authToken) {
console.warn(
"PUBLIC_EID_WALLET_TOKEN not set, request may fail authentication",
);
authToken = entropyResponse.data?.token || null;
} catch (error) {
console.error("Failed to get auth token from registry:", error);
// Continue without token - server will reject if auth is required
}

// Call PATCH /public-key to save the public key
Expand All@@ -148,11 +179,7 @@ export class VaultController {
headers["Authorization"] = `Bearer ${authToken}`;
}

await axios.patch(
patchUrl,
{ publicKey },
{ headers }
);
await axios.patch(patchUrl, { publicKey }, { headers });

// Mark as saved
localStorage.setItem(`publicKeySaved_${eName}`, "true");
Expand Down
6 changes: 5 additions & 1 deletion infrastructure/eid-wallet/src/lib/global/state.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,11 @@ export class GlobalState {
this.securityController = new SecurityController(store);
this.userController = new UserController(store);
this.keyService = keyService;
this.vaultController = new VaultController(store, this.userController, keyService);
this.vaultController = new VaultController(
store,
this.userController,
keyService,
);
this.notificationService = NotificationService.getInstance();
}

Expand Down
20 changes: 20 additions & 0 deletions infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,6 +98,16 @@ onMount(async () => {
}
// For other errors, continue to app - non-blocking
}

// Sync public key to eVault core
try {
await globalState.vaultController.syncPublicKey(
vault.ename,
);
} catch (error) {
console.error("Error syncing public key:", error);
// Continue to app even if sync fails - non-blocking
}
}
} catch (error) {
console.error("Error during eVault health check:", error);
Expand DownExpand Up@@ -170,6 +180,16 @@ onMount(async () => {
}
// For other errors, continue to app - non-blocking
}

// Sync public key to eVault core
try {
await globalState.vaultController.syncPublicKey(
vault.ename,
);
} catch (error) {
console.error("Error syncing public key:", error);
// Continue to app even if sync fails - non-blocking
}
}
} catch (error) {
console.error("Error during eVault health check:", error);
Expand Down
68 changes: 68 additions & 0 deletions infrastructure/evault-core/generate-test-token.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env node

/**
* Script to generate a test JWT token for evault-core authentication
*
* Usage:
* REGISTRY_ENTROPY_KEY_JWK='{"kty":"EC",...}' node generate-test-token.js
*
* Or set the environment variable in a .env file
*
* You can also use tsx to run it:
* REGISTRY_ENTROPY_KEY_JWK='...' npx tsx generate-test-token.js
*/

const { importJWK, SignJWT } = require("jose");
const dotenv = require("dotenv");
const path = require("path");

// Load .env file if it exists (try both root and current directory)
dotenv.config({ path: path.resolve(__dirname, "../../../.env") });
dotenv.config({ path: path.resolve(__dirname, ".env") });

async function generateTestToken() {
const jwkString = '{"kty":"EC","use":"sig","alg":"ES256","kid":"entropy-key-1","crv":"P-256","x":"POWUVJwOulAW0gheTVUHF4nXUenMCg0jxhGKI8M1LLU","y":"Cb4GC8Tt0gW7zMr-DhsDJisGVNgWttwjnyQl1HyU7hg","d":"FWqvWBzoiZcD0hh4JAMdJ7foxFRGqyV2Ei_eWvr1Si4"}';

if (!jwkString) {
console.error("Error: REGISTRY_ENTROPY_KEY_JWK environment variable is required");
console.error("\nUsage:");
console.error(" REGISTRY_ENTROPY_KEY_JWK='<your-jwk-json>' node generate-test-token.js");
console.error("\nOr set it in your .env file");
process.exit(1);
}

try {
const jwk = JSON.parse(jwkString);
const privateKey = await importJWK(jwk, "ES256");

// Generate token valid for 1 year
const token = await new SignJWT({
// Add any custom claims you want
platform: "eid-wallet",
purpose: "public-key-sync",
})
.setProtectedHeader({
alg: "ES256",
kid: jwk.kid || "entropy-key-1",
})
.setIssuedAt()
.setExpirationTime("1y") // Valid for 1 year
.sign(privateKey);

console.log("\n✅ Generated JWT Token (valid for 1 year):\n");
console.log(token);
console.log("\n📋 Use this token in the Authorization header:");
console.log(` Authorization: Bearer ${token}\n`);

return token;
} catch (error) {
console.error("Error generating token:", error.message);
if (error.message.includes("JSON")) {
console.error("\nMake sure REGISTRY_ENTROPY_KEY_JWK is valid JSON");
}
process.exit(1);
}
}

generateTestToken();

1 change: 1 addition & 0 deletions infrastructure/evault-core/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@
"migration:revert": "npm run typeorm migration:revert -- -d dist/config/database.js"
},
"dependencies": {
"@fastify/cors": "^8.5.0",
"@fastify/formbody": "^8.0.2",
"@fastify/swagger": "^8.14.0",
"@fastify/swagger-ui": "^3.0.0",
Expand Down
42 changes: 31 additions & 11 deletions infrastructure/evault-core/src/core/http/server.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -286,30 +286,50 @@ export async function registerHttpRoutes(
// Helper function to validate JWT token
async function validateToken(authHeader: string | null): Promise<any | null> {
if (!authHeader || !authHeader.startsWith("Bearer ")) {
console.error("Token validation: Missing or invalid Authorization header format");
return null;
}

const token = authHeader.substring(7); // Remove 'Bearer ' prefix

try {
if (!process.env.REGISTRY_URL) {
console.error("REGISTRY_URL is not set");
// Try REGISTRY_URL first, fallback to PUBLIC_REGISTRY_URL
const registryUrl = process.env.REGISTRY_URL || process.env.PUBLIC_REGISTRY_URL;
if (!registryUrl) {
console.error("Token validation: REGISTRY_URL or PUBLIC_REGISTRY_URL is not set");
return null;
}

const jwksResponse = await axios.get(
new URL(
`/.well-known/jwks.json`,
process.env.REGISTRY_URL
).toString()
);
const jwksUrl = new URL(`/.well-known/jwks.json`, registryUrl).toString();
console.log(`Token validation: Fetching JWKS from ${jwksUrl}`);

const jwksResponse = await axios.get(jwksUrl, {
timeout: 5000,
});

console.log(`Token validation: JWKS response keys count: ${jwksResponse.data?.keys?.length || 0}`);

const JWKS = jose.createLocalJWKSet(jwksResponse.data);

// Decode token header to see what kid it's using
const decodedHeader = jose.decodeProtectedHeader(token);
console.log(`Token validation: Token header - alg: ${decodedHeader.alg}, kid: ${decodedHeader.kid}`);

const { payload } = await jose.jwtVerify(token, JWKS);


console.log(`Token validation: Token verified successfully, payload:`, payload);
return payload;
} catch (error) {
console.error("Token validation failed:", error);
} catch (error: any) {
console.error("Token validation failed:", error.message || error);
if (error.code) {
console.error(`Token validation error code: ${error.code}`);
}
if (error.response) {
console.error(`Token validation HTTP error: ${error.response.status} - ${error.response.statusText}`);
}
if (error.cause) {
console.error(`Token validation error cause:`, error.cause);
}
return null;
}
}
Expand Down
9 changes: 9 additions & 0 deletions infrastructure/evault-core/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@ import fastify, {
FastifyRequest,
FastifyReply,
} from "fastify";
import fastifyCors from "@fastify/cors";
import { renderVoyagerPage } from "graphql-voyager/middleware";
import { connectWithRetry } from "./core/db/retry-neo4j";
import neo4j, { Driver } from "neo4j-driver";
Expand DownExpand Up@@ -116,6 +117,14 @@ const initializeEVault = async (provisioningServiceInstance?: ProvisioningServic
logger: true,
});

// Register CORS plugin with relaxed settings
await fastifyServer.register(fastifyCors, {
origin: true, // Allow all origins
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization", "X-ENAME"],
credentials: true,
});

// Register HTTP routes with provisioning service if available
await registerHttpRoutes(fastifyServer, evaultInstance, provisioningServiceInstance, dbService);

Expand Down
5 changes: 5 additions & 0 deletions infrastructure/signature-validator/.gitignore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
node_modules/
dist/
*.log
.DS_Store

Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions infrastructure/eid-wallet/src/env.d.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,4 +5,5 @@ declare namespace App {}
declare module "$env/static/public" {
export const PUBLIC_REGISTRY_URL: string;
export const PUBLIC_PROVISIONER_URL: string;
export const PUBLIC_EID_WALLET_TOKEN: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

PUBLIC_EID_WALLET_TOKEN in $env/static/public will be exposed to the browser

Declaring PUBLIC_EID_WALLET_TOKEN in the $env/static/public module means its value is statically inlined into client bundles and is not secret. If this token is used for authenticating sensitive operations (e.g., calling eVault /public-key), exposing it to all client code (and, transitively, attackers running JS in the browser) significantly weakens your security model.

Please:

  • Confirm that this token is truly safe to be public, or
  • Move it to a private env module ($env/static/private/dynamic private) and adjust the flow so that only trusted server-side code uses it.

Based on prior learnings about avoiding PUBLIC_* envs for critical configuration (e.g., PUBLIC_PLATFORM_URL), this is worth a careful threat-model review rather than treating it as just another public constant.


🏁 Script executed:

#!/bin/bash# Examine env.d.ts file content to see token declaration context
cat infrastructure/eid-wallet/src/env.d.ts

Length of output: 251


🏁 Script executed:

#!/bin/bash# Search for how PUBLIC_EID_WALLET_TOKEN is used in the codebase
rg -n "PUBLIC_EID_WALLET_TOKEN" infrastructure/eid-wallet/

Length of output: 469


🏁 Script executed:

#!/bin/bash# Check for .env.example or .env template files
fd -t f "\\.env" infrastructure/eid-wallet/ | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash# Search for eVault API calls and /public-key references to understand token usage
rg -n "public-key|eVault|PUBLIC_EID_WALLET" infrastructure/eid-wallet/ -A 3 -B 3

Length of output: 47537


}
71 changes: 49 additions & 22 deletions infrastructure/eid-wallet/src/lib/global/controllers/evault.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
import { PUBLIC_REGISTRY_URL, PUBLIC_PROVISIONER_URL } from "$env/static/public";
import {
PUBLIC_REGISTRY_URL,
PUBLIC_PROVISIONER_URL,
PUBLIC_EID_WALLET_TOKEN,
} from "$env/static/public";
import type { Store } from "@tauri-apps/plugin-store";
import axios from "axios";
import { GraphQLClient } from "graphql-request";
Expand DownExpand Up@@ -52,7 +56,11 @@ export class VaultController {
#profileCreationStatus: "idle" | "loading" | "success" | "failed" = "idle";
#notificationService: NotificationService;

constructor(store: Store, userController: UserController, keyService?: KeyService) {
constructor(
store: Store,
userController: UserController,
keyService?: KeyService,
) {
this.#store = store;
this.#userController = userController;
this.#keyService = keyService || null;
Expand DownExpand Up@@ -81,17 +89,21 @@ export class VaultController {
* Sync public key to eVault core
* Checks if public key was already saved, calls /whois, and PATCH if needed
*/
private async syncPublicKey(eName: string): Promise<void> {
async syncPublicKey(eName: string): Promise<void> {
try {
// Check if we've already saved the public key
const savedKey = localStorage.getItem(`publicKeySaved_${eName}`);
if (savedKey === "true") {
console.log(`Public key already saved for ${eName}, skipping sync`);
console.log(
`Public key already saved for ${eName}, skipping sync`,
);
return;
}

if (!this.#keyService) {
console.warn("KeyService not available, cannot sync public key");
console.warn(
"KeyService not available, cannot sync public key",
);
return;
}

Expand All@@ -118,23 +130,42 @@ export class VaultController {
return;
}

// Get public key from KeyService
const publicKey = await this.#keyService.getPublicKey(eName, "signing");
// Get public key using the exact same logic as onboarding/verification flow
// KEY_ID is always "default", context depends on whether user is pre-verification
const KEY_ID = "default";

// Determine context: check if user is pre-verification (fake/demo user)
const isFake = await this.#userController.isFake;
const context = isFake ? "pre-verification" : "onboarding";

// Get public key using the same method as getApplicationPublicKey() in onboarding/verify
let publicKey: string | undefined;
try {
publicKey = await this.#keyService.getPublicKey(
KEY_ID,
context,
);
} catch (error) {
console.error(
`Failed to get public key for ${KEY_ID} with context ${context}:`,
error,
);
return;
}

if (!publicKey) {
console.warn(`No public key found for ${eName}, cannot sync`);
console.warn(
`No public key found for ${KEY_ID} with context ${context}, cannot sync`,
);
return;
}

// Get authentication token from registry
let authToken: string | null = null;
try {
const entropyResponse = await axios.get(
new URL("/entropy", PUBLIC_REGISTRY_URL).toString()
// Get authentication token from environment variable
const authToken = PUBLIC_EID_WALLET_TOKEN || null;
if (!authToken) {
console.warn(
"PUBLIC_EID_WALLET_TOKEN not set, request may fail authentication",
);
authToken = entropyResponse.data?.token || null;
} catch (error) {
console.error("Failed to get auth token from registry:", error);
// Continue without token - server will reject if auth is required
}

// Call PATCH /public-key to save the public key
Expand All@@ -148,11 +179,7 @@ export class VaultController {
headers["Authorization"] = `Bearer ${authToken}`;
}

await axios.patch(
patchUrl,
{ publicKey },
{ headers }
);
await axios.patch(patchUrl, { publicKey }, { headers });

// Mark as saved
localStorage.setItem(`publicKeySaved_${eName}`, "true");
Expand Down
6 changes: 5 additions & 1 deletion infrastructure/eid-wallet/src/lib/global/state.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,11 @@ export class GlobalState {
this.securityController = new SecurityController(store);
this.userController = new UserController(store);
this.keyService = keyService;
this.vaultController = new VaultController(store, this.userController, keyService);
this.vaultController = new VaultController(
store,
this.userController,
keyService,
);
this.notificationService = NotificationService.getInstance();
}

Expand Down
20 changes: 20 additions & 0 deletions infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,6 +98,16 @@ onMount(async () => {
}
// For other errors, continue to app - non-blocking
}

// Sync public key to eVault core
try {
await globalState.vaultController.syncPublicKey(
vault.ename,
);
} catch (error) {
console.error("Error syncing public key:", error);
// Continue to app even if sync fails - non-blocking
}
}
} catch (error) {
console.error("Error during eVault health check:", error);
Expand DownExpand Up@@ -170,6 +180,16 @@ onMount(async () => {
}
// For other errors, continue to app - non-blocking
}

// Sync public key to eVault core
try {
await globalState.vaultController.syncPublicKey(
vault.ename,
);
} catch (error) {
console.error("Error syncing public key:", error);
// Continue to app even if sync fails - non-blocking
}
}
} catch (error) {
console.error("Error during eVault health check:", error);
Expand Down
68 changes: 68 additions & 0 deletions infrastructure/evault-core/generate-test-token.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env node

/**
* Script to generate a test JWT token for evault-core authentication
*
* Usage:
* REGISTRY_ENTROPY_KEY_JWK='{"kty":"EC",...}' node generate-test-token.js
*
* Or set the environment variable in a .env file
*
* You can also use tsx to run it:
* REGISTRY_ENTROPY_KEY_JWK='...' npx tsx generate-test-token.js
*/

const { importJWK, SignJWT } = require("jose");
const dotenv = require("dotenv");
const path = require("path");

// Load .env file if it exists (try both root and current directory)
dotenv.config({ path: path.resolve(__dirname, "../../../.env") });
dotenv.config({ path: path.resolve(__dirname, ".env") });

async function generateTestToken() {
const jwkString = '{"kty":"EC","use":"sig","alg":"ES256","kid":"entropy-key-1","crv":"P-256","x":"POWUVJwOulAW0gheTVUHF4nXUenMCg0jxhGKI8M1LLU","y":"Cb4GC8Tt0gW7zMr-DhsDJisGVNgWttwjnyQl1HyU7hg","d":"FWqvWBzoiZcD0hh4JAMdJ7foxFRGqyV2Ei_eWvr1Si4"}';

if (!jwkString) {
console.error("Error: REGISTRY_ENTROPY_KEY_JWK environment variable is required");
console.error("\nUsage:");
console.error(" REGISTRY_ENTROPY_KEY_JWK='<your-jwk-json>' node generate-test-token.js");
console.error("\nOr set it in your .env file");
process.exit(1);
}

try {
const jwk = JSON.parse(jwkString);
const privateKey = await importJWK(jwk, "ES256");

// Generate token valid for 1 year
const token = await new SignJWT({
// Add any custom claims you want
platform: "eid-wallet",
purpose: "public-key-sync",
})
.setProtectedHeader({
alg: "ES256",
kid: jwk.kid || "entropy-key-1",
})
.setIssuedAt()
.setExpirationTime("1y") // Valid for 1 year
.sign(privateKey);

console.log("\n✅ Generated JWT Token (valid for 1 year):\n");
console.log(token);
console.log("\n📋 Use this token in the Authorization header:");
console.log(` Authorization: Bearer ${token}\n`);

return token;
} catch (error) {
console.error("Error generating token:", error.message);
if (error.message.includes("JSON")) {
console.error("\nMake sure REGISTRY_ENTROPY_KEY_JWK is valid JSON");
}
process.exit(1);
}
}

generateTestToken();

1 change: 1 addition & 0 deletions infrastructure/evault-core/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@
"migration:revert": "npm run typeorm migration:revert -- -d dist/config/database.js"
},
"dependencies": {
"@fastify/cors": "^8.5.0",
"@fastify/formbody": "^8.0.2",
"@fastify/swagger": "^8.14.0",
"@fastify/swagger-ui": "^3.0.0",
Expand Down
42 changes: 31 additions & 11 deletions infrastructure/evault-core/src/core/http/server.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -286,30 +286,50 @@ export async function registerHttpRoutes(
// Helper function to validate JWT token
async function validateToken(authHeader: string | null): Promise<any | null> {
if (!authHeader || !authHeader.startsWith("Bearer ")) {
console.error("Token validation: Missing or invalid Authorization header format");
return null;
}

const token = authHeader.substring(7); // Remove 'Bearer ' prefix

try {
if (!process.env.REGISTRY_URL) {
console.error("REGISTRY_URL is not set");
// Try REGISTRY_URL first, fallback to PUBLIC_REGISTRY_URL
const registryUrl = process.env.REGISTRY_URL || process.env.PUBLIC_REGISTRY_URL;
if (!registryUrl) {
console.error("Token validation: REGISTRY_URL or PUBLIC_REGISTRY_URL is not set");
return null;
}

const jwksResponse = await axios.get(
new URL(
`/.well-known/jwks.json`,
process.env.REGISTRY_URL
).toString()
);
const jwksUrl = new URL(`/.well-known/jwks.json`, registryUrl).toString();
console.log(`Token validation: Fetching JWKS from ${jwksUrl}`);

const jwksResponse = await axios.get(jwksUrl, {
timeout: 5000,
});

console.log(`Token validation: JWKS response keys count: ${jwksResponse.data?.keys?.length || 0}`);

const JWKS = jose.createLocalJWKSet(jwksResponse.data);

// Decode token header to see what kid it's using
const decodedHeader = jose.decodeProtectedHeader(token);
console.log(`Token validation: Token header - alg: ${decodedHeader.alg}, kid: ${decodedHeader.kid}`);

const { payload } = await jose.jwtVerify(token, JWKS);


console.log(`Token validation: Token verified successfully, payload:`, payload);
return payload;
} catch (error) {
console.error("Token validation failed:", error);
} catch (error: any) {
console.error("Token validation failed:", error.message || error);
if (error.code) {
console.error(`Token validation error code: ${error.code}`);
}
if (error.response) {
console.error(`Token validation HTTP error: ${error.response.status} - ${error.response.statusText}`);
}
if (error.cause) {
console.error(`Token validation error cause:`, error.cause);
}
return null;
}
}
Expand Down
9 changes: 9 additions & 0 deletions infrastructure/evault-core/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@ import fastify, {
FastifyRequest,
FastifyReply,
} from "fastify";
import fastifyCors from "@fastify/cors";
import { renderVoyagerPage } from "graphql-voyager/middleware";
import { connectWithRetry } from "./core/db/retry-neo4j";
import neo4j, { Driver } from "neo4j-driver";
Expand DownExpand Up@@ -116,6 +117,14 @@ const initializeEVault = async (provisioningServiceInstance?: ProvisioningServic
logger: true,
});

// Register CORS plugin with relaxed settings
await fastifyServer.register(fastifyCors, {
origin: true, // Allow all origins
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization", "X-ENAME"],
credentials: true,
});

// Register HTTP routes with provisioning service if available
await registerHttpRoutes(fastifyServer, evaultInstance, provisioningServiceInstance, dbService);

Expand Down
5 changes: 5 additions & 0 deletions infrastructure/signature-validator/.gitignore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
node_modules/
dist/
*.log
.DS_Store

Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions infrastructure/eid-wallet/src/env.d.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,4 +5,5 @@ declare namespace App {}
declare module "$env/static/public" {
export const PUBLIC_REGISTRY_URL: string;
export const PUBLIC_PROVISIONER_URL: string;
export const PUBLIC_EID_WALLET_TOKEN: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

PUBLIC_EID_WALLET_TOKEN in $env/static/public will be exposed to the browser

Declaring PUBLIC_EID_WALLET_TOKEN in the $env/static/public module means its value is statically inlined into client bundles and is not secret. If this token is used for authenticating sensitive operations (e.g., calling eVault /public-key), exposing it to all client code (and, transitively, attackers running JS in the browser) significantly weakens your security model.

Please:

  • Confirm that this token is truly safe to be public, or
  • Move it to a private env module ($env/static/private/dynamic private) and adjust the flow so that only trusted server-side code uses it.

Based on prior learnings about avoiding PUBLIC_* envs for critical configuration (e.g., PUBLIC_PLATFORM_URL), this is worth a careful threat-model review rather than treating it as just another public constant.


🏁 Script executed:

#!/bin/bash# Examine env.d.ts file content to see token declaration context
cat infrastructure/eid-wallet/src/env.d.ts

Length of output: 251


🏁 Script executed:

#!/bin/bash# Search for how PUBLIC_EID_WALLET_TOKEN is used in the codebase
rg -n "PUBLIC_EID_WALLET_TOKEN" infrastructure/eid-wallet/

Length of output: 469


🏁 Script executed:

#!/bin/bash# Check for .env.example or .env template files
fd -t f "\\.env" infrastructure/eid-wallet/ | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash# Search for eVault API calls and /public-key references to understand token usage
rg -n "public-key|eVault|PUBLIC_EID_WALLET" infrastructure/eid-wallet/ -A 3 -B 3

Length of output: 47537


}
71 changes: 49 additions & 22 deletions infrastructure/eid-wallet/src/lib/global/controllers/evault.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
import { PUBLIC_REGISTRY_URL, PUBLIC_PROVISIONER_URL } from "$env/static/public";
import {
PUBLIC_REGISTRY_URL,
PUBLIC_PROVISIONER_URL,
PUBLIC_EID_WALLET_TOKEN,
} from "$env/static/public";
import type { Store } from "@tauri-apps/plugin-store";
import axios from "axios";
import { GraphQLClient } from "graphql-request";
Expand DownExpand Up@@ -52,7 +56,11 @@ export class VaultController {
#profileCreationStatus: "idle" | "loading" | "success" | "failed" = "idle";
#notificationService: NotificationService;

constructor(store: Store, userController: UserController, keyService?: KeyService) {
constructor(
store: Store,
userController: UserController,
keyService?: KeyService,
) {
this.#store = store;
this.#userController = userController;
this.#keyService = keyService || null;
Expand DownExpand Up@@ -81,17 +89,21 @@ export class VaultController {
* Sync public key to eVault core
* Checks if public key was already saved, calls /whois, and PATCH if needed
*/
private async syncPublicKey(eName: string): Promise<void> {
async syncPublicKey(eName: string): Promise<void> {
try {
// Check if we've already saved the public key
const savedKey = localStorage.getItem(`publicKeySaved_${eName}`);
if (savedKey === "true") {
console.log(`Public key already saved for ${eName}, skipping sync`);
console.log(
`Public key already saved for ${eName}, skipping sync`,
);
return;
}

if (!this.#keyService) {
console.warn("KeyService not available, cannot sync public key");
console.warn(
"KeyService not available, cannot sync public key",
);
return;
}

Expand All@@ -118,23 +130,42 @@ export class VaultController {
return;
}

// Get public key from KeyService
const publicKey = await this.#keyService.getPublicKey(eName, "signing");
// Get public key using the exact same logic as onboarding/verification flow
// KEY_ID is always "default", context depends on whether user is pre-verification
const KEY_ID = "default";

// Determine context: check if user is pre-verification (fake/demo user)
const isFake = await this.#userController.isFake;
const context = isFake ? "pre-verification" : "onboarding";

// Get public key using the same method as getApplicationPublicKey() in onboarding/verify
let publicKey: string | undefined;
try {
publicKey = await this.#keyService.getPublicKey(
KEY_ID,
context,
);
} catch (error) {
console.error(
`Failed to get public key for ${KEY_ID} with context ${context}:`,
error,
);
return;
}

if (!publicKey) {
console.warn(`No public key found for ${eName}, cannot sync`);
console.warn(
`No public key found for ${KEY_ID} with context ${context}, cannot sync`,
);
return;
}

// Get authentication token from registry
let authToken: string | null = null;
try {
const entropyResponse = await axios.get(
new URL("/entropy", PUBLIC_REGISTRY_URL).toString()
// Get authentication token from environment variable
const authToken = PUBLIC_EID_WALLET_TOKEN || null;
if (!authToken) {
console.warn(
"PUBLIC_EID_WALLET_TOKEN not set, request may fail authentication",
);
authToken = entropyResponse.data?.token || null;
} catch (error) {
console.error("Failed to get auth token from registry:", error);
// Continue without token - server will reject if auth is required
}

// Call PATCH /public-key to save the public key
Expand All@@ -148,11 +179,7 @@ export class VaultController {
headers["Authorization"] = `Bearer ${authToken}`;
}

await axios.patch(
patchUrl,
{ publicKey },
{ headers }
);
await axios.patch(patchUrl, { publicKey }, { headers });

// Mark as saved
localStorage.setItem(`publicKeySaved_${eName}`, "true");
Expand Down
6 changes: 5 additions & 1 deletion infrastructure/eid-wallet/src/lib/global/state.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,11 @@ export class GlobalState {
this.securityController = new SecurityController(store);
this.userController = new UserController(store);
this.keyService = keyService;
this.vaultController = new VaultController(store, this.userController, keyService);
this.vaultController = new VaultController(
store,
this.userController,
keyService,
);
this.notificationService = NotificationService.getInstance();
}

Expand Down
20 changes: 20 additions & 0 deletions infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,6 +98,16 @@ onMount(async () => {
}
// For other errors, continue to app - non-blocking
}

// Sync public key to eVault core
try {
await globalState.vaultController.syncPublicKey(
vault.ename,
);
} catch (error) {
console.error("Error syncing public key:", error);
// Continue to app even if sync fails - non-blocking
}
}
} catch (error) {
console.error("Error during eVault health check:", error);
Expand DownExpand Up@@ -170,6 +180,16 @@ onMount(async () => {
}
// For other errors, continue to app - non-blocking
}

// Sync public key to eVault core
try {
await globalState.vaultController.syncPublicKey(
vault.ename,
);
} catch (error) {
console.error("Error syncing public key:", error);
// Continue to app even if sync fails - non-blocking
}
}
} catch (error) {
console.error("Error during eVault health check:", error);
Expand Down
68 changes: 68 additions & 0 deletions infrastructure/evault-core/generate-test-token.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env node

/**
* Script to generate a test JWT token for evault-core authentication
*
* Usage:
* REGISTRY_ENTROPY_KEY_JWK='{"kty":"EC",...}' node generate-test-token.js
*
* Or set the environment variable in a .env file
*
* You can also use tsx to run it:
* REGISTRY_ENTROPY_KEY_JWK='...' npx tsx generate-test-token.js
*/

const { importJWK, SignJWT } = require("jose");
const dotenv = require("dotenv");
const path = require("path");

// Load .env file if it exists (try both root and current directory)
dotenv.config({ path: path.resolve(__dirname, "../../../.env") });
dotenv.config({ path: path.resolve(__dirname, ".env") });

async function generateTestToken() {
const jwkString = '{"kty":"EC","use":"sig","alg":"ES256","kid":"entropy-key-1","crv":"P-256","x":"POWUVJwOulAW0gheTVUHF4nXUenMCg0jxhGKI8M1LLU","y":"Cb4GC8Tt0gW7zMr-DhsDJisGVNgWttwjnyQl1HyU7hg","d":"FWqvWBzoiZcD0hh4JAMdJ7foxFRGqyV2Ei_eWvr1Si4"}';

if (!jwkString) {
console.error("Error: REGISTRY_ENTROPY_KEY_JWK environment variable is required");
console.error("\nUsage:");
console.error(" REGISTRY_ENTROPY_KEY_JWK='<your-jwk-json>' node generate-test-token.js");
console.error("\nOr set it in your .env file");
process.exit(1);
}

try {
const jwk = JSON.parse(jwkString);
const privateKey = await importJWK(jwk, "ES256");

// Generate token valid for 1 year
const token = await new SignJWT({
// Add any custom claims you want
platform: "eid-wallet",
purpose: "public-key-sync",
})
.setProtectedHeader({
alg: "ES256",
kid: jwk.kid || "entropy-key-1",
})
.setIssuedAt()
.setExpirationTime("1y") // Valid for 1 year
.sign(privateKey);

console.log("\n✅ Generated JWT Token (valid for 1 year):\n");
console.log(token);
console.log("\n📋 Use this token in the Authorization header:");
console.log(` Authorization: Bearer ${token}\n`);

return token;
} catch (error) {
console.error("Error generating token:", error.message);
if (error.message.includes("JSON")) {
console.error("\nMake sure REGISTRY_ENTROPY_KEY_JWK is valid JSON");
}
process.exit(1);
}
}

generateTestToken();

1 change: 1 addition & 0 deletions infrastructure/evault-core/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@
"migration:revert": "npm run typeorm migration:revert -- -d dist/config/database.js"
},
"dependencies": {
"@fastify/cors": "^8.5.0",
"@fastify/formbody": "^8.0.2",
"@fastify/swagger": "^8.14.0",
"@fastify/swagger-ui": "^3.0.0",
Expand Down
42 changes: 31 additions & 11 deletions infrastructure/evault-core/src/core/http/server.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -286,30 +286,50 @@ export async function registerHttpRoutes(
// Helper function to validate JWT token
async function validateToken(authHeader: string | null): Promise<any | null> {
if (!authHeader || !authHeader.startsWith("Bearer ")) {
console.error("Token validation: Missing or invalid Authorization header format");
return null;
}

const token = authHeader.substring(7); // Remove 'Bearer ' prefix

try {
if (!process.env.REGISTRY_URL) {
console.error("REGISTRY_URL is not set");
// Try REGISTRY_URL first, fallback to PUBLIC_REGISTRY_URL
const registryUrl = process.env.REGISTRY_URL || process.env.PUBLIC_REGISTRY_URL;
if (!registryUrl) {
console.error("Token validation: REGISTRY_URL or PUBLIC_REGISTRY_URL is not set");
return null;
}

const jwksResponse = await axios.get(
new URL(
`/.well-known/jwks.json`,
process.env.REGISTRY_URL
).toString()
);
const jwksUrl = new URL(`/.well-known/jwks.json`, registryUrl).toString();
console.log(`Token validation: Fetching JWKS from ${jwksUrl}`);

const jwksResponse = await axios.get(jwksUrl, {
timeout: 5000,
});

console.log(`Token validation: JWKS response keys count: ${jwksResponse.data?.keys?.length || 0}`);

const JWKS = jose.createLocalJWKSet(jwksResponse.data);

// Decode token header to see what kid it's using
const decodedHeader = jose.decodeProtectedHeader(token);
console.log(`Token validation: Token header - alg: ${decodedHeader.alg}, kid: ${decodedHeader.kid}`);

const { payload } = await jose.jwtVerify(token, JWKS);


console.log(`Token validation: Token verified successfully, payload:`, payload);
return payload;
} catch (error) {
console.error("Token validation failed:", error);
} catch (error: any) {
console.error("Token validation failed:", error.message || error);
if (error.code) {
console.error(`Token validation error code: ${error.code}`);
}
if (error.response) {
console.error(`Token validation HTTP error: ${error.response.status} - ${error.response.statusText}`);
}
if (error.cause) {
console.error(`Token validation error cause:`, error.cause);
}
return null;
}
}
Expand Down
9 changes: 9 additions & 0 deletions infrastructure/evault-core/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@ import fastify, {
FastifyRequest,
FastifyReply,
} from "fastify";
import fastifyCors from "@fastify/cors";
import { renderVoyagerPage } from "graphql-voyager/middleware";
import { connectWithRetry } from "./core/db/retry-neo4j";
import neo4j, { Driver } from "neo4j-driver";
Expand DownExpand Up@@ -116,6 +117,14 @@ const initializeEVault = async (provisioningServiceInstance?: ProvisioningServic
logger: true,
});

// Register CORS plugin with relaxed settings
await fastifyServer.register(fastifyCors, {
origin: true, // Allow all origins
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization", "X-ENAME"],
credentials: true,
});

// Register HTTP routes with provisioning service if available
await registerHttpRoutes(fastifyServer, evaultInstance, provisioningServiceInstance, dbService);

Expand Down
5 changes: 5 additions & 0 deletions infrastructure/signature-validator/.gitignore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
node_modules/
dist/
*.log
.DS_Store

Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions infrastructure/eid-wallet/src/env.d.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,4 +5,5 @@ declare namespace App {}
declare module "$env/static/public" {
export const PUBLIC_REGISTRY_URL: string;
export const PUBLIC_PROVISIONER_URL: string;
export const PUBLIC_EID_WALLET_TOKEN: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

PUBLIC_EID_WALLET_TOKEN in $env/static/public will be exposed to the browser

Declaring PUBLIC_EID_WALLET_TOKEN in the $env/static/public module means its value is statically inlined into client bundles and is not secret. If this token is used for authenticating sensitive operations (e.g., calling eVault /public-key), exposing it to all client code (and, transitively, attackers running JS in the browser) significantly weakens your security model.

Please:

  • Confirm that this token is truly safe to be public, or
  • Move it to a private env module ($env/static/private/dynamic private) and adjust the flow so that only trusted server-side code uses it.

Based on prior learnings about avoiding PUBLIC_* envs for critical configuration (e.g., PUBLIC_PLATFORM_URL), this is worth a careful threat-model review rather than treating it as just another public constant.


🏁 Script executed:

#!/bin/bash# Examine env.d.ts file content to see token declaration context
cat infrastructure/eid-wallet/src/env.d.ts

Length of output: 251


🏁 Script executed:

#!/bin/bash# Search for how PUBLIC_EID_WALLET_TOKEN is used in the codebase
rg -n "PUBLIC_EID_WALLET_TOKEN" infrastructure/eid-wallet/

Length of output: 469


🏁 Script executed:

#!/bin/bash# Check for .env.example or .env template files
fd -t f "\\.env" infrastructure/eid-wallet/ | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash# Search for eVault API calls and /public-key references to understand token usage
rg -n "public-key|eVault|PUBLIC_EID_WALLET" infrastructure/eid-wallet/ -A 3 -B 3

Length of output: 47537


}
71 changes: 49 additions & 22 deletions infrastructure/eid-wallet/src/lib/global/controllers/evault.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
import { PUBLIC_REGISTRY_URL, PUBLIC_PROVISIONER_URL } from "$env/static/public";
import {
PUBLIC_REGISTRY_URL,
PUBLIC_PROVISIONER_URL,
PUBLIC_EID_WALLET_TOKEN,
} from "$env/static/public";
import type { Store } from "@tauri-apps/plugin-store";
import axios from "axios";
import { GraphQLClient } from "graphql-request";
Expand DownExpand Up@@ -52,7 +56,11 @@ export class VaultController {
#profileCreationStatus: "idle" | "loading" | "success" | "failed" = "idle";
#notificationService: NotificationService;

constructor(store: Store, userController: UserController, keyService?: KeyService) {
constructor(
store: Store,
userController: UserController,
keyService?: KeyService,
) {
this.#store = store;
this.#userController = userController;
this.#keyService = keyService || null;
Expand DownExpand Up@@ -81,17 +89,21 @@ export class VaultController {
* Sync public key to eVault core
* Checks if public key was already saved, calls /whois, and PATCH if needed
*/
private async syncPublicKey(eName: string): Promise<void> {
async syncPublicKey(eName: string): Promise<void> {
try {
// Check if we've already saved the public key
const savedKey = localStorage.getItem(`publicKeySaved_${eName}`);
if (savedKey === "true") {
console.log(`Public key already saved for ${eName}, skipping sync`);
console.log(
`Public key already saved for ${eName}, skipping sync`,
);
return;
}

if (!this.#keyService) {
console.warn("KeyService not available, cannot sync public key");
console.warn(
"KeyService not available, cannot sync public key",
);
return;
}

Expand All@@ -118,23 +130,42 @@ export class VaultController {
return;
}

// Get public key from KeyService
const publicKey = await this.#keyService.getPublicKey(eName, "signing");
// Get public key using the exact same logic as onboarding/verification flow
// KEY_ID is always "default", context depends on whether user is pre-verification
const KEY_ID = "default";

// Determine context: check if user is pre-verification (fake/demo user)
const isFake = await this.#userController.isFake;
const context = isFake ? "pre-verification" : "onboarding";

// Get public key using the same method as getApplicationPublicKey() in onboarding/verify
let publicKey: string | undefined;
try {
publicKey = await this.#keyService.getPublicKey(
KEY_ID,
context,
);
} catch (error) {
console.error(
`Failed to get public key for ${KEY_ID} with context ${context}:`,
error,
);
return;
}

if (!publicKey) {
console.warn(`No public key found for ${eName}, cannot sync`);
console.warn(
`No public key found for ${KEY_ID} with context ${context}, cannot sync`,
);
return;
}

// Get authentication token from registry
let authToken: string | null = null;
try {
const entropyResponse = await axios.get(
new URL("/entropy", PUBLIC_REGISTRY_URL).toString()
// Get authentication token from environment variable
const authToken = PUBLIC_EID_WALLET_TOKEN || null;
if (!authToken) {
console.warn(
"PUBLIC_EID_WALLET_TOKEN not set, request may fail authentication",
);
authToken = entropyResponse.data?.token || null;
} catch (error) {
console.error("Failed to get auth token from registry:", error);
// Continue without token - server will reject if auth is required
}

// Call PATCH /public-key to save the public key
Expand All@@ -148,11 +179,7 @@ export class VaultController {
headers["Authorization"] = `Bearer ${authToken}`;
}

await axios.patch(
patchUrl,
{ publicKey },
{ headers }
);
await axios.patch(patchUrl, { publicKey }, { headers });

// Mark as saved
localStorage.setItem(`publicKeySaved_${eName}`, "true");
Expand Down
6 changes: 5 additions & 1 deletion infrastructure/eid-wallet/src/lib/global/state.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,11 @@ export class GlobalState {
this.securityController = new SecurityController(store);
this.userController = new UserController(store);
this.keyService = keyService;
this.vaultController = new VaultController(store, this.userController, keyService);
this.vaultController = new VaultController(
store,
this.userController,
keyService,
);
this.notificationService = NotificationService.getInstance();
}

Expand Down
20 changes: 20 additions & 0 deletions infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,6 +98,16 @@ onMount(async () => {
}
// For other errors, continue to app - non-blocking
}

// Sync public key to eVault core
try {
await globalState.vaultController.syncPublicKey(
vault.ename,
);
} catch (error) {
console.error("Error syncing public key:", error);
// Continue to app even if sync fails - non-blocking
}
}
} catch (error) {
console.error("Error during eVault health check:", error);
Expand DownExpand Up@@ -170,6 +180,16 @@ onMount(async () => {
}
// For other errors, continue to app - non-blocking
}

// Sync public key to eVault core
try {
await globalState.vaultController.syncPublicKey(
vault.ename,
);
} catch (error) {
console.error("Error syncing public key:", error);
// Continue to app even if sync fails - non-blocking
}
}
} catch (error) {
console.error("Error during eVault health check:", error);
Expand Down
68 changes: 68 additions & 0 deletions infrastructure/evault-core/generate-test-token.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env node

/**
* Script to generate a test JWT token for evault-core authentication
*
* Usage:
* REGISTRY_ENTROPY_KEY_JWK='{"kty":"EC",...}' node generate-test-token.js
*
* Or set the environment variable in a .env file
*
* You can also use tsx to run it:
* REGISTRY_ENTROPY_KEY_JWK='...' npx tsx generate-test-token.js
*/

const { importJWK, SignJWT } = require("jose");
const dotenv = require("dotenv");
const path = require("path");

// Load .env file if it exists (try both root and current directory)
dotenv.config({ path: path.resolve(__dirname, "../../../.env") });
dotenv.config({ path: path.resolve(__dirname, ".env") });

async function generateTestToken() {
const jwkString = '{"kty":"EC","use":"sig","alg":"ES256","kid":"entropy-key-1","crv":"P-256","x":"POWUVJwOulAW0gheTVUHF4nXUenMCg0jxhGKI8M1LLU","y":"Cb4GC8Tt0gW7zMr-DhsDJisGVNgWttwjnyQl1HyU7hg","d":"FWqvWBzoiZcD0hh4JAMdJ7foxFRGqyV2Ei_eWvr1Si4"}';

if (!jwkString) {
console.error("Error: REGISTRY_ENTROPY_KEY_JWK environment variable is required");
console.error("\nUsage:");
console.error(" REGISTRY_ENTROPY_KEY_JWK='<your-jwk-json>' node generate-test-token.js");
console.error("\nOr set it in your .env file");
process.exit(1);
}

try {
const jwk = JSON.parse(jwkString);
const privateKey = await importJWK(jwk, "ES256");

// Generate token valid for 1 year
const token = await new SignJWT({
// Add any custom claims you want
platform: "eid-wallet",
purpose: "public-key-sync",
})
.setProtectedHeader({
alg: "ES256",
kid: jwk.kid || "entropy-key-1",
})
.setIssuedAt()
.setExpirationTime("1y") // Valid for 1 year
.sign(privateKey);

console.log("\n✅ Generated JWT Token (valid for 1 year):\n");
console.log(token);
console.log("\n📋 Use this token in the Authorization header:");
console.log(` Authorization: Bearer ${token}\n`);

return token;
} catch (error) {
console.error("Error generating token:", error.message);
if (error.message.includes("JSON")) {
console.error("\nMake sure REGISTRY_ENTROPY_KEY_JWK is valid JSON");
}
process.exit(1);
}
}

generateTestToken();

1 change: 1 addition & 0 deletions infrastructure/evault-core/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@
"migration:revert": "npm run typeorm migration:revert -- -d dist/config/database.js"
},
"dependencies": {
"@fastify/cors": "^8.5.0",
"@fastify/formbody": "^8.0.2",
"@fastify/swagger": "^8.14.0",
"@fastify/swagger-ui": "^3.0.0",
Expand Down
42 changes: 31 additions & 11 deletions infrastructure/evault-core/src/core/http/server.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -286,30 +286,50 @@ export async function registerHttpRoutes(
// Helper function to validate JWT token
async function validateToken(authHeader: string | null): Promise<any | null> {
if (!authHeader || !authHeader.startsWith("Bearer ")) {
console.error("Token validation: Missing or invalid Authorization header format");
return null;
}

const token = authHeader.substring(7); // Remove 'Bearer ' prefix

try {
if (!process.env.REGISTRY_URL) {
console.error("REGISTRY_URL is not set");
// Try REGISTRY_URL first, fallback to PUBLIC_REGISTRY_URL
const registryUrl = process.env.REGISTRY_URL || process.env.PUBLIC_REGISTRY_URL;
if (!registryUrl) {
console.error("Token validation: REGISTRY_URL or PUBLIC_REGISTRY_URL is not set");
return null;
}

const jwksResponse = await axios.get(
new URL(
`/.well-known/jwks.json`,
process.env.REGISTRY_URL
).toString()
);
const jwksUrl = new URL(`/.well-known/jwks.json`, registryUrl).toString();
console.log(`Token validation: Fetching JWKS from ${jwksUrl}`);

const jwksResponse = await axios.get(jwksUrl, {
timeout: 5000,
});

console.log(`Token validation: JWKS response keys count: ${jwksResponse.data?.keys?.length || 0}`);

const JWKS = jose.createLocalJWKSet(jwksResponse.data);

// Decode token header to see what kid it's using
const decodedHeader = jose.decodeProtectedHeader(token);
console.log(`Token validation: Token header - alg: ${decodedHeader.alg}, kid: ${decodedHeader.kid}`);

const { payload } = await jose.jwtVerify(token, JWKS);


console.log(`Token validation: Token verified successfully, payload:`, payload);
return payload;
} catch (error) {
console.error("Token validation failed:", error);
} catch (error: any) {
console.error("Token validation failed:", error.message || error);
if (error.code) {
console.error(`Token validation error code: ${error.code}`);
}
if (error.response) {
console.error(`Token validation HTTP error: ${error.response.status} - ${error.response.statusText}`);
}
if (error.cause) {
console.error(`Token validation error cause:`, error.cause);
}
return null;
}
}
Expand Down
9 changes: 9 additions & 0 deletions infrastructure/evault-core/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@ import fastify, {
FastifyRequest,
FastifyReply,
} from "fastify";
import fastifyCors from "@fastify/cors";
import { renderVoyagerPage } from "graphql-voyager/middleware";
import { connectWithRetry } from "./core/db/retry-neo4j";
import neo4j, { Driver } from "neo4j-driver";
Expand DownExpand Up@@ -116,6 +117,14 @@ const initializeEVault = async (provisioningServiceInstance?: ProvisioningServic
logger: true,
});

// Register CORS plugin with relaxed settings
await fastifyServer.register(fastifyCors, {
origin: true, // Allow all origins
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization", "X-ENAME"],
credentials: true,
});

// Register HTTP routes with provisioning service if available
await registerHttpRoutes(fastifyServer, evaultInstance, provisioningServiceInstance, dbService);

Expand Down
5 changes: 5 additions & 0 deletions infrastructure/signature-validator/.gitignore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
node_modules/
dist/
*.log
.DS_Store

Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions infrastructure/eid-wallet/src/env.d.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,4 +5,5 @@ declare namespace App {}
declare module "$env/static/public" {
export const PUBLIC_REGISTRY_URL: string;
export const PUBLIC_PROVISIONER_URL: string;
export const PUBLIC_EID_WALLET_TOKEN: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

PUBLIC_EID_WALLET_TOKEN in $env/static/public will be exposed to the browser

Declaring PUBLIC_EID_WALLET_TOKEN in the $env/static/public module means its value is statically inlined into client bundles and is not secret. If this token is used for authenticating sensitive operations (e.g., calling eVault /public-key), exposing it to all client code (and, transitively, attackers running JS in the browser) significantly weakens your security model.

Please:

  • Confirm that this token is truly safe to be public, or
  • Move it to a private env module ($env/static/private/dynamic private) and adjust the flow so that only trusted server-side code uses it.

Based on prior learnings about avoiding PUBLIC_* envs for critical configuration (e.g., PUBLIC_PLATFORM_URL), this is worth a careful threat-model review rather than treating it as just another public constant.


🏁 Script executed:

#!/bin/bash# Examine env.d.ts file content to see token declaration context
cat infrastructure/eid-wallet/src/env.d.ts

Length of output: 251


🏁 Script executed:

#!/bin/bash# Search for how PUBLIC_EID_WALLET_TOKEN is used in the codebase
rg -n "PUBLIC_EID_WALLET_TOKEN" infrastructure/eid-wallet/

Length of output: 469


🏁 Script executed:

#!/bin/bash# Check for .env.example or .env template files
fd -t f "\\.env" infrastructure/eid-wallet/ | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash# Search for eVault API calls and /public-key references to understand token usage
rg -n "public-key|eVault|PUBLIC_EID_WALLET" infrastructure/eid-wallet/ -A 3 -B 3

Length of output: 47537


}
71 changes: 49 additions & 22 deletions infrastructure/eid-wallet/src/lib/global/controllers/evault.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
import { PUBLIC_REGISTRY_URL, PUBLIC_PROVISIONER_URL } from "$env/static/public";
import {
PUBLIC_REGISTRY_URL,
PUBLIC_PROVISIONER_URL,
PUBLIC_EID_WALLET_TOKEN,
} from "$env/static/public";
import type { Store } from "@tauri-apps/plugin-store";
import axios from "axios";
import { GraphQLClient } from "graphql-request";
Expand DownExpand Up@@ -52,7 +56,11 @@ export class VaultController {
#profileCreationStatus: "idle" | "loading" | "success" | "failed" = "idle";
#notificationService: NotificationService;

constructor(store: Store, userController: UserController, keyService?: KeyService) {
constructor(
store: Store,
userController: UserController,
keyService?: KeyService,
) {
this.#store = store;
this.#userController = userController;
this.#keyService = keyService || null;
Expand DownExpand Up@@ -81,17 +89,21 @@ export class VaultController {
* Sync public key to eVault core
* Checks if public key was already saved, calls /whois, and PATCH if needed
*/
private async syncPublicKey(eName: string): Promise<void> {
async syncPublicKey(eName: string): Promise<void> {
try {
// Check if we've already saved the public key
const savedKey = localStorage.getItem(`publicKeySaved_${eName}`);
if (savedKey === "true") {
console.log(`Public key already saved for ${eName}, skipping sync`);
console.log(
`Public key already saved for ${eName}, skipping sync`,
);
return;
}

if (!this.#keyService) {
console.warn("KeyService not available, cannot sync public key");
console.warn(
"KeyService not available, cannot sync public key",
);
return;
}

Expand All@@ -118,23 +130,42 @@ export class VaultController {
return;
}

// Get public key from KeyService
const publicKey = await this.#keyService.getPublicKey(eName, "signing");
// Get public key using the exact same logic as onboarding/verification flow
// KEY_ID is always "default", context depends on whether user is pre-verification
const KEY_ID = "default";

// Determine context: check if user is pre-verification (fake/demo user)
const isFake = await this.#userController.isFake;
const context = isFake ? "pre-verification" : "onboarding";

// Get public key using the same method as getApplicationPublicKey() in onboarding/verify
let publicKey: string | undefined;
try {
publicKey = await this.#keyService.getPublicKey(
KEY_ID,
context,
);
} catch (error) {
console.error(
`Failed to get public key for ${KEY_ID} with context ${context}:`,
error,
);
return;
}

if (!publicKey) {
console.warn(`No public key found for ${eName}, cannot sync`);
console.warn(
`No public key found for ${KEY_ID} with context ${context}, cannot sync`,
);
return;
}

// Get authentication token from registry
let authToken: string | null = null;
try {
const entropyResponse = await axios.get(
new URL("/entropy", PUBLIC_REGISTRY_URL).toString()
// Get authentication token from environment variable
const authToken = PUBLIC_EID_WALLET_TOKEN || null;
if (!authToken) {
console.warn(
"PUBLIC_EID_WALLET_TOKEN not set, request may fail authentication",
);
authToken = entropyResponse.data?.token || null;
} catch (error) {
console.error("Failed to get auth token from registry:", error);
// Continue without token - server will reject if auth is required
}

// Call PATCH /public-key to save the public key
Expand All@@ -148,11 +179,7 @@ export class VaultController {
headers["Authorization"] = `Bearer ${authToken}`;
}

await axios.patch(
patchUrl,
{ publicKey },
{ headers }
);
await axios.patch(patchUrl, { publicKey }, { headers });

// Mark as saved
localStorage.setItem(`publicKeySaved_${eName}`, "true");
Expand Down
6 changes: 5 additions & 1 deletion infrastructure/eid-wallet/src/lib/global/state.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,11 @@ export class GlobalState {
this.securityController = new SecurityController(store);
this.userController = new UserController(store);
this.keyService = keyService;
this.vaultController = new VaultController(store, this.userController, keyService);
this.vaultController = new VaultController(
store,
this.userController,
keyService,
);
this.notificationService = NotificationService.getInstance();
}

Expand Down
20 changes: 20 additions & 0 deletions infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,6 +98,16 @@ onMount(async () => {
}
// For other errors, continue to app - non-blocking
}

// Sync public key to eVault core
try {
await globalState.vaultController.syncPublicKey(
vault.ename,
);
} catch (error) {
console.error("Error syncing public key:", error);
// Continue to app even if sync fails - non-blocking
}
}
} catch (error) {
console.error("Error during eVault health check:", error);
Expand DownExpand Up@@ -170,6 +180,16 @@ onMount(async () => {
}
// For other errors, continue to app - non-blocking
}

// Sync public key to eVault core
try {
await globalState.vaultController.syncPublicKey(
vault.ename,
);
} catch (error) {
console.error("Error syncing public key:", error);
// Continue to app even if sync fails - non-blocking
}
}
} catch (error) {
console.error("Error during eVault health check:", error);
Expand Down
68 changes: 68 additions & 0 deletions infrastructure/evault-core/generate-test-token.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env node

/**
* Script to generate a test JWT token for evault-core authentication
*
* Usage:
* REGISTRY_ENTROPY_KEY_JWK='{"kty":"EC",...}' node generate-test-token.js
*
* Or set the environment variable in a .env file
*
* You can also use tsx to run it:
* REGISTRY_ENTROPY_KEY_JWK='...' npx tsx generate-test-token.js
*/

const { importJWK, SignJWT } = require("jose");
const dotenv = require("dotenv");
const path = require("path");

// Load .env file if it exists (try both root and current directory)
dotenv.config({ path: path.resolve(__dirname, "../../../.env") });
dotenv.config({ path: path.resolve(__dirname, ".env") });

async function generateTestToken() {
const jwkString = '{"kty":"EC","use":"sig","alg":"ES256","kid":"entropy-key-1","crv":"P-256","x":"POWUVJwOulAW0gheTVUHF4nXUenMCg0jxhGKI8M1LLU","y":"Cb4GC8Tt0gW7zMr-DhsDJisGVNgWttwjnyQl1HyU7hg","d":"FWqvWBzoiZcD0hh4JAMdJ7foxFRGqyV2Ei_eWvr1Si4"}';

if (!jwkString) {
console.error("Error: REGISTRY_ENTROPY_KEY_JWK environment variable is required");
console.error("\nUsage:");
console.error(" REGISTRY_ENTROPY_KEY_JWK='<your-jwk-json>' node generate-test-token.js");
console.error("\nOr set it in your .env file");
process.exit(1);
}

try {
const jwk = JSON.parse(jwkString);
const privateKey = await importJWK(jwk, "ES256");

// Generate token valid for 1 year
const token = await new SignJWT({
// Add any custom claims you want
platform: "eid-wallet",
purpose: "public-key-sync",
})
.setProtectedHeader({
alg: "ES256",
kid: jwk.kid || "entropy-key-1",
})
.setIssuedAt()
.setExpirationTime("1y") // Valid for 1 year
.sign(privateKey);

console.log("\n✅ Generated JWT Token (valid for 1 year):\n");
console.log(token);
console.log("\n📋 Use this token in the Authorization header:");
console.log(` Authorization: Bearer ${token}\n`);

return token;
} catch (error) {
console.error("Error generating token:", error.message);
if (error.message.includes("JSON")) {
console.error("\nMake sure REGISTRY_ENTROPY_KEY_JWK is valid JSON");
}
process.exit(1);
}
}

generateTestToken();

1 change: 1 addition & 0 deletions infrastructure/evault-core/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@
"migration:revert": "npm run typeorm migration:revert -- -d dist/config/database.js"
},
"dependencies": {
"@fastify/cors": "^8.5.0",
"@fastify/formbody": "^8.0.2",
"@fastify/swagger": "^8.14.0",
"@fastify/swagger-ui": "^3.0.0",
Expand Down
42 changes: 31 additions & 11 deletions infrastructure/evault-core/src/core/http/server.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -286,30 +286,50 @@ export async function registerHttpRoutes(
// Helper function to validate JWT token
async function validateToken(authHeader: string | null): Promise<any | null> {
if (!authHeader || !authHeader.startsWith("Bearer ")) {
console.error("Token validation: Missing or invalid Authorization header format");
return null;
}

const token = authHeader.substring(7); // Remove 'Bearer ' prefix

try {
if (!process.env.REGISTRY_URL) {
console.error("REGISTRY_URL is not set");
// Try REGISTRY_URL first, fallback to PUBLIC_REGISTRY_URL
const registryUrl = process.env.REGISTRY_URL || process.env.PUBLIC_REGISTRY_URL;
if (!registryUrl) {
console.error("Token validation: REGISTRY_URL or PUBLIC_REGISTRY_URL is not set");
return null;
}

const jwksResponse = await axios.get(
new URL(
`/.well-known/jwks.json`,
process.env.REGISTRY_URL
).toString()
);
const jwksUrl = new URL(`/.well-known/jwks.json`, registryUrl).toString();
console.log(`Token validation: Fetching JWKS from ${jwksUrl}`);

const jwksResponse = await axios.get(jwksUrl, {
timeout: 5000,
});

console.log(`Token validation: JWKS response keys count: ${jwksResponse.data?.keys?.length || 0}`);

const JWKS = jose.createLocalJWKSet(jwksResponse.data);

// Decode token header to see what kid it's using
const decodedHeader = jose.decodeProtectedHeader(token);
console.log(`Token validation: Token header - alg: ${decodedHeader.alg}, kid: ${decodedHeader.kid}`);

const { payload } = await jose.jwtVerify(token, JWKS);


console.log(`Token validation: Token verified successfully, payload:`, payload);
return payload;
} catch (error) {
console.error("Token validation failed:", error);
} catch (error: any) {
console.error("Token validation failed:", error.message || error);
if (error.code) {
console.error(`Token validation error code: ${error.code}`);
}
if (error.response) {
console.error(`Token validation HTTP error: ${error.response.status} - ${error.response.statusText}`);
}
if (error.cause) {
console.error(`Token validation error cause:`, error.cause);
}
return null;
}
}
Expand Down
9 changes: 9 additions & 0 deletions infrastructure/evault-core/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@ import fastify, {
FastifyRequest,
FastifyReply,
} from "fastify";
import fastifyCors from "@fastify/cors";
import { renderVoyagerPage } from "graphql-voyager/middleware";
import { connectWithRetry } from "./core/db/retry-neo4j";
import neo4j, { Driver } from "neo4j-driver";
Expand DownExpand Up@@ -116,6 +117,14 @@ const initializeEVault = async (provisioningServiceInstance?: ProvisioningServic
logger: true,
});

// Register CORS plugin with relaxed settings
await fastifyServer.register(fastifyCors, {
origin: true, // Allow all origins
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization", "X-ENAME"],
credentials: true,
});

// Register HTTP routes with provisioning service if available
await registerHttpRoutes(fastifyServer, evaultInstance, provisioningServiceInstance, dbService);

Expand Down
5 changes: 5 additions & 0 deletions infrastructure/signature-validator/.gitignore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
node_modules/
dist/
*.log
.DS_Store

Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions infrastructure/eid-wallet/src/env.d.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,4 +5,5 @@ declare namespace App {}
declare module "$env/static/public" {
export const PUBLIC_REGISTRY_URL: string;
export const PUBLIC_PROVISIONER_URL: string;
export const PUBLIC_EID_WALLET_TOKEN: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

PUBLIC_EID_WALLET_TOKEN in $env/static/public will be exposed to the browser

Declaring PUBLIC_EID_WALLET_TOKEN in the $env/static/public module means its value is statically inlined into client bundles and is not secret. If this token is used for authenticating sensitive operations (e.g., calling eVault /public-key), exposing it to all client code (and, transitively, attackers running JS in the browser) significantly weakens your security model.

Please:

  • Confirm that this token is truly safe to be public, or
  • Move it to a private env module ($env/static/private/dynamic private) and adjust the flow so that only trusted server-side code uses it.

Based on prior learnings about avoiding PUBLIC_* envs for critical configuration (e.g., PUBLIC_PLATFORM_URL), this is worth a careful threat-model review rather than treating it as just another public constant.


🏁 Script executed:

#!/bin/bash# Examine env.d.ts file content to see token declaration context
cat infrastructure/eid-wallet/src/env.d.ts

Length of output: 251


🏁 Script executed:

#!/bin/bash# Search for how PUBLIC_EID_WALLET_TOKEN is used in the codebase
rg -n "PUBLIC_EID_WALLET_TOKEN" infrastructure/eid-wallet/

Length of output: 469


🏁 Script executed:

#!/bin/bash# Check for .env.example or .env template files
fd -t f "\\.env" infrastructure/eid-wallet/ | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash# Search for eVault API calls and /public-key references to understand token usage
rg -n "public-key|eVault|PUBLIC_EID_WALLET" infrastructure/eid-wallet/ -A 3 -B 3

Length of output: 47537


}
71 changes: 49 additions & 22 deletions infrastructure/eid-wallet/src/lib/global/controllers/evault.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
import { PUBLIC_REGISTRY_URL, PUBLIC_PROVISIONER_URL } from "$env/static/public";
import {
PUBLIC_REGISTRY_URL,
PUBLIC_PROVISIONER_URL,
PUBLIC_EID_WALLET_TOKEN,
} from "$env/static/public";
import type { Store } from "@tauri-apps/plugin-store";
import axios from "axios";
import { GraphQLClient } from "graphql-request";
Expand DownExpand Up@@ -52,7 +56,11 @@ export class VaultController {
#profileCreationStatus: "idle" | "loading" | "success" | "failed" = "idle";
#notificationService: NotificationService;

constructor(store: Store, userController: UserController, keyService?: KeyService) {
constructor(
store: Store,
userController: UserController,
keyService?: KeyService,
) {
this.#store = store;
this.#userController = userController;
this.#keyService = keyService || null;
Expand DownExpand Up@@ -81,17 +89,21 @@ export class VaultController {
* Sync public key to eVault core
* Checks if public key was already saved, calls /whois, and PATCH if needed
*/
private async syncPublicKey(eName: string): Promise<void> {
async syncPublicKey(eName: string): Promise<void> {
try {
// Check if we've already saved the public key
const savedKey = localStorage.getItem(`publicKeySaved_${eName}`);
if (savedKey === "true") {
console.log(`Public key already saved for ${eName}, skipping sync`);
console.log(
`Public key already saved for ${eName}, skipping sync`,
);
return;
}

if (!this.#keyService) {
console.warn("KeyService not available, cannot sync public key");
console.warn(
"KeyService not available, cannot sync public key",
);
return;
}

Expand All@@ -118,23 +130,42 @@ export class VaultController {
return;
}

// Get public key from KeyService
const publicKey = await this.#keyService.getPublicKey(eName, "signing");
// Get public key using the exact same logic as onboarding/verification flow
// KEY_ID is always "default", context depends on whether user is pre-verification
const KEY_ID = "default";

// Determine context: check if user is pre-verification (fake/demo user)
const isFake = await this.#userController.isFake;
const context = isFake ? "pre-verification" : "onboarding";

// Get public key using the same method as getApplicationPublicKey() in onboarding/verify
let publicKey: string | undefined;
try {
publicKey = await this.#keyService.getPublicKey(
KEY_ID,
context,
);
} catch (error) {
console.error(
`Failed to get public key for ${KEY_ID} with context ${context}:`,
error,
);
return;
}

if (!publicKey) {
console.warn(`No public key found for ${eName}, cannot sync`);
console.warn(
`No public key found for ${KEY_ID} with context ${context}, cannot sync`,
);
return;
}

// Get authentication token from registry
let authToken: string | null = null;
try {
const entropyResponse = await axios.get(
new URL("/entropy", PUBLIC_REGISTRY_URL).toString()
// Get authentication token from environment variable
const authToken = PUBLIC_EID_WALLET_TOKEN || null;
if (!authToken) {
console.warn(
"PUBLIC_EID_WALLET_TOKEN not set, request may fail authentication",
);
authToken = entropyResponse.data?.token || null;
} catch (error) {
console.error("Failed to get auth token from registry:", error);
// Continue without token - server will reject if auth is required
}

// Call PATCH /public-key to save the public key
Expand All@@ -148,11 +179,7 @@ export class VaultController {
headers["Authorization"] = `Bearer ${authToken}`;
}

await axios.patch(
patchUrl,
{ publicKey },
{ headers }
);
await axios.patch(patchUrl, { publicKey }, { headers });

// Mark as saved
localStorage.setItem(`publicKeySaved_${eName}`, "true");
Expand Down
6 changes: 5 additions & 1 deletion infrastructure/eid-wallet/src/lib/global/state.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,11 @@ export class GlobalState {
this.securityController = new SecurityController(store);
this.userController = new UserController(store);
this.keyService = keyService;
this.vaultController = new VaultController(store, this.userController, keyService);
this.vaultController = new VaultController(
store,
this.userController,
keyService,
);
this.notificationService = NotificationService.getInstance();
}

Expand Down
20 changes: 20 additions & 0 deletions infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,6 +98,16 @@ onMount(async () => {
}
// For other errors, continue to app - non-blocking
}

// Sync public key to eVault core
try {
await globalState.vaultController.syncPublicKey(
vault.ename,
);
} catch (error) {
console.error("Error syncing public key:", error);
// Continue to app even if sync fails - non-blocking
}
}
} catch (error) {
console.error("Error during eVault health check:", error);
Expand DownExpand Up@@ -170,6 +180,16 @@ onMount(async () => {
}
// For other errors, continue to app - non-blocking
}

// Sync public key to eVault core
try {
await globalState.vaultController.syncPublicKey(
vault.ename,
);
} catch (error) {
console.error("Error syncing public key:", error);
// Continue to app even if sync fails - non-blocking
}
}
} catch (error) {
console.error("Error during eVault health check:", error);
Expand Down
68 changes: 68 additions & 0 deletions infrastructure/evault-core/generate-test-token.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env node

/**
* Script to generate a test JWT token for evault-core authentication
*
* Usage:
* REGISTRY_ENTROPY_KEY_JWK='{"kty":"EC",...}' node generate-test-token.js
*
* Or set the environment variable in a .env file
*
* You can also use tsx to run it:
* REGISTRY_ENTROPY_KEY_JWK='...' npx tsx generate-test-token.js
*/

const { importJWK, SignJWT } = require("jose");
const dotenv = require("dotenv");
const path = require("path");

// Load .env file if it exists (try both root and current directory)
dotenv.config({ path: path.resolve(__dirname, "../../../.env") });
dotenv.config({ path: path.resolve(__dirname, ".env") });

async function generateTestToken() {
const jwkString = '{"kty":"EC","use":"sig","alg":"ES256","kid":"entropy-key-1","crv":"P-256","x":"POWUVJwOulAW0gheTVUHF4nXUenMCg0jxhGKI8M1LLU","y":"Cb4GC8Tt0gW7zMr-DhsDJisGVNgWttwjnyQl1HyU7hg","d":"FWqvWBzoiZcD0hh4JAMdJ7foxFRGqyV2Ei_eWvr1Si4"}';

if (!jwkString) {
console.error("Error: REGISTRY_ENTROPY_KEY_JWK environment variable is required");
console.error("\nUsage:");
console.error(" REGISTRY_ENTROPY_KEY_JWK='<your-jwk-json>' node generate-test-token.js");
console.error("\nOr set it in your .env file");
process.exit(1);
}

try {
const jwk = JSON.parse(jwkString);
const privateKey = await importJWK(jwk, "ES256");

// Generate token valid for 1 year
const token = await new SignJWT({
// Add any custom claims you want
platform: "eid-wallet",
purpose: "public-key-sync",
})
.setProtectedHeader({
alg: "ES256",
kid: jwk.kid || "entropy-key-1",
})
.setIssuedAt()
.setExpirationTime("1y") // Valid for 1 year
.sign(privateKey);

console.log("\n✅ Generated JWT Token (valid for 1 year):\n");
console.log(token);
console.log("\n📋 Use this token in the Authorization header:");
console.log(` Authorization: Bearer ${token}\n`);

return token;
} catch (error) {
console.error("Error generating token:", error.message);
if (error.message.includes("JSON")) {
console.error("\nMake sure REGISTRY_ENTROPY_KEY_JWK is valid JSON");
}
process.exit(1);
}
}

generateTestToken();

1 change: 1 addition & 0 deletions infrastructure/evault-core/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@
"migration:revert": "npm run typeorm migration:revert -- -d dist/config/database.js"
},
"dependencies": {
"@fastify/cors": "^8.5.0",
"@fastify/formbody": "^8.0.2",
"@fastify/swagger": "^8.14.0",
"@fastify/swagger-ui": "^3.0.0",
Expand Down
42 changes: 31 additions & 11 deletions infrastructure/evault-core/src/core/http/server.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -286,30 +286,50 @@ export async function registerHttpRoutes(
// Helper function to validate JWT token
async function validateToken(authHeader: string | null): Promise<any | null> {
if (!authHeader || !authHeader.startsWith("Bearer ")) {
console.error("Token validation: Missing or invalid Authorization header format");
return null;
}

const token = authHeader.substring(7); // Remove 'Bearer ' prefix

try {
if (!process.env.REGISTRY_URL) {
console.error("REGISTRY_URL is not set");
// Try REGISTRY_URL first, fallback to PUBLIC_REGISTRY_URL
const registryUrl = process.env.REGISTRY_URL || process.env.PUBLIC_REGISTRY_URL;
if (!registryUrl) {
console.error("Token validation: REGISTRY_URL or PUBLIC_REGISTRY_URL is not set");
return null;
}

const jwksResponse = await axios.get(
new URL(
`/.well-known/jwks.json`,
process.env.REGISTRY_URL
).toString()
);
const jwksUrl = new URL(`/.well-known/jwks.json`, registryUrl).toString();
console.log(`Token validation: Fetching JWKS from ${jwksUrl}`);

const jwksResponse = await axios.get(jwksUrl, {
timeout: 5000,
});

console.log(`Token validation: JWKS response keys count: ${jwksResponse.data?.keys?.length || 0}`);

const JWKS = jose.createLocalJWKSet(jwksResponse.data);

// Decode token header to see what kid it's using
const decodedHeader = jose.decodeProtectedHeader(token);
console.log(`Token validation: Token header - alg: ${decodedHeader.alg}, kid: ${decodedHeader.kid}`);

const { payload } = await jose.jwtVerify(token, JWKS);


console.log(`Token validation: Token verified successfully, payload:`, payload);
return payload;
} catch (error) {
console.error("Token validation failed:", error);
} catch (error: any) {
console.error("Token validation failed:", error.message || error);
if (error.code) {
console.error(`Token validation error code: ${error.code}`);
}
if (error.response) {
console.error(`Token validation HTTP error: ${error.response.status} - ${error.response.statusText}`);
}
if (error.cause) {
console.error(`Token validation error cause:`, error.cause);
}
return null;
}
}
Expand Down
9 changes: 9 additions & 0 deletions infrastructure/evault-core/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@ import fastify, {
FastifyRequest,
FastifyReply,
} from "fastify";
import fastifyCors from "@fastify/cors";
import { renderVoyagerPage } from "graphql-voyager/middleware";
import { connectWithRetry } from "./core/db/retry-neo4j";
import neo4j, { Driver } from "neo4j-driver";
Expand DownExpand Up@@ -116,6 +117,14 @@ const initializeEVault = async (provisioningServiceInstance?: ProvisioningServic
logger: true,
});

// Register CORS plugin with relaxed settings
await fastifyServer.register(fastifyCors, {
origin: true, // Allow all origins
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization", "X-ENAME"],
credentials: true,
});

// Register HTTP routes with provisioning service if available
await registerHttpRoutes(fastifyServer, evaultInstance, provisioningServiceInstance, dbService);

Expand Down
5 changes: 5 additions & 0 deletions infrastructure/signature-validator/.gitignore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
node_modules/
dist/
*.log
.DS_Store

Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions infrastructure/eid-wallet/src/env.d.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,4 +5,5 @@ declare namespace App {}
declare module "$env/static/public" {
export const PUBLIC_REGISTRY_URL: string;
export const PUBLIC_PROVISIONER_URL: string;
export const PUBLIC_EID_WALLET_TOKEN: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

PUBLIC_EID_WALLET_TOKEN in $env/static/public will be exposed to the browser

Declaring PUBLIC_EID_WALLET_TOKEN in the $env/static/public module means its value is statically inlined into client bundles and is not secret. If this token is used for authenticating sensitive operations (e.g., calling eVault /public-key), exposing it to all client code (and, transitively, attackers running JS in the browser) significantly weakens your security model.

Please:

  • Confirm that this token is truly safe to be public, or
  • Move it to a private env module ($env/static/private/dynamic private) and adjust the flow so that only trusted server-side code uses it.

Based on prior learnings about avoiding PUBLIC_* envs for critical configuration (e.g., PUBLIC_PLATFORM_URL), this is worth a careful threat-model review rather than treating it as just another public constant.


🏁 Script executed:

#!/bin/bash# Examine env.d.ts file content to see token declaration context
cat infrastructure/eid-wallet/src/env.d.ts

Length of output: 251


🏁 Script executed:

#!/bin/bash# Search for how PUBLIC_EID_WALLET_TOKEN is used in the codebase
rg -n "PUBLIC_EID_WALLET_TOKEN" infrastructure/eid-wallet/

Length of output: 469


🏁 Script executed:

#!/bin/bash# Check for .env.example or .env template files
fd -t f "\\.env" infrastructure/eid-wallet/ | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash# Search for eVault API calls and /public-key references to understand token usage
rg -n "public-key|eVault|PUBLIC_EID_WALLET" infrastructure/eid-wallet/ -A 3 -B 3

Length of output: 47537


}
71 changes: 49 additions & 22 deletions infrastructure/eid-wallet/src/lib/global/controllers/evault.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
import { PUBLIC_REGISTRY_URL, PUBLIC_PROVISIONER_URL } from "$env/static/public";
import {
PUBLIC_REGISTRY_URL,
PUBLIC_PROVISIONER_URL,
PUBLIC_EID_WALLET_TOKEN,
} from "$env/static/public";
import type { Store } from "@tauri-apps/plugin-store";
import axios from "axios";
import { GraphQLClient } from "graphql-request";
Expand DownExpand Up@@ -52,7 +56,11 @@ export class VaultController {
#profileCreationStatus: "idle" | "loading" | "success" | "failed" = "idle";
#notificationService: NotificationService;

constructor(store: Store, userController: UserController, keyService?: KeyService) {
constructor(
store: Store,
userController: UserController,
keyService?: KeyService,
) {
this.#store = store;
this.#userController = userController;
this.#keyService = keyService || null;
Expand DownExpand Up@@ -81,17 +89,21 @@ export class VaultController {
* Sync public key to eVault core
* Checks if public key was already saved, calls /whois, and PATCH if needed
*/
private async syncPublicKey(eName: string): Promise<void> {
async syncPublicKey(eName: string): Promise<void> {
try {
// Check if we've already saved the public key
const savedKey = localStorage.getItem(`publicKeySaved_${eName}`);
if (savedKey === "true") {
console.log(`Public key already saved for ${eName}, skipping sync`);
console.log(
`Public key already saved for ${eName}, skipping sync`,
);
return;
}

if (!this.#keyService) {
console.warn("KeyService not available, cannot sync public key");
console.warn(
"KeyService not available, cannot sync public key",
);
return;
}

Expand All@@ -118,23 +130,42 @@ export class VaultController {
return;
}

// Get public key from KeyService
const publicKey = await this.#keyService.getPublicKey(eName, "signing");
// Get public key using the exact same logic as onboarding/verification flow
// KEY_ID is always "default", context depends on whether user is pre-verification
const KEY_ID = "default";

// Determine context: check if user is pre-verification (fake/demo user)
const isFake = await this.#userController.isFake;
const context = isFake ? "pre-verification" : "onboarding";

// Get public key using the same method as getApplicationPublicKey() in onboarding/verify
let publicKey: string | undefined;
try {
publicKey = await this.#keyService.getPublicKey(
KEY_ID,
context,
);
} catch (error) {
console.error(
`Failed to get public key for ${KEY_ID} with context ${context}:`,
error,
);
return;
}

if (!publicKey) {
console.warn(`No public key found for ${eName}, cannot sync`);
console.warn(
`No public key found for ${KEY_ID} with context ${context}, cannot sync`,
);
return;
}

// Get authentication token from registry
let authToken: string | null = null;
try {
const entropyResponse = await axios.get(
new URL("/entropy", PUBLIC_REGISTRY_URL).toString()
// Get authentication token from environment variable
const authToken = PUBLIC_EID_WALLET_TOKEN || null;
if (!authToken) {
console.warn(
"PUBLIC_EID_WALLET_TOKEN not set, request may fail authentication",
);
authToken = entropyResponse.data?.token || null;
} catch (error) {
console.error("Failed to get auth token from registry:", error);
// Continue without token - server will reject if auth is required
}

// Call PATCH /public-key to save the public key
Expand All@@ -148,11 +179,7 @@ export class VaultController {
headers["Authorization"] = `Bearer ${authToken}`;
}

await axios.patch(
patchUrl,
{ publicKey },
{ headers }
);
await axios.patch(patchUrl, { publicKey }, { headers });

// Mark as saved
localStorage.setItem(`publicKeySaved_${eName}`, "true");
Expand Down
6 changes: 5 additions & 1 deletion infrastructure/eid-wallet/src/lib/global/state.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,11 @@ export class GlobalState {
this.securityController = new SecurityController(store);
this.userController = new UserController(store);
this.keyService = keyService;
this.vaultController = new VaultController(store, this.userController, keyService);
this.vaultController = new VaultController(
store,
this.userController,
keyService,
);
this.notificationService = NotificationService.getInstance();
}

Expand Down
20 changes: 20 additions & 0 deletions infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,6 +98,16 @@ onMount(async () => {
}
// For other errors, continue to app - non-blocking
}

// Sync public key to eVault core
try {
await globalState.vaultController.syncPublicKey(
vault.ename,
);
} catch (error) {
console.error("Error syncing public key:", error);
// Continue to app even if sync fails - non-blocking
}
}
} catch (error) {
console.error("Error during eVault health check:", error);
Expand DownExpand Up@@ -170,6 +180,16 @@ onMount(async () => {
}
// For other errors, continue to app - non-blocking
}

// Sync public key to eVault core
try {
await globalState.vaultController.syncPublicKey(
vault.ename,
);
} catch (error) {
console.error("Error syncing public key:", error);
// Continue to app even if sync fails - non-blocking
}
}
} catch (error) {
console.error("Error during eVault health check:", error);
Expand Down
68 changes: 68 additions & 0 deletions infrastructure/evault-core/generate-test-token.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env node

/**
* Script to generate a test JWT token for evault-core authentication
*
* Usage:
* REGISTRY_ENTROPY_KEY_JWK='{"kty":"EC",...}' node generate-test-token.js
*
* Or set the environment variable in a .env file
*
* You can also use tsx to run it:
* REGISTRY_ENTROPY_KEY_JWK='...' npx tsx generate-test-token.js
*/

const { importJWK, SignJWT } = require("jose");
const dotenv = require("dotenv");
const path = require("path");

// Load .env file if it exists (try both root and current directory)
dotenv.config({ path: path.resolve(__dirname, "../../../.env") });
dotenv.config({ path: path.resolve(__dirname, ".env") });

async function generateTestToken() {
const jwkString = '{"kty":"EC","use":"sig","alg":"ES256","kid":"entropy-key-1","crv":"P-256","x":"POWUVJwOulAW0gheTVUHF4nXUenMCg0jxhGKI8M1LLU","y":"Cb4GC8Tt0gW7zMr-DhsDJisGVNgWttwjnyQl1HyU7hg","d":"FWqvWBzoiZcD0hh4JAMdJ7foxFRGqyV2Ei_eWvr1Si4"}';

if (!jwkString) {
console.error("Error: REGISTRY_ENTROPY_KEY_JWK environment variable is required");
console.error("\nUsage:");
console.error(" REGISTRY_ENTROPY_KEY_JWK='<your-jwk-json>' node generate-test-token.js");
console.error("\nOr set it in your .env file");
process.exit(1);
}

try {
const jwk = JSON.parse(jwkString);
const privateKey = await importJWK(jwk, "ES256");

// Generate token valid for 1 year
const token = await new SignJWT({
// Add any custom claims you want
platform: "eid-wallet",
purpose: "public-key-sync",
})
.setProtectedHeader({
alg: "ES256",
kid: jwk.kid || "entropy-key-1",
})
.setIssuedAt()
.setExpirationTime("1y") // Valid for 1 year
.sign(privateKey);

console.log("\n✅ Generated JWT Token (valid for 1 year):\n");
console.log(token);
console.log("\n📋 Use this token in the Authorization header:");
console.log(` Authorization: Bearer ${token}\n`);

return token;
} catch (error) {
console.error("Error generating token:", error.message);
if (error.message.includes("JSON")) {
console.error("\nMake sure REGISTRY_ENTROPY_KEY_JWK is valid JSON");
}
process.exit(1);
}
}

generateTestToken();

1 change: 1 addition & 0 deletions infrastructure/evault-core/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@
"migration:revert": "npm run typeorm migration:revert -- -d dist/config/database.js"
},
"dependencies": {
"@fastify/cors": "^8.5.0",
"@fastify/formbody": "^8.0.2",
"@fastify/swagger": "^8.14.0",
"@fastify/swagger-ui": "^3.0.0",
Expand Down
42 changes: 31 additions & 11 deletions infrastructure/evault-core/src/core/http/server.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -286,30 +286,50 @@ export async function registerHttpRoutes(
// Helper function to validate JWT token
async function validateToken(authHeader: string | null): Promise<any | null> {
if (!authHeader || !authHeader.startsWith("Bearer ")) {
console.error("Token validation: Missing or invalid Authorization header format");
return null;
}

const token = authHeader.substring(7); // Remove 'Bearer ' prefix

try {
if (!process.env.REGISTRY_URL) {
console.error("REGISTRY_URL is not set");
// Try REGISTRY_URL first, fallback to PUBLIC_REGISTRY_URL
const registryUrl = process.env.REGISTRY_URL || process.env.PUBLIC_REGISTRY_URL;
if (!registryUrl) {
console.error("Token validation: REGISTRY_URL or PUBLIC_REGISTRY_URL is not set");
return null;
}

const jwksResponse = await axios.get(
new URL(
`/.well-known/jwks.json`,
process.env.REGISTRY_URL
).toString()
);
const jwksUrl = new URL(`/.well-known/jwks.json`, registryUrl).toString();
console.log(`Token validation: Fetching JWKS from ${jwksUrl}`);

const jwksResponse = await axios.get(jwksUrl, {
timeout: 5000,
});

console.log(`Token validation: JWKS response keys count: ${jwksResponse.data?.keys?.length || 0}`);

const JWKS = jose.createLocalJWKSet(jwksResponse.data);

// Decode token header to see what kid it's using
const decodedHeader = jose.decodeProtectedHeader(token);
console.log(`Token validation: Token header - alg: ${decodedHeader.alg}, kid: ${decodedHeader.kid}`);

const { payload } = await jose.jwtVerify(token, JWKS);


console.log(`Token validation: Token verified successfully, payload:`, payload);
return payload;
} catch (error) {
console.error("Token validation failed:", error);
} catch (error: any) {
console.error("Token validation failed:", error.message || error);
if (error.code) {
console.error(`Token validation error code: ${error.code}`);
}
if (error.response) {
console.error(`Token validation HTTP error: ${error.response.status} - ${error.response.statusText}`);
}
if (error.cause) {
console.error(`Token validation error cause:`, error.cause);
}
return null;
}
}
Expand Down
9 changes: 9 additions & 0 deletions infrastructure/evault-core/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,7 @@ import fastify, {
FastifyRequest,
FastifyReply,
} from "fastify";
import fastifyCors from "@fastify/cors";
import { renderVoyagerPage } from "graphql-voyager/middleware";
import { connectWithRetry } from "./core/db/retry-neo4j";
import neo4j, { Driver } from "neo4j-driver";
Expand DownExpand Up@@ -116,6 +117,14 @@ const initializeEVault = async (provisioningServiceInstance?: ProvisioningServic
logger: true,
});

// Register CORS plugin with relaxed settings
await fastifyServer.register(fastifyCors, {
origin: true, // Allow all origins
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization", "X-ENAME"],
credentials: true,
});

// Register HTTP routes with provisioning service if available
await registerHttpRoutes(fastifyServer, evaultInstance, provisioningServiceInstance, dbService);

Expand Down
5 changes: 5 additions & 0 deletions infrastructure/signature-validator/.gitignore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
node_modules/
dist/
*.log
.DS_Store

Loading
Loading