effortless-aws 0.36.1 → 0.38.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 +27 -26
- package/dist/auth-YWMYFMBX.js +11 -0
- package/dist/bucket-client-FRWISKEB.js +9 -0
- package/dist/chunk-3NKYT4OX.js +268 -0
- package/dist/chunk-4UKWMWM5.js +113 -0
- package/dist/chunk-JFNBO4K3.js +243 -0
- package/dist/chunk-U56MLLWP.js +6 -0
- package/dist/chunk-UU3AKDJU.js +159 -0
- package/dist/chunk-VONNUUEN.js +16 -0
- package/dist/email-client-RMDQDOYI.js +34 -0
- package/dist/index.d.ts +106 -77
- package/dist/index.js +17 -4
- package/dist/index.js.map +1 -1
- package/dist/queue-client-WDAJYGQO.js +57 -0
- package/dist/runtime/wrap-api.js +14 -5
- package/dist/runtime/wrap-bucket.js +11 -4
- package/dist/runtime/wrap-cron.js +5 -2
- package/dist/runtime/wrap-mcp.js +5 -2
- package/dist/runtime/{wrap-fifo-queue.js → wrap-queue.js} +10 -7
- package/dist/runtime/wrap-table-stream.js +16 -9
- package/dist/runtime/wrap-worker.js +2 -1
- package/dist/ssm-client-2XDJC3HF.js +33 -0
- package/dist/table-client-BBLIC3EV.js +7 -0
- package/dist/worker-client-ZS25XCC2.js +69 -0
- package/package.json +1 -1
- package/dist/chunk-Q3ZDMZF3.js +0 -889
package/README.md
CHANGED
|
@@ -2,48 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/effortless-aws)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
You write handlers. The framework builds, bundles, provisions AWS resources, wires IAM permissions, and deploys — all from your TypeScript code.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
npm install effortless-aws
|
|
9
|
-
```
|
|
7
|
+
Your TypeScript is the single source of truth — for code and infrastructure.
|
|
10
8
|
|
|
11
|
-
##
|
|
9
|
+
## You write this
|
|
12
10
|
|
|
13
11
|
```typescript
|
|
14
|
-
import { defineApi } from "effortless-aws";
|
|
12
|
+
import { defineApi, defineTable } from "effortless-aws";
|
|
13
|
+
|
|
14
|
+
const db = defineTable<Order>();
|
|
15
15
|
|
|
16
|
-
export const
|
|
17
|
-
.
|
|
16
|
+
export const api = defineApi({ basePath: "/orders" })
|
|
17
|
+
.deps(() => ({ db }))
|
|
18
|
+
.get("/{id}", async ({ params, deps, ok }) => {
|
|
19
|
+
const order = await deps.db.get(params.id);
|
|
20
|
+
return ok(order);
|
|
21
|
+
});
|
|
18
22
|
```
|
|
19
23
|
|
|
20
|
-
##
|
|
24
|
+
## You run this
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
| `defineApp` | SSR framework deployment (Nuxt, Next.js) via CloudFront |
|
|
26
|
-
| `defineTable` | DynamoDB table with stream processing |
|
|
27
|
-
| `defineFifoQueue` | SQS FIFO queue consumer |
|
|
28
|
-
| `defineBucket` | S3 bucket with event triggers |
|
|
29
|
-
| `defineMailer` | SES email sending |
|
|
30
|
-
| `defineStaticSite` | CloudFront + S3 static site with optional middleware |
|
|
26
|
+
```bash
|
|
27
|
+
eff deploy
|
|
28
|
+
```
|
|
31
29
|
|
|
32
|
-
##
|
|
30
|
+
## The framework handles the rest
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
- **Typed everything** — `defineTable<Order>()` gives you typed `put()`, typed `deps.orders.get()`, typed `record.new`
|
|
36
|
-
- **Cross-handler deps** — `.deps(() => ({ orders }))` auto-wires IAM and injects a typed `TableClient`
|
|
37
|
-
- **SSM params** — `.config(({ defineSecret }) => ...)` fetches secrets from Parameter Store at cold start
|
|
38
|
-
- **Static files** — `static: ["templates/*.ejs"]` bundles files into the Lambda ZIP
|
|
39
|
-
- **Cold start caching** — `setup` factory runs once per cold start, cached across invocations
|
|
32
|
+
From the example above, `eff deploy` will:
|
|
40
33
|
|
|
41
|
-
|
|
34
|
+
- **Bundle** your code with esbuild and package dependencies into a Lambda layer
|
|
35
|
+
- **Create a DynamoDB table** with streams and indexes — from `defineTable<Order>()`
|
|
36
|
+
- **Create a Lambda** with a public HTTP endpoint — from `defineApi()`
|
|
37
|
+
- **Wire IAM permissions** so the API can read/write the table — from `.deps(() => ({ db }))`
|
|
38
|
+
- **Type everything** — `deps.db.get()` returns `Order`, no casts, no `as any`
|
|
39
|
+
|
|
40
|
+
The same principle works for S3 buckets, SQS queues, SES email, static sites, SSR apps, cron jobs — define a handler, the infrastructure follows.
|
|
42
41
|
|
|
43
42
|
## Documentation
|
|
44
43
|
|
|
45
44
|
Full docs, examples, and API reference: **[effortless-aws.website](https://effortless-aws.website)**
|
|
46
45
|
|
|
46
|
+
Deploy with [`@effortless-aws/cli`](https://www.npmjs.com/package/@effortless-aws/cli).
|
|
47
|
+
|
|
47
48
|
## License
|
|
48
49
|
|
|
49
50
|
MIT
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import {
|
|
2
|
+
toSeconds
|
|
3
|
+
} from "./chunk-VONNUUEN.js";
|
|
4
|
+
|
|
5
|
+
// src/runtime/handler-utils.ts
|
|
6
|
+
import { readFileSync } from "fs";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
var lazyTableClient = async (name, opts) => {
|
|
9
|
+
const { createTableClient } = await import("./table-client-BBLIC3EV.js");
|
|
10
|
+
return createTableClient(name, opts);
|
|
11
|
+
};
|
|
12
|
+
var lazyBucketClient = async (name) => {
|
|
13
|
+
const { createBucketClient } = await import("./bucket-client-FRWISKEB.js");
|
|
14
|
+
return createBucketClient(name);
|
|
15
|
+
};
|
|
16
|
+
var lazyBucketClientWithEntities = async (name, config) => {
|
|
17
|
+
const { createBucketClientWithEntities } = await import("./bucket-client-FRWISKEB.js");
|
|
18
|
+
return createBucketClientWithEntities(name, config);
|
|
19
|
+
};
|
|
20
|
+
var lazyEmailClient = async () => {
|
|
21
|
+
const { createEmailClient } = await import("./email-client-RMDQDOYI.js");
|
|
22
|
+
return createEmailClient();
|
|
23
|
+
};
|
|
24
|
+
var lazyQueueClient = async (name) => {
|
|
25
|
+
const { createQueueClient } = await import("./queue-client-WDAJYGQO.js");
|
|
26
|
+
return createQueueClient(name);
|
|
27
|
+
};
|
|
28
|
+
var lazyWorkerClient = async (name) => {
|
|
29
|
+
const { createWorkerClient } = await import("./worker-client-ZS25XCC2.js");
|
|
30
|
+
return createWorkerClient(name);
|
|
31
|
+
};
|
|
32
|
+
var lazyGetParameters = async (paths) => {
|
|
33
|
+
const { getParameters } = await import("./ssm-client-2XDJC3HF.js");
|
|
34
|
+
return getParameters(paths);
|
|
35
|
+
};
|
|
36
|
+
var lazyCreateAuthRuntime = async (secret, defaultExpiresIn, apiTokenVerify, apiTokenHeader, apiTokenCacheTtlSeconds, cfSigningConfig) => {
|
|
37
|
+
const { createAuthRuntime } = await import("./auth-YWMYFMBX.js");
|
|
38
|
+
return createAuthRuntime(secret, defaultExpiresIn, apiTokenVerify, apiTokenHeader, apiTokenCacheTtlSeconds, cfSigningConfig);
|
|
39
|
+
};
|
|
40
|
+
var ENV_DEP_PREFIX = "EFF_DEP_";
|
|
41
|
+
var ENV_PARAM_PREFIX = "EFF_PARAM_";
|
|
42
|
+
var LOG_RANK = { error: 0, info: 1, debug: 2 };
|
|
43
|
+
var truncate = (value, maxLength = 4096) => {
|
|
44
|
+
if (value === void 0 || value === null) return value;
|
|
45
|
+
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
46
|
+
if (str.length <= maxLength) return value;
|
|
47
|
+
return str.slice(0, maxLength) + "...[truncated]";
|
|
48
|
+
};
|
|
49
|
+
var DEP_FACTORIES = {
|
|
50
|
+
table: (name, depHandler) => {
|
|
51
|
+
const tagField = depHandler?.__spec?.tagField;
|
|
52
|
+
return lazyTableClient(name, tagField ? { tagField } : void 0);
|
|
53
|
+
},
|
|
54
|
+
bucket: (name, depHandler) => {
|
|
55
|
+
const entities = depHandler?.__spec?.entities;
|
|
56
|
+
if (entities && Object.keys(entities).length > 0) {
|
|
57
|
+
const config = {};
|
|
58
|
+
for (const [entityName, entityOpts] of Object.entries(entities)) {
|
|
59
|
+
config[entityName] = entityOpts.cache ? { cacheSeconds: toSeconds(entityOpts.cache) } : {};
|
|
60
|
+
}
|
|
61
|
+
return lazyBucketClientWithEntities(name, config);
|
|
62
|
+
}
|
|
63
|
+
return lazyBucketClient(name);
|
|
64
|
+
},
|
|
65
|
+
mailer: () => lazyEmailClient(),
|
|
66
|
+
queue: (name) => lazyQueueClient(name),
|
|
67
|
+
worker: (name) => lazyWorkerClient(name)
|
|
68
|
+
};
|
|
69
|
+
var parseDepValue = (raw) => {
|
|
70
|
+
const idx = raw.indexOf(":");
|
|
71
|
+
return { type: raw.slice(0, idx), name: raw.slice(idx + 1) };
|
|
72
|
+
};
|
|
73
|
+
var resolveDepsInput = (deps) => typeof deps === "function" ? deps() : deps;
|
|
74
|
+
var buildDeps = async (rawDeps) => {
|
|
75
|
+
const deps = resolveDepsInput(rawDeps);
|
|
76
|
+
if (!deps) return void 0;
|
|
77
|
+
const result = {};
|
|
78
|
+
const entries = Object.keys(deps).map((key) => {
|
|
79
|
+
const raw = process.env[`${ENV_DEP_PREFIX}${key}`];
|
|
80
|
+
if (!raw) throw new Error(`Missing environment variable ${ENV_DEP_PREFIX}${key} for dep "${key}"`);
|
|
81
|
+
const { type, name } = parseDepValue(raw);
|
|
82
|
+
const factory = DEP_FACTORIES[type];
|
|
83
|
+
if (!factory) throw new Error(`Unknown dep type "${type}" for dep "${key}"`);
|
|
84
|
+
return { key, promise: factory(name, deps[key]) };
|
|
85
|
+
});
|
|
86
|
+
for (const { key, promise } of entries) {
|
|
87
|
+
result[key] = await promise;
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
};
|
|
91
|
+
var buildParams = async (params) => {
|
|
92
|
+
if (!params) return void 0;
|
|
93
|
+
const entries = [];
|
|
94
|
+
for (const propName of Object.keys(params)) {
|
|
95
|
+
const ssmPath = process.env[`${ENV_PARAM_PREFIX}${propName}`];
|
|
96
|
+
if (!ssmPath) {
|
|
97
|
+
throw new Error(`Missing environment variable ${ENV_PARAM_PREFIX}${propName} for param "${propName}"`);
|
|
98
|
+
}
|
|
99
|
+
entries.push({ propName, ssmPath });
|
|
100
|
+
}
|
|
101
|
+
if (entries.length === 0) return void 0;
|
|
102
|
+
const values = await lazyGetParameters(entries.map((e) => e.ssmPath));
|
|
103
|
+
const result = {};
|
|
104
|
+
for (const { propName, ssmPath } of entries) {
|
|
105
|
+
const raw = values.get(ssmPath) ?? "";
|
|
106
|
+
const ref = params[propName];
|
|
107
|
+
const transform = typeof ref === "object" && ref !== null && "transform" in ref && typeof ref.transform === "function" ? ref.transform : void 0;
|
|
108
|
+
result[propName] = transform ? transform(raw) : raw;
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
};
|
|
112
|
+
var resolvePath = (filePath) => join(process.cwd(), filePath);
|
|
113
|
+
var staticFiles = {
|
|
114
|
+
read: (filePath) => readFileSync(resolvePath(filePath), "utf-8"),
|
|
115
|
+
readBuffer: (filePath) => readFileSync(resolvePath(filePath)),
|
|
116
|
+
path: resolvePath
|
|
117
|
+
};
|
|
118
|
+
var createHandlerRuntime = (handler, handlerType, logLevel = "info", extraSetupArgs) => {
|
|
119
|
+
const handlerName = process.env.EFF_HANDLER ?? "unknown";
|
|
120
|
+
const rank = LOG_RANK[logLevel];
|
|
121
|
+
let ctx = null;
|
|
122
|
+
let resolvedAuthRuntime = null;
|
|
123
|
+
let depsPromise = null;
|
|
124
|
+
const getDeps = () => depsPromise ??= buildDeps(handler.deps);
|
|
125
|
+
let configPromise = null;
|
|
126
|
+
const getConfig = () => configPromise ??= buildParams(handler.config);
|
|
127
|
+
let resolvedCfSigningConfig = null;
|
|
128
|
+
const getCfSigningConfig = async () => {
|
|
129
|
+
if (resolvedCfSigningConfig !== null) return resolvedCfSigningConfig;
|
|
130
|
+
const cfSigningKeySsmPath = process.env.EFF_CF_SIGNING_KEY;
|
|
131
|
+
const cfKeyPairId = process.env.EFF_CF_KEY_PAIR_ID;
|
|
132
|
+
const cfDomain = process.env.EFF_CF_DOMAIN;
|
|
133
|
+
if (!cfSigningKeySsmPath || !cfKeyPairId || !cfDomain) {
|
|
134
|
+
resolvedCfSigningConfig = void 0;
|
|
135
|
+
return void 0;
|
|
136
|
+
}
|
|
137
|
+
const values = await lazyGetParameters([cfSigningKeySsmPath]);
|
|
138
|
+
const privateKey = values.get(cfSigningKeySsmPath);
|
|
139
|
+
if (!privateKey) {
|
|
140
|
+
resolvedCfSigningConfig = void 0;
|
|
141
|
+
return void 0;
|
|
142
|
+
}
|
|
143
|
+
resolvedCfSigningConfig = { privateKey, keyPairId: cfKeyPairId, domain: cfDomain };
|
|
144
|
+
return resolvedCfSigningConfig;
|
|
145
|
+
};
|
|
146
|
+
const getAuthRuntime = async () => {
|
|
147
|
+
if (resolvedAuthRuntime !== null) return resolvedAuthRuntime;
|
|
148
|
+
if (!handler.authFn) {
|
|
149
|
+
resolvedAuthRuntime = void 0;
|
|
150
|
+
return void 0;
|
|
151
|
+
}
|
|
152
|
+
const config = await getConfig();
|
|
153
|
+
const deps = await getDeps();
|
|
154
|
+
const authArgs = {};
|
|
155
|
+
if (config) authArgs.config = config;
|
|
156
|
+
if (deps) authArgs.deps = deps;
|
|
157
|
+
const authOpts = await handler.authFn(authArgs);
|
|
158
|
+
if (!authOpts?.secret) {
|
|
159
|
+
resolvedAuthRuntime = void 0;
|
|
160
|
+
return void 0;
|
|
161
|
+
}
|
|
162
|
+
const secret = authOpts.secret;
|
|
163
|
+
resolvedAuthHeaderName = authOpts.apiToken?.header;
|
|
164
|
+
const defaultExpires = authOpts.expiresIn ? toSeconds(authOpts.expiresIn) : 604800;
|
|
165
|
+
const apiToken = authOpts.apiToken;
|
|
166
|
+
const cacheTtlSeconds = apiToken?.cacheTtl ? toSeconds(apiToken.cacheTtl) : void 0;
|
|
167
|
+
const rawVerify = apiToken?.verify;
|
|
168
|
+
const wrappedVerify = rawVerify ? (args) => rawVerify(args.value) : void 0;
|
|
169
|
+
const cfSigningConfig = await getCfSigningConfig();
|
|
170
|
+
resolvedAuthRuntime = await lazyCreateAuthRuntime(
|
|
171
|
+
secret,
|
|
172
|
+
defaultExpires,
|
|
173
|
+
wrappedVerify,
|
|
174
|
+
apiToken?.header,
|
|
175
|
+
cacheTtlSeconds,
|
|
176
|
+
cfSigningConfig
|
|
177
|
+
);
|
|
178
|
+
return resolvedAuthRuntime;
|
|
179
|
+
};
|
|
180
|
+
const getSetup = async () => {
|
|
181
|
+
if (ctx !== null) return ctx;
|
|
182
|
+
if (handler.setup) {
|
|
183
|
+
const config = await getConfig();
|
|
184
|
+
const deps = await getDeps();
|
|
185
|
+
const args = {};
|
|
186
|
+
if (config) args.config = config;
|
|
187
|
+
if (deps) args.deps = deps;
|
|
188
|
+
if (handler.static) args.files = staticFiles;
|
|
189
|
+
if (extraSetupArgs) Object.assign(args, await extraSetupArgs());
|
|
190
|
+
ctx = await handler.setup(args);
|
|
191
|
+
}
|
|
192
|
+
return ctx;
|
|
193
|
+
};
|
|
194
|
+
let resolvedAuthHeaderName;
|
|
195
|
+
const commonArgs = async (cookieValue, authHeader, headers) => {
|
|
196
|
+
const args = {};
|
|
197
|
+
if (handler.setup) args.ctx = await getSetup();
|
|
198
|
+
const deps = await getDeps();
|
|
199
|
+
if (deps) args.deps = deps;
|
|
200
|
+
const config = await getConfig();
|
|
201
|
+
if (config) args.config = config;
|
|
202
|
+
if (handler.static) args.files = staticFiles;
|
|
203
|
+
const authRuntime = await getAuthRuntime();
|
|
204
|
+
if (authRuntime) {
|
|
205
|
+
let finalAuthHeader = authHeader;
|
|
206
|
+
if (finalAuthHeader === void 0 && headers && resolvedAuthHeaderName) {
|
|
207
|
+
finalAuthHeader = headers[resolvedAuthHeaderName] ?? headers[resolvedAuthHeaderName.toLowerCase()] ?? void 0;
|
|
208
|
+
}
|
|
209
|
+
args.auth = await authRuntime.forRequest(cookieValue, finalAuthHeader);
|
|
210
|
+
}
|
|
211
|
+
return args;
|
|
212
|
+
};
|
|
213
|
+
const logExecution = (startTime, input, output) => {
|
|
214
|
+
if (rank < LOG_RANK.info) return;
|
|
215
|
+
const entry = {
|
|
216
|
+
level: "info",
|
|
217
|
+
handler: handlerName,
|
|
218
|
+
type: handlerType,
|
|
219
|
+
ms: Date.now() - startTime
|
|
220
|
+
};
|
|
221
|
+
if (rank >= LOG_RANK.debug) {
|
|
222
|
+
entry.input = truncate(input);
|
|
223
|
+
entry.output = truncate(output);
|
|
224
|
+
}
|
|
225
|
+
console.log(JSON.stringify(entry));
|
|
226
|
+
};
|
|
227
|
+
const logError = (startTime, input, error) => {
|
|
228
|
+
const entry = {
|
|
229
|
+
level: "error",
|
|
230
|
+
handler: handlerName,
|
|
231
|
+
type: handlerType,
|
|
232
|
+
ms: Date.now() - startTime,
|
|
233
|
+
error: error instanceof Error ? error.message : String(error)
|
|
234
|
+
};
|
|
235
|
+
if (rank >= LOG_RANK.debug) {
|
|
236
|
+
entry.input = truncate(input);
|
|
237
|
+
}
|
|
238
|
+
console.error(JSON.stringify(entry));
|
|
239
|
+
};
|
|
240
|
+
const noop = () => {
|
|
241
|
+
};
|
|
242
|
+
const saved = { log: console.log, info: console.info, debug: console.debug };
|
|
243
|
+
const patchConsole = () => {
|
|
244
|
+
if (rank < LOG_RANK.debug) console.debug = noop;
|
|
245
|
+
if (rank < LOG_RANK.info) {
|
|
246
|
+
console.log = noop;
|
|
247
|
+
console.info = noop;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
const restoreConsole = () => {
|
|
251
|
+
console.log = saved.log;
|
|
252
|
+
console.info = saved.info;
|
|
253
|
+
console.debug = saved.debug;
|
|
254
|
+
};
|
|
255
|
+
const preload = async () => {
|
|
256
|
+
await getDeps();
|
|
257
|
+
await getConfig();
|
|
258
|
+
await getAuthRuntime();
|
|
259
|
+
await getSetup();
|
|
260
|
+
};
|
|
261
|
+
return { preload, commonArgs, logExecution, logError, patchConsole, restoreConsole, handlerName };
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
export {
|
|
265
|
+
buildDeps,
|
|
266
|
+
buildParams,
|
|
267
|
+
createHandlerRuntime
|
|
268
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import {
|
|
2
|
+
toSeconds
|
|
3
|
+
} from "./chunk-VONNUUEN.js";
|
|
4
|
+
|
|
5
|
+
// src/handlers/auth.ts
|
|
6
|
+
import * as crypto from "crypto";
|
|
7
|
+
var cfBase64Encode = (buffer) => buffer.toString("base64").replace(/\+/g, "-").replace(/=/g, "_").replace(/\//g, "~");
|
|
8
|
+
var signCfCookies = (policy, config) => {
|
|
9
|
+
const ttlSeconds = toSeconds(policy.ttl);
|
|
10
|
+
const expireTime = Math.floor(Date.now() / 1e3) + ttlSeconds;
|
|
11
|
+
const resource = config.domain === "*" ? `https://*${policy.path}` : `https://${config.domain}${policy.path}`;
|
|
12
|
+
const policyJson = JSON.stringify({
|
|
13
|
+
Statement: [{
|
|
14
|
+
Resource: resource,
|
|
15
|
+
Condition: {
|
|
16
|
+
DateLessThan: { "AWS:EpochTime": expireTime }
|
|
17
|
+
}
|
|
18
|
+
}]
|
|
19
|
+
});
|
|
20
|
+
const policyBase64 = cfBase64Encode(Buffer.from(policyJson, "utf-8"));
|
|
21
|
+
const signature = cfBase64Encode(
|
|
22
|
+
crypto.sign("sha1", Buffer.from(policyJson, "utf-8"), config.privateKey)
|
|
23
|
+
);
|
|
24
|
+
const cookieAttrs = `; Secure; SameSite=Lax; Path=/; Max-Age=${ttlSeconds}`;
|
|
25
|
+
return [
|
|
26
|
+
`CloudFront-Policy=${policyBase64}${cookieAttrs}`,
|
|
27
|
+
`CloudFront-Signature=${signature}${cookieAttrs}`,
|
|
28
|
+
`CloudFront-Key-Pair-Id=${config.keyPairId}${cookieAttrs}`
|
|
29
|
+
];
|
|
30
|
+
};
|
|
31
|
+
var AUTH_COOKIE_NAME = "__eff_session";
|
|
32
|
+
var createAuthRuntime = (secret, defaultExpiresIn, apiTokenVerify, apiTokenHeader, apiTokenCacheTtlSeconds, cfSigningConfig) => {
|
|
33
|
+
const tokenCache = apiTokenCacheTtlSeconds ? /* @__PURE__ */ new Map() : void 0;
|
|
34
|
+
const sign2 = (payload) => crypto.createHmac("sha256", secret).update(payload).digest("base64url");
|
|
35
|
+
const cookieBase = `${AUTH_COOKIE_NAME}=`;
|
|
36
|
+
const cookieAttrs = "; HttpOnly; Secure; SameSite=Lax; Path=/";
|
|
37
|
+
const decodeSession = (cookieValue) => {
|
|
38
|
+
if (!cookieValue) return void 0;
|
|
39
|
+
const dot = cookieValue.indexOf(".");
|
|
40
|
+
if (dot === -1) return void 0;
|
|
41
|
+
const payload = cookieValue.slice(0, dot);
|
|
42
|
+
const sig = cookieValue.slice(dot + 1);
|
|
43
|
+
if (sign2(payload) !== sig) return void 0;
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
|
|
46
|
+
if (parsed.exp <= Math.floor(Date.now() / 1e3)) return void 0;
|
|
47
|
+
const { exp: _, ...data } = parsed;
|
|
48
|
+
return Object.keys(data).length > 0 ? data : void 0;
|
|
49
|
+
} catch {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const extractTokenValue = (headerValue) => {
|
|
54
|
+
const isDefaultHeader = !apiTokenHeader || apiTokenHeader.toLowerCase() === "authorization";
|
|
55
|
+
if (isDefaultHeader && headerValue.toLowerCase().startsWith("bearer ")) {
|
|
56
|
+
return headerValue.slice(7);
|
|
57
|
+
}
|
|
58
|
+
return headerValue;
|
|
59
|
+
};
|
|
60
|
+
const buildHelpers = (sessionData) => ({
|
|
61
|
+
createSession(data, options) {
|
|
62
|
+
const seconds = options?.expiresIn ? toSeconds(options.expiresIn) : defaultExpiresIn;
|
|
63
|
+
const exp = Math.floor(Date.now() / 1e3) + seconds;
|
|
64
|
+
const payload = Buffer.from(JSON.stringify({ exp, ...data }), "utf-8").toString("base64url");
|
|
65
|
+
const sig = sign2(payload);
|
|
66
|
+
const sessionCookie = `${cookieBase}${payload}.${sig}${cookieAttrs}; Max-Age=${seconds}`;
|
|
67
|
+
const cfCookies = options?.cdnPolicy && cfSigningConfig ? signCfCookies(options.cdnPolicy, cfSigningConfig) : void 0;
|
|
68
|
+
return {
|
|
69
|
+
status: 200,
|
|
70
|
+
body: { ok: true },
|
|
71
|
+
headers: {
|
|
72
|
+
"set-cookie": sessionCookie
|
|
73
|
+
},
|
|
74
|
+
...cfCookies ? { cookies: [sessionCookie, ...cfCookies] } : {}
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
clearSession() {
|
|
78
|
+
return {
|
|
79
|
+
status: 200,
|
|
80
|
+
body: { ok: true },
|
|
81
|
+
headers: {
|
|
82
|
+
"set-cookie": `${cookieBase}${cookieAttrs}; Max-Age=0`
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
session: sessionData
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
async forRequest(cookieValue, authHeader) {
|
|
90
|
+
if (authHeader && apiTokenVerify) {
|
|
91
|
+
const tokenValue = extractTokenValue(authHeader);
|
|
92
|
+
if (tokenCache) {
|
|
93
|
+
const cached = tokenCache.get(tokenValue);
|
|
94
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
95
|
+
return buildHelpers(cached.session);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const session = await apiTokenVerify({ value: tokenValue });
|
|
99
|
+
if (tokenCache && apiTokenCacheTtlSeconds) {
|
|
100
|
+
tokenCache.set(tokenValue, { session, expiresAt: Date.now() + apiTokenCacheTtlSeconds * 1e3 });
|
|
101
|
+
}
|
|
102
|
+
return buildHelpers(session);
|
|
103
|
+
}
|
|
104
|
+
return buildHelpers(decodeSession(cookieValue));
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export {
|
|
110
|
+
signCfCookies,
|
|
111
|
+
AUTH_COOKIE_NAME,
|
|
112
|
+
createAuthRuntime
|
|
113
|
+
};
|