session code updated using axios

This commit is contained in:
Sukchan Lee 2017-07-07 20:18:18 +09:00
parent 2e7ba7118b
commit 43ad929062
3 changed files with 62 additions and 115 deletions

View File

@ -79,7 +79,7 @@ co(function* () {
next();
})
// server.use(csrf);
server.use(csrf);
server.use(passport.initialize());
server.use(passport.session());

View File

@ -45,7 +45,7 @@ class HeaderContainer extends Component {
const session = new Session()
await session.signout()
// @FIXME next/router not working reliably so using window.location
// @FIXME next/router not working reliably so using window.location
window.location = '/'
}
};

View File

@ -1,6 +1,3 @@
/* global window */
/* global localStorage */
/* global XMLHttpRequest */
/**
* A class to handle signing in and out and caching session data in sessionStore
*
@ -9,28 +6,37 @@
* yet (!) so if we tried to get or pass the CSRF token it would mismatch.
*/
import axios from 'axios';
const authApi = (method, url, csrf, data) => {
return axios({
baseURL: '/api/auth',
headers: { 'X-CSRF-TOKEN': csrf },
method,
url,
data
});
}
export default class Session {
constructor({req} = {}) {
this._session = {}
this.session = {}
try {
if (req) {
// If running on server we can access the server side environment
this._session = {
this.session = {
csrfToken: req.connection._httpMessage.locals._csrf
}
// If the session is associated with a user add user object to session
if (req.user) {
this._session.user = req.user
this.session.user = req.user
}
} else {
// If running on client, attempt to load session from localStorage
this._session = this._getLocalStore('session')
this.session = this.getLocalStore('session')
}
} catch (err) {
// Handle if error reading from localStorage or server state is safe to
// ignore (will just cause session data to be fetched by ajax)
return
console.log(err);
}
}
@ -40,159 +46,100 @@ export default class Session {
return reject(Error('This method should only be called on the client'))
}
let xhr = new XMLHttpRequest()
xhr.open('GET', '/api/auth/csrf', true)
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
const responseJson = JSON.parse(xhr.responseText)
resolve(responseJson.csrfToken)
} else {
reject(Error('Unexpected response when trying to get CSRF token'))
}
}
}
xhr.onerror = () => {
reject(Error('XMLHttpRequest error: Unable to get CSRF token'))
}
xhr.send()
authApi(
'get', '/csrf'
).then(
response => resolve(response.data.csrfToken)
).catch(
error => reject(Error('Unexpected response when trying to get CSRF token'))
)
})
}
// We can't do async requests in the constructor so access is via asyc method
// This allows us to use XMLHttpRequest when running on the client to fetch it
// Note: We use XMLHttpRequest instead of fetch so auth cookies are passed
async getSession(forceUpdate) {
// If running on the server, return session as will be loaded in constructor
if (typeof window === 'undefined') {
return new Promise(resolve => {
resolve(this._session)
resolve(this.session)
})
}
// If force update is set, clear data from store
if (forceUpdate === true) {
this._session = {}
this._removeLocalStore('session')
this.session = {}
this.removeLocalStore('session')
}
// Attempt to load session data from sessionStore on every call
this._session = this._getLocalStore('session')
this.session = this.getLocalStore('session')
// 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 && this._session.expires > Date.now()) {
if (this.session && Object.keys(this.session).length > 0 && this.session.expires && this.session.expires > Date.now()) {
return new Promise(resolve => {
resolve(this._session)
resolve(this.session)
})
}
// 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', '/api/auth/session', true)
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// 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._saveLocalStore('session', this._session)
resolve(this._session)
} else {
reject(Error('XMLHttpRequest failed: Unable to get session'))
}
authApi(
'get', '/session'
).then(
response => {
this.session = response.data;
this.session.expires = Date.now() + this.session.clientMaxAge
this.saveLocalStore('session', this.session)
resolve(this.session)
}
}
xhr.onerror = () => {
reject(Error('XMLHttpRequest error: Unable to get session'))
}
xhr.send()
).catch(
error => reject(Error('XMLHttpRequest failed: Unable to get session'))
)
})
}
async signin(username, password) {
// Sign in to the server
return new Promise(async (resolve, reject) => {
if (typeof window === 'undefined') {
return reject(Error('This method should only be called on the client'))
}
// Make sure we have session in memory
this._session = await this.getSession()
const csrf = await Session.getCsrfToken()
// Make sure we have the latest CSRF Token in our session
this._session.csrfToken = await Session.getCsrfToken()
let xhr = new XMLHttpRequest()
xhr.open('POST', '/api/auth/login', true)
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
xhr.onreadystatechange = async () => {
if (xhr.readyState === 4) {
if (xhr.status !== 200) {
return reject(Error('Incorrect username or password.'))
authApi(
'post', '/login', csrf, { username, password }
).then(
async response => {
if (response.status !== 200) {
return reject(Error('XMLHttpRequest error: Unable to login'))
}
// Update local session data
this._session = await this.getSession(true)
return resolve(true)
this.session = await this.getSession(true)
resolve(true)
}
}
xhr.onerror = () => {
return reject(Error('XMLHttpRequest error: Unable to signin'))
}
xhr.send('_csrf=' + encodeURIComponent(this._session.csrfToken) + '&' +
'username=' + encodeURIComponent(username) + '&' +
'password=' + encodeURIComponent(password))
).catch(
error => reject(Error('Incorrect username or password.'))
)
})
}
async signout() {
// Signout from the server
return new Promise(async (resolve, reject) => {
if (typeof window === 'undefined') {
return reject(Error('This method should only be called on the client'))
}
let xhr = new XMLHttpRequest()
xhr.open('POST', '/api/auth/logout', true)
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
xhr.onreadystatechange = async () => {
if (xhr.readyState === 4) {
// @TODO We aren't checking for success, just completion
// Update local session data
this._session = await this.getSession(true)
resolve(true)
}
}
xhr.onerror = () => {
reject(Error('XMLHttpRequest error: Unable to signout'))
}
xhr.send('_csrf=' + encodeURIComponent(this._session.csrfToken))
// We aren't checking for success, just completion
await authApi('post', '/logout', this.session.csrfToken)
this.session = await this.getSession(true)
resolve(true)
})
}
// The Web Storage API is widely supported, but not always available (e.g.
// it can be restricted in private browsing mode, triggering an exception).
// We handle that silently by just returning null here.
_getLocalStore(name) {
getLocalStore(name) {
try {
return JSON.parse(localStorage.getItem(name))
} catch (err) {
return null
}
}
_saveLocalStore(name, data) {
saveLocalStore(name, data) {
try {
localStorage.setItem(name, JSON.stringify(data))
return true
@ -200,7 +147,7 @@ export default class Session {
return false
}
}
_removeLocalStore(name) {
removeLocalStore(name) {
try {
localStorage.removeItem(name)
return true