@tesouro/embedded-components-widget-token 0.0.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 +135 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index2.js +82 -0
- package/dist/lib/createWidgetToken.d.ts +26 -0
- package/dist/lib/createWidgetToken.d.ts.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# @tesouro/embedded-components-widget-token
|
|
2
|
+
|
|
3
|
+
Server-side helpers for minting widget tokens used by Tesouro's embedded
|
|
4
|
+
components. Mints an [RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693)
|
|
5
|
+
token-exchange request and encrypts it as a JWE (`A256KW` + `A256GCM`) using
|
|
6
|
+
your shared widget secret.
|
|
7
|
+
|
|
8
|
+
> **Server-only.** This package handles your `clientSecret` and `widgetSecret`,
|
|
9
|
+
> so keep it on the server and never bundle it into client code. It runs in any
|
|
10
|
+
> server runtime — a Next.js Route Handler, an Express server, or a Lambda
|
|
11
|
+
> handler.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
yarn add @tesouro/embedded-components-widget-token
|
|
17
|
+
# or
|
|
18
|
+
npm install @tesouro/embedded-components-widget-token
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### One-shot
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { createWidgetToken } from '@tesouro/embedded-components-widget-token';
|
|
27
|
+
|
|
28
|
+
const { widgetToken, exp } = await createWidgetToken({
|
|
29
|
+
clientId: process.env.TESOURO_CLIENT_ID!,
|
|
30
|
+
clientSecret: process.env.TESOURO_CLIENT_SECRET!,
|
|
31
|
+
widgetSecret: process.env.TESOURO_WIDGET_SECRET!,
|
|
32
|
+
userId: 'user_123',
|
|
33
|
+
userEmail: 'user@example.com',
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`exp` is the token's expiration time in seconds since epoch — return it to the
|
|
38
|
+
client so it can refresh before expiry.
|
|
39
|
+
|
|
40
|
+
### Pre-configured factory
|
|
41
|
+
|
|
42
|
+
When the same credentials are reused across many requests, bind them once with
|
|
43
|
+
`configureCreateWidgetToken`:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { configureCreateWidgetToken } from '@tesouro/embedded-components-widget-token';
|
|
47
|
+
|
|
48
|
+
const mintWidgetToken = configureCreateWidgetToken({
|
|
49
|
+
clientId: process.env.TESOURO_CLIENT_ID!,
|
|
50
|
+
clientSecret: process.env.TESOURO_CLIENT_SECRET!,
|
|
51
|
+
widgetSecret: process.env.TESOURO_WIDGET_SECRET!,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const { widgetToken, exp } = await mintWidgetToken({
|
|
55
|
+
userId: 'user_123',
|
|
56
|
+
userEmail: 'user@example.com',
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Next.js Route Handler
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
// app/api/widget-token/route.ts
|
|
64
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
65
|
+
import { configureCreateWidgetToken } from '@tesouro/embedded-components-widget-token';
|
|
66
|
+
|
|
67
|
+
const mintWidgetToken = configureCreateWidgetToken({
|
|
68
|
+
clientId: process.env.TESOURO_CLIENT_ID!,
|
|
69
|
+
clientSecret: process.env.TESOURO_CLIENT_SECRET!,
|
|
70
|
+
widgetSecret: process.env.TESOURO_WIDGET_SECRET!,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export async function POST(req: NextRequest) {
|
|
74
|
+
const { userId, userEmail } = await req.json();
|
|
75
|
+
const { widgetToken, exp } = await mintWidgetToken({ userId, userEmail });
|
|
76
|
+
return NextResponse.json({ widgetToken, exp });
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## API
|
|
81
|
+
|
|
82
|
+
### `createWidgetToken(params): Promise<WidgetTokenResult>`
|
|
83
|
+
|
|
84
|
+
| Param | Type | Required | Description |
|
|
85
|
+
| --------------------- | -------- | -------- | -------------------------------------------------------------------------------------------- |
|
|
86
|
+
| `clientId` | `string` | yes | Tesouro-issued client identifier. Used as JWE `kid` and as `iss`/`client_id` in the payload. |
|
|
87
|
+
| `clientSecret` | `string` | yes | Tesouro-issued client secret. Carried as `client_secret` inside the encrypted payload. |
|
|
88
|
+
| `widgetSecret` | `string` | yes | Shared symmetric key used to encrypt the JWE. |
|
|
89
|
+
| `userId` | `string` | yes | Subject (`sub`) of the inner identity token. |
|
|
90
|
+
| `userEmail` | `string` | yes | Email claim of the inner identity token. |
|
|
91
|
+
| `expirationInSeconds` | `number` | no | Token TTL in seconds. Defaults to `DEFAULT_EXPIRATION_IN_SECONDS` (480 — 8 minutes). |
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
interface WidgetTokenResult {
|
|
97
|
+
widgetToken: string; // compact-serialized JWE
|
|
98
|
+
exp: number; // expiration in seconds since epoch (UTC)
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Throws if `clientId`, `clientSecret`, or `widgetSecret` is missing or empty.
|
|
103
|
+
|
|
104
|
+
### `configureCreateWidgetToken(creds)`
|
|
105
|
+
|
|
106
|
+
Binds `clientId`, `clientSecret`, `widgetSecret`, and an optional default
|
|
107
|
+
`expirationInSeconds`, and returns a function that only takes per-request
|
|
108
|
+
`userId` / `userEmail` (and an optional per-call `expirationInSeconds` that
|
|
109
|
+
overrides the bound default).
|
|
110
|
+
|
|
111
|
+
### `DEFAULT_EXPIRATION_IN_SECONDS`
|
|
112
|
+
|
|
113
|
+
`480` — the default token lifetime in seconds (8 minutes).
|
|
114
|
+
|
|
115
|
+
## Token format
|
|
116
|
+
|
|
117
|
+
`createWidgetToken` produces a JWE with the following protected header:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"alg": "A256KW",
|
|
122
|
+
"enc": "A256GCM",
|
|
123
|
+
"kid": "<clientId>",
|
|
124
|
+
"typ": "JWT",
|
|
125
|
+
"cty": "JWT"
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The encrypted payload is an RFC 8693 token-exchange request whose
|
|
130
|
+
`subject_token` is an unsigned identity JWT (`alg: none`) bearing the user's
|
|
131
|
+
`sub`, `email`, `iss`, `aud`, `iat`, and `exp` claims.
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
UNLICENSED — internal Tesouro package.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC"}
|
package/dist/index.js
ADDED
package/dist/index2.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import d from "crypto";
|
|
2
|
+
import { EncryptJWT as y } from "jose";
|
|
3
|
+
const k = 480;
|
|
4
|
+
async function T({
|
|
5
|
+
clientId: e,
|
|
6
|
+
clientSecret: o,
|
|
7
|
+
widgetSecret: r,
|
|
8
|
+
userId: i,
|
|
9
|
+
userEmail: s,
|
|
10
|
+
organizationReference: a,
|
|
11
|
+
expirationInSeconds: c = k
|
|
12
|
+
}) {
|
|
13
|
+
if (!r || !e || !o)
|
|
14
|
+
throw new Error(
|
|
15
|
+
"Missing required parameters for widget token generation: clientId, clientSecret, and widgetSecret are required"
|
|
16
|
+
);
|
|
17
|
+
const t = Math.floor(Date.now() / 1e3), n = t + c, u = new TextEncoder().encode(r), f = {
|
|
18
|
+
sub: i,
|
|
19
|
+
// Subject (user identifier)
|
|
20
|
+
email: s,
|
|
21
|
+
// Email claim
|
|
22
|
+
aud: "tesouro",
|
|
23
|
+
// Audience
|
|
24
|
+
iss: e,
|
|
25
|
+
// Issuer
|
|
26
|
+
exp: n,
|
|
27
|
+
// Expiration
|
|
28
|
+
iat: t
|
|
29
|
+
// Issued at
|
|
30
|
+
}, g = Buffer.from(JSON.stringify({ alg: "none", typ: "JWT" })).toString(
|
|
31
|
+
"base64url"
|
|
32
|
+
) + "." + Buffer.from(JSON.stringify(f)).toString("base64url") + ".", p = {
|
|
33
|
+
aud: "tesouro",
|
|
34
|
+
client_id: e,
|
|
35
|
+
client_secret: o,
|
|
36
|
+
exp: n,
|
|
37
|
+
grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
|
|
38
|
+
iat: t,
|
|
39
|
+
iss: e,
|
|
40
|
+
jti: d.randomUUID(),
|
|
41
|
+
...a ? { organization_reference: a } : {},
|
|
42
|
+
subject_token: g,
|
|
43
|
+
subject_token_type: "urn:ietf:params:oauth:token-type:access_token"
|
|
44
|
+
};
|
|
45
|
+
return { widgetToken: await new y(p).setProtectedHeader({
|
|
46
|
+
alg: "A256KW",
|
|
47
|
+
enc: "A256GCM",
|
|
48
|
+
kid: e,
|
|
49
|
+
typ: "JWT",
|
|
50
|
+
cty: "JWT"
|
|
51
|
+
}).encrypt(u), exp: n };
|
|
52
|
+
}
|
|
53
|
+
function b({
|
|
54
|
+
clientId: e,
|
|
55
|
+
clientSecret: o,
|
|
56
|
+
widgetSecret: r,
|
|
57
|
+
organizationReference: i,
|
|
58
|
+
expirationInSeconds: s
|
|
59
|
+
}) {
|
|
60
|
+
return ({
|
|
61
|
+
userId: a,
|
|
62
|
+
userEmail: c,
|
|
63
|
+
// Defaults to the partner-config value bound at configure time. A per-call
|
|
64
|
+
// override supports partners whose single client serves multiple
|
|
65
|
+
// organizations; it must still be resolved server-side, never from the browser.
|
|
66
|
+
organizationReference: t = i,
|
|
67
|
+
expirationInSeconds: n = s
|
|
68
|
+
}) => T({
|
|
69
|
+
clientId: e,
|
|
70
|
+
clientSecret: o,
|
|
71
|
+
widgetSecret: r,
|
|
72
|
+
userId: a,
|
|
73
|
+
userEmail: c,
|
|
74
|
+
organizationReference: t,
|
|
75
|
+
expirationInSeconds: n
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
k as DEFAULT_EXPIRATION_IN_SECONDS,
|
|
80
|
+
b as configureCreateWidgetToken,
|
|
81
|
+
T as createWidgetToken
|
|
82
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const DEFAULT_EXPIRATION_IN_SECONDS = 480;
|
|
2
|
+
export interface CreateWidgetTokenParams {
|
|
3
|
+
clientId: string;
|
|
4
|
+
clientSecret: string;
|
|
5
|
+
widgetSecret: string;
|
|
6
|
+
userId: string;
|
|
7
|
+
userEmail: string;
|
|
8
|
+
/**
|
|
9
|
+
* The partner's stable, opaque identifier for the business/organization
|
|
10
|
+
* (SMB grouping) the applicant belongs to. Minted into the outer JWE payload
|
|
11
|
+
* as `organization_reference` so the widget proxy can call
|
|
12
|
+
* embedded-banking/v1/application-status to drive widget visibility.
|
|
13
|
+
*
|
|
14
|
+
* MUST be resolved server-side from the partner's organization config — never
|
|
15
|
+
* accept this value from the browser.
|
|
16
|
+
*/
|
|
17
|
+
organizationReference?: string;
|
|
18
|
+
expirationInSeconds?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface WidgetTokenResult {
|
|
21
|
+
widgetToken: string;
|
|
22
|
+
exp: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function createWidgetToken({ clientId, clientSecret, widgetSecret, userId, userEmail, organizationReference, expirationInSeconds, }: CreateWidgetTokenParams): Promise<WidgetTokenResult>;
|
|
25
|
+
export declare function configureCreateWidgetToken({ clientId, clientSecret, widgetSecret, organizationReference: defaultOrganizationReference, expirationInSeconds: defaultExpiration, }: Pick<CreateWidgetTokenParams, 'clientId' | 'clientSecret' | 'widgetSecret' | 'organizationReference' | 'expirationInSeconds'>): ({ userId, userEmail, organizationReference, expirationInSeconds, }: Pick<CreateWidgetTokenParams, "userId" | "userEmail" | "organizationReference" | "expirationInSeconds">) => Promise<WidgetTokenResult>;
|
|
26
|
+
//# sourceMappingURL=createWidgetToken.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createWidgetToken.d.ts","sourceRoot":"","sources":["../../src/lib/createWidgetToken.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,6BAA6B,MAAM,CAAC;AAEjD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;;OAQG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAsB,iBAAiB,CAAC,EACtC,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,MAAM,EACN,SAAS,EACT,qBAAqB,EACrB,mBAAmD,GACpD,EAAE,uBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA6DtD;AAED,wBAAgB,0BAA0B,CAAC,EACzC,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,qBAAqB,EAAE,4BAA4B,EACnD,mBAAmB,EAAE,iBAAiB,GACvC,EAAE,IAAI,CACL,uBAAuB,EACrB,UAAU,GACV,cAAc,GACd,cAAc,GACd,uBAAuB,GACvB,qBAAqB,CACxB,IACS,oEAQL,IAAI,CACL,uBAAuB,EACvB,QAAQ,GAAG,WAAW,GAAG,uBAAuB,GAAG,qBAAqB,CACzE,gCAUF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tesouro/embedded-components-widget-token",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/tesourohq/Tesouro.Payments.Frontends.git",
|
|
9
|
+
"directory": "packages/embedded-components-widget-token"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/tesourohq/Tesouro.Payments.Frontends/tree/main/packages/embedded-components-widget-token#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/tesourohq/Tesouro.Payments.Frontends/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public",
|
|
17
|
+
"provenance": false
|
|
18
|
+
},
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"module": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
"./package.json": "./package.json",
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"import": "./dist/index.js",
|
|
27
|
+
"default": "./dist/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"!**/*.tsbuildinfo"
|
|
33
|
+
],
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"jose": "^6.1.3"
|
|
36
|
+
},
|
|
37
|
+
"nx": {
|
|
38
|
+
"name": "embedded-components-widget-token",
|
|
39
|
+
"tags": [
|
|
40
|
+
"type:feature",
|
|
41
|
+
"server-only"
|
|
42
|
+
],
|
|
43
|
+
"targets": {
|
|
44
|
+
"verify-publish": {
|
|
45
|
+
"executor": "nx:run-commands",
|
|
46
|
+
"dependsOn": [
|
|
47
|
+
"build"
|
|
48
|
+
],
|
|
49
|
+
"inputs": [
|
|
50
|
+
"default",
|
|
51
|
+
"{workspaceRoot}/tools/publishable-package/**"
|
|
52
|
+
],
|
|
53
|
+
"options": {
|
|
54
|
+
"command": "node tools/publishable-package/verify-publish.mjs --config {projectRoot}/publish.config.mjs"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|