@tapbuy-public/sso 1.0.0
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 +193 -0
- package/dist/esm/index.d.ts +119 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +284 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/index.d.ts +119 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +320 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# @tapbuy-public/sso
|
|
2
|
+
|
|
3
|
+
Framework-agnostic SSO handler for Tapbuy checkout. Decrypts an encrypted cookie payload passed via URL query parameter and sets or removes cookies accordingly.
|
|
4
|
+
|
|
5
|
+
Works with any runtime that supports the Web standard `Request`/`Response` API: **Next.js (App Router)**, Nuxt, Remix, Deno, Bun, Cloudflare Workers, etc.
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
1. The Tapbuy API returns a `singleSignOnURL` pointing to the retailer's SSO endpoint.
|
|
10
|
+
2. The Tapbuy checkout renders a hidden `<img src="{singleSignOnURL}">` pixel.
|
|
11
|
+
3. The retailer's SSO endpoint (powered by this package) reads the `token` and `action` query params, decrypts the encrypted payload, sets or removes cookies based on its content, and returns a 1×1 transparent GIF.
|
|
12
|
+
|
|
13
|
+
The `token` query parameter contains a **base64-encoded encrypted JSON payload** — an array of cookie operations (`set` / `remove`) with names and values. The encryption key must match the retailer's `encryption_key` configured on the Tapbuy API side.
|
|
14
|
+
|
|
15
|
+
Because the Tapbuy checkout runs on the retailer's subdomain (e.g. `checkout.retailer.com`), the pixel request is **same-site** — no third-party cookie issues.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
yarn add @tapbuy-public/sso
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Next.js (App Router)
|
|
26
|
+
|
|
27
|
+
Create a route handler at `app/api/tapbuy-sso/route.ts`:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { createSSOHandler } from '@tapbuy-public/sso';
|
|
31
|
+
|
|
32
|
+
export const { GET } = createSSOHandler({
|
|
33
|
+
cookies: {
|
|
34
|
+
login: [
|
|
35
|
+
{
|
|
36
|
+
name: 'userId',
|
|
37
|
+
httpOnly: true,
|
|
38
|
+
path: '/',
|
|
39
|
+
domain: '.example.com',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'sessionExpiration',
|
|
43
|
+
httpOnly: false,
|
|
44
|
+
path: '/',
|
|
45
|
+
domain: '.example.com',
|
|
46
|
+
maxAge: 3600,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
logout: ['userId', 'sessionExpiration'],
|
|
50
|
+
},
|
|
51
|
+
encryptionKey: process.env.TAPBUY_SSO_ENCRYPTION_KEY!,
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
That's it — one file. No other changes required.
|
|
56
|
+
|
|
57
|
+
### Minimal example
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { createSSOHandler } from '@tapbuy-public/sso';
|
|
61
|
+
|
|
62
|
+
export const { GET } = createSSOHandler({
|
|
63
|
+
cookies: {
|
|
64
|
+
login: [
|
|
65
|
+
{
|
|
66
|
+
name: 'userToken',
|
|
67
|
+
httpOnly: false,
|
|
68
|
+
path: '/',
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
logout: ['userToken'],
|
|
72
|
+
},
|
|
73
|
+
encryptionKey: process.env.TAPBUY_SSO_ENCRYPTION_KEY!,
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Using AES-256-ECB (Node.js only)
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
export const { GET } = createSSOHandler({
|
|
81
|
+
cookies: {
|
|
82
|
+
login: [{ name: 'userToken', httpOnly: true, path: '/' }],
|
|
83
|
+
logout: ['userToken'],
|
|
84
|
+
},
|
|
85
|
+
encryptionKey: process.env.TAPBUY_SSO_ENCRYPTION_KEY!,
|
|
86
|
+
encryptionAlgorithm: 'aes-256-ecb',
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## API
|
|
91
|
+
|
|
92
|
+
### `createSSOHandler(config: SSOConfig)`
|
|
93
|
+
|
|
94
|
+
Returns `{ GET: (request: Request) => Response | Promise<Response> }`.
|
|
95
|
+
|
|
96
|
+
The `GET` handler reads two query parameters from the request URL:
|
|
97
|
+
|
|
98
|
+
| Parameter | Required | Description |
|
|
99
|
+
| --------- | ----------------- | --------------------------------------------------------------------------- |
|
|
100
|
+
| `action` | Always | `"login"` or `"logout"` |
|
|
101
|
+
| `token` | When action=login | Base64-encoded encrypted JSON payload describing which cookies to set/remove |
|
|
102
|
+
|
|
103
|
+
**Encrypted payload format**: The decrypted `token` is a JSON array of cookie operations:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
interface SSOCookiePayloadItem {
|
|
107
|
+
name: string; // Cookie name
|
|
108
|
+
value: string; // Cookie value to set
|
|
109
|
+
action: 'set' | 'remove'; // Whether to set or remove (expire) this cookie
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**On login**: decrypts the `token`, then for each item in the payload:
|
|
114
|
+
- `action: 'set'` — sets the cookie with the given value, using security options from the matching `cookies.login` config.
|
|
115
|
+
- `action: 'remove'` — expires the cookie by setting `Max-Age=0`.
|
|
116
|
+
|
|
117
|
+
**On logout**: expires each cookie name listed in `config.cookies.logout` by setting `Max-Age=0`. Path and domain are inherited from the matching login config (if any).
|
|
118
|
+
|
|
119
|
+
**Response**: Always returns a 1×1 transparent GIF (`image/gif`) with no-cache headers.
|
|
120
|
+
|
|
121
|
+
**Error**: Returns HTTP 400 (still a GIF) when `action` is missing/invalid, when `token` is missing on login, or when decryption fails.
|
|
122
|
+
|
|
123
|
+
### `SSOConfig`
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
interface SSOConfig {
|
|
127
|
+
cookies: {
|
|
128
|
+
/** Cookie security options for login (httpOnly, secure, path, domain, etc.). Cookie names and values come from the encrypted payload. */
|
|
129
|
+
login: SSOCookieConfig[];
|
|
130
|
+
/** Cookie names to delete on logout. */
|
|
131
|
+
logout: string[];
|
|
132
|
+
};
|
|
133
|
+
/** AES-256 encryption key. Must match the retailer's encryption_key on the API side. */
|
|
134
|
+
encryptionKey: string;
|
|
135
|
+
/** Encryption algorithm. @default 'aes-256-gcm' */
|
|
136
|
+
encryptionAlgorithm?: 'aes-256-gcm' | 'aes-256-ecb';
|
|
137
|
+
/** Optional allowed origins for CORS headers. */
|
|
138
|
+
allowedOrigins?: (string | RegExp)[];
|
|
139
|
+
/** Optional callback after cookies are set/deleted. */
|
|
140
|
+
onComplete?: (action: 'login' | 'logout', request: Request) => void | Promise<void>;
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### `SSOCookieConfig`
|
|
145
|
+
|
|
146
|
+
Defines **security options** for a cookie. The cookie name and value are provided by the encrypted payload at runtime.
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
interface SSOCookieConfig {
|
|
150
|
+
name: string;
|
|
151
|
+
httpOnly?: boolean; // default: true
|
|
152
|
+
secure?: boolean; // default: true
|
|
153
|
+
sameSite?: 'Strict' | 'Lax' | 'None'; // default: "Lax"
|
|
154
|
+
path?: string; // default: "/"
|
|
155
|
+
domain?: string; // default: request host
|
|
156
|
+
maxAge?: number; // default: 86400 (24h)
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Encryption algorithms
|
|
161
|
+
|
|
162
|
+
| Algorithm | Default | Runtime requirement | Notes |
|
|
163
|
+
| --------------- | ------- | ---------------------------------- | ---------------------------------------- |
|
|
164
|
+
| `aes-256-gcm` | Yes | Web Crypto API (works everywhere) | Recommended. Authenticated encryption. |
|
|
165
|
+
| `aes-256-ecb` | No | Node.js `crypto` module | Legacy. Use only if required by the API. |
|
|
166
|
+
|
|
167
|
+
## How to configure the Tapbuy API
|
|
168
|
+
|
|
169
|
+
The Tapbuy API adapter must return a `singleSignOnURL` in the login/guest response. The URL should point to the retailer's SSO endpoint with `{token}` and `{action}` placeholders:
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
https://example.com/api/tapbuy-sso?token={token}&action={action}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The Tapbuy checkout replaces `{token}` with the encrypted cookie payload and `{action}` with `login` or `logout` before firing the pixel.
|
|
176
|
+
|
|
177
|
+
The `encryption_key` configured on the Tapbuy API side must match the `encryptionKey` passed to `createSSOHandler`.
|
|
178
|
+
|
|
179
|
+
## Development
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Install dependencies
|
|
183
|
+
yarn
|
|
184
|
+
|
|
185
|
+
# Run tests
|
|
186
|
+
yarn test
|
|
187
|
+
|
|
188
|
+
# Build
|
|
189
|
+
yarn build
|
|
190
|
+
|
|
191
|
+
# Lint
|
|
192
|
+
yarn lint
|
|
193
|
+
```
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @tapbuy-public/sso
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic SSO handler for Tapbuy checkout.
|
|
5
|
+
* Decrypts an encrypted cookie payload and sets/deletes cookies accordingly.
|
|
6
|
+
*
|
|
7
|
+
* Works with any framework that uses the Web standard Request/Response API:
|
|
8
|
+
* Next.js (App Router), Nuxt, Remix, Deno, Bun, Cloudflare Workers, etc.
|
|
9
|
+
*
|
|
10
|
+
* Supported encryption algorithms:
|
|
11
|
+
* - **AES-256-GCM** (default): uses Web Crypto API — works everywhere.
|
|
12
|
+
* - **AES-256-ECB**: uses Node.js `crypto` module — requires Node.js runtime.
|
|
13
|
+
*
|
|
14
|
+
* The `token` query parameter contains an encrypted JSON payload
|
|
15
|
+
* describing which cookies to set/remove and their values.
|
|
16
|
+
* The encryption key must match the retailer's `encryption_key` on the API side.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // Next.js — app/api/tapbuy-sso/route.ts
|
|
20
|
+
* import { createSSOHandler } from '@tapbuy-public/sso'
|
|
21
|
+
*
|
|
22
|
+
* export const { GET } = createSSOHandler({
|
|
23
|
+
* cookies: {
|
|
24
|
+
* login: [
|
|
25
|
+
* { name: 'userId', httpOnly: true, path: '/', domain: '.example.com' },
|
|
26
|
+
* ],
|
|
27
|
+
* logout: ['userId'],
|
|
28
|
+
* },
|
|
29
|
+
* encryptionKey: process.env.TAPBUY_SSO_ENCRYPTION_KEY!,
|
|
30
|
+
* // encryptionAlgorithm: 'aes-256-ecb', // optional, default is 'aes-256-gcm'
|
|
31
|
+
* })
|
|
32
|
+
*/
|
|
33
|
+
/** Configuration for a single cookie to set on login. */
|
|
34
|
+
export interface SSOCookieConfig {
|
|
35
|
+
/** Cookie name (e.g. "userId", "userToken"). */
|
|
36
|
+
name: string;
|
|
37
|
+
/** Whether the cookie is inaccessible to JavaScript. @default true */
|
|
38
|
+
httpOnly?: boolean;
|
|
39
|
+
/** Only send cookie over HTTPS. @default true */
|
|
40
|
+
secure?: boolean;
|
|
41
|
+
/** SameSite attribute. @default "Lax" */
|
|
42
|
+
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
43
|
+
/** Cookie path. @default "/" */
|
|
44
|
+
path?: string;
|
|
45
|
+
/** Cookie domain (e.g. ".website.com"). If omitted, defaults to the request host. */
|
|
46
|
+
domain?: string;
|
|
47
|
+
/** Cookie max-age in seconds. @default 86400 (24 h) */
|
|
48
|
+
maxAge?: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A single cookie operation from the encrypted payload.
|
|
52
|
+
* The API builds an array of these, encrypts it, and passes it as the `token` query param.
|
|
53
|
+
*/
|
|
54
|
+
export interface SSOCookiePayloadItem {
|
|
55
|
+
/** Cookie name. Must match a name in `cookies.login` for security options. */
|
|
56
|
+
name: string;
|
|
57
|
+
/** Cookie value to set. */
|
|
58
|
+
value: string;
|
|
59
|
+
/** Whether to set or remove (expire) this cookie. */
|
|
60
|
+
action: 'set' | 'remove';
|
|
61
|
+
}
|
|
62
|
+
/** Supported encryption algorithms for the SSO token. */
|
|
63
|
+
export type SSOEncryptionAlgorithm = 'aes-256-gcm' | 'aes-256-ecb';
|
|
64
|
+
/** Main configuration object for the SSO handler. */
|
|
65
|
+
export interface SSOConfig {
|
|
66
|
+
cookies: {
|
|
67
|
+
/**
|
|
68
|
+
* Cookie security options for login.
|
|
69
|
+
* Defines httpOnly, secure, sameSite, path, domain, maxAge for each cookie.
|
|
70
|
+
* Cookie names and values come from the encrypted payload.
|
|
71
|
+
*/
|
|
72
|
+
login: SSOCookieConfig[];
|
|
73
|
+
/**
|
|
74
|
+
* Cookie names to delete when `action=logout`.
|
|
75
|
+
* They are expired by setting `Max-Age=0`.
|
|
76
|
+
*/
|
|
77
|
+
logout: string[];
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* AES-256-GCM encryption key.
|
|
81
|
+
* Must match the `encryption_key` retailer config on the API side.
|
|
82
|
+
* The `token` query param is decrypted using this key to get the cookie payload.
|
|
83
|
+
*/
|
|
84
|
+
encryptionKey: string;
|
|
85
|
+
/**
|
|
86
|
+
* Encryption algorithm used for the `token` query parameter.
|
|
87
|
+
* - `'aes-256-gcm'` (default): Web Crypto API — works everywhere.
|
|
88
|
+
* - `'aes-256-ecb'`: Node.js `crypto` module — requires Node.js runtime.
|
|
89
|
+
* @default 'aes-256-gcm'
|
|
90
|
+
*/
|
|
91
|
+
encryptionAlgorithm?: SSOEncryptionAlgorithm;
|
|
92
|
+
/**
|
|
93
|
+
* Optional list of allowed origins for CORS.
|
|
94
|
+
* When provided, the handler adds `Access-Control-Allow-Origin` for matching origins.
|
|
95
|
+
* Supports exact strings or RegExp patterns.
|
|
96
|
+
* @default [] (no CORS headers)
|
|
97
|
+
*/
|
|
98
|
+
allowedOrigins?: (string | RegExp)[];
|
|
99
|
+
/**
|
|
100
|
+
* Optional callback fired after cookies are set/deleted, before the response is returned.
|
|
101
|
+
* Useful for side-effects like logging or analytics.
|
|
102
|
+
*/
|
|
103
|
+
onComplete?: (action: 'login' | 'logout', request: Request) => void | Promise<void>;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Create a SSO route handler.
|
|
107
|
+
*
|
|
108
|
+
* Returns an object with a `GET` method compatible with the Web standard
|
|
109
|
+
* `Request → Response` signature (Next.js App Router, etc.).
|
|
110
|
+
*
|
|
111
|
+
* Query parameters:
|
|
112
|
+
* - `token` — encrypted cookie payload, base64-encoded (required for login)
|
|
113
|
+
* - `action` — `"login"` or `"logout"` (required)
|
|
114
|
+
*/
|
|
115
|
+
export declare function createSSOHandler(config: SSOConfig): {
|
|
116
|
+
GET: (request: Request) => Response | Promise<Response>;
|
|
117
|
+
};
|
|
118
|
+
export default createSSOHandler;
|
|
119
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAMH,yDAAyD;AACzD,MAAM,WAAW,eAAe;IAC9B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IACrC,gCAAgC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,MAAM,EAAE,KAAK,GAAG,QAAQ,CAAC;CAC1B;AAED,yDAAyD;AACzD,MAAM,MAAM,sBAAsB,GAAG,aAAa,GAAG,aAAa,CAAC;AAEnE,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE;QACP;;;;WAIG;QACH,KAAK,EAAE,eAAe,EAAE,CAAC;QACzB;;;WAGG;QACH,MAAM,EAAE,MAAM,EAAE,CAAC;KAClB,CAAC;IACF;;;;OAIG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,sBAAsB,CAAC;IAC7C;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IACrC;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,GAAG,QAAQ,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF;AAmMD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG;IACnD,GAAG,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACzD,CAqGA;AAGD,eAAe,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @tapbuy-public/sso
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic SSO handler for Tapbuy checkout.
|
|
5
|
+
* Decrypts an encrypted cookie payload and sets/deletes cookies accordingly.
|
|
6
|
+
*
|
|
7
|
+
* Works with any framework that uses the Web standard Request/Response API:
|
|
8
|
+
* Next.js (App Router), Nuxt, Remix, Deno, Bun, Cloudflare Workers, etc.
|
|
9
|
+
*
|
|
10
|
+
* Supported encryption algorithms:
|
|
11
|
+
* - **AES-256-GCM** (default): uses Web Crypto API — works everywhere.
|
|
12
|
+
* - **AES-256-ECB**: uses Node.js `crypto` module — requires Node.js runtime.
|
|
13
|
+
*
|
|
14
|
+
* The `token` query parameter contains an encrypted JSON payload
|
|
15
|
+
* describing which cookies to set/remove and their values.
|
|
16
|
+
* The encryption key must match the retailer's `encryption_key` on the API side.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // Next.js — app/api/tapbuy-sso/route.ts
|
|
20
|
+
* import { createSSOHandler } from '@tapbuy-public/sso'
|
|
21
|
+
*
|
|
22
|
+
* export const { GET } = createSSOHandler({
|
|
23
|
+
* cookies: {
|
|
24
|
+
* login: [
|
|
25
|
+
* { name: 'userId', httpOnly: true, path: '/', domain: '.example.com' },
|
|
26
|
+
* ],
|
|
27
|
+
* logout: ['userId'],
|
|
28
|
+
* },
|
|
29
|
+
* encryptionKey: process.env.TAPBUY_SSO_ENCRYPTION_KEY!,
|
|
30
|
+
* // encryptionAlgorithm: 'aes-256-ecb', // optional, default is 'aes-256-gcm'
|
|
31
|
+
* })
|
|
32
|
+
*/
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// 1x1 transparent GIF
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
/** Minimal 1×1 transparent GIF (43 bytes). */
|
|
37
|
+
const TRANSPARENT_GIF = new Uint8Array([
|
|
38
|
+
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, // GIF89a
|
|
39
|
+
0x01, 0x00, 0x01, 0x00, // 1×1
|
|
40
|
+
0x80, 0x00, 0x00, // GCT flag, 2 colors
|
|
41
|
+
0x00, 0x00, 0x00, // color 0: black
|
|
42
|
+
0xff, 0xff, 0xff, // color 1: white
|
|
43
|
+
0x21, 0xf9, 0x04, // GCE
|
|
44
|
+
0x01, 0x00, 0x00, 0x00, 0x00, // transparent index 0
|
|
45
|
+
0x2c, // image descriptor
|
|
46
|
+
0x00, 0x00, 0x00, 0x00, // left, top
|
|
47
|
+
0x01, 0x00, 0x01, 0x00, // width, height
|
|
48
|
+
0x00, // packed byte
|
|
49
|
+
0x02, 0x02, 0x44, 0x01, 0x00, // LZW min code size + data
|
|
50
|
+
0x3b, // trailer
|
|
51
|
+
]);
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Helpers
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
function serializeCookie(name, value, options) {
|
|
56
|
+
const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
|
|
57
|
+
if (options.path)
|
|
58
|
+
parts.push(`Path=${options.path}`);
|
|
59
|
+
if (options.domain)
|
|
60
|
+
parts.push(`Domain=${options.domain}`);
|
|
61
|
+
if (options.maxAge !== undefined)
|
|
62
|
+
parts.push(`Max-Age=${options.maxAge}`);
|
|
63
|
+
if (options.secure)
|
|
64
|
+
parts.push('Secure');
|
|
65
|
+
if (options.httpOnly)
|
|
66
|
+
parts.push('HttpOnly');
|
|
67
|
+
if (options.sameSite)
|
|
68
|
+
parts.push(`SameSite=${options.sameSite}`);
|
|
69
|
+
return parts.join('; ');
|
|
70
|
+
}
|
|
71
|
+
function matchesOrigin(origin, allowed) {
|
|
72
|
+
return allowed.some((pattern) => {
|
|
73
|
+
if (typeof pattern === 'string')
|
|
74
|
+
return origin === pattern;
|
|
75
|
+
pattern.lastIndex = 0;
|
|
76
|
+
return pattern.test(origin);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const VALID_ACTIONS = new Set(['set', 'remove']);
|
|
80
|
+
function isValidPayload(payload) {
|
|
81
|
+
if (!Array.isArray(payload))
|
|
82
|
+
return false;
|
|
83
|
+
return payload.every((item) => item != null &&
|
|
84
|
+
typeof item === 'object' &&
|
|
85
|
+
typeof item.name === 'string' &&
|
|
86
|
+
typeof item.value === 'string' &&
|
|
87
|
+
VALID_ACTIONS.has(item.action));
|
|
88
|
+
}
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Encrypted mode helpers
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
/**
|
|
93
|
+
* Decrypt an AES-256-GCM encrypted payload.
|
|
94
|
+
*
|
|
95
|
+
* The encrypted data format (matching PHP `encodeCartKey`):
|
|
96
|
+
* base64( iv[12 bytes] + tag[16 bytes] + cipherText )
|
|
97
|
+
*
|
|
98
|
+
* Web Crypto API expects: cipherText + tag (tag appended at the end).
|
|
99
|
+
*
|
|
100
|
+
* @param encryptedBase64 - Base64-encoded encrypted string from the `token` query param.
|
|
101
|
+
* @param encryptionKey - Shared secret key (will be padded/truncated to 32 bytes).
|
|
102
|
+
* @returns Parsed array of cookie payload items.
|
|
103
|
+
*/
|
|
104
|
+
async function decryptPayload(encryptedBase64, encryptionKey) {
|
|
105
|
+
// Pad/truncate key to 32 bytes — matches PHP: substr(str_pad($key, 32, "\0"), 0, 32)
|
|
106
|
+
const keyBytes = new Uint8Array(32);
|
|
107
|
+
const encoder = new TextEncoder();
|
|
108
|
+
const rawKey = encoder.encode(encryptionKey);
|
|
109
|
+
keyBytes.set(rawKey.subarray(0, 32));
|
|
110
|
+
// Decode base64
|
|
111
|
+
const binaryStr = atob(encryptedBase64);
|
|
112
|
+
const data = new Uint8Array(binaryStr.length);
|
|
113
|
+
for (let i = 0; i < binaryStr.length; i++) {
|
|
114
|
+
data[i] = binaryStr.charCodeAt(i);
|
|
115
|
+
}
|
|
116
|
+
// PHP format: iv(12) + tag(16) + cipherText
|
|
117
|
+
const iv = data.slice(0, 12);
|
|
118
|
+
const tag = data.slice(12, 28);
|
|
119
|
+
const cipherText = data.slice(28);
|
|
120
|
+
// Web Crypto expects: cipherText + tag (tag appended)
|
|
121
|
+
const combined = new Uint8Array(cipherText.length + tag.length);
|
|
122
|
+
combined.set(cipherText);
|
|
123
|
+
combined.set(tag, cipherText.length);
|
|
124
|
+
// Import key
|
|
125
|
+
const cryptoKey = await globalThis.crypto.subtle.importKey('raw', keyBytes, { name: 'AES-GCM' }, false, ['decrypt']);
|
|
126
|
+
// Decrypt
|
|
127
|
+
const decrypted = await globalThis.crypto.subtle.decrypt({ name: 'AES-GCM', iv, tagLength: 128 }, cryptoKey, combined);
|
|
128
|
+
const jsonStr = new TextDecoder().decode(decrypted);
|
|
129
|
+
return JSON.parse(jsonStr);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Decrypt an AES-256-ECB encrypted payload.
|
|
133
|
+
*
|
|
134
|
+
* ECB mode is not supported by the Web Crypto API, so this uses the Node.js
|
|
135
|
+
* `crypto` module. Works in Next.js, Nuxt, Remix, Deno, and Bun.
|
|
136
|
+
*
|
|
137
|
+
* The encrypted data format (matching PHP `phpseclib` AES ECB with PKCS7 padding):
|
|
138
|
+
* base64( AES-256-ECB-PKCS7(json) )
|
|
139
|
+
*
|
|
140
|
+
* @param encryptedBase64 - Base64-encoded encrypted string from the `token` query param.
|
|
141
|
+
* @param encryptionKey - Shared secret key (will be padded/truncated to 32 bytes).
|
|
142
|
+
* @returns Parsed array of cookie payload items.
|
|
143
|
+
*/
|
|
144
|
+
async function decryptPayloadECB(encryptedBase64, encryptionKey) {
|
|
145
|
+
// Pad/truncate key to 32 bytes — matches PHP: substr(str_pad($key, 32, "\0"), 0, 32)
|
|
146
|
+
const keyBytes = new Uint8Array(32);
|
|
147
|
+
const encoder = new TextEncoder();
|
|
148
|
+
const rawKey = encoder.encode(encryptionKey);
|
|
149
|
+
keyBytes.set(rawKey.subarray(0, 32));
|
|
150
|
+
// Decode base64
|
|
151
|
+
const binaryStr = atob(encryptedBase64);
|
|
152
|
+
const data = new Uint8Array(binaryStr.length);
|
|
153
|
+
for (let i = 0; i < binaryStr.length; i++) {
|
|
154
|
+
data[i] = binaryStr.charCodeAt(i);
|
|
155
|
+
}
|
|
156
|
+
// ECB mode is not supported by Web Crypto API.
|
|
157
|
+
// Use Node.js crypto module (available in Next.js, Nuxt, Remix, Deno, Bun).
|
|
158
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
159
|
+
let nodeCrypto;
|
|
160
|
+
try {
|
|
161
|
+
nodeCrypto = await import('crypto');
|
|
162
|
+
}
|
|
163
|
+
catch (_a) {
|
|
164
|
+
throw new Error('AES-256-ECB requires the Node.js crypto module. ' +
|
|
165
|
+
'Use AES-256-GCM for environments without Node.js (e.g. Cloudflare Workers).');
|
|
166
|
+
}
|
|
167
|
+
const decipher = nodeCrypto.createDecipheriv('aes-256-ecb', keyBytes, null);
|
|
168
|
+
const part1 = decipher.update(data);
|
|
169
|
+
const part2 = decipher.final();
|
|
170
|
+
const result = new Uint8Array(part1.length + part2.length);
|
|
171
|
+
result.set(part1);
|
|
172
|
+
result.set(part2, part1.length);
|
|
173
|
+
const jsonStr = new TextDecoder().decode(result);
|
|
174
|
+
return JSON.parse(jsonStr);
|
|
175
|
+
}
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// Handler factory
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
/**
|
|
180
|
+
* Create a SSO route handler.
|
|
181
|
+
*
|
|
182
|
+
* Returns an object with a `GET` method compatible with the Web standard
|
|
183
|
+
* `Request → Response` signature (Next.js App Router, etc.).
|
|
184
|
+
*
|
|
185
|
+
* Query parameters:
|
|
186
|
+
* - `token` — encrypted cookie payload, base64-encoded (required for login)
|
|
187
|
+
* - `action` — `"login"` or `"logout"` (required)
|
|
188
|
+
*/
|
|
189
|
+
export function createSSOHandler(config) {
|
|
190
|
+
const { cookies, encryptionKey, encryptionAlgorithm = 'aes-256-gcm', allowedOrigins = [], onComplete } = config;
|
|
191
|
+
async function GET(request) {
|
|
192
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
193
|
+
const url = new URL(request.url);
|
|
194
|
+
const token = url.searchParams.get('token');
|
|
195
|
+
const action = url.searchParams.get('action');
|
|
196
|
+
// Build common response headers
|
|
197
|
+
const headers = new Headers({
|
|
198
|
+
'Content-Type': 'image/gif',
|
|
199
|
+
'Cache-Control': 'no-store, no-cache, must-revalidate, private',
|
|
200
|
+
'Pragma': 'no-cache',
|
|
201
|
+
});
|
|
202
|
+
// CORS — only if an origin matches the allow-list
|
|
203
|
+
const origin = request.headers.get('Origin');
|
|
204
|
+
if (origin && allowedOrigins.length > 0 && matchesOrigin(origin, allowedOrigins)) {
|
|
205
|
+
headers.set('Access-Control-Allow-Origin', origin);
|
|
206
|
+
headers.set('Access-Control-Allow-Credentials', 'true');
|
|
207
|
+
}
|
|
208
|
+
// Validate action
|
|
209
|
+
if (action !== 'login' && action !== 'logout') {
|
|
210
|
+
return new Response(TRANSPARENT_GIF, { status: 400, headers });
|
|
211
|
+
}
|
|
212
|
+
// Login — set cookies
|
|
213
|
+
if (action === 'login') {
|
|
214
|
+
if (!token) {
|
|
215
|
+
return new Response(TRANSPARENT_GIF, { status: 400, headers });
|
|
216
|
+
}
|
|
217
|
+
// Decrypt the encrypted cookie payload
|
|
218
|
+
try {
|
|
219
|
+
const decrypt = encryptionAlgorithm === 'aes-256-ecb' ? decryptPayloadECB : decryptPayload;
|
|
220
|
+
const payload = await decrypt(token, encryptionKey);
|
|
221
|
+
if (!isValidPayload(payload)) {
|
|
222
|
+
return new Response(TRANSPARENT_GIF, { status: 400, headers });
|
|
223
|
+
}
|
|
224
|
+
for (const item of payload) {
|
|
225
|
+
// Only allow cookies explicitly listed in config.cookies.login
|
|
226
|
+
const cookieCfg = cookies.login.find((c) => c.name === item.name);
|
|
227
|
+
if (!cookieCfg)
|
|
228
|
+
continue;
|
|
229
|
+
if (item.action === 'set') {
|
|
230
|
+
const serialized = serializeCookie(item.name, item.value, {
|
|
231
|
+
httpOnly: (_a = cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.httpOnly) !== null && _a !== void 0 ? _a : true,
|
|
232
|
+
secure: (_b = cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.secure) !== null && _b !== void 0 ? _b : true,
|
|
233
|
+
sameSite: (_c = cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.sameSite) !== null && _c !== void 0 ? _c : 'Lax',
|
|
234
|
+
path: (_d = cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.path) !== null && _d !== void 0 ? _d : '/',
|
|
235
|
+
domain: cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.domain,
|
|
236
|
+
maxAge: (_e = cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.maxAge) !== null && _e !== void 0 ? _e : 86400,
|
|
237
|
+
});
|
|
238
|
+
headers.append('Set-Cookie', serialized);
|
|
239
|
+
}
|
|
240
|
+
else if (item.action === 'remove') {
|
|
241
|
+
const serialized = serializeCookie(item.name, '', {
|
|
242
|
+
httpOnly: (_f = cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.httpOnly) !== null && _f !== void 0 ? _f : true,
|
|
243
|
+
secure: (_g = cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.secure) !== null && _g !== void 0 ? _g : true,
|
|
244
|
+
sameSite: (_h = cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.sameSite) !== null && _h !== void 0 ? _h : 'Lax',
|
|
245
|
+
path: (_j = cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.path) !== null && _j !== void 0 ? _j : '/',
|
|
246
|
+
domain: cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.domain,
|
|
247
|
+
maxAge: 0,
|
|
248
|
+
});
|
|
249
|
+
headers.append('Set-Cookie', serialized);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (_p) {
|
|
254
|
+
// Decryption or JSON parse failed — bad token
|
|
255
|
+
return new Response(TRANSPARENT_GIF, { status: 400, headers });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Logout — expire cookies
|
|
259
|
+
if (action === 'logout') {
|
|
260
|
+
for (const cookieName of cookies.logout) {
|
|
261
|
+
// Find matching login config for path/domain, or use defaults
|
|
262
|
+
const loginCfg = cookies.login.find((c) => c.name === cookieName);
|
|
263
|
+
const serialized = serializeCookie(cookieName, '', {
|
|
264
|
+
httpOnly: (_k = loginCfg === null || loginCfg === void 0 ? void 0 : loginCfg.httpOnly) !== null && _k !== void 0 ? _k : true,
|
|
265
|
+
secure: (_l = loginCfg === null || loginCfg === void 0 ? void 0 : loginCfg.secure) !== null && _l !== void 0 ? _l : true,
|
|
266
|
+
sameSite: (_m = loginCfg === null || loginCfg === void 0 ? void 0 : loginCfg.sameSite) !== null && _m !== void 0 ? _m : 'Lax',
|
|
267
|
+
path: (_o = loginCfg === null || loginCfg === void 0 ? void 0 : loginCfg.path) !== null && _o !== void 0 ? _o : '/',
|
|
268
|
+
domain: loginCfg === null || loginCfg === void 0 ? void 0 : loginCfg.domain,
|
|
269
|
+
maxAge: 0, // Expire immediately
|
|
270
|
+
});
|
|
271
|
+
headers.append('Set-Cookie', serialized);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Optional side-effect callback
|
|
275
|
+
if (onComplete) {
|
|
276
|
+
await onComplete(action, request);
|
|
277
|
+
}
|
|
278
|
+
return new Response(TRANSPARENT_GIF, { status: 200, headers });
|
|
279
|
+
}
|
|
280
|
+
return { GET };
|
|
281
|
+
}
|
|
282
|
+
// Default export for convenience
|
|
283
|
+
export default createSSOHandler;
|
|
284
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @tapbuy-public/sso
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic SSO handler for Tapbuy checkout.
|
|
5
|
+
* Decrypts an encrypted cookie payload and sets/deletes cookies accordingly.
|
|
6
|
+
*
|
|
7
|
+
* Works with any framework that uses the Web standard Request/Response API:
|
|
8
|
+
* Next.js (App Router), Nuxt, Remix, Deno, Bun, Cloudflare Workers, etc.
|
|
9
|
+
*
|
|
10
|
+
* Supported encryption algorithms:
|
|
11
|
+
* - **AES-256-GCM** (default): uses Web Crypto API — works everywhere.
|
|
12
|
+
* - **AES-256-ECB**: uses Node.js `crypto` module — requires Node.js runtime.
|
|
13
|
+
*
|
|
14
|
+
* The `token` query parameter contains an encrypted JSON payload
|
|
15
|
+
* describing which cookies to set/remove and their values.
|
|
16
|
+
* The encryption key must match the retailer's `encryption_key` on the API side.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // Next.js — app/api/tapbuy-sso/route.ts
|
|
20
|
+
* import { createSSOHandler } from '@tapbuy-public/sso'
|
|
21
|
+
*
|
|
22
|
+
* export const { GET } = createSSOHandler({
|
|
23
|
+
* cookies: {
|
|
24
|
+
* login: [
|
|
25
|
+
* { name: 'userId', httpOnly: true, path: '/', domain: '.example.com' },
|
|
26
|
+
* ],
|
|
27
|
+
* logout: ['userId'],
|
|
28
|
+
* },
|
|
29
|
+
* encryptionKey: process.env.TAPBUY_SSO_ENCRYPTION_KEY!,
|
|
30
|
+
* // encryptionAlgorithm: 'aes-256-ecb', // optional, default is 'aes-256-gcm'
|
|
31
|
+
* })
|
|
32
|
+
*/
|
|
33
|
+
/** Configuration for a single cookie to set on login. */
|
|
34
|
+
export interface SSOCookieConfig {
|
|
35
|
+
/** Cookie name (e.g. "userId", "userToken"). */
|
|
36
|
+
name: string;
|
|
37
|
+
/** Whether the cookie is inaccessible to JavaScript. @default true */
|
|
38
|
+
httpOnly?: boolean;
|
|
39
|
+
/** Only send cookie over HTTPS. @default true */
|
|
40
|
+
secure?: boolean;
|
|
41
|
+
/** SameSite attribute. @default "Lax" */
|
|
42
|
+
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
43
|
+
/** Cookie path. @default "/" */
|
|
44
|
+
path?: string;
|
|
45
|
+
/** Cookie domain (e.g. ".website.com"). If omitted, defaults to the request host. */
|
|
46
|
+
domain?: string;
|
|
47
|
+
/** Cookie max-age in seconds. @default 86400 (24 h) */
|
|
48
|
+
maxAge?: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A single cookie operation from the encrypted payload.
|
|
52
|
+
* The API builds an array of these, encrypts it, and passes it as the `token` query param.
|
|
53
|
+
*/
|
|
54
|
+
export interface SSOCookiePayloadItem {
|
|
55
|
+
/** Cookie name. Must match a name in `cookies.login` for security options. */
|
|
56
|
+
name: string;
|
|
57
|
+
/** Cookie value to set. */
|
|
58
|
+
value: string;
|
|
59
|
+
/** Whether to set or remove (expire) this cookie. */
|
|
60
|
+
action: 'set' | 'remove';
|
|
61
|
+
}
|
|
62
|
+
/** Supported encryption algorithms for the SSO token. */
|
|
63
|
+
export type SSOEncryptionAlgorithm = 'aes-256-gcm' | 'aes-256-ecb';
|
|
64
|
+
/** Main configuration object for the SSO handler. */
|
|
65
|
+
export interface SSOConfig {
|
|
66
|
+
cookies: {
|
|
67
|
+
/**
|
|
68
|
+
* Cookie security options for login.
|
|
69
|
+
* Defines httpOnly, secure, sameSite, path, domain, maxAge for each cookie.
|
|
70
|
+
* Cookie names and values come from the encrypted payload.
|
|
71
|
+
*/
|
|
72
|
+
login: SSOCookieConfig[];
|
|
73
|
+
/**
|
|
74
|
+
* Cookie names to delete when `action=logout`.
|
|
75
|
+
* They are expired by setting `Max-Age=0`.
|
|
76
|
+
*/
|
|
77
|
+
logout: string[];
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* AES-256-GCM encryption key.
|
|
81
|
+
* Must match the `encryption_key` retailer config on the API side.
|
|
82
|
+
* The `token` query param is decrypted using this key to get the cookie payload.
|
|
83
|
+
*/
|
|
84
|
+
encryptionKey: string;
|
|
85
|
+
/**
|
|
86
|
+
* Encryption algorithm used for the `token` query parameter.
|
|
87
|
+
* - `'aes-256-gcm'` (default): Web Crypto API — works everywhere.
|
|
88
|
+
* - `'aes-256-ecb'`: Node.js `crypto` module — requires Node.js runtime.
|
|
89
|
+
* @default 'aes-256-gcm'
|
|
90
|
+
*/
|
|
91
|
+
encryptionAlgorithm?: SSOEncryptionAlgorithm;
|
|
92
|
+
/**
|
|
93
|
+
* Optional list of allowed origins for CORS.
|
|
94
|
+
* When provided, the handler adds `Access-Control-Allow-Origin` for matching origins.
|
|
95
|
+
* Supports exact strings or RegExp patterns.
|
|
96
|
+
* @default [] (no CORS headers)
|
|
97
|
+
*/
|
|
98
|
+
allowedOrigins?: (string | RegExp)[];
|
|
99
|
+
/**
|
|
100
|
+
* Optional callback fired after cookies are set/deleted, before the response is returned.
|
|
101
|
+
* Useful for side-effects like logging or analytics.
|
|
102
|
+
*/
|
|
103
|
+
onComplete?: (action: 'login' | 'logout', request: Request) => void | Promise<void>;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Create a SSO route handler.
|
|
107
|
+
*
|
|
108
|
+
* Returns an object with a `GET` method compatible with the Web standard
|
|
109
|
+
* `Request → Response` signature (Next.js App Router, etc.).
|
|
110
|
+
*
|
|
111
|
+
* Query parameters:
|
|
112
|
+
* - `token` — encrypted cookie payload, base64-encoded (required for login)
|
|
113
|
+
* - `action` — `"login"` or `"logout"` (required)
|
|
114
|
+
*/
|
|
115
|
+
export declare function createSSOHandler(config: SSOConfig): {
|
|
116
|
+
GET: (request: Request) => Response | Promise<Response>;
|
|
117
|
+
};
|
|
118
|
+
export default createSSOHandler;
|
|
119
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAMH,yDAAyD;AACzD,MAAM,WAAW,eAAe;IAC9B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IACrC,gCAAgC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,MAAM,EAAE,KAAK,GAAG,QAAQ,CAAC;CAC1B;AAED,yDAAyD;AACzD,MAAM,MAAM,sBAAsB,GAAG,aAAa,GAAG,aAAa,CAAC;AAEnE,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE;QACP;;;;WAIG;QACH,KAAK,EAAE,eAAe,EAAE,CAAC;QACzB;;;WAGG;QACH,MAAM,EAAE,MAAM,EAAE,CAAC;KAClB,CAAC;IACF;;;;OAIG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,sBAAsB,CAAC;IAC7C;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IACrC;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,GAAG,QAAQ,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF;AAmMD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG;IACnD,GAAG,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACzD,CAqGA;AAGD,eAAe,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @tapbuy-public/sso
|
|
4
|
+
*
|
|
5
|
+
* Framework-agnostic SSO handler for Tapbuy checkout.
|
|
6
|
+
* Decrypts an encrypted cookie payload and sets/deletes cookies accordingly.
|
|
7
|
+
*
|
|
8
|
+
* Works with any framework that uses the Web standard Request/Response API:
|
|
9
|
+
* Next.js (App Router), Nuxt, Remix, Deno, Bun, Cloudflare Workers, etc.
|
|
10
|
+
*
|
|
11
|
+
* Supported encryption algorithms:
|
|
12
|
+
* - **AES-256-GCM** (default): uses Web Crypto API — works everywhere.
|
|
13
|
+
* - **AES-256-ECB**: uses Node.js `crypto` module — requires Node.js runtime.
|
|
14
|
+
*
|
|
15
|
+
* The `token` query parameter contains an encrypted JSON payload
|
|
16
|
+
* describing which cookies to set/remove and their values.
|
|
17
|
+
* The encryption key must match the retailer's `encryption_key` on the API side.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // Next.js — app/api/tapbuy-sso/route.ts
|
|
21
|
+
* import { createSSOHandler } from '@tapbuy-public/sso'
|
|
22
|
+
*
|
|
23
|
+
* export const { GET } = createSSOHandler({
|
|
24
|
+
* cookies: {
|
|
25
|
+
* login: [
|
|
26
|
+
* { name: 'userId', httpOnly: true, path: '/', domain: '.example.com' },
|
|
27
|
+
* ],
|
|
28
|
+
* logout: ['userId'],
|
|
29
|
+
* },
|
|
30
|
+
* encryptionKey: process.env.TAPBUY_SSO_ENCRYPTION_KEY!,
|
|
31
|
+
* // encryptionAlgorithm: 'aes-256-ecb', // optional, default is 'aes-256-gcm'
|
|
32
|
+
* })
|
|
33
|
+
*/
|
|
34
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
35
|
+
if (k2 === undefined) k2 = k;
|
|
36
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
37
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
38
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
39
|
+
}
|
|
40
|
+
Object.defineProperty(o, k2, desc);
|
|
41
|
+
}) : (function(o, m, k, k2) {
|
|
42
|
+
if (k2 === undefined) k2 = k;
|
|
43
|
+
o[k2] = m[k];
|
|
44
|
+
}));
|
|
45
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
46
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
47
|
+
}) : function(o, v) {
|
|
48
|
+
o["default"] = v;
|
|
49
|
+
});
|
|
50
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
51
|
+
var ownKeys = function(o) {
|
|
52
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
53
|
+
var ar = [];
|
|
54
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
55
|
+
return ar;
|
|
56
|
+
};
|
|
57
|
+
return ownKeys(o);
|
|
58
|
+
};
|
|
59
|
+
return function (mod) {
|
|
60
|
+
if (mod && mod.__esModule) return mod;
|
|
61
|
+
var result = {};
|
|
62
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
63
|
+
__setModuleDefault(result, mod);
|
|
64
|
+
return result;
|
|
65
|
+
};
|
|
66
|
+
})();
|
|
67
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
68
|
+
exports.createSSOHandler = createSSOHandler;
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// 1x1 transparent GIF
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
/** Minimal 1×1 transparent GIF (43 bytes). */
|
|
73
|
+
const TRANSPARENT_GIF = new Uint8Array([
|
|
74
|
+
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, // GIF89a
|
|
75
|
+
0x01, 0x00, 0x01, 0x00, // 1×1
|
|
76
|
+
0x80, 0x00, 0x00, // GCT flag, 2 colors
|
|
77
|
+
0x00, 0x00, 0x00, // color 0: black
|
|
78
|
+
0xff, 0xff, 0xff, // color 1: white
|
|
79
|
+
0x21, 0xf9, 0x04, // GCE
|
|
80
|
+
0x01, 0x00, 0x00, 0x00, 0x00, // transparent index 0
|
|
81
|
+
0x2c, // image descriptor
|
|
82
|
+
0x00, 0x00, 0x00, 0x00, // left, top
|
|
83
|
+
0x01, 0x00, 0x01, 0x00, // width, height
|
|
84
|
+
0x00, // packed byte
|
|
85
|
+
0x02, 0x02, 0x44, 0x01, 0x00, // LZW min code size + data
|
|
86
|
+
0x3b, // trailer
|
|
87
|
+
]);
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Helpers
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
function serializeCookie(name, value, options) {
|
|
92
|
+
const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
|
|
93
|
+
if (options.path)
|
|
94
|
+
parts.push(`Path=${options.path}`);
|
|
95
|
+
if (options.domain)
|
|
96
|
+
parts.push(`Domain=${options.domain}`);
|
|
97
|
+
if (options.maxAge !== undefined)
|
|
98
|
+
parts.push(`Max-Age=${options.maxAge}`);
|
|
99
|
+
if (options.secure)
|
|
100
|
+
parts.push('Secure');
|
|
101
|
+
if (options.httpOnly)
|
|
102
|
+
parts.push('HttpOnly');
|
|
103
|
+
if (options.sameSite)
|
|
104
|
+
parts.push(`SameSite=${options.sameSite}`);
|
|
105
|
+
return parts.join('; ');
|
|
106
|
+
}
|
|
107
|
+
function matchesOrigin(origin, allowed) {
|
|
108
|
+
return allowed.some((pattern) => {
|
|
109
|
+
if (typeof pattern === 'string')
|
|
110
|
+
return origin === pattern;
|
|
111
|
+
pattern.lastIndex = 0;
|
|
112
|
+
return pattern.test(origin);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
const VALID_ACTIONS = new Set(['set', 'remove']);
|
|
116
|
+
function isValidPayload(payload) {
|
|
117
|
+
if (!Array.isArray(payload))
|
|
118
|
+
return false;
|
|
119
|
+
return payload.every((item) => item != null &&
|
|
120
|
+
typeof item === 'object' &&
|
|
121
|
+
typeof item.name === 'string' &&
|
|
122
|
+
typeof item.value === 'string' &&
|
|
123
|
+
VALID_ACTIONS.has(item.action));
|
|
124
|
+
}
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Encrypted mode helpers
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
/**
|
|
129
|
+
* Decrypt an AES-256-GCM encrypted payload.
|
|
130
|
+
*
|
|
131
|
+
* The encrypted data format (matching PHP `encodeCartKey`):
|
|
132
|
+
* base64( iv[12 bytes] + tag[16 bytes] + cipherText )
|
|
133
|
+
*
|
|
134
|
+
* Web Crypto API expects: cipherText + tag (tag appended at the end).
|
|
135
|
+
*
|
|
136
|
+
* @param encryptedBase64 - Base64-encoded encrypted string from the `token` query param.
|
|
137
|
+
* @param encryptionKey - Shared secret key (will be padded/truncated to 32 bytes).
|
|
138
|
+
* @returns Parsed array of cookie payload items.
|
|
139
|
+
*/
|
|
140
|
+
async function decryptPayload(encryptedBase64, encryptionKey) {
|
|
141
|
+
// Pad/truncate key to 32 bytes — matches PHP: substr(str_pad($key, 32, "\0"), 0, 32)
|
|
142
|
+
const keyBytes = new Uint8Array(32);
|
|
143
|
+
const encoder = new TextEncoder();
|
|
144
|
+
const rawKey = encoder.encode(encryptionKey);
|
|
145
|
+
keyBytes.set(rawKey.subarray(0, 32));
|
|
146
|
+
// Decode base64
|
|
147
|
+
const binaryStr = atob(encryptedBase64);
|
|
148
|
+
const data = new Uint8Array(binaryStr.length);
|
|
149
|
+
for (let i = 0; i < binaryStr.length; i++) {
|
|
150
|
+
data[i] = binaryStr.charCodeAt(i);
|
|
151
|
+
}
|
|
152
|
+
// PHP format: iv(12) + tag(16) + cipherText
|
|
153
|
+
const iv = data.slice(0, 12);
|
|
154
|
+
const tag = data.slice(12, 28);
|
|
155
|
+
const cipherText = data.slice(28);
|
|
156
|
+
// Web Crypto expects: cipherText + tag (tag appended)
|
|
157
|
+
const combined = new Uint8Array(cipherText.length + tag.length);
|
|
158
|
+
combined.set(cipherText);
|
|
159
|
+
combined.set(tag, cipherText.length);
|
|
160
|
+
// Import key
|
|
161
|
+
const cryptoKey = await globalThis.crypto.subtle.importKey('raw', keyBytes, { name: 'AES-GCM' }, false, ['decrypt']);
|
|
162
|
+
// Decrypt
|
|
163
|
+
const decrypted = await globalThis.crypto.subtle.decrypt({ name: 'AES-GCM', iv, tagLength: 128 }, cryptoKey, combined);
|
|
164
|
+
const jsonStr = new TextDecoder().decode(decrypted);
|
|
165
|
+
return JSON.parse(jsonStr);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Decrypt an AES-256-ECB encrypted payload.
|
|
169
|
+
*
|
|
170
|
+
* ECB mode is not supported by the Web Crypto API, so this uses the Node.js
|
|
171
|
+
* `crypto` module. Works in Next.js, Nuxt, Remix, Deno, and Bun.
|
|
172
|
+
*
|
|
173
|
+
* The encrypted data format (matching PHP `phpseclib` AES ECB with PKCS7 padding):
|
|
174
|
+
* base64( AES-256-ECB-PKCS7(json) )
|
|
175
|
+
*
|
|
176
|
+
* @param encryptedBase64 - Base64-encoded encrypted string from the `token` query param.
|
|
177
|
+
* @param encryptionKey - Shared secret key (will be padded/truncated to 32 bytes).
|
|
178
|
+
* @returns Parsed array of cookie payload items.
|
|
179
|
+
*/
|
|
180
|
+
async function decryptPayloadECB(encryptedBase64, encryptionKey) {
|
|
181
|
+
// Pad/truncate key to 32 bytes — matches PHP: substr(str_pad($key, 32, "\0"), 0, 32)
|
|
182
|
+
const keyBytes = new Uint8Array(32);
|
|
183
|
+
const encoder = new TextEncoder();
|
|
184
|
+
const rawKey = encoder.encode(encryptionKey);
|
|
185
|
+
keyBytes.set(rawKey.subarray(0, 32));
|
|
186
|
+
// Decode base64
|
|
187
|
+
const binaryStr = atob(encryptedBase64);
|
|
188
|
+
const data = new Uint8Array(binaryStr.length);
|
|
189
|
+
for (let i = 0; i < binaryStr.length; i++) {
|
|
190
|
+
data[i] = binaryStr.charCodeAt(i);
|
|
191
|
+
}
|
|
192
|
+
// ECB mode is not supported by Web Crypto API.
|
|
193
|
+
// Use Node.js crypto module (available in Next.js, Nuxt, Remix, Deno, Bun).
|
|
194
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
195
|
+
let nodeCrypto;
|
|
196
|
+
try {
|
|
197
|
+
nodeCrypto = await Promise.resolve().then(() => __importStar(require('crypto')));
|
|
198
|
+
}
|
|
199
|
+
catch (_a) {
|
|
200
|
+
throw new Error('AES-256-ECB requires the Node.js crypto module. ' +
|
|
201
|
+
'Use AES-256-GCM for environments without Node.js (e.g. Cloudflare Workers).');
|
|
202
|
+
}
|
|
203
|
+
const decipher = nodeCrypto.createDecipheriv('aes-256-ecb', keyBytes, null);
|
|
204
|
+
const part1 = decipher.update(data);
|
|
205
|
+
const part2 = decipher.final();
|
|
206
|
+
const result = new Uint8Array(part1.length + part2.length);
|
|
207
|
+
result.set(part1);
|
|
208
|
+
result.set(part2, part1.length);
|
|
209
|
+
const jsonStr = new TextDecoder().decode(result);
|
|
210
|
+
return JSON.parse(jsonStr);
|
|
211
|
+
}
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Handler factory
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
/**
|
|
216
|
+
* Create a SSO route handler.
|
|
217
|
+
*
|
|
218
|
+
* Returns an object with a `GET` method compatible with the Web standard
|
|
219
|
+
* `Request → Response` signature (Next.js App Router, etc.).
|
|
220
|
+
*
|
|
221
|
+
* Query parameters:
|
|
222
|
+
* - `token` — encrypted cookie payload, base64-encoded (required for login)
|
|
223
|
+
* - `action` — `"login"` or `"logout"` (required)
|
|
224
|
+
*/
|
|
225
|
+
function createSSOHandler(config) {
|
|
226
|
+
const { cookies, encryptionKey, encryptionAlgorithm = 'aes-256-gcm', allowedOrigins = [], onComplete } = config;
|
|
227
|
+
async function GET(request) {
|
|
228
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
229
|
+
const url = new URL(request.url);
|
|
230
|
+
const token = url.searchParams.get('token');
|
|
231
|
+
const action = url.searchParams.get('action');
|
|
232
|
+
// Build common response headers
|
|
233
|
+
const headers = new Headers({
|
|
234
|
+
'Content-Type': 'image/gif',
|
|
235
|
+
'Cache-Control': 'no-store, no-cache, must-revalidate, private',
|
|
236
|
+
'Pragma': 'no-cache',
|
|
237
|
+
});
|
|
238
|
+
// CORS — only if an origin matches the allow-list
|
|
239
|
+
const origin = request.headers.get('Origin');
|
|
240
|
+
if (origin && allowedOrigins.length > 0 && matchesOrigin(origin, allowedOrigins)) {
|
|
241
|
+
headers.set('Access-Control-Allow-Origin', origin);
|
|
242
|
+
headers.set('Access-Control-Allow-Credentials', 'true');
|
|
243
|
+
}
|
|
244
|
+
// Validate action
|
|
245
|
+
if (action !== 'login' && action !== 'logout') {
|
|
246
|
+
return new Response(TRANSPARENT_GIF, { status: 400, headers });
|
|
247
|
+
}
|
|
248
|
+
// Login — set cookies
|
|
249
|
+
if (action === 'login') {
|
|
250
|
+
if (!token) {
|
|
251
|
+
return new Response(TRANSPARENT_GIF, { status: 400, headers });
|
|
252
|
+
}
|
|
253
|
+
// Decrypt the encrypted cookie payload
|
|
254
|
+
try {
|
|
255
|
+
const decrypt = encryptionAlgorithm === 'aes-256-ecb' ? decryptPayloadECB : decryptPayload;
|
|
256
|
+
const payload = await decrypt(token, encryptionKey);
|
|
257
|
+
if (!isValidPayload(payload)) {
|
|
258
|
+
return new Response(TRANSPARENT_GIF, { status: 400, headers });
|
|
259
|
+
}
|
|
260
|
+
for (const item of payload) {
|
|
261
|
+
// Only allow cookies explicitly listed in config.cookies.login
|
|
262
|
+
const cookieCfg = cookies.login.find((c) => c.name === item.name);
|
|
263
|
+
if (!cookieCfg)
|
|
264
|
+
continue;
|
|
265
|
+
if (item.action === 'set') {
|
|
266
|
+
const serialized = serializeCookie(item.name, item.value, {
|
|
267
|
+
httpOnly: (_a = cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.httpOnly) !== null && _a !== void 0 ? _a : true,
|
|
268
|
+
secure: (_b = cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.secure) !== null && _b !== void 0 ? _b : true,
|
|
269
|
+
sameSite: (_c = cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.sameSite) !== null && _c !== void 0 ? _c : 'Lax',
|
|
270
|
+
path: (_d = cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.path) !== null && _d !== void 0 ? _d : '/',
|
|
271
|
+
domain: cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.domain,
|
|
272
|
+
maxAge: (_e = cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.maxAge) !== null && _e !== void 0 ? _e : 86400,
|
|
273
|
+
});
|
|
274
|
+
headers.append('Set-Cookie', serialized);
|
|
275
|
+
}
|
|
276
|
+
else if (item.action === 'remove') {
|
|
277
|
+
const serialized = serializeCookie(item.name, '', {
|
|
278
|
+
httpOnly: (_f = cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.httpOnly) !== null && _f !== void 0 ? _f : true,
|
|
279
|
+
secure: (_g = cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.secure) !== null && _g !== void 0 ? _g : true,
|
|
280
|
+
sameSite: (_h = cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.sameSite) !== null && _h !== void 0 ? _h : 'Lax',
|
|
281
|
+
path: (_j = cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.path) !== null && _j !== void 0 ? _j : '/',
|
|
282
|
+
domain: cookieCfg === null || cookieCfg === void 0 ? void 0 : cookieCfg.domain,
|
|
283
|
+
maxAge: 0,
|
|
284
|
+
});
|
|
285
|
+
headers.append('Set-Cookie', serialized);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch (_p) {
|
|
290
|
+
// Decryption or JSON parse failed — bad token
|
|
291
|
+
return new Response(TRANSPARENT_GIF, { status: 400, headers });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// Logout — expire cookies
|
|
295
|
+
if (action === 'logout') {
|
|
296
|
+
for (const cookieName of cookies.logout) {
|
|
297
|
+
// Find matching login config for path/domain, or use defaults
|
|
298
|
+
const loginCfg = cookies.login.find((c) => c.name === cookieName);
|
|
299
|
+
const serialized = serializeCookie(cookieName, '', {
|
|
300
|
+
httpOnly: (_k = loginCfg === null || loginCfg === void 0 ? void 0 : loginCfg.httpOnly) !== null && _k !== void 0 ? _k : true,
|
|
301
|
+
secure: (_l = loginCfg === null || loginCfg === void 0 ? void 0 : loginCfg.secure) !== null && _l !== void 0 ? _l : true,
|
|
302
|
+
sameSite: (_m = loginCfg === null || loginCfg === void 0 ? void 0 : loginCfg.sameSite) !== null && _m !== void 0 ? _m : 'Lax',
|
|
303
|
+
path: (_o = loginCfg === null || loginCfg === void 0 ? void 0 : loginCfg.path) !== null && _o !== void 0 ? _o : '/',
|
|
304
|
+
domain: loginCfg === null || loginCfg === void 0 ? void 0 : loginCfg.domain,
|
|
305
|
+
maxAge: 0, // Expire immediately
|
|
306
|
+
});
|
|
307
|
+
headers.append('Set-Cookie', serialized);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// Optional side-effect callback
|
|
311
|
+
if (onComplete) {
|
|
312
|
+
await onComplete(action, request);
|
|
313
|
+
}
|
|
314
|
+
return new Response(TRANSPARENT_GIF, { status: 200, headers });
|
|
315
|
+
}
|
|
316
|
+
return { GET };
|
|
317
|
+
}
|
|
318
|
+
// Default export for convenience
|
|
319
|
+
exports.default = createSSOHandler;
|
|
320
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tapbuy-public/sso",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Framework-agnostic SSO handler for Tapbuy checkout — decrypts an encrypted token from a query param and sets/deletes auth cookies accordingly",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "yarn clean && yarn build:cjs && yarn build:esm",
|
|
13
|
+
"build:cjs": "tsc",
|
|
14
|
+
"build:esm": "tsc --project tsconfig.esm.json --outDir dist/esm",
|
|
15
|
+
"clean": "rm -rf dist",
|
|
16
|
+
"dev": "tsc --watch",
|
|
17
|
+
"test": "jest",
|
|
18
|
+
"lint": "eslint \"**/*.ts\"",
|
|
19
|
+
"lint:fix": "eslint \"**/*.ts\" --fix",
|
|
20
|
+
"prepublishOnly": "yarn build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"sso",
|
|
24
|
+
"single-sign-on",
|
|
25
|
+
"tapbuy",
|
|
26
|
+
"checkout",
|
|
27
|
+
"auth",
|
|
28
|
+
"cookie",
|
|
29
|
+
"typescript"
|
|
30
|
+
],
|
|
31
|
+
"author": "Tapbuy",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/tapbuy/sso.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/tapbuy/sso/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/tapbuy/sso#readme",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/jest": "^29.5.0",
|
|
46
|
+
"@types/node": "^20.0.0",
|
|
47
|
+
"@typescript-eslint/eslint-plugin": "^8.44.1",
|
|
48
|
+
"@typescript-eslint/parser": "^8.44.1",
|
|
49
|
+
"eslint": "^8.0.0",
|
|
50
|
+
"jest": "^29.5.0",
|
|
51
|
+
"ts-jest": "^29.1.0",
|
|
52
|
+
"typescript": "^5.0.0"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"exports": {
|
|
58
|
+
".": {
|
|
59
|
+
"types": "./dist/index.d.ts",
|
|
60
|
+
"import": "./dist/esm/index.js",
|
|
61
|
+
"require": "./dist/index.js"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|