Improve session handling, so incoming/outgoing cookie is only checked/set once
authorNick Downing <nick@ndcode.org>
Sun, 23 Jan 2022 07:54:56 +0000 (18:54 +1100)
committerNick Downing <nick@ndcode.org>
Sun, 23 Jan 2022 07:54:56 +0000 (18:54 +1100)
_config/site.jst
_lib/session_cookie.jst

index 39cd99f..6871bc0 100644 (file)
@@ -1,4 +1,6 @@
 let assert = require('assert')
+let cookie = require('cookie')
+let crypto = require('crypto')
 let logjson = (await import('@ndcode/logjson')).default
 //let NodeMailerCache = require('@ndcode/nodemailer_cache')
 let XDate = require('xdate')
@@ -97,6 +99,69 @@ return async (resources, root, prev_site) => {
       return /*await*/ this.zettair_cache.get(this.root + pathname)
     }
 
+    // customize template serving to initialize env.now, env.session_key first
+    async serve_jst(env, pathname, ...args) {
+      let template
+      try {
+        template = await this.jst_cache.get(pathname)
+      }
+      catch (err) {
+        if (!(err instanceof Error) || err.code !== 'ENOENT')
+          throw err
+        return false
+      }
+      env.site = this
+
+      // added part
+      env.now = XDate.now()
+      let transaction = await this.database.Transaction()
+      try {
+        let sessions = await (
+          await transaction.get({})
+        ).get('sessions', {})
+
+        let cookies = cookie.parse(env.request.headers.cookie || '')
+        let session, expires = new XDate(env.now)
+        if (
+          Object.prototype.hasOwnProperty.call(cookies, 'session_key') &&
+            (
+              session = await sessions.get(
+                env.session_key = cookies.session_key
+              )
+            ) !== undefined &&
+            env.now < await session.get('expires', 0)
+        )
+          // if session key is already in database, the requester supports
+          // cookies, and each access extends the session expiry by 1 month
+          expires.addMonths(1)
+        else {
+          // first request for session, maybe a bot, retain for only 1 day
+          expires.addDays(1)
+
+          do {
+            env.session_key = crypto.randomBytes(16).toString('hex')
+          } while (sessions.has(env.session_key))
+          session = transaction.LazyObject()
+          sessions.set(env.session_key, session)
+        }
+
+        await session.set('expires', expires.getTime())
+        env.response.setHeader(
+          'Set-Cookie',
+          `session_key=${env.session_key}; expires=${expires.toUTCString()}; path=/;`
+        )
+
+        await transaction.commit()
+      }
+      catch (error) {
+        transaction.rollback()
+        throw error
+      }
+
+      await template(env, ...args)
+      return true
+    }
+
     // customize the file search/serving algorithm for this particular website
     async respond(env) {
       if (
index 861e702..98c8761 100644 (file)
@@ -1,46 +1,20 @@
-let cookie = require('cookie')
-let crypto = require('crypto')
 let XDate = require('xdate')
 
-// note: this routine is "almost" idempotent
-// it will return the same session key each time
-// expiry may be increased slightly at each call
 return async (env, transaction) => {
   let sessions = await (
     await transaction.get({})
   ).get('sessions', {})
 
-  let cookies = cookie.parse(env.request.headers.cookie || '')
-  let now = Date.now()
-
-  let session, expires = new XDate(now)
-  if (
-    Object.prototype.hasOwnProperty.call(cookies, 'session_key') &&
-      (session = await sessions.get(cookies.session_key)) !== undefined &&
-      now < await session.get('expires', 0)
-  ) {
-    // if session key is already in database, we know the requester supports
-    // cookies, therefore each access extends the session expiry by 1 month
-    env.session_key = cookies.session_key
-    expires.addMonths(1)
-  }
-  else {
-    // first request for session, maybe a bot, retain session for only 1 day
-    if (!Object.prototype.hasOwnProperty.call(env, 'session_key')) {
-      do {
-        env.session_key = crypto.randomBytes(16).toString('hex')
-      } while (sessions.has(env.session_key))
-    }
-    session = await sessions.get(env.session_key, {})
+  let session = await sessions.get(env.session_key)
+  if (session === undefined) {
+    // this should never happen, but could happen if we take more than a day
+    // to process an incoming request, and database is cleaned in the meantime
+    let expires = new XDate(env.now)
     expires.addDays(1)
+    session = Transaction.json_to_logjson({expires: expires.getTime()})
+    sessions.set(env.session_key, session)
   }
 
-  await session.set('expires', expires.getTime())
-  env.response.setHeader(
-    'Set-Cookie',
-    `session_key=${env.session_key}; expires=${expires.toUTCString()}; path=/;`
-  )
-
   env.signed_in_as = await session.get_json('signed_in_as', null)
   return session
 }