lambder 1.0.114 → 1.0.115

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 CHANGED
@@ -55,7 +55,7 @@ lambder.addApi("getCompanyPage", async ({ apiPayload }, res) => {
55
55
  // Start a session from an API
56
56
  lambder.addApi("loginUser", async (ctx, res) => {
57
57
  const user = await fetchUserData();
58
- await lambder.getSessionController(ctx).startSession(user.id);
58
+ await lambder.getSessionController(ctx).createSession(user.id);
59
59
  return res.api({ success: true });
60
60
  });
61
61
 
@@ -218,25 +218,77 @@ lambder.addModule(async (lambder: Lambder): Promise<void> => {
218
218
 
219
219
  ### Session Management
220
220
 
221
+ You can enable session tracking by:
222
+
221
223
  ```typescript
222
- lambder.addApi("getCompanyPage", async (ctx, res) => {
224
+ // Enable session
225
+ lambder.enableDdbSession({
226
+ tableName: "website-session",
227
+ tableRegion: "us-east-1",
228
+ sessionSalt: "8p6Vt+4b1w3N8d/dcJ47QF3DRkp9koFg0G" // Change salt
229
+ });
230
+ ```
231
+
232
+ After you enable the session, you can access to the session controller:
233
+
234
+ ```typescript
235
+ // Create session controller:
223
236
  const sessionController = lambder.getSessionController(ctx);
237
+
238
+ /*
239
+ sessionController: {
240
+ async createSession(sessionKey, data, ttlInSeconds):
241
+ // Starts a new session and persists the session data to DDB.
242
+
243
+ async fetchSession():
244
+ // Fetch and validate if there is an existing session
245
+ // This is automatically done for addSessionRoute and addSessionApi
246
+
247
+ async updateSessionData(updatedData):
248
+ // Updates the active sessions data and persist it to ddb.
249
+
250
+ async endSession(): Start a new session
251
+ // End session and delete from DDB.
252
+
253
+ async endSessionAll(): Start a new session
254
+ // Ends and deletes all registered sessions for this sessionKey across all devices
255
+ }
256
+ */
257
+ ```
258
+
259
+
260
+
261
+ #### Session Examples
262
+ ```typescript
263
+ lambder.addApi("getCompanyPage", async (ctx, res) => {
264
+
265
+ // createSession: Start a new session
224
266
  const userId = "37234";
225
- await sessionController.startSession(userId, { "business": "Session data goes here" });
226
- console.log(ctx.session?.userKey); // "37234"
267
+ await lambder.getSessionController(ctx)
268
+ .createSession(userId, { "business": "Session data goes here" });
269
+ console.log(ctx.session?.sessionKey); // "37234"
227
270
  console.log(ctx.session?.data?.business); // "Session data goes here"
228
271
 
229
- await sessionController.updateSessionData({ "business2": "Session data updated" });
230
- console.log(ctx.session?.userKey); // "37234"
272
+ // fetchSession: Fetch and validate if there is an existing session
273
+ // This is automatically done for addSessionRoute and addSessionApi
274
+ await lambder.getSessionController(ctx).fetchSession();
275
+ console.log(ctx.session?.sessionKey); // "37234"
276
+
277
+ // updateSessionData: Updates the active sessions data and persist it to ddb.
278
+ await lambder.getSessionController(ctx)
279
+ .updateSessionData({ "business2": "Session data updated" });
280
+ console.log(ctx.session?.sessionKey); // "37234"
231
281
  console.log(ctx.session?.data?.business); // undefined
232
282
  console.log(ctx.session?.data?.business2); // "Session data updated"
233
283
 
234
- await sessionController.deleteSession(); // End session
235
- console.log(ctx.session?.userKey); // undefined
284
+ // endSession: Ends the session and removes it from ddb
285
+ await lambder.getSessionController(ctx).endSession(); // End session
286
+ console.log(ctx.session?.sessionKey); // undefined
236
287
  console.log(ctx.session?.data?.business); // undefined
237
288
 
238
- await sessionController.deleteSessionAll(); // End session for this user in all devices
239
- console.log(ctx.session?.userKey); // undefined
289
+ // endSessionAll: Ends all registered sessions for this user in all devices.
290
+ await lambder.getSessionController(ctx).endSessionAll();
291
+ console.log(ctx.session?.sessionKey); // undefined
240
292
  console.log(ctx.session?.data?.business); // undefined
241
293
 
242
294
  });
@@ -296,6 +348,7 @@ lambder.addApi("getCompanyName", async (ctx, res) => {
296
348
  headers, // Request Headers in an object. Exp: { "User-Agent": "....", ... }
297
349
  apiName, // In this function it would return "getCompanyName"
298
350
  apiPayload, // Same as post.payload
351
+ session, // Stores session. Only available in addSessionRoute and addSessionApi, otherwise null.
299
352
  } = ctx;
300
353
  return res.json({});
301
354
  });
package/dist/Lambder.d.ts CHANGED
@@ -63,11 +63,12 @@ export default class Lambder {
63
63
  setRouteFallbackHandler(routeFallbackHandler: RouteFallbackHandlerFunction): void;
64
64
  setApiFallbackHandler(apiFallbackHandler: ApiFallbackHandlerFunction): void;
65
65
  setGlobalErrorHandler(globalErrorHandler: GlobalErrorHandlerFunction): void;
66
- getPatternMatch(pattern: string, path: string): Record<string, any>;
67
- testPatternMatch(pattern: string, path: string): boolean;
68
- private fetchSession;
69
- private validateSessionForRoute;
70
- private validateSessionForAPI;
66
+ private getPatternMatch;
67
+ private testPatternMatch;
68
+ fetchSessionIfExist(ctx: LambderRenderContext): Promise<LambderSessionContext | null>;
69
+ fetchSession(ctx: LambderRenderContext): Promise<LambderSessionContext>;
70
+ private areRequestSessionTokensValid;
71
+ private isSessionValid;
71
72
  private handleNoMatchedAction;
72
73
  addModule(moduleFn: LambderModuleFunction): Promise<void>;
73
74
  importModule(moduleImport: Promise<{
@@ -82,10 +83,11 @@ export default class Lambder {
82
83
  addHook(hookEvent: 'afterRender', hookFn: HookAfterRenderFunction, priority?: number): Promise<void>;
83
84
  addHook(hookEvent: 'fallback', hookFn: HookFallbackFunction, priority?: number): Promise<void>;
84
85
  getSessionController(ctx: LambderRenderContext): {
85
- startSession: (userKey: string, data?: any, ttlInSeconds?: number) => Promise<LambderSessionContext>;
86
+ createSession: (sessionKey: string, data?: any, ttlInSeconds?: number) => Promise<LambderSessionContext>;
87
+ fetchSession: () => Promise<LambderSessionContext>;
86
88
  updateSessionData: (newData: any) => Promise<LambderSessionContext>;
87
- deleteSession: () => Promise<void>;
88
- deleteSessionAll: () => Promise<void>;
89
+ endSession: () => Promise<void>;
90
+ endSessionAll: () => Promise<void>;
89
91
  };
90
92
  getResponseBuilder(): LambderResponseBuilder;
91
93
  private getResolver;
package/dist/Lambder.js CHANGED
@@ -5,6 +5,9 @@ import LambderResolver from "./LambderResolver.js";
5
5
  import LambderResponseBuilder from "./LambderResponseBuilder.js";
6
6
  import LambderUtils from "./LambderUtils.js";
7
7
  import LambderSession from "./LambderSession.js";
8
+ const isApiCallChecker = (method, path, post, apiPath) => {
9
+ return method === "POST" && apiPath && path === apiPath && post.apiName;
10
+ };
8
11
  export const createContext = (event, lambdaContext, apiPath) => {
9
12
  const host = event.headers.Host || event.headers.host || "";
10
13
  const path = event.path;
@@ -27,9 +30,9 @@ export const createContext = (event, lambdaContext, apiPath) => {
27
30
  }
28
31
  catch (e) { }
29
32
  // Parse api variables
30
- const isAPICall = method === "POST" && apiPath && path === apiPath && post.apiName;
31
- const apiName = isAPICall ? post.apiName : null;
32
- const apiPayload = isAPICall ? post.payload : null;
33
+ const isApiCall = isApiCallChecker(method, path, post, apiPath);
34
+ const apiName = isApiCall ? post.apiName : null;
35
+ const apiPayload = isApiCall ? post.payload : null;
33
36
  return { host, path, pathParams, method, get, post, cookie, apiName, apiPayload, headers, session, lambdaContext };
34
37
  };
35
38
  export default class Lambder {
@@ -90,32 +93,59 @@ export default class Lambder {
90
93
  testPatternMatch(pattern, path) {
91
94
  return (match(pattern, { decode: decodeURIComponent }))(path) !== false;
92
95
  }
96
+ async fetchSessionIfExist(ctx) {
97
+ try {
98
+ return await this.fetchSession(ctx);
99
+ }
100
+ catch (err) {
101
+ return null;
102
+ }
103
+ }
93
104
  async fetchSession(ctx) {
94
105
  if (!this.lambderSession)
95
- throw "Session not found";
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
+ }
96
110
  const sessionToken = ctx.cookie?.[this.sessionTokenCookieKey];
97
111
  if (!sessionToken)
98
- throw "Session not found";
112
+ throw new Error("Session token not found");
99
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");
100
118
  ctx.session = session;
101
- return true;
119
+ return session;
102
120
  }
103
121
  ;
104
- validateSessionForRoute(ctx) {
105
- if (!this.lambderSession)
106
- return false;
122
+ areRequestSessionTokensValid(ctx) {
123
+ const isApiCall = isApiCallChecker(ctx.method, ctx.path, ctx.post, this.apiPath);
107
124
  const sessionToken = ctx.cookie?.[this.sessionTokenCookieKey];
108
- return this.lambderSession.isSessionValid(ctx.session, sessionToken, null, true);
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
+ }
109
134
  }
110
- ;
111
- validateSessionForAPI(ctx) {
135
+ isSessionValid(ctx, session) {
112
136
  if (!this.lambderSession)
113
137
  return false;
114
- const sessionToken = ctx.cookie?.[this.sessionTokenCookieKey];
115
- const csrfToken = ctx.post?.token;
116
- return this.lambderSession.isSessionValid(ctx.session, sessionToken, csrfToken);
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
+ }
117
148
  }
118
- ;
119
149
  async handleNoMatchedAction(ctx, resolver) {
120
150
  for (const hook of this.hookList["fallback"]) {
121
151
  await hook.hookFn(ctx, resolver);
@@ -166,9 +196,6 @@ export default class Lambder {
166
196
  (condition?.constructor == RegExp && condition.test(ctx.path)))),
167
197
  actionFn: async (ctx, resolver) => {
168
198
  await this.fetchSession(ctx);
169
- const isSessionValid = this.validateSessionForRoute(ctx);
170
- if (!isSessionValid)
171
- throw new Error("Session not found");
172
199
  if (typeof condition === "string") {
173
200
  ctx.pathParams = this.getPatternMatch(condition, ctx.path);
174
201
  }
@@ -196,9 +223,6 @@ export default class Lambder {
196
223
  (apiName?.constructor == RegExp && apiName.test(ctx.apiName)))),
197
224
  actionFn: async (ctx, resolver) => {
198
225
  await this.fetchSession(ctx);
199
- const isSessionValid = this.validateSessionForAPI(ctx);
200
- if (!isSessionValid)
201
- throw new Error("Session not found");
202
226
  return await actionFn(ctx, resolver);
203
227
  }
204
228
  });
@@ -215,33 +239,38 @@ export default class Lambder {
215
239
  }
216
240
  getSessionController(ctx) {
217
241
  return {
218
- startSession: async (userKey, data, ttlInSeconds) => {
242
+ createSession: async (sessionKey, data, ttlInSeconds) => {
219
243
  if (!this.lambderSession)
220
- throw "Session is not enabled. Use lambder.enableDdbSession(...) to configure.";
221
- ctx.session = await this.lambderSession.createSession(userKey, data, ttlInSeconds);
244
+ throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
245
+ ctx.session = await this.lambderSession.createSession(sessionKey, data, ttlInSeconds);
222
246
  return ctx.session;
223
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
+ },
224
253
  updateSessionData: async (newData) => {
225
254
  if (!this.lambderSession)
226
- throw "Session is not enabled. Use lambder.enableDdbSession(...) to configure.";
255
+ throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
227
256
  if (!ctx.session)
228
- throw "Session not found.";
257
+ throw new Error("Session not found.");
229
258
  ctx.session = await this.lambderSession.updateSessionData(ctx.session, newData);
230
259
  return ctx.session;
231
260
  },
232
- deleteSession: async () => {
261
+ endSession: async () => {
233
262
  if (!this.lambderSession)
234
- throw "Session is not enabled. Use lambder.enableDdbSession(...) to configure.";
263
+ throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
235
264
  if (!ctx.session)
236
- throw "Session not found.";
265
+ throw new Error("Session not found.");
237
266
  await this.lambderSession.deleteSession(ctx.session);
238
267
  ctx.session = null;
239
268
  },
240
- deleteSessionAll: async () => {
269
+ endSessionAll: async () => {
241
270
  if (!this.lambderSession)
242
- throw "Session is not enabled. Use lambder.enableDdbSession(...) to configure.";
271
+ throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
243
272
  if (!ctx.session)
244
- throw "Session not found.";
273
+ throw new Error("Session not found.");
245
274
  await this.lambderSession.deleteSessionAll(ctx.session);
246
275
  ctx.session = null;
247
276
  },
@@ -2,7 +2,7 @@ export type LambderSessionContext = {
2
2
  [x: string]: any;
3
3
  sessionToken: string;
4
4
  csrfToken: string;
5
- userKey: string;
5
+ sessionKey: string;
6
6
  data: any;
7
7
  createdAt: number;
8
8
  expiresAt: number;
@@ -27,7 +27,7 @@ export default class LambderSession {
27
27
  private ddbDeleteItem;
28
28
  private ddbQueryAllByPartitionKey;
29
29
  private ddbDeleteAllByPartitionKey;
30
- createSession(userKey: string, data?: any, ttlInSeconds?: number): Promise<LambderSessionContext>;
30
+ createSession(sessionKey: string, data?: any, ttlInSeconds?: number): Promise<LambderSessionContext>;
31
31
  updateSessionData(session: LambderSessionContext, newData?: any): Promise<LambderSessionContext>;
32
32
  getSession(sessionToken: string): Promise<LambderSessionContext | null>;
33
33
  isSessionValid(session: any, sessionToken: any, csrfToken: any, skipCsrfTokenCheck?: boolean): boolean;
@@ -63,18 +63,18 @@ export default class LambderSession {
63
63
  }));
64
64
  }
65
65
  }
66
- async createSession(userKey, data = {}, ttlInSeconds = 30 * 24 * 60 * 60) {
67
- const userKeyHash = this.sessionUserKeyHasher(userKey);
66
+ async createSession(sessionKey, data = {}, ttlInSeconds = 30 * 24 * 60 * 60) {
67
+ const sessionKeyHash = this.sessionUserKeyHasher(sessionKey);
68
68
  const sessionSortKey = crypto.randomBytes(32).toString("hex");
69
- const sessionToken = `${userKeyHash}:${sessionSortKey}`;
69
+ const sessionToken = `${sessionKeyHash}:${sessionSortKey}`;
70
70
  const csrfToken = crypto.randomBytes(8).toString("hex");
71
71
  const createdAt = Math.floor(Date.now() / 1000);
72
72
  const expiresAt = Number(createdAt) + Number(ttlInSeconds);
73
73
  const session = {
74
- [this.partitionKey]: userKeyHash,
74
+ [this.partitionKey]: sessionKeyHash,
75
75
  [this.sortKey]: sessionSortKey,
76
76
  sessionToken, csrfToken,
77
- userKey, data,
77
+ sessionKey, data,
78
78
  createdAt, expiresAt, ttlInSeconds
79
79
  };
80
80
  await this.ddbPutItem(session);
@@ -82,32 +82,32 @@ export default class LambderSession {
82
82
  }
83
83
  async updateSessionData(session, newData) {
84
84
  if (!session)
85
- throw "Invalid session";
85
+ throw new Error("Invalid session");
86
86
  session.data = newData;
87
87
  await this.ddbPutItem(session);
88
88
  return session;
89
89
  }
90
90
  async getSession(sessionToken) {
91
- const [userKeyHash, sessionSortKey] = sessionToken.split(":");
92
- if (!userKeyHash || !sessionSortKey)
91
+ const [sessionKeyHash, sessionSortKey] = sessionToken.split(":");
92
+ if (!sessionKeyHash || !sessionSortKey)
93
93
  return null;
94
94
  try {
95
95
  let session = await this.ddbGetItem({
96
- [this.partitionKey]: userKeyHash,
96
+ [this.partitionKey]: sessionKeyHash,
97
97
  [this.sortKey]: sessionSortKey
98
98
  });
99
99
  if (!session)
100
- throw "Session not found";
100
+ throw new Error("Session not found");
101
101
  if (!session.sessionToken || session.sessionToken !== sessionToken)
102
- throw "Not found: session.sessionToken";
102
+ throw new Error("Not found: session.sessionToken");
103
103
  if (!session.csrfToken)
104
- throw "Not found: session.csrfToken";
105
- if (!session.userKey)
106
- throw "Not found: session.userKey";
104
+ throw new Error("Not found: session.csrfToken");
105
+ if (!session.sessionKey)
106
+ throw new Error("Not found: session.sessionKey");
107
107
  if (!session.createdAt)
108
- throw "Not found: session.createdAt";
108
+ throw new Error("Not found: session.createdAt");
109
109
  if (!session.expiresAt || session.expiresAt < Date.now() / 1000)
110
- throw "Not found: session.expiresAt";
110
+ throw new Error("Not found: session.expiresAt");
111
111
  return session;
112
112
  }
113
113
  catch (err) {
@@ -124,7 +124,7 @@ export default class LambderSession {
124
124
  return false;
125
125
  if (!session.csrfToken)
126
126
  return false;
127
- if (!session.userKey)
127
+ if (!session.sessionKey)
128
128
  return false;
129
129
  if (!session.createdAt)
130
130
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambder",
3
- "version": "1.0.114",
3
+ "version": "1.0.115",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/Lambder.ts CHANGED
@@ -41,6 +41,9 @@ type GlobalErrorHandlerFunction = (err: Error, ctx: LambderRenderContext|null, r
41
41
  type RouteFallbackHandlerFunction = (ctx:LambderRenderContext, resolver: LambderResolver) => LambderResolverResponse;
42
42
  type ApiFallbackHandlerFunction = (ctx:LambderRenderContext, resolver: LambderResolver) => LambderResolverResponse;
43
43
 
44
+ const isApiCallChecker = (method: string, path: string, post: Record<any,any>, apiPath: string): boolean => {
45
+ return method === "POST" && apiPath && path === apiPath && post.apiName;
46
+ }
44
47
 
45
48
  export const createContext = (
46
49
  event: APIGatewayProxyEvent,
@@ -63,9 +66,9 @@ export const createContext = (
63
66
  catch(e){ post = querystring.parse(decodedBody) || {}; }
64
67
  }catch(e){}
65
68
  // Parse api variables
66
- const isAPICall = method === "POST" && apiPath && path === apiPath && post.apiName;
67
- const apiName:string = isAPICall ? post.apiName : null;
68
- const apiPayload:string = isAPICall ? post.payload : null;
69
+ const isApiCall = isApiCallChecker(method, path, post, apiPath);
70
+ const apiName:string = isApiCall ? post.apiName : null;
71
+ const apiPayload:string = isApiCall ? post.payload : null;
69
72
 
70
73
  return { host, path, pathParams, method, get, post, cookie, apiName, apiPayload, headers, session, lambdaContext };
71
74
  }
@@ -140,37 +143,65 @@ export default class Lambder {
140
143
  setGlobalErrorHandler(globalErrorHandler: GlobalErrorHandlerFunction){
141
144
  this.globalErrorHandler = globalErrorHandler;
142
145
  }
143
- getPatternMatch(pattern: string, path: string): Record<string, any> {
146
+ private getPatternMatch(pattern: string, path: string): Record<string, any> {
144
147
  const result = (match(pattern, { decode: decodeURIComponent }))(path);
145
148
  if(!result) return {};
146
149
  return result?.params || {};
147
150
  }
148
- testPatternMatch(pattern: string, path: string): boolean{
151
+ private testPatternMatch(pattern: string, path: string): boolean{
149
152
  return (match(pattern, { decode: decodeURIComponent }))(path) !== false;
150
153
  }
151
154
 
152
- private async fetchSession (ctx: LambderRenderContext): Promise<boolean>{
153
- if(!this.lambderSession) throw "Session not found";
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
+
154
167
  const sessionToken = ctx.cookie?.[this.sessionTokenCookieKey];
155
- if(!sessionToken) throw "Session not found";
168
+ if(!sessionToken) throw new Error("Session token not found");
169
+
156
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");
157
174
  ctx.session = session;
158
- return true;
175
+ return session;
159
176
  };
160
177
 
161
- private validateSessionForRoute (ctx: LambderRenderContext): boolean{
162
- if(!this.lambderSession) return false;
178
+ private areRequestSessionTokensValid(ctx: LambderRenderContext): boolean {
179
+ const isApiCall = isApiCallChecker(ctx.method, ctx.path, ctx.post, this.apiPath);
163
180
  const sessionToken = ctx.cookie?.[this.sessionTokenCookieKey];
164
- return this.lambderSession.isSessionValid(ctx.session, sessionToken, null, true);
165
- };
181
+ const isSessionTokenValid = sessionToken && sessionToken?.split(":")?.length === 2;
166
182
 
167
- private validateSessionForAPI (ctx: LambderRenderContext): boolean{
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 {
168
193
  if(!this.lambderSession) return false;
169
- const sessionToken = ctx.cookie?.[this.sessionTokenCookieKey];
170
- const csrfToken = ctx.post?.token;
171
- return this.lambderSession.isSessionValid(ctx.session, sessionToken, csrfToken);
172
- };
173
-
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
+
174
205
  private async handleNoMatchedAction(ctx: LambderRenderContext, resolver: LambderResolver){
175
206
  for(const hook of this.hookList["fallback"]){ await hook.hookFn(ctx, resolver); }
176
207
 
@@ -228,8 +259,6 @@ export default class Lambder {
228
259
  ),
229
260
  actionFn: async (ctx:LambderRenderContext, resolver: LambderResolver) => {
230
261
  await this.fetchSession(ctx);
231
- const isSessionValid = this.validateSessionForRoute(ctx);
232
- if(!isSessionValid) throw new Error("Session not found");
233
262
  if(typeof condition === "string"){
234
263
  ctx.pathParams = this.getPatternMatch(condition, ctx.path);
235
264
  }else if(condition?.constructor == RegExp){
@@ -264,8 +293,6 @@ export default class Lambder {
264
293
  ),
265
294
  actionFn: async (ctx:LambderRenderContext, resolver: LambderResolver) => {
266
295
  await this.fetchSession(ctx);
267
- const isSessionValid = this.validateSessionForAPI(ctx);
268
- if(!isSessionValid) throw new Error("Session not found");
269
296
  return await actionFn(ctx, resolver);
270
297
  }
271
298
  });
@@ -288,35 +315,39 @@ export default class Lambder {
288
315
  }
289
316
  }
290
317
 
291
- public getSessionController(ctx: LambderRenderContext){
318
+ getSessionController(ctx: LambderRenderContext){
292
319
  return {
293
- startSession: async (userKey: string, data?: any, ttlInSeconds?: number): Promise<LambderSessionContext> => {
294
- if(!this.lambderSession) throw "Session is not enabled. Use lambder.enableDdbSession(...) to configure.";
295
- ctx.session = await this.lambderSession.createSession(userKey, data, ttlInSeconds);
320
+ createSession: async (sessionKey: string, data?: any, ttlInSeconds?: number): Promise<LambderSessionContext> => {
321
+ if(!this.lambderSession) throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
322
+ ctx.session = await this.lambderSession.createSession(sessionKey, data, ttlInSeconds);
296
323
  return ctx.session;
297
324
  },
325
+ fetchSession: async (): Promise<LambderSessionContext> => {
326
+ if(!this.lambderSession) throw new Error("Session is not enabled. Use lambder.enableDdbSession(...) to enable.");
327
+ return this.fetchSession(ctx);
328
+ },
298
329
  updateSessionData: async (newData: any): Promise<LambderSessionContext> => {
299
- if(!this.lambderSession) throw "Session is not enabled. Use lambder.enableDdbSession(...) to configure.";
300
- if(!ctx.session) throw "Session not found.";
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.");
301
332
  ctx.session = await this.lambderSession.updateSessionData(ctx.session, newData);
302
333
  return ctx.session;
303
334
  },
304
- deleteSession: async () => {
305
- if(!this.lambderSession) throw "Session is not enabled. Use lambder.enableDdbSession(...) to configure.";
306
- if(!ctx.session) throw "Session not found.";
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.");
307
338
  await this.lambderSession.deleteSession(ctx.session);
308
339
  ctx.session = null
309
340
  },
310
- deleteSessionAll: async () => {
311
- if(!this.lambderSession) throw "Session is not enabled. Use lambder.enableDdbSession(...) to configure.";
312
- if(!ctx.session) throw "Session not found.";
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.");
313
344
  await this.lambderSession.deleteSessionAll(ctx.session);
314
345
  ctx.session = null
315
346
  },
316
347
  }
317
348
  }
318
349
 
319
- public getResponseBuilder(){
350
+ getResponseBuilder(){
320
351
  return new LambderResponseBuilder({
321
352
  isCorsEnabled: this.isCorsEnabled,
322
353
  publicPath: this.publicPath,
@@ -6,7 +6,7 @@ export type LambderSessionContext = {
6
6
  [x: string]: any;
7
7
  sessionToken: string;
8
8
  csrfToken: string;
9
- userKey: string;
9
+ sessionKey: string;
10
10
  data: any;
11
11
  createdAt: number;
12
12
  expiresAt: number;
@@ -94,22 +94,22 @@ export default class LambderSession{
94
94
  }
95
95
 
96
96
  public async createSession(
97
- userKey: string,
97
+ sessionKey: string,
98
98
  data: any = {},
99
99
  ttlInSeconds:number = 30*24*60*60
100
100
  ): Promise<LambderSessionContext> {
101
- const userKeyHash = this.sessionUserKeyHasher(userKey);
101
+ const sessionKeyHash = this.sessionUserKeyHasher(sessionKey);
102
102
  const sessionSortKey = crypto.randomBytes(32).toString("hex");
103
- const sessionToken = `${userKeyHash}:${sessionSortKey}`;
103
+ const sessionToken = `${sessionKeyHash}:${sessionSortKey}`;
104
104
  const csrfToken = crypto.randomBytes(8).toString("hex");
105
105
  const createdAt = Math.floor(Date.now()/1000);
106
106
  const expiresAt = Number(createdAt) + Number(ttlInSeconds);
107
107
 
108
108
  const session = {
109
- [this.partitionKey]: userKeyHash,
109
+ [this.partitionKey]: sessionKeyHash,
110
110
  [this.sortKey]: sessionSortKey,
111
111
  sessionToken, csrfToken,
112
- userKey, data,
112
+ sessionKey, data,
113
113
  createdAt, expiresAt, ttlInSeconds
114
114
  };
115
115
  await this.ddbPutItem(session);
@@ -120,26 +120,26 @@ export default class LambderSession{
120
120
  session: LambderSessionContext,
121
121
  newData?: any
122
122
  ): Promise<LambderSessionContext> {
123
- if(!session) throw "Invalid session";
123
+ if(!session) throw new Error("Invalid session");
124
124
  session.data = newData;
125
125
  await this.ddbPutItem(session);
126
126
  return session;
127
127
  }
128
128
 
129
129
  public async getSession(sessionToken: string): Promise<LambderSessionContext|null>{
130
- const [ userKeyHash, sessionSortKey ] = sessionToken.split(":");
131
- if(!userKeyHash || !sessionSortKey) return null;
130
+ const [ sessionKeyHash, sessionSortKey ] = sessionToken.split(":");
131
+ if(!sessionKeyHash || !sessionSortKey) return null;
132
132
  try{
133
133
  let session = await this.ddbGetItem({
134
- [this.partitionKey]: userKeyHash,
134
+ [this.partitionKey]: sessionKeyHash,
135
135
  [this.sortKey]: sessionSortKey
136
136
  });
137
- if(!session) throw "Session not found";
138
- if(!session.sessionToken || session.sessionToken !== sessionToken) throw "Not found: session.sessionToken";
139
- if(!session.csrfToken) throw "Not found: session.csrfToken";
140
- if(!session.userKey) throw "Not found: session.userKey";
141
- if(!session.createdAt) throw "Not found: session.createdAt";
142
- if(!session.expiresAt || session.expiresAt < Date.now()/1000) throw "Not found: session.expiresAt";
137
+ if(!session) throw new Error("Session not found");
138
+ if(!session.sessionToken || session.sessionToken !== sessionToken) throw new Error("Not found: session.sessionToken");
139
+ if(!session.csrfToken) throw new Error("Not found: session.csrfToken");
140
+ if(!session.sessionKey) throw new Error("Not found: session.sessionKey");
141
+ if(!session.createdAt) throw new Error("Not found: session.createdAt");
142
+ if(!session.expiresAt || session.expiresAt < Date.now()/1000) throw new Error("Not found: session.expiresAt");
143
143
  return session;
144
144
  }catch(err){
145
145
  return null;
@@ -151,7 +151,7 @@ export default class LambderSession{
151
151
  if(!sessionToken || typeof sessionToken !== "string") return false;
152
152
  if(session.sessionToken !== sessionToken) return false;
153
153
  if(!session.csrfToken) return false;
154
- if(!session.userKey) return false;
154
+ if(!session.sessionKey) return false;
155
155
  if(!session.createdAt) return false;
156
156
  if(!session.expiresAt || session.expiresAt < Date.now()/1000) return false;
157
157
  if(!skipCsrfTokenCheck){