alepha 0.13.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-jobs/index.d.ts +26 -26
- package/dist/api-users/index.d.ts +1 -1
- package/dist/cli/{dist-Sz2EXvQX.cjs → dist-Dl9Vl7Ur.js} +17 -13
- package/dist/cli/{dist-BBPjuQ56.js.map → dist-Dl9Vl7Ur.js.map} +1 -1
- package/dist/cli/index.d.ts +3 -11
- package/dist/cli/index.js +106 -74
- package/dist/cli/index.js.map +1 -1
- package/dist/email/index.js +71 -73
- package/dist/email/index.js.map +1 -1
- package/dist/orm/index.d.ts +1 -1
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/index.d.ts +4 -4
- package/dist/retry/index.d.ts +1 -1
- package/dist/retry/index.js +2 -2
- package/dist/retry/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +6 -6
- package/dist/security/index.d.ts +28 -28
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server-health/index.d.ts +17 -17
- package/dist/server-metrics/index.js +170 -174
- package/dist/server-metrics/index.js.map +1 -1
- package/dist/server-security/index.d.ts +9 -9
- package/dist/vite/index.js +4 -5
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.d.ts +7 -7
- package/package.json +52 -103
- package/src/cli/apps/AlephaPackageBuilderCli.ts +7 -2
- package/src/cli/assets/appRouterTs.ts +9 -0
- package/src/cli/assets/indexHtml.ts +2 -1
- package/src/cli/assets/mainBrowserTs.ts +10 -0
- package/src/cli/commands/CoreCommands.ts +6 -5
- package/src/cli/commands/DrizzleCommands.ts +65 -57
- package/src/cli/commands/VerifyCommands.ts +1 -1
- package/src/cli/services/ProjectUtils.ts +44 -38
- package/src/orm/providers/DrizzleKitProvider.ts +1 -1
- package/src/retry/descriptors/$retry.ts +5 -3
- package/src/server/providers/NodeHttpServerProvider.ts +1 -1
- package/src/vite/helpers/boot.ts +3 -3
- package/dist/api-files/index.cjs +0 -1293
- package/dist/api-files/index.cjs.map +0 -1
- package/dist/api-files/index.d.cts +0 -829
- package/dist/api-jobs/index.cjs +0 -274
- package/dist/api-jobs/index.cjs.map +0 -1
- package/dist/api-jobs/index.d.cts +0 -654
- package/dist/api-notifications/index.cjs +0 -380
- package/dist/api-notifications/index.cjs.map +0 -1
- package/dist/api-notifications/index.d.cts +0 -289
- package/dist/api-parameters/index.cjs +0 -66
- package/dist/api-parameters/index.cjs.map +0 -1
- package/dist/api-parameters/index.d.cts +0 -84
- package/dist/api-users/index.cjs +0 -6009
- package/dist/api-users/index.cjs.map +0 -1
- package/dist/api-users/index.d.cts +0 -4740
- package/dist/api-verifications/index.cjs +0 -407
- package/dist/api-verifications/index.cjs.map +0 -1
- package/dist/api-verifications/index.d.cts +0 -207
- package/dist/batch/index.cjs +0 -408
- package/dist/batch/index.cjs.map +0 -1
- package/dist/batch/index.d.cts +0 -330
- package/dist/bin/index.cjs +0 -17
- package/dist/bin/index.cjs.map +0 -1
- package/dist/bin/index.d.cts +0 -1
- package/dist/bucket/index.cjs +0 -303
- package/dist/bucket/index.cjs.map +0 -1
- package/dist/bucket/index.d.cts +0 -355
- package/dist/cache/index.cjs +0 -241
- package/dist/cache/index.cjs.map +0 -1
- package/dist/cache/index.d.cts +0 -202
- package/dist/cache-redis/index.cjs +0 -84
- package/dist/cache-redis/index.cjs.map +0 -1
- package/dist/cache-redis/index.d.cts +0 -40
- package/dist/cli/chunk-DSlc6foC.cjs +0 -43
- package/dist/cli/dist-BBPjuQ56.js +0 -2778
- package/dist/cli/dist-Sz2EXvQX.cjs.map +0 -1
- package/dist/cli/index.cjs +0 -1241
- package/dist/cli/index.cjs.map +0 -1
- package/dist/cli/index.d.cts +0 -422
- package/dist/command/index.cjs +0 -693
- package/dist/command/index.cjs.map +0 -1
- package/dist/command/index.d.cts +0 -340
- package/dist/core/index.cjs +0 -2264
- package/dist/core/index.cjs.map +0 -1
- package/dist/core/index.d.cts +0 -1927
- package/dist/datetime/index.cjs +0 -318
- package/dist/datetime/index.cjs.map +0 -1
- package/dist/datetime/index.d.cts +0 -145
- package/dist/email/index.cjs +0 -10874
- package/dist/email/index.cjs.map +0 -1
- package/dist/email/index.d.cts +0 -186
- package/dist/fake/index.cjs +0 -34641
- package/dist/fake/index.cjs.map +0 -1
- package/dist/fake/index.d.cts +0 -74
- package/dist/file/index.cjs +0 -1212
- package/dist/file/index.cjs.map +0 -1
- package/dist/file/index.d.cts +0 -698
- package/dist/lock/index.cjs +0 -226
- package/dist/lock/index.cjs.map +0 -1
- package/dist/lock/index.d.cts +0 -361
- package/dist/lock-redis/index.cjs +0 -113
- package/dist/lock-redis/index.cjs.map +0 -1
- package/dist/lock-redis/index.d.cts +0 -24
- package/dist/logger/index.cjs +0 -521
- package/dist/logger/index.cjs.map +0 -1
- package/dist/logger/index.d.cts +0 -281
- package/dist/orm/index.cjs +0 -2986
- package/dist/orm/index.cjs.map +0 -1
- package/dist/orm/index.d.cts +0 -2213
- package/dist/queue/index.cjs +0 -1044
- package/dist/queue/index.cjs.map +0 -1
- package/dist/queue/index.d.cts +0 -1265
- package/dist/queue-redis/index.cjs +0 -873
- package/dist/queue-redis/index.cjs.map +0 -1
- package/dist/queue-redis/index.d.cts +0 -82
- package/dist/redis/index.cjs +0 -153
- package/dist/redis/index.cjs.map +0 -1
- package/dist/redis/index.d.cts +0 -82
- package/dist/retry/index.cjs +0 -146
- package/dist/retry/index.cjs.map +0 -1
- package/dist/retry/index.d.cts +0 -172
- package/dist/router/index.cjs +0 -111
- package/dist/router/index.cjs.map +0 -1
- package/dist/router/index.d.cts +0 -46
- package/dist/scheduler/index.cjs +0 -576
- package/dist/scheduler/index.cjs.map +0 -1
- package/dist/scheduler/index.d.cts +0 -145
- package/dist/security/index.cjs +0 -2402
- package/dist/security/index.cjs.map +0 -1
- package/dist/security/index.d.cts +0 -598
- package/dist/server/index.cjs +0 -1680
- package/dist/server/index.cjs.map +0 -1
- package/dist/server/index.d.cts +0 -810
- package/dist/server-auth/index.cjs +0 -3146
- package/dist/server-auth/index.cjs.map +0 -1
- package/dist/server-auth/index.d.cts +0 -1164
- package/dist/server-cache/index.cjs +0 -252
- package/dist/server-cache/index.cjs.map +0 -1
- package/dist/server-cache/index.d.cts +0 -164
- package/dist/server-compress/index.cjs +0 -141
- package/dist/server-compress/index.cjs.map +0 -1
- package/dist/server-compress/index.d.cts +0 -38
- package/dist/server-cookies/index.cjs +0 -234
- package/dist/server-cookies/index.cjs.map +0 -1
- package/dist/server-cookies/index.d.cts +0 -144
- package/dist/server-cors/index.cjs +0 -201
- package/dist/server-cors/index.cjs.map +0 -1
- package/dist/server-cors/index.d.cts +0 -140
- package/dist/server-health/index.cjs +0 -62
- package/dist/server-health/index.cjs.map +0 -1
- package/dist/server-health/index.d.cts +0 -58
- package/dist/server-helmet/index.cjs +0 -131
- package/dist/server-helmet/index.cjs.map +0 -1
- package/dist/server-helmet/index.d.cts +0 -97
- package/dist/server-links/index.cjs +0 -992
- package/dist/server-links/index.cjs.map +0 -1
- package/dist/server-links/index.d.cts +0 -513
- package/dist/server-metrics/index.cjs +0 -4535
- package/dist/server-metrics/index.cjs.map +0 -1
- package/dist/server-metrics/index.d.cts +0 -35
- package/dist/server-multipart/index.cjs +0 -237
- package/dist/server-multipart/index.cjs.map +0 -1
- package/dist/server-multipart/index.d.cts +0 -50
- package/dist/server-proxy/index.cjs +0 -186
- package/dist/server-proxy/index.cjs.map +0 -1
- package/dist/server-proxy/index.d.cts +0 -234
- package/dist/server-rate-limit/index.cjs +0 -241
- package/dist/server-rate-limit/index.cjs.map +0 -1
- package/dist/server-rate-limit/index.d.cts +0 -183
- package/dist/server-security/index.cjs +0 -316
- package/dist/server-security/index.cjs.map +0 -1
- package/dist/server-security/index.d.cts +0 -173
- package/dist/server-static/index.cjs +0 -170
- package/dist/server-static/index.cjs.map +0 -1
- package/dist/server-static/index.d.cts +0 -121
- package/dist/server-swagger/index.cjs +0 -1021
- package/dist/server-swagger/index.cjs.map +0 -1
- package/dist/server-swagger/index.d.cts +0 -382
- package/dist/sms/index.cjs +0 -221
- package/dist/sms/index.cjs.map +0 -1
- package/dist/sms/index.d.cts +0 -130
- package/dist/thread/index.cjs +0 -350
- package/dist/thread/index.cjs.map +0 -1
- package/dist/thread/index.d.cts +0 -260
- package/dist/topic/index.cjs +0 -282
- package/dist/topic/index.cjs.map +0 -1
- package/dist/topic/index.d.cts +0 -523
- package/dist/topic-redis/index.cjs +0 -71
- package/dist/topic-redis/index.cjs.map +0 -1
- package/dist/topic-redis/index.d.cts +0 -42
- package/dist/vite/index.cjs +0 -1077
- package/dist/vite/index.cjs.map +0 -1
- package/dist/vite/index.d.cts +0 -542
- package/dist/websocket/index.cjs +0 -1117
- package/dist/websocket/index.cjs.map +0 -1
- package/dist/websocket/index.d.cts +0 -861
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
let alepha = require("alepha");
|
|
2
|
-
let alepha_server = require("alepha/server");
|
|
3
|
-
let node_crypto = require("node:crypto");
|
|
4
|
-
let node_zlib = require("node:zlib");
|
|
5
|
-
let alepha_datetime = require("alepha/datetime");
|
|
6
|
-
let alepha_logger = require("alepha/logger");
|
|
7
|
-
let alepha_security = require("alepha/security");
|
|
8
|
-
|
|
9
|
-
//#region src/server-cookies/services/CookieParser.ts
|
|
10
|
-
var CookieParser = class {
|
|
11
|
-
parseRequestCookies(header) {
|
|
12
|
-
const cookies = {};
|
|
13
|
-
const parts = header.split(";");
|
|
14
|
-
for (const part of parts) {
|
|
15
|
-
const [key, value] = part.split("=");
|
|
16
|
-
if (!key || !value) continue;
|
|
17
|
-
cookies[key.trim()] = value.trim();
|
|
18
|
-
}
|
|
19
|
-
return cookies;
|
|
20
|
-
}
|
|
21
|
-
serializeResponseCookies(cookies, isHttps) {
|
|
22
|
-
const headers = [];
|
|
23
|
-
for (const [name, cookie] of Object.entries(cookies)) {
|
|
24
|
-
if (cookie == null) {
|
|
25
|
-
headers.push(`${name}=; Path=/; Max-Age=0`);
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
if (!cookie.value) continue;
|
|
29
|
-
headers.push(this.cookieToString(name, cookie, isHttps));
|
|
30
|
-
}
|
|
31
|
-
return headers;
|
|
32
|
-
}
|
|
33
|
-
cookieToString(name, cookie, isHttps) {
|
|
34
|
-
const parts = [];
|
|
35
|
-
parts.push(`${name}=${cookie.value}`);
|
|
36
|
-
if (cookie.path) parts.push(`Path=${cookie.path}`);
|
|
37
|
-
if (cookie.maxAge) parts.push(`Max-Age=${cookie.maxAge}`);
|
|
38
|
-
if (cookie.secure !== false && isHttps) parts.push("Secure");
|
|
39
|
-
if (cookie.httpOnly) parts.push("HttpOnly");
|
|
40
|
-
if (cookie.sameSite) parts.push(`SameSite=${cookie.sameSite}`);
|
|
41
|
-
if (cookie.domain) parts.push(`Domain=${cookie.domain}`);
|
|
42
|
-
return parts.join("; ");
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
//#endregion
|
|
47
|
-
//#region src/server-cookies/providers/ServerCookiesProvider.ts
|
|
48
|
-
const envSchema = alepha.t.object({ APP_SECRET: alepha.t.text({ default: alepha_security.DEFAULT_APP_SECRET }) });
|
|
49
|
-
var ServerCookiesProvider = class {
|
|
50
|
-
alepha = (0, alepha.$inject)(alepha.Alepha);
|
|
51
|
-
log = (0, alepha_logger.$logger)();
|
|
52
|
-
cookieParser = (0, alepha.$inject)(CookieParser);
|
|
53
|
-
dateTimeProvider = (0, alepha.$inject)(alepha_datetime.DateTimeProvider);
|
|
54
|
-
env = (0, alepha.$env)(envSchema);
|
|
55
|
-
ALGORITHM = "aes-256-gcm";
|
|
56
|
-
IV_LENGTH = 16;
|
|
57
|
-
AUTH_TAG_LENGTH = 16;
|
|
58
|
-
SIGNATURE_LENGTH = 32;
|
|
59
|
-
onRequest = (0, alepha.$hook)({
|
|
60
|
-
on: "server:onRequest",
|
|
61
|
-
handler: async ({ request }) => {
|
|
62
|
-
request.cookies = {
|
|
63
|
-
req: this.cookieParser.parseRequestCookies(request.headers.cookie ?? ""),
|
|
64
|
-
res: {}
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
onAction = (0, alepha.$hook)({
|
|
69
|
-
on: "action:onRequest",
|
|
70
|
-
handler: async ({ request }) => {
|
|
71
|
-
request.cookies = {
|
|
72
|
-
req: this.cookieParser.parseRequestCookies(request.headers.cookie ?? ""),
|
|
73
|
-
res: {}
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
onSend = (0, alepha.$hook)({
|
|
78
|
-
on: "server:onSend",
|
|
79
|
-
handler: async ({ request }) => {
|
|
80
|
-
if (request.cookies && Object.keys(request.cookies.res).length > 0) {
|
|
81
|
-
const setCookieHeaders = this.cookieParser.serializeResponseCookies(request.cookies.res, request.url.protocol === "https:");
|
|
82
|
-
if (setCookieHeaders.length > 0) request.reply.headers["set-cookie"] = setCookieHeaders;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
getCookiesFromContext(cookies) {
|
|
87
|
-
const contextCookies = this.alepha.context.get("request")?.cookies;
|
|
88
|
-
if (cookies) return cookies;
|
|
89
|
-
if (contextCookies) return contextCookies;
|
|
90
|
-
throw new Error("Cookie context is not available. This method must be called within a server request cycle.");
|
|
91
|
-
}
|
|
92
|
-
getCookie(name, options, contextCookies) {
|
|
93
|
-
const cookies = this.getCookiesFromContext(contextCookies);
|
|
94
|
-
let rawValue = cookies.req[name];
|
|
95
|
-
if (!rawValue) return void 0;
|
|
96
|
-
try {
|
|
97
|
-
rawValue = decodeURIComponent(rawValue);
|
|
98
|
-
if (options.sign) {
|
|
99
|
-
const signature = rawValue.substring(0, this.SIGNATURE_LENGTH * 2);
|
|
100
|
-
const value = rawValue.substring(this.SIGNATURE_LENGTH * 2);
|
|
101
|
-
const expectedSignature = this.sign(value);
|
|
102
|
-
if (!(0, node_crypto.timingSafeEqual)(Buffer.from(signature, "hex"), Buffer.from(expectedSignature, "hex"))) {
|
|
103
|
-
this.log.warn(`Invalid signature for cookie "${name}".`);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
rawValue = value;
|
|
107
|
-
}
|
|
108
|
-
if (options.encrypt) rawValue = this.decrypt(rawValue);
|
|
109
|
-
if (options.compress) rawValue = (0, node_zlib.inflateRawSync)(Buffer.from(rawValue, "base64")).toString("utf8");
|
|
110
|
-
return this.alepha.codec.decode(options.schema, JSON.parse(rawValue));
|
|
111
|
-
} catch (error) {
|
|
112
|
-
this.log.warn(`Failed to parse cookie "${name}"`, error);
|
|
113
|
-
this.deleteCookie(name, cookies);
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
setCookie(name, options, data, contextCookies) {
|
|
118
|
-
const cookies = this.getCookiesFromContext(contextCookies);
|
|
119
|
-
let value = JSON.stringify(this.alepha.codec.decode(options.schema, data));
|
|
120
|
-
if (options.compress) value = (0, node_zlib.deflateRawSync)(value).toString("base64");
|
|
121
|
-
if (options.encrypt) value = this.encrypt(value);
|
|
122
|
-
if (options.sign) value = this.sign(value) + value;
|
|
123
|
-
const cookie = {
|
|
124
|
-
value: encodeURIComponent(value),
|
|
125
|
-
path: options.path ?? "/",
|
|
126
|
-
sameSite: options.sameSite ?? "lax",
|
|
127
|
-
secure: options.secure ?? this.alepha.isProduction(),
|
|
128
|
-
httpOnly: options.httpOnly,
|
|
129
|
-
domain: options.domain
|
|
130
|
-
};
|
|
131
|
-
if (options.ttl) cookie.maxAge = this.dateTimeProvider.duration(options.ttl).as("seconds");
|
|
132
|
-
cookies.res[name] = cookie;
|
|
133
|
-
}
|
|
134
|
-
deleteCookie(name, contextCookies) {
|
|
135
|
-
const cookies = this.getCookiesFromContext(contextCookies);
|
|
136
|
-
cookies.res[name] = null;
|
|
137
|
-
}
|
|
138
|
-
encrypt(text) {
|
|
139
|
-
const iv = (0, node_crypto.randomBytes)(this.IV_LENGTH);
|
|
140
|
-
const cipher = (0, node_crypto.createCipheriv)(this.ALGORITHM, Buffer.from(this.secretKey()), iv);
|
|
141
|
-
const encrypted = Buffer.concat([cipher.update(text, "utf8"), cipher.final()]);
|
|
142
|
-
const authTag = cipher.getAuthTag();
|
|
143
|
-
return Buffer.concat([
|
|
144
|
-
iv,
|
|
145
|
-
authTag,
|
|
146
|
-
encrypted
|
|
147
|
-
]).toString("base64");
|
|
148
|
-
}
|
|
149
|
-
decrypt(encryptedText) {
|
|
150
|
-
const data = Buffer.from(encryptedText, "base64");
|
|
151
|
-
const iv = data.subarray(0, this.IV_LENGTH);
|
|
152
|
-
const authTag = data.subarray(this.IV_LENGTH, this.IV_LENGTH + this.AUTH_TAG_LENGTH);
|
|
153
|
-
const encrypted = data.subarray(this.IV_LENGTH + this.AUTH_TAG_LENGTH);
|
|
154
|
-
const decipher = (0, node_crypto.createDecipheriv)(this.ALGORITHM, Buffer.from(this.secretKey()), iv);
|
|
155
|
-
decipher.setAuthTag(authTag);
|
|
156
|
-
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf8");
|
|
157
|
-
}
|
|
158
|
-
secretKey() {
|
|
159
|
-
let secret = this.env.APP_SECRET;
|
|
160
|
-
if (secret.length < 32) secret = secret.padEnd(32, "0");
|
|
161
|
-
else if (secret.length > 32) secret = secret.substring(0, 32);
|
|
162
|
-
return secret;
|
|
163
|
-
}
|
|
164
|
-
sign(data) {
|
|
165
|
-
return (0, node_crypto.createHmac)("sha256", this.secretKey()).update(data).digest("hex");
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
//#endregion
|
|
170
|
-
//#region src/server-cookies/descriptors/$cookie.ts
|
|
171
|
-
/**
|
|
172
|
-
* Declares a type-safe, configurable HTTP cookie.
|
|
173
|
-
* This descriptor provides methods to get, set, and delete the cookie
|
|
174
|
-
* within the server request/response cycle.
|
|
175
|
-
*/
|
|
176
|
-
const $cookie = (options) => {
|
|
177
|
-
return (0, alepha.createDescriptor)(CookieDescriptor, options);
|
|
178
|
-
};
|
|
179
|
-
var CookieDescriptor = class extends alepha.Descriptor {
|
|
180
|
-
serverCookiesProvider = (0, alepha.$inject)(ServerCookiesProvider);
|
|
181
|
-
get schema() {
|
|
182
|
-
return this.options.schema;
|
|
183
|
-
}
|
|
184
|
-
get name() {
|
|
185
|
-
return this.options.name ?? `${this.config.propertyKey}`;
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Sets the cookie with the given value in the current request's response.
|
|
189
|
-
*/
|
|
190
|
-
set(value, options) {
|
|
191
|
-
this.serverCookiesProvider.setCookie(this.name, {
|
|
192
|
-
...this.options,
|
|
193
|
-
ttl: options?.ttl ?? this.options.ttl
|
|
194
|
-
}, value, options?.cookies);
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Gets the cookie value from the current request. Returns undefined if not found or invalid.
|
|
198
|
-
*/
|
|
199
|
-
get(options) {
|
|
200
|
-
return this.serverCookiesProvider.getCookie(this.name, this.options, options?.cookies);
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Deletes the cookie in the current request's response.
|
|
204
|
-
*/
|
|
205
|
-
del(options) {
|
|
206
|
-
this.serverCookiesProvider.deleteCookie(this.name, options?.cookies);
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
$cookie[alepha.KIND] = CookieDescriptor;
|
|
210
|
-
|
|
211
|
-
//#endregion
|
|
212
|
-
//#region src/server-cookies/index.ts
|
|
213
|
-
/**
|
|
214
|
-
* Provides HTTP cookie management capabilities for server requests and responses with type-safe cookie descriptors.
|
|
215
|
-
*
|
|
216
|
-
* The server-cookies module enables declarative cookie handling using the `$cookie` descriptor on class properties.
|
|
217
|
-
* It offers automatic cookie parsing, secure cookie configuration, and seamless integration with server routes
|
|
218
|
-
* for managing user sessions, preferences, and authentication tokens.
|
|
219
|
-
*
|
|
220
|
-
* @see {@link $cookie}
|
|
221
|
-
* @module alepha.server.cookies
|
|
222
|
-
*/
|
|
223
|
-
const AlephaServerCookies = (0, alepha.$module)({
|
|
224
|
-
name: "alepha.server.cookies",
|
|
225
|
-
descriptors: [$cookie],
|
|
226
|
-
services: [alepha_server.AlephaServer, ServerCookiesProvider]
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
//#endregion
|
|
230
|
-
exports.$cookie = $cookie;
|
|
231
|
-
exports.AlephaServerCookies = AlephaServerCookies;
|
|
232
|
-
exports.CookieDescriptor = CookieDescriptor;
|
|
233
|
-
exports.ServerCookiesProvider = ServerCookiesProvider;
|
|
234
|
-
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["cookies: Record<string, string>","parts: string[]","t","DEFAULT_APP_SECRET","Alepha","DateTimeProvider","cookie: Cookie","Descriptor","KIND","AlephaServer"],"sources":["../../src/server-cookies/services/CookieParser.ts","../../src/server-cookies/providers/ServerCookiesProvider.ts","../../src/server-cookies/descriptors/$cookie.ts","../../src/server-cookies/index.ts"],"sourcesContent":["import type { Cookie } from \"../descriptors/$cookie.ts\";\n\nexport class CookieParser {\n public parseRequestCookies(header: string): Record<string, string> {\n const cookies: Record<string, string> = {};\n const parts = header.split(\";\");\n for (const part of parts) {\n const [key, value] = part.split(\"=\");\n if (!key || !value) {\n continue;\n }\n\n cookies[key.trim()] = value.trim();\n }\n\n return cookies;\n }\n\n public serializeResponseCookies(\n cookies: Record<string, Cookie | null>,\n isHttps: boolean,\n ): string[] {\n const headers = [];\n\n for (const [name, cookie] of Object.entries(cookies)) {\n // If the cookie is null, we need to delete it\n if (cookie == null) {\n headers.push(`${name}=; Path=/; Max-Age=0`);\n continue;\n }\n\n if (!cookie.value) {\n continue;\n }\n\n headers.push(this.cookieToString(name, cookie, isHttps));\n }\n\n return headers;\n }\n\n public cookieToString(\n name: string,\n cookie: Cookie,\n isHttps?: boolean,\n ): string {\n const parts: string[] = [];\n\n parts.push(`${name}=${cookie.value}`);\n\n if (cookie.path) {\n parts.push(`Path=${cookie.path}`);\n }\n if (cookie.maxAge) {\n parts.push(`Max-Age=${cookie.maxAge}`);\n }\n if (cookie.secure !== false && isHttps) {\n parts.push(\"Secure\");\n }\n if (cookie.httpOnly) {\n parts.push(\"HttpOnly\");\n }\n if (cookie.sameSite) {\n parts.push(`SameSite=${cookie.sameSite}`);\n }\n if (cookie.domain) {\n parts.push(`Domain=${cookie.domain}`);\n }\n\n return parts.join(\"; \");\n }\n}\n","import {\n createCipheriv,\n createDecipheriv,\n createHmac,\n randomBytes,\n timingSafeEqual,\n} from \"node:crypto\";\nimport { deflateRawSync, inflateRawSync } from \"node:zlib\";\nimport {\n $env,\n $hook,\n $inject,\n Alepha,\n type Static,\n type TSchema,\n t,\n} from \"alepha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { DEFAULT_APP_SECRET } from \"alepha/security\";\nimport type { ServerRequest } from \"alepha/server\";\nimport type {\n Cookie,\n CookieDescriptorOptions,\n Cookies,\n} from \"../descriptors/$cookie.ts\";\nimport { CookieParser } from \"../services/CookieParser.ts\";\n\nconst envSchema = t.object({\n APP_SECRET: t.text({\n default: DEFAULT_APP_SECRET,\n }),\n});\n\nexport class ServerCookiesProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly cookieParser = $inject(CookieParser);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly env = $env(envSchema);\n\n // crypto constants\n protected readonly ALGORITHM = \"aes-256-gcm\";\n protected readonly IV_LENGTH = 16; // For GCM\n protected readonly AUTH_TAG_LENGTH = 16;\n protected readonly SIGNATURE_LENGTH = 32; // For SHA256\n\n public readonly onRequest = $hook({\n on: \"server:onRequest\",\n handler: async ({ request }) => {\n request.cookies = {\n req: this.cookieParser.parseRequestCookies(\n request.headers.cookie ?? \"\",\n ),\n res: {},\n };\n },\n });\n\n public readonly onAction = $hook({\n on: \"action:onRequest\",\n handler: async ({ request }) => {\n request.cookies = {\n req: this.cookieParser.parseRequestCookies(\n request.headers.cookie ?? \"\",\n ),\n res: {},\n };\n },\n });\n\n public readonly onSend = $hook({\n on: \"server:onSend\",\n handler: async ({ request }) => {\n if (request.cookies && Object.keys(request.cookies.res).length > 0) {\n const setCookieHeaders = this.cookieParser.serializeResponseCookies(\n request.cookies.res,\n request.url.protocol === \"https:\",\n );\n if (setCookieHeaders.length > 0) {\n request.reply.headers[\"set-cookie\"] = setCookieHeaders;\n }\n }\n },\n });\n\n protected getCookiesFromContext(cookies?: Cookies): Cookies {\n const contextCookies =\n this.alepha.context.get<ServerRequest>(\"request\")?.cookies;\n if (cookies) return cookies;\n if (contextCookies) return contextCookies;\n throw new Error(\n \"Cookie context is not available. This method must be called within a server request cycle.\",\n );\n }\n\n public getCookie<T extends TSchema>(\n name: string,\n options: CookieDescriptorOptions<T>,\n contextCookies?: Cookies,\n ): Static<T> | undefined {\n const cookies = this.getCookiesFromContext(contextCookies);\n let rawValue = cookies.req[name];\n\n if (!rawValue) return undefined;\n\n try {\n rawValue = decodeURIComponent(rawValue);\n\n if (options.sign) {\n const signature = rawValue.substring(0, this.SIGNATURE_LENGTH * 2);\n const value = rawValue.substring(this.SIGNATURE_LENGTH * 2);\n const expectedSignature = this.sign(value);\n\n if (\n !timingSafeEqual(\n Buffer.from(signature, \"hex\"),\n Buffer.from(expectedSignature, \"hex\"),\n )\n ) {\n this.log.warn(`Invalid signature for cookie \"${name}\".`);\n return undefined;\n }\n rawValue = value;\n }\n\n if (options.encrypt) {\n rawValue = this.decrypt(rawValue);\n }\n\n if (options.compress) {\n rawValue = inflateRawSync(Buffer.from(rawValue, \"base64\")).toString(\n \"utf8\",\n );\n }\n\n return this.alepha.codec.decode(options.schema, JSON.parse(rawValue));\n } catch (error) {\n this.log.warn(`Failed to parse cookie \"${name}\"`, error);\n // corrupted or invalid cookie, instruct browser to delete it on next response\n this.deleteCookie(name, cookies);\n return undefined;\n }\n }\n\n public setCookie<T extends TSchema>(\n name: string,\n options: CookieDescriptorOptions<T>,\n data: Static<T>,\n contextCookies?: Cookies,\n ): void {\n const cookies = this.getCookiesFromContext(contextCookies);\n let value = JSON.stringify(this.alepha.codec.decode(options.schema, data));\n\n if (options.compress) {\n value = deflateRawSync(value).toString(\"base64\");\n }\n\n if (options.encrypt) {\n value = this.encrypt(value);\n }\n\n if (options.sign) {\n value = this.sign(value) + value;\n }\n\n const cookie: Cookie = {\n value: encodeURIComponent(value),\n path: options.path ?? \"/\",\n sameSite: options.sameSite ?? \"lax\",\n secure: options.secure ?? this.alepha.isProduction(),\n httpOnly: options.httpOnly,\n domain: options.domain,\n };\n\n if (options.ttl) {\n cookie.maxAge = this.dateTimeProvider.duration(options.ttl).as(\"seconds\");\n }\n\n cookies.res[name] = cookie;\n }\n\n public deleteCookie<T extends TSchema>(\n name: string,\n contextCookies?: Cookies,\n ): void {\n const cookies = this.getCookiesFromContext(contextCookies);\n cookies.res[name] = null;\n }\n\n // --- Crypto & Parsing ---\n\n protected encrypt(text: string): string {\n const iv = randomBytes(this.IV_LENGTH);\n const cipher = createCipheriv(\n this.ALGORITHM,\n Buffer.from(this.secretKey()),\n iv,\n );\n const encrypted = Buffer.concat([\n cipher.update(text, \"utf8\"),\n cipher.final(),\n ]);\n const authTag = cipher.getAuthTag();\n return Buffer.concat([iv, authTag, encrypted]).toString(\"base64\");\n }\n\n protected decrypt(encryptedText: string): string {\n const data = Buffer.from(encryptedText, \"base64\");\n const iv = data.subarray(0, this.IV_LENGTH);\n const authTag = data.subarray(\n this.IV_LENGTH,\n this.IV_LENGTH + this.AUTH_TAG_LENGTH,\n );\n\n const encrypted = data.subarray(this.IV_LENGTH + this.AUTH_TAG_LENGTH);\n const decipher = createDecipheriv(\n this.ALGORITHM,\n Buffer.from(this.secretKey()),\n iv,\n );\n\n decipher.setAuthTag(authTag);\n\n const decrypted = Buffer.concat([\n decipher.update(encrypted),\n decipher.final(),\n ]);\n\n return decrypted.toString(\"utf8\");\n }\n\n public secretKey(): string {\n let secret = this.env.APP_SECRET;\n if (secret.length < 32) {\n // pad secret to 32 bytes\n secret = secret.padEnd(32, \"0\");\n } else if (secret.length > 32) {\n // truncate secret to 32 bytes\n secret = secret.substring(0, 32);\n }\n return secret;\n }\n\n protected sign(data: string): string {\n return createHmac(\"sha256\", this.secretKey()).update(data).digest(\"hex\");\n }\n}\n","import {\n $inject,\n createDescriptor,\n Descriptor,\n KIND,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport type { DurationLike } from \"alepha/datetime\";\nimport { ServerCookiesProvider } from \"../providers/ServerCookiesProvider.ts\";\n\n/**\n * Declares a type-safe, configurable HTTP cookie.\n * This descriptor provides methods to get, set, and delete the cookie\n * within the server request/response cycle.\n */\nexport const $cookie = <T extends TSchema>(\n options: CookieDescriptorOptions<T>,\n): AbstractCookieDescriptor<T> => {\n return createDescriptor(CookieDescriptor<T>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface CookieDescriptorOptions<T extends TSchema> {\n /** The schema for the cookie's value, used for validation and type safety. */\n schema: T;\n\n /** The name of the cookie. */\n name?: string;\n\n /** The cookie's path. Defaults to \"/\". */\n path?: string;\n\n /** Time-to-live for the cookie. Maps to `Max-Age`. */\n ttl?: DurationLike;\n\n /** If true, the cookie is only sent over HTTPS. Defaults to true in production. */\n secure?: boolean;\n\n /** If true, the cookie cannot be accessed by client-side scripts. */\n httpOnly?: boolean;\n\n /** SameSite policy for the cookie. Defaults to \"lax\". */\n sameSite?: \"strict\" | \"lax\" | \"none\";\n\n /** The domain for the cookie. */\n domain?: string;\n\n /** If true, the cookie value will be compressed using zlib. */\n compress?: boolean;\n\n /** If true, the cookie value will be encrypted. Requires `COOKIE_SECRET` env var. */\n encrypt?: boolean;\n\n /** If true, the cookie will be signed to prevent tampering. Requires `COOKIE_SECRET` env var. */\n sign?: boolean;\n}\n\nexport interface AbstractCookieDescriptor<T extends TSchema> {\n readonly name: string;\n readonly options: CookieDescriptorOptions<T>;\n set(\n value: Static<T>,\n options?: { cookies?: Cookies; ttl?: DurationLike },\n ): void;\n get(options?: { cookies?: Cookies }): Static<T> | undefined;\n del(options?: { cookies?: Cookies }): void;\n}\n\nexport class CookieDescriptor<T extends TSchema>\n extends Descriptor<CookieDescriptorOptions<T>>\n implements AbstractCookieDescriptor<T>\n{\n protected readonly serverCookiesProvider = $inject(ServerCookiesProvider);\n\n public get schema(): T {\n return this.options.schema;\n }\n\n public get name(): string {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n /**\n * Sets the cookie with the given value in the current request's response.\n */\n public set(\n value: Static<T>,\n options?: { cookies?: Cookies; ttl?: DurationLike },\n ): void {\n this.serverCookiesProvider.setCookie(\n this.name,\n {\n ...this.options,\n ttl: options?.ttl ?? this.options.ttl,\n },\n value,\n options?.cookies,\n );\n }\n\n /**\n * Gets the cookie value from the current request. Returns undefined if not found or invalid.\n */\n public get(options?: { cookies?: Cookies }): Static<T> | undefined {\n return this.serverCookiesProvider.getCookie(\n this.name,\n this.options,\n options?.cookies,\n );\n }\n\n /**\n * Deletes the cookie in the current request's response.\n */\n public del(options?: { cookies?: Cookies }): void {\n this.serverCookiesProvider.deleteCookie(this.name, options?.cookies);\n }\n}\n\n$cookie[KIND] = CookieDescriptor;\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface Cookies {\n req: Record<string, string>;\n res: Record<string, Cookie | null>;\n}\n\nexport interface Cookie {\n value: string;\n path?: string;\n maxAge?: number;\n secure?: boolean;\n httpOnly?: boolean;\n sameSite?: \"strict\" | \"lax\" | \"none\";\n domain?: string;\n}\n","import { $module } from \"alepha\";\nimport { AlephaServer } from \"alepha/server\";\nimport { $cookie, type Cookies } from \"./descriptors/$cookie.ts\";\nimport { ServerCookiesProvider } from \"./providers/ServerCookiesProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./descriptors/$cookie.ts\";\nexport * from \"./providers/ServerCookiesProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha/server\" {\n interface ServerRequest {\n cookies: Cookies;\n }\n}\n\n/**\n * Provides HTTP cookie management capabilities for server requests and responses with type-safe cookie descriptors.\n *\n * The server-cookies module enables declarative cookie handling using the `$cookie` descriptor on class properties.\n * It offers automatic cookie parsing, secure cookie configuration, and seamless integration with server routes\n * for managing user sessions, preferences, and authentication tokens.\n *\n * @see {@link $cookie}\n * @module alepha.server.cookies\n */\nexport const AlephaServerCookies = $module({\n name: \"alepha.server.cookies\",\n descriptors: [$cookie],\n services: [AlephaServer, ServerCookiesProvider],\n});\n"],"mappings":"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"}
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import * as alepha1 from "alepha";
|
|
2
|
-
import { Alepha, Descriptor, KIND, Static, TSchema } from "alepha";
|
|
3
|
-
import { DateTimeProvider, DurationLike } from "alepha/datetime";
|
|
4
|
-
import * as alepha_logger0 from "alepha/logger";
|
|
5
|
-
|
|
6
|
-
//#region src/server-cookies/services/CookieParser.d.ts
|
|
7
|
-
declare class CookieParser {
|
|
8
|
-
parseRequestCookies(header: string): Record<string, string>;
|
|
9
|
-
serializeResponseCookies(cookies: Record<string, Cookie | null>, isHttps: boolean): string[];
|
|
10
|
-
cookieToString(name: string, cookie: Cookie, isHttps?: boolean): string;
|
|
11
|
-
}
|
|
12
|
-
//#endregion
|
|
13
|
-
//#region src/server-cookies/providers/ServerCookiesProvider.d.ts
|
|
14
|
-
declare class ServerCookiesProvider {
|
|
15
|
-
protected readonly alepha: Alepha;
|
|
16
|
-
protected readonly log: alepha_logger0.Logger;
|
|
17
|
-
protected readonly cookieParser: CookieParser;
|
|
18
|
-
protected readonly dateTimeProvider: DateTimeProvider;
|
|
19
|
-
protected readonly env: {
|
|
20
|
-
APP_SECRET: string;
|
|
21
|
-
};
|
|
22
|
-
protected readonly ALGORITHM = "aes-256-gcm";
|
|
23
|
-
protected readonly IV_LENGTH = 16;
|
|
24
|
-
protected readonly AUTH_TAG_LENGTH = 16;
|
|
25
|
-
protected readonly SIGNATURE_LENGTH = 32;
|
|
26
|
-
readonly onRequest: alepha1.HookDescriptor<"server:onRequest">;
|
|
27
|
-
readonly onAction: alepha1.HookDescriptor<"action:onRequest">;
|
|
28
|
-
readonly onSend: alepha1.HookDescriptor<"server:onSend">;
|
|
29
|
-
protected getCookiesFromContext(cookies?: Cookies): Cookies;
|
|
30
|
-
getCookie<T extends TSchema>(name: string, options: CookieDescriptorOptions<T>, contextCookies?: Cookies): Static<T> | undefined;
|
|
31
|
-
setCookie<T extends TSchema>(name: string, options: CookieDescriptorOptions<T>, data: Static<T>, contextCookies?: Cookies): void;
|
|
32
|
-
deleteCookie<T extends TSchema>(name: string, contextCookies?: Cookies): void;
|
|
33
|
-
protected encrypt(text: string): string;
|
|
34
|
-
protected decrypt(encryptedText: string): string;
|
|
35
|
-
secretKey(): string;
|
|
36
|
-
protected sign(data: string): string;
|
|
37
|
-
}
|
|
38
|
-
//#endregion
|
|
39
|
-
//#region src/server-cookies/descriptors/$cookie.d.ts
|
|
40
|
-
/**
|
|
41
|
-
* Declares a type-safe, configurable HTTP cookie.
|
|
42
|
-
* This descriptor provides methods to get, set, and delete the cookie
|
|
43
|
-
* within the server request/response cycle.
|
|
44
|
-
*/
|
|
45
|
-
declare const $cookie: {
|
|
46
|
-
<T extends TSchema>(options: CookieDescriptorOptions<T>): AbstractCookieDescriptor<T>;
|
|
47
|
-
[KIND]: typeof CookieDescriptor;
|
|
48
|
-
};
|
|
49
|
-
interface CookieDescriptorOptions<T extends TSchema> {
|
|
50
|
-
/** The schema for the cookie's value, used for validation and type safety. */
|
|
51
|
-
schema: T;
|
|
52
|
-
/** The name of the cookie. */
|
|
53
|
-
name?: string;
|
|
54
|
-
/** The cookie's path. Defaults to "/". */
|
|
55
|
-
path?: string;
|
|
56
|
-
/** Time-to-live for the cookie. Maps to `Max-Age`. */
|
|
57
|
-
ttl?: DurationLike;
|
|
58
|
-
/** If true, the cookie is only sent over HTTPS. Defaults to true in production. */
|
|
59
|
-
secure?: boolean;
|
|
60
|
-
/** If true, the cookie cannot be accessed by client-side scripts. */
|
|
61
|
-
httpOnly?: boolean;
|
|
62
|
-
/** SameSite policy for the cookie. Defaults to "lax". */
|
|
63
|
-
sameSite?: "strict" | "lax" | "none";
|
|
64
|
-
/** The domain for the cookie. */
|
|
65
|
-
domain?: string;
|
|
66
|
-
/** If true, the cookie value will be compressed using zlib. */
|
|
67
|
-
compress?: boolean;
|
|
68
|
-
/** If true, the cookie value will be encrypted. Requires `COOKIE_SECRET` env var. */
|
|
69
|
-
encrypt?: boolean;
|
|
70
|
-
/** If true, the cookie will be signed to prevent tampering. Requires `COOKIE_SECRET` env var. */
|
|
71
|
-
sign?: boolean;
|
|
72
|
-
}
|
|
73
|
-
interface AbstractCookieDescriptor<T extends TSchema> {
|
|
74
|
-
readonly name: string;
|
|
75
|
-
readonly options: CookieDescriptorOptions<T>;
|
|
76
|
-
set(value: Static<T>, options?: {
|
|
77
|
-
cookies?: Cookies;
|
|
78
|
-
ttl?: DurationLike;
|
|
79
|
-
}): void;
|
|
80
|
-
get(options?: {
|
|
81
|
-
cookies?: Cookies;
|
|
82
|
-
}): Static<T> | undefined;
|
|
83
|
-
del(options?: {
|
|
84
|
-
cookies?: Cookies;
|
|
85
|
-
}): void;
|
|
86
|
-
}
|
|
87
|
-
declare class CookieDescriptor<T extends TSchema> extends Descriptor<CookieDescriptorOptions<T>> implements AbstractCookieDescriptor<T> {
|
|
88
|
-
protected readonly serverCookiesProvider: ServerCookiesProvider;
|
|
89
|
-
get schema(): T;
|
|
90
|
-
get name(): string;
|
|
91
|
-
/**
|
|
92
|
-
* Sets the cookie with the given value in the current request's response.
|
|
93
|
-
*/
|
|
94
|
-
set(value: Static<T>, options?: {
|
|
95
|
-
cookies?: Cookies;
|
|
96
|
-
ttl?: DurationLike;
|
|
97
|
-
}): void;
|
|
98
|
-
/**
|
|
99
|
-
* Gets the cookie value from the current request. Returns undefined if not found or invalid.
|
|
100
|
-
*/
|
|
101
|
-
get(options?: {
|
|
102
|
-
cookies?: Cookies;
|
|
103
|
-
}): Static<T> | undefined;
|
|
104
|
-
/**
|
|
105
|
-
* Deletes the cookie in the current request's response.
|
|
106
|
-
*/
|
|
107
|
-
del(options?: {
|
|
108
|
-
cookies?: Cookies;
|
|
109
|
-
}): void;
|
|
110
|
-
}
|
|
111
|
-
interface Cookies {
|
|
112
|
-
req: Record<string, string>;
|
|
113
|
-
res: Record<string, Cookie | null>;
|
|
114
|
-
}
|
|
115
|
-
interface Cookie {
|
|
116
|
-
value: string;
|
|
117
|
-
path?: string;
|
|
118
|
-
maxAge?: number;
|
|
119
|
-
secure?: boolean;
|
|
120
|
-
httpOnly?: boolean;
|
|
121
|
-
sameSite?: "strict" | "lax" | "none";
|
|
122
|
-
domain?: string;
|
|
123
|
-
}
|
|
124
|
-
//#endregion
|
|
125
|
-
//#region src/server-cookies/index.d.ts
|
|
126
|
-
declare module "alepha/server" {
|
|
127
|
-
interface ServerRequest {
|
|
128
|
-
cookies: Cookies;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Provides HTTP cookie management capabilities for server requests and responses with type-safe cookie descriptors.
|
|
133
|
-
*
|
|
134
|
-
* The server-cookies module enables declarative cookie handling using the `$cookie` descriptor on class properties.
|
|
135
|
-
* It offers automatic cookie parsing, secure cookie configuration, and seamless integration with server routes
|
|
136
|
-
* for managing user sessions, preferences, and authentication tokens.
|
|
137
|
-
*
|
|
138
|
-
* @see {@link $cookie}
|
|
139
|
-
* @module alepha.server.cookies
|
|
140
|
-
*/
|
|
141
|
-
declare const AlephaServerCookies: alepha1.Service<alepha1.Module>;
|
|
142
|
-
//#endregion
|
|
143
|
-
export { $cookie, AbstractCookieDescriptor, AlephaServerCookies, Cookie, CookieDescriptor, CookieDescriptorOptions, Cookies, ServerCookiesProvider };
|
|
144
|
-
//# sourceMappingURL=index.d.cts.map
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
let alepha = require("alepha");
|
|
2
|
-
let alepha_logger = require("alepha/logger");
|
|
3
|
-
let alepha_server = require("alepha/server");
|
|
4
|
-
|
|
5
|
-
//#region src/server-cors/providers/ServerCorsProvider.ts
|
|
6
|
-
/**
|
|
7
|
-
* CORS configuration atom (global defaults)
|
|
8
|
-
*/
|
|
9
|
-
const corsOptions = (0, alepha.$atom)({
|
|
10
|
-
name: "alepha.server.cors.options",
|
|
11
|
-
schema: alepha.t.object({
|
|
12
|
-
origin: alepha.t.optional(alepha.t.string({
|
|
13
|
-
description: "Allowed origins (* for all, string for single, comma-separated for multiple)",
|
|
14
|
-
default: "*"
|
|
15
|
-
})),
|
|
16
|
-
methods: alepha.t.array(alepha.t.string(), {
|
|
17
|
-
description: "Allowed HTTP methods",
|
|
18
|
-
default: [
|
|
19
|
-
"GET",
|
|
20
|
-
"POST",
|
|
21
|
-
"PUT",
|
|
22
|
-
"PATCH",
|
|
23
|
-
"DELETE",
|
|
24
|
-
"OPTIONS"
|
|
25
|
-
]
|
|
26
|
-
}),
|
|
27
|
-
headers: alepha.t.array(alepha.t.string(), {
|
|
28
|
-
description: "Allowed headers",
|
|
29
|
-
default: ["Content-Type", "Authorization"]
|
|
30
|
-
}),
|
|
31
|
-
credentials: alepha.t.optional(alepha.t.boolean({
|
|
32
|
-
description: "Allow credentials",
|
|
33
|
-
default: true
|
|
34
|
-
})),
|
|
35
|
-
maxAge: alepha.t.optional(alepha.t.number({ description: "Preflight cache duration in seconds" }))
|
|
36
|
-
}),
|
|
37
|
-
default: {
|
|
38
|
-
origin: "*",
|
|
39
|
-
methods: [
|
|
40
|
-
"GET",
|
|
41
|
-
"POST",
|
|
42
|
-
"PUT",
|
|
43
|
-
"PATCH",
|
|
44
|
-
"DELETE",
|
|
45
|
-
"OPTIONS"
|
|
46
|
-
],
|
|
47
|
-
headers: ["Content-Type", "Authorization"],
|
|
48
|
-
credentials: true
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
var ServerCorsProvider = class {
|
|
52
|
-
log = (0, alepha_logger.$logger)();
|
|
53
|
-
serverRouterProvider = (0, alepha.$inject)(alepha_server.ServerRouterProvider);
|
|
54
|
-
globalOptions = (0, alepha.$use)(corsOptions);
|
|
55
|
-
/**
|
|
56
|
-
* Registered CORS configurations with their path patterns
|
|
57
|
-
*/
|
|
58
|
-
registeredConfigs = [];
|
|
59
|
-
/**
|
|
60
|
-
* Register a CORS configuration (called by descriptors)
|
|
61
|
-
*/
|
|
62
|
-
registerCors(config) {
|
|
63
|
-
this.registeredConfigs.push(config);
|
|
64
|
-
}
|
|
65
|
-
onStart = (0, alepha.$hook)({
|
|
66
|
-
on: "start",
|
|
67
|
-
handler: async () => {
|
|
68
|
-
for (const config of this.registeredConfigs) if (config.paths) for (const pattern of config.paths) {
|
|
69
|
-
const matchedRoutes = this.serverRouterProvider.getRoutes(pattern);
|
|
70
|
-
for (const route of matchedRoutes) route.cors = this.buildCorsOptions(config);
|
|
71
|
-
}
|
|
72
|
-
if (this.registeredConfigs.length > 0) this.log.info(`Initialized with ${this.registeredConfigs.length} registered CORS configurations.`);
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
configure = (0, alepha.$hook)({
|
|
76
|
-
on: "configure",
|
|
77
|
-
handler: () => {
|
|
78
|
-
const routes = this.serverRouterProvider.getRoutes();
|
|
79
|
-
for (const route of routes) {
|
|
80
|
-
if (!route.method || route.method === "GET" || route.method === "OPTIONS") continue;
|
|
81
|
-
this.serverRouterProvider.createRoute({
|
|
82
|
-
path: route.path,
|
|
83
|
-
method: "OPTIONS",
|
|
84
|
-
handler: ({ reply }) => {
|
|
85
|
-
reply.setStatus(204);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
onRequest = (0, alepha.$hook)({
|
|
92
|
-
on: "server:onRequest",
|
|
93
|
-
handler: ({ route, request }) => {
|
|
94
|
-
const corsConfig = route.cors ?? this.globalOptions;
|
|
95
|
-
this.applyCorsHeaders(request, corsConfig);
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
/**
|
|
99
|
-
* Build complete CORS options by merging with global defaults
|
|
100
|
-
*/
|
|
101
|
-
buildCorsOptions(config) {
|
|
102
|
-
return {
|
|
103
|
-
origin: config.origin ?? this.globalOptions.origin,
|
|
104
|
-
methods: config.methods ?? this.globalOptions.methods,
|
|
105
|
-
headers: config.headers ?? this.globalOptions.headers,
|
|
106
|
-
credentials: config.credentials ?? this.globalOptions.credentials,
|
|
107
|
-
maxAge: config.maxAge ?? this.globalOptions.maxAge
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Apply CORS headers to the response
|
|
112
|
-
*/
|
|
113
|
-
applyCorsHeaders(request, options) {
|
|
114
|
-
const reqOrigin = request.headers.origin;
|
|
115
|
-
const { origin, methods, headers, credentials, maxAge } = options;
|
|
116
|
-
if (reqOrigin && this.isOriginAllowed(reqOrigin, origin)) request.reply.setHeader("Access-Control-Allow-Origin", reqOrigin);
|
|
117
|
-
if (credentials) request.reply.setHeader("Access-Control-Allow-Credentials", "true");
|
|
118
|
-
request.reply.setHeader("Access-Control-Allow-Methods", methods.join(", "));
|
|
119
|
-
request.reply.setHeader("Access-Control-Allow-Headers", headers.join(", "));
|
|
120
|
-
if (maxAge != null) request.reply.setHeader("Access-Control-Max-Age", String(maxAge));
|
|
121
|
-
}
|
|
122
|
-
isOriginAllowed(origin, allowed) {
|
|
123
|
-
if (!allowed) return false;
|
|
124
|
-
if (allowed === "*") return true;
|
|
125
|
-
return allowed.split(",").map((o) => o.trim()).includes(origin ?? "");
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
//#endregion
|
|
130
|
-
//#region src/server-cors/descriptors/$cors.ts
|
|
131
|
-
/**
|
|
132
|
-
* Declares CORS configuration for specific server routes.
|
|
133
|
-
* This descriptor provides path-based CORS configuration.
|
|
134
|
-
*
|
|
135
|
-
* @example
|
|
136
|
-
* ```ts
|
|
137
|
-
* class ApiService {
|
|
138
|
-
* // Apply specific CORS to API routes
|
|
139
|
-
* cors = $cors({
|
|
140
|
-
* paths: ["/api/*"],
|
|
141
|
-
* origin: "https://app.example.com",
|
|
142
|
-
* credentials: true,
|
|
143
|
-
* });
|
|
144
|
-
* }
|
|
145
|
-
* ```
|
|
146
|
-
*/
|
|
147
|
-
const $cors = (options) => {
|
|
148
|
-
return (0, alepha.createDescriptor)(CorsDescriptor, options);
|
|
149
|
-
};
|
|
150
|
-
var CorsDescriptor = class extends alepha.Descriptor {
|
|
151
|
-
serverCorsProvider = (0, alepha.$inject)(ServerCorsProvider);
|
|
152
|
-
get name() {
|
|
153
|
-
return this.options.name ?? `${this.config.propertyKey}`;
|
|
154
|
-
}
|
|
155
|
-
onInit() {
|
|
156
|
-
this.serverCorsProvider.registerCors(this.options);
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
$cors[alepha.KIND] = CorsDescriptor;
|
|
160
|
-
|
|
161
|
-
//#endregion
|
|
162
|
-
//#region src/server-cors/index.ts
|
|
163
|
-
/**
|
|
164
|
-
* Plugin for configuring CORS on the Alepha server.
|
|
165
|
-
*
|
|
166
|
-
* @example
|
|
167
|
-
* ```ts
|
|
168
|
-
* import { Alepha, $route } from "alepha";
|
|
169
|
-
* import { AlephaServerCors, $cors } from "alepha/server-cors";
|
|
170
|
-
*
|
|
171
|
-
* class ApiService {
|
|
172
|
-
* // Global CORS is applied via corsOptions atom
|
|
173
|
-
*
|
|
174
|
-
* // Path-specific CORS for API routes
|
|
175
|
-
* apiCors = $cors({
|
|
176
|
-
* paths: ["/api/*"],
|
|
177
|
-
* origin: "https://app.example.com",
|
|
178
|
-
* credentials: true,
|
|
179
|
-
* });
|
|
180
|
-
*
|
|
181
|
-
* route = $route({
|
|
182
|
-
* path: "/api/data",
|
|
183
|
-
* method: "POST",
|
|
184
|
-
* handler: () => ({ data: "hello" }),
|
|
185
|
-
* });
|
|
186
|
-
* }
|
|
187
|
-
* ```
|
|
188
|
-
*/
|
|
189
|
-
const AlephaServerCors = (0, alepha.$module)({
|
|
190
|
-
name: "alepha.server.cors",
|
|
191
|
-
descriptors: [$cors],
|
|
192
|
-
services: [ServerCorsProvider]
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
//#endregion
|
|
196
|
-
exports.$cors = $cors;
|
|
197
|
-
exports.AlephaServerCors = AlephaServerCors;
|
|
198
|
-
exports.CorsDescriptor = CorsDescriptor;
|
|
199
|
-
exports.ServerCorsProvider = ServerCorsProvider;
|
|
200
|
-
exports.corsOptions = corsOptions;
|
|
201
|
-
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["t","ServerRouterProvider","Descriptor","KIND"],"sources":["../../src/server-cors/providers/ServerCorsProvider.ts","../../src/server-cors/descriptors/$cors.ts","../../src/server-cors/index.ts"],"sourcesContent":["import { $atom, $hook, $inject, $use, type Static, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { ServerRouterProvider } from \"alepha/server\";\nimport type { CorsDescriptorConfig } from \"../descriptors/$cors.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * CORS configuration atom (global defaults)\n */\nexport const corsOptions = $atom({\n name: \"alepha.server.cors.options\",\n schema: t.object({\n origin: t.optional(\n t.string({\n description:\n \"Allowed origins (* for all, string for single, comma-separated for multiple)\",\n default: \"*\",\n }),\n ),\n methods: t.array(t.string(), {\n description: \"Allowed HTTP methods\",\n default: [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\"],\n }),\n headers: t.array(t.string(), {\n description: \"Allowed headers\",\n default: [\"Content-Type\", \"Authorization\"],\n }),\n credentials: t.optional(\n t.boolean({\n description: \"Allow credentials\",\n default: true,\n }),\n ),\n maxAge: t.optional(\n t.number({\n description: \"Preflight cache duration in seconds\",\n }),\n ),\n }),\n default: {\n origin: \"*\",\n methods: [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\"],\n headers: [\"Content-Type\", \"Authorization\"],\n credentials: true,\n },\n});\n\nexport type CorsOptions = Static<typeof corsOptions.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [corsOptions.key]: CorsOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class ServerCorsProvider {\n protected readonly log = $logger();\n protected readonly serverRouterProvider = $inject(ServerRouterProvider);\n protected readonly globalOptions = $use(corsOptions);\n\n /**\n * Registered CORS configurations with their path patterns\n */\n public readonly registeredConfigs: CorsDescriptorConfig[] = [];\n\n /**\n * Register a CORS configuration (called by descriptors)\n */\n public registerCors(config: CorsDescriptorConfig): void {\n this.registeredConfigs.push(config);\n }\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n // Apply path-specific CORS configs to routes\n for (const config of this.registeredConfigs) {\n if (config.paths) {\n for (const pattern of config.paths) {\n const matchedRoutes = this.serverRouterProvider.getRoutes(pattern);\n for (const route of matchedRoutes) {\n route.cors = this.buildCorsOptions(config);\n }\n }\n }\n }\n\n if (this.registeredConfigs.length > 0) {\n this.log.info(\n `Initialized with ${this.registeredConfigs.length} registered CORS configurations.`,\n );\n }\n },\n });\n\n protected readonly configure = $hook({\n on: \"configure\",\n handler: () => {\n const routes = this.serverRouterProvider.getRoutes();\n for (const route of routes) {\n if (\n !route.method ||\n route.method === \"GET\" ||\n route.method === \"OPTIONS\"\n ) {\n continue;\n }\n\n this.serverRouterProvider.createRoute({\n path: route.path,\n method: \"OPTIONS\",\n handler: ({ reply }) => {\n reply.setStatus(204);\n },\n });\n }\n },\n });\n\n protected readonly onRequest = $hook({\n on: \"server:onRequest\",\n handler: ({ route, request }) => {\n // Use route-specific CORS if defined, otherwise use global options\n const corsConfig = route.cors ?? this.globalOptions;\n this.applyCorsHeaders(request, corsConfig);\n },\n });\n\n /**\n * Build complete CORS options by merging with global defaults\n */\n protected buildCorsOptions(config: CorsDescriptorConfig): CorsOptions {\n return {\n origin: config.origin ?? this.globalOptions.origin,\n methods: config.methods ?? this.globalOptions.methods,\n headers: config.headers ?? this.globalOptions.headers,\n credentials: config.credentials ?? this.globalOptions.credentials,\n maxAge: config.maxAge ?? this.globalOptions.maxAge,\n };\n }\n\n /**\n * Apply CORS headers to the response\n */\n protected applyCorsHeaders(\n request: {\n headers: { origin?: string };\n reply: { setHeader: (name: string, value: string) => void };\n },\n options: CorsOptions,\n ): void {\n const reqOrigin = request.headers.origin;\n const { origin, methods, headers, credentials, maxAge } = options;\n\n if (reqOrigin && this.isOriginAllowed(reqOrigin, origin)) {\n request.reply.setHeader(\"Access-Control-Allow-Origin\", reqOrigin);\n }\n\n if (credentials) {\n request.reply.setHeader(\"Access-Control-Allow-Credentials\", \"true\");\n }\n\n request.reply.setHeader(\"Access-Control-Allow-Methods\", methods.join(\", \"));\n request.reply.setHeader(\"Access-Control-Allow-Headers\", headers.join(\", \"));\n\n if (maxAge != null) {\n request.reply.setHeader(\"Access-Control-Max-Age\", String(maxAge));\n }\n }\n\n public isOriginAllowed(\n origin: string | undefined,\n allowed: CorsOptions[\"origin\"],\n ): boolean {\n if (!allowed) return false;\n if (allowed === \"*\") return true;\n return allowed\n .split(\",\")\n .map((o) => o.trim())\n .includes(origin ?? \"\");\n }\n}\n\nexport type ServerCorsProviderOptions = CorsOptions;\n","import { $inject, createDescriptor, Descriptor, KIND } from \"alepha\";\nimport type { CorsOptions } from \"../providers/ServerCorsProvider.ts\";\nimport { ServerCorsProvider } from \"../providers/ServerCorsProvider.ts\";\n\n/**\n * Declares CORS configuration for specific server routes.\n * This descriptor provides path-based CORS configuration.\n *\n * @example\n * ```ts\n * class ApiService {\n * // Apply specific CORS to API routes\n * cors = $cors({\n * paths: [\"/api/*\"],\n * origin: \"https://app.example.com\",\n * credentials: true,\n * });\n * }\n * ```\n */\nexport const $cors = (\n options: CorsDescriptorConfig,\n): AbstractCorsDescriptor => {\n return createDescriptor(CorsDescriptor, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface CorsDescriptorConfig extends Partial<CorsOptions> {\n /** Name identifier for this CORS config (default: property key) */\n name?: string;\n /** Path patterns to match (supports wildcards like /api/*) */\n paths?: string[];\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface AbstractCorsDescriptor {\n readonly name: string;\n readonly options: CorsDescriptorConfig;\n}\n\nexport class CorsDescriptor\n extends Descriptor<CorsDescriptorConfig>\n implements AbstractCorsDescriptor\n{\n protected readonly serverCorsProvider = $inject(ServerCorsProvider);\n\n public get name(): string {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n protected onInit() {\n // Register this CORS configuration with the provider\n this.serverCorsProvider.registerCors(this.options);\n }\n}\n\n$cors[KIND] = CorsDescriptor;\n","import { $module } from \"alepha\";\nimport { $cors } from \"./descriptors/$cors.ts\";\nimport {\n type CorsOptions,\n ServerCorsProvider,\n} from \"./providers/ServerCorsProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./descriptors/$cors.ts\";\nexport * from \"./providers/ServerCorsProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha/server\" {\n interface ServerRoute {\n /**\n * Route-specific CORS configuration.\n * If set, overrides the global CORS options for this route.\n */\n cors?: CorsOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Plugin for configuring CORS on the Alepha server.\n *\n * @example\n * ```ts\n * import { Alepha, $route } from \"alepha\";\n * import { AlephaServerCors, $cors } from \"alepha/server-cors\";\n *\n * class ApiService {\n * // Global CORS is applied via corsOptions atom\n *\n * // Path-specific CORS for API routes\n * apiCors = $cors({\n * paths: [\"/api/*\"],\n * origin: \"https://app.example.com\",\n * credentials: true,\n * });\n *\n * route = $route({\n * path: \"/api/data\",\n * method: \"POST\",\n * handler: () => ({ data: \"hello\" }),\n * });\n * }\n * ```\n */\nexport const AlephaServerCors = $module({\n name: \"alepha.server.cors\",\n descriptors: [$cors],\n services: [ServerCorsProvider],\n});\n"],"mappings":"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"}
|