@eudi-verify/server 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,11 +15,11 @@ import {
15
15
  createVerifierHandlers,
16
16
  OpenEudiEngine,
17
17
  MemoryKVStore,
18
- } from '@eudi-verify/server';
18
+ } from "@eudi-verify/server";
19
19
 
20
20
  // 1. Create engine and store
21
- const BASE_URL = process.env.BASE_URL || 'http://localhost:3000/api/eudi';
22
- const engine = new OpenEudiEngine({ mode: 'demo', baseUrl: BASE_URL });
21
+ const BASE_URL = process.env.BASE_URL || "http://localhost:3000/api/eudi";
22
+ const engine = new OpenEudiEngine({ mode: "demo", baseUrl: BASE_URL });
23
23
  const store = new MemoryKVStore();
24
24
 
25
25
  // 2. Create handlers
@@ -27,7 +27,7 @@ const handlers = createVerifierHandlers({
27
27
  engine,
28
28
  store,
29
29
  baseUrl: BASE_URL,
30
- mode: 'demo',
30
+ mode: "demo",
31
31
  tokenSecret: process.env.TOKEN_SECRET!, // 32+ chars
32
32
  });
33
33
 
@@ -40,11 +40,11 @@ const handlers = createVerifierHandlers({
40
40
  ### Node.js HTTP
41
41
 
42
42
  ```ts
43
- import http from 'node:http';
43
+ import http from "node:http";
44
44
 
45
45
  function buildContext(req, params = {}, body = undefined) {
46
46
  return {
47
- ip: req.socket.remoteAddress ?? '127.0.0.1',
47
+ ip: req.socket.remoteAddress ?? "127.0.0.1",
48
48
  origin: req.headers.origin,
49
49
  params,
50
50
  body,
@@ -53,11 +53,13 @@ function buildContext(req, params = {}, body = undefined) {
53
53
 
54
54
  const server = http.createServer(async (req, res) => {
55
55
  const url = new URL(req.url!, `http://${req.headers.host}`);
56
-
56
+
57
57
  // Route to handlers
58
- if (url.pathname === '/sessions' && req.method === 'POST') {
58
+ if (url.pathname === "/sessions" && req.method === "POST") {
59
59
  const body = await readBody(req);
60
- const result = await handlers.createSession(buildContext(req, {}, JSON.parse(body)));
60
+ const result = await handlers.createSession(
61
+ buildContext(req, {}, JSON.parse(body)),
62
+ );
61
63
  sendJson(res, result.status, result.body, result.headers);
62
64
  }
63
65
  // ... other routes
@@ -67,36 +69,40 @@ const server = http.createServer(async (req, res) => {
67
69
  ### Express
68
70
 
69
71
  ```ts
70
- import express from 'express';
72
+ import express from "express";
71
73
 
72
74
  const app = express();
73
75
  app.use(express.json());
74
76
 
75
77
  function buildContext(req, params = {}, body = undefined) {
76
78
  return {
77
- ip: req.ip ?? '127.0.0.1',
79
+ ip: req.ip ?? "127.0.0.1",
78
80
  origin: req.headers.origin,
79
81
  params,
80
82
  body,
81
83
  };
82
84
  }
83
85
 
84
- app.post('/sessions', async (req, res) => {
86
+ app.post("/sessions", async (req, res) => {
85
87
  const result = await handlers.createSession(buildContext(req, {}, req.body));
86
88
  res.status(result.status).set(result.headers).json(result.body);
87
89
  });
88
90
 
89
- app.get('/sessions/:id', async (req, res) => {
90
- const result = await handlers.getSession(buildContext(req, { sessionId: req.params.id }));
91
+ app.get("/sessions/:id", async (req, res) => {
92
+ const result = await handlers.getSession(
93
+ buildContext(req, { sessionId: req.params.id }),
94
+ );
91
95
  res.status(result.status).set(result.headers).json(result.body);
92
96
  });
93
97
 
94
- app.post('/sessions/:id/cancel', async (req, res) => {
95
- const result = await handlers.cancelSession(buildContext(req, { sessionId: req.params.id }));
98
+ app.post("/sessions/:id/cancel", async (req, res) => {
99
+ const result = await handlers.cancelSession(
100
+ buildContext(req, { sessionId: req.params.id }),
101
+ );
96
102
  res.status(result.status).set(result.headers).json(result.body);
97
103
  });
98
104
 
99
- app.post('/tokens/verify', async (req, res) => {
105
+ app.post("/tokens/verify", async (req, res) => {
100
106
  const result = await handlers.verifyToken(buildContext(req, {}, req.body));
101
107
  res.status(result.status).json(result.body);
102
108
  });
@@ -105,21 +111,23 @@ app.post('/tokens/verify', async (req, res) => {
105
111
  ### Hono
106
112
 
107
113
  ```ts
108
- import { Hono } from 'hono';
114
+ import { Hono } from "hono";
109
115
 
110
116
  const app = new Hono();
111
117
 
112
118
  function buildContext(c, params = {}, body = undefined) {
113
119
  return {
114
- ip: c.req.header('x-forwarded-for') ?? '127.0.0.1',
115
- origin: c.req.header('origin'),
120
+ ip: c.req.header("x-forwarded-for") ?? "127.0.0.1",
121
+ origin: c.req.header("origin"),
116
122
  params,
117
123
  body,
118
124
  };
119
125
  }
120
126
 
121
- app.post('/sessions', async (c) => {
122
- const result = await handlers.createSession(buildContext(c, {}, await c.req.json()));
127
+ app.post("/sessions", async (c) => {
128
+ const result = await handlers.createSession(
129
+ buildContext(c, {}, await c.req.json()),
130
+ );
123
131
  return c.json(result.body, result.status, result.headers);
124
132
  });
125
133
 
@@ -130,30 +138,30 @@ app.post('/sessions', async (c) => {
130
138
 
131
139
  ```ts
132
140
  interface VerifierConfig {
133
- engine: VerifierEngine; // OpenEudiEngine or MockEngine
134
- store: IKVStore; // MemoryKVStore (or Redis for production)
135
- baseUrl: string; // Public callback URL (e.g., https://example.com/api/eudi)
136
- mode: 'demo' | 'production';
137
- tokenSecret: string; // HMAC secret, 32+ characters
138
- tokenTtlMs?: number; // Default: 300000 (5 min)
139
- sessionTtlMs?: number; // Default: 300000 (5 min)
141
+ engine: VerifierEngine; // OpenEudiEngine or MockEngine
142
+ store: IKVStore; // MemoryKVStore (or Redis for production)
143
+ baseUrl: string; // Public callback URL (e.g., https://example.com/api/eudi)
144
+ mode: "demo" | "production";
145
+ tokenSecret: string; // HMAC secret, 32+ characters
146
+ tokenTtlMs?: number; // Default: 300000 (5 min)
147
+ sessionTtlMs?: number; // Default: 300000 (5 min)
140
148
  rateLimit?: {
141
- maxRequests: number; // Default: 10
142
- windowMs: number; // Default: 60000 (1 min)
149
+ maxRequests: number; // Default: 10
150
+ windowMs: number; // Default: 60000 (1 min)
143
151
  };
144
- allowedOrigins?: string[]; // CORS/Origin check (empty = allow all)
152
+ allowedOrigins?: string[]; // CORS/Origin check (empty = allow all)
145
153
  }
146
154
  ```
147
155
 
148
156
  ## Handlers
149
157
 
150
- | Handler | Route | Description |
151
- |---------|-------|-------------|
152
- | `createSession(body, ctx)` | `POST /sessions` | Create verification session |
153
- | `getSession(id)` | `GET /sessions/:id` | Get session status |
154
- | `cancelSession(id)` | `POST /sessions/:id/cancel` | Cancel active session |
155
- | `verifyToken(body)` | `POST /tokens/verify` | Validate verification token |
156
- | `handleCallback(data)` | `POST /callback` | Wallet callback (internal) |
158
+ | Handler | Route | Description |
159
+ | -------------------------- | --------------------------- | --------------------------- |
160
+ | `createSession(body, ctx)` | `POST /sessions` | Create verification session |
161
+ | `getSession(id)` | `GET /sessions/:id` | Get session status |
162
+ | `cancelSession(id)` | `POST /sessions/:id/cancel` | Cancel active session |
163
+ | `verifyToken(body)` | `POST /tokens/verify` | Validate verification token |
164
+ | `handleCallback(data)` | `POST /callback` | Wallet callback (internal) |
157
165
 
158
166
  ## Error Boundaries
159
167
 
@@ -163,22 +171,22 @@ Handlers return `{ status, headers?, body }` — they **never throw**. Your fram
163
171
 
164
172
  **1. HTTP errors** — returned as `{ error, message, details? }` with 4xx/5xx status:
165
173
 
166
- | Status | `error` code | Typical cause |
167
- |--------|--------------|---------------|
168
- | 400 | `bad_request` | Invalid input |
169
- | 403 | `forbidden` | Origin not in `allowedOrigins` |
170
- | 404 | `not_found` | Session missing |
171
- | 409 | `conflict` | Cancel on terminal session |
172
- | 429 | `rate_limited` | Rate limit exceeded |
173
- | 500 | `internal_error` | Engine failure on create |
174
+ | Status | `error` code | Typical cause |
175
+ | ------ | ---------------- | ------------------------------ |
176
+ | 400 | `bad_request` | Invalid input |
177
+ | 403 | `forbidden` | Origin not in `allowedOrigins` |
178
+ | 404 | `not_found` | Session missing |
179
+ | 409 | `conflict` | Cancel on terminal session |
180
+ | 429 | `rate_limited` | Rate limit exceeded |
181
+ | 500 | `internal_error` | Engine failure on create |
174
182
 
175
183
  **2. Session outcomes** — HTTP 200, check `body.status`:
176
184
 
177
- | `status` | Meaning |
178
- |----------|---------|
179
- | `rejected` | User declined in wallet |
180
- | `expired` | Session TTL elapsed |
181
- | `error` | VP validation or engine failure |
185
+ | `status` | Meaning |
186
+ | ---------- | ------------------------------- |
187
+ | `rejected` | User declined in wallet |
188
+ | `expired` | Session TTL elapsed |
189
+ | `error` | VP validation or engine failure |
182
190
 
183
191
  These surface to your frontend via `GET /sessions/:id` polling, not via callback HTTP status.
184
192
 
@@ -198,12 +206,12 @@ To report callback-path failures server-side, inspect the session after handling
198
206
  ### Route adapter pattern
199
207
 
200
208
  ```ts
201
- app.post('/sessions', async (req, res) => {
209
+ app.post("/sessions", async (req, res) => {
202
210
  const result = await handlers.createSession(buildContext(req, {}, req.body));
203
211
 
204
- if (result.status >= 400 && 'error' in result.body) {
212
+ if (result.status >= 400 && "error" in result.body) {
205
213
  // Your error reporting hook
206
- reportError({ handler: 'createSession', ...result.body });
214
+ reportError({ handler: "createSession", ...result.body });
207
215
  }
208
216
 
209
217
  res.status(result.status).set(result.headers).json(result.body);
@@ -225,11 +233,11 @@ After the widget emits a `verified` event with a token, validate it server-side:
225
233
 
226
234
  ```ts
227
235
  // In your protected endpoint
228
- app.post('/checkout', async (req, res) => {
236
+ app.post("/checkout", async (req, res) => {
229
237
  const { eudiToken } = req.body;
230
-
238
+
231
239
  const result = await handlers.verifyToken({ token: eudiToken });
232
-
240
+
233
241
  if (result.body.valid) {
234
242
  // Token is valid, claims are verified
235
243
  const { age_over_18, nationality } = result.body.claims;
@@ -246,6 +254,7 @@ app.post('/checkout', async (req, res) => {
246
254
  ⚠️ Demo mode accepts simulated credentials. **Never use in production.**
247
255
 
248
256
  Demo mode is indicated by:
257
+
249
258
  - Console warning on startup
250
259
  - `X-Eudi-Mode: demo` header on all responses
251
260
 
package/dist/index.d.ts CHANGED
@@ -39,7 +39,7 @@ interface VerificationRequest {
39
39
  * Flow: pending → waiting_for_wallet → verified|rejected|expired|error
40
40
  * ↳ cancelled (from any non-terminal state)
41
41
  */
42
- type SessionStatus = 'pending' | 'waiting_for_wallet' | 'verified' | 'rejected' | 'expired' | 'cancelled' | 'error';
42
+ type SessionStatus = "pending" | "waiting_for_wallet" | "verified" | "rejected" | "expired" | "cancelled" | "error";
43
43
  /**
44
44
  * Terminal states that cannot transition further.
45
45
  */
@@ -155,7 +155,7 @@ interface VerifyTokenInput {
155
155
  interface VerifyTokenResult {
156
156
  valid: boolean;
157
157
  claims?: VerifiedClaims;
158
- error?: 'invalid_token' | 'expired' | 'already_consumed' | 'invalid_signature';
158
+ error?: "invalid_token" | "expired" | "already_consumed" | "invalid_signature";
159
159
  }
160
160
  /**
161
161
  * API error response.
@@ -168,7 +168,7 @@ interface ApiError {
168
168
  /**
169
169
  * Operation mode for the verifier.
170
170
  */
171
- type VerifierMode = 'demo' | 'production';
171
+ type VerifierMode = "demo" | "production";
172
172
 
173
173
  /**
174
174
  * @eudi-verify/server - Key-Value Store Interface
package/dist/index.js CHANGED
@@ -134,7 +134,11 @@ var MockEngine = class {
134
134
  const requestedClaims = Object.keys(config.request).filter(
135
135
  (k) => config.request[k] === true
136
136
  );
137
- const qrUrl = this.buildMockQrUrl(config.sessionId, config.baseUrl, requestedClaims);
137
+ const qrUrl = this.buildMockQrUrl(
138
+ config.sessionId,
139
+ config.baseUrl,
140
+ requestedClaims
141
+ );
138
142
  return {
139
143
  qrUrl,
140
144
  engineData: {
@@ -274,7 +278,9 @@ var OpenEudiEngine = class {
274
278
  redirect_uri: `${this.config.baseUrl}/callback`,
275
279
  state: session.id,
276
280
  nonce,
277
- presentation_definition: this.buildPresentationDefinition(session.request),
281
+ presentation_definition: this.buildPresentationDefinition(
282
+ session.request
283
+ ),
278
284
  mode: "demo"
279
285
  });
280
286
  }
@@ -285,7 +291,9 @@ var OpenEudiEngine = class {
285
291
  redirect_uri: `${this.config.baseUrl}/callback`,
286
292
  state: session.id,
287
293
  nonce,
288
- presentation_definition: this.buildPresentationDefinition(session.request)
294
+ presentation_definition: this.buildPresentationDefinition(
295
+ session.request
296
+ )
289
297
  });
290
298
  }
291
299
  async cancelSession(_session) {
@@ -328,7 +336,9 @@ var OpenEudiEngine = class {
328
336
  return `openid4vp://authorize?${params.toString()}`;
329
337
  }
330
338
  buildPresentationDefinition(request) {
331
- const requestedClaims = Object.keys(request).filter((k) => request[k] === true);
339
+ const requestedClaims = Object.keys(request).filter(
340
+ (k) => request[k] === true
341
+ );
332
342
  const inputDescriptors = requestedClaims.map((claim) => ({
333
343
  id: claim,
334
344
  name: this.getClaimDisplayName(claim),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eudi-verify/server",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Framework-agnostic REST API handlers for EUDI Wallet verification",
5
5
  "type": "module",
6
6
  "engines": {