lambder 2.0.18 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +162 -41
- package/dist/Lambder.d.ts +154 -46
- package/dist/Lambder.js +312 -166
- package/dist/LambderCaller.js +6 -3
- package/dist/LambderContext.d.ts +20 -9
- package/dist/LambderContext.js +57 -17
- package/dist/LambderCors.d.ts +12 -0
- package/dist/LambderCors.js +30 -0
- package/dist/LambderHtml.d.ts +33 -0
- package/dist/LambderHtml.js +62 -0
- package/dist/LambderMSW.d.ts +16 -1
- package/dist/LambderMSW.js +5 -9
- package/dist/LambderPublicFiles.d.ts +47 -0
- package/dist/LambderPublicFiles.js +108 -0
- package/dist/LambderResolver.d.ts +30 -31
- package/dist/LambderResolver.js +29 -43
- package/dist/LambderResponse.d.ts +71 -0
- package/dist/LambderResponse.js +196 -0
- package/dist/LambderResponseBuilder.d.ts +58 -33
- package/dist/LambderResponseBuilder.js +114 -167
- package/dist/LambderRouting.d.ts +23 -0
- package/dist/LambderRouting.js +67 -0
- package/dist/LambderSessionController.d.ts +13 -1
- package/dist/LambderSessionController.js +33 -10
- package/dist/LambderSessionManager.d.ts +3 -1
- package/dist/LambderSessionManager.js +15 -6
- package/dist/LambderTemplatingEngine.d.ts +87 -0
- package/dist/LambderTemplatingEngine.js +156 -0
- package/dist/index.d.ts +14 -2
- package/dist/index.js +10 -1
- package/dist/node-polyfills.d.ts +4 -2
- package/dist/node-polyfills.js +28 -0
- package/package.json +7 -5
- package/.eslintrc.cjs +0 -26
- package/.vscode/settings.json +0 -26
- package/deploy +0 -22
- package/dist/LambderUtils.d.ts +0 -10
- package/dist/LambderUtils.js +0 -70
- package/docs/DYNAMODB_SETUP.md +0 -96
- package/docs/LAMBDER_MSW.md +0 -409
- package/docs/TYPE_SAFE_QUICK_START.md +0 -77
- package/examples/msw-testing-example.ts +0 -280
- package/examples/secure-session-example.ts +0 -207
- package/examples/zod-chained-api-example.ts +0 -63
- package/src/Lambder.ts +0 -430
- package/src/LambderApiContract.ts +0 -20
- package/src/LambderCaller.ts +0 -238
- package/src/LambderContext.ts +0 -78
- package/src/LambderMSW.ts +0 -180
- package/src/LambderResolver.ts +0 -101
- package/src/LambderResponseBuilder.ts +0 -332
- package/src/LambderSessionController.ts +0 -114
- package/src/LambderSessionManager.ts +0 -217
- package/src/LambderUtils.ts +0 -75
- package/src/index.ts +0 -17
- package/src/node-polyfills.ts +0 -27
- package/tests/error-handling.test.ts +0 -585
- package/tests/file-serving.test.ts +0 -194
- package/tests/fixtures/public/index.html +0 -1
- package/tests/fixtures/public/main.css +0 -1
- package/tests/hooks.test.ts +0 -561
- package/tests/output-type-runtime.test.ts +0 -381
- package/tests/redirect.test.ts +0 -88
- package/tests/routes.test.ts +0 -543
- package/tests/session.test.ts +0 -1083
- package/tests/use-plugin.test.ts +0 -460
- package/tsconfig.json +0 -24
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { LambderRenderContext, LambderSessionRenderContext } from "./LambderContext.js";
|
|
2
|
-
import type LambderSessionManager from "./LambderSessionManager.js";
|
|
3
|
-
import type { LambderSessionContext } from "./LambderSessionManager.js";
|
|
4
|
-
|
|
5
|
-
export default class LambderSessionController<TSessionData = any> {
|
|
6
|
-
lambderSessionManager: LambderSessionManager;
|
|
7
|
-
sessionTokenCookieKey: string;
|
|
8
|
-
sessionCsrfCookieKey: string;
|
|
9
|
-
ctx: LambderRenderContext<any> | LambderSessionRenderContext<any, TSessionData>; // Internal context with mutable session property
|
|
10
|
-
|
|
11
|
-
constructor(
|
|
12
|
-
{
|
|
13
|
-
lambderSessionManager,
|
|
14
|
-
sessionTokenCookieKey,
|
|
15
|
-
sessionCsrfCookieKey,
|
|
16
|
-
ctx,
|
|
17
|
-
}: {
|
|
18
|
-
lambderSessionManager: LambderSessionManager,
|
|
19
|
-
sessionTokenCookieKey: string,
|
|
20
|
-
sessionCsrfCookieKey: string,
|
|
21
|
-
ctx: LambderRenderContext<any> | LambderSessionRenderContext<any, TSessionData>,
|
|
22
|
-
}
|
|
23
|
-
){
|
|
24
|
-
this.lambderSessionManager = lambderSessionManager;
|
|
25
|
-
this.sessionTokenCookieKey = sessionTokenCookieKey;
|
|
26
|
-
this.sessionCsrfCookieKey = sessionCsrfCookieKey;
|
|
27
|
-
this.ctx = ctx;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
private areRequestSessionTokensValid(): boolean {
|
|
31
|
-
const sessionToken = this.ctx.cookie?.[this.sessionTokenCookieKey];
|
|
32
|
-
const isSessionTokenValid = sessionToken && sessionToken?.split(":")?.length === 2;
|
|
33
|
-
|
|
34
|
-
if(this.ctx._otherInternal.isApiCall){
|
|
35
|
-
const csrfToken = this.ctx.post?.token;
|
|
36
|
-
const isCsrfTokenValid = typeof csrfToken === "string" && csrfToken.length > 0
|
|
37
|
-
return isSessionTokenValid && isCsrfTokenValid;
|
|
38
|
-
}else{
|
|
39
|
-
return isSessionTokenValid;
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
async createSession (sessionKey: string, data?: TSessionData, ttlInSeconds?: number): Promise<LambderSessionContext<TSessionData>> {
|
|
44
|
-
const session = await this.lambderSessionManager.createSession(sessionKey, data, ttlInSeconds);
|
|
45
|
-
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionTokenCookieKey}=${session.sessionToken}; Expires=${new Date(session.expiresAt * 1000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure` });
|
|
46
|
-
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionCsrfCookieKey}=${session.csrfToken}; Expires=${new Date(session.expiresAt * 1000).toUTCString()}; Path=/; SameSite=Lax; Secure` });
|
|
47
|
-
this.ctx.session = session;
|
|
48
|
-
return this.ctx.session;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
async regenerateSession (): Promise<LambderSessionContext<TSessionData>> {
|
|
52
|
-
if(!this.ctx.session) throw new Error("Session not found.");
|
|
53
|
-
const newSession = await this.lambderSessionManager.regenerateSession(this.ctx.session);
|
|
54
|
-
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionTokenCookieKey}=${newSession.sessionToken}; Expires=${new Date(newSession.expiresAt * 1000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure` });
|
|
55
|
-
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionCsrfCookieKey}=${newSession.csrfToken}; Expires=${new Date(newSession.expiresAt * 1000).toUTCString()}; Path=/; SameSite=Lax; Secure` });
|
|
56
|
-
this.ctx.session = newSession;
|
|
57
|
-
return this.ctx.session;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
async fetchSession (): Promise<LambderSessionContext<TSessionData>>{
|
|
61
|
-
if(!this.areRequestSessionTokensValid()){ throw new Error("Session tokens are invalid"); }
|
|
62
|
-
|
|
63
|
-
const sessionToken = this.ctx.cookie?.[this.sessionTokenCookieKey];
|
|
64
|
-
if(!sessionToken) throw new Error("Session token not found");
|
|
65
|
-
|
|
66
|
-
const session = await this.lambderSessionManager.getSession(sessionToken);
|
|
67
|
-
if(!session) throw new Error("Session not found");
|
|
68
|
-
|
|
69
|
-
if(!this.isSessionValid(session)) throw new Error("Invalid session");
|
|
70
|
-
this.ctx.session = session;
|
|
71
|
-
return session;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
async fetchSessionIfExists (): Promise<LambderSessionContext|null> {
|
|
75
|
-
try {
|
|
76
|
-
return await this.fetchSession();
|
|
77
|
-
}catch(err){
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
isSessionValid(session: any): boolean {
|
|
83
|
-
if(this.ctx._otherInternal.isApiCall){
|
|
84
|
-
const sessionToken = this.ctx.cookie?.[this.sessionTokenCookieKey];
|
|
85
|
-
const csrfToken = this.ctx.post?.token;
|
|
86
|
-
return this.lambderSessionManager.isSessionValid(session, sessionToken, csrfToken);
|
|
87
|
-
}else{
|
|
88
|
-
const sessionToken = this.ctx.cookie?.[this.sessionTokenCookieKey];
|
|
89
|
-
return this.lambderSessionManager.isSessionValid(session, sessionToken, null, true);
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
async updateSessionData (newData: any): Promise<LambderSessionContext> {
|
|
94
|
-
if(!this.ctx.session) throw new Error("Session not found.");
|
|
95
|
-
this.ctx.session = await this.lambderSessionManager.updateSessionData(this.ctx.session, newData);
|
|
96
|
-
return this.ctx.session;
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
async endSession (){
|
|
100
|
-
if(!this.ctx.session) throw new Error("Session not found.");
|
|
101
|
-
await this.lambderSessionManager.deleteSession(this.ctx.session);
|
|
102
|
-
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionTokenCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure` });
|
|
103
|
-
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionCsrfCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; SameSite=Lax; Secure` });
|
|
104
|
-
(this.ctx as any).session = null;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
async endSessionAll (){
|
|
108
|
-
if(!this.ctx.session) throw new Error("Session not found.");
|
|
109
|
-
await this.lambderSessionManager.deleteSessionAll(this.ctx.session);
|
|
110
|
-
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionTokenCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; HttpOnly; SameSite=Lax; Secure` });
|
|
111
|
-
this.ctx._otherInternal.addHeaderFnAccumulator.push({ key: "Set-Cookie", value: `${this.sessionCsrfCookieKey}=0; Expires=${new Date(Date.now() - 100000).toUTCString()}; Path=/; SameSite=Lax; Secure` });
|
|
112
|
-
(this.ctx as any).session = null;
|
|
113
|
-
};
|
|
114
|
-
};
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import crypto from "crypto";
|
|
2
|
-
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
3
|
-
import { DynamoDBDocumentClient, QueryCommand, DeleteCommand, PutCommand, GetCommand } from "@aws-sdk/lib-dynamodb";
|
|
4
|
-
|
|
5
|
-
export type LambderSessionContext<SessionData = any> = {
|
|
6
|
-
[x: string]: any;
|
|
7
|
-
sessionToken: string;
|
|
8
|
-
csrfToken: string;
|
|
9
|
-
sessionKey: string;
|
|
10
|
-
data: SessionData;
|
|
11
|
-
createdAt: number;
|
|
12
|
-
expiresAt: number;
|
|
13
|
-
lastAccessedAt: number;
|
|
14
|
-
ttlInSeconds: number;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export default class LambderSessionManager{
|
|
19
|
-
private tableName: string;
|
|
20
|
-
private sessionSalt: string;
|
|
21
|
-
private partitionKey: string;
|
|
22
|
-
private sortKey: string;
|
|
23
|
-
private ddbDocumentClient: DynamoDBDocumentClient;
|
|
24
|
-
private enableSlidingExpiration: boolean;
|
|
25
|
-
|
|
26
|
-
constructor(
|
|
27
|
-
{
|
|
28
|
-
tableName, tableRegion,
|
|
29
|
-
partitionKey, sortKey,
|
|
30
|
-
sessionSalt,
|
|
31
|
-
enableSlidingExpiration = true,
|
|
32
|
-
}: {
|
|
33
|
-
tableName: string, tableRegion: string,
|
|
34
|
-
partitionKey: string, sortKey: string,
|
|
35
|
-
sessionSalt: string,
|
|
36
|
-
enableSlidingExpiration?: boolean,
|
|
37
|
-
}
|
|
38
|
-
){
|
|
39
|
-
this.tableName = tableName;
|
|
40
|
-
this.sessionSalt = sessionSalt;
|
|
41
|
-
this.partitionKey = partitionKey;
|
|
42
|
-
this.sortKey = sortKey;
|
|
43
|
-
this.enableSlidingExpiration = enableSlidingExpiration;
|
|
44
|
-
|
|
45
|
-
const ddbClient = new DynamoDBClient({ region: tableRegion });
|
|
46
|
-
this.ddbDocumentClient = DynamoDBDocumentClient.from(ddbClient);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
private sessionUserKeyHasher(password:string){
|
|
50
|
-
return crypto.createHash("sha256")
|
|
51
|
-
.update(`${password}${this.sessionSalt}`)
|
|
52
|
-
.digest("hex");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
private constantTimeCompare(a: string, b: string): boolean {
|
|
56
|
-
if (a.length !== b.length) return false;
|
|
57
|
-
const bufferA = Buffer.from(a, 'utf8');
|
|
58
|
-
const bufferB = Buffer.from(b, 'utf8');
|
|
59
|
-
return crypto.timingSafeEqual(new Uint8Array(bufferA), new Uint8Array(bufferB));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
private async ddbGetItem<T=any>(key:Record<string,string|number>){
|
|
63
|
-
const response = await this.ddbDocumentClient.send(
|
|
64
|
-
new GetCommand({ TableName: this.tableName, Key: key, ConsistentRead: true })
|
|
65
|
-
);
|
|
66
|
-
if(response.Item) return response.Item as T;
|
|
67
|
-
return null;
|
|
68
|
-
};
|
|
69
|
-
private async ddbPutItem(item: Record<string,any>){
|
|
70
|
-
return await this.ddbDocumentClient.send(
|
|
71
|
-
new PutCommand({ TableName: this.tableName, Item: item, })
|
|
72
|
-
);
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
private async ddbDeleteItem(key:Record<string,string|number>){
|
|
76
|
-
return await this.ddbDocumentClient.send(
|
|
77
|
-
new DeleteCommand({ TableName: this.tableName, Key: key, })
|
|
78
|
-
);
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
private async ddbQueryAllByPartitionKey (partitionValue: string){
|
|
82
|
-
const params: any = {
|
|
83
|
-
TableName: this.tableName,
|
|
84
|
-
KeyConditionExpression: "#pk = :pv",
|
|
85
|
-
ExpressionAttributeNames:{ "#pk": this.partitionKey },
|
|
86
|
-
ExpressionAttributeValues: { ":pv": partitionValue },
|
|
87
|
-
}
|
|
88
|
-
const queryResults: any[] = [];
|
|
89
|
-
do{
|
|
90
|
-
const {Items, LastEvaluatedKey} = await this.ddbDocumentClient.send(new QueryCommand(params));
|
|
91
|
-
if(Items) queryResults.push(...Items);
|
|
92
|
-
params.ExclusiveStartKey = LastEvaluatedKey;
|
|
93
|
-
if(typeof LastEvaluatedKey == "undefined") return queryResults;
|
|
94
|
-
// eslint-disable-next-line no-constant-condition
|
|
95
|
-
} while (true);
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
private async ddbDeleteAllByPartitionKey(partitionValue: string){
|
|
99
|
-
const queryResults = await this.ddbQueryAllByPartitionKey(partitionValue);
|
|
100
|
-
for(const item of queryResults){
|
|
101
|
-
await this.ddbDocumentClient.send(new DeleteCommand({
|
|
102
|
-
TableName: this.tableName,
|
|
103
|
-
Key: { [this.partitionKey]: partitionValue, [this.sortKey]: item[this.sortKey] }
|
|
104
|
-
}));
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
public async createSession(
|
|
109
|
-
sessionKey: string,
|
|
110
|
-
data: any = {},
|
|
111
|
-
ttlInSeconds:number = 30*24*60*60
|
|
112
|
-
): Promise<LambderSessionContext> {
|
|
113
|
-
const sessionKeyHash = this.sessionUserKeyHasher(sessionKey);
|
|
114
|
-
const sessionSortKey = crypto.randomBytes(32).toString("hex");
|
|
115
|
-
const sessionToken = `${sessionKeyHash}:${sessionSortKey}`;
|
|
116
|
-
const csrfToken = crypto.randomBytes(32).toString("hex");
|
|
117
|
-
const createdAt = Math.floor(Date.now()/1000);
|
|
118
|
-
const lastAccessedAt = createdAt;
|
|
119
|
-
const expiresAt = Number(createdAt) + Number(ttlInSeconds);
|
|
120
|
-
|
|
121
|
-
const session = {
|
|
122
|
-
[this.partitionKey]: sessionKeyHash,
|
|
123
|
-
[this.sortKey]: sessionSortKey,
|
|
124
|
-
sessionToken, csrfToken,
|
|
125
|
-
sessionKey, data,
|
|
126
|
-
createdAt, lastAccessedAt, expiresAt, ttlInSeconds
|
|
127
|
-
};
|
|
128
|
-
await this.ddbPutItem(session);
|
|
129
|
-
return session;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
public async updateSessionData(
|
|
133
|
-
session: LambderSessionContext,
|
|
134
|
-
newData?: any
|
|
135
|
-
): Promise<LambderSessionContext> {
|
|
136
|
-
if(!session) throw new Error("Invalid session");
|
|
137
|
-
session.data = newData;
|
|
138
|
-
session.lastAccessedAt = Math.floor(Date.now()/1000);
|
|
139
|
-
|
|
140
|
-
// Update expiration if sliding expiration is enabled
|
|
141
|
-
if(this.enableSlidingExpiration){
|
|
142
|
-
session.expiresAt = session.lastAccessedAt + session.ttlInSeconds;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
await this.ddbPutItem(session);
|
|
146
|
-
return session;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
public async getSession(sessionToken: string): Promise<LambderSessionContext|null>{
|
|
150
|
-
const [ sessionKeyHash, sessionSortKey ] = sessionToken.split(":");
|
|
151
|
-
if(!sessionKeyHash || !sessionSortKey) return null;
|
|
152
|
-
try{
|
|
153
|
-
let session = await this.ddbGetItem({
|
|
154
|
-
[this.partitionKey]: sessionKeyHash,
|
|
155
|
-
[this.sortKey]: sessionSortKey
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// Use constant error response to prevent timing attacks
|
|
159
|
-
if(!session) return null;
|
|
160
|
-
if(!session.sessionToken || !this.constantTimeCompare(session.sessionToken, sessionToken)) return null;
|
|
161
|
-
if(!session.csrfToken) return null;
|
|
162
|
-
if(!session.sessionKey) return null;
|
|
163
|
-
if(!session.createdAt) return null;
|
|
164
|
-
if(!session.expiresAt || session.expiresAt < Date.now()/1000) return null;
|
|
165
|
-
|
|
166
|
-
// Update last accessed time if sliding expiration is enabled
|
|
167
|
-
if(this.enableSlidingExpiration){
|
|
168
|
-
session.lastAccessedAt = Math.floor(Date.now()/1000);
|
|
169
|
-
session.expiresAt = session.lastAccessedAt + session.ttlInSeconds;
|
|
170
|
-
// Wait for the update to ensure it persists before Lambda freezes
|
|
171
|
-
await this.ddbPutItem(session).catch(() => {});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return session;
|
|
175
|
-
}catch(err){
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
public isSessionValid(session: any, sessionToken: any, csrfToken: any, skipCsrfTokenCheck = false){
|
|
181
|
-
if(!session) return false;
|
|
182
|
-
if(!sessionToken || typeof sessionToken !== "string") return false;
|
|
183
|
-
if(!this.constantTimeCompare(session.sessionToken, sessionToken)) return false;
|
|
184
|
-
if(!session.csrfToken) return false;
|
|
185
|
-
if(!session.sessionKey) return false;
|
|
186
|
-
if(!session.createdAt) return false;
|
|
187
|
-
if(!session.expiresAt || session.expiresAt < Date.now()/1000) return false;
|
|
188
|
-
if(!skipCsrfTokenCheck){
|
|
189
|
-
if(!csrfToken || typeof csrfToken !== "string") return false;
|
|
190
|
-
if(!this.constantTimeCompare(session.csrfToken, csrfToken)) return false;
|
|
191
|
-
}
|
|
192
|
-
return true;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
public async deleteSession(session: Record<string, any>): Promise<boolean>{
|
|
196
|
-
await this.ddbDeleteItem({
|
|
197
|
-
[this.partitionKey]: session[this.partitionKey],
|
|
198
|
-
[this.sortKey]: session[this.sortKey],
|
|
199
|
-
});
|
|
200
|
-
return true;
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
public async deleteSessionAll (session: Record<string, any>): Promise<boolean>{
|
|
204
|
-
await this.ddbDeleteAllByPartitionKey(session[this.partitionKey])
|
|
205
|
-
return true;
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
public async regenerateSession(session: LambderSessionContext): Promise<LambderSessionContext> {
|
|
209
|
-
if(!session) throw new Error("Invalid session");
|
|
210
|
-
|
|
211
|
-
// Delete old session
|
|
212
|
-
await this.deleteSession(session);
|
|
213
|
-
|
|
214
|
-
// Create new session with same sessionKey and data but new tokens
|
|
215
|
-
return await this.createSession(session.sessionKey, session.data, session.ttlInSeconds);
|
|
216
|
-
}
|
|
217
|
-
};
|
package/src/LambderUtils.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import ejs from "ejs";
|
|
2
|
-
import { getFS, getPath } from "./node-polyfills.js";
|
|
3
|
-
|
|
4
|
-
export default class LambderUtils {
|
|
5
|
-
private ejsPath?: string;
|
|
6
|
-
|
|
7
|
-
constructor(
|
|
8
|
-
{ ejsPath }: {
|
|
9
|
-
ejsPath?: string
|
|
10
|
-
} = {}
|
|
11
|
-
){
|
|
12
|
-
this.ejsPath = ejsPath;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
private async readEjsFileSync(filePath: string){
|
|
16
|
-
const fs = await getFS();
|
|
17
|
-
const path = await getPath();
|
|
18
|
-
|
|
19
|
-
if (!fs || !path) {
|
|
20
|
-
throw new Error("File system operations are not available in browser environment");
|
|
21
|
-
}
|
|
22
|
-
if(!this.ejsPath){ return "EJS PATH NOT SET!"; }
|
|
23
|
-
const ejsPath = path.resolve(this.ejsPath);
|
|
24
|
-
const normalizedFilePath = filePath.startsWith('/') ? filePath.slice(1) : filePath;
|
|
25
|
-
const absolutePath = path.resolve(ejsPath, normalizedFilePath);
|
|
26
|
-
if(!absolutePath.startsWith(ejsPath)){ return "forbidden-ejs-path"; }
|
|
27
|
-
return await fs.promises.readFile(absolutePath, 'utf-8');
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
private async checkEjsFileExist(filePath: string){
|
|
31
|
-
const fs = await getFS();
|
|
32
|
-
const path = await getPath();
|
|
33
|
-
|
|
34
|
-
if (!fs || !path) {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
if(!this.ejsPath){ return "EJS PATH NOT SET!"; }
|
|
38
|
-
const ejsPath = path.resolve(this.ejsPath);
|
|
39
|
-
const normalizedFilePath = filePath.startsWith('/') ? filePath.slice(1) : filePath;
|
|
40
|
-
const absolutePath = path.resolve(ejsPath, normalizedFilePath);
|
|
41
|
-
if(!absolutePath.startsWith(ejsPath)){ return false; }
|
|
42
|
-
try {
|
|
43
|
-
const stat = await fs.promises.stat(absolutePath);
|
|
44
|
-
return stat.isFile();
|
|
45
|
-
} catch {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
async renderEjs(
|
|
51
|
-
template: string,
|
|
52
|
-
pageData: Record<string, any>,
|
|
53
|
-
):Promise<string>{
|
|
54
|
-
const includeRenderedFile = async (filePath: string, partialData: Record<string, any>) => {
|
|
55
|
-
const template = await this.readEjsFileSync(filePath);
|
|
56
|
-
return await ejs.render(template, { page: pageData, partial: partialData, include: includeRenderedFile }, { async: true });
|
|
57
|
-
}
|
|
58
|
-
const renderedResult = await ejs.render(template, { page: pageData, include: includeRenderedFile }, { async: true });
|
|
59
|
-
|
|
60
|
-
return renderedResult;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
async renderEjsFile(
|
|
64
|
-
filePath: string,
|
|
65
|
-
pageData: Record<string, any>,
|
|
66
|
-
):Promise<string>{
|
|
67
|
-
const doesFileExist = await this.checkEjsFileExist(filePath);
|
|
68
|
-
if(!doesFileExist){
|
|
69
|
-
return "File not found: " + filePath;
|
|
70
|
-
}
|
|
71
|
-
const template = await this.readEjsFileSync(filePath);
|
|
72
|
-
return this.renderEjs(template, pageData);
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
};
|
package/src/index.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import Lambder from './Lambder.js';
|
|
2
|
-
|
|
3
|
-
export default Lambder;
|
|
4
|
-
export { default as LambderCaller } from "./LambderCaller.js";
|
|
5
|
-
export { default as LambderResponseBuilder } from "./LambderResponseBuilder.js";
|
|
6
|
-
export { default as LambderResolver } from "./LambderResolver.js";
|
|
7
|
-
export { default as LambderSessionManager } from "./LambderSessionManager.js";
|
|
8
|
-
export { default as LambderMSW } from "./LambderMSW.js";
|
|
9
|
-
|
|
10
|
-
// Type-safe API contract utilities
|
|
11
|
-
export {
|
|
12
|
-
type ApiContractShape,
|
|
13
|
-
} from "./LambderApiContract.js";
|
|
14
|
-
|
|
15
|
-
// Context types and utilities
|
|
16
|
-
export type { LambderRenderContext, LambderSessionRenderContext } from "./LambderContext.js";
|
|
17
|
-
export { createContext } from "./LambderContext.js";
|
package/src/node-polyfills.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// Node.js polyfills for browser compatibility
|
|
2
|
-
// This file provides optional Node.js modules that fail gracefully in browser environments
|
|
3
|
-
|
|
4
|
-
let fs: any = null;
|
|
5
|
-
let path: any = null;
|
|
6
|
-
|
|
7
|
-
export async function getFS(): Promise<any> {
|
|
8
|
-
try {
|
|
9
|
-
if(fs){ return fs; }
|
|
10
|
-
fs = await import('fs');
|
|
11
|
-
return fs;
|
|
12
|
-
} catch (e) {
|
|
13
|
-
// Silently fail - we're in a browser environment
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function getPath(): Promise<any> {
|
|
19
|
-
try {
|
|
20
|
-
if(path){ return path; }
|
|
21
|
-
path = await import('path');
|
|
22
|
-
return path;
|
|
23
|
-
} catch (e) {
|
|
24
|
-
// Silently fail - we're in a browser environment
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
}
|