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 number Diff line number Diff 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
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 number Diff line number Diff 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 Down Expand 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 Down Expand 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 number Diff line number Diff 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 number Diff line number Diff 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 Down Expand 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 number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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 Down Expand 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 number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
dist/
*.log
.DS_Store

Loading
Loading