@nevermined-io/ui-widgets-server 0.4.5 → 0.4.6

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
@@ -25,11 +25,14 @@ import { createInitToken } from '@nevermined-io/ui-widgets-server'
25
25
  const app = express()
26
26
 
27
27
  app.get('/api/widget-init-token', async (req, res) => {
28
- // Authenticate the request your normal way and resolve the user.
29
- const userId = req.user.id
28
+ // Authenticate the request your normal way and resolve the user's email.
29
+ // The email is the canonical Nevermined identity — a user that later logs
30
+ // into nevermined.dev directly with the same email is resolved to the
31
+ // same Privy user, the same wallet, and the same widget profile.
32
+ const email = req.user.email
30
33
 
31
34
  const initToken = await createInitToken({
32
- userId,
35
+ email,
33
36
  orgId: process.env.NVM_ORG_ID!,
34
37
  secretKey: process.env.NVM_WIDGET_KEY_SECRET!, // never expose to the browser
35
38
  })
@@ -55,13 +58,13 @@ const nvm = await NeverminedWidgets.initialize({
55
58
 
56
59
  ### `createInitToken(options): Promise<string>`
57
60
 
58
- Returns a signed JWT (HS256) that the browser SDK passes to `POST /api/v1/widgets/session`. The token's audience is `nvm:widgets` and it carries the user identity (`sub`) and organization id (`orgId`) as claims.
61
+ Returns a signed JWT (HS256) that the browser SDK passes to `POST /api/v1/widgets/session`. The token's audience is `nvm:widgets` and it carries the user's email (`email`, the canonical Nevermined identity) and organization id (`orgId`) as claims.
59
62
 
60
63
  #### Options
61
64
 
62
65
  | Field | Type | Required | Default | Description |
63
66
  | ------------ | -------- | -------- | ------- | ----------- |
64
- | `userId` | `string` | yes | — | Stable identifier for the end user in your system. Becomes the JWT `sub` claim. |
67
+ | `email` | `string` | yes | — | The end user's email. Becomes the JWT `email` claim and the canonical Nevermined identity — a user that logs into nevermined.dev directly with the same email is resolved to the same Privy user, wallet, and profile. |
65
68
  | `orgId` | `string` | yes | — | Your Nevermined organization id. |
66
69
  | `secretKey` | `string` | yes | — | The widget key secret (`wk_...`) issued in the Nevermined dashboard. **Server-side only.** |
67
70
  | `expiresIn` | `string` | no | `'5m'` | JWT lifetime — any [`jose`-compatible](https://github.com/panva/jose) duration string. The default is intentionally short: the browser exchanges the token immediately for a long-lived session. |
@@ -70,7 +73,8 @@ Returns a signed JWT (HS256) that the browser SDK passes to `POST /api/v1/widget
70
73
 
71
74
  ```ts
72
75
  interface InitTokenPayload {
73
- sub: string // userId
76
+ sub: string // mirrors `email` (legacy claim, kept for compatibility)
77
+ email: string // canonical Nevermined identity
74
78
  orgId: string
75
79
  aud: 'nvm:widgets'
76
80
  iat: number
@@ -78,6 +82,10 @@ interface InitTokenPayload {
78
82
  }
79
83
  ```
80
84
 
85
+ #### Breaking change vs `< 0.5.0`
86
+
87
+ The `userId` parameter was renamed to `email` and is now required to be a real email address. The widget session API resolves the Privy user via `getByEmailAddress` and only falls back to creating a new user if no match exists; an arbitrary `userId` no longer maps to a deterministic Privy user. Update your backend integration to forward the user's email to `createInitToken` instead of an internal id.
88
+
81
89
  ## Security notes
82
90
 
83
91
  - Treat `secretKey` like a database password: never inline it in client code, commit it, or send it through an unencrypted channel. If it leaks, rotate the widget key in the dashboard immediately.
@@ -1 +1 @@
1
- {"version":3,"file":"create-init-token.d.ts","sourceRoot":"","sources":["../src/create-init-token.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAKxD,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBtF"}
1
+ {"version":3,"file":"create-init-token.d.ts","sourceRoot":"","sources":["../src/create-init-token.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAKxD,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoBtF"}
@@ -1,18 +1,21 @@
1
1
  import { SignJWT } from 'jose';
2
+ import { normalizeEmail } from './normalize-email.js';
2
3
  const WIDGET_AUDIENCE = 'nvm:widgets';
3
4
  const DEFAULT_EXPIRES_IN = '5m';
4
5
  export async function createInitToken(options) {
5
- const { userId, orgId, secretKey, expiresIn = DEFAULT_EXPIRES_IN } = options;
6
+ const { email, orgId, secretKey, expiresIn = DEFAULT_EXPIRES_IN } = options;
6
7
  if (!secretKey?.trim())
7
8
  throw new Error('secretKey is required');
8
- if (!userId?.trim())
9
- throw new Error('userId is required');
10
9
  if (!orgId?.trim())
11
10
  throw new Error('orgId is required');
11
+ const normalizedEmail = normalizeEmail(email);
12
12
  const secret = new TextEncoder().encode(secretKey);
13
- return new SignJWT({ orgId })
13
+ // `sub` is set to the email so the API's existence check on the claim
14
+ // continues to pass without a parallel API change. The downstream
15
+ // identity resolution uses the explicit `email` claim, not `sub`.
16
+ return new SignJWT({ orgId, email: normalizedEmail })
14
17
  .setProtectedHeader({ alg: 'HS256' })
15
- .setSubject(userId)
18
+ .setSubject(normalizedEmail)
16
19
  .setAudience(WIDGET_AUDIENCE)
17
20
  .setIssuedAt()
18
21
  .setExpirationTime(expiresIn)
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Validates and normalizes an email to a canonical form: trimmed and lowercased,
3
+ * capped at 254 chars (RFC 5321), and matched against a permissive format regex.
4
+ *
5
+ * Normalization is implicit so `Alice@Example.COM` and `alice@example.com` map
6
+ * to the same Nevermined identity downstream (#1520 — "one email = one user,
7
+ * one wallet, one widget profile"). Callers should not rely on the original
8
+ * case being preserved.
9
+ *
10
+ * Throws with caller-friendly messages — the SDK is the first boundary that
11
+ * knows the value is supposed to be an email.
12
+ */
13
+ export declare function normalizeEmail(email: string | undefined): string;
14
+ //# sourceMappingURL=normalize-email.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize-email.d.ts","sourceRoot":"","sources":["../src/normalize-email.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAahE"}
@@ -0,0 +1,31 @@
1
+ // Matches the regex behind <input type="email"> and what RFC 5322 implementations
2
+ // accept in practice. Intentionally permissive — strict RFC validation belongs in
3
+ // the integrator's auth layer, not here.
4
+ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
5
+ // RFC 5321 caps email addresses at 254 chars. Enforcing here prevents a broken
6
+ // upstream integrator from ballooning the JWT and the server-side decode path.
7
+ const EMAIL_MAX_LENGTH = 254;
8
+ /**
9
+ * Validates and normalizes an email to a canonical form: trimmed and lowercased,
10
+ * capped at 254 chars (RFC 5321), and matched against a permissive format regex.
11
+ *
12
+ * Normalization is implicit so `Alice@Example.COM` and `alice@example.com` map
13
+ * to the same Nevermined identity downstream (#1520 — "one email = one user,
14
+ * one wallet, one widget profile"). Callers should not rely on the original
15
+ * case being preserved.
16
+ *
17
+ * Throws with caller-friendly messages — the SDK is the first boundary that
18
+ * knows the value is supposed to be an email.
19
+ */
20
+ export function normalizeEmail(email) {
21
+ if (!email?.trim())
22
+ throw new Error('email is required');
23
+ const normalized = email.trim().toLowerCase();
24
+ if (normalized.length > EMAIL_MAX_LENGTH) {
25
+ throw new Error(`email is too long (max ${EMAIL_MAX_LENGTH} chars per RFC 5321)`);
26
+ }
27
+ if (!EMAIL_RE.test(normalized)) {
28
+ throw new Error('email must be a valid email address');
29
+ }
30
+ return normalized;
31
+ }
package/dist/types.d.ts CHANGED
@@ -1,11 +1,29 @@
1
1
  export interface CreateInitTokenOptions {
2
- userId: string;
2
+ /**
3
+ * The end user's email — the canonical Nevermined identity (#1520). The
4
+ * widget session API uses this to resolve or provision the Privy user.
5
+ *
6
+ * Validated and normalized at the SDK boundary: trimmed, lowercased, capped
7
+ * at 254 chars (RFC 5321), and matched against a basic format regex. The
8
+ * normalized value is what ends up in the JWT — callers should not rely on
9
+ * case being preserved.
10
+ */
11
+ email: string;
3
12
  orgId: string;
4
13
  secretKey: string;
5
14
  expiresIn?: string;
6
15
  }
7
16
  export interface InitTokenPayload {
17
+ /**
18
+ * Mirrors `email` so the widget session API's non-empty existence check on
19
+ * `sub` passes without a parallel API change. Do NOT read `sub` for identity
20
+ * downstream — read `email` instead. This claim will be dropped once the
21
+ * API stops reading `sub` (follow-up to #1520).
22
+ *
23
+ * @deprecated Read `email` instead. Kept temporarily for API compatibility.
24
+ */
8
25
  sub: string;
26
+ email: string;
9
27
  orgId: string;
10
28
  aud: string;
11
29
  iat: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,sBAAsB;IACrC;;;;;;;;OAQG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;OAOG;IACH,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nevermined-io/ui-widgets-server",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",