Skip to content
This repository has been archived by the owner on Jul 15, 2020. It is now read-only.

Commit

Permalink
Now silently revalidates sessions periodically
Browse files Browse the repository at this point in the history
This resolves issue #6.

The client how checks 'clientMaxAge', which is set on the server (along side all the other session options) and uses that value to determine if it it should fetch the session data from the server or from the cache on disk.

Note that the cache on disk is shared between tabs, so logging you out on one will cause you also to be logged out on the other pages when you next interact with the site regardless.

This option just ensures the client will never stray too far from the data on the server (e.g. in the event a users session information is updated or forceably expired on the server).
  • Loading branch information
iaincollins committed Feb 9, 2017
1 parent 400d2eb commit e8a6d6c
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 23 deletions.
21 changes: 13 additions & 8 deletions components/session.js
Expand Up @@ -66,15 +66,18 @@ export default class Session {

// Attempt to load session data from sessionStore on every call
this._session = this._getSessionStore()

//console.log("Time left till session expires in seconds: "+((this._session.expires - Date.now()) / 1000))

if (window.session && this._session && Object.keys(this._session).length > 0 && forceUpdate !== true) {
// If we have a populated session object already AND forceUpdate is not
// set to true then return the session data we have already
// If session data exists, has not expired AND forceUpdate is not set then
// return the stored session we already have.
if (this._session && Object.keys(this._session).length > 0 && this._session.expires > Date.now() && forceUpdate !== true) {
return new Promise((resolve) => {
resolve(this._session)
})
} else {
// If we don't have session data (or forceUpdate is true) then get it
// If we don't have session data, or it's expired, or forceUpdate is set
// to true then revalidate it by fetching it again from the server.
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open("GET", '/auth/session', true)
Expand All @@ -84,12 +87,13 @@ export default class Session {
// Update session with session info
this._session = JSON.parse(xhr.responseText)

// Set a value we will use to check this client should silently
// revalidate based on the value of clientMaxAge set by the server
this._session.expires = Date.now() + this._session.clientMaxAge

// Save changes to session
this._setSessionStore(this._session)

// Save session to window.session object
window.session = this._session


resolve(this._session)
} else {
reject(Error('XMLHttpRequest failed: Unable to get session'))
Expand Down Expand Up @@ -149,6 +153,7 @@ export default class Session {
// Set isLoggedIn to false and destory user object
this._session.csrfToken = await Session.getCsrfToken()
this._session.isLoggedIn = false
this._session.expires = Date.now()
delete this._session.user

// Save changes to session
Expand Down
8 changes: 4 additions & 4 deletions index.js
Expand Up @@ -36,10 +36,10 @@ app.prepare()

// Define user object
const User = db.define("user", {
name : { type: "text" },
email : { type: "text", unique: true },
token : { type: "text" },
verified : { type: "boolean", defaultValue: false }
name : { type: "text" },
email : { type: "text", unique: true },
token : { type: "text" },
verified : { type: "boolean", defaultValue: false }
})

// Create table
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "nextjs-starter",
"version": "2.4.3",
"version": "2.4.4",
"description": "A starter Next.js project",
"main": "index.js",
"dependencies": {
Expand Down
32 changes: 22 additions & 10 deletions routes/auth.js
Expand Up @@ -27,25 +27,34 @@ exports.configure = (app, server, options) => {
const User = options.db.models.user

// Base path for auth URLs
const path = (options.path) ? options.path : '/auth'
const path = options.path || '/auth'

// Directory for auth pages
const pages = (options.pages) ? options.pages : 'auth'
const pages = options.pages || 'auth'

// The secret is used to encrypt/decrypt sessions (you should pass your own!)
const secret = (options.secret) ? options.secret : 'AAAA-BBBB-CCCC-DDDD'
const secret = options.secret || 'AAAA-BBBB-CCCC-DDDD'

// Configure session store (defaults to using file system)
const store = (options.store) ? options.store : new FileStore({ path: '/tmp/sessions', secret: secret })
const store = options.store || new FileStore({ path: '/tmp/sessions', secret: secret })

// Max cookie age (default is 4 weeks)
const maxAge = (options.maxAge) ? options.maxAge : 3600000 * 24 * 7 * 4
// Max session age in ms (default is 4 weeks)
// NB: With 'rolling: true' passed to session() the session expiry time will
// be reset every time a user visits the site again before it expires.
const maxAge = options.maxAge || 60000 * 60 * 24 * 7 * 4

// How often the client should revalidate the session in ms (default 60s)
// Does not impact the session life on the server, but causes the client to
// always refetch session info after N seconds has elapsed since last checked.
// Sensible values are between 0 (always check the server) and a few minutes.
const clientMaxAge = options.clientMaxAge || 60000

// URL of the server (e.g. "http://www.example.com"), autodetects if null
const serverUrl = (options.serverUrl) ? options.serverUrl : null
// URL of the server (e.g. "http://www.example.com"). Used when sending
// sign-in emails. Autodetects current server hostname / domain if null.
const serverUrl = options.serverUrl || null

// Mailserver (defaults to sending from localhost)
const mailserver = (options.mailserver) ? options.mailserver : null
// Mailserver (defaults to sending from localhost if null)
const mailserver = options.mailserver || null

// Load body parser to handle POST requests
server.use(bodyParser.json())
Expand Down Expand Up @@ -77,9 +86,12 @@ exports.configure = (app, server, options) => {

// Return session info
server.get(path+'/session', (req, res) => {
// @TODO Instead of storing the "user" object in the sesssion, we should
// really just store the User ID and fetch the User object.
return res.json({
user: req.session.user || null,
isLoggedIn: (req.session.user) ? true : false,
clientMaxAge: clientMaxAge,
csrfToken: res.locals._csrf
})
})
Expand Down

0 comments on commit e8a6d6c

Please sign in to comment.