diff --git a/packages/auth/package.json b/packages/auth/package.json index 294b09321d..b4f4b1cb33 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -12,6 +12,7 @@ "passport-google-auth": "^1.0.2", "passport-google-oauth": "^2.0.0", "passport-jwt": "^4.0.0", - "passport-local": "^1.0.0" + "passport-local": "^1.0.0", + "uuid": "^8.3.2" } } diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index dc1512c995..a4c0cce0dd 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -1,3 +1,9 @@ +const uuid = require("uuid/v4") + +exports.ViewNames = { + USER_BY_EMAIL: "by_email", +} + exports.StaticDatabases = { GLOBAL: { name: "global-db", @@ -17,28 +23,23 @@ const SEPARATOR = "_" exports.SEPARATOR = SEPARATOR /** - * Generates a new user ID based on the passed in email. - * @param {string} email The email which the ID is going to be built up of. + * Generates a new global user ID. * @returns {string} The new user ID which the user doc can be stored under. */ -exports.generateUserID = email => { - return `${DocumentTypes.USER}${SEPARATOR}${email}` -} - -exports.getEmailFromUserID = userId => { - return userId.split(`${DocumentTypes.USER}${SEPARATOR}`)[1] +exports.generateUserID = () => { + return `${DocumentTypes.USER}${SEPARATOR}${uuid()}` } /** * Gets parameters for retrieving users, this is a utility function for the getDocParams function. */ -exports.getUserParams = (email = "", otherProps = {}) => { - if (!email) { - email = "" +exports.getUserParams = (globalId = "", otherProps = {}) => { + if (!globalId) { + globalId = "" } return { ...otherProps, - startkey: `${DocumentTypes.USER}${SEPARATOR}${email}`, - endkey: `${DocumentTypes.USER}${SEPARATOR}${email}${UNICODE_MAX}`, + startkey: `${DocumentTypes.USER}${SEPARATOR}${globalId}`, + endkey: `${DocumentTypes.USER}${SEPARATOR}${globalId}${UNICODE_MAX}`, } } diff --git a/packages/auth/src/db/views.js b/packages/auth/src/db/views.js new file mode 100644 index 0000000000..86c919b012 --- /dev/null +++ b/packages/auth/src/db/views.js @@ -0,0 +1,20 @@ +const { DocumentTypes, ViewNames, StaticDatabases } = require("./utils") +const { CouchDB } = require("./index") + +exports.createUserEmailView = async () => { + const db = new CouchDB(StaticDatabases.GLOBAL.name) + const designDoc = await db.get("_design/database") + const view = { + // if using variables in a map function need to inject them before use + map: `function(doc) { + if (doc._id.startsWith("${DocumentTypes.USER}")) { + emit(doc.email, doc._id) + } + }`, + } + designDoc.views = { + ...designDoc.views, + [ViewNames.USER_BY_EMAIL]: view, + } + await db.put(designDoc) +} diff --git a/packages/auth/src/index.js b/packages/auth/src/index.js index 6ac56f8f36..2dbd027061 100644 --- a/packages/auth/src/index.js +++ b/packages/auth/src/index.js @@ -17,8 +17,8 @@ const { const { generateUserID, getUserParams, - getEmailFromUserID, } = require("./db/utils") +const { getGlobalUserByEmail } = require("./utils") // Strategies passport.use(new LocalStrategy(local.options, local.authenticate)) @@ -49,7 +49,6 @@ module.exports = { StaticDatabases, generateUserID, getUserParams, - getEmailFromUserID, hash, compare, getAppId, @@ -58,4 +57,5 @@ module.exports = { clearCookie, authenticated, isClient, + getGlobalUserByEmail, } diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js index af2c7d5575..8f69a49e17 100644 --- a/packages/auth/src/middleware/authenticated.js +++ b/packages/auth/src/middleware/authenticated.js @@ -1,6 +1,5 @@ const { Cookies } = require("../constants") const { getCookie } = require("../utils") -const { getEmailFromUserID } = require("../db/utils") module.exports = async (ctx, next) => { try { @@ -10,8 +9,6 @@ module.exports = async (ctx, next) => { if (authCookie) { ctx.isAuthenticated = true ctx.user = authCookie - // make sure email is correct from ID - ctx.user.email = getEmailFromUserID(authCookie.userId) } await next() diff --git a/packages/auth/src/middleware/passport/local.js b/packages/auth/src/middleware/passport/local.js index 6249050d05..333f381297 100644 --- a/packages/auth/src/middleware/passport/local.js +++ b/packages/auth/src/middleware/passport/local.js @@ -4,6 +4,7 @@ const database = require("../../db") const { StaticDatabases, generateUserID } = require("../../db/utils") const { compare } = require("../../hashing") const env = require("../../environment") +const { getGlobalUserByEmail } = require("../../utils") const INVALID_ERR = "Invalid Credentials" @@ -11,21 +12,18 @@ exports.options = {} /** * Passport Local Authentication Middleware. - * @param {*} username - username to login with + * @param {*} email - username to login with * @param {*} password - plain text password to log in with * @param {*} done - callback from passport to return user information and errors * @returns The authenticated user, or errors if they occur */ -exports.authenticate = async function(username, password, done) { - if (!username) return done(null, false, "Email Required.") +exports.authenticate = async function(email, password, done) { + if (!email) return done(null, false, "Email Required.") if (!password) return done(null, false, "Password Required.") - // Check the user exists in the instance DB by email - const db = new database.CouchDB(StaticDatabases.GLOBAL.name) - let dbUser try { - dbUser = await db.get(generateUserID(username)) + dbUser = await getGlobalUserByEmail(email) } catch (err) { console.error("User not found", err) return done(null, false, { message: "User not found" }) diff --git a/packages/auth/src/utils.js b/packages/auth/src/utils.js index 5f0a135a45..2320896150 100644 --- a/packages/auth/src/utils.js +++ b/packages/auth/src/utils.js @@ -1,6 +1,8 @@ -const { DocumentTypes, SEPARATOR } = require("./db/utils") +const { DocumentTypes, SEPARATOR, ViewNames, StaticDatabases } = require("./db/utils") const jwt = require("jsonwebtoken") const { options } = require("./middleware/passport/jwt") +const { createUserEmailView } = require("./db/views") +const { CouchDB } = require("./db") const APP_PREFIX = DocumentTypes.APP + SEPARATOR @@ -97,3 +99,21 @@ exports.clearCookie = (ctx, name) => { exports.isClient = ctx => { return ctx.headers["x-budibase-type"] === "client" } + +exports.getGlobalUserByEmail = async email => { + const db = new CouchDB(StaticDatabases.GLOBAL.name) + try { + let users = (await db.query( + `database/${ViewNames.USER_BY_EMAIL}`, + { + key: email + }) + ).rows + return users.length <= 1 ? users[0] : users + } catch (err) { + if (err != null && err.name === "not_found") { + await createUserEmailView() + return exports.getGlobalUserByEmail(email) + } + } +} diff --git a/packages/auth/yarn.lock b/packages/auth/yarn.lock index 6771306258..c3066ebdc1 100644 --- a/packages/auth/yarn.lock +++ b/packages/auth/yarn.lock @@ -584,6 +584,11 @@ uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"