diff --git a/.gitignore b/.gitignore index e583affafe..48ed3a4e73 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,6 @@ node_modules # Build dirs .next /build -/dist /www/build # Generated files diff --git a/dist/adapters/error-handler.js b/dist/adapters/error-handler.js new file mode 100644 index 0000000000..a785a35100 --- /dev/null +++ b/dist/adapters/error-handler.js @@ -0,0 +1,38 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = adapterErrorHandler; + +var _errors = require("../lib/errors"); + +function adapterErrorHandler(adapter, logger) { + return Object.keys(adapter).reduce((acc, method) => { + const name = capitalize(method); + const code = upperSnake(name, adapter.displayName); + const adapterMethod = adapter[method]; + + acc[method] = async (...args) => { + try { + logger.debug(code, ...args); + return await adapterMethod(...args); + } catch (error) { + logger.error(`${code}_ERROR`, error); + const e = new _errors.UnknownError(error); + e.name = `${name}Error`; + throw e; + } + }; + + return acc; + }, {}); +} + +function capitalize(s) { + return `${s[0].toUpperCase()}${s.slice(1)}`; +} + +function upperSnake(s, prefix = "ADAPTER") { + return `${prefix}_${s.replace(/([A-Z])/g, "_$1")}`.toUpperCase(); +} \ No newline at end of file diff --git a/dist/adapters/index.js b/dist/adapters/index.js new file mode 100644 index 0000000000..f69898429e --- /dev/null +++ b/dist/adapters/index.js @@ -0,0 +1,22 @@ +"use strict"; + +var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.Prisma = exports.TypeORM = exports.default = void 0; + +var TypeORM = _interopRequireWildcard(require("./typeorm")); + +exports.TypeORM = TypeORM; + +var Prisma = _interopRequireWildcard(require("./prisma")); + +exports.Prisma = Prisma; +var _default = { + Default: TypeORM.Adapter, + TypeORM, + Prisma +}; +exports.default = _default; \ No newline at end of file diff --git a/dist/adapters/prisma.js b/dist/adapters/prisma.js new file mode 100644 index 0000000000..a52bbda532 --- /dev/null +++ b/dist/adapters/prisma.js @@ -0,0 +1,13 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "Adapter", { + enumerable: true, + get: function () { + return _prismaLegacyAdapter.PrismaLegacyAdapter; + } +}); + +var _prismaLegacyAdapter = require("@next-auth/prisma-legacy-adapter"); \ No newline at end of file diff --git a/dist/adapters/typeorm.js b/dist/adapters/typeorm.js new file mode 100644 index 0000000000..b239a36344 --- /dev/null +++ b/dist/adapters/typeorm.js @@ -0,0 +1,19 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "Adapter", { + enumerable: true, + get: function () { + return _typeormLegacyAdapter.TypeORMLegacyAdapter; + } +}); +Object.defineProperty(exports, "Models", { + enumerable: true, + get: function () { + return _typeormLegacyAdapter.Models; + } +}); + +var _typeormLegacyAdapter = require("@next-auth/typeorm-legacy-adapter"); \ No newline at end of file diff --git a/dist/client/index.js b/dist/client/index.js new file mode 100644 index 0000000000..012fd472fe --- /dev/null +++ b/dist/client/index.js @@ -0,0 +1,722 @@ +"use strict"; + +var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.useSession = useSession; +exports.session = exports.getSession = getSession; +exports.csrfToken = exports.getCsrfToken = getCsrfToken; +exports.providers = exports.getProviders = getProviders; +exports.signin = exports.signIn = signIn; +exports.signout = exports.signOut = signOut; +exports.options = exports.setOptions = setOptions; +exports.Provider = Provider; +exports.default = void 0; + +var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); + +var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); + +var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); + +var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); + +var _react = require("react"); + +var _logger2 = _interopRequireWildcard(require("../lib/logger")); + +var _parseUrl = _interopRequireDefault(require("../lib/parse-url")); + +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } + +var __NEXTAUTH = { + baseUrl: (0, _parseUrl.default)(process.env.NEXTAUTH_URL || process.env.VERCEL_URL).baseUrl, + basePath: (0, _parseUrl.default)(process.env.NEXTAUTH_URL).basePath, + baseUrlServer: (0, _parseUrl.default)(process.env.NEXTAUTH_URL_INTERNAL || process.env.NEXTAUTH_URL || process.env.VERCEL_URL).baseUrl, + basePathServer: (0, _parseUrl.default)(process.env.NEXTAUTH_URL_INTERNAL || process.env.NEXTAUTH_URL).basePath, + keepAlive: 0, + clientMaxAge: 0, + _clientLastSync: 0, + _clientSyncTimer: null, + _eventListenersAdded: false, + _clientSession: undefined, + _getSession: function _getSession() {} +}; +var logger = (0, _logger2.proxyLogger)(_logger2.default, __NEXTAUTH.basePath); +var broadcast = BroadcastChannel(); + +if (typeof window !== 'undefined' && !__NEXTAUTH._eventListenersAdded) { + __NEXTAUTH._eventListenersAdded = true; + broadcast.receive(function () { + return __NEXTAUTH._getSession({ + event: 'storage' + }); + }); + document.addEventListener('visibilitychange', function () { + !document.hidden && __NEXTAUTH._getSession({ + event: 'visibilitychange' + }); + }, false); +} + +var SessionContext = (0, _react.createContext)(); + +function useSession(session) { + var context = (0, _react.useContext)(SessionContext); + if (context) return context; + return _useSessionHook(session); +} + +function _useSessionHook(session) { + var _useState = (0, _react.useState)(session), + _useState2 = (0, _slicedToArray2.default)(_useState, 2), + data = _useState2[0], + setData = _useState2[1]; + + var _useState3 = (0, _react.useState)(!data), + _useState4 = (0, _slicedToArray2.default)(_useState3, 2), + loading = _useState4[0], + setLoading = _useState4[1]; + + (0, _react.useEffect)(function () { + __NEXTAUTH._getSession = (0, _asyncToGenerator2.default)(_regenerator.default.mark(function _callee() { + var _ref2, + _ref2$event, + event, + triggredByEvent, + triggeredByStorageEvent, + clientMaxAge, + clientLastSync, + currentTime, + clientSession, + newClientSessionData, + _args = arguments; + + return _regenerator.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + _ref2 = _args.length > 0 && _args[0] !== undefined ? _args[0] : {}, _ref2$event = _ref2.event, event = _ref2$event === void 0 ? null : _ref2$event; + _context.prev = 1; + triggredByEvent = event !== null; + triggeredByStorageEvent = event === 'storage'; + clientMaxAge = __NEXTAUTH.clientMaxAge; + clientLastSync = parseInt(__NEXTAUTH._clientLastSync); + currentTime = _now(); + clientSession = __NEXTAUTH._clientSession; + + if (!(!triggeredByStorageEvent && clientSession !== undefined)) { + _context.next = 19; + break; + } + + if (!(clientMaxAge === 0 && triggredByEvent !== true)) { + _context.next = 13; + break; + } + + return _context.abrupt("return"); + + case 13: + if (!(clientMaxAge > 0 && clientSession === null)) { + _context.next = 17; + break; + } + + return _context.abrupt("return"); + + case 17: + if (!(clientMaxAge > 0 && currentTime < clientLastSync + clientMaxAge)) { + _context.next = 19; + break; + } + + return _context.abrupt("return"); + + case 19: + if (clientSession === undefined) { + __NEXTAUTH._clientSession = null; + } + + __NEXTAUTH._clientLastSync = _now(); + _context.next = 23; + return getSession({ + triggerEvent: !triggeredByStorageEvent + }); + + case 23: + newClientSessionData = _context.sent; + __NEXTAUTH._clientSession = newClientSessionData; + setData(newClientSessionData); + setLoading(false); + _context.next = 33; + break; + + case 29: + _context.prev = 29; + _context.t0 = _context["catch"](1); + logger.error('CLIENT_USE_SESSION_ERROR', _context.t0); + setLoading(false); + + case 33: + case "end": + return _context.stop(); + } + } + }, _callee, null, [[1, 29]]); + })); + + __NEXTAUTH._getSession(); + }); + return [data, loading]; +} + +function getSession(_x) { + return _getSession2.apply(this, arguments); +} + +function _getSession2() { + _getSession2 = (0, _asyncToGenerator2.default)(_regenerator.default.mark(function _callee4(ctx) { + var _ctx$triggerEvent; + + var session; + return _regenerator.default.wrap(function _callee4$(_context4) { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + _context4.next = 2; + return _fetchData('session', ctx); + + case 2: + session = _context4.sent; + + if ((_ctx$triggerEvent = ctx === null || ctx === void 0 ? void 0 : ctx.triggerEvent) !== null && _ctx$triggerEvent !== void 0 ? _ctx$triggerEvent : true) { + broadcast.post({ + event: 'session', + data: { + trigger: 'getSession' + } + }); + } + + return _context4.abrupt("return", session); + + case 5: + case "end": + return _context4.stop(); + } + } + }, _callee4); + })); + return _getSession2.apply(this, arguments); +} + +function getCsrfToken(_x2) { + return _getCsrfToken.apply(this, arguments); +} + +function _getCsrfToken() { + _getCsrfToken = (0, _asyncToGenerator2.default)(_regenerator.default.mark(function _callee5(ctx) { + var _yield$_fetchData; + + return _regenerator.default.wrap(function _callee5$(_context5) { + while (1) { + switch (_context5.prev = _context5.next) { + case 0: + _context5.next = 2; + return _fetchData('csrf', ctx); + + case 2: + _context5.t1 = _yield$_fetchData = _context5.sent; + _context5.t0 = _context5.t1 === null; + + if (_context5.t0) { + _context5.next = 6; + break; + } + + _context5.t0 = _yield$_fetchData === void 0; + + case 6: + if (!_context5.t0) { + _context5.next = 10; + break; + } + + _context5.t2 = void 0; + _context5.next = 11; + break; + + case 10: + _context5.t2 = _yield$_fetchData.csrfToken; + + case 11: + return _context5.abrupt("return", _context5.t2); + + case 12: + case "end": + return _context5.stop(); + } + } + }, _callee5); + })); + return _getCsrfToken.apply(this, arguments); +} + +function getProviders() { + return _getProviders.apply(this, arguments); +} + +function _getProviders() { + _getProviders = (0, _asyncToGenerator2.default)(_regenerator.default.mark(function _callee6() { + return _regenerator.default.wrap(function _callee6$(_context6) { + while (1) { + switch (_context6.prev = _context6.next) { + case 0: + return _context6.abrupt("return", _fetchData('providers')); + + case 1: + case "end": + return _context6.stop(); + } + } + }, _callee6); + })); + return _getProviders.apply(this, arguments); +} + +function signIn(_x3) { + return _signIn.apply(this, arguments); +} + +function _signIn() { + _signIn = (0, _asyncToGenerator2.default)(_regenerator.default.mark(function _callee7(provider) { + var options, + authorizationParams, + _options$callbackUrl, + callbackUrl, + _options$redirect, + redirect, + baseUrl, + providers, + isCredentials, + isEmail, + canRedirectBeDisabled, + signInUrl, + fetchOptions, + _signInUrl, + res, + data, + _data$url, + url, + error, + _args7 = arguments; + + return _regenerator.default.wrap(function _callee7$(_context7) { + while (1) { + switch (_context7.prev = _context7.next) { + case 0: + options = _args7.length > 1 && _args7[1] !== undefined ? _args7[1] : {}; + authorizationParams = _args7.length > 2 && _args7[2] !== undefined ? _args7[2] : {}; + _options$callbackUrl = options.callbackUrl, callbackUrl = _options$callbackUrl === void 0 ? window.location : _options$callbackUrl, _options$redirect = options.redirect, redirect = _options$redirect === void 0 ? true : _options$redirect; + baseUrl = _apiBaseUrl(); + _context7.next = 6; + return getProviders(); + + case 6: + providers = _context7.sent; + + if (provider in providers) { + _context7.next = 10; + break; + } + + window.location = "".concat(baseUrl, "/signin?callbackUrl=").concat(encodeURIComponent(callbackUrl)); + return _context7.abrupt("return"); + + case 10: + isCredentials = providers[provider].type === 'credentials'; + isEmail = providers[provider].type === 'email'; + canRedirectBeDisabled = isCredentials || isEmail; + signInUrl = isCredentials ? "".concat(baseUrl, "/callback/").concat(provider) : "".concat(baseUrl, "/signin/").concat(provider); + _context7.t0 = { + 'Content-Type': 'application/x-www-form-urlencoded' + }; + _context7.t1 = URLSearchParams; + _context7.t2 = _objectSpread; + _context7.t3 = _objectSpread({}, options); + _context7.t4 = {}; + _context7.next = 21; + return getCsrfToken(); + + case 21: + _context7.t5 = _context7.sent; + _context7.t6 = callbackUrl; + _context7.t7 = { + csrfToken: _context7.t5, + callbackUrl: _context7.t6, + json: true + }; + _context7.t8 = (0, _context7.t2)(_context7.t3, _context7.t4, _context7.t7); + _context7.t9 = new _context7.t1(_context7.t8); + fetchOptions = { + method: 'post', + headers: _context7.t0, + body: _context7.t9 + }; + _signInUrl = "".concat(signInUrl, "?").concat(new URLSearchParams(authorizationParams)); + _context7.next = 30; + return fetch(_signInUrl, fetchOptions); + + case 30: + res = _context7.sent; + _context7.next = 33; + return res.json(); + + case 33: + data = _context7.sent; + + if (!(redirect || !canRedirectBeDisabled)) { + _context7.next = 39; + break; + } + + url = (_data$url = data.url) !== null && _data$url !== void 0 ? _data$url : callbackUrl; + window.location = url; + if (url.includes('#')) window.location.reload(); + return _context7.abrupt("return"); + + case 39: + error = new URL(data.url).searchParams.get('error'); + + if (!res.ok) { + _context7.next = 43; + break; + } + + _context7.next = 43; + return __NEXTAUTH._getSession({ + event: 'storage' + }); + + case 43: + return _context7.abrupt("return", { + error: error, + status: res.status, + ok: res.ok, + url: error ? null : data.url + }); + + case 44: + case "end": + return _context7.stop(); + } + } + }, _callee7); + })); + return _signIn.apply(this, arguments); +} + +function signOut() { + return _signOut.apply(this, arguments); +} + +function _signOut() { + _signOut = (0, _asyncToGenerator2.default)(_regenerator.default.mark(function _callee8() { + var options, + _options$callbackUrl2, + callbackUrl, + _options$redirect2, + redirect, + baseUrl, + fetchOptions, + res, + data, + _data$url2, + url, + _args8 = arguments; + + return _regenerator.default.wrap(function _callee8$(_context8) { + while (1) { + switch (_context8.prev = _context8.next) { + case 0: + options = _args8.length > 0 && _args8[0] !== undefined ? _args8[0] : {}; + _options$callbackUrl2 = options.callbackUrl, callbackUrl = _options$callbackUrl2 === void 0 ? window.location : _options$callbackUrl2, _options$redirect2 = options.redirect, redirect = _options$redirect2 === void 0 ? true : _options$redirect2; + baseUrl = _apiBaseUrl(); + _context8.t0 = { + 'Content-Type': 'application/x-www-form-urlencoded' + }; + _context8.t1 = URLSearchParams; + _context8.next = 7; + return getCsrfToken(); + + case 7: + _context8.t2 = _context8.sent; + _context8.t3 = callbackUrl; + _context8.t4 = { + csrfToken: _context8.t2, + callbackUrl: _context8.t3, + json: true + }; + _context8.t5 = new _context8.t1(_context8.t4); + fetchOptions = { + method: 'post', + headers: _context8.t0, + body: _context8.t5 + }; + _context8.next = 14; + return fetch("".concat(baseUrl, "/signout"), fetchOptions); + + case 14: + res = _context8.sent; + _context8.next = 17; + return res.json(); + + case 17: + data = _context8.sent; + broadcast.post({ + event: 'session', + data: { + trigger: 'signout' + } + }); + + if (!redirect) { + _context8.next = 24; + break; + } + + url = (_data$url2 = data.url) !== null && _data$url2 !== void 0 ? _data$url2 : callbackUrl; + window.location = url; + if (url.includes('#')) window.location.reload(); + return _context8.abrupt("return"); + + case 24: + _context8.next = 26; + return __NEXTAUTH._getSession({ + event: 'storage' + }); + + case 26: + return _context8.abrupt("return", data); + + case 27: + case "end": + return _context8.stop(); + } + } + }, _callee8); + })); + return _signOut.apply(this, arguments); +} + +function setOptions() { + var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, + baseUrl = _ref3.baseUrl, + basePath = _ref3.basePath, + clientMaxAge = _ref3.clientMaxAge, + keepAlive = _ref3.keepAlive; + + if (baseUrl) __NEXTAUTH.baseUrl = baseUrl; + if (basePath) __NEXTAUTH.basePath = basePath; + if (clientMaxAge) __NEXTAUTH.clientMaxAge = clientMaxAge; + + if (keepAlive) { + __NEXTAUTH.keepAlive = keepAlive; + if (typeof window === 'undefined') return; + + if (__NEXTAUTH._clientSyncTimer !== null) { + clearTimeout(__NEXTAUTH._clientSyncTimer); + } + + __NEXTAUTH._clientSyncTimer = setTimeout((0, _asyncToGenerator2.default)(_regenerator.default.mark(function _callee2() { + return _regenerator.default.wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + if (__NEXTAUTH._clientSession) { + _context2.next = 2; + break; + } + + return _context2.abrupt("return"); + + case 2: + _context2.next = 4; + return __NEXTAUTH._getSession({ + event: 'timer' + }); + + case 4: + case "end": + return _context2.stop(); + } + } + }, _callee2); + })), keepAlive * 1000); + } +} + +function Provider(_ref5) { + var children = _ref5.children, + session = _ref5.session, + options = _ref5.options; + setOptions(options); + return (0, _react.createElement)(SessionContext.Provider, { + value: useSession(session) + }, children); +} + +function _fetchData(_x4) { + return _fetchData2.apply(this, arguments); +} + +function _fetchData2() { + _fetchData2 = (0, _asyncToGenerator2.default)(_regenerator.default.mark(function _callee9(path) { + var _ref7, + ctx, + _ref7$req, + req, + baseUrl, + options, + res, + data, + _args9 = arguments; + + return _regenerator.default.wrap(function _callee9$(_context9) { + while (1) { + switch (_context9.prev = _context9.next) { + case 0: + _ref7 = _args9.length > 1 && _args9[1] !== undefined ? _args9[1] : {}, ctx = _ref7.ctx, _ref7$req = _ref7.req, req = _ref7$req === void 0 ? ctx === null || ctx === void 0 ? void 0 : ctx.req : _ref7$req; + _context9.prev = 1; + _context9.next = 4; + return _apiBaseUrl(); + + case 4: + baseUrl = _context9.sent; + options = req ? { + headers: { + cookie: req.headers.cookie + } + } : {}; + _context9.next = 8; + return fetch("".concat(baseUrl, "/").concat(path), options); + + case 8: + res = _context9.sent; + _context9.next = 11; + return res.json(); + + case 11: + data = _context9.sent; + return _context9.abrupt("return", Object.keys(data).length > 0 ? data : null); + + case 15: + _context9.prev = 15; + _context9.t0 = _context9["catch"](1); + logger.error('CLIENT_FETCH_ERROR', path, _context9.t0); + return _context9.abrupt("return", null); + + case 19: + case "end": + return _context9.stop(); + } + } + }, _callee9, null, [[1, 15]]); + })); + return _fetchData2.apply(this, arguments); +} + +function _apiBaseUrl() { + if (typeof window === 'undefined') { + if (!process.env.NEXTAUTH_URL) { + logger.warn('NEXTAUTH_URL', 'NEXTAUTH_URL environment variable not set'); + } + + return "".concat(__NEXTAUTH.baseUrlServer).concat(__NEXTAUTH.basePathServer); + } + + return __NEXTAUTH.basePath; +} + +function _now() { + return Math.floor(Date.now() / 1000); +} + +function BroadcastChannel() { + var name = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'nextauth.message'; + return { + receive: function receive(onReceive) { + if (typeof window === 'undefined') return; + window.addEventListener('storage', function () { + var _ref6 = (0, _asyncToGenerator2.default)(_regenerator.default.mark(function _callee3(event) { + var message; + return _regenerator.default.wrap(function _callee3$(_context3) { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + if (!(event.key !== name)) { + _context3.next = 2; + break; + } + + return _context3.abrupt("return"); + + case 2: + message = JSON.parse(event.newValue); + + if (!((message === null || message === void 0 ? void 0 : message.event) !== 'session' || !(message !== null && message !== void 0 && message.data))) { + _context3.next = 5; + break; + } + + return _context3.abrupt("return"); + + case 5: + onReceive(message); + + case 6: + case "end": + return _context3.stop(); + } + } + }, _callee3); + })); + + return function (_x5) { + return _ref6.apply(this, arguments); + }; + }()); + }, + post: function post(message) { + if (typeof localStorage === 'undefined') return; + localStorage.setItem(name, JSON.stringify(_objectSpread(_objectSpread({}, message), {}, { + timestamp: _now() + }))); + } + }; +} + +var _default = { + getSession: getSession, + getCsrfToken: getCsrfToken, + getProviders: getProviders, + useSession: useSession, + signIn: signIn, + signOut: signOut, + Provider: Provider, + setOptions: setOptions, + options: setOptions, + session: getSession, + providers: getProviders, + csrfToken: getCsrfToken, + signin: signIn, + signout: signOut +}; +exports.default = _default; \ No newline at end of file diff --git a/dist/css/index.css b/dist/css/index.css new file mode 100644 index 0000000000..a33726944c --- /dev/null +++ b/dist/css/index.css @@ -0,0 +1 @@ +:root{--border-width:1px;--border-radius:.3rem;--color-error:#c94b4b;--color-info:#157efb;--color-info-text:#fff}.__next-auth-theme-auto,.__next-auth-theme-light{--color-background:#fff;--color-text:#000;--color-primary:#444;--color-control-border:#bbb;--color-button-active-background:#f9f9f9;--color-button-active-border:#aaa;--color-seperator:#ccc}.__next-auth-theme-dark{--color-background:#000;--color-text:#fff;--color-primary:#ccc;--color-control-border:#555;--color-button-active-background:#060606;--color-button-active-border:#666;--color-seperator:#444}@media (prefers-color-scheme:dark){.__next-auth-theme-auto{--color-background:#000;--color-text:#fff;--color-primary:#ccc;--color-control-border:#555;--color-button-active-background:#060606;--color-button-active-border:#666;--color-seperator:#444}}body{background-color:var(--color-background);margin:0;padding:0;font-family:-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,Helvetica,Arial,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol}h1{font-weight:400;margin-bottom:1.5rem;padding:0 1rem}h1,p{color:var(--color-text)}form{margin:0;padding:0}label{font-weight:500;text-align:left;margin-bottom:.25rem;display:block;color:#666}input[type]{box-sizing:border-box;display:block;width:100%;padding:.5rem 1rem;border:var(--border-width) solid var(--color-control-border);background:var(--color-background);font-size:1rem;border-radius:var(--border-radius);box-shadow:inset 0 .1rem .2rem rgba(0,0,0,.2);color:var(--color-text)}input[type]:focus{box-shadow:none}p{margin:0 0 1.5rem;padding:0 1rem;font-size:1.1rem;line-height:2rem}a.button{text-decoration:none;line-height:1rem}a.button,a.button:link,a.button:visited,button{background-color:var(--color-background);color:var(--color-primary)}a.button,button{margin:0 0 .75rem;padding:.75rem 1rem;border:var(--border-width) solid var(--color-control-border);font-size:1rem;border-radius:var(--border-radius);transition:all .1s ease-in-out;box-shadow:0 .15rem .3rem rgba(0,0,0,.15),inset 0 .1rem .2rem var(--color-background),inset 0 -.1rem .1rem rgba(0,0,0,.05);font-weight:500;position:relative}a.button:hover,button:hover{cursor:pointer}a.button:active,button:active{box-shadow:0 .15rem .3rem rgba(0,0,0,.15),inset 0 .1rem .2rem var(--color-background),inset 0 -.1rem .1rem rgba(0,0,0,.1);background-color:var(--color-button-active-background);border-color:var(--color-button-active-border);cursor:pointer}a.site{color:var(--color-primary);text-decoration:none;font-size:1rem;line-height:2rem}a.site:hover{text-decoration:underline}.page{position:absolute;width:100%;height:100%;display:table;margin:0;padding:0}.page>div{display:table-cell;vertical-align:middle;text-align:center;padding:.5rem}.error a.button{display:inline-block;padding-left:2rem;padding-right:2rem;margin-top:.5rem}.error .message{margin-bottom:1.5rem}.signin a.button,.signin button,.signin input[type=text]{margin-left:auto;margin-right:auto;display:block}.signin hr{display:block;border:0;border-top:1px solid var(--color-seperator);margin:1.5em auto 0;overflow:visible}.signin hr:before{content:"or";background:var(--color-background);color:#888;padding:0 .4rem;position:relative;top:-.6rem}.signin .error{background:#f5f5f5;font-weight:500;border-radius:.3rem;background:var(--color-info)}.signin .error p{text-align:left;padding:.5rem 1rem;font-size:.9rem;line-height:1.2rem;color:var(--color-info-text)}.signin>div,.signin form{display:block;margin:0 auto .5rem;max-width:300px}.signin>div input[type],.signin form input[type]{margin-bottom:.5rem}.signin>div button,.signin form button{width:100%} \ No newline at end of file diff --git a/dist/css/index.js b/dist/css/index.js new file mode 100644 index 0000000000..9bf7105234 --- /dev/null +++ b/dist/css/index.js @@ -0,0 +1 @@ +module.exports = function() { return ":root{--border-width:1px;--border-radius:.3rem;--color-error:#c94b4b;--color-info:#157efb;--color-info-text:#fff}.__next-auth-theme-auto,.__next-auth-theme-light{--color-background:#fff;--color-text:#000;--color-primary:#444;--color-control-border:#bbb;--color-button-active-background:#f9f9f9;--color-button-active-border:#aaa;--color-seperator:#ccc}.__next-auth-theme-dark{--color-background:#000;--color-text:#fff;--color-primary:#ccc;--color-control-border:#555;--color-button-active-background:#060606;--color-button-active-border:#666;--color-seperator:#444}@media (prefers-color-scheme:dark){.__next-auth-theme-auto{--color-background:#000;--color-text:#fff;--color-primary:#ccc;--color-control-border:#555;--color-button-active-background:#060606;--color-button-active-border:#666;--color-seperator:#444}}body{background-color:var(--color-background);margin:0;padding:0;font-family:-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,Helvetica,Arial,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol}h1{font-weight:400;margin-bottom:1.5rem;padding:0 1rem}h1,p{color:var(--color-text)}form{margin:0;padding:0}label{font-weight:500;text-align:left;margin-bottom:.25rem;display:block;color:#666}input[type]{box-sizing:border-box;display:block;width:100%;padding:.5rem 1rem;border:var(--border-width) solid var(--color-control-border);background:var(--color-background);font-size:1rem;border-radius:var(--border-radius);box-shadow:inset 0 .1rem .2rem rgba(0,0,0,.2);color:var(--color-text)}input[type]:focus{box-shadow:none}p{margin:0 0 1.5rem;padding:0 1rem;font-size:1.1rem;line-height:2rem}a.button{text-decoration:none;line-height:1rem}a.button,a.button:link,a.button:visited,button{background-color:var(--color-background);color:var(--color-primary)}a.button,button{margin:0 0 .75rem;padding:.75rem 1rem;border:var(--border-width) solid var(--color-control-border);font-size:1rem;border-radius:var(--border-radius);transition:all .1s ease-in-out;box-shadow:0 .15rem .3rem rgba(0,0,0,.15),inset 0 .1rem .2rem var(--color-background),inset 0 -.1rem .1rem rgba(0,0,0,.05);font-weight:500;position:relative}a.button:hover,button:hover{cursor:pointer}a.button:active,button:active{box-shadow:0 .15rem .3rem rgba(0,0,0,.15),inset 0 .1rem .2rem var(--color-background),inset 0 -.1rem .1rem rgba(0,0,0,.1);background-color:var(--color-button-active-background);border-color:var(--color-button-active-border);cursor:pointer}a.site{color:var(--color-primary);text-decoration:none;font-size:1rem;line-height:2rem}a.site:hover{text-decoration:underline}.page{position:absolute;width:100%;height:100%;display:table;margin:0;padding:0}.page>div{display:table-cell;vertical-align:middle;text-align:center;padding:.5rem}.error a.button{display:inline-block;padding-left:2rem;padding-right:2rem;margin-top:.5rem}.error .message{margin-bottom:1.5rem}.signin a.button,.signin button,.signin input[type=text]{margin-left:auto;margin-right:auto;display:block}.signin hr{display:block;border:0;border-top:1px solid var(--color-seperator);margin:1.5em auto 0;overflow:visible}.signin hr:before{content:\"or\";background:var(--color-background);color:#888;padding:0 .4rem;position:relative;top:-.6rem}.signin .error{background:#f5f5f5;font-weight:500;border-radius:.3rem;background:var(--color-info)}.signin .error p{text-align:left;padding:.5rem 1rem;font-size:.9rem;line-height:1.2rem;color:var(--color-info-text)}.signin>div,.signin form{display:block;margin:0 auto .5rem;max-width:300px}.signin>div input[type],.signin form input[type]{margin-bottom:.5rem}.signin>div button,.signin form button{width:100%}" } \ No newline at end of file diff --git a/dist/lib/errors.js b/dist/lib/errors.js new file mode 100644 index 0000000000..2a5ed5e7d3 --- /dev/null +++ b/dist/lib/errors.js @@ -0,0 +1,214 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.DeleteVerificationRequestError = exports.GetVerificationRequestError = exports.CreateVerificationRequestError = exports.DeleteSessionError = exports.UpdateSessionError = exports.GetSessionError = exports.CreateSessionError = exports.UnlinkAccountError = exports.LinkAccountError = exports.DeleteUserError = exports.UpdateUserError = exports.GetUserByProviderAccountIdError = exports.GetUserByIdError = exports.GetUserByEmailError = exports.GetUserError = exports.CreateUserError = exports.AccountNotLinkedError = exports.OAuthCallbackError = exports.UnknownError = void 0; + +var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); + +class UnknownError extends Error { + constructor(error) { + var _error$message; + + super((_error$message = error === null || error === void 0 ? void 0 : error.message) !== null && _error$message !== void 0 ? _error$message : error); + this.name = "UnknownError"; + + if (error instanceof Error) { + this.stack = error.stack; + } + } + + toJSON() { + return { + name: this.name, + message: this.message, + stack: this.stack + }; + } + +} + +exports.UnknownError = UnknownError; + +class OAuthCallbackError extends UnknownError { + constructor(...args) { + super(...args); + (0, _defineProperty2.default)(this, "name", "OAuthCallbackError"); + } + +} + +exports.OAuthCallbackError = OAuthCallbackError; + +class AccountNotLinkedError extends UnknownError { + constructor(...args) { + super(...args); + (0, _defineProperty2.default)(this, "name", "AccountNotLinkedError"); + } + +} + +exports.AccountNotLinkedError = AccountNotLinkedError; + +class CreateUserError extends UnknownError { + constructor(...args) { + super(...args); + (0, _defineProperty2.default)(this, "name", "CreateUserError"); + } + +} + +exports.CreateUserError = CreateUserError; + +class GetUserError extends UnknownError { + constructor(...args) { + super(...args); + (0, _defineProperty2.default)(this, "name", "GetUserError"); + } + +} + +exports.GetUserError = GetUserError; + +class GetUserByEmailError extends UnknownError { + constructor(...args) { + super(...args); + (0, _defineProperty2.default)(this, "name", "GetUserByEmailError"); + } + +} + +exports.GetUserByEmailError = GetUserByEmailError; + +class GetUserByIdError extends UnknownError { + constructor(...args) { + super(...args); + (0, _defineProperty2.default)(this, "name", "GetUserByIdError"); + } + +} + +exports.GetUserByIdError = GetUserByIdError; + +class GetUserByProviderAccountIdError extends UnknownError { + constructor(...args) { + super(...args); + (0, _defineProperty2.default)(this, "name", "GetUserByProviderAccountIdError"); + } + +} + +exports.GetUserByProviderAccountIdError = GetUserByProviderAccountIdError; + +class UpdateUserError extends UnknownError { + constructor(...args) { + super(...args); + (0, _defineProperty2.default)(this, "name", "UpdateUserError"); + } + +} + +exports.UpdateUserError = UpdateUserError; + +class DeleteUserError extends UnknownError { + constructor(...args) { + super(...args); + (0, _defineProperty2.default)(this, "name", "DeleteUserError"); + } + +} + +exports.DeleteUserError = DeleteUserError; + +class LinkAccountError extends UnknownError { + constructor(...args) { + super(...args); + (0, _defineProperty2.default)(this, "name", "LinkAccountError"); + } + +} + +exports.LinkAccountError = LinkAccountError; + +class UnlinkAccountError extends UnknownError { + constructor(...args) { + super(...args); + (0, _defineProperty2.default)(this, "name", "UnlinkAccountError"); + } + +} + +exports.UnlinkAccountError = UnlinkAccountError; + +class CreateSessionError extends UnknownError { + constructor(...args) { + super(...args); + (0, _defineProperty2.default)(this, "name", "CreateSessionError"); + } + +} + +exports.CreateSessionError = CreateSessionError; + +class GetSessionError extends UnknownError { + constructor(...args) { + super(...args); + (0, _defineProperty2.default)(this, "name", "GetSessionError"); + } + +} + +exports.GetSessionError = GetSessionError; + +class UpdateSessionError extends UnknownError { + constructor(...args) { + super(...args); + (0, _defineProperty2.default)(this, "name", "UpdateSessionError"); + } + +} + +exports.UpdateSessionError = UpdateSessionError; + +class DeleteSessionError extends UnknownError { + constructor(...args) { + super(...args); + (0, _defineProperty2.default)(this, "name", "DeleteSessionError"); + } + +} + +exports.DeleteSessionError = DeleteSessionError; + +class CreateVerificationRequestError extends UnknownError { + constructor(...args) { + super(...args); + (0, _defineProperty2.default)(this, "name", "CreateVerificationRequestError"); + } + +} + +exports.CreateVerificationRequestError = CreateVerificationRequestError; + +class GetVerificationRequestError extends UnknownError { + constructor(...args) { + super(...args); + (0, _defineProperty2.default)(this, "name", "GetVerificationRequestError"); + } + +} + +exports.GetVerificationRequestError = GetVerificationRequestError; + +class DeleteVerificationRequestError extends UnknownError { + constructor(...args) { + super(...args); + (0, _defineProperty2.default)(this, "name", "DeleteVerificationRequestError"); + } + +} + +exports.DeleteVerificationRequestError = DeleteVerificationRequestError; \ No newline at end of file diff --git a/dist/lib/jwt.js b/dist/lib/jwt.js new file mode 100644 index 0000000000..432f28a5c1 --- /dev/null +++ b/dist/lib/jwt.js @@ -0,0 +1,183 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.encode = encode; +exports.decode = decode; +exports.getToken = getToken; +exports.default = void 0; + +var _crypto = _interopRequireDefault(require("crypto")); + +var _jose = _interopRequireDefault(require("jose")); + +var _logger = _interopRequireDefault(require("./logger")); + +const DEFAULT_SIGNATURE_ALGORITHM = "HS512"; +const DEFAULT_ENCRYPTION_ALGORITHM = "A256GCM"; +const DEFAULT_ENCRYPTION_ENABLED = false; +const DEFAULT_MAX_AGE = 30 * 24 * 60 * 60; + +async function encode({ + token = {}, + maxAge = DEFAULT_MAX_AGE, + secret, + signingKey, + signingOptions = { + expiresIn: `${maxAge}s` + }, + encryptionKey, + encryptionOptions = { + alg: "dir", + enc: DEFAULT_ENCRYPTION_ALGORITHM, + zip: "DEF" + }, + encryption = DEFAULT_ENCRYPTION_ENABLED +} = {}) { + const _signingKey = signingKey ? _jose.default.JWK.asKey(JSON.parse(signingKey)) : getDerivedSigningKey(secret); + + const signedToken = _jose.default.JWT.sign(token, _signingKey, signingOptions); + + if (encryption) { + const _encryptionKey = encryptionKey ? _jose.default.JWK.asKey(JSON.parse(encryptionKey)) : getDerivedEncryptionKey(secret); + + return _jose.default.JWE.encrypt(signedToken, _encryptionKey, encryptionOptions); + } + + return signedToken; +} + +async function decode({ + secret, + token, + maxAge = DEFAULT_MAX_AGE, + signingKey, + verificationKey = signingKey, + verificationOptions = { + maxTokenAge: `${maxAge}s`, + algorithms: [DEFAULT_SIGNATURE_ALGORITHM] + }, + encryptionKey, + decryptionKey = encryptionKey, + decryptionOptions = { + algorithms: [DEFAULT_ENCRYPTION_ALGORITHM] + }, + encryption = DEFAULT_ENCRYPTION_ENABLED +} = {}) { + if (!token) return null; + let tokenToVerify = token; + + if (encryption) { + const _encryptionKey = decryptionKey ? _jose.default.JWK.asKey(JSON.parse(decryptionKey)) : getDerivedEncryptionKey(secret); + + const decryptedToken = _jose.default.JWE.decrypt(token, _encryptionKey, decryptionOptions); + + tokenToVerify = decryptedToken.toString("utf8"); + } + + const _signingKey = verificationKey ? _jose.default.JWK.asKey(JSON.parse(verificationKey)) : getDerivedSigningKey(secret); + + return _jose.default.JWT.verify(tokenToVerify, _signingKey, verificationOptions); +} + +async function getToken(params) { + var _req$headers$authoriz; + + const { + req, + secureCookie = !(!process.env.NEXTAUTH_URL || process.env.NEXTAUTH_URL.startsWith("http://")), + cookieName = secureCookie ? "__Secure-next-auth.session-token" : "next-auth.session-token", + raw = false, + decode: _decode = decode + } = params; + if (!req) throw new Error("Must pass `req` to JWT getToken()"); + let token = req.cookies[cookieName]; + + if (!token && ((_req$headers$authoriz = req.headers.authorization) === null || _req$headers$authoriz === void 0 ? void 0 : _req$headers$authoriz.split(" ")[0]) === "Bearer") { + const urlEncodedToken = req.headers.authorization.split(" ")[1]; + token = decodeURIComponent(urlEncodedToken); + } + + if (raw) { + return token; + } + + try { + return _decode({ + token, + ...params + }); + } catch (_unused) { + return null; + } +} + +let DERIVED_SIGNING_KEY_WARNING = false; +let DERIVED_ENCRYPTION_KEY_WARNING = false; + +function hkdf(secret, { + byteLength, + encryptionInfo, + digest = "sha256" +}) { + if (_crypto.default.hkdfSync) { + return Buffer.from(_crypto.default.hkdfSync(digest, secret, Buffer.alloc(0), encryptionInfo, byteLength)); + } + + return require("futoin-hkdf")(secret, byteLength, { + info: encryptionInfo, + hash: digest + }); +} + +function getDerivedSigningKey(secret) { + if (!DERIVED_SIGNING_KEY_WARNING) { + _logger.default.warn("JWT_AUTO_GENERATED_SIGNING_KEY"); + + DERIVED_SIGNING_KEY_WARNING = true; + } + + const buffer = hkdf(secret, { + byteLength: 64, + encryptionInfo: "NextAuth.js Generated Signing Key" + }); + + const key = _jose.default.JWK.asKey(buffer, { + alg: DEFAULT_SIGNATURE_ALGORITHM, + use: "sig", + kid: "nextauth-auto-generated-signing-key" + }); + + return key; +} + +function getDerivedEncryptionKey(secret) { + if (!DERIVED_ENCRYPTION_KEY_WARNING) { + _logger.default.warn("JWT_AUTO_GENERATED_ENCRYPTION_KEY"); + + DERIVED_ENCRYPTION_KEY_WARNING = true; + } + + const buffer = hkdf(secret, { + byteLength: 32, + encryptionInfo: "NextAuth.js Generated Encryption Key" + }); + + const key = _jose.default.JWK.asKey(buffer, { + alg: DEFAULT_ENCRYPTION_ALGORITHM, + use: "enc", + kid: "nextauth-auto-generated-encryption-key" + }); + + return key; +} + +var _default = { + encode, + decode, + getToken +}; +exports.default = _default; \ No newline at end of file diff --git a/dist/lib/logger.js b/dist/lib/logger.js new file mode 100644 index 0000000000..a68ba2724f --- /dev/null +++ b/dist/lib/logger.js @@ -0,0 +1,83 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.setLogger = setLogger; +exports.proxyLogger = proxyLogger; +exports.default = void 0; +const _logger = { + error(code, ...message) { + console.error(`[next-auth][error][${code.toLowerCase()}]`, `\nhttps://next-auth.js.org/errors#${code.toLowerCase()}`, ...message); + }, + + warn(code, ...message) { + console.warn(`[next-auth][warn][${code.toLowerCase()}]`, `\nhttps://next-auth.js.org/warnings#${code.toLowerCase()}`, ...message); + }, + + debug(code, ...message) { + var _process, _process$env; + + if (!((_process = process) !== null && _process !== void 0 && (_process$env = _process.env) !== null && _process$env !== void 0 && _process$env._NEXTAUTH_DEBUG)) return; + console.log(`[next-auth][debug][${code.toLowerCase()}]`, ...message); + } + +}; + +function setLogger(newLogger = {}) { + if (newLogger.error) _logger.error = newLogger.error; + if (newLogger.warn) _logger.warn = newLogger.warn; + if (newLogger.debug) _logger.debug = newLogger.debug; +} + +var _default = _logger; +exports.default = _default; + +function proxyLogger(logger = _logger, basePath) { + try { + if (typeof window === "undefined") { + return logger; + } + + const clientLogger = {}; + + for (const level in logger) { + clientLogger[level] = (code, ...message) => { + _logger[level](code, ...message); + + const url = `${basePath}/_log`; + const body = new URLSearchParams({ + level, + code, + message: JSON.stringify(message.map(m => { + if (m instanceof Error) { + return { + name: m.name, + message: m.message, + stack: m.stack + }; + } + + return m; + })) + }); + + if (navigator.sendBeacon) { + return navigator.sendBeacon(url, body); + } + + return fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body + }); + }; + } + + return clientLogger; + } catch (_unused) { + return _logger; + } +} \ No newline at end of file diff --git a/dist/lib/parse-url.js b/dist/lib/parse-url.js new file mode 100644 index 0000000000..d8e502edad --- /dev/null +++ b/dist/lib/parse-url.js @@ -0,0 +1,25 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = parseUrl; + +function parseUrl(url) { + const defaultHost = 'http://localhost:3000'; + const defaultPath = '/api/auth'; + + if (!url) { + url = `${defaultHost}${defaultPath}`; + } + + const protocol = url.startsWith('http:') ? 'http' : 'https'; + url = url.replace(/^https?:\/\//, '').replace(/\/$/, ''); + const [_host, ..._path] = url.split('/'); + const baseUrl = _host ? `${protocol}://${_host}` : defaultHost; + const basePath = _path.length > 0 ? `/${_path.join('/')}` : defaultPath; + return { + baseUrl, + basePath + }; +} \ No newline at end of file diff --git a/dist/providers/42.js b/dist/providers/42.js new file mode 100644 index 0000000000..c7af653dec --- /dev/null +++ b/dist/providers/42.js @@ -0,0 +1,28 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = FortyTwo; + +function FortyTwo(options) { + return { + id: '42-school', + name: '42 School', + type: 'oauth', + version: '2.0', + params: { + grant_type: 'authorization_code' + }, + accessTokenUrl: 'https://api.intra.42.fr/oauth/token', + authorizationUrl: 'https://api.intra.42.fr/oauth/authorize?response_type=code', + profileUrl: 'https://api.intra.42.fr/v2/me', + profile: profile => ({ + id: profile.id, + email: profile.email, + image: profile.image_url, + name: profile.usual_full_name + }), + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/apple.js b/dist/providers/apple.js new file mode 100644 index 0000000000..6bd2582848 --- /dev/null +++ b/dist/providers/apple.js @@ -0,0 +1,40 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Apple; + +function Apple(options) { + return { + id: "apple", + name: "Apple", + type: "oauth", + version: "2.0", + scope: "name email", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://appleid.apple.com/auth/token", + authorizationUrl: "https://appleid.apple.com/auth/authorize?response_type=code&id_token&response_mode=form_post", + profileUrl: null, + idToken: true, + + profile(profile) { + return { + id: profile.sub, + name: profile.user != null ? profile.user.name.firstName + " " + profile.user.name.lastName : null, + email: profile.email + }; + }, + + clientId: null, + clientSecret: { + teamId: null, + privateKey: null, + keyId: null + }, + protection: "none", + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/atlassian.js b/dist/providers/atlassian.js new file mode 100644 index 0000000000..a87985150d --- /dev/null +++ b/dist/providers/atlassian.js @@ -0,0 +1,32 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Atlassian; + +function Atlassian(options) { + return { + id: "atlassian", + name: "Atlassian", + type: "oauth", + version: "2.0", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://auth.atlassian.com/oauth/token", + authorizationUrl: "https://auth.atlassian.com/authorize?audience=api.atlassian.com&response_type=code&prompt=consent", + profileUrl: "https://api.atlassian.com/me", + + profile(profile) { + return { + id: profile.account_id, + name: profile.name, + email: profile.email, + image: profile.picture + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/auth0.js b/dist/providers/auth0.js new file mode 100644 index 0000000000..189b4a7e9d --- /dev/null +++ b/dist/providers/auth0.js @@ -0,0 +1,33 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Auth0; + +function Auth0(options) { + return { + id: "auth0", + name: "Auth0", + type: "oauth", + version: "2.0", + params: { + grant_type: "authorization_code" + }, + scope: "openid email profile", + accessTokenUrl: `https://${options.domain}/oauth/token`, + authorizationUrl: `https://${options.domain}/authorize?response_type=code`, + profileUrl: `https://${options.domain}/userinfo`, + + profile(profile) { + return { + id: profile.sub, + name: profile.nickname, + email: profile.email, + image: profile.picture + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/azure-ad-b2c.js b/dist/providers/azure-ad-b2c.js new file mode 100644 index 0000000000..fecfb3f867 --- /dev/null +++ b/dist/providers/azure-ad-b2c.js @@ -0,0 +1,32 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = AzureADB2C; + +function AzureADB2C(options) { + const tenant = options.tenantId ? options.tenantId : "common"; + return { + id: "azure-ad-b2c", + name: "Azure Active Directory B2C", + type: "oauth", + version: "2.0", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`, + authorizationUrl: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize?response_type=code&response_mode=query`, + profileUrl: "https://graph.microsoft.com/v1.0/me/", + + profile(profile) { + return { + id: profile.id, + name: profile.displayName, + email: profile.userPrincipalName + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/basecamp.js b/dist/providers/basecamp.js new file mode 100644 index 0000000000..a029fd3122 --- /dev/null +++ b/dist/providers/basecamp.js @@ -0,0 +1,29 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Basecamp; + +function Basecamp(options) { + return { + id: "basecamp", + name: "Basecamp", + type: "oauth", + version: "2.0", + accessTokenUrl: "https://launchpad.37signals.com/authorization/token?type=web_server", + authorizationUrl: "https://launchpad.37signals.com/authorization/new?type=web_server", + profileUrl: "https://launchpad.37signals.com/authorization.json", + + profile(profile) { + return { + id: profile.identity.id, + name: `${profile.identity.first_name} ${profile.identity.last_name}`, + email: profile.identity.email_address, + image: null + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/battlenet.js b/dist/providers/battlenet.js new file mode 100644 index 0000000000..ef381cc91f --- /dev/null +++ b/dist/providers/battlenet.js @@ -0,0 +1,36 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = BattleNet; + +function BattleNet(options) { + const { + region + } = options; + return { + id: "battlenet", + name: "Battle.net", + type: "oauth", + version: "2.0", + scope: "openid", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: region === "CN" ? "https://www.battlenet.com.cn/oauth/token" : `https://${region}.battle.net/oauth/token`, + authorizationUrl: region === "CN" ? "https://www.battlenet.com.cn/oauth/authorize?response_type=code" : `https://${region}.battle.net/oauth/authorize?response_type=code`, + profileUrl: "https://us.battle.net/oauth/userinfo", + + profile(profile) { + return { + id: profile.id, + name: profile.battletag, + email: null, + image: null + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/box.js b/dist/providers/box.js new file mode 100644 index 0000000000..9469a85c05 --- /dev/null +++ b/dist/providers/box.js @@ -0,0 +1,33 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Box; + +function Box(options) { + return { + id: "box", + name: "Box", + type: "oauth", + version: "2.0", + scope: "", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://api.box.com/oauth2/token", + authorizationUrl: "https://account.box.com/api/oauth2/authorize?response_type=code", + profileUrl: "https://api.box.com/2.0/users/me", + + profile(profile) { + return { + id: profile.id, + name: profile.name, + email: profile.login, + image: profile.avatar_url + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/bungie.js b/dist/providers/bungie.js new file mode 100644 index 0000000000..a9d86a1a08 --- /dev/null +++ b/dist/providers/bungie.js @@ -0,0 +1,43 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Bungie; + +function Bungie(options) { + return { + id: "bungie", + name: "Bungie", + type: "oauth", + version: "2.0", + scope: "", + params: { + reauth: "true", + grant_type: "authorization_code" + }, + accessTokenUrl: "https://www.bungie.net/platform/app/oauth/token/", + requestTokenUrl: "https://www.bungie.net/platform/app/oauth/token/", + authorizationUrl: "https://www.bungie.net/en/OAuth/Authorize?response_type=code", + profileUrl: "https://www.bungie.net/platform/User/GetBungieAccount/{membershipId}/254/", + + profile(profile) { + const { + bungieNetUser: user + } = profile.Response; + return { + id: user.membershipId, + name: user.displayName, + image: `https://www.bungie.net${user.profilePicturePath.startsWith("/") ? "" : "/"}${user.profilePicturePath}`, + email: null + }; + }, + + headers: { + "X-API-Key": null + }, + clientId: null, + clientSecret: null, + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/cognito.js b/dist/providers/cognito.js new file mode 100644 index 0000000000..33559121bc --- /dev/null +++ b/dist/providers/cognito.js @@ -0,0 +1,36 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Cognito; + +function Cognito(options) { + const { + domain + } = options; + return { + id: "cognito", + name: "Cognito", + type: "oauth", + version: "2.0", + scope: "openid profile email", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: `https://${domain}/oauth2/token`, + authorizationUrl: `https://${domain}/oauth2/authorize?response_type=code`, + profileUrl: `https://${domain}/oauth2/userInfo`, + + profile(profile) { + return { + id: profile.sub, + name: profile.username, + email: profile.email, + image: null + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/credentials.js b/dist/providers/credentials.js new file mode 100644 index 0000000000..a8fe677a01 --- /dev/null +++ b/dist/providers/credentials.js @@ -0,0 +1,17 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Credentials; + +function Credentials(options) { + return { + id: "credentials", + name: "Credentials", + type: "credentials", + authorize: null, + credentials: null, + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/discord.js b/dist/providers/discord.js new file mode 100644 index 0000000000..54148e08c9 --- /dev/null +++ b/dist/providers/discord.js @@ -0,0 +1,41 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Discord; + +function Discord(options) { + return { + id: "discord", + name: "Discord", + type: "oauth", + version: "2.0", + scope: "identify email", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://discord.com/api/oauth2/token", + authorizationUrl: "https://discord.com/api/oauth2/authorize?response_type=code&prompt=none", + profileUrl: "https://discord.com/api/users/@me", + + profile(profile) { + if (profile.avatar === null) { + const defaultAvatarNumber = parseInt(profile.discriminator) % 5; + profile.image_url = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarNumber}.png`; + } else { + const format = profile.avatar.startsWith("a_") ? "gif" : "png"; + profile.image_url = `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.${format}`; + } + + return { + id: profile.id, + name: profile.username, + image: profile.image_url, + email: profile.email + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/dropbox.js b/dist/providers/dropbox.js new file mode 100644 index 0000000000..3bcd4f9c2b --- /dev/null +++ b/dist/providers/dropbox.js @@ -0,0 +1,33 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Dropbox; + +function Dropbox(options) { + return { + id: 'dropbox', + name: 'Dropbox', + type: 'oauth', + version: '2.0', + scope: 'account_info.read', + params: { + grant_type: 'authorization_code' + }, + accessTokenUrl: 'https://api.dropboxapi.com/oauth2/token', + authorizationUrl: 'https://www.dropbox.com/oauth2/authorize?token_access_type=offline&response_type=code', + profileUrl: 'https://api.dropboxapi.com/2/users/get_current_account', + profile: profile => { + return { + id: profile.account_id, + name: profile.name.display_name, + email: profile.email, + image: profile.profile_photo_url, + email_verified: profile.email_verified + }; + }, + protection: ["state", "pkce"], + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/email.js b/dist/providers/email.js new file mode 100644 index 0000000000..ba9638ae14 --- /dev/null +++ b/dist/providers/email.js @@ -0,0 +1,123 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Email; + +var _nodemailer = _interopRequireDefault(require("nodemailer")); + +var _logger = _interopRequireDefault(require("../lib/logger")); + +function Email(options) { + return { + id: "email", + type: "email", + name: "Email", + server: { + host: "localhost", + port: 25, + auth: { + user: "", + pass: "" + } + }, + from: "NextAuth ", + maxAge: 24 * 60 * 60, + sendVerificationRequest, + ...options + }; +} + +const sendVerificationRequest = ({ + identifier: email, + url, + baseUrl, + provider +}) => { + return new Promise((resolve, reject) => { + const { + server, + from + } = provider; + const site = baseUrl.replace(/^https?:\/\//, ""); + + _nodemailer.default.createTransport(server).sendMail({ + to: email, + from, + subject: `Sign in to ${site}`, + text: text({ + url, + site, + email + }), + html: html({ + url, + site, + email + }) + }, error => { + if (error) { + _logger.default.error("SEND_VERIFICATION_EMAIL_ERROR", email, error); + + return reject(new Error("SEND_VERIFICATION_EMAIL_ERROR", error)); + } + + return resolve(); + }); + }); +}; + +const html = ({ + url, + site, + email +}) => { + const escapedEmail = `${email.replace(/\./g, "​.")}`; + const escapedSite = `${site.replace(/\./g, "​.")}`; + const backgroundColor = "#f9f9f9"; + const textColor = "#444444"; + const mainBackgroundColor = "#ffffff"; + const buttonBackgroundColor = "#346df1"; + const buttonBorderColor = "#346df1"; + const buttonTextColor = "#ffffff"; + return ` + + + + + +
+ ${escapedSite} +
+ + + + + + + + + + +
+ Sign in as ${escapedEmail} +
+ + + + +
Sign in
+
+ If you did not request this email you can safely ignore it. +
+ +`; +}; + +const text = ({ + url, + site +}) => `Sign in to ${site}\n${url}\n\n`; \ No newline at end of file diff --git a/dist/providers/eveonline.js b/dist/providers/eveonline.js new file mode 100644 index 0000000000..ddeea85cbb --- /dev/null +++ b/dist/providers/eveonline.js @@ -0,0 +1,32 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = EVEOnline; + +function EVEOnline(options) { + return { + id: "eveonline", + name: "EVE Online", + type: "oauth", + version: "2.0", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://login.eveonline.com/oauth/token", + authorizationUrl: "https://login.eveonline.com/oauth/authorize?response_type=code", + profileUrl: "https://login.eveonline.com/oauth/verify", + + profile(profile) { + return { + id: profile.CharacterID, + name: profile.CharacterName, + image: `https://image.eveonline.com/Character/${profile.CharacterID}_128.jpg`, + email: null + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/facebook.js b/dist/providers/facebook.js new file mode 100644 index 0000000000..59540d28eb --- /dev/null +++ b/dist/providers/facebook.js @@ -0,0 +1,30 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Facebook; + +function Facebook(options) { + return { + id: "facebook", + name: "Facebook", + type: "oauth", + version: "2.0", + scope: "email", + accessTokenUrl: "https://graph.facebook.com/oauth/access_token", + authorizationUrl: "https://www.facebook.com/v7.0/dialog/oauth?response_type=code", + profileUrl: "https://graph.facebook.com/me?fields=email,name,picture", + + profile(profile) { + return { + id: profile.id, + name: profile.name, + email: profile.email, + image: profile.picture.data.url + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/faceit.js b/dist/providers/faceit.js new file mode 100644 index 0000000000..3fe78c0bbc --- /dev/null +++ b/dist/providers/faceit.js @@ -0,0 +1,41 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = FACEIT; + +function FACEIT(options) { + return { + id: "faceit", + name: "FACEIT", + type: "oauth", + version: "2.0", + params: { + grant_type: "authorization_code" + }, + headers: { + Authorization: `Basic ${Buffer.from(`${options.clientId}:${options.clientSecret}`).toString("base64")}` + }, + accessTokenUrl: "https://api.faceit.com/auth/v1/oauth/token", + authorizationUrl: "https://accounts.faceit.com/accounts?redirect_popup=true&response_type=code", + profileUrl: "https://api.faceit.com/auth/v1/resources/userinfo", + + profile(profile) { + const { + guid: id, + nickname: name, + email, + picture: image + } = profile; + return { + id, + name, + email, + image + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/foursquare.js b/dist/providers/foursquare.js new file mode 100644 index 0000000000..1e4caa43f7 --- /dev/null +++ b/dist/providers/foursquare.js @@ -0,0 +1,35 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Foursquare; + +function Foursquare(options) { + const { + apiVersion + } = options; + return { + id: "foursquare", + name: "Foursquare", + type: "oauth", + version: "2.0", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://foursquare.com/oauth2/access_token", + authorizationUrl: "https://foursquare.com/oauth2/authenticate?response_type=code", + profileUrl: `https://api.foursquare.com/v2/users/self?v=${apiVersion}`, + + profile(profile) { + return { + id: profile.id, + name: `${profile.firstName} ${profile.lastName}`, + image: `${profile.prefix}original${profile.suffix}`, + email: profile.contact.email + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/fusionauth.js b/dist/providers/fusionauth.js new file mode 100644 index 0000000000..ae32728b9c --- /dev/null +++ b/dist/providers/fusionauth.js @@ -0,0 +1,39 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = FusionAuth; + +function FusionAuth(options) { + let authorizationUrl = `https://${options.domain}/oauth2/authorize?response_type=code`; + + if (options.tenantId) { + authorizationUrl += `&tenantId=${options.tenantId}`; + } + + return { + id: "fusionauth", + name: "FusionAuth", + type: "oauth", + version: "2.0", + scope: "openid", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: `https://${options.domain}/oauth2/token`, + authorizationUrl, + profileUrl: `https://${options.domain}/oauth2/userinfo`, + + profile(profile) { + return { + id: profile.sub, + name: profile.name, + email: profile.email, + image: profile.picture + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/github.js b/dist/providers/github.js new file mode 100644 index 0000000000..dccf709f20 --- /dev/null +++ b/dist/providers/github.js @@ -0,0 +1,30 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = GitHub; + +function GitHub(options) { + return { + id: "github", + name: "GitHub", + type: "oauth", + version: "2.0", + scope: "user", + accessTokenUrl: "https://github.com/login/oauth/access_token", + authorizationUrl: "https://github.com/login/oauth/authorize", + profileUrl: "https://api.github.com/user", + + profile(profile) { + return { + id: profile.id, + name: profile.name || profile.login, + email: profile.email, + image: profile.avatar_url + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/gitlab.js b/dist/providers/gitlab.js new file mode 100644 index 0000000000..01bb77bd19 --- /dev/null +++ b/dist/providers/gitlab.js @@ -0,0 +1,33 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = GitLab; + +function GitLab(options) { + return { + id: "gitlab", + name: "GitLab", + type: "oauth", + version: "2.0", + scope: "read_user", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://gitlab.com/oauth/token", + authorizationUrl: "https://gitlab.com/oauth/authorize?response_type=code", + profileUrl: "https://gitlab.com/api/v4/user", + + profile(profile) { + return { + id: profile.id, + name: profile.username, + email: profile.email, + image: profile.avatar_url + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/google.js b/dist/providers/google.js new file mode 100644 index 0000000000..13a0c6de33 --- /dev/null +++ b/dist/providers/google.js @@ -0,0 +1,34 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Google; + +function Google(options) { + return { + id: "google", + name: "Google", + type: "oauth", + version: "2.0", + scope: "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://accounts.google.com/o/oauth2/token", + requestTokenUrl: "https://accounts.google.com/o/oauth2/auth", + authorizationUrl: "https://accounts.google.com/o/oauth2/auth?response_type=code", + profileUrl: "https://www.googleapis.com/oauth2/v1/userinfo?alt=json", + + profile(profile) { + return { + id: profile.id, + name: profile.name, + email: profile.email, + image: profile.picture + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/identity-server4.js b/dist/providers/identity-server4.js new file mode 100644 index 0000000000..34e6e25392 --- /dev/null +++ b/dist/providers/identity-server4.js @@ -0,0 +1,30 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = IdentityServer4; + +function IdentityServer4(options) { + return { + id: "identity-server4", + name: "IdentityServer4", + type: "oauth", + version: "2.0", + scope: "openid profile email", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: `https://${options.domain}/connect/token`, + authorizationUrl: `https://${options.domain}/connect/authorize?response_type=code`, + profileUrl: `https://${options.domain}/connect/userinfo`, + + profile(profile) { + return { ...profile, + id: profile.sub + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/index.js b/dist/providers/index.js new file mode 100644 index 0000000000..b78b6bdf0e --- /dev/null +++ b/dist/providers/index.js @@ -0,0 +1,147 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _ = _interopRequireDefault(require("./42.js")); + +var _apple = _interopRequireDefault(require("./apple.js")); + +var _atlassian = _interopRequireDefault(require("./atlassian.js")); + +var _auth = _interopRequireDefault(require("./auth0.js")); + +var _azureAdB2c = _interopRequireDefault(require("./azure-ad-b2c.js")); + +var _basecamp = _interopRequireDefault(require("./basecamp.js")); + +var _battlenet = _interopRequireDefault(require("./battlenet.js")); + +var _box = _interopRequireDefault(require("./box.js")); + +var _bungie = _interopRequireDefault(require("./bungie.js")); + +var _cognito = _interopRequireDefault(require("./cognito.js")); + +var _credentials = _interopRequireDefault(require("./credentials.js")); + +var _discord = _interopRequireDefault(require("./discord.js")); + +var _dropbox = _interopRequireDefault(require("./dropbox.js")); + +var _email = _interopRequireDefault(require("./email.js")); + +var _eveonline = _interopRequireDefault(require("./eveonline.js")); + +var _facebook = _interopRequireDefault(require("./facebook.js")); + +var _faceit = _interopRequireDefault(require("./faceit.js")); + +var _foursquare = _interopRequireDefault(require("./foursquare.js")); + +var _fusionauth = _interopRequireDefault(require("./fusionauth.js")); + +var _github = _interopRequireDefault(require("./github.js")); + +var _gitlab = _interopRequireDefault(require("./gitlab.js")); + +var _google = _interopRequireDefault(require("./google.js")); + +var _identityServer = _interopRequireDefault(require("./identity-server4.js")); + +var _instagram = _interopRequireDefault(require("./instagram.js")); + +var _kakao = _interopRequireDefault(require("./kakao.js")); + +var _line = _interopRequireDefault(require("./line.js")); + +var _linkedin = _interopRequireDefault(require("./linkedin.js")); + +var _mailchimp = _interopRequireDefault(require("./mailchimp.js")); + +var _mailru = _interopRequireDefault(require("./mailru.js")); + +var _medium = _interopRequireDefault(require("./medium.js")); + +var _netlify = _interopRequireDefault(require("./netlify.js")); + +var _okta = _interopRequireDefault(require("./okta.js")); + +var _osso = _interopRequireDefault(require("./osso.js")); + +var _reddit = _interopRequireDefault(require("./reddit.js")); + +var _salesforce = _interopRequireDefault(require("./salesforce.js")); + +var _slack = _interopRequireDefault(require("./slack.js")); + +var _spotify = _interopRequireDefault(require("./spotify.js")); + +var _strava = _interopRequireDefault(require("./strava.js")); + +var _twitch = _interopRequireDefault(require("./twitch.js")); + +var _twitter = _interopRequireDefault(require("./twitter.js")); + +var _vk = _interopRequireDefault(require("./vk.js")); + +var _wordpress = _interopRequireDefault(require("./wordpress.js")); + +var _workos = _interopRequireDefault(require("./workos.js")); + +var _yandex = _interopRequireDefault(require("./yandex.js")); + +var _zoho = _interopRequireDefault(require("./zoho.js")); + +var _default = { + FortyTwo: _.default, + Apple: _apple.default, + Atlassian: _atlassian.default, + Auth0: _auth.default, + AzureADB2C: _azureAdB2c.default, + Basecamp: _basecamp.default, + BattleNet: _battlenet.default, + Box: _box.default, + Bungie: _bungie.default, + Cognito: _cognito.default, + Credentials: _credentials.default, + Discord: _discord.default, + Dropbox: _dropbox.default, + Email: _email.default, + EVEOnline: _eveonline.default, + Facebook: _facebook.default, + FACEIT: _faceit.default, + Foursquare: _foursquare.default, + FusionAuth: _fusionauth.default, + GitHub: _github.default, + GitLab: _gitlab.default, + Google: _google.default, + IdentityServer4: _identityServer.default, + Instagram: _instagram.default, + Kakao: _kakao.default, + LINE: _line.default, + LinkedIn: _linkedin.default, + Mailchimp: _mailchimp.default, + MailRu: _mailru.default, + Medium: _medium.default, + Netlify: _netlify.default, + Okta: _okta.default, + Osso: _osso.default, + Reddit: _reddit.default, + Salesforce: _salesforce.default, + Slack: _slack.default, + Spotify: _spotify.default, + Strava: _strava.default, + Twitch: _twitch.default, + Twitter: _twitter.default, + VK: _vk.default, + WordPress: _wordpress.default, + WorkOS: _workos.default, + Yandex: _yandex.default, + Zoho: _zoho.default +}; +exports.default = _default; \ No newline at end of file diff --git a/dist/providers/instagram.js b/dist/providers/instagram.js new file mode 100644 index 0000000000..d4cf79028c --- /dev/null +++ b/dist/providers/instagram.js @@ -0,0 +1,33 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Instagram; + +function Instagram(options) { + return { + id: "instagram", + name: "Instagram", + type: "oauth", + version: "2.0", + scope: "user_profile", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://api.instagram.com/oauth/access_token", + authorizationUrl: "https://api.instagram.com/oauth/authorize?response_type=code", + profileUrl: "https://graph.instagram.com/me?fields=id,username,account_type,name", + + async profile(profile) { + return { + id: profile.id, + name: profile.username, + email: null, + image: null + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/kakao.js b/dist/providers/kakao.js new file mode 100644 index 0000000000..4f9f83b537 --- /dev/null +++ b/dist/providers/kakao.js @@ -0,0 +1,34 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Kakao; + +function Kakao(options) { + return { + id: "kakao", + name: "Kakao", + type: "oauth", + version: "2.0", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://kauth.kakao.com/oauth/token", + authorizationUrl: "https://kauth.kakao.com/oauth/authorize?response_type=code", + profileUrl: "https://kapi.kakao.com/v2/user/me", + + profile(profile) { + var _profile$kakao_accoun, _profile$kakao_accoun2, _profile$kakao_accoun3; + + return { + id: profile.id, + name: (_profile$kakao_accoun = profile.kakao_account) === null || _profile$kakao_accoun === void 0 ? void 0 : _profile$kakao_accoun.profile.nickname, + email: (_profile$kakao_accoun2 = profile.kakao_account) === null || _profile$kakao_accoun2 === void 0 ? void 0 : _profile$kakao_accoun2.email, + image: (_profile$kakao_accoun3 = profile.kakao_account) === null || _profile$kakao_accoun3 === void 0 ? void 0 : _profile$kakao_accoun3.profile.profile_image_url + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/line.js b/dist/providers/line.js new file mode 100644 index 0000000000..d9be9ad3e4 --- /dev/null +++ b/dist/providers/line.js @@ -0,0 +1,33 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = LINE; + +function LINE(options) { + return { + id: "line", + name: "LINE", + type: "oauth", + version: "2.0", + scope: "profile openid", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://api.line.me/oauth2/v2.1/token", + authorizationUrl: "https://access.line.me/oauth2/v2.1/authorize?response_type=code", + profileUrl: "https://api.line.me/v2/profile", + + profile(profile) { + return { + id: profile.userId, + name: profile.displayName, + email: null, + image: profile.pictureUrl + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/linkedin.js b/dist/providers/linkedin.js new file mode 100644 index 0000000000..a3eb22209b --- /dev/null +++ b/dist/providers/linkedin.js @@ -0,0 +1,35 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = LinkedIn; + +function LinkedIn(options) { + return { + id: "linkedin", + name: "LinkedIn", + type: "oauth", + version: "2.0", + scope: "r_liteprofile", + params: { + grant_type: "authorization_code", + client_id: options.clientId, + client_secret: options.clientSecret + }, + accessTokenUrl: "https://www.linkedin.com/oauth/v2/accessToken", + authorizationUrl: "https://www.linkedin.com/oauth/v2/authorization?response_type=code", + profileUrl: "https://api.linkedin.com/v2/me?projection=(id,localizedFirstName,localizedLastName)", + + profile(profile) { + return { + id: profile.id, + name: profile.localizedFirstName + " " + profile.localizedLastName, + email: null, + image: null + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/mailchimp.js b/dist/providers/mailchimp.js new file mode 100644 index 0000000000..de9f22a8f8 --- /dev/null +++ b/dist/providers/mailchimp.js @@ -0,0 +1,31 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Mailchimp; + +function Mailchimp(options) { + return { + id: 'mailchimp', + name: 'Mailchimp', + type: 'oauth', + version: '2.0', + scope: '', + params: { + grant_type: 'authorization_code' + }, + accessTokenUrl: 'https://login.mailchimp.com/oauth2/token', + authorizationUrl: 'https://login.mailchimp.com/oauth2/authorize?response_type=code', + profileUrl: 'https://login.mailchimp.com/oauth2/metadata', + profile: profile => { + return { + id: profile.login.login_id, + name: profile.accountname, + email: profile.login.email, + image: null + }; + }, + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/mailru.js b/dist/providers/mailru.js new file mode 100644 index 0000000000..db1a040590 --- /dev/null +++ b/dist/providers/mailru.js @@ -0,0 +1,34 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = MailRu; + +function MailRu(options) { + return { + id: "mailru", + name: "Mail.ru", + type: "oauth", + version: "2.0", + scope: "userinfo", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://oauth.mail.ru/token", + requestTokenUrl: "https://oauth.mail.ru/token", + authorizationUrl: "https://oauth.mail.ru/login?response_type=code", + profileUrl: "https://oauth.mail.ru/userinfo", + + profile(profile) { + return { + id: profile.id, + name: profile.name, + email: profile.email, + image: profile.image + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/medium.js b/dist/providers/medium.js new file mode 100644 index 0000000000..1116bdc478 --- /dev/null +++ b/dist/providers/medium.js @@ -0,0 +1,33 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Medium; + +function Medium(options) { + return { + id: "medium", + name: "Medium", + type: "oauth", + version: "2.0", + scope: "basicProfile", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://api.medium.com/v1/tokens", + authorizationUrl: "https://medium.com/m/oauth/authorize?response_type=code", + profileUrl: "https://api.medium.com/v1/me", + + profile(profile) { + return { + id: profile.data.id, + name: profile.data.name, + email: null, + image: profile.data.imageUrl + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/netlify.js b/dist/providers/netlify.js new file mode 100644 index 0000000000..d8211bc76b --- /dev/null +++ b/dist/providers/netlify.js @@ -0,0 +1,32 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Netlify; + +function Netlify(options) { + return { + id: "netlify", + name: "Netlify", + type: "oauth", + version: "2.0", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://api.netlify.com/oauth/token", + authorizationUrl: "https://app.netlify.com/authorize?response_type=code", + profileUrl: "https://api.netlify.com/api/v1/user", + + profile(profile) { + return { + id: profile.id, + name: profile.full_name, + email: profile.email, + image: profile.avatar_url + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/okta.js b/dist/providers/okta.js new file mode 100644 index 0000000000..b17a3f02c3 --- /dev/null +++ b/dist/providers/okta.js @@ -0,0 +1,32 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Okta; + +function Okta(options) { + return { + id: "okta", + name: "Okta", + type: "oauth", + version: "2.0", + scope: "openid profile email", + params: { + grant_type: "authorization_code", + client_id: options.clientId, + client_secret: options.clientSecret + }, + accessTokenUrl: `https://${options.domain}/v1/token`, + authorizationUrl: `https://${options.domain}/v1/authorize/?response_type=code`, + profileUrl: `https://${options.domain}/v1/userinfo/`, + + profile(profile) { + return { ...profile, + id: profile.sub + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/osso.js b/dist/providers/osso.js new file mode 100644 index 0000000000..df3e2709b5 --- /dev/null +++ b/dist/providers/osso.js @@ -0,0 +1,31 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Osso; + +function Osso(options) { + return { + id: "osso", + name: "SAML SSO", + type: "oauth", + version: "2.0", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: `https://${options.domain}/oauth/token`, + authorizationUrl: `https://${options.domain}/oauth/authorize?response_type=code`, + profileUrl: `https://${options.domain}/oauth/me`, + + profile(profile) { + return { + id: profile.id, + name: profile.name || profile.email, + email: profile.email + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/reddit.js b/dist/providers/reddit.js new file mode 100644 index 0000000000..f23b5981f3 --- /dev/null +++ b/dist/providers/reddit.js @@ -0,0 +1,33 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Reddit; + +function Reddit(options) { + return { + id: "reddit", + name: "Reddit", + type: "oauth", + version: "2.0", + scope: "identity", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: " https://www.reddit.com/api/v1/access_token", + authorizationUrl: "https://www.reddit.com/api/v1/authorize?response_type=code", + profileUrl: "https://oauth.reddit.com/api/v1/me", + + profile(profile) { + return { + id: profile.id, + name: profile.name, + image: null, + email: null + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/salesforce.js b/dist/providers/salesforce.js new file mode 100644 index 0000000000..8842ce5fa0 --- /dev/null +++ b/dist/providers/salesforce.js @@ -0,0 +1,32 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Salesforce; + +function Salesforce(options) { + return { + id: "salesforce", + name: "Salesforce", + type: "oauth", + version: "2.0", + params: { + display: "page", + grant_type: "authorization_code" + }, + accessTokenUrl: "https://login.salesforce.com/services/oauth2/token", + authorizationUrl: "https://login.salesforce.com/services/oauth2/authorize?response_type=code", + profileUrl: "https://login.salesforce.com/services/oauth2/userinfo", + protection: "none", + + profile(profile) { + return { ...profile, + id: profile.user_id, + image: profile.picture + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/slack.js b/dist/providers/slack.js new file mode 100644 index 0000000000..968d07d574 --- /dev/null +++ b/dist/providers/slack.js @@ -0,0 +1,39 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Slack; + +function Slack(options) { + return { + id: "slack", + name: "Slack", + type: "oauth", + version: "2.0", + scope: [], + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://slack.com/api/oauth.v2.access", + authorizationUrl: "https://slack.com/oauth/v2/authorize", + authorizationParams: { + user_scope: "identity.basic,identity.email,identity.avatar" + }, + profileUrl: "https://slack.com/api/users.identity", + + profile(profile) { + const { + user + } = profile; + return { + id: user.id, + name: user.name, + image: user.image_512, + email: user.email + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/spotify.js b/dist/providers/spotify.js new file mode 100644 index 0000000000..6f3e330339 --- /dev/null +++ b/dist/providers/spotify.js @@ -0,0 +1,35 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Spotify; + +function Spotify(options) { + return { + id: "spotify", + name: "Spotify", + type: "oauth", + version: "2.0", + scope: "user-read-email", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://accounts.spotify.com/api/token", + authorizationUrl: "https://accounts.spotify.com/authorize?response_type=code", + profileUrl: "https://api.spotify.com/v1/me", + + profile(profile) { + var _profile$images, _profile$images$; + + return { + id: profile.id, + name: profile.display_name, + email: profile.email, + image: (_profile$images = profile.images) === null || _profile$images === void 0 ? void 0 : (_profile$images$ = _profile$images[0]) === null || _profile$images$ === void 0 ? void 0 : _profile$images$.url + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/strava.js b/dist/providers/strava.js new file mode 100644 index 0000000000..eca27c865c --- /dev/null +++ b/dist/providers/strava.js @@ -0,0 +1,32 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Strava; + +function Strava(options) { + return { + id: "strava", + name: "Strava", + type: "oauth", + version: "2.0", + scope: "read", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://www.strava.com/api/v3/oauth/token", + authorizationUrl: "https://www.strava.com/api/v3/oauth/authorize?response_type=code", + profileUrl: "https://www.strava.com/api/v3/athlete", + + profile(profile) { + return { + id: profile.id, + name: profile.firstname, + image: profile.profile + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/twitch.js b/dist/providers/twitch.js new file mode 100644 index 0000000000..ba4df29329 --- /dev/null +++ b/dist/providers/twitch.js @@ -0,0 +1,34 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Twitch; + +function Twitch(options) { + return { + id: "twitch", + name: "Twitch", + type: "oauth", + version: "2.0", + scope: "user:read:email", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://id.twitch.tv/oauth2/token", + authorizationUrl: "https://id.twitch.tv/oauth2/authorize?response_type=code", + profileUrl: "https://api.twitch.tv/helix/users", + + profile(profile) { + const data = profile.data[0]; + return { + id: data.id, + name: data.display_name, + image: data.profile_image_url, + email: data.email + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/twitter.js b/dist/providers/twitter.js new file mode 100644 index 0000000000..a8f126048c --- /dev/null +++ b/dist/providers/twitter.js @@ -0,0 +1,31 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Twitter; + +function Twitter(options) { + return { + id: "twitter", + name: "Twitter", + type: "oauth", + version: "1.0A", + scope: "", + accessTokenUrl: "https://api.twitter.com/oauth/access_token", + requestTokenUrl: "https://api.twitter.com/oauth/request_token", + authorizationUrl: "https://api.twitter.com/oauth/authenticate", + profileUrl: "https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true", + + profile(profile) { + return { + id: profile.id_str, + name: profile.name, + email: profile.email, + image: profile.profile_image_url_https.replace(/_normal\.(jpg|png|gif)$/, ".$1") + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/vk.js b/dist/providers/vk.js new file mode 100644 index 0000000000..1bda12b330 --- /dev/null +++ b/dist/providers/vk.js @@ -0,0 +1,36 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = VK; + +function VK(options) { + const apiVersion = "5.126"; + return { + id: "vk", + name: "VK", + type: "oauth", + version: "2.0", + scope: "email", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: `https://oauth.vk.com/access_token?v=${apiVersion}`, + requestTokenUrl: `https://oauth.vk.com/access_token?v=${apiVersion}`, + authorizationUrl: `https://oauth.vk.com/authorize?response_type=code&v=${apiVersion}`, + profileUrl: `https://api.vk.com/method/users.get?fields=photo_100&v=${apiVersion}`, + profile: result => { + var _result$response$, _result$response; + + const profile = (_result$response$ = (_result$response = result.response) === null || _result$response === void 0 ? void 0 : _result$response[0]) !== null && _result$response$ !== void 0 ? _result$response$ : {}; + return { + id: profile.id, + name: [profile.first_name, profile.last_name].filter(Boolean).join(" "), + email: profile.email, + image: profile.photo_100 + }; + }, + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/wordpress.js b/dist/providers/wordpress.js new file mode 100644 index 0000000000..204ae56035 --- /dev/null +++ b/dist/providers/wordpress.js @@ -0,0 +1,33 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = WordPress; + +function WordPress(options) { + return { + id: "wordpress", + name: "WordPress.com", + type: "oauth", + version: "2.0", + scope: "auth", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://public-api.wordpress.com/oauth2/token", + authorizationUrl: "https://public-api.wordpress.com/oauth2/authorize?response_type=code", + profileUrl: "https://public-api.wordpress.com/rest/v1/me", + + profile(profile) { + return { + id: profile.ID, + name: profile.display_name, + email: profile.email, + image: profile.avatar_URL + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/workos.js b/dist/providers/workos.js new file mode 100644 index 0000000000..ab1f543225 --- /dev/null +++ b/dist/providers/workos.js @@ -0,0 +1,31 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = WorkOS; + +function WorkOS(options) { + const domain = options.domain || 'api.workos.com'; + return { + id: 'workos', + name: 'WorkOS', + type: 'oauth', + version: '2.0', + scope: '', + params: { + grant_type: 'authorization_code', + client_id: options.clientId, + client_secret: options.clientSecret + }, + accessTokenUrl: `https://${domain}/sso/token`, + authorizationUrl: `https://${domain}/sso/authorize?response_type=code`, + profileUrl: `https://${domain}/sso/profile`, + profile: profile => { + return { ...profile, + name: `${profile.first_name} ${profile.last_name}` + }; + }, + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/yandex.js b/dist/providers/yandex.js new file mode 100644 index 0000000000..35de0d5c54 --- /dev/null +++ b/dist/providers/yandex.js @@ -0,0 +1,34 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Yandex; + +function Yandex(options) { + return { + id: "yandex", + name: "Yandex", + type: "oauth", + version: "2.0", + scope: "login:email login:info", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://oauth.yandex.ru/token", + requestTokenUrl: "https://oauth.yandex.ru/token", + authorizationUrl: "https://oauth.yandex.ru/authorize?response_type=code", + profileUrl: "https://login.yandex.ru/info?format=json", + + profile(profile) { + return { + id: profile.id, + name: profile.real_name, + email: profile.default_email, + image: null + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/providers/zoho.js b/dist/providers/zoho.js new file mode 100644 index 0000000000..ae7d66155d --- /dev/null +++ b/dist/providers/zoho.js @@ -0,0 +1,33 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = Zoho; + +function Zoho(options) { + return { + id: "zoho", + name: "Zoho", + type: "oauth", + version: "2.0", + scope: "AaaServer.profile.Read", + params: { + grant_type: "authorization_code" + }, + accessTokenUrl: "https://accounts.zoho.com/oauth/v2/token", + authorizationUrl: "https://accounts.zoho.com/oauth/v2/auth?response_type=code", + profileUrl: "https://accounts.zoho.com/oauth/user/info", + + profile(profile) { + return { + id: profile.ZUID, + name: `${profile.First_Name} ${profile.Last_Name}`, + email: profile.Email, + image: null + }; + }, + + ...options + }; +} \ No newline at end of file diff --git a/dist/server/index.js b/dist/server/index.js new file mode 100644 index 0000000000..172c4d5e67 --- /dev/null +++ b/dist/server/index.js @@ -0,0 +1,274 @@ +"use strict"; + +var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = NextAuth; + +var _adapters = _interopRequireDefault(require("../adapters")); + +var _jwt = _interopRequireDefault(require("../lib/jwt")); + +var _parseUrl = _interopRequireDefault(require("../lib/parse-url")); + +var _logger = _interopRequireWildcard(require("../lib/logger")); + +var cookie = _interopRequireWildcard(require("./lib/cookie")); + +var defaultEvents = _interopRequireWildcard(require("./lib/default-events")); + +var defaultCallbacks = _interopRequireWildcard(require("./lib/default-callbacks")); + +var _providers = _interopRequireDefault(require("./lib/providers")); + +var routes = _interopRequireWildcard(require("./routes")); + +var _pages = _interopRequireDefault(require("./pages")); + +var _createSecret = _interopRequireDefault(require("./lib/create-secret")); + +var _callbackUrlHandler = _interopRequireDefault(require("./lib/callback-url-handler")); + +var _extendRes = _interopRequireDefault(require("./lib/extend-res")); + +var _csrfTokenHandler = _interopRequireDefault(require("./lib/csrf-token-handler")); + +var pkce = _interopRequireWildcard(require("./lib/oauth/pkce-handler")); + +var state = _interopRequireWildcard(require("./lib/oauth/state-handler")); + +if (!process.env.NEXTAUTH_URL) { + _logger.default.warn("NEXTAUTH_URL", "NEXTAUTH_URL environment variable not set"); +} + +async function NextAuthHandler(req, res, userOptions) { + if (userOptions.logger) { + (0, _logger.setLogger)(userOptions.logger); + } + + if (userOptions.debug) { + process.env._NEXTAUTH_DEBUG = true; + } + + return new Promise(async resolve => { + var _provider$version, _userOptions$adapter; + + (0, _extendRes.default)(req, res, resolve); + + if (!req.query.nextauth) { + const error = "Cannot find [...nextauth].js in pages/api/auth. Make sure the filename is written correctly."; + + _logger.default.error("MISSING_NEXTAUTH_API_ROUTE_ERROR", error); + + return res.status(500).end(`Error: ${error}`); + } + + const { + nextauth, + action = nextauth[0], + providerId = nextauth[1], + error = nextauth[1] + } = req.query; + const { + basePath, + baseUrl + } = (0, _parseUrl.default)(process.env.NEXTAUTH_URL || process.env.VERCEL_URL); + const cookies = { ...cookie.defaultCookies(userOptions.useSecureCookies || baseUrl.startsWith("https://")), + ...userOptions.cookies + }; + const secret = (0, _createSecret.default)({ + userOptions, + basePath, + baseUrl + }); + const providers = (0, _providers.default)({ + providers: userOptions.providers, + baseUrl, + basePath + }); + const provider = providers.find(({ + id + }) => id === providerId); + + if ((provider === null || provider === void 0 ? void 0 : provider.type) === "oauth" && (_provider$version = provider.version) !== null && _provider$version !== void 0 && _provider$version.startsWith("2")) { + if (provider.protection) { + provider.protection = Array.isArray(provider.protection) ? provider.protection : [provider.protection]; + } else if (provider.state !== undefined) { + provider.protection = [provider.state ? "state" : "none"]; + } else { + provider.protection = ["state"]; + } + } + + const maxAge = 30 * 24 * 60 * 60; + const adapter = (_userOptions$adapter = userOptions.adapter) !== null && _userOptions$adapter !== void 0 ? _userOptions$adapter : userOptions.database && _adapters.default.Default(userOptions.database); + req.options = { + debug: false, + pages: {}, + theme: "auto", + ...userOptions, + adapter, + baseUrl, + basePath, + action, + provider, + cookies, + secret, + providers, + session: { + jwt: !adapter, + maxAge, + updateAge: 24 * 60 * 60, + ...userOptions.session + }, + jwt: { + secret, + maxAge, + encode: _jwt.default.encode, + decode: _jwt.default.decode, + ...userOptions.jwt + }, + events: { ...defaultEvents, + ...userOptions.events + }, + callbacks: { ...defaultCallbacks, + ...userOptions.callbacks + }, + pkce: {}, + logger: _logger.default + }; + (0, _csrfTokenHandler.default)(req, res); + await (0, _callbackUrlHandler.default)(req, res); + const render = (0, _pages.default)(req, res); + const { + pages + } = req.options; + + if (req.method === "GET") { + switch (action) { + case "providers": + return routes.providers(req, res); + + case "session": + return routes.session(req, res); + + case "csrf": + return res.json({ + csrfToken: req.options.csrfToken + }); + + case "signin": + if (pages.signIn) { + let signinUrl = `${pages.signIn}${pages.signIn.includes("?") ? "&" : "?"}callbackUrl=${req.options.callbackUrl}`; + + if (error) { + signinUrl = `${signinUrl}&error=${error}`; + } + + return res.redirect(signinUrl); + } + + return render.signin(); + + case "signout": + if (pages.signOut) return res.redirect(pages.signOut); + return render.signout(); + + case "callback": + if (provider) { + if (await pkce.handleCallback(req, res)) return; + if (await state.handleCallback(req, res)) return; + return routes.callback(req, res); + } + + break; + + case "verify-request": + if (pages.verifyRequest) { + return res.redirect(pages.verifyRequest); + } + + return render.verifyRequest(); + + case "error": + if (pages.error) { + return res.redirect(`${pages.error}${pages.error.includes("?") ? "&" : "?"}error=${error}`); + } + + if (["Signin", "OAuthSignin", "OAuthCallback", "OAuthCreateAccount", "EmailCreateAccount", "Callback", "OAuthAccountNotLinked", "EmailSignin", "CredentialsSignin"].includes(error)) { + return res.redirect(`${baseUrl}${basePath}/signin?error=${error}`); + } + + return render.error({ + error + }); + + default: + } + } else if (req.method === "POST") { + switch (action) { + case "signin": + if (req.options.csrfTokenVerified && provider) { + if (await pkce.handleSignin(req, res)) return; + if (await state.handleSignin(req, res)) return; + return routes.signin(req, res); + } + + return res.redirect(`${baseUrl}${basePath}/signin?csrf=true`); + + case "signout": + if (req.options.csrfTokenVerified) { + return routes.signout(req, res); + } + + return res.redirect(`${baseUrl}${basePath}/signout?csrf=true`); + + case "callback": + if (provider) { + if (provider.type === "credentials" && !req.options.csrfTokenVerified) { + return res.redirect(`${baseUrl}${basePath}/signin?csrf=true`); + } + + if (await pkce.handleCallback(req, res)) return; + if (await state.handleCallback(req, res)) return; + return routes.callback(req, res); + } + + break; + + case "_log": + if (userOptions.logger) { + try { + const { + code = "CLIENT_ERROR", + level = "error", + message = "[]" + } = req.body; + + _logger.default[level](code, ...JSON.parse(message)); + } catch (error) { + _logger.default.error("LOGGER_ERROR", error); + } + } + + return res.end(); + + default: + } + } + + return res.status(400).end(`Error: HTTP ${req.method} is not supported for ${req.url}`); + }); +} + +function NextAuth(...args) { + if (args.length === 1) { + return (req, res) => NextAuthHandler(req, res, args[0]); + } + + return NextAuthHandler(...args); +} \ No newline at end of file diff --git a/dist/server/lib/callback-handler.js b/dist/server/lib/callback-handler.js new file mode 100644 index 0000000000..bc3aee67fd --- /dev/null +++ b/dist/server/lib/callback-handler.js @@ -0,0 +1,168 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = callbackHandler; + +var _errors = require("../../lib/errors"); + +var _dispatchEvent = _interopRequireDefault(require("../lib/dispatch-event")); + +var _errorHandler = _interopRequireDefault(require("../../adapters/error-handler")); + +async function callbackHandler(sessionToken, profile, providerAccount, options) { + if (!profile) throw new Error("Missing profile"); + if (!(providerAccount !== null && providerAccount !== void 0 && providerAccount.id) || !providerAccount.type) throw new Error("Missing or invalid provider account"); + if (!["email", "oauth"].includes(providerAccount.type)) throw new Error("Provider not supported"); + const { + adapter, + jwt, + events, + session: { + jwt: useJwtSession + } + } = options; + + if (!adapter) { + return { + user: profile, + account: providerAccount, + session: {} + }; + } + + const { + createUser, + updateUser, + getUser, + getUserByProviderAccountId, + getUserByEmail, + linkAccount, + createSession, + getSession, + deleteSession + } = (0, _errorHandler.default)(await adapter.getAdapter(options), options.logger); + let session = null; + let user = null; + let isSignedIn = null; + let isNewUser = false; + + if (sessionToken) { + var _session2; + + if (useJwtSession) { + try { + var _session; + + session = await jwt.decode({ ...jwt, + token: sessionToken + }); + + if ((_session = session) !== null && _session !== void 0 && _session.sub) { + user = await getUser(session.sub); + isSignedIn = !!user; + } + } catch (_unused) {} + } + + session = await getSession(sessionToken); + + if ((_session2 = session) !== null && _session2 !== void 0 && _session2.userId) { + user = await getUser(session.userId); + isSignedIn = !!user; + } + } + + if (providerAccount.type === "email") { + const userByEmail = profile.email ? await getUserByEmail(profile.email) : null; + + if (userByEmail) { + if (isSignedIn) { + if (user.id !== userByEmail.id && !useJwtSession) { + await deleteSession(sessionToken); + } + } + + const currentDate = new Date(); + user = await updateUser({ ...userByEmail, + emailVerified: currentDate + }); + await (0, _dispatchEvent.default)(events.updateUser, user); + } else { + const currentDate = new Date(); + user = await createUser({ ...profile, + emailVerified: currentDate + }); + await (0, _dispatchEvent.default)(events.createUser, user); + isNewUser = true; + } + + session = useJwtSession ? {} : await createSession(user); + return { + session, + user, + isNewUser + }; + } else if (providerAccount.type === "oauth") { + const userByProviderAccountId = await getUserByProviderAccountId(providerAccount.provider, providerAccount.id); + + if (userByProviderAccountId) { + if (isSignedIn) { + if (`${userByProviderAccountId.id}` === `${user.id}`) { + return { + session, + user, + isNewUser + }; + } + + throw new _errors.AccountNotLinkedError(); + } + + session = useJwtSession ? {} : await createSession(userByProviderAccountId); + return { + session, + user: userByProviderAccountId, + isNewUser + }; + } else { + if (isSignedIn) { + await linkAccount(user.id, providerAccount.provider, providerAccount.type, providerAccount.id, providerAccount.refreshToken, providerAccount.accessToken, providerAccount.accessTokenExpires); + await (0, _dispatchEvent.default)(events.linkAccount, { + user, + providerAccount: providerAccount + }); + return { + session, + user, + isNewUser + }; + } + + const userByEmail = profile.email ? await getUserByEmail(profile.email) : null; + + if (userByEmail) { + user = userByEmail; + } else { + user = await createUser(profile); + await (0, _dispatchEvent.default)(events.createUser, user); + } + + await linkAccount(user.id, providerAccount.provider, providerAccount.type, providerAccount.id, providerAccount.refreshToken, providerAccount.accessToken, providerAccount.accessTokenExpires); + await (0, _dispatchEvent.default)(events.linkAccount, { + user, + providerAccount: providerAccount + }); + session = useJwtSession ? {} : await createSession(user); + isNewUser = true; + return { + session, + user, + isNewUser + }; + } + } +} \ No newline at end of file diff --git a/dist/server/lib/callback-url-handler.js b/dist/server/lib/callback-url-handler.js new file mode 100644 index 0000000000..03f68eedc7 --- /dev/null +++ b/dist/server/lib/callback-url-handler.js @@ -0,0 +1,40 @@ +"use strict"; + +var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = callbackUrlHandler; + +var cookie = _interopRequireWildcard(require("../lib/cookie")); + +async function callbackUrlHandler(req, res) { + const { + query + } = req; + const { + body + } = req; + const { + cookies, + baseUrl, + defaultCallbackUrl, + callbacks + } = req.options; + let callbackUrl = defaultCallbackUrl || baseUrl; + const callbackUrlParamValue = body.callbackUrl || query.callbackUrl || null; + const callbackUrlCookieValue = req.cookies[cookies.callbackUrl.name] || null; + + if (callbackUrlParamValue) { + callbackUrl = await callbacks.redirect(callbackUrlParamValue, baseUrl); + } else if (callbackUrlCookieValue) { + callbackUrl = await callbacks.redirect(callbackUrlCookieValue, baseUrl); + } + + if (callbackUrl && callbackUrl !== callbackUrlCookieValue) { + cookie.set(res, cookies.callbackUrl.name, callbackUrl, cookies.callbackUrl.options); + } + + req.options.callbackUrl = callbackUrl; +} \ No newline at end of file diff --git a/dist/server/lib/cookie.js b/dist/server/lib/cookie.js new file mode 100644 index 0000000000..be94cd8018 --- /dev/null +++ b/dist/server/lib/cookie.js @@ -0,0 +1,164 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.set = set; +exports.defaultCookies = defaultCookies; + +function set(res, name, value, options = {}) { + const stringValue = typeof value === 'object' ? 'j:' + JSON.stringify(value) : String(value); + + if ('maxAge' in options) { + options.expires = new Date(Date.now() + options.maxAge); + options.maxAge /= 1000; + } + + let setCookieHeader = res.getHeader('Set-Cookie') || []; + + if (!Array.isArray(setCookieHeader)) { + setCookieHeader = [setCookieHeader]; + } + + setCookieHeader.push(_serialize(name, String(stringValue), options)); + res.setHeader('Set-Cookie', setCookieHeader); +} + +function _serialize(name, val, options) { + const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/; + const opt = options || {}; + const enc = opt.encode || encodeURIComponent; + + if (typeof enc !== 'function') { + throw new TypeError('option encode is invalid'); + } + + if (!fieldContentRegExp.test(name)) { + throw new TypeError('argument name is invalid'); + } + + const value = enc(val); + + if (value && !fieldContentRegExp.test(value)) { + throw new TypeError('argument val is invalid'); + } + + let str = name + '=' + value; + + if (opt.maxAge != null) { + const maxAge = opt.maxAge - 0; + + if (isNaN(maxAge) || !isFinite(maxAge)) { + throw new TypeError('option maxAge is invalid'); + } + + str += '; Max-Age=' + Math.floor(maxAge); + } + + if (opt.domain) { + if (!fieldContentRegExp.test(opt.domain)) { + throw new TypeError('option domain is invalid'); + } + + str += '; Domain=' + opt.domain; + } + + if (opt.path) { + if (!fieldContentRegExp.test(opt.path)) { + throw new TypeError('option path is invalid'); + } + + str += '; Path=' + opt.path; + } else { + str += '; Path=/'; + } + + if (opt.expires) { + let expires = opt.expires; + + if (typeof opt.expires.toUTCString === 'function') { + expires = opt.expires.toUTCString(); + } else { + const dateExpires = new Date(opt.expires); + expires = dateExpires.toUTCString(); + } + + str += '; Expires=' + expires; + } + + if (opt.httpOnly) { + str += '; HttpOnly'; + } + + if (opt.secure) { + str += '; Secure'; + } + + if (opt.sameSite) { + const sameSite = typeof opt.sameSite === 'string' ? opt.sameSite.toLowerCase() : opt.sameSite; + + switch (sameSite) { + case true: + str += '; SameSite=Strict'; + break; + + case 'lax': + str += '; SameSite=Lax'; + break; + + case 'strict': + str += '; SameSite=Strict'; + break; + + case 'none': + str += '; SameSite=None'; + break; + + default: + throw new TypeError('option sameSite is invalid'); + } + } + + return str; +} + +function defaultCookies(useSecureCookies) { + const cookiePrefix = useSecureCookies ? '__Secure-' : ''; + return { + sessionToken: { + name: `${cookiePrefix}next-auth.session-token`, + options: { + httpOnly: true, + sameSite: 'lax', + path: '/', + secure: useSecureCookies + } + }, + callbackUrl: { + name: `${cookiePrefix}next-auth.callback-url`, + options: { + sameSite: 'lax', + path: '/', + secure: useSecureCookies + } + }, + csrfToken: { + name: `${useSecureCookies ? '__Host-' : ''}next-auth.csrf-token`, + options: { + httpOnly: true, + sameSite: 'lax', + path: '/', + secure: useSecureCookies + } + }, + pkceCodeVerifier: { + name: `${cookiePrefix}next-auth.pkce.code_verifier`, + options: { + httpOnly: true, + sameSite: 'lax', + path: '/', + secure: useSecureCookies + } + } + }; +} \ No newline at end of file diff --git a/dist/server/lib/create-secret.js b/dist/server/lib/create-secret.js new file mode 100644 index 0000000000..3ba2f5ba44 --- /dev/null +++ b/dist/server/lib/create-secret.js @@ -0,0 +1,20 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = createSecret; + +var _crypto = require("crypto"); + +function createSecret({ + userOptions, + basePath, + baseUrl +}) { + return userOptions.secret || (0, _crypto.createHash)('sha256').update(JSON.stringify({ + baseUrl, + basePath, + ...userOptions + })).digest('hex'); +} \ No newline at end of file diff --git a/dist/server/lib/csrf-token-handler.js b/dist/server/lib/csrf-token-handler.js new file mode 100644 index 0000000000..a802801b55 --- /dev/null +++ b/dist/server/lib/csrf-token-handler.js @@ -0,0 +1,37 @@ +"use strict"; + +var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = csrfTokenHandler; + +var _crypto = require("crypto"); + +var cookie = _interopRequireWildcard(require("./cookie")); + +function csrfTokenHandler(req, res) { + const { + cookies, + secret + } = req.options; + + if (cookies.csrfToken.name in req.cookies) { + const [csrfToken, csrfTokenHash] = req.cookies[cookies.csrfToken.name].split('|'); + const expectedCsrfTokenHash = (0, _crypto.createHash)('sha256').update(`${csrfToken}${secret}`).digest('hex'); + + if (csrfTokenHash === expectedCsrfTokenHash) { + const csrfTokenVerified = req.method === 'POST' && csrfToken === req.body.csrfToken; + req.options.csrfToken = csrfToken; + req.options.csrfTokenVerified = csrfTokenVerified; + return; + } + } + + const csrfToken = (0, _crypto.randomBytes)(32).toString('hex'); + const csrfTokenHash = (0, _crypto.createHash)('sha256').update(`${csrfToken}${secret}`).digest('hex'); + const csrfTokenCookie = `${csrfToken}|${csrfTokenHash}`; + cookie.set(res, cookies.csrfToken.name, csrfTokenCookie, cookies.csrfToken.options); + req.options.csrfToken = csrfToken; +} \ No newline at end of file diff --git a/dist/server/lib/default-callbacks.js b/dist/server/lib/default-callbacks.js new file mode 100644 index 0000000000..845b46e845 --- /dev/null +++ b/dist/server/lib/default-callbacks.js @@ -0,0 +1,29 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.signIn = signIn; +exports.redirect = redirect; +exports.session = session; +exports.jwt = jwt; + +async function signIn() { + return true; +} + +async function redirect(url, baseUrl) { + if (url.startsWith(baseUrl)) { + return url; + } + + return baseUrl; +} + +async function session(session) { + return session; +} + +async function jwt(token) { + return token; +} \ No newline at end of file diff --git a/dist/server/lib/default-events.js b/dist/server/lib/default-events.js new file mode 100644 index 0000000000..7595ed9df1 --- /dev/null +++ b/dist/server/lib/default-events.js @@ -0,0 +1,26 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.signIn = signIn; +exports.signOut = signOut; +exports.createUser = createUser; +exports.updateUser = updateUser; +exports.linkAccount = linkAccount; +exports.session = session; +exports.error = error; + +async function signIn(message) {} + +async function signOut(message) {} + +async function createUser(message) {} + +async function updateUser(message) {} + +async function linkAccount(message) {} + +async function session(message) {} + +async function error(message) {} \ No newline at end of file diff --git a/dist/server/lib/dispatch-event.js b/dist/server/lib/dispatch-event.js new file mode 100644 index 0000000000..c19f390dc2 --- /dev/null +++ b/dist/server/lib/dispatch-event.js @@ -0,0 +1,18 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = dispatchEvent; + +var _logger = _interopRequireDefault(require("../../lib/logger")); + +async function dispatchEvent(event, message) { + try { + await event(message); + } catch (e) { + _logger.default.error('EVENT_ERROR', e); + } +} \ No newline at end of file diff --git a/dist/server/lib/extend-res.js b/dist/server/lib/extend-res.js new file mode 100644 index 0000000000..824e071da0 --- /dev/null +++ b/dist/server/lib/extend-res.js @@ -0,0 +1,42 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extendRes; + +function extendRes(req, res, done) { + const originalResEnd = res.end.bind(res); + + res.end = (...args) => { + done(); + return originalResEnd(...args); + }; + + const originalResJson = res.json.bind(res); + + res.json = (...args) => { + done(); + return originalResJson(...args); + }; + + const originalResSend = res.send.bind(res); + + res.send = (...args) => { + done(); + return originalResSend(...args); + }; + + res.redirect = url => { + var _req$body; + + if (((_req$body = req.body) === null || _req$body === void 0 ? void 0 : _req$body.json) === 'true') { + return res.json({ + url + }); + } + + res.status(302).setHeader('Location', url); + return res.end(); + }; +} \ No newline at end of file diff --git a/dist/server/lib/oauth/callback.js b/dist/server/lib/oauth/callback.js new file mode 100644 index 0000000000..aec83ab31b --- /dev/null +++ b/dist/server/lib/oauth/callback.js @@ -0,0 +1,148 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = oAuthCallback; + +var _jsonwebtoken = require("jsonwebtoken"); + +var _client = _interopRequireDefault(require("./client")); + +var _logger = _interopRequireDefault(require("../../../lib/logger")); + +var _errors = require("../../../lib/errors"); + +async function oAuthCallback(req) { + var _provider$version; + + const { + provider, + pkce + } = req.options; + const client = (0, _client.default)(provider); + + if ((_provider$version = provider.version) !== null && _provider$version !== void 0 && _provider$version.startsWith("2.")) { + let { + code, + user + } = req.query; + + if (req.method === "POST") { + try { + const body = JSON.parse(JSON.stringify(req.body)); + + if (body.error) { + throw new Error(body.error); + } + + code = body.code; + user = body.user != null ? JSON.parse(body.user) : null; + } catch (error) { + _logger.default.error("OAUTH_CALLBACK_HANDLER_ERROR", error, req.body, provider.id, code); + + throw error; + } + } + + if (Object.prototype.hasOwnProperty.call(provider, "useAuthTokenHeader")) { + client.useAuthorizationHeaderforGET(provider.useAuthTokenHeader); + } else { + client.useAuthorizationHeaderforGET(true); + } + + try { + const tokens = await client.getOAuthAccessToken(code, provider, pkce.code_verifier); + let profileData; + + if (provider.idToken) { + if (!(tokens !== null && tokens !== void 0 && tokens.id_token)) { + throw new _errors.OAuthCallbackError("Missing JWT ID Token"); + } + + profileData = (0, _jsonwebtoken.decode)(tokens.id_token, { + json: true + }); + } else { + profileData = await client.get(provider, tokens.accessToken, tokens); + } + + return getProfile({ + profileData, + provider, + tokens, + user + }); + } catch (error) { + _logger.default.error("OAUTH_GET_ACCESS_TOKEN_ERROR", error, provider.id, code); + + throw error; + } + } + + try { + const { + oauth_token, + oauth_verifier + } = req.query; + const { + token_secret + } = await client.getOAuthRequestToken(provider.params); + const tokens = await client.getOAuthAccessToken(oauth_token, token_secret, oauth_verifier); + const profileData = await client.get(provider.profileUrl, tokens.oauth_token, tokens.oauth_token_secret); + return getProfile({ + profileData, + tokens, + provider + }); + } catch (error) { + _logger.default.error("OAUTH_V1_GET_ACCESS_TOKEN_ERROR", error); + + throw error; + } +} + +async function getProfile({ + profileData, + tokens, + provider, + user +}) { + try { + var _profile$email$toLowe, _profile$email; + + if (typeof profileData === "string" || profileData instanceof String) { + profileData = JSON.parse(profileData); + } + + if (user != null) { + profileData.user = user; + } + + _logger.default.debug("PROFILE_DATA", profileData); + + const profile = await provider.profile(profileData, tokens); + return { + profile: { ...profile, + email: (_profile$email$toLowe = (_profile$email = profile.email) === null || _profile$email === void 0 ? void 0 : _profile$email.toLowerCase()) !== null && _profile$email$toLowe !== void 0 ? _profile$email$toLowe : null + }, + account: { + provider: provider.id, + type: provider.type, + id: profile.id, + ...tokens + }, + OAuthProfile: profileData + }; + } catch (exception) { + _logger.default.error("OAUTH_PARSE_PROFILE_ERROR", exception, profileData); + + return { + profile: null, + account: null, + OAuthProfile: profileData + }; + } +} \ No newline at end of file diff --git a/dist/server/lib/oauth/client.js b/dist/server/lib/oauth/client.js new file mode 100644 index 0000000000..e35acd0535 --- /dev/null +++ b/dist/server/lib/oauth/client.js @@ -0,0 +1,257 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = oAuthClient; + +var _oauth = require("oauth"); + +var _querystring = _interopRequireDefault(require("querystring")); + +var _logger = _interopRequireDefault(require("../../../lib/logger")); + +var _jsonwebtoken = require("jsonwebtoken"); + +function oAuthClient(provider) { + var _provider$version; + + if ((_provider$version = provider.version) !== null && _provider$version !== void 0 && _provider$version.startsWith('2.')) { + const authorizationUrl = new URL(provider.authorizationUrl); + const basePath = authorizationUrl.origin; + const authorizePath = authorizationUrl.pathname; + const accessTokenPath = new URL(provider.accessTokenUrl).pathname; + const oauth2Client = new _oauth.OAuth2(provider.clientId, provider.clientSecret, basePath, authorizePath, accessTokenPath, provider.headers); + oauth2Client.getOAuthAccessToken = getOAuth2AccessToken; + oauth2Client.get = getOAuth2; + return oauth2Client; + } + + const oauth1Client = new _oauth.OAuth(provider.requestTokenUrl, provider.accessTokenUrl, provider.clientId, provider.clientSecret, provider.version || '1.0', provider.callbackUrl, provider.encoding || 'HMAC-SHA1'); + const originalGet = oauth1Client.get.bind(oauth1Client); + + oauth1Client.get = (...args) => { + return new Promise((resolve, reject) => { + originalGet(...args, (error, result) => { + if (error) { + return reject(error); + } + + resolve(result); + }); + }); + }; + + const originalGetOAuth1AccessToken = oauth1Client.getOAuthAccessToken.bind(oauth1Client); + + oauth1Client.getOAuthAccessToken = (...args) => { + return new Promise((resolve, reject) => { + originalGetOAuth1AccessToken(...args, (error, oauth_token, oauth_token_secret, params) => { + if (error) { + return reject(error); + } + + resolve({ + accessToken: oauth_token, + refreshToken: oauth_token_secret, + results: params, + oauth_token, + oauth_token_secret, + params + }); + }); + }); + }; + + const originalGetOAuthRequestToken = oauth1Client.getOAuthRequestToken.bind(oauth1Client); + + oauth1Client.getOAuthRequestToken = (params = {}) => { + return new Promise((resolve, reject) => { + originalGetOAuthRequestToken(params, (error, oauth_token, oauth_token_secret, params) => { + if (error) { + return reject(error); + } + + resolve({ + oauth_token, + oauth_token_secret, + params + }); + }); + }); + }; + + return oauth1Client; +} + +async function getOAuth2AccessToken(code, provider, codeVerifier) { + const url = provider.accessTokenUrl; + const params = { ...provider.params + }; + const headers = { ...provider.headers + }; + const codeParam = params.grant_type === 'refresh_token' ? 'refresh_token' : 'code'; + + if (!params[codeParam]) { + params[codeParam] = code; + } + + if (!params.client_id) { + params.client_id = provider.clientId; + } + + if (provider.id === 'apple' && typeof provider.clientSecret === 'object') { + const { + keyId, + teamId, + privateKey + } = provider.clientSecret; + const clientSecret = (0, _jsonwebtoken.sign)({ + iss: teamId, + iat: Math.floor(Date.now() / 1000), + exp: Math.floor(Date.now() / 1000) + 86400 * 180, + aud: 'https://appleid.apple.com', + sub: provider.clientId + }, privateKey.replace(/\\n/g, '\n'), { + algorithm: 'ES256', + keyid: keyId + }); + params.client_secret = clientSecret; + } else { + params.client_secret = provider.clientSecret; + } + + if (!params.redirect_uri) { + params.redirect_uri = provider.callbackUrl; + } + + if (!headers['Content-Type']) { + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } + + if (!headers['Client-ID']) { + headers['Client-ID'] = provider.clientId; + } + + if (provider.id === 'reddit') { + headers.Authorization = 'Basic ' + Buffer.from(provider.clientId + ':' + provider.clientSecret).toString('base64'); + } + + if (provider.id === 'identity-server4' && !headers.Authorization) { + headers.Authorization = `Bearer ${code}`; + } + + if (provider.protection.includes('pkce')) { + params.code_verifier = codeVerifier; + } + + const postData = _querystring.default.stringify(params); + + return new Promise((resolve, reject) => { + this._request('POST', url, headers, postData, null, (error, data, response) => { + if (error) { + _logger.default.error('OAUTH_GET_ACCESS_TOKEN_ERROR', error, data, response); + + return reject(error); + } + + let raw; + + try { + raw = JSON.parse(data); + } catch (_unused) { + raw = _querystring.default.parse(data); + } + + let accessToken; + + if (provider.id === 'slack') { + const { + ok, + error + } = raw; + + if (!ok) { + return reject(error); + } + + accessToken = raw.authed_user.access_token; + } else { + accessToken = raw.access_token; + } + + resolve({ + accessToken, + accessTokenExpires: null, + refreshToken: raw.refresh_token, + idToken: raw.id_token, + ...raw + }); + }); + }); +} + +async function getOAuth2(provider, accessToken, results) { + let url = provider.profileUrl; + let httpMethod = 'GET'; + const headers = { ...provider.headers + }; + + if (this._useAuthorizationHeaderForGET) { + headers.Authorization = this.buildAuthHeader(accessToken); + + if (['mailru', 'vk'].includes(provider.id)) { + const safeAccessTokenURL = new URL(url); + safeAccessTokenURL.searchParams.append('access_token', accessToken); + url = safeAccessTokenURL.href; + } + + if (provider.id === 'twitch') { + headers['Client-ID'] = provider.clientId; + } + + accessToken = null; + } + + if (provider.id === 'bungie') { + url = prepareProfileUrl({ + provider, + url, + results + }); + } + + if (provider.id === 'dropbox') { + httpMethod = 'POST'; + } + + return new Promise((resolve, reject) => { + this._request(httpMethod, url, headers, null, accessToken, (error, profileData) => { + if (error) { + return reject(error); + } + + resolve(profileData); + }); + }); +} + +function prepareProfileUrl({ + provider, + url, + results +}) { + var _provider$headers; + + if (!results.membership_id) { + throw new Error('Expected membership_id to be passed.'); + } + + if (!((_provider$headers = provider.headers) !== null && _provider$headers !== void 0 && _provider$headers['X-API-Key'])) { + throw new Error('The Bungie provider requires the X-API-Key option to be present in "headers".'); + } + + return url.replace('{membershipId}', results.membership_id); +} \ No newline at end of file diff --git a/dist/server/lib/oauth/pkce-handler.js b/dist/server/lib/oauth/pkce-handler.js new file mode 100644 index 0000000000..713e243b61 --- /dev/null +++ b/dist/server/lib/oauth/pkce-handler.js @@ -0,0 +1,122 @@ +"use strict"; + +var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.handleCallback = handleCallback; +exports.handleSignin = handleSignin; +exports.default = void 0; + +var _pkceChallenge = _interopRequireDefault(require("pkce-challenge")); + +var cookie = _interopRequireWildcard(require("../cookie")); + +var _jwt = _interopRequireDefault(require("../../../lib/jwt")); + +var _logger = _interopRequireDefault(require("../../../lib/logger")); + +var _errors = require("../../../lib/errors"); + +const PKCE_LENGTH = 64; +const PKCE_CODE_CHALLENGE_METHOD = 'S256'; +const PKCE_MAX_AGE = 60 * 15; + +async function handleCallback(req, res) { + const { + cookies, + provider, + baseUrl, + basePath + } = req.options; + + try { + var _provider$protection; + + if (!((_provider$protection = provider.protection) !== null && _provider$protection !== void 0 && _provider$protection.includes('pkce'))) { + return; + } + + if (!(cookies.pkceCodeVerifier.name in req.cookies)) { + throw new _errors.OAuthCallbackError('The code_verifier cookie was not found.'); + } + + const pkce = await _jwt.default.decode({ ...req.options.jwt, + token: req.cookies[cookies.pkceCodeVerifier.name], + maxAge: PKCE_MAX_AGE, + encryption: true + }); + req.options.pkce = pkce; + + _logger.default.debug('OAUTH_CALLBACK_PROTECTION', 'Read PKCE verifier from cookie', { + code_verifier: pkce.code_verifier, + pkceLength: PKCE_LENGTH, + method: PKCE_CODE_CHALLENGE_METHOD + }); + + cookie.set(res, cookies.pkceCodeVerifier.name, null, { + maxAge: 0 + }); + } catch (error) { + _logger.default.error('CALLBACK_OAUTH_ERROR', error); + + return res.redirect(`${baseUrl}${basePath}/error?error=OAuthCallback`); + } +} + +async function handleSignin(req, res) { + const { + cookies, + provider, + baseUrl, + basePath + } = req.options; + + try { + var _provider$protection2; + + if (!((_provider$protection2 = provider.protection) !== null && _provider$protection2 !== void 0 && _provider$protection2.includes('pkce'))) { + return; + } + + const pkce = (0, _pkceChallenge.default)(PKCE_LENGTH); + + _logger.default.debug('OAUTH_SIGNIN_PROTECTION', 'Created PKCE challenge/verifier', { ...pkce, + pkceLength: PKCE_LENGTH, + method: PKCE_CODE_CHALLENGE_METHOD + }); + + provider.authorizationParams = { ...provider.authorizationParams, + code_challenge: pkce.code_challenge, + code_challenge_method: PKCE_CODE_CHALLENGE_METHOD + }; + const encryptedCodeVerifier = await _jwt.default.encode({ ...req.options.jwt, + maxAge: PKCE_MAX_AGE, + token: { + code_verifier: pkce.code_verifier + }, + encryption: true + }); + const cookieExpires = new Date(); + cookieExpires.setTime(cookieExpires.getTime() + PKCE_MAX_AGE * 1000); + cookie.set(res, cookies.pkceCodeVerifier.name, encryptedCodeVerifier, { + expires: cookieExpires.toISOString(), + ...cookies.pkceCodeVerifier.options + }); + + _logger.default.debug('OAUTH_SIGNIN_PROTECTION', 'Created PKCE code_verifier saved in cookie'); + } catch (error) { + _logger.default.error('SIGNIN_OAUTH_ERROR', error); + + return res.redirect(`${baseUrl}${basePath}/error?error=OAuthSignin`); + } +} + +var _default = { + handleSignin, + handleCallback +}; +exports.default = _default; \ No newline at end of file diff --git a/dist/server/lib/oauth/state-handler.js b/dist/server/lib/oauth/state-handler.js new file mode 100644 index 0000000000..17a2997fb8 --- /dev/null +++ b/dist/server/lib/oauth/state-handler.js @@ -0,0 +1,89 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.handleCallback = handleCallback; +exports.handleSignin = handleSignin; +exports.default = void 0; + +var _crypto = require("crypto"); + +var _logger = _interopRequireDefault(require("../../../lib/logger")); + +var _errors = require("../../../lib/errors"); + +async function handleCallback(req, res) { + const { + csrfToken, + provider, + baseUrl, + basePath + } = req.options; + + try { + var _provider$protection; + + if (!((_provider$protection = provider.protection) !== null && _provider$protection !== void 0 && _provider$protection.includes('state'))) { + return; + } + + const state = req.query.state || req.body.state; + const expectedState = (0, _crypto.createHash)('sha256').update(csrfToken).digest('hex'); + + _logger.default.debug('OAUTH_CALLBACK_PROTECTION', 'Comparing received and expected state', { + state, + expectedState + }); + + if (state !== expectedState) { + throw new _errors.OAuthCallbackError('Invalid state returned from OAuth provider'); + } + } catch (error) { + _logger.default.error('STATE_ERROR', error); + + return res.redirect(`${baseUrl}${basePath}/error?error=OAuthCallback`); + } +} + +async function handleSignin(req, res) { + const { + provider, + baseUrl, + basePath, + csrfToken + } = req.options; + + try { + var _provider$protection2; + + if (!((_provider$protection2 = provider.protection) !== null && _provider$protection2 !== void 0 && _provider$protection2.includes('state'))) { + return; + } + + if ('state' in provider) { + _logger.default.warn('STATE_OPTION_DEPRECATION', 'The `state` provider option is being replaced with `protection`. See the docs.'); + } + + const state = (0, _crypto.createHash)('sha256').update(csrfToken).digest('hex'); + provider.authorizationParams = { ...provider.authorizationParams, + state + }; + + _logger.default.debug('OAUTH_CALLBACK_PROTECTION', 'Added state to authorization params', { + state + }); + } catch (error) { + _logger.default.error('SIGNIN_OAUTH_ERROR', error); + + return res.redirect(`${baseUrl}${basePath}/error?error=OAuthSignin`); + } +} + +var _default = { + handleSignin, + handleCallback +}; +exports.default = _default; \ No newline at end of file diff --git a/dist/server/lib/providers.js b/dist/server/lib/providers.js new file mode 100644 index 0000000000..465093a08d --- /dev/null +++ b/dist/server/lib/providers.js @@ -0,0 +1,17 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = parseProviders; + +function parseProviders({ + providers = [], + baseUrl, + basePath +}) { + return providers.map(provider => ({ ...provider, + signinUrl: `${baseUrl}${basePath}/signin/${provider.id}`, + callbackUrl: `${baseUrl}${basePath}/callback/${provider.id}` + })); +} \ No newline at end of file diff --git a/dist/server/lib/signin/email.js b/dist/server/lib/signin/email.js new file mode 100644 index 0000000000..0c7386f5d6 --- /dev/null +++ b/dist/server/lib/signin/email.js @@ -0,0 +1,35 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = email; + +var _crypto = require("crypto"); + +var _errorHandler = _interopRequireDefault(require("../../../adapters/error-handler")); + +async function email(email, provider, options) { + try { + var _await$provider$gener, _provider$generateVer; + + const { + baseUrl, + basePath, + adapter, + logger + } = options; + const { + createVerificationRequest + } = (0, _errorHandler.default)(await adapter.getAdapter(options), logger); + const secret = provider.secret || options.secret; + const token = (_await$provider$gener = await ((_provider$generateVer = provider.generateVerificationToken) === null || _provider$generateVer === void 0 ? void 0 : _provider$generateVer.call(provider))) !== null && _await$provider$gener !== void 0 ? _await$provider$gener : (0, _crypto.randomBytes)(32).toString("hex"); + const url = `${baseUrl}${basePath}/callback/${encodeURIComponent(provider.id)}?email=${encodeURIComponent(email)}&token=${encodeURIComponent(token)}`; + await createVerificationRequest(email, url, token, secret, provider, options); + return Promise.resolve(); + } catch (error) { + return Promise.reject(error); + } +} \ No newline at end of file diff --git a/dist/server/lib/signin/oauth.js b/dist/server/lib/signin/oauth.js new file mode 100644 index 0000000000..74510240fe --- /dev/null +++ b/dist/server/lib/signin/oauth.js @@ -0,0 +1,60 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = getAuthorizationUrl; + +var _client = _interopRequireDefault(require("../oauth/client")); + +var _logger = _interopRequireDefault(require("../../../lib/logger")); + +async function getAuthorizationUrl(req) { + var _req$query, _provider$version; + + const { + provider + } = req.options; + (_req$query = req.query) === null || _req$query === void 0 ? true : delete _req$query.nextauth; + const params = { ...provider.authorizationParams, + ...req.query + }; + const client = (0, _client.default)(provider); + + if ((_provider$version = provider.version) !== null && _provider$version !== void 0 && _provider$version.startsWith('2.')) { + let url = client.getAuthorizeUrl({ + scope: provider.scope, + ...params, + redirect_uri: provider.callbackUrl + }); + + if (provider.authorizationUrl.includes('?')) { + const parseUrl = new URL(provider.authorizationUrl); + const baseUrl = `${parseUrl.origin}${parseUrl.pathname}?`; + url = url.replace(baseUrl, provider.authorizationUrl + '&'); + } + + _logger.default.debug('GET_AUTHORIZATION_URL', url); + + return url; + } + + try { + const tokens = await client.getOAuthRequestToken(params); + const url = `${provider.authorizationUrl}?${new URLSearchParams({ + oauth_token: tokens.oauth_token, + oauth_token_secret: tokens.oauth_token_secret, + ...tokens.params + })}`; + + _logger.default.debug('GET_AUTHORIZATION_URL', url); + + return url; + } catch (error) { + _logger.default.error('GET_AUTHORIZATION_URL_ERROR', error); + + throw error; + } +} \ No newline at end of file diff --git a/dist/server/pages/error.js b/dist/server/pages/error.js new file mode 100644 index 0000000000..12c296d487 --- /dev/null +++ b/dist/server/pages/error.js @@ -0,0 +1,63 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = error; + +var _preact = require("preact"); + +function error({ + baseUrl, + basePath, + error = 'default', + res +}) { + var _errors$error$toLower; + + const signinPageUrl = `${baseUrl}${basePath}/signin`; + const errors = { + default: { + statusCode: 200, + heading: 'Error', + message: (0, _preact.h)("p", null, (0, _preact.h)("a", { + className: "site", + href: baseUrl + }, baseUrl.replace(/^https?:\/\//, ''))) + }, + configuration: { + statusCode: 500, + heading: 'Server error', + message: (0, _preact.h)("div", null, (0, _preact.h)("p", null, "There is a problem with the server configuration."), (0, _preact.h)("p", null, "Check the server logs for more information.")) + }, + accessdenied: { + statusCode: 403, + heading: 'Access Denied', + message: (0, _preact.h)("div", null, (0, _preact.h)("p", null, "You do not have permission to sign in."), (0, _preact.h)("p", null, (0, _preact.h)("a", { + className: "button", + href: signinPageUrl + }, "Sign in"))) + }, + verification: { + statusCode: 403, + heading: 'Unable to sign in', + message: (0, _preact.h)("div", null, (0, _preact.h)("p", null, "The sign in link is no longer valid."), (0, _preact.h)("p", null, "It may have been used already or it may have expired.")), + signin: (0, _preact.h)("p", null, (0, _preact.h)("a", { + className: "button", + href: signinPageUrl + }, "Sign in")) + } + }; + const { + statusCode, + heading, + message, + signin + } = (_errors$error$toLower = errors[error.toLowerCase()]) !== null && _errors$error$toLower !== void 0 ? _errors$error$toLower : errors.default; + res.status(statusCode); + return (0, _preact.h)("div", { + className: "error" + }, (0, _preact.h)("h1", null, heading), (0, _preact.h)("div", { + className: "message" + }, message), signin); +} \ No newline at end of file diff --git a/dist/server/pages/index.js b/dist/server/pages/index.js new file mode 100644 index 0000000000..8a4842e2d1 --- /dev/null +++ b/dist/server/pages/index.js @@ -0,0 +1,89 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = renderPage; + +var _preactRenderToString = _interopRequireDefault(require("preact-render-to-string")); + +var _signin = _interopRequireDefault(require("./signin")); + +var _signout = _interopRequireDefault(require("./signout")); + +var _verifyRequest = _interopRequireDefault(require("./verify-request")); + +var _error = _interopRequireDefault(require("./error")); + +var _css = _interopRequireDefault(require("../../css")); + +function renderPage(req, res) { + const { + baseUrl, + basePath, + callbackUrl, + csrfToken, + providers, + theme + } = req.options; + res.setHeader('Content-Type', 'text/html'); + + function send({ + html, + title + }) { + res.send(`${title}
${(0, _preactRenderToString.default)(html)}
`); + } + + return { + signin(props) { + send({ + html: (0, _signin.default)({ + csrfToken, + providers, + callbackUrl, + ...req.query, + ...props + }), + title: 'Sign In' + }); + }, + + signout(props) { + send({ + html: (0, _signout.default)({ + csrfToken, + baseUrl, + basePath, + ...props + }), + title: 'Sign Out' + }); + }, + + verifyRequest(props) { + send({ + html: (0, _verifyRequest.default)({ + baseUrl, + ...props + }), + title: 'Verify Request' + }); + }, + + error(props) { + send({ + html: (0, _error.default)({ + basePath, + baseUrl, + res, + ...props + }), + title: 'Error' + }); + } + + }; +} \ No newline at end of file diff --git a/dist/server/pages/signin.js b/dist/server/pages/signin.js new file mode 100644 index 0000000000..4a452c12a4 --- /dev/null +++ b/dist/server/pages/signin.js @@ -0,0 +1,102 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = signin; + +var _preact = require("preact"); + +function signin({ + csrfToken, + providers, + callbackUrl, + email, + error: errorType +}) { + var _errors$errorType; + + const providersToRender = providers.filter(provider => { + if (provider.type === 'oauth' || provider.type === 'email') { + return true; + } else if (provider.type === 'credentials' && provider.credentials) { + return true; + } + + return false; + }); + const errors = { + Signin: 'Try signing with a different account.', + OAuthSignin: 'Try signing with a different account.', + OAuthCallback: 'Try signing with a different account.', + OAuthCreateAccount: 'Try signing with a different account.', + EmailCreateAccount: 'Try signing with a different account.', + Callback: 'Try signing with a different account.', + OAuthAccountNotLinked: 'To confirm your identity, sign in with the same account you used originally.', + EmailSignin: 'Check your email address.', + CredentialsSignin: 'Sign in failed. Check the details you provided are correct.', + default: 'Unable to sign in.' + }; + const error = errorType && ((_errors$errorType = errors[errorType]) !== null && _errors$errorType !== void 0 ? _errors$errorType : errors.default); + return (0, _preact.h)("div", { + className: "signin" + }, error && (0, _preact.h)("div", { + className: "error" + }, (0, _preact.h)("p", null, error)), providersToRender.map((provider, i) => (0, _preact.h)("div", { + key: provider.id, + className: "provider" + }, provider.type === 'oauth' && (0, _preact.h)("form", { + action: provider.signinUrl, + method: "POST" + }, (0, _preact.h)("input", { + type: "hidden", + name: "csrfToken", + value: csrfToken + }), callbackUrl && (0, _preact.h)("input", { + type: "hidden", + name: "callbackUrl", + value: callbackUrl + }), (0, _preact.h)("button", { + type: "submit", + className: "button" + }, "Sign in with ", provider.name)), (provider.type === 'email' || provider.type === 'credentials') && i > 0 && providersToRender[i - 1].type !== 'email' && providersToRender[i - 1].type !== 'credentials' && (0, _preact.h)("hr", null), provider.type === 'email' && (0, _preact.h)("form", { + action: provider.signinUrl, + method: "POST" + }, (0, _preact.h)("input", { + type: "hidden", + name: "csrfToken", + value: csrfToken + }), (0, _preact.h)("label", { + for: `input-email-for-${provider.id}-provider` + }, "Email"), (0, _preact.h)("input", { + id: `input-email-for-${provider.id}-provider`, + autoFocus: true, + type: "text", + name: "email", + value: email, + placeholder: "email@example.com" + }), (0, _preact.h)("button", { + type: "submit" + }, "Sign in with ", provider.name)), provider.type === 'credentials' && (0, _preact.h)("form", { + action: provider.callbackUrl, + method: "POST" + }, (0, _preact.h)("input", { + type: "hidden", + name: "csrfToken", + value: csrfToken + }), Object.keys(provider.credentials).map(credential => { + return (0, _preact.h)("div", { + key: `input-group-${provider.id}` + }, (0, _preact.h)("label", { + for: `input-${credential}-for-${provider.id}-provider` + }, provider.credentials[credential].label || credential), (0, _preact.h)("input", { + name: credential, + id: `input-${credential}-for-${provider.id}-provider`, + type: provider.credentials[credential].type || 'text', + value: provider.credentials[credential].value || '', + placeholder: provider.credentials[credential].placeholder || '' + })); + }), (0, _preact.h)("button", { + type: "submit" + }, "Sign in with ", provider.name)), (provider.type === 'email' || provider.type === 'credentials') && i + 1 < providersToRender.length && (0, _preact.h)("hr", null)))); +} \ No newline at end of file diff --git a/dist/server/pages/signout.js b/dist/server/pages/signout.js new file mode 100644 index 0000000000..f69f48c1de --- /dev/null +++ b/dist/server/pages/signout.js @@ -0,0 +1,27 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = signout; + +var _preact = require("preact"); + +function signout({ + baseUrl, + basePath, + csrfToken +}) { + return (0, _preact.h)("div", { + className: "signout" + }, (0, _preact.h)("h1", null, "Are you sure you want to sign out?"), (0, _preact.h)("form", { + action: `${baseUrl}${basePath}/signout`, + method: "POST" + }, (0, _preact.h)("input", { + type: "hidden", + name: "csrfToken", + value: csrfToken + }), (0, _preact.h)("button", { + type: "submit" + }, "Sign out"))); +} \ No newline at end of file diff --git a/dist/server/pages/verify-request.js b/dist/server/pages/verify-request.js new file mode 100644 index 0000000000..b66dd26b6b --- /dev/null +++ b/dist/server/pages/verify-request.js @@ -0,0 +1,19 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = verifyRequest; + +var _preact = require("preact"); + +function verifyRequest({ + baseUrl +}) { + return (0, _preact.h)("div", { + className: "verify-request" + }, (0, _preact.h)("h1", null, "Check your email"), (0, _preact.h)("p", null, "A sign in link has been sent to your email address."), (0, _preact.h)("p", null, (0, _preact.h)("a", { + className: "site", + href: baseUrl + }, baseUrl.replace(/^https?:\/\//, '')))); +} \ No newline at end of file diff --git a/dist/server/routes/callback.js b/dist/server/routes/callback.js new file mode 100644 index 0000000000..8455810854 --- /dev/null +++ b/dist/server/routes/callback.js @@ -0,0 +1,333 @@ +"use strict"; + +var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = callback; + +var _callback = _interopRequireDefault(require("../lib/oauth/callback")); + +var _callbackHandler = _interopRequireDefault(require("../lib/callback-handler")); + +var cookie = _interopRequireWildcard(require("../lib/cookie")); + +var _dispatchEvent = _interopRequireDefault(require("../lib/dispatch-event")); + +var _errorHandler = _interopRequireDefault(require("../../adapters/error-handler")); + +async function callback(req, res) { + var _req$cookies$cookies$, _req$cookies; + + const { + provider, + adapter, + baseUrl, + basePath, + secret, + cookies, + callbackUrl, + pages, + jwt, + events, + callbacks, + session: { + jwt: useJwtSession, + maxAge: sessionMaxAge + }, + logger + } = req.options; + const sessionToken = (_req$cookies$cookies$ = (_req$cookies = req.cookies) === null || _req$cookies === void 0 ? void 0 : _req$cookies[cookies.sessionToken.name]) !== null && _req$cookies$cookies$ !== void 0 ? _req$cookies$cookies$ : null; + + if (provider.type === "oauth") { + try { + const { + profile, + account, + OAuthProfile + } = await (0, _callback.default)(req); + + try { + logger.debug("OAUTH_CALLBACK_RESPONSE", { + profile, + account, + OAuthProfile + }); + + if (!profile) { + return res.redirect(`${baseUrl}${basePath}/signin`); + } + + let userOrProfile = profile; + + if (adapter) { + const { + getUserByProviderAccountId + } = (0, _errorHandler.default)(await adapter.getAdapter(req.options), logger); + const userFromProviderAccountId = await getUserByProviderAccountId(account.provider, account.id); + + if (userFromProviderAccountId) { + userOrProfile = userFromProviderAccountId; + } + } + + try { + const signInCallbackResponse = await callbacks.signIn(userOrProfile, account, OAuthProfile); + + if (signInCallbackResponse === false) { + return res.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`); + } else if (typeof signInCallbackResponse === "string") { + return res.redirect(signInCallbackResponse); + } + } catch (error) { + if (error instanceof Error) { + return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`); + } + + logger.warn("SIGNIN_CALLBACK_REJECT_REDIRECT"); + return res.redirect(error); + } + + const { + user, + session, + isNewUser + } = await (0, _callbackHandler.default)(sessionToken, profile, account, req.options); + + if (useJwtSession) { + var _user$id; + + const defaultJwtPayload = { + name: user.name, + email: user.email, + picture: user.image, + sub: (_user$id = user.id) === null || _user$id === void 0 ? void 0 : _user$id.toString() + }; + const jwtPayload = await callbacks.jwt(defaultJwtPayload, user, account, OAuthProfile, isNewUser); + const newEncodedJwt = await jwt.encode({ ...jwt, + token: jwtPayload + }); + const cookieExpires = new Date(); + cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000); + cookie.set(res, cookies.sessionToken.name, newEncodedJwt, { + expires: cookieExpires.toISOString(), + ...cookies.sessionToken.options + }); + } else { + cookie.set(res, cookies.sessionToken.name, session.sessionToken, { + expires: session.expires || null, + ...cookies.sessionToken.options + }); + } + + await (0, _dispatchEvent.default)(events.signIn, { + user, + account, + isNewUser + }); + + if (isNewUser && pages.newUser) { + return res.redirect(`${pages.newUser}${pages.newUser.includes("?") ? "&" : "?"}callbackUrl=${encodeURIComponent(callbackUrl)}`); + } + + return res.redirect(callbackUrl || baseUrl); + } catch (error) { + if (error.name === "AccountNotLinkedError") { + return res.redirect(`${baseUrl}${basePath}/error?error=OAuthAccountNotLinked`); + } else if (error.name === "CreateUserError") { + return res.redirect(`${baseUrl}${basePath}/error?error=OAuthCreateAccount`); + } + + logger.error("OAUTH_CALLBACK_HANDLER_ERROR", error); + return res.redirect(`${baseUrl}${basePath}/error?error=Callback`); + } + } catch (error) { + if (error.name === "OAuthCallbackError") { + logger.error("CALLBACK_OAUTH_ERROR", error); + return res.redirect(`${baseUrl}${basePath}/error?error=OAuthCallback`); + } + + logger.error("OAUTH_CALLBACK_ERROR", error); + return res.redirect(`${baseUrl}${basePath}/error?error=Callback`); + } + } else if (provider.type === "email") { + try { + if (!adapter) { + logger.error("EMAIL_REQUIRES_ADAPTER_ERROR"); + return res.redirect(`${baseUrl}${basePath}/error?error=Configuration`); + } + + const { + getVerificationRequest, + deleteVerificationRequest, + getUserByEmail + } = (0, _errorHandler.default)(await adapter.getAdapter(req.options), logger); + const verificationToken = req.query.token; + const email = req.query.email; + const invite = await getVerificationRequest(email, verificationToken, secret, provider); + + if (!invite) { + return res.redirect(`${baseUrl}${basePath}/error?error=Verification`); + } + + await deleteVerificationRequest(email, verificationToken, secret, provider); + const profile = (await getUserByEmail(email)) || { + email + }; + const account = { + id: provider.id, + type: "email", + providerAccountId: email + }; + + try { + const signInCallbackResponse = await callbacks.signIn(profile, account, { + email + }); + + if (signInCallbackResponse === false) { + return res.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`); + } else if (typeof signInCallbackResponse === "string") { + return res.redirect(signInCallbackResponse); + } + } catch (error) { + if (error instanceof Error) { + return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`); + } + + logger.warn("SIGNIN_CALLBACK_REJECT_REDIRECT"); + return res.redirect(error); + } + + const { + user, + session, + isNewUser + } = await (0, _callbackHandler.default)(sessionToken, profile, account, req.options); + + if (useJwtSession) { + var _user$id2; + + const defaultJwtPayload = { + name: user.name, + email: user.email, + picture: user.image, + sub: (_user$id2 = user.id) === null || _user$id2 === void 0 ? void 0 : _user$id2.toString() + }; + const jwtPayload = await callbacks.jwt(defaultJwtPayload, user, account, profile, isNewUser); + const newEncodedJwt = await jwt.encode({ ...jwt, + token: jwtPayload + }); + const cookieExpires = new Date(); + cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000); + cookie.set(res, cookies.sessionToken.name, newEncodedJwt, { + expires: cookieExpires.toISOString(), + ...cookies.sessionToken.options + }); + } else { + cookie.set(res, cookies.sessionToken.name, session.sessionToken, { + expires: session.expires || null, + ...cookies.sessionToken.options + }); + } + + await (0, _dispatchEvent.default)(events.signIn, { + user, + account, + isNewUser + }); + + if (isNewUser && pages.newUser) { + return res.redirect(`${pages.newUser}${pages.newUser.includes("?") ? "&" : "?"}callbackUrl=${encodeURIComponent(callbackUrl)}`); + } + + return res.redirect(callbackUrl || baseUrl); + } catch (error) { + if (error.name === "CreateUserError") { + return res.redirect(`${baseUrl}${basePath}/error?error=EmailCreateAccount`); + } + + logger.error("CALLBACK_EMAIL_ERROR", error); + return res.redirect(`${baseUrl}${basePath}/error?error=Callback`); + } + } else if (provider.type === "credentials" && req.method === "POST") { + var _user$id3; + + if (!useJwtSession) { + logger.error("CALLBACK_CREDENTIALS_JWT_ERROR", "Signin in with credentials is only supported if JSON Web Tokens are enabled"); + return res.status(500).redirect(`${baseUrl}${basePath}/error?error=Configuration`); + } + + if (!provider.authorize) { + logger.error("CALLBACK_CREDENTIALS_HANDLER_ERROR", "Must define an authorize() handler to use credentials authentication provider"); + return res.status(500).redirect(`${baseUrl}${basePath}/error?error=Configuration`); + } + + const credentials = req.body; + let userObjectReturnedFromAuthorizeHandler; + + try { + userObjectReturnedFromAuthorizeHandler = await provider.authorize(credentials, { ...req, + options: {}, + cookies: {} + }); + + if (!userObjectReturnedFromAuthorizeHandler) { + return res.status(401).redirect(`${baseUrl}${basePath}/error?error=CredentialsSignin&provider=${encodeURIComponent(provider.id)}`); + } + } catch (error) { + if (error instanceof Error) { + return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`); + } + + return res.redirect(error); + } + + const user = userObjectReturnedFromAuthorizeHandler; + const account = { + id: provider.id, + type: "credentials" + }; + + try { + const signInCallbackResponse = await callbacks.signIn(user, account, credentials); + + if (signInCallbackResponse === false) { + return res.status(403).redirect(`${baseUrl}${basePath}/error?error=AccessDenied`); + } + } catch (error) { + if (error instanceof Error) { + return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`); + } + + return res.redirect(error); + } + + const defaultJwtPayload = { + name: user.name, + email: user.email, + picture: user.image, + sub: (_user$id3 = user.id) === null || _user$id3 === void 0 ? void 0 : _user$id3.toString() + }; + const jwtPayload = await callbacks.jwt(defaultJwtPayload, user, account, userObjectReturnedFromAuthorizeHandler, false); + const newEncodedJwt = await jwt.encode({ ...jwt, + token: jwtPayload + }); + const cookieExpires = new Date(); + cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000); + cookie.set(res, cookies.sessionToken.name, newEncodedJwt, { + expires: cookieExpires.toISOString(), + ...cookies.sessionToken.options + }); + await (0, _dispatchEvent.default)(events.signIn, { + user, + account + }); + return res.redirect(callbackUrl || baseUrl); + } + + return res.status(500).end(`Error: Callback for provider type ${provider.type} not supported`); +} \ No newline at end of file diff --git a/dist/server/routes/index.js b/dist/server/routes/index.js new file mode 100644 index 0000000000..59ceb9c776 --- /dev/null +++ b/dist/server/routes/index.js @@ -0,0 +1,47 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "callback", { + enumerable: true, + get: function () { + return _callback.default; + } +}); +Object.defineProperty(exports, "signin", { + enumerable: true, + get: function () { + return _signin.default; + } +}); +Object.defineProperty(exports, "signout", { + enumerable: true, + get: function () { + return _signout.default; + } +}); +Object.defineProperty(exports, "session", { + enumerable: true, + get: function () { + return _session.default; + } +}); +Object.defineProperty(exports, "providers", { + enumerable: true, + get: function () { + return _providers.default; + } +}); + +var _callback = _interopRequireDefault(require("./callback")); + +var _signin = _interopRequireDefault(require("./signin")); + +var _signout = _interopRequireDefault(require("./signout")); + +var _session = _interopRequireDefault(require("./session")); + +var _providers = _interopRequireDefault(require("./providers")); \ No newline at end of file diff --git a/dist/server/routes/providers.js b/dist/server/routes/providers.js new file mode 100644 index 0000000000..0c70dfef06 --- /dev/null +++ b/dist/server/routes/providers.js @@ -0,0 +1,29 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = providers; + +function providers(req, res) { + const { + providers + } = req.options; + const result = providers.reduce((acc, { + id, + name, + type, + signinUrl, + callbackUrl + }) => { + acc[id] = { + id, + name, + type, + signinUrl, + callbackUrl + }; + return acc; + }, {}); + res.json(result); +} \ No newline at end of file diff --git a/dist/server/routes/session.js b/dist/server/routes/session.js new file mode 100644 index 0000000000..bf2c1c5867 --- /dev/null +++ b/dist/server/routes/session.js @@ -0,0 +1,114 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = session; + +var cookie = _interopRequireWildcard(require("../lib/cookie")); + +var _dispatchEvent = _interopRequireDefault(require("../lib/dispatch-event")); + +var _errorHandler = _interopRequireDefault(require("../../adapters/error-handler")); + +async function session(req, res) { + const { + cookies, + adapter, + jwt, + events, + callbacks, + logger + } = req.options; + const useJwtSession = req.options.session.jwt; + const sessionMaxAge = req.options.session.maxAge; + const sessionToken = req.cookies[cookies.sessionToken.name]; + + if (!sessionToken) { + return res.json({}); + } + + let response = {}; + + if (useJwtSession) { + try { + const decodedJwt = await jwt.decode({ ...jwt, + token: sessionToken + }); + const sessionExpiresDate = new Date(); + sessionExpiresDate.setTime(sessionExpiresDate.getTime() + sessionMaxAge * 1000); + const sessionExpires = sessionExpiresDate.toISOString(); + const defaultSessionPayload = { + user: { + name: decodedJwt.name || null, + email: decodedJwt.email || null, + image: decodedJwt.picture || null + }, + expires: sessionExpires + }; + const jwtPayload = await callbacks.jwt(decodedJwt); + const sessionPayload = await callbacks.session(defaultSessionPayload, jwtPayload); + response = sessionPayload; + const newEncodedJwt = await jwt.encode({ ...jwt, + token: jwtPayload + }); + cookie.set(res, cookies.sessionToken.name, newEncodedJwt, { + expires: sessionExpires, + ...cookies.sessionToken.options + }); + await (0, _dispatchEvent.default)(events.session, { + session: sessionPayload, + jwt: jwtPayload + }); + } catch (error) { + logger.error("JWT_SESSION_ERROR", error); + cookie.set(res, cookies.sessionToken.name, "", { ...cookies.sessionToken.options, + maxAge: 0 + }); + } + } else { + try { + const { + getUser, + getSession, + updateSession + } = (0, _errorHandler.default)(await adapter.getAdapter(req.options), logger); + const session = await getSession(sessionToken); + + if (session) { + await updateSession(session); + const user = await getUser(session.userId); + const defaultSessionPayload = { + user: { + name: user.name, + email: user.email, + image: user.image + }, + accessToken: session.accessToken, + expires: session.expires + }; + const sessionPayload = await callbacks.session(defaultSessionPayload, user); + response = sessionPayload; + cookie.set(res, cookies.sessionToken.name, sessionToken, { + expires: session.expires, + ...cookies.sessionToken.options + }); + await (0, _dispatchEvent.default)(events.session, { + session: sessionPayload + }); + } else if (sessionToken) { + cookie.set(res, cookies.sessionToken.name, "", { ...cookies.sessionToken.options, + maxAge: 0 + }); + } + } catch (error) { + logger.error("SESSION_ERROR", error); + } + } + + res.json(response); +} \ No newline at end of file diff --git a/dist/server/routes/signin.js b/dist/server/routes/signin.js new file mode 100644 index 0000000000..4d5a549a70 --- /dev/null +++ b/dist/server/routes/signin.js @@ -0,0 +1,90 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = signin; + +var _oauth = _interopRequireDefault(require("../lib/signin/oauth")); + +var _email = _interopRequireDefault(require("../lib/signin/email")); + +var _errorHandler = _interopRequireDefault(require("../../adapters/error-handler")); + +async function signin(req, res) { + const { + provider, + baseUrl, + basePath, + adapter, + callbacks, + logger + } = req.options; + + if (!provider.type) { + return res.status(500).end(`Error: Type not specified for ${provider.name}`); + } + + if (provider.type === "oauth" && req.method === "POST") { + try { + const authorizationUrl = await (0, _oauth.default)(req); + return res.redirect(authorizationUrl); + } catch (error) { + logger.error("SIGNIN_OAUTH_ERROR", error); + return res.redirect(`${baseUrl}${basePath}/error?error=OAuthSignin`); + } + } else if (provider.type === "email" && req.method === "POST") { + var _req$body$email$toLow, _req$body$email; + + if (!adapter) { + logger.error("EMAIL_REQUIRES_ADAPTER_ERROR"); + return res.redirect(`${baseUrl}${basePath}/error?error=Configuration`); + } + + const { + getUserByEmail + } = (0, _errorHandler.default)(await adapter.getAdapter(req.options), logger); + const email = (_req$body$email$toLow = (_req$body$email = req.body.email) === null || _req$body$email === void 0 ? void 0 : _req$body$email.toLowerCase()) !== null && _req$body$email$toLow !== void 0 ? _req$body$email$toLow : null; + const profile = (await getUserByEmail(email)) || { + email + }; + const account = { + id: provider.id, + type: "email", + providerAccountId: email + }; + + try { + const signInCallbackResponse = await callbacks.signIn(profile, account, { + email, + verificationRequest: true + }); + + if (signInCallbackResponse === false) { + return res.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`); + } else if (typeof signInCallbackResponse === "string") { + return res.redirect(signInCallbackResponse); + } + } catch (error) { + if (error instanceof Error) { + return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`); + } + + logger.warn("SIGNIN_CALLBACK_REJECT_REDIRECT"); + return res.redirect(error); + } + + try { + await (0, _email.default)(email, provider, req.options); + } catch (error) { + logger.error("SIGNIN_EMAIL_ERROR", error); + return res.redirect(`${baseUrl}${basePath}/error?error=EmailSignin`); + } + + return res.redirect(`${baseUrl}${basePath}/verify-request?provider=${encodeURIComponent(provider.id)}&type=${encodeURIComponent(provider.type)}`); + } + + return res.redirect(`${baseUrl}${basePath}/signin`); +} \ No newline at end of file diff --git a/dist/server/routes/signout.js b/dist/server/routes/signout.js new file mode 100644 index 0000000000..70ea54fadd --- /dev/null +++ b/dist/server/routes/signout.js @@ -0,0 +1,59 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = signout; + +var cookie = _interopRequireWildcard(require("../lib/cookie")); + +var _dispatchEvent = _interopRequireDefault(require("../lib/dispatch-event")); + +var _errorHandler = _interopRequireDefault(require("../../adapters/error-handler")); + +async function signout(req, res) { + const { + adapter, + cookies, + events, + jwt, + callbackUrl, + logger + } = req.options; + const useJwtSession = req.options.session.jwt; + const sessionToken = req.cookies[cookies.sessionToken.name]; + + if (useJwtSession) { + try { + const decodedJwt = await jwt.decode({ ...jwt, + token: sessionToken + }); + await (0, _dispatchEvent.default)(events.signOut, decodedJwt); + } catch (error) {} + } else { + const { + getSession, + deleteSession + } = (0, _errorHandler.default)(await adapter.getAdapter(req.options), logger); + + try { + const session = await getSession(sessionToken); + await (0, _dispatchEvent.default)(events.signOut, session); + } catch (error) {} + + try { + await deleteSession(sessionToken); + } catch (error) { + logger.error("SIGNOUT_ERROR", error); + } + } + + cookie.set(res, cookies.sessionToken.name, "", { ...cookies.sessionToken.options, + maxAge: 0 + }); + return res.redirect(callbackUrl); +} \ No newline at end of file diff --git a/package.json b/package.json index fccd4ac4a2..e392f8d3b9 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,9 @@ { - "name": "next-auth", - "version": "0.0.0-semantically-released", + "name": "@GitStartHQ/next-auth", + "version": "0.0.2", "description": "Authentication for Next.js", "homepage": "https://next-auth.js.org", - "repository": "https://github.com/nextauthjs/next-auth.git", - "author": "Iain Collins ", + "repository": "https://github.com/GitStartHQ/next-auth.git", "main": "index.js", "types": "./index.d.ts", "keywords": [ @@ -177,5 +176,6 @@ "type": "github", "url": "https://github.com/sponsors/balazsorban44" } - ] + ], + "publishConfig":{"registry":"https://npm.pkg.github.com"} } diff --git a/src/server/lib/callback-handler.js b/src/server/lib/callback-handler.js index a3c67dfcb9..8054ee0503 100644 --- a/src/server/lib/callback-handler.js +++ b/src/server/lib/callback-handler.js @@ -206,19 +206,21 @@ export default async function callbackHandler( // we do already have an account with the same email address as the one in the // OAuth profile the user has just tried to sign in with. // - // We don't want to have two accounts with the same email address, and we don't - // want to link them in case it's not safe to do so, so instead we prompt the user - // to sign in via email to verify their identity and then link the accounts. - throw new AccountNotLinkedError() + // It's not safe to link email with a new profile, but explicilty allowed + // for now + + user = userByEmail + } + else { + // If the current user is not logged in and the profile isn't linked to any user + // accounts (by email or provider account id)... + // + // If no account matching the same [provider].id or .email exists, we can + // create a new account for the user, link it to the OAuth acccount and + // create a new session for them so they are signed in with it. + user = await createUser(profile) + await dispatchEvent(events.createUser, user) } - // If the current user is not logged in and the profile isn't linked to any user - // accounts (by email or provider account id)... - // - // If no account matching the same [provider].id or .email exists, we can - // create a new account for the user, link it to the OAuth acccount and - // create a new session for them so they are signed in with it. - user = await createUser(profile) - await dispatchEvent(events.createUser, user) await linkAccount( user.id,