Skip to content

Commit 63f8fee

Browse files
authored
Feat/emover fixes (#765)
* fix: return evault id on core whois endpoint * fix: not found case * fix: styling
1 parent 570a770 commit 63f8fee

10 files changed

Lines changed: 245 additions & 83 deletions

File tree

docs/docs/Infrastructure/eVault-Key-Delegation.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,15 @@ X-ENAME: @user.w3id
141141
```json
142142
{
143143
"w3id": "@user.w3id",
144+
"evaultId": "evault-identifier",
144145
"keyBindingCertificates": [
145146
"eyJhbGciOiJFUzI1NiIsImtpZCI6ImVudHJvcHkta2V5LTEifQ.eyJlbmFtZSI6IkB1c2VyLnczaWQiLCJwdWJsaWNLZXkiOiJ6MzA1OTMwMTMwNjA3MmE4NjQ4Y2UzZDAyMDEwNjA4MmE4NjQ4Y2UzZDAzMDEwNzAzNDIwMDA0Li4uIn0..."
146147
]
147148
}
148149
```
149150

151+
`evaultId` is the eVault instance identifier (when configured); it matches the `evault` value registered with the Registry.
152+
150153
## Code Examples
151154

152155
### Setting Public Key During eVault Creation

docs/docs/Infrastructure/eVault.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,20 +275,26 @@ X-ENAME: @user-a.w3id
275275
**Response**:
276276
```json
277277
{
278+
"w3id": "@user-a.w3id",
279+
"evaultId": "@evault-identifier",
278280
"keyBindingCertificates": [
279281
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...",
280282
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9..."
281283
]
282284
}
283285
```
284286

287+
- `w3id`: The W3ID (eName) from the request.
288+
- `evaultId`: The eVault instance identifier (when configured via `EVAULT_ID`). Matches the `evault` value registered with the Registry.
289+
- `keyBindingCertificates`: JWTs binding the eName to public keys.
290+
285291
**Example**:
286292
```bash
287293
curl -X GET http://localhost:4000/whois \
288294
-H "X-ENAME: @user-a.w3id"
289295
```
290296

291-
**Use Case**: Platforms use this endpoint to retrieve public keys for signature verification.
297+
**Use Case**: Platforms use this endpoint to retrieve public keys for signature verification and the eVault instance id for routing or auditing.
292298

293299
### /logs
294300

infrastructure/evault-core/src/core/http/server.ts

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export async function registerHttpRoutes(
7373
type: "object",
7474
properties: {
7575
w3id: { type: "string" },
76+
evaultId: { type: ["string", "null"] },
7677
keyBindingCertificates: {
7778
type: "array",
7879
items: { type: "string" },
@@ -114,15 +115,19 @@ export async function registerHttpRoutes(
114115

115116
// Generate key binding certificates for each public key
116117
const keyBindingCertificates: string[] = [];
117-
const registryUrl = process.env.PUBLIC_REGISTRY_URL || process.env.REGISTRY_URL;
118+
const registryUrl =
119+
process.env.PUBLIC_REGISTRY_URL || process.env.REGISTRY_URL;
118120
const sharedSecret = process.env.REGISTRY_SHARED_SECRET;
119121

120122
if (registryUrl && sharedSecret && publicKeys.length > 0) {
121123
try {
122124
for (const publicKey of publicKeys) {
123125
try {
124126
const response = await axios.post(
125-
new URL("/key-binding-certificate", registryUrl).toString(),
127+
new URL(
128+
"/key-binding-certificate",
129+
registryUrl,
130+
).toString(),
126131
{
127132
ename: eName,
128133
publicKey: publicKey,
@@ -132,10 +137,12 @@ export async function registerHttpRoutes(
132137
Authorization: `Bearer ${sharedSecret}`,
133138
},
134139
timeout: 10000,
135-
}
140+
},
136141
);
137142
if (response.data?.token) {
138-
keyBindingCertificates.push(response.data.token);
143+
keyBindingCertificates.push(
144+
response.data.token,
145+
);
139146
}
140147
} catch (error) {
141148
console.error(
@@ -154,8 +161,44 @@ export async function registerHttpRoutes(
154161
}
155162
}
156163

164+
// Resolve eName via Registry (same logic as /resolve) to get evault id
165+
let evaultId: string | null = null;
166+
const registryUrlForResolve =
167+
process.env.PUBLIC_REGISTRY_URL || process.env.REGISTRY_URL;
168+
if (registryUrlForResolve) {
169+
try {
170+
const resolveResponse = await axios.get<{
171+
ename: string;
172+
uri: string;
173+
evault: string;
174+
originalUri?: string;
175+
resolved?: boolean;
176+
}>(
177+
new URL(
178+
`/resolve?w3id=${encodeURIComponent(eName)}`,
179+
registryUrlForResolve,
180+
).toString(),
181+
{ timeout: 10000 },
182+
);
183+
if (resolveResponse.data?.evault) {
184+
evaultId = resolveResponse.data.evault;
185+
}
186+
} catch (error) {
187+
// 404 or network error: evault not registered for this eName, or registry unavailable
188+
if (
189+
axios.isAxiosError(error) &&
190+
error.response?.status !== 404
191+
) {
192+
console.error(
193+
"Error resolving eName via Registry for whois evaultId:",
194+
error.message,
195+
);
196+
}
197+
}
198+
}
157199
const result = {
158200
w3id: eName,
201+
evaultId,
159202
keyBindingCertificates: keyBindingCertificates,
160203
};
161204
return result;
@@ -182,8 +225,14 @@ export async function registerHttpRoutes(
182225
querystring: {
183226
type: "object",
184227
properties: {
185-
limit: { type: "string", description: "Page size (default 20, max 100)" },
186-
cursor: { type: "string", description: "Opaque cursor for next page" },
228+
limit: {
229+
type: "string",
230+
description: "Page size (default 20, max 100)",
231+
},
232+
cursor: {
233+
type: "string",
234+
description: "Opaque cursor for next page",
235+
},
187236
},
188237
},
189238
response: {
@@ -243,14 +292,17 @@ export async function registerHttpRoutes(
243292

244293
try {
245294
const limit = Math.min(
246-
Math.max(1, parseInt(request.query.limit || "20", 10) || 20),
295+
Math.max(
296+
1,
297+
Number.parseInt(request.query.limit || "20", 10) || 20,
298+
),
247299
100,
248300
);
249301
const cursor = request.query.cursor ?? null;
250-
const result = await dbService.getEnvelopeOperationLogs(
251-
eName,
252-
{ limit, cursor },
253-
);
302+
const result = await dbService.getEnvelopeOperationLogs(eName, {
303+
limit,
304+
cursor,
305+
});
254306
return {
255307
logs: result.logs,
256308
nextCursor: result.nextCursor,

infrastructure/evault-core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ const initializeEVault = async (provisioningServiceInstance?: ProvisioningServic
133133
const evaultInstance = {
134134
publicKey,
135135
w3id,
136+
evaultId: process.env.EVAULT_ID || undefined,
136137
};
137138

138139
graphqlServer = new GraphQLServer(dbService, publicKey, w3id, evaultInstance);

platforms/emover/src/app/(app)/layout.tsx

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@
22
import { useAuth } from "@/lib/auth-context";
33
import { useRouter } from "next/navigation";
44
import { useEffect } from "react";
5+
import { ArrowRightLeft } from "lucide-react";
56

67
export default function AppLayout({
78
children,
89
}: {
910
children: React.ReactNode;
1011
}) {
11-
const { isAuthenticated, isLoading } = useAuth();
12+
const { isAuthenticated, isLoading, logout } = useAuth();
1213
const router = useRouter();
1314

15+
function handleLogout() {
16+
logout();
17+
router.push("/login");
18+
}
19+
1420
useEffect(() => {
1521
if (!isLoading && !isAuthenticated) {
1622
router.push("/login");
@@ -19,8 +25,11 @@ export default function AppLayout({
1925

2026
if (isLoading) {
2127
return (
22-
<div className="flex items-center justify-center min-h-screen">
23-
<div>Loading...</div>
28+
<div className="flex items-center justify-center min-h-screen bg-gray-50">
29+
<div className="text-center">
30+
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mb-4" />
31+
<p className="text-gray-600">Loading...</p>
32+
</div>
2433
</div>
2534
);
2635
}
@@ -29,6 +38,31 @@ export default function AppLayout({
2938
return null;
3039
}
3140

32-
return <>{children}</>;
41+
return (
42+
<div className="min-h-screen bg-gray-50">
43+
<header className="sticky top-0 z-10 bg-white shadow-sm border-b border-gray-200">
44+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
45+
<div className="flex items-center justify-between">
46+
<div className="flex items-center gap-3">
47+
<div className="w-10 h-10 bg-blue-600 rounded-xl flex items-center justify-center shadow-md transform rotate-12">
48+
<ArrowRightLeft className="w-6 h-6 text-white transform -rotate-12" />
49+
</div>
50+
<h1 className="text-2xl font-bold text-gray-900">
51+
Emover
52+
</h1>
53+
</div>
54+
<button
55+
type="button"
56+
onClick={handleLogout}
57+
className="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors text-sm font-medium text-gray-700"
58+
>
59+
Logout
60+
</button>
61+
</div>
62+
</div>
63+
</header>
64+
{children}
65+
</div>
66+
);
3367
}
3468

platforms/emover/src/app/(app)/migrate/page.tsx

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ function MigrateContent() {
127127
const interval = setInterval(pollStatus, 2000);
128128

129129
return () => clearInterval(interval);
130-
}, [migrationId, router]);
130+
}, [migrationId]);
131131

132132
// Listen for signing confirmation via SSE
133133
useEffect(() => {
@@ -160,8 +160,8 @@ function MigrateContent() {
160160

161161
if (error && migrationStatus !== "failed") {
162162
return (
163-
<div className="container mx-auto px-4 py-8">
164-
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
163+
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
164+
<div className="rounded-lg border border-red-400 bg-red-100 p-4 text-red-700">
165165
{error}
166166
</div>
167167
</div>
@@ -176,17 +176,19 @@ function MigrateContent() {
176176
};
177177

178178
return (
179-
<div className="container mx-auto px-4 py-8 max-w-4xl">
180-
<h1 className="text-3xl font-bold mb-8">Migration in Progress</h1>
179+
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
180+
<h1 className="text-2xl sm:text-3xl font-bold text-gray-900 mb-8">
181+
Migration in Progress
182+
</h1>
181183

182184
{qrData && !isSigned && (
183-
<div className="bg-white p-6 rounded-lg shadow mb-6">
184-
<h2 className="text-xl font-semibold mb-4">
185+
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4 sm:p-6 mb-6">
186+
<h2 className="text-xl font-semibold text-gray-900 mb-4">
185187
Scan QR Code to Confirm Migration
186188
</h2>
187189
<p className="text-sm text-gray-600 mb-4">
188-
Please scan this QR code with your eID Wallet app to confirm
189-
the migration.
190+
Please scan this QR code with your eID Wallet app to
191+
confirm the migration.
190192
</p>
191193
<div className="flex justify-center">
192194
<QRCodeSVG value={qrData} size={256} />
@@ -196,7 +198,7 @@ function MigrateContent() {
196198

197199
{(migrationStatus || isActivated) && (
198200
<div
199-
className={`p-6 rounded-lg shadow mb-6 ${getStatusColor(
201+
className={`p-4 sm:p-6 rounded-lg shadow-sm border border-gray-200 mb-6 ${getStatusColor(
200202
isActivated ? "completed" : migrationStatus,
201203
)}`}
202204
>
@@ -213,6 +215,7 @@ function MigrateContent() {
213215
activated!
214216
</p>
215217
<button
218+
type="button"
216219
onClick={() => router.push("/")}
217220
className="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-6 rounded-lg transition-colors"
218221
>
@@ -233,11 +236,16 @@ function MigrateContent() {
233236
)}
234237

235238
{logs.length > 0 && (
236-
<div className="bg-white p-6 rounded-lg shadow">
237-
<h2 className="text-xl font-semibold mb-4">Migration Logs</h2>
238-
<div className="space-y-1 font-mono text-sm max-h-96 overflow-y-auto bg-gray-50 p-4 rounded">
239+
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4 sm:p-6">
240+
<h2 className="text-xl font-semibold text-gray-900 mb-4">
241+
Migration Logs
242+
</h2>
243+
<div className="space-y-1 font-mono text-sm max-h-96 overflow-y-auto bg-gray-50 p-4 rounded-lg">
239244
{logs.map((log, index) => (
240-
<div key={index} className="text-gray-700">
245+
<div
246+
key={`${log.slice(0, 50)}-${index}`}
247+
className="text-gray-700"
248+
>
241249
{log}
242250
</div>
243251
))}
@@ -250,7 +258,16 @@ function MigrateContent() {
250258

251259
export default function MigratePage() {
252260
return (
253-
<Suspense fallback={<div>Loading...</div>}>
261+
<Suspense
262+
fallback={
263+
<div className="flex items-center justify-center min-h-[50vh] bg-gray-50">
264+
<div className="text-center">
265+
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mb-4" />
266+
<p className="text-gray-600">Loading...</p>
267+
</div>
268+
</div>
269+
}
270+
>
254271
<MigrateContent />
255272
</Suspense>
256273
);

0 commit comments

Comments
 (0)