@open-loyalty/mcp-server 1.8.0 → 1.13.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/dist/config.d.ts +0 -9
- package/dist/config.js +0 -23
- package/dist/instructions.d.ts +1 -1
- package/dist/instructions.js +58 -22
- package/dist/tools/apps/rewards-catalog/handlers.js +2 -1
- package/dist/tools/campaign/index.d.ts +16 -3
- package/dist/tools/campaign/index.js +15 -4
- package/dist/tools/campaign/member-handlers.d.ts +12 -0
- package/dist/tools/campaign/member-handlers.js +33 -0
- package/dist/tools/campaign/schemas.d.ts +6 -0
- package/dist/tools/campaign/schemas.js +6 -0
- package/dist/tools/channel/handlers.d.ts +32 -0
- package/dist/tools/channel/handlers.js +130 -0
- package/dist/tools/channel/index.d.ts +68 -0
- package/dist/tools/channel/index.js +59 -0
- package/dist/tools/channel/schemas.d.ts +29 -0
- package/dist/tools/channel/schemas.js +30 -0
- package/dist/tools/context/handlers.d.ts +49 -0
- package/dist/tools/context/handlers.js +131 -0
- package/dist/tools/context/index.d.ts +15 -0
- package/dist/tools/context/index.js +20 -0
- package/dist/tools/context/schemas.d.ts +7 -0
- package/dist/tools/context/schemas.js +4 -0
- package/dist/tools/group-of-values/handlers.d.ts +39 -0
- package/dist/tools/group-of-values/handlers.js +133 -0
- package/dist/tools/group-of-values/index.d.ts +82 -0
- package/dist/tools/group-of-values/index.js +72 -0
- package/dist/tools/group-of-values/schemas.d.ts +36 -0
- package/dist/tools/group-of-values/schemas.js +39 -0
- package/dist/tools/index.js +12 -0
- package/dist/tools/language/handlers.d.ts +24 -0
- package/dist/tools/language/handlers.js +127 -0
- package/dist/tools/language/index.d.ts +64 -0
- package/dist/tools/language/index.js +60 -0
- package/dist/tools/language/schemas.d.ts +25 -0
- package/dist/tools/language/schemas.js +25 -0
- package/dist/tools/member/handlers.d.ts +4 -0
- package/dist/tools/member/handlers.js +27 -0
- package/dist/tools/member/index.d.ts +14 -2
- package/dist/tools/member/index.js +15 -2
- package/dist/tools/points/fraud-handlers.d.ts +21 -0
- package/dist/tools/points/fraud-handlers.js +96 -0
- package/dist/tools/points/index.d.ts +50 -1
- package/dist/tools/points/index.js +45 -2
- package/dist/tools/points/schemas.d.ts +11 -0
- package/dist/tools/points/schemas.js +11 -0
- package/dist/tools/reward/category-handlers.d.ts +27 -0
- package/dist/tools/reward/category-handlers.js +70 -0
- package/dist/tools/reward/handlers.d.ts +0 -12
- package/dist/tools/reward/handlers.js +0 -28
- package/dist/tools/reward/index.d.ts +76 -3
- package/dist/tools/reward/index.js +63 -4
- package/dist/tools/reward/photo-handlers.d.ts +10 -0
- package/dist/tools/reward/photo-handlers.js +97 -0
- package/dist/tools/reward/redemption-handlers.d.ts +23 -0
- package/dist/tools/reward/redemption-handlers.js +50 -0
- package/dist/tools/reward/schemas.d.ts +31 -0
- package/dist/tools/reward/schemas.js +33 -0
- package/dist/tools/segment/handlers.js +14 -10
- package/dist/tools/segment/index.js +1 -1
- package/dist/tools/segment/schemas.js +3 -3
- package/dist/tools/store/handlers.d.ts +24 -0
- package/dist/tools/store/handlers.js +29 -1
- package/dist/tools/store/index.d.ts +41 -3
- package/dist/tools/store/index.js +27 -4
- package/dist/tools/store/schemas.d.ts +24 -0
- package/dist/tools/store/schemas.js +24 -0
- package/package.json +2 -12
- package/dist/auth/provider.d.ts +0 -33
- package/dist/auth/provider.js +0 -383
- package/dist/auth/storage.d.ts +0 -16
- package/dist/auth/storage.js +0 -120
- package/dist/http.d.ts +0 -2
- package/dist/http.js +0 -319
package/dist/auth/provider.js
DELETED
|
@@ -1,383 +0,0 @@
|
|
|
1
|
-
import crypto from "crypto";
|
|
2
|
-
import { getStorage, KEYS } from "./storage.js";
|
|
3
|
-
// Expiration times
|
|
4
|
-
const AUTH_CODE_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
|
5
|
-
const ACCESS_TOKEN_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
6
|
-
const CLIENT_TTL_MS = 365 * 24 * 60 * 60 * 1000; // 1 year
|
|
7
|
-
const CONFIG_TTL_MS = 365 * 24 * 60 * 60 * 1000; // 1 year
|
|
8
|
-
/**
|
|
9
|
-
* Storage-backed OAuth clients store
|
|
10
|
-
*/
|
|
11
|
-
class StorageClientsStore {
|
|
12
|
-
async getClient(clientId) {
|
|
13
|
-
const storage = getStorage();
|
|
14
|
-
const client = await storage.get(KEYS.client(clientId));
|
|
15
|
-
return client ?? undefined;
|
|
16
|
-
}
|
|
17
|
-
async registerClient(client) {
|
|
18
|
-
const storage = getStorage();
|
|
19
|
-
const clientId = crypto.randomBytes(16).toString("hex");
|
|
20
|
-
const clientIdIssuedAt = Math.floor(Date.now() / 1000);
|
|
21
|
-
const fullClient = {
|
|
22
|
-
...client,
|
|
23
|
-
client_id: clientId,
|
|
24
|
-
client_id_issued_at: clientIdIssuedAt,
|
|
25
|
-
};
|
|
26
|
-
await storage.set(KEYS.client(clientId), fullClient, CLIENT_TTL_MS);
|
|
27
|
-
return fullClient;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Creates the OAuth server provider
|
|
32
|
-
*/
|
|
33
|
-
export function createOAuthProvider(issuerUrl) {
|
|
34
|
-
const clientsStore = new StorageClientsStore();
|
|
35
|
-
return {
|
|
36
|
-
get clientsStore() {
|
|
37
|
-
return clientsStore;
|
|
38
|
-
},
|
|
39
|
-
/**
|
|
40
|
-
* Handles authorization by showing a configuration form
|
|
41
|
-
*/
|
|
42
|
-
async authorize(client, params, res) {
|
|
43
|
-
const storage = getStorage();
|
|
44
|
-
// Generate session ID to track this authorization flow
|
|
45
|
-
const sessionId = crypto.randomBytes(16).toString("hex");
|
|
46
|
-
// Store the pending authorization
|
|
47
|
-
const sessionData = {
|
|
48
|
-
clientId: client.client_id,
|
|
49
|
-
redirectUri: params.redirectUri,
|
|
50
|
-
codeChallenge: params.codeChallenge,
|
|
51
|
-
state: params.state,
|
|
52
|
-
scope: params.scopes?.join(" "),
|
|
53
|
-
expiresAt: Date.now() + AUTH_CODE_TTL_MS,
|
|
54
|
-
};
|
|
55
|
-
await storage.set(KEYS.session(sessionId), sessionData, AUTH_CODE_TTL_MS);
|
|
56
|
-
// Render the configuration form
|
|
57
|
-
const html = renderAuthorizationForm({
|
|
58
|
-
sessionId,
|
|
59
|
-
state: params.state,
|
|
60
|
-
clientName: client.client_name || "ChatGPT",
|
|
61
|
-
issuerUrl,
|
|
62
|
-
});
|
|
63
|
-
res.setHeader("Content-Type", "text/html");
|
|
64
|
-
res.send(html);
|
|
65
|
-
},
|
|
66
|
-
/**
|
|
67
|
-
* Returns the code challenge for a given authorization code
|
|
68
|
-
*/
|
|
69
|
-
async challengeForAuthorizationCode(_client, authorizationCode) {
|
|
70
|
-
const storage = getStorage();
|
|
71
|
-
const codeData = await storage.get(KEYS.authCode(authorizationCode));
|
|
72
|
-
if (!codeData || codeData.expiresAt < Date.now()) {
|
|
73
|
-
await storage.delete(KEYS.authCode(authorizationCode));
|
|
74
|
-
throw new Error("Authorization code not found or expired");
|
|
75
|
-
}
|
|
76
|
-
return codeData.codeChallenge;
|
|
77
|
-
},
|
|
78
|
-
/**
|
|
79
|
-
* Exchanges authorization code for tokens
|
|
80
|
-
*/
|
|
81
|
-
async exchangeAuthorizationCode(client, authorizationCode) {
|
|
82
|
-
const storage = getStorage();
|
|
83
|
-
const codeData = await storage.get(KEYS.authCode(authorizationCode));
|
|
84
|
-
if (!codeData || codeData.expiresAt < Date.now()) {
|
|
85
|
-
await storage.delete(KEYS.authCode(authorizationCode));
|
|
86
|
-
throw new Error("Authorization code not found or expired");
|
|
87
|
-
}
|
|
88
|
-
if (codeData.clientId !== client.client_id) {
|
|
89
|
-
throw new Error("Authorization code was not issued to this client");
|
|
90
|
-
}
|
|
91
|
-
// Delete the code (one-time use)
|
|
92
|
-
await storage.delete(KEYS.authCode(authorizationCode));
|
|
93
|
-
// Store the client config if provided
|
|
94
|
-
if (codeData.pendingConfig) {
|
|
95
|
-
await storage.set(KEYS.config(client.client_id), codeData.pendingConfig, CONFIG_TTL_MS);
|
|
96
|
-
}
|
|
97
|
-
// Generate access token
|
|
98
|
-
const accessToken = crypto.randomBytes(32).toString("hex");
|
|
99
|
-
const expiresAt = Date.now() + ACCESS_TOKEN_TTL_MS;
|
|
100
|
-
const tokenData = {
|
|
101
|
-
clientId: client.client_id,
|
|
102
|
-
scope: codeData.scope,
|
|
103
|
-
expiresAt,
|
|
104
|
-
};
|
|
105
|
-
await storage.set(KEYS.token(accessToken), tokenData, ACCESS_TOKEN_TTL_MS);
|
|
106
|
-
return {
|
|
107
|
-
access_token: accessToken,
|
|
108
|
-
token_type: "Bearer",
|
|
109
|
-
expires_in: Math.floor(ACCESS_TOKEN_TTL_MS / 1000),
|
|
110
|
-
scope: codeData.scope,
|
|
111
|
-
};
|
|
112
|
-
},
|
|
113
|
-
/**
|
|
114
|
-
* Exchanges refresh token (not supported)
|
|
115
|
-
*/
|
|
116
|
-
async exchangeRefreshToken() {
|
|
117
|
-
throw new Error("Refresh tokens are not supported");
|
|
118
|
-
},
|
|
119
|
-
/**
|
|
120
|
-
* Verifies an access token
|
|
121
|
-
*/
|
|
122
|
-
async verifyAccessToken(token) {
|
|
123
|
-
const storage = getStorage();
|
|
124
|
-
const tokenData = await storage.get(KEYS.token(token));
|
|
125
|
-
if (!tokenData) {
|
|
126
|
-
throw new Error("Invalid access token");
|
|
127
|
-
}
|
|
128
|
-
if (tokenData.expiresAt < Date.now()) {
|
|
129
|
-
await storage.delete(KEYS.token(token));
|
|
130
|
-
throw new Error("Access token has expired");
|
|
131
|
-
}
|
|
132
|
-
return {
|
|
133
|
-
token,
|
|
134
|
-
clientId: tokenData.clientId,
|
|
135
|
-
scopes: tokenData.scope ? tokenData.scope.split(" ") : [],
|
|
136
|
-
expiresAt: Math.floor(tokenData.expiresAt / 1000),
|
|
137
|
-
};
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Completes authorization after form submission
|
|
143
|
-
*/
|
|
144
|
-
export async function completeAuthorization(sessionId, config) {
|
|
145
|
-
const storage = getStorage();
|
|
146
|
-
const sessionData = await storage.get(KEYS.session(sessionId));
|
|
147
|
-
if (!sessionData || sessionData.expiresAt < Date.now()) {
|
|
148
|
-
await storage.delete(KEYS.session(sessionId));
|
|
149
|
-
return { error: "Session expired. Please start the authorization process again." };
|
|
150
|
-
}
|
|
151
|
-
// Delete session
|
|
152
|
-
await storage.delete(KEYS.session(sessionId));
|
|
153
|
-
// Generate authorization code
|
|
154
|
-
const authorizationCode = crypto.randomBytes(32).toString("hex");
|
|
155
|
-
// Store with pending config
|
|
156
|
-
const codeData = {
|
|
157
|
-
...sessionData,
|
|
158
|
-
pendingConfig: config,
|
|
159
|
-
expiresAt: Date.now() + AUTH_CODE_TTL_MS,
|
|
160
|
-
};
|
|
161
|
-
await storage.set(KEYS.authCode(authorizationCode), codeData, AUTH_CODE_TTL_MS);
|
|
162
|
-
// Build redirect URL
|
|
163
|
-
const redirectUrl = new URL(sessionData.redirectUri);
|
|
164
|
-
redirectUrl.searchParams.set("code", authorizationCode);
|
|
165
|
-
if (sessionData.state) {
|
|
166
|
-
redirectUrl.searchParams.set("state", sessionData.state);
|
|
167
|
-
}
|
|
168
|
-
return { redirectUrl: redirectUrl.toString() };
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Gets the Open Loyalty config for a client
|
|
172
|
-
*/
|
|
173
|
-
export async function getClientConfig(clientId) {
|
|
174
|
-
const storage = getStorage();
|
|
175
|
-
const config = await storage.get(KEYS.config(clientId));
|
|
176
|
-
return config ?? undefined;
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Validates Open Loyalty credentials
|
|
180
|
-
* Uses the member list endpoint to validate both API token and store code
|
|
181
|
-
*/
|
|
182
|
-
export async function validateOpenLoyaltyCredentials(config) {
|
|
183
|
-
try {
|
|
184
|
-
// Use member list endpoint with limit=1 to validate credentials
|
|
185
|
-
// This validates both the API token and the store code existence
|
|
186
|
-
const response = await fetch(`${config.apiUrl}/${config.storeCode}/member?_itemsOnPage=1`, {
|
|
187
|
-
method: "GET",
|
|
188
|
-
headers: {
|
|
189
|
-
"Content-Type": "application/json",
|
|
190
|
-
"X-AUTH-TOKEN": config.apiToken,
|
|
191
|
-
},
|
|
192
|
-
});
|
|
193
|
-
if (response.status === 401) {
|
|
194
|
-
return { valid: false, error: "Invalid API token" };
|
|
195
|
-
}
|
|
196
|
-
if (response.status === 403) {
|
|
197
|
-
return { valid: false, error: "API token does not have required permissions" };
|
|
198
|
-
}
|
|
199
|
-
if (response.status === 404) {
|
|
200
|
-
return { valid: false, error: "Store code not found or invalid API URL" };
|
|
201
|
-
}
|
|
202
|
-
if (!response.ok) {
|
|
203
|
-
return { valid: false, error: `API returned status ${response.status}` };
|
|
204
|
-
}
|
|
205
|
-
return { valid: true };
|
|
206
|
-
}
|
|
207
|
-
catch (error) {
|
|
208
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
209
|
-
return { valid: false, error: `Failed to connect: ${message}` };
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
|
-
* Renders the authorization form HTML
|
|
214
|
-
*/
|
|
215
|
-
function renderAuthorizationForm(params) {
|
|
216
|
-
const { sessionId, state, clientName, issuerUrl } = params;
|
|
217
|
-
return `<!DOCTYPE html>
|
|
218
|
-
<html lang="en">
|
|
219
|
-
<head>
|
|
220
|
-
<meta charset="UTF-8">
|
|
221
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
222
|
-
<title>Connect to Open Loyalty</title>
|
|
223
|
-
<style>
|
|
224
|
-
* { box-sizing: border-box; }
|
|
225
|
-
body {
|
|
226
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
227
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
228
|
-
min-height: 100vh;
|
|
229
|
-
margin: 0;
|
|
230
|
-
padding: 20px;
|
|
231
|
-
display: flex;
|
|
232
|
-
justify-content: center;
|
|
233
|
-
align-items: center;
|
|
234
|
-
}
|
|
235
|
-
.container {
|
|
236
|
-
background: white;
|
|
237
|
-
border-radius: 16px;
|
|
238
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
239
|
-
padding: 40px;
|
|
240
|
-
width: 100%;
|
|
241
|
-
max-width: 420px;
|
|
242
|
-
}
|
|
243
|
-
h1 {
|
|
244
|
-
color: #1a1a2e;
|
|
245
|
-
font-size: 24px;
|
|
246
|
-
text-align: center;
|
|
247
|
-
margin: 0 0 8px 0;
|
|
248
|
-
}
|
|
249
|
-
.subtitle {
|
|
250
|
-
color: #6b7280;
|
|
251
|
-
text-align: center;
|
|
252
|
-
font-size: 14px;
|
|
253
|
-
margin-bottom: 32px;
|
|
254
|
-
}
|
|
255
|
-
.client-name { color: #667eea; font-weight: 500; }
|
|
256
|
-
.form-group { margin-bottom: 20px; }
|
|
257
|
-
label {
|
|
258
|
-
display: block;
|
|
259
|
-
color: #374151;
|
|
260
|
-
font-size: 14px;
|
|
261
|
-
font-weight: 500;
|
|
262
|
-
margin-bottom: 6px;
|
|
263
|
-
}
|
|
264
|
-
input {
|
|
265
|
-
width: 100%;
|
|
266
|
-
padding: 12px 16px;
|
|
267
|
-
border: 2px solid #e5e7eb;
|
|
268
|
-
border-radius: 8px;
|
|
269
|
-
font-size: 14px;
|
|
270
|
-
}
|
|
271
|
-
input:focus {
|
|
272
|
-
outline: none;
|
|
273
|
-
border-color: #667eea;
|
|
274
|
-
}
|
|
275
|
-
.help-text { color: #6b7280; font-size: 12px; margin-top: 4px; }
|
|
276
|
-
button {
|
|
277
|
-
width: 100%;
|
|
278
|
-
padding: 14px 24px;
|
|
279
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
280
|
-
color: white;
|
|
281
|
-
border: none;
|
|
282
|
-
border-radius: 8px;
|
|
283
|
-
font-size: 16px;
|
|
284
|
-
font-weight: 600;
|
|
285
|
-
cursor: pointer;
|
|
286
|
-
}
|
|
287
|
-
button:hover { opacity: 0.9; }
|
|
288
|
-
button:disabled { opacity: 0.7; cursor: not-allowed; }
|
|
289
|
-
.error { background: #fef2f2; border: 1px solid #fecaca; color: #dc2626; padding: 12px; border-radius: 8px; margin-bottom: 20px; display: none; }
|
|
290
|
-
.error.visible { display: block; }
|
|
291
|
-
</style>
|
|
292
|
-
</head>
|
|
293
|
-
<body>
|
|
294
|
-
<div class="container">
|
|
295
|
-
<h1>Connect to Open Loyalty</h1>
|
|
296
|
-
<p class="subtitle">
|
|
297
|
-
<span class="client-name">${escapeHtml(clientName)}</span> wants to access your Open Loyalty account
|
|
298
|
-
</p>
|
|
299
|
-
|
|
300
|
-
<div id="error" class="error"></div>
|
|
301
|
-
|
|
302
|
-
<form id="authForm">
|
|
303
|
-
<input type="hidden" name="session_id" value="${escapeHtml(sessionId)}">
|
|
304
|
-
${state ? `<input type="hidden" name="state" value="${escapeHtml(state)}">` : ""}
|
|
305
|
-
|
|
306
|
-
<div class="form-group">
|
|
307
|
-
<label for="apiUrl">API URL</label>
|
|
308
|
-
<input type="url" id="apiUrl" name="api_url" placeholder="https://api.openloyalty.io" required>
|
|
309
|
-
<p class="help-text">Your Open Loyalty API endpoint</p>
|
|
310
|
-
</div>
|
|
311
|
-
|
|
312
|
-
<div class="form-group">
|
|
313
|
-
<label for="apiToken">API Token</label>
|
|
314
|
-
<input type="password" id="apiToken" name="api_token" required>
|
|
315
|
-
<p class="help-text">From your Open Loyalty admin panel</p>
|
|
316
|
-
</div>
|
|
317
|
-
|
|
318
|
-
<div class="form-group">
|
|
319
|
-
<label for="storeCode">Store Code</label>
|
|
320
|
-
<input type="text" id="storeCode" name="store_code" value="default" required>
|
|
321
|
-
<p class="help-text">Usually "default"</p>
|
|
322
|
-
</div>
|
|
323
|
-
|
|
324
|
-
<button type="submit" id="submitBtn">Connect Account</button>
|
|
325
|
-
</form>
|
|
326
|
-
</div>
|
|
327
|
-
|
|
328
|
-
<script>
|
|
329
|
-
const form = document.getElementById('authForm');
|
|
330
|
-
const errorEl = document.getElementById('error');
|
|
331
|
-
const submitBtn = document.getElementById('submitBtn');
|
|
332
|
-
|
|
333
|
-
form.addEventListener('submit', async (e) => {
|
|
334
|
-
e.preventDefault();
|
|
335
|
-
errorEl.classList.remove('visible');
|
|
336
|
-
submitBtn.disabled = true;
|
|
337
|
-
submitBtn.textContent = 'Connecting...';
|
|
338
|
-
|
|
339
|
-
try {
|
|
340
|
-
const formData = new FormData(form);
|
|
341
|
-
const response = await fetch('${issuerUrl}/authorize/submit', {
|
|
342
|
-
method: 'POST',
|
|
343
|
-
headers: { 'Content-Type': 'application/json' },
|
|
344
|
-
body: JSON.stringify({
|
|
345
|
-
session_id: formData.get('session_id'),
|
|
346
|
-
state: formData.get('state'),
|
|
347
|
-
api_url: formData.get('api_url'),
|
|
348
|
-
api_token: formData.get('api_token'),
|
|
349
|
-
store_code: formData.get('store_code'),
|
|
350
|
-
}),
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
const result = await response.json();
|
|
354
|
-
|
|
355
|
-
if (result.redirect_url) {
|
|
356
|
-
window.location.href = result.redirect_url;
|
|
357
|
-
} else if (result.error) {
|
|
358
|
-
errorEl.textContent = result.error;
|
|
359
|
-
errorEl.classList.add('visible');
|
|
360
|
-
submitBtn.disabled = false;
|
|
361
|
-
submitBtn.textContent = 'Connect Account';
|
|
362
|
-
}
|
|
363
|
-
} catch (err) {
|
|
364
|
-
errorEl.textContent = 'Connection failed. Please try again.';
|
|
365
|
-
errorEl.classList.add('visible');
|
|
366
|
-
submitBtn.disabled = false;
|
|
367
|
-
submitBtn.textContent = 'Connect Account';
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
</script>
|
|
371
|
-
</body>
|
|
372
|
-
</html>`;
|
|
373
|
-
}
|
|
374
|
-
function escapeHtml(text) {
|
|
375
|
-
const escapes = {
|
|
376
|
-
"&": "&",
|
|
377
|
-
"<": "<",
|
|
378
|
-
">": ">",
|
|
379
|
-
'"': """,
|
|
380
|
-
"'": "'",
|
|
381
|
-
};
|
|
382
|
-
return text.replace(/[&<>"']/g, (c) => escapes[c]);
|
|
383
|
-
}
|
package/dist/auth/storage.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export interface StorageBackend {
|
|
2
|
-
get<T>(key: string): Promise<T | null>;
|
|
3
|
-
set<T>(key: string, value: T, ttlMs?: number): Promise<void>;
|
|
4
|
-
delete(key: string): Promise<void>;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Get the storage backend (Redis if available, otherwise in-memory)
|
|
8
|
-
*/
|
|
9
|
-
export declare function getStorage(): StorageBackend;
|
|
10
|
-
export declare const KEYS: {
|
|
11
|
-
client: (id: string) => string;
|
|
12
|
-
authCode: (code: string) => string;
|
|
13
|
-
session: (id: string) => string;
|
|
14
|
-
token: (token: string) => string;
|
|
15
|
-
config: (clientId: string) => string;
|
|
16
|
-
};
|
package/dist/auth/storage.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Storage abstraction for OAuth data
|
|
3
|
-
* Uses Redis if REDIS_URL is set, otherwise falls back to in-memory storage
|
|
4
|
-
*/
|
|
5
|
-
import { Redis } from "ioredis";
|
|
6
|
-
/**
|
|
7
|
-
* In-memory storage for local development
|
|
8
|
-
* Includes periodic cleanup to prevent memory leaks from expired entries
|
|
9
|
-
*/
|
|
10
|
-
class InMemoryStorage {
|
|
11
|
-
data = new Map();
|
|
12
|
-
cleanupInterval;
|
|
13
|
-
constructor() {
|
|
14
|
-
// Periodic cleanup every 5 minutes to remove expired entries
|
|
15
|
-
this.cleanupInterval = setInterval(() => this.cleanup(), 5 * 60 * 1000);
|
|
16
|
-
this.cleanupInterval.unref(); // Don't prevent process exit
|
|
17
|
-
}
|
|
18
|
-
cleanup() {
|
|
19
|
-
const now = Date.now();
|
|
20
|
-
for (const [key, entry] of this.data) {
|
|
21
|
-
if (entry.expiresAt && entry.expiresAt < now) {
|
|
22
|
-
this.data.delete(key);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
async get(key) {
|
|
27
|
-
const entry = this.data.get(key);
|
|
28
|
-
if (!entry)
|
|
29
|
-
return null;
|
|
30
|
-
if (entry.expiresAt && entry.expiresAt < Date.now()) {
|
|
31
|
-
this.data.delete(key);
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
return entry.value;
|
|
35
|
-
}
|
|
36
|
-
async set(key, value, ttlMs) {
|
|
37
|
-
this.data.set(key, {
|
|
38
|
-
value,
|
|
39
|
-
expiresAt: ttlMs ? Date.now() + ttlMs : undefined,
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
async delete(key) {
|
|
43
|
-
this.data.delete(key);
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Close the storage and clean up resources
|
|
47
|
-
*/
|
|
48
|
-
close() {
|
|
49
|
-
clearInterval(this.cleanupInterval);
|
|
50
|
-
this.data.clear();
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Redis storage for production
|
|
55
|
-
*/
|
|
56
|
-
class RedisStorage {
|
|
57
|
-
client;
|
|
58
|
-
constructor(redisUrl) {
|
|
59
|
-
this.client = new Redis(redisUrl, {
|
|
60
|
-
maxRetriesPerRequest: 3,
|
|
61
|
-
retryStrategy: (times) => Math.min(times * 100, 3000),
|
|
62
|
-
});
|
|
63
|
-
this.client.on("error", (err) => {
|
|
64
|
-
console.error("Redis connection error:", err.message);
|
|
65
|
-
});
|
|
66
|
-
this.client.on("connect", () => {
|
|
67
|
-
console.log("Connected to Redis");
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
async get(key) {
|
|
71
|
-
const data = await this.client.get(key);
|
|
72
|
-
if (!data)
|
|
73
|
-
return null;
|
|
74
|
-
try {
|
|
75
|
-
return JSON.parse(data);
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
async set(key, value, ttlMs) {
|
|
82
|
-
const data = JSON.stringify(value);
|
|
83
|
-
if (ttlMs) {
|
|
84
|
-
await this.client.set(key, data, "PX", ttlMs);
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
await this.client.set(key, data);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
async delete(key) {
|
|
91
|
-
await this.client.del(key);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
// Singleton storage instance
|
|
95
|
-
let storage = null;
|
|
96
|
-
/**
|
|
97
|
-
* Get the storage backend (Redis if available, otherwise in-memory)
|
|
98
|
-
*/
|
|
99
|
-
export function getStorage() {
|
|
100
|
-
if (storage)
|
|
101
|
-
return storage;
|
|
102
|
-
const redisUrl = process.env.REDIS_URL;
|
|
103
|
-
if (redisUrl) {
|
|
104
|
-
console.log("Using Redis for OAuth storage");
|
|
105
|
-
storage = new RedisStorage(redisUrl);
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
console.log("Using in-memory storage for OAuth (set REDIS_URL for persistence)");
|
|
109
|
-
storage = new InMemoryStorage();
|
|
110
|
-
}
|
|
111
|
-
return storage;
|
|
112
|
-
}
|
|
113
|
-
// Storage key prefixes
|
|
114
|
-
export const KEYS = {
|
|
115
|
-
client: (id) => `oauth:client:${id}`,
|
|
116
|
-
authCode: (code) => `oauth:code:${code}`,
|
|
117
|
-
session: (id) => `oauth:session:${id}`,
|
|
118
|
-
token: (token) => `oauth:token:${token}`,
|
|
119
|
-
config: (clientId) => `oauth:config:${clientId}`,
|
|
120
|
-
};
|
package/dist/http.d.ts
DELETED