@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 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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"}
@@ -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":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6RH,4CAuGC;AAlTD,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E,8CAA8C;AAC9C,MAAM,eAAe,GAAG,IAAI,UAAU,CAAC;IACrC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS;IAC7C,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAc,MAAM;IAC1C,IAAI,EAAE,IAAI,EAAE,IAAI,EAAoB,qBAAqB;IACzD,IAAI,EAAE,IAAI,EAAE,IAAI,EAAoB,iBAAiB;IACrD,IAAI,EAAE,IAAI,EAAE,IAAI,EAAoB,iBAAiB;IACrD,IAAI,EAAE,IAAI,EAAE,IAAI,EAAoB,MAAM;IAC1C,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAQ,sBAAsB;IAC1D,IAAI,EAAgC,mBAAmB;IACvD,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAc,YAAY;IAChD,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAc,gBAAgB;IACpD,IAAI,EAAgC,cAAc;IAClD,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAQ,2BAA2B;IAC/D,IAAI,EAAgC,UAAU;CAC/C,CAAC,CAAC;AAEH,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,SAAS,eAAe,CACtB,IAAY,EACZ,KAAa,EACb,OAOC;IAED,MAAM,KAAK,GAAa,CAAC,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAErF,IAAI,OAAO,CAAC,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IACrD,IAAI,OAAO,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3D,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1E,IAAI,OAAO,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,OAAO,CAAC,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC7C,IAAI,OAAO,CAAC,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,YAAY,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAEjE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,aAAa,CAAC,MAAc,EAAE,OAA4B;IACjE,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9B,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,MAAM,KAAK,OAAO,CAAC;QAC3D,OAAO,CAAC,SAAS,GAAG,CAAC,CAAC;QACtB,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;AAEjD,SAAS,cAAc,CAAC,OAAgB;IACtC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1C,OAAO,OAAO,CAAC,KAAK,CAClB,CAAC,IAAI,EAAE,EAAE,CACP,IAAI,IAAI,IAAI;QACZ,OAAO,IAAI,KAAK,QAAQ;QACxB,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;QAC7B,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ;QAC9B,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CACjC,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,yBAAyB;AACzB,8EAA8E;AAE9E;;;;;;;;;;;GAWG;AACH,KAAK,UAAU,cAAc,CAC3B,eAAuB,EACvB,aAAqB;IAErB,qFAAqF;IACrF,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAC7C,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAErC,gBAAgB;IAChB,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC9C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,IAAI,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC;IAED,4CAA4C;IAC5C,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/B,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAElC,sDAAsD;IACtD,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAC,UAAU,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;IAChE,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACzB,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAErC,aAAa;IACb,MAAM,SAAS,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CACxD,KAAK,EACL,QAAQ,EACR,EAAE,IAAI,EAAE,SAAS,EAAE,EACnB,KAAK,EACL,CAAC,SAAS,CAAC,CACZ,CAAC;IAEF,UAAU;IACV,MAAM,SAAS,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CACtD,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,EACvC,SAAS,EACT,QAAQ,CACT,CAAC;IAEF,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AAC7B,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,KAAK,UAAU,iBAAiB,CAC9B,eAAuB,EACvB,aAAqB;IAErB,qFAAqF;IACrF,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAC7C,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAErC,gBAAgB;IAChB,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC9C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,IAAI,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC;IAED,+CAA+C;IAC/C,4EAA4E;IAC5E,8DAA8D;IAC9D,IAAI,UAAe,CAAC;IACpB,IAAI,CAAC;QACH,UAAU,GAAG,wDAAa,QAAQ,GAAC,CAAC;IACtC,CAAC;IAAC,WAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,kDAAkD;YAChD,6EAA6E,CAChF,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,UAAU,CAAC,gBAAgB,CAAC,aAAa,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC5E,MAAM,KAAK,GAAe,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,KAAK,GAAe,QAAQ,CAAC,KAAK,EAAE,CAAC;IAE3C,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3D,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAClB,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAEhC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACjD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AAC7B,CAAC;AAED,8EAA8E;AAC9E,kBAAkB;AAClB,8EAA8E;AAE9E;;;;;;;;;GASG;AACH,SAAgB,gBAAgB,CAAC,MAAiB;IAGhD,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,mBAAmB,GAAG,aAAa,EAAE,cAAc,GAAG,EAAE,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;IAEhH,KAAK,UAAU,GAAG,CAAC,OAAgB;;QACjC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAA8B,CAAC;QAE3E,gCAAgC;QAChC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC;YAC1B,cAAc,EAAE,WAAW;YAC3B,eAAe,EAAE,8CAA8C;YAC/D,QAAQ,EAAE,UAAU;SACrB,CAAC,CAAC;QAEH,kDAAkD;QAClD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7C,IAAI,MAAM,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,IAAI,aAAa,CAAC,MAAM,EAAE,cAAc,CAAC,EAAE,CAAC;YACjF,OAAO,CAAC,GAAG,CAAC,6BAA6B,EAAE,MAAM,CAAC,CAAC;YACnD,OAAO,CAAC,GAAG,CAAC,kCAAkC,EAAE,MAAM,CAAC,CAAC;QAC1D,CAAC;QAED,kBAAkB;QAClB,IAAI,MAAM,KAAK,OAAO,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC9C,OAAO,IAAI,QAAQ,CAAC,eAAe,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;QACjE,CAAC;QAED,sBAAsB;QACtB,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YACvB,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,IAAI,QAAQ,CAAC,eAAe,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;YACjE,CAAC;YAED,uCAAuC;YACvC,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,mBAAmB,KAAK,aAAa,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,cAAc,CAAC;gBAC3F,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;gBAEpD,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC7B,OAAO,IAAI,QAAQ,CAAC,eAAe,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;gBACjE,CAAC;gBAED,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;oBAC3B,+DAA+D;oBAC/D,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC;oBAClE,IAAI,CAAC,SAAS;wBAAE,SAAS;oBAEzB,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;wBAC1B,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE;4BACxD,QAAQ,EAAE,MAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,QAAQ,mCAAI,IAAI;4BACrC,MAAM,EAAE,MAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,MAAM,mCAAI,IAAI;4BACjC,QAAQ,EAAE,MAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,QAAQ,mCAAI,KAAK;4BACtC,IAAI,EAAE,MAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,IAAI,mCAAI,GAAG;4BAC5B,MAAM,EAAE,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,MAAM;4BACzB,MAAM,EAAE,MAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,MAAM,mCAAI,KAAK;yBACnC,CAAC,CAAC;wBACH,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;oBAC3C,CAAC;yBAAM,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;wBACpC,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE;4BAChD,QAAQ,EAAE,MAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,QAAQ,mCAAI,IAAI;4BACrC,MAAM,EAAE,MAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,MAAM,mCAAI,IAAI;4BACjC,QAAQ,EAAE,MAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,QAAQ,mCAAI,KAAK;4BACtC,IAAI,EAAE,MAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,IAAI,mCAAI,GAAG;4BAC5B,MAAM,EAAE,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,MAAM;4BACzB,MAAM,EAAE,CAAC;yBACV,CAAC,CAAC;wBACH,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;oBAC3C,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,WAAM,CAAC;gBACP,8CAA8C;gBAC9C,OAAO,IAAI,QAAQ,CAAC,eAAe,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;YACjE,CAAC;QACH,CAAC;QAED,0BAA0B;QAC1B,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;YACxB,KAAK,MAAM,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;gBACxC,8DAA8D;gBAC9D,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;gBAClE,MAAM,UAAU,GAAG,eAAe,CAAC,UAAU,EAAE,EAAE,EAAE;oBACjD,QAAQ,EAAE,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,QAAQ,mCAAI,IAAI;oBACpC,MAAM,EAAE,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,MAAM,mCAAI,IAAI;oBAChC,QAAQ,EAAE,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,QAAQ,mCAAI,KAAK;oBACrC,IAAI,EAAE,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,IAAI,mCAAI,GAAG;oBAC3B,MAAM,EAAE,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,MAAM;oBACxB,MAAM,EAAE,CAAC,EAAE,qBAAqB;iBACjC,CAAC,CAAC;gBACH,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC;QAED,gCAAgC;QAChC,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC,CAAC;QAED,OAAO,IAAI,QAAQ,CAAC,eAAe,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,OAAO,EAAE,GAAG,EAAE,CAAC;AACjB,CAAC;AAED,iCAAiC;AACjC,kBAAe,gBAAgB,CAAC"}
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
+ }