lambder 1.0.115 → 1.0.116
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 +4 -0
- package/dist/Lambder.d.ts +8 -14
- package/dist/Lambder.js +21 -100
- package/dist/LambderSessionController.d.ts +21 -0
- package/dist/LambderSessionController.js +88 -0
- package/dist/{LambderSession.d.ts → LambderSessionManager.d.ts} +1 -1
- package/dist/{LambderSession.js → LambderSessionManager.js} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/Lambder.ts +27 -91
- package/src/LambderSessionController.ts +94 -0
- package/src/{LambderSession.ts → LambderSessionManager.ts} +1 -1
- package/src/index.ts +1 -1
package/Readme.md
CHANGED
|
@@ -243,6 +243,10 @@ After you enable the session, you can access to the session controller:
|
|
|
243
243
|
async fetchSession():
|
|
244
244
|
// Fetch and validate if there is an existing session
|
|
245
245
|
// This is automatically done for addSessionRoute and addSessionApi
|
|
246
|
+
// Throws if session not found
|
|
247
|
+
|
|
248
|
+
async fetchSessionIfExists():
|
|
249
|
+
// Runs fetchSession and returns the session if found. Otherwise return null
|
|
246
250
|
|
|
247
251
|
async updateSessionData(updatedData):
|
|
248
252
|
// Updates the active sessions data and persist it to ddb.
|
package/dist/Lambder.d.ts
CHANGED
|
@@ -2,9 +2,10 @@ import type { APIGatewayProxyEvent, APIGatewayProxyEventHeaders, Context } from
|
|
|
2
2
|
import LambderResolver from "./LambderResolver.js";
|
|
3
3
|
import LambderResponseBuilder, { LambderResolverResponse } from "./LambderResponseBuilder.js";
|
|
4
4
|
import LambderUtils from "./LambderUtils.js";
|
|
5
|
-
import { type LambderSessionContext } from "./
|
|
5
|
+
import { type LambderSessionContext } from "./LambderSessionManager.js";
|
|
6
|
+
import LambderSessionController from "./LambderSessionController.js";
|
|
6
7
|
type Path = `/${string}`;
|
|
7
|
-
type LambderRenderContext = {
|
|
8
|
+
export type LambderRenderContext = {
|
|
8
9
|
host: string;
|
|
9
10
|
path: string;
|
|
10
11
|
pathParams: Record<string, any> | null;
|
|
@@ -17,6 +18,9 @@ type LambderRenderContext = {
|
|
|
17
18
|
headers: APIGatewayProxyEventHeaders;
|
|
18
19
|
session: LambderSessionContext | null;
|
|
19
20
|
lambdaContext: Context;
|
|
21
|
+
_otherInternal: {
|
|
22
|
+
isApiCall: boolean;
|
|
23
|
+
};
|
|
20
24
|
};
|
|
21
25
|
type LambderModuleFunction = (lambderInstance: Lambder) => void | Promise<void>;
|
|
22
26
|
type ConditionFunction = (ctx: LambderRenderContext) => boolean;
|
|
@@ -41,7 +45,7 @@ export default class Lambder {
|
|
|
41
45
|
private routeFallbackHandler;
|
|
42
46
|
private apiFallbackHandler;
|
|
43
47
|
utils: LambderUtils;
|
|
44
|
-
private
|
|
48
|
+
private lambderSessionManager?;
|
|
45
49
|
private sessionTokenCookieKey;
|
|
46
50
|
private sessionCsrfCookieKey;
|
|
47
51
|
constructor({ publicPath, apiPath, ejsPath, apiVersion }: {
|
|
@@ -65,10 +69,6 @@ export default class Lambder {
|
|
|
65
69
|
setGlobalErrorHandler(globalErrorHandler: GlobalErrorHandlerFunction): void;
|
|
66
70
|
private getPatternMatch;
|
|
67
71
|
private testPatternMatch;
|
|
68
|
-
fetchSessionIfExist(ctx: LambderRenderContext): Promise<LambderSessionContext | null>;
|
|
69
|
-
fetchSession(ctx: LambderRenderContext): Promise<LambderSessionContext>;
|
|
70
|
-
private areRequestSessionTokensValid;
|
|
71
|
-
private isSessionValid;
|
|
72
72
|
private handleNoMatchedAction;
|
|
73
73
|
addModule(moduleFn: LambderModuleFunction): Promise<void>;
|
|
74
74
|
importModule(moduleImport: Promise<{
|
|
@@ -82,13 +82,7 @@ export default class Lambder {
|
|
|
82
82
|
addHook(hookEvent: 'beforeRender', hookFn: HookBeforeRenderFunction, priority?: number): Promise<void>;
|
|
83
83
|
addHook(hookEvent: 'afterRender', hookFn: HookAfterRenderFunction, priority?: number): Promise<void>;
|
|
84
84
|
addHook(hookEvent: 'fallback', hookFn: HookFallbackFunction, priority?: number): Promise<void>;
|
|
85
|
-
getSessionController(ctx: LambderRenderContext):
|
|
86
|
-
createSession: (sessionKey: string, data?: any, ttlInSeconds?: number) => Promise<LambderSessionContext>;
|
|
87
|
-
fetchSession: () => Promise<LambderSessionContext>;
|
|
88
|
-
updateSessionData: (newData: any) => Promise<LambderSessionContext>;
|
|
89
|
-
endSession: () => Promise<void>;
|
|
90
|
-
endSessionAll: () => Promise<void>;
|
|
91
|
-
};
|
|
85
|
+
getSessionController(ctx: LambderRenderContext): LambderSessionController;
|
|
92
86
|
getResponseBuilder(): LambderResponseBuilder;
|
|
93
87
|
private getResolver;
|
|
94
88
|
render(event: APIGatewayProxyEvent, lambdaContext: Context): Promise<LambderResolverResponse>;
|
package/dist/Lambder.js
CHANGED
|
@@ -4,10 +4,8 @@ import { match } from "path-to-regexp";
|
|
|
4
4
|
import LambderResolver from "./LambderResolver.js";
|
|
5
5
|
import LambderResponseBuilder from "./LambderResponseBuilder.js";
|
|
6
6
|
import LambderUtils from "./LambderUtils.js";
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
return method === "POST" && apiPath && path === apiPath && post.apiName;
|
|
10
|
-
};
|
|
7
|
+
import LambderSessionManager from "./LambderSessionManager.js";
|
|
8
|
+
import LambderSessionController from "./LambderSessionController.js";
|
|
11
9
|
export const createContext = (event, lambdaContext, apiPath) => {
|
|
12
10
|
const host = event.headers.Host || event.headers.host || "";
|
|
13
11
|
const path = event.path;
|
|
@@ -30,10 +28,16 @@ export const createContext = (event, lambdaContext, apiPath) => {
|
|
|
30
28
|
}
|
|
31
29
|
catch (e) { }
|
|
32
30
|
// Parse api variables
|
|
33
|
-
const isApiCall =
|
|
31
|
+
const isApiCall = method === "POST" && apiPath && path === apiPath && post.apiName;
|
|
34
32
|
const apiName = isApiCall ? post.apiName : null;
|
|
35
33
|
const apiPayload = isApiCall ? post.payload : null;
|
|
36
|
-
return {
|
|
34
|
+
return {
|
|
35
|
+
host, path, pathParams, method,
|
|
36
|
+
get, post, cookie,
|
|
37
|
+
apiName, apiPayload,
|
|
38
|
+
headers, session, lambdaContext,
|
|
39
|
+
_otherInternal: { isApiCall }
|
|
40
|
+
};
|
|
37
41
|
};
|
|
38
42
|
export default class Lambder {
|
|
39
43
|
apiPath;
|
|
@@ -47,7 +51,7 @@ export default class Lambder {
|
|
|
47
51
|
routeFallbackHandler = null;
|
|
48
52
|
apiFallbackHandler = null;
|
|
49
53
|
utils;
|
|
50
|
-
|
|
54
|
+
lambderSessionManager;
|
|
51
55
|
sessionTokenCookieKey = "LMDRSESSIONTKID";
|
|
52
56
|
sessionCsrfCookieKey = "LMDRSESSIONCSTK";
|
|
53
57
|
constructor({ publicPath, apiPath, ejsPath, apiVersion }) {
|
|
@@ -67,7 +71,7 @@ export default class Lambder {
|
|
|
67
71
|
this.isCorsEnabled = isCorsEnabled;
|
|
68
72
|
}
|
|
69
73
|
enableDdbSession({ tableName, tableRegion, sessionSalt }, { partitionKey, sortKey } = { partitionKey: "pk", sortKey: "sk" }) {
|
|
70
|
-
this.
|
|
74
|
+
this.lambderSessionManager = new LambderSessionManager({
|
|
71
75
|
tableName, tableRegion, partitionKey, sortKey, sessionSalt
|
|
72
76
|
});
|
|
73
77
|
}
|
|
@@ -93,59 +97,6 @@ export default class Lambder {
|
|
|
93
97
|
testPatternMatch(pattern, path) {
|
|
94
98
|
return (match(pattern, { decode: decodeURIComponent }))(path) !== false;
|
|
95
99
|
}
|
|
96
|
-
async fetchSessionIfExist(ctx) {
|
|
97
|
-
try {
|
|
98
|
-
return await this.fetchSession(ctx);
|
|
99
|
-
}
|
|
100
|
-
catch (err) {
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
async fetchSession(ctx) {
|
|
105
|
-
if (!this.lambderSession)
|
|
106
|
-
throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
|
|
107
|
-
if (!this.areRequestSessionTokensValid(ctx)) {
|
|
108
|
-
throw new Error("Session tokens are invalid");
|
|
109
|
-
}
|
|
110
|
-
const sessionToken = ctx.cookie?.[this.sessionTokenCookieKey];
|
|
111
|
-
if (!sessionToken)
|
|
112
|
-
throw new Error("Session token not found");
|
|
113
|
-
const session = await this.lambderSession.getSession(sessionToken);
|
|
114
|
-
if (!session)
|
|
115
|
-
throw new Error("Session not found");
|
|
116
|
-
if (!this.isSessionValid(ctx, session))
|
|
117
|
-
throw new Error("Invalid session");
|
|
118
|
-
ctx.session = session;
|
|
119
|
-
return session;
|
|
120
|
-
}
|
|
121
|
-
;
|
|
122
|
-
areRequestSessionTokensValid(ctx) {
|
|
123
|
-
const isApiCall = isApiCallChecker(ctx.method, ctx.path, ctx.post, this.apiPath);
|
|
124
|
-
const sessionToken = ctx.cookie?.[this.sessionTokenCookieKey];
|
|
125
|
-
const isSessionTokenValid = sessionToken && sessionToken?.split(":")?.length === 2;
|
|
126
|
-
if (isApiCall) {
|
|
127
|
-
const csrfToken = ctx.post?.token;
|
|
128
|
-
const isCsrfTokenValid = typeof csrfToken === "string" && csrfToken.length > 0;
|
|
129
|
-
return isSessionTokenValid && isCsrfTokenValid;
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
return isSessionTokenValid;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
isSessionValid(ctx, session) {
|
|
136
|
-
if (!this.lambderSession)
|
|
137
|
-
return false;
|
|
138
|
-
const isApiCall = isApiCallChecker(ctx.method, ctx.path, ctx.post, this.apiPath);
|
|
139
|
-
if (isApiCall) {
|
|
140
|
-
const sessionToken = ctx.cookie?.[this.sessionTokenCookieKey];
|
|
141
|
-
const csrfToken = ctx.post?.token;
|
|
142
|
-
return this.lambderSession.isSessionValid(session, sessionToken, csrfToken);
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
const sessionToken = ctx.cookie?.[this.sessionTokenCookieKey];
|
|
146
|
-
return this.lambderSession.isSessionValid(session, sessionToken, null, true);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
100
|
async handleNoMatchedAction(ctx, resolver) {
|
|
150
101
|
for (const hook of this.hookList["fallback"]) {
|
|
151
102
|
await hook.hookFn(ctx, resolver);
|
|
@@ -195,7 +146,7 @@ export default class Lambder {
|
|
|
195
146
|
(typeof condition === "function" && condition(ctx)) ||
|
|
196
147
|
(condition?.constructor == RegExp && condition.test(ctx.path)))),
|
|
197
148
|
actionFn: async (ctx, resolver) => {
|
|
198
|
-
await this.
|
|
149
|
+
await this.getSessionController(ctx).fetchSession();
|
|
199
150
|
if (typeof condition === "string") {
|
|
200
151
|
ctx.pathParams = this.getPatternMatch(condition, ctx.path);
|
|
201
152
|
}
|
|
@@ -222,7 +173,7 @@ export default class Lambder {
|
|
|
222
173
|
(typeof apiName === "function" && apiName(ctx)) ||
|
|
223
174
|
(apiName?.constructor == RegExp && apiName.test(ctx.apiName)))),
|
|
224
175
|
actionFn: async (ctx, resolver) => {
|
|
225
|
-
await this.
|
|
176
|
+
await this.getSessionController(ctx).fetchSession();
|
|
226
177
|
return await actionFn(ctx, resolver);
|
|
227
178
|
}
|
|
228
179
|
});
|
|
@@ -238,43 +189,13 @@ export default class Lambder {
|
|
|
238
189
|
}
|
|
239
190
|
}
|
|
240
191
|
getSessionController(ctx) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
fetchSession: async () => {
|
|
249
|
-
if (!this.lambderSession)
|
|
250
|
-
throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
|
|
251
|
-
return this.fetchSession(ctx);
|
|
252
|
-
},
|
|
253
|
-
updateSessionData: async (newData) => {
|
|
254
|
-
if (!this.lambderSession)
|
|
255
|
-
throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
|
|
256
|
-
if (!ctx.session)
|
|
257
|
-
throw new Error("Session not found.");
|
|
258
|
-
ctx.session = await this.lambderSession.updateSessionData(ctx.session, newData);
|
|
259
|
-
return ctx.session;
|
|
260
|
-
},
|
|
261
|
-
endSession: async () => {
|
|
262
|
-
if (!this.lambderSession)
|
|
263
|
-
throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
|
|
264
|
-
if (!ctx.session)
|
|
265
|
-
throw new Error("Session not found.");
|
|
266
|
-
await this.lambderSession.deleteSession(ctx.session);
|
|
267
|
-
ctx.session = null;
|
|
268
|
-
},
|
|
269
|
-
endSessionAll: async () => {
|
|
270
|
-
if (!this.lambderSession)
|
|
271
|
-
throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
|
|
272
|
-
if (!ctx.session)
|
|
273
|
-
throw new Error("Session not found.");
|
|
274
|
-
await this.lambderSession.deleteSessionAll(ctx.session);
|
|
275
|
-
ctx.session = null;
|
|
276
|
-
},
|
|
277
|
-
};
|
|
192
|
+
if (!this.lambderSessionManager)
|
|
193
|
+
throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
|
|
194
|
+
return new LambderSessionController({
|
|
195
|
+
lambderSessionManager: this.lambderSessionManager,
|
|
196
|
+
sessionTokenCookieKey: this.sessionTokenCookieKey,
|
|
197
|
+
ctx,
|
|
198
|
+
});
|
|
278
199
|
}
|
|
279
200
|
getResponseBuilder() {
|
|
280
201
|
return new LambderResponseBuilder({
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { LambderRenderContext } from "./Lambder.js";
|
|
2
|
+
import type LambderSessionManager from "./LambderSessionManager.js";
|
|
3
|
+
import type { LambderSessionContext } from "./LambderSessionManager.js";
|
|
4
|
+
export default class LambderSessionController {
|
|
5
|
+
lambderSessionManager: LambderSessionManager;
|
|
6
|
+
sessionTokenCookieKey: string;
|
|
7
|
+
ctx: LambderRenderContext;
|
|
8
|
+
constructor({ lambderSessionManager, sessionTokenCookieKey, ctx, }: {
|
|
9
|
+
lambderSessionManager: LambderSessionManager;
|
|
10
|
+
sessionTokenCookieKey: string;
|
|
11
|
+
ctx: LambderRenderContext;
|
|
12
|
+
});
|
|
13
|
+
private areRequestSessionTokensValid;
|
|
14
|
+
createSession(sessionKey: string, data?: any, ttlInSeconds?: number): Promise<LambderSessionContext>;
|
|
15
|
+
fetchSession(): Promise<LambderSessionContext>;
|
|
16
|
+
fetchSessionIfExists(): Promise<LambderSessionContext | null>;
|
|
17
|
+
isSessionValid(session: any): boolean;
|
|
18
|
+
updateSessionData(newData: any): Promise<LambderSessionContext>;
|
|
19
|
+
endSession(): Promise<void>;
|
|
20
|
+
endSessionAll(): Promise<void>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export default class LambderSessionController {
|
|
2
|
+
lambderSessionManager;
|
|
3
|
+
sessionTokenCookieKey;
|
|
4
|
+
ctx;
|
|
5
|
+
constructor({ lambderSessionManager, sessionTokenCookieKey, ctx, }) {
|
|
6
|
+
this.ctx = ctx;
|
|
7
|
+
this.sessionTokenCookieKey = sessionTokenCookieKey;
|
|
8
|
+
this.lambderSessionManager = lambderSessionManager;
|
|
9
|
+
}
|
|
10
|
+
;
|
|
11
|
+
areRequestSessionTokensValid() {
|
|
12
|
+
const sessionToken = this.ctx.cookie?.[this.sessionTokenCookieKey];
|
|
13
|
+
const isSessionTokenValid = sessionToken && sessionToken?.split(":")?.length === 2;
|
|
14
|
+
if (this.ctx._otherInternal.isApiCall) {
|
|
15
|
+
const csrfToken = this.ctx.post?.token;
|
|
16
|
+
const isCsrfTokenValid = typeof csrfToken === "string" && csrfToken.length > 0;
|
|
17
|
+
return isSessionTokenValid && isCsrfTokenValid;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
return isSessionTokenValid;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
;
|
|
24
|
+
async createSession(sessionKey, data, ttlInSeconds) {
|
|
25
|
+
this.ctx.session = await this.lambderSessionManager.createSession(sessionKey, data, ttlInSeconds);
|
|
26
|
+
return this.ctx.session;
|
|
27
|
+
}
|
|
28
|
+
;
|
|
29
|
+
async fetchSession() {
|
|
30
|
+
if (!this.areRequestSessionTokensValid()) {
|
|
31
|
+
throw new Error("Session tokens are invalid");
|
|
32
|
+
}
|
|
33
|
+
const sessionToken = this.ctx.cookie?.[this.sessionTokenCookieKey];
|
|
34
|
+
if (!sessionToken)
|
|
35
|
+
throw new Error("Session token not found");
|
|
36
|
+
const session = await this.lambderSessionManager.getSession(sessionToken);
|
|
37
|
+
if (!session)
|
|
38
|
+
throw new Error("Session not found");
|
|
39
|
+
if (!this.isSessionValid(session))
|
|
40
|
+
throw new Error("Invalid session");
|
|
41
|
+
this.ctx.session = session;
|
|
42
|
+
return session;
|
|
43
|
+
}
|
|
44
|
+
;
|
|
45
|
+
async fetchSessionIfExists() {
|
|
46
|
+
try {
|
|
47
|
+
return await this.fetchSession();
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
;
|
|
54
|
+
isSessionValid(session) {
|
|
55
|
+
if (this.ctx._otherInternal.isApiCall) {
|
|
56
|
+
const sessionToken = this.ctx.cookie?.[this.sessionTokenCookieKey];
|
|
57
|
+
const csrfToken = this.ctx.post?.token;
|
|
58
|
+
return this.lambderSessionManager.isSessionValid(session, sessionToken, csrfToken);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const sessionToken = this.ctx.cookie?.[this.sessionTokenCookieKey];
|
|
62
|
+
return this.lambderSessionManager.isSessionValid(session, sessionToken, null, true);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
;
|
|
66
|
+
async updateSessionData(newData) {
|
|
67
|
+
if (!this.ctx.session)
|
|
68
|
+
throw new Error("Session not found.");
|
|
69
|
+
this.ctx.session = await this.lambderSessionManager.updateSessionData(this.ctx.session, newData);
|
|
70
|
+
return this.ctx.session;
|
|
71
|
+
}
|
|
72
|
+
;
|
|
73
|
+
async endSession() {
|
|
74
|
+
if (!this.ctx.session)
|
|
75
|
+
throw new Error("Session not found.");
|
|
76
|
+
await this.lambderSessionManager.deleteSession(this.ctx.session);
|
|
77
|
+
this.ctx.session = null;
|
|
78
|
+
}
|
|
79
|
+
;
|
|
80
|
+
async endSessionAll() {
|
|
81
|
+
if (!this.ctx.session)
|
|
82
|
+
throw new Error("Session not found.");
|
|
83
|
+
await this.lambderSessionManager.deleteSessionAll(this.ctx.session);
|
|
84
|
+
this.ctx.session = null;
|
|
85
|
+
}
|
|
86
|
+
;
|
|
87
|
+
}
|
|
88
|
+
;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import crypto from "crypto";
|
|
2
2
|
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
3
3
|
import { DynamoDBDocumentClient, QueryCommand, DeleteCommand, PutCommand, GetCommand } from "@aws-sdk/lib-dynamodb";
|
|
4
|
-
export default class
|
|
4
|
+
export default class LambderSessionManager {
|
|
5
5
|
tableName;
|
|
6
6
|
sessionSalt;
|
|
7
7
|
partitionKey;
|
package/dist/index.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ export default Lambder;
|
|
|
3
3
|
export { default as LambderCaller } from "./LambderCaller.js";
|
|
4
4
|
export { default as LambderResponseBuilder } from "./LambderResponseBuilder.js";
|
|
5
5
|
export { default as LambderResolver } from "./LambderResolver.js";
|
|
6
|
-
export { default as
|
|
6
|
+
export { default as LambderSessionManager } from "./LambderSessionManager.js";
|
package/dist/index.js
CHANGED
|
@@ -3,4 +3,4 @@ export default Lambder;
|
|
|
3
3
|
export { default as LambderCaller } from "./LambderCaller.js";
|
|
4
4
|
export { default as LambderResponseBuilder } from "./LambderResponseBuilder.js";
|
|
5
5
|
export { default as LambderResolver } from "./LambderResolver.js";
|
|
6
|
-
export { default as
|
|
6
|
+
export { default as LambderSessionManager } from "./LambderSessionManager.js";
|
package/package.json
CHANGED
package/src/Lambder.ts
CHANGED
|
@@ -6,11 +6,12 @@ import type { APIGatewayProxyEvent, APIGatewayProxyEventHeaders, Context } from
|
|
|
6
6
|
import LambderResolver from "./LambderResolver.js";
|
|
7
7
|
import LambderResponseBuilder, { LambderResolverResponse } from "./LambderResponseBuilder.js";
|
|
8
8
|
import LambderUtils from "./LambderUtils.js";
|
|
9
|
-
import
|
|
9
|
+
import LambderSessionManager, { type LambderSessionContext } from "./LambderSessionManager.js";
|
|
10
|
+
import LambderSessionController from "./LambderSessionController.js";
|
|
10
11
|
|
|
11
12
|
type Path = `/${string}`;
|
|
12
13
|
|
|
13
|
-
type LambderRenderContext = {
|
|
14
|
+
export type LambderRenderContext = {
|
|
14
15
|
host: string;
|
|
15
16
|
path: string;
|
|
16
17
|
pathParams: Record<string, any> | null;
|
|
@@ -23,6 +24,7 @@ type LambderRenderContext = {
|
|
|
23
24
|
headers: APIGatewayProxyEventHeaders;
|
|
24
25
|
session: LambderSessionContext|null;
|
|
25
26
|
lambdaContext: Context;
|
|
27
|
+
_otherInternal: { isApiCall: boolean };
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
type LambderModuleFunction = (lambderInstance: Lambder) => void | Promise<void>;
|
|
@@ -41,9 +43,6 @@ type GlobalErrorHandlerFunction = (err: Error, ctx: LambderRenderContext|null, r
|
|
|
41
43
|
type RouteFallbackHandlerFunction = (ctx:LambderRenderContext, resolver: LambderResolver) => LambderResolverResponse;
|
|
42
44
|
type ApiFallbackHandlerFunction = (ctx:LambderRenderContext, resolver: LambderResolver) => LambderResolverResponse;
|
|
43
45
|
|
|
44
|
-
const isApiCallChecker = (method: string, path: string, post: Record<any,any>, apiPath: string): boolean => {
|
|
45
|
-
return method === "POST" && apiPath && path === apiPath && post.apiName;
|
|
46
|
-
}
|
|
47
46
|
|
|
48
47
|
export const createContext = (
|
|
49
48
|
event: APIGatewayProxyEvent,
|
|
@@ -58,6 +57,7 @@ export const createContext = (
|
|
|
58
57
|
const cookie = cookieParser.parse(event.headers.Cookie || event.headers.cookie || "");
|
|
59
58
|
const headers = event.headers;
|
|
60
59
|
const session = null;
|
|
60
|
+
|
|
61
61
|
// Decode body for the post
|
|
62
62
|
let post: Record<string, any> = {};
|
|
63
63
|
try {
|
|
@@ -66,11 +66,18 @@ export const createContext = (
|
|
|
66
66
|
catch(e){ post = querystring.parse(decodedBody) || {}; }
|
|
67
67
|
}catch(e){}
|
|
68
68
|
// Parse api variables
|
|
69
|
-
|
|
69
|
+
|
|
70
|
+
const isApiCall = method === "POST" && apiPath && path === apiPath && post.apiName;
|
|
70
71
|
const apiName:string = isApiCall ? post.apiName : null;
|
|
71
72
|
const apiPayload:string = isApiCall ? post.payload : null;
|
|
72
73
|
|
|
73
|
-
return {
|
|
74
|
+
return {
|
|
75
|
+
host, path, pathParams, method,
|
|
76
|
+
get, post, cookie,
|
|
77
|
+
apiName, apiPayload,
|
|
78
|
+
headers, session, lambdaContext,
|
|
79
|
+
_otherInternal: { isApiCall }
|
|
80
|
+
};
|
|
74
81
|
}
|
|
75
82
|
|
|
76
83
|
|
|
@@ -93,7 +100,7 @@ export default class Lambder {
|
|
|
93
100
|
|
|
94
101
|
public utils: LambderUtils;
|
|
95
102
|
|
|
96
|
-
private
|
|
103
|
+
private lambderSessionManager?: LambderSessionManager;
|
|
97
104
|
private sessionTokenCookieKey = "LMDRSESSIONTKID";
|
|
98
105
|
private sessionCsrfCookieKey = "LMDRSESSIONCSTK";
|
|
99
106
|
|
|
@@ -124,7 +131,7 @@ export default class Lambder {
|
|
|
124
131
|
{ tableName, tableRegion, sessionSalt }: { tableName: string; tableRegion: string; sessionSalt: string; },
|
|
125
132
|
{ partitionKey, sortKey }: { partitionKey: string, sortKey: string } = { partitionKey: "pk", sortKey: "sk" }
|
|
126
133
|
){
|
|
127
|
-
this.
|
|
134
|
+
this.lambderSessionManager = new LambderSessionManager({
|
|
128
135
|
tableName, tableRegion, partitionKey, sortKey, sessionSalt
|
|
129
136
|
});
|
|
130
137
|
}
|
|
@@ -151,56 +158,6 @@ export default class Lambder {
|
|
|
151
158
|
private testPatternMatch(pattern: string, path: string): boolean{
|
|
152
159
|
return (match(pattern, { decode: decodeURIComponent }))(path) !== false;
|
|
153
160
|
}
|
|
154
|
-
|
|
155
|
-
async fetchSessionIfExist(ctx: LambderRenderContext): Promise<LambderSessionContext|null>{
|
|
156
|
-
try {
|
|
157
|
-
return await this.fetchSession(ctx);
|
|
158
|
-
}catch(err){
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async fetchSession (ctx: LambderRenderContext): Promise<LambderSessionContext>{
|
|
164
|
-
if(!this.lambderSession) throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
|
|
165
|
-
if(!this.areRequestSessionTokensValid(ctx)){ throw new Error("Session tokens are invalid"); }
|
|
166
|
-
|
|
167
|
-
const sessionToken = ctx.cookie?.[this.sessionTokenCookieKey];
|
|
168
|
-
if(!sessionToken) throw new Error("Session token not found");
|
|
169
|
-
|
|
170
|
-
const session = await this.lambderSession.getSession(sessionToken);
|
|
171
|
-
if(!session) throw new Error("Session not found");
|
|
172
|
-
|
|
173
|
-
if(!this.isSessionValid(ctx, session)) throw new Error("Invalid session");
|
|
174
|
-
ctx.session = session;
|
|
175
|
-
return session;
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
private areRequestSessionTokensValid(ctx: LambderRenderContext): boolean {
|
|
179
|
-
const isApiCall = isApiCallChecker(ctx.method, ctx.path, ctx.post, this.apiPath);
|
|
180
|
-
const sessionToken = ctx.cookie?.[this.sessionTokenCookieKey];
|
|
181
|
-
const isSessionTokenValid = sessionToken && sessionToken?.split(":")?.length === 2;
|
|
182
|
-
|
|
183
|
-
if(isApiCall){
|
|
184
|
-
const csrfToken = ctx.post?.token;
|
|
185
|
-
const isCsrfTokenValid = typeof csrfToken === "string" && csrfToken.length > 0
|
|
186
|
-
return isSessionTokenValid && isCsrfTokenValid;
|
|
187
|
-
}else{
|
|
188
|
-
return isSessionTokenValid;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
private isSessionValid(ctx: LambderRenderContext, session: any): boolean {
|
|
193
|
-
if(!this.lambderSession) return false;
|
|
194
|
-
const isApiCall = isApiCallChecker(ctx.method, ctx.path, ctx.post, this.apiPath);
|
|
195
|
-
if(isApiCall){
|
|
196
|
-
const sessionToken = ctx.cookie?.[this.sessionTokenCookieKey];
|
|
197
|
-
const csrfToken = ctx.post?.token;
|
|
198
|
-
return this.lambderSession.isSessionValid(session, sessionToken, csrfToken);
|
|
199
|
-
}else{
|
|
200
|
-
const sessionToken = ctx.cookie?.[this.sessionTokenCookieKey];
|
|
201
|
-
return this.lambderSession.isSessionValid(session, sessionToken, null, true);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
161
|
|
|
205
162
|
private async handleNoMatchedAction(ctx: LambderRenderContext, resolver: LambderResolver){
|
|
206
163
|
for(const hook of this.hookList["fallback"]){ await hook.hookFn(ctx, resolver); }
|
|
@@ -258,7 +215,8 @@ export default class Lambder {
|
|
|
258
215
|
)
|
|
259
216
|
),
|
|
260
217
|
actionFn: async (ctx:LambderRenderContext, resolver: LambderResolver) => {
|
|
261
|
-
await this.
|
|
218
|
+
await this.getSessionController(ctx).fetchSession();
|
|
219
|
+
|
|
262
220
|
if(typeof condition === "string"){
|
|
263
221
|
ctx.pathParams = this.getPatternMatch(condition, ctx.path);
|
|
264
222
|
}else if(condition?.constructor == RegExp){
|
|
@@ -292,7 +250,7 @@ export default class Lambder {
|
|
|
292
250
|
)
|
|
293
251
|
),
|
|
294
252
|
actionFn: async (ctx:LambderRenderContext, resolver: LambderResolver) => {
|
|
295
|
-
await this.
|
|
253
|
+
await this.getSessionController(ctx).fetchSession();
|
|
296
254
|
return await actionFn(ctx, resolver);
|
|
297
255
|
}
|
|
298
256
|
});
|
|
@@ -315,36 +273,14 @@ export default class Lambder {
|
|
|
315
273
|
}
|
|
316
274
|
}
|
|
317
275
|
|
|
318
|
-
getSessionController(ctx: LambderRenderContext){
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if(!this.lambderSession) throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
|
|
327
|
-
return this.fetchSession(ctx);
|
|
328
|
-
},
|
|
329
|
-
updateSessionData: async (newData: any): Promise<LambderSessionContext> => {
|
|
330
|
-
if(!this.lambderSession) throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
|
|
331
|
-
if(!ctx.session) throw new Error("Session not found.");
|
|
332
|
-
ctx.session = await this.lambderSession.updateSessionData(ctx.session, newData);
|
|
333
|
-
return ctx.session;
|
|
334
|
-
},
|
|
335
|
-
endSession: async () => {
|
|
336
|
-
if(!this.lambderSession) throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
|
|
337
|
-
if(!ctx.session) throw new Error("Session not found.");
|
|
338
|
-
await this.lambderSession.deleteSession(ctx.session);
|
|
339
|
-
ctx.session = null
|
|
340
|
-
},
|
|
341
|
-
endSessionAll: async () => {
|
|
342
|
-
if(!this.lambderSession) throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
|
|
343
|
-
if(!ctx.session) throw new Error("Session not found.");
|
|
344
|
-
await this.lambderSession.deleteSessionAll(ctx.session);
|
|
345
|
-
ctx.session = null
|
|
346
|
-
},
|
|
347
|
-
}
|
|
276
|
+
getSessionController(ctx: LambderRenderContext): LambderSessionController{
|
|
277
|
+
if(!this.lambderSessionManager) throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
|
|
278
|
+
|
|
279
|
+
return new LambderSessionController({
|
|
280
|
+
lambderSessionManager: this.lambderSessionManager,
|
|
281
|
+
sessionTokenCookieKey: this.sessionTokenCookieKey,
|
|
282
|
+
ctx,
|
|
283
|
+
});
|
|
348
284
|
}
|
|
349
285
|
|
|
350
286
|
getResponseBuilder(){
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { LambderRenderContext } from "./Lambder.js";
|
|
2
|
+
import type LambderSessionManager from "./LambderSessionManager.js";
|
|
3
|
+
import type { LambderSessionContext } from "./LambderSessionManager.js";
|
|
4
|
+
|
|
5
|
+
export default class LambderSessionController {
|
|
6
|
+
lambderSessionManager: LambderSessionManager;
|
|
7
|
+
sessionTokenCookieKey: string;
|
|
8
|
+
ctx: LambderRenderContext;
|
|
9
|
+
|
|
10
|
+
constructor(
|
|
11
|
+
{
|
|
12
|
+
lambderSessionManager,
|
|
13
|
+
sessionTokenCookieKey,
|
|
14
|
+
ctx,
|
|
15
|
+
}: {
|
|
16
|
+
lambderSessionManager: LambderSessionManager,
|
|
17
|
+
sessionTokenCookieKey: string,
|
|
18
|
+
ctx: LambderRenderContext,
|
|
19
|
+
}
|
|
20
|
+
){
|
|
21
|
+
this.ctx = ctx;
|
|
22
|
+
this.sessionTokenCookieKey = sessionTokenCookieKey;
|
|
23
|
+
this.lambderSessionManager = lambderSessionManager;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
private areRequestSessionTokensValid(): boolean {
|
|
27
|
+
const sessionToken = this.ctx.cookie?.[this.sessionTokenCookieKey];
|
|
28
|
+
const isSessionTokenValid = sessionToken && sessionToken?.split(":")?.length === 2;
|
|
29
|
+
|
|
30
|
+
if(this.ctx._otherInternal.isApiCall){
|
|
31
|
+
const csrfToken = this.ctx.post?.token;
|
|
32
|
+
const isCsrfTokenValid = typeof csrfToken === "string" && csrfToken.length > 0
|
|
33
|
+
return isSessionTokenValid && isCsrfTokenValid;
|
|
34
|
+
}else{
|
|
35
|
+
return isSessionTokenValid;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
async createSession (sessionKey: string, data?: any, ttlInSeconds?: number): Promise<LambderSessionContext> {
|
|
40
|
+
this.ctx.session = await this.lambderSessionManager.createSession(sessionKey, data, ttlInSeconds);
|
|
41
|
+
return this.ctx.session;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
async fetchSession (): Promise<LambderSessionContext>{
|
|
45
|
+
if(!this.areRequestSessionTokensValid()){ throw new Error("Session tokens are invalid"); }
|
|
46
|
+
|
|
47
|
+
const sessionToken = this.ctx.cookie?.[this.sessionTokenCookieKey];
|
|
48
|
+
if(!sessionToken) throw new Error("Session token not found");
|
|
49
|
+
|
|
50
|
+
const session = await this.lambderSessionManager.getSession(sessionToken);
|
|
51
|
+
if(!session) throw new Error("Session not found");
|
|
52
|
+
|
|
53
|
+
if(!this.isSessionValid(session)) throw new Error("Invalid session");
|
|
54
|
+
this.ctx.session = session;
|
|
55
|
+
return session;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
async fetchSessionIfExists (): Promise<LambderSessionContext|null> {
|
|
59
|
+
try {
|
|
60
|
+
return await this.fetchSession();
|
|
61
|
+
}catch(err){
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
isSessionValid(session: any): boolean {
|
|
67
|
+
if(this.ctx._otherInternal.isApiCall){
|
|
68
|
+
const sessionToken = this.ctx.cookie?.[this.sessionTokenCookieKey];
|
|
69
|
+
const csrfToken = this.ctx.post?.token;
|
|
70
|
+
return this.lambderSessionManager.isSessionValid(session, sessionToken, csrfToken);
|
|
71
|
+
}else{
|
|
72
|
+
const sessionToken = this.ctx.cookie?.[this.sessionTokenCookieKey];
|
|
73
|
+
return this.lambderSessionManager.isSessionValid(session, sessionToken, null, true);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
async updateSessionData (newData: any): Promise<LambderSessionContext> {
|
|
78
|
+
if(!this.ctx.session) throw new Error("Session not found.");
|
|
79
|
+
this.ctx.session = await this.lambderSessionManager.updateSessionData(this.ctx.session, newData);
|
|
80
|
+
return this.ctx.session;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
async endSession (){
|
|
84
|
+
if(!this.ctx.session) throw new Error("Session not found.");
|
|
85
|
+
await this.lambderSessionManager.deleteSession(this.ctx.session);
|
|
86
|
+
this.ctx.session = null
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
async endSessionAll (){
|
|
90
|
+
if(!this.ctx.session) throw new Error("Session not found.");
|
|
91
|
+
await this.lambderSessionManager.deleteSessionAll(this.ctx.session);
|
|
92
|
+
this.ctx.session = null
|
|
93
|
+
};
|
|
94
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -4,4 +4,4 @@ export default Lambder;
|
|
|
4
4
|
export { default as LambderCaller } from "./LambderCaller.js";
|
|
5
5
|
export { default as LambderResponseBuilder } from "./LambderResponseBuilder.js";
|
|
6
6
|
export { default as LambderResolver } from "./LambderResolver.js";
|
|
7
|
-
export { default as
|
|
7
|
+
export { default as LambderSessionManager } from "./LambderSessionManager.js";
|