diff --git a/infrastructure/eid-wallet/src/env.d.ts b/infrastructure/eid-wallet/src/env.d.ts index 103b229d..af2f5ae3 100644 --- a/infrastructure/eid-wallet/src/env.d.ts +++ b/infrastructure/eid-wallet/src/env.d.ts @@ -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; } diff --git a/infrastructure/eid-wallet/src/lib/global/controllers/evault.ts b/infrastructure/eid-wallet/src/lib/global/controllers/evault.ts index 8df56e1f..917f6ab4 100644 --- a/infrastructure/eid-wallet/src/lib/global/controllers/evault.ts +++ b/infrastructure/eid-wallet/src/lib/global/controllers/evault.ts @@ -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"; @@ -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; @@ -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 { + async syncPublicKey(eName: string): Promise { 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; } @@ -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 @@ -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"); diff --git a/infrastructure/eid-wallet/src/lib/global/state.ts b/infrastructure/eid-wallet/src/lib/global/state.ts index 5a1fc67d..cbe10731 100644 --- a/infrastructure/eid-wallet/src/lib/global/state.ts +++ b/infrastructure/eid-wallet/src/lib/global/state.ts @@ -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(); } diff --git a/infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte b/infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte index 6bb33e99..40b09162 100644 --- a/infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte +++ b/infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte @@ -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); @@ -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); diff --git a/infrastructure/evault-core/generate-test-token.js b/infrastructure/evault-core/generate-test-token.js new file mode 100644 index 00000000..3d9e1254 --- /dev/null +++ b/infrastructure/evault-core/generate-test-token.js @@ -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='' 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(); + diff --git a/infrastructure/evault-core/package.json b/infrastructure/evault-core/package.json index 94da22be..6342e050 100644 --- a/infrastructure/evault-core/package.json +++ b/infrastructure/evault-core/package.json @@ -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", diff --git a/infrastructure/evault-core/src/core/http/server.ts b/infrastructure/evault-core/src/core/http/server.ts index c45bd7b3..898901de 100644 --- a/infrastructure/evault-core/src/core/http/server.ts +++ b/infrastructure/evault-core/src/core/http/server.ts @@ -286,30 +286,50 @@ export async function registerHttpRoutes( // Helper function to validate JWT token async function validateToken(authHeader: string | null): Promise { 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; } } diff --git a/infrastructure/evault-core/src/index.ts b/infrastructure/evault-core/src/index.ts index 259d388f..96d8313f 100644 --- a/infrastructure/evault-core/src/index.ts +++ b/infrastructure/evault-core/src/index.ts @@ -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"; @@ -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); diff --git a/infrastructure/signature-validator/.gitignore b/infrastructure/signature-validator/.gitignore new file mode 100644 index 00000000..d451c1bb --- /dev/null +++ b/infrastructure/signature-validator/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +*.log +.DS_Store + diff --git a/infrastructure/signature-validator/README.md b/infrastructure/signature-validator/README.md new file mode 100644 index 00000000..d66f7d6d --- /dev/null +++ b/infrastructure/signature-validator/README.md @@ -0,0 +1,67 @@ +# Signature Validator + +A TypeScript library for verifying signatures using public keys retrieved from eVault. + +## Installation + +```bash +npm install +npm run build +``` + +## Usage + +```typescript +import { verifySignature } from "signature-validator"; + +const result = await verifySignature({ + eName: "@user.w3id", + signature: "z...", // multibase encoded signature + payload: "message to verify", + registryBaseUrl: "https://registry.example.com" +}); + +if (result.valid) { + console.log("Signature is valid!"); +} else { + console.error("Signature invalid:", result.error); +} +``` + +## API + +### `verifySignature(options: VerifySignatureOptions): Promise` + +Verifies a signature by: +1. Resolving the eVault URL from the registry using the eName +2. Fetching the public key from the eVault `/whois` endpoint +3. Decoding the multibase-encoded public key +4. Verifying the signature using Web Crypto API + +#### Parameters + +- `eName`: The eName (W3ID) of the user +- `signature`: The signature to verify (multibase encoded string, supports 'z' prefix for base58btc or base64) +- `payload`: The payload that was signed (string) +- `registryBaseUrl`: Base URL of the registry service + +#### Returns + +- `valid`: Boolean indicating if the signature is valid +- `error`: Error message if verification failed +- `publicKey`: The public key that was used for verification + +## Public Key Format + +Public keys are expected to be in multibase format starting with 'z': +- `z` prefix indicates multibase encoding +- Supports base58btc (standard) or hex encoding + +Example: `z3059301306072a8648ce3d020106082a8648ce3d03010703420004a16b063e785d25945c44ae2e7a4cbd94c3316533427261244f696609d6afb848155b9016ad8d5c9ec59053b3b2cf2511af0c2414fc53d2abf96323bb1a031902` + +## Signature Format + +Signatures can be: +- Multibase encoded (starting with 'z' for base58btc) +- Base64 encoded (for software keys) + diff --git a/infrastructure/signature-validator/package.json b/infrastructure/signature-validator/package.json new file mode 100644 index 00000000..e9e0ca63 --- /dev/null +++ b/infrastructure/signature-validator/package.json @@ -0,0 +1,21 @@ +{ + "name": "signature-validator", + "version": "1.0.0", + "description": "Library for verifying signatures using public keys from eVault", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "test": "vitest", + "dev": "tsc --watch" + }, + "dependencies": { + "axios": "^1.6.7", + "multiformats": "^13.3.2" + }, + "devDependencies": { + "@types/node": "^20.11.24", + "typescript": "^5.3.3", + "vitest": "^1.6.1" + } +} \ No newline at end of file diff --git a/infrastructure/signature-validator/src/index.ts b/infrastructure/signature-validator/src/index.ts new file mode 100644 index 00000000..5690b0eb --- /dev/null +++ b/infrastructure/signature-validator/src/index.ts @@ -0,0 +1,242 @@ +import axios from "axios"; +import { base58btc } from "multiformats/bases/base58"; + +/** + * Options for signature verification + */ +export interface VerifySignatureOptions { + /** The eName (W3ID) of the user */ + eName: string; + /** The signature to verify (multibase encoded string) */ + signature: string; + /** The payload that was signed (string) */ + payload: string; + /** Base URL of the registry service */ + registryBaseUrl: string; +} + +/** + * Result of signature verification + */ +export interface VerifySignatureResult { + /** Whether the signature is valid */ + valid: boolean; + /** Error message if verification failed */ + error?: string; + /** The public key that was used for verification */ + publicKey?: string; +} + +/** + * Decodes a multibase-encoded public key + * Supports 'z' prefix for base58btc or hex encoding + * Based on the format used in SoftwareKeyManager: 'z' + hex + */ +function decodeMultibasePublicKey(multibaseKey: string): Uint8Array { + if (!multibaseKey.startsWith("z")) { + throw new Error("Public key must start with 'z' multibase prefix"); + } + + const encoded = multibaseKey.slice(1); // Remove 'z' prefix + + // Try hex first (as used in SoftwareKeyManager: 'z' + hex) + // Check if it looks like hex (only contains 0-9, a-f, A-F) + if (/^[0-9a-fA-F]+$/.test(encoded)) { + try { + if (encoded.length % 2 !== 0) { + throw new Error("Hex string must have even length"); + } + const bytes = new Uint8Array(encoded.length / 2); + for (let i = 0; i < encoded.length; i += 2) { + bytes[i / 2] = Number.parseInt(encoded.slice(i, i + 2), 16); + } + return bytes; + } catch (hexError) { + // Fall through to try base58btc + } + } + + // Try base58btc (standard multibase 'z' prefix) + try { + return base58btc.decode(encoded); + } catch (error) { + throw new Error( + `Failed to decode multibase public key. Tried hex and base58btc. Error: ${error instanceof Error ? error.message : String(error)}` + ); + } +} + +/** + * Decodes a multibase-encoded signature + * Supports base58btc or base64 + */ +function decodeSignature(signature: string): Uint8Array { + // If it starts with 'z', it's multibase base58btc + if (signature.startsWith("z")) { + try { + return base58btc.decode(signature.slice(1)); + } catch (error) { + throw new Error(`Failed to decode multibase signature: ${error instanceof Error ? error.message : String(error)}`); + } + } + + // Otherwise, try base64 (software keys return base64) + try { + const binaryString = atob(signature); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes; + } catch (error) { + throw new Error(`Failed to decode signature as base64: ${error instanceof Error ? error.message : String(error)}`); + } +} + +/** + * Retrieves the public key for a given eName + * @param eName - The eName (W3ID) of the user + * @param registryBaseUrl - Base URL of the registry service + * @returns The public key in multibase format + */ +async function getPublicKey(eName: string, registryBaseUrl: string): Promise { + // Step 1: Resolve eVault URL from registry + const resolveUrl = new URL(`/resolve?w3id=${encodeURIComponent(eName)}`, registryBaseUrl).toString(); + const resolveResponse = await axios.get(resolveUrl, { + timeout: 10000, + }); + + if (!resolveResponse.data?.uri) { + throw new Error(`Failed to resolve eVault URL for eName: ${eName}`); + } + + const evaultUrl = resolveResponse.data.uri; + + // Step 2: Get public key from eVault /whois endpoint + const whoisUrl = new URL("/whois", evaultUrl).toString(); + const whoisResponse = await axios.get(whoisUrl, { + headers: { + "X-ENAME": eName, + }, + timeout: 10000, + }); + + const publicKey = whoisResponse.data?.publicKey; + if (!publicKey) { + throw new Error(`No public key found for eName: ${eName}`); + } + + return publicKey; +} + +/** + * Verifies a signature using a public key from eVault + * + * @param options - Verification options + * @returns Promise resolving to verification result + * + * @example + * ```ts + * const result = await verifySignature({ + * eName: "@user.w3id", + * signature: "z...", + * payload: "message to verify", + * registryBaseUrl: "https://registry.example.com" + * }); + * + * if (result.valid) { + * console.log("Signature is valid!"); + * } else { + * console.error("Signature invalid:", result.error); + * } + * ``` + */ +export async function verifySignature( + options: VerifySignatureOptions +): Promise { + try { + const { eName, signature, payload, registryBaseUrl } = options; + + if (!eName) { + return { + valid: false, + error: "eName is required", + }; + } + + if (!signature) { + return { + valid: false, + error: "signature is required", + }; + } + + if (!payload) { + return { + valid: false, + error: "payload is required", + }; + } + + if (!registryBaseUrl) { + return { + valid: false, + error: "registryBaseUrl is required", + }; + } + + // Get public key from eVault + const publicKeyMultibase = await getPublicKey(eName, registryBaseUrl); + + // Decode the public key + const publicKeyBytes = decodeMultibasePublicKey(publicKeyMultibase); + + // Import the public key for Web Crypto API + // The public key is in SPKI format (SubjectPublicKeyInfo) + // Create a new ArrayBuffer from the Uint8Array + const publicKeyBuffer = new Uint8Array(publicKeyBytes).buffer; + + const publicKey = await crypto.subtle.importKey( + "spki", + publicKeyBuffer, + { + name: "ECDSA", + namedCurve: "P-256", + }, + false, + ["verify"] + ); + + // Decode the signature + const signatureBytes = decodeSignature(signature); + + // Convert payload to ArrayBuffer + const payloadBuffer = new TextEncoder().encode(payload); + + // Create a new ArrayBuffer from the signature Uint8Array + const signatureBuffer = new Uint8Array(signatureBytes).buffer; + + // Verify the signature + const isValid = await crypto.subtle.verify( + { + name: "ECDSA", + hash: "SHA-256", + }, + publicKey, + signatureBuffer, + payloadBuffer + ); + + return { + valid: isValid, + publicKey: publicKeyMultibase, + error: isValid ? undefined : "Signature verification failed", + }; + } catch (error) { + return { + valid: false, + error: error instanceof Error ? error.message : String(error), + }; + } +} + diff --git a/infrastructure/signature-validator/tsconfig.json b/infrastructure/signature-validator/tsconfig.json new file mode 100644 index 00000000..23b61758 --- /dev/null +++ b/infrastructure/signature-validator/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "dist", + "rootDir": "src", + "sourceMap": true, + "declaration": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} + diff --git a/platforms/blabsy/src/components/common/seo.tsx b/platforms/blabsy/src/components/common/seo.tsx index 71f3de5f..5e0ea509 100644 --- a/platforms/blabsy/src/components/common/seo.tsx +++ b/platforms/blabsy/src/components/common/seo.tsx @@ -8,11 +8,7 @@ type SEOProps = { description?: string; }; -export function SEO({ - title, - image, - description -}: SEOProps): JSX.Element { +export function SEO({ title, image, description }: SEOProps): JSX.Element { const { asPath } = useRouter(); return ( diff --git a/platforms/blabsy/src/lib/context/theme-context.tsx b/platforms/blabsy/src/lib/context/theme-context.tsx index 12e9885c..1e53926b 100644 --- a/platforms/blabsy/src/lib/context/theme-context.tsx +++ b/platforms/blabsy/src/lib/context/theme-context.tsx @@ -57,7 +57,7 @@ export function ThemeContextProvider({ const root = document.documentElement; // Always use dark theme const forcedTheme: Theme = 'dark'; - + // Always ensure dark class is present and never remove it root.classList.add('dark'); // Prevent any accidental removal @@ -95,7 +95,7 @@ export function ThemeContextProvider({ // Ensure dark class is always applied on mount and updates const root = document.documentElement; root.classList.add('dark'); - + return () => clearTimeout(timeoutId); }, [userId]); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eace876c..3015fe36 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -365,6 +365,9 @@ importers: infrastructure/evault-core: dependencies: + '@fastify/cors': + specifier: ^8.5.0 + version: 8.5.0 '@fastify/formbody': specifier: ^8.0.2 version: 8.0.2 @@ -472,6 +475,25 @@ importers: specifier: ^1.6.1 version: 1.6.1(@types/node@20.16.11)(jsdom@19.0.0(bufferutil@4.0.9))(lightningcss@1.30.2)(sass@1.94.1) + infrastructure/signature-validator: + dependencies: + axios: + specifier: ^1.6.7 + version: 1.13.2 + multiformats: + specifier: ^13.3.2 + version: 13.4.1 + devDependencies: + '@types/node': + specifier: ^20.11.24 + version: 20.16.11 + typescript: + specifier: ^5.3.3 + version: 5.8.2 + vitest: + specifier: ^1.6.1 + version: 1.6.1(@types/node@20.16.11)(jsdom@19.0.0(bufferutil@4.0.9))(lightningcss@1.30.2)(sass@1.94.1) + infrastructure/w3id: dependencies: canonicalize: