Release 1.11.0 - Internet Explorer 11 support

* Added Babel Pollyfill to the next-auth client and rollup config.
* Client longer relies on ES6 features and so works in Internet Explorer (support for which had been dropped was was conflicting with newerver version of webpack when building isomorphic libraries).
* Example client now has slightly better example of how to handle the callback screen for browsers that don’t have JavaScript enabled.
This commit is contained in:
Iain Collins
2018-08-25 17:25:08 +01:00
parent 50c5613fed
commit e83c77f6de
7 changed files with 1471 additions and 271 deletions

731
client.js
View File

@@ -1,276 +1,489 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('isomorphic-fetch')) :
typeof define === 'function' && define.amd ? define(['exports', 'isomorphic-fetch'], factory) :
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('isomorphic-fetch'), require('babel-polyfill')) :
typeof define === 'function' && define.amd ? define(['exports', 'isomorphic-fetch', 'babel-polyfill'], factory) :
(factory((global['next-auth-client'] = {}),null));
}(this, (function (exports,fetch) { 'use strict';
fetch = fetch && fetch.hasOwnProperty('default') ? fetch['default'] : fetch;
class nextAuthClient {
/**
* This is an async, isometric method which returns a session object -
* either by looking up the current express session object when run on the
* server, or by using fetch (and optionally caching the result in local
* storage) when run on the client.
*
* Note that actual session tokens are not stored in local storage, they are
* kept in an HTTP Only cookie as protection against session hi-jacking by
* malicious JavaScript.
**/
static async init({
req = null,
force = false
} = {}) {
let session = {};
if (req) {
if (req.session) {
// If running on the server session data should be in the req object
session.csrfToken = req.connection._httpMessage.locals._csrf;
session.expires = req.session.cookie._expires;
// If the user is logged in, add the user to the session object
if (req.user) {
session.user = req.user;
}
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var _class = function () {
function _class() {
_classCallCheck(this, _class);
}
_createClass(_class, null, [{
key: 'init',
/**
* This is an async, isometric method which returns a session object -
* either by looking up the current express session object when run on the
* server, or by using fetch (and optionally caching the result in local
* storage) when run on the client.
*
* Note that actual session tokens are not stored in local storage, they are
* kept in an HTTP Only cookie as protection against session hi-jacking by
* malicious JavaScript.
**/
value: function () {
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
var _this = this;
var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
_ref2$req = _ref2.req,
req = _ref2$req === undefined ? null : _ref2$req,
_ref2$force = _ref2.force,
force = _ref2$force === undefined ? false : _ref2$force;
var session;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
session = {};
if (req) {
if (req.session) {
// If running on the server session data should be in the req object
session.csrfToken = req.connection._httpMessage.locals._csrf;
session.expires = req.session.cookie._expires;
// If the user is logged in, add the user to the session object
if (req.user) {
session.user = req.user;
}
}
} else {
// If running in the browser attempt to load session from sessionStore
if (force === true) {
// If force update is set, reset data store
this._removeLocalStore('session');
} else {
session = this._getLocalStore('session');
}
}
// If session data exists, has not expired AND force is not set then
// return the stored session we already have.
if (!(session && Object.keys(session).length > 0 && session.expires && session.expires > Date.now())) {
_context.next = 6;
break;
}
return _context.abrupt('return', new Promise(function (resolve) {
resolve(session);
}));
case 6:
if (!(typeof window === 'undefined')) {
_context.next = 8;
break;
}
return _context.abrupt('return', new Promise(function (resolve) {
resolve({});
}));
case 8:
return _context.abrupt('return', fetch('/auth/session', {
credentials: 'same-origin'
}).then(function (response) {
if (response.ok) {
return response;
} else {
return Promise.reject(Error('HTTP error when trying to get session'));
}
}).then(function (response) {
return response.json();
}).then(function (data) {
// Update session with session info
session = data;
// Set a value we will use to check this client should silently
// revalidate, using the value for revalidateAge returned by the server.
session.expires = Date.now() + session.revalidateAge;
// Save changes to session
_this._saveLocalStore('session', session);
return session;
}).catch(function () {
return Error('Unable to get session');
}));
case 9:
case 'end':
return _context.stop();
}
}
}, _callee, this);
}));
function init() {
return _ref.apply(this, arguments);
}
} else {
// If running in the browser attempt to load session from sessionStore
if (force === true) {
// If force update is set, reset data store
this._removeLocalStore('session');
} else {
session = this._getLocalStore('session');
return init;
}()
/**
* A simple static method to get the CSRF Token is provided for convenience
**/
}, {
key: 'csrfToken',
value: function () {
var _ref3 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {
return regeneratorRuntime.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
return _context2.abrupt('return', fetch('/auth/csrf', {
credentials: 'same-origin'
}).then(function (response) {
if (response.ok) {
return response;
} else {
return Promise.reject(Error('Unexpected response when trying to get CSRF token'));
}
}).then(function (response) {
return response.json();
}).then(function (data) {
return data.csrfToken;
}).catch(function () {
return Error('Unable to get CSRF token');
}));
case 1:
case 'end':
return _context2.stop();
}
}
}, _callee2, this);
}));
function csrfToken() {
return _ref3.apply(this, arguments);
}
return csrfToken;
}()
/**
* A static method to get list of currently linked oAuth accounts
**/
}, {
key: 'linked',
value: function () {
var _ref4 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee3() {
var _ref5 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
_ref5$req = _ref5.req,
req = _ref5$req === undefined ? null : _ref5$req;
return regeneratorRuntime.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
if (!req) {
_context3.next = 2;
break;
}
return _context3.abrupt('return', req.linked());
case 2:
return _context3.abrupt('return', fetch('/auth/linked', {
credentials: 'same-origin'
}).then(function (response) {
if (response.ok) {
return response;
} else {
return Promise.reject(Error('Unexpected response when trying to get linked accounts'));
}
}).then(function (response) {
return response.json();
}).then(function (data) {
return data;
}).catch(function () {
return Error('Unable to get linked accounts');
}));
case 3:
case 'end':
return _context3.stop();
}
}
}, _callee3, this);
}));
function linked() {
return _ref4.apply(this, arguments);
}
return linked;
}()
/**
* A static method to get list of currently configured oAuth providers
**/
}, {
key: 'providers',
value: function () {
var _ref6 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee4() {
var _ref7 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
_ref7$req = _ref7.req,
req = _ref7$req === undefined ? null : _ref7$req;
return regeneratorRuntime.wrap(function _callee4$(_context4) {
while (1) {
switch (_context4.prev = _context4.next) {
case 0:
if (!req) {
_context4.next = 2;
break;
}
return _context4.abrupt('return', req.providers());
case 2:
return _context4.abrupt('return', fetch('/auth/providers', {
credentials: 'same-origin'
}).then(function (response) {
if (response.ok) {
return response;
} else {
console.log("NextAuth Error Fetching Providers");
return null;
}
}).then(function (response) {
return response.json();
}).then(function (data) {
return data;
}).catch(function (e) {
console.log("NextAuth Error Loading Providers");
console.log(e);
return null;
}));
case 3:
case 'end':
return _context4.stop();
}
}
}, _callee4, this);
}));
function providers() {
return _ref6.apply(this, arguments);
}
return providers;
}()
/*
* Sign in
*
* Will post a form to /auth/signin auth route if an object is passed.
* If the details are valid a session will be created and you should redirect
* to your callback page so the session is loaded in the client.
*
* If just a string containing an email address is specififed will generate a
* a one-time use sign in link and send it via email; you should redirect to a
* page telling the user to check their inbox for an email with the link.
*/
}, {
key: 'signin',
value: function () {
var _ref8 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee6(params) {
var _this2 = this;
var formData, route, encodedForm;
return regeneratorRuntime.wrap(function _callee6$(_context6) {
while (1) {
switch (_context6.prev = _context6.next) {
case 0:
// Params can be just string (an email address) or an object (form fields)
formData = typeof params === 'string' ? { email: params } : params;
// Use either the email token generation route or the custom form auth route
route = typeof params === 'string' ? '/auth/email/signin' : '/auth/signin';
// Add latest CSRF Token to request
_context6.next = 4;
return this.csrfToken();
case 4:
formData._csrf = _context6.sent;
// Encoded form parser for sending data in the body
encodedForm = Object.keys(formData).map(function (key) {
return encodeURIComponent(key) + '=' + encodeURIComponent(formData[key]);
}).join('&');
return _context6.abrupt('return', fetch(route, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest' // So Express can detect AJAX post
},
body: encodedForm,
credentials: 'same-origin'
}).then(function () {
var _ref9 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee5(response) {
return regeneratorRuntime.wrap(function _callee5$(_context5) {
while (1) {
switch (_context5.prev = _context5.next) {
case 0:
if (!response.ok) {
_context5.next = 6;
break;
}
_context5.next = 3;
return response.json();
case 3:
return _context5.abrupt('return', _context5.sent);
case 6:
throw new Error('HTTP error while attempting to sign in');
case 7:
case 'end':
return _context5.stop();
}
}
}, _callee5, _this2);
}));
return function (_x5) {
return _ref9.apply(this, arguments);
};
}()).then(function (data) {
if (data.success && data.success === true) {
return Promise.resolve(true);
} else {
return Promise.resolve(false);
}
}));
case 7:
case 'end':
return _context6.stop();
}
}
}, _callee6, this);
}));
function signin(_x4) {
return _ref8.apply(this, arguments);
}
return signin;
}()
}, {
key: 'signout',
value: function () {
var _ref10 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee7() {
var csrfToken, formData, encodedForm;
return regeneratorRuntime.wrap(function _callee7$(_context7) {
while (1) {
switch (_context7.prev = _context7.next) {
case 0:
_context7.next = 2;
return this.csrfToken();
case 2:
csrfToken = _context7.sent;
formData = { _csrf: csrfToken
// Encoded form parser for sending data in the body
};
encodedForm = Object.keys(formData).map(function (key) {
return encodeURIComponent(key) + '=' + encodeURIComponent(formData[key]);
}).join('&');
// Remove cached session data
this._removeLocalStore('session');
return _context7.abrupt('return', fetch('/auth/signout', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: encodedForm,
credentials: 'same-origin'
}).then(function () {
return true;
}).catch(function () {
return Error('Unable to sign out');
}));
case 7:
case 'end':
return _context7.stop();
}
}
}, _callee7, this);
}));
function signout() {
return _ref10.apply(this, arguments);
}
return signout;
}()
// 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.
}, {
key: '_getLocalStore',
value: function _getLocalStore(name) {
try {
return JSON.parse(localStorage.getItem(name));
} catch (err) {
return null;
}
}
// If session data exists, has not expired AND force is not set then
// return the stored session we already have.
if (session && Object.keys(session).length > 0 && session.expires && session.expires > Date.now()) {
return new Promise(resolve => {
resolve(session);
})
} else {
// If running on server, but session has expired return empty object
// (no valid session)
if (typeof window === 'undefined') {
return new Promise(resolve => {
resolve({});
})
}, {
key: '_saveLocalStore',
value: function _saveLocalStore(name, data) {
try {
localStorage.setItem(name, JSON.stringify(data));
return true;
} catch (err) {
return false;
}
}
// If we don't have session data, or it's expired, or force is set
// to true then revalidate it by fetching it again from the server.
return fetch('/auth/session', {
credentials: 'same-origin'
})
.then(response => {
if (response.ok) {
return response
} else {
return Promise.reject(Error('HTTP error when trying to get session'))
}, {
key: '_removeLocalStore',
value: function _removeLocalStore(name) {
try {
localStorage.removeItem(name);
return true;
} catch (err) {
return false;
}
})
.then(response => response.json())
.then(data => {
// Update session with session info
session = data;
// Set a value we will use to check this client should silently
// revalidate, using the value for revalidateAge returned by the server.
session.expires = Date.now() + session.revalidateAge;
// Save changes to session
this._saveLocalStore('session', session);
return session
})
.catch(() => Error('Unable to get session'))
}
/**
* A simple static method to get the CSRF Token is provided for convenience
**/
static async csrfToken() {
return fetch('/auth/csrf', {
credentials: 'same-origin'
})
.then(response => {
if (response.ok) {
return response
} else {
return Promise.reject(Error('Unexpected response when trying to get CSRF token'))
}
})
.then(response => response.json())
.then(data => data.csrfToken)
.catch(() => Error('Unable to get CSRF token'))
}
/**
* A static method to get list of currently linked oAuth accounts
**/
static async linked({
req = null
} = {}) {
// If running server side, uses server side method
if (req) return req.linked()
// If running client side, use RESTful endpoint
return fetch('/auth/linked', {
credentials: 'same-origin'
})
.then(response => {
if (response.ok) {
return response
} else {
return Promise.reject(Error('Unexpected response when trying to get linked accounts'))
}
})
.then(response => response.json())
.then(data => data)
.catch(() => Error('Unable to get linked accounts'))
}
/**
* A static method to get list of currently configured oAuth providers
**/
static async providers({
req = null
} = {}) {
// If running server side, uses server side method
if (req) return req.providers()
// If running client side, use RESTful endpoint
return fetch('/auth/providers', {
credentials: 'same-origin'
})
.then(response => {
if (response.ok) {
return response
} else {
console.log("NextAuth Error Fetching Providers");
return null
}
})
.then(response => response.json())
.then(data => data)
.catch((e) => {
console.log("NextAuth Error Loading Providers");
console.log(e);
return null
})
}
/*
* Sign in
*
* Will post a form to /auth/signin auth route if an object is passed.
* If the details are valid a session will be created and you should redirect
* to your callback page so the session is loaded in the client.
*
* If just a string containing an email address is specififed will generate a
* a one-time use sign in link and send it via email; you should redirect to a
* page telling the user to check their inbox for an email with the link.
*/
static async signin(params) {
// Params can be just string (an email address) or an object (form fields)
const formData = (typeof params === 'string') ? { email: params } : params;
// Use either the email token generation route or the custom form auth route
const route = (typeof params === 'string') ? '/auth/email/signin' : '/auth/signin';
// Add latest CSRF Token to request
formData._csrf = await this.csrfToken();
// Encoded form parser for sending data in the body
const encodedForm = Object.keys(formData).map((key) => {
return encodeURIComponent(key) + '=' + encodeURIComponent(formData[key])
}).join('&');
return fetch(route, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest' // So Express can detect AJAX post
},
body: encodedForm,
credentials: 'same-origin'
})
.then(async response => {
if (response.ok) {
return await response.json()
} else {
throw new Error('HTTP error while attempting to sign in')
}
})
.then(data => {
if (data.success && data.success === true) {
return Promise.resolve(true)
} else {
return Promise.resolve(false)
}
})
}
static async signout() {
// Signout from the server
const csrfToken = await this.csrfToken();
const formData = { _csrf: csrfToken };
// Encoded form parser for sending data in the body
const encodedForm = Object.keys(formData).map((key) => {
return encodeURIComponent(key) + '=' + encodeURIComponent(formData[key])
}).join('&');
// Remove cached session data
this._removeLocalStore('session');
return fetch('/auth/signout', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: encodedForm,
credentials: 'same-origin'
})
.then(() => {
return true
})
.catch(() => Error('Unable to sign out'))
}
// 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.
static _getLocalStore(name) {
try {
return JSON.parse(localStorage.getItem(name))
} catch (err) {
return null
}
}
static _saveLocalStore(name, data) {
try {
localStorage.setItem(name, JSON.stringify(data));
return true
} catch (err) {
return false
}
}
static _removeLocalStore(name) {
try {
localStorage.removeItem(name);
return true
} catch (err) {
return false
}
}
}
}]);
exports.NextAuth = nextAuthClient;
return _class;
}();
exports.NextAuth = _class;
Object.defineProperty(exports, '__esModule', { value: true });

View File

@@ -1,6 +1,6 @@
{
"name": "next-auth-examples",
"version": "1.10.0",
"version": "1.11.0",
"description": "An example project for next-auth",
"repository": "https://github.com/iaincollins/next-auth.git",
"main": "",

View File

@@ -35,14 +35,12 @@ export default class extends React.Component {
text-align: center;
transform: translate(-50%, -50%);
}
.circle-loader .circle {
fill: transparent;
stroke: rgba(0,0,0,0.2);
stroke-width: 4px;
animation: dash 2s ease infinite, rotate 2s linear infinite;
}
@keyframes dash {
0% {
stroke-dasharray: 1,95;
@@ -57,17 +55,29 @@ export default class extends React.Component {
stroke-dashoffset: -93;
}
}
@keyframes rotate {
0% {transform: rotate(0deg); }
100% {transform: rotate(360deg); }
}
`}</style>
<noscript>
<style>{`
svg {
display: none;
}
a {
font-weight: bold;
}
`}</style>
</noscript>
<a href="/" className="circle-loader">
<svg className="circle" width="60" height="60" version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle cx="30" cy="30" r="15"/>
</svg>
</a>
<noscript>
Click here to continue
</noscript>
</a>
</React.Fragment>
)
}

971
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "next-auth",
"version": "1.10.0",
"version": "1.11.0",
"description": "An authentication library for Next.js",
"repository": "https://github.com/iaincollins/next-auth.git",
"main": "index.js",
@@ -11,15 +11,20 @@
"author": "",
"license": "ISC",
"dependencies": {
"babel-core": "^6.26.3",
"babel-polyfill": "^6.26.0",
"body-parser": "^1.18.2",
"express": "^4.16.3",
"express-session": "^1.15.6",
"isomorphic-fetch": "^2.2.1",
"lusca": "^1.6.0",
"passport": "^0.4.0",
"rollup-plugin-babel": "^3.0.7",
"uuid": "^3.2.1"
},
"devDependencies": {
"babel-plugin-external-helpers": "^6.22.0",
"babel-preset-env": "^1.7.0",
"rollup": "^0.63.0",
"rollup-plugin-commonjs": "^9.1.3",
"rollup-plugin-json": "^3.0.0",

View File

@@ -1,3 +1,5 @@
import babel from 'rollup-plugin-babel'
export default {
input: 'src/client/index.js',
output: {
@@ -7,5 +9,13 @@ export default {
globals: {
'fetch': 'isomorphic-fetch'
}
}
},
plugins: [
babel({
babelrc: false,
exclude: [ 'node_modules/**' ],
presets: [['env', { modules: false }]]
})
],
}

View File

@@ -1,5 +1,6 @@
'use strict'
import "babel-polyfill"
import NextAuth from './next-auth-client'
export {