Merge pull request #3155 from Budibase/match-lowercase-emails
Ignore case when finding user by email
This commit is contained in:
commit
dfbbbc443b
|
@ -13,6 +13,7 @@ exports.DocumentTypes = {
|
||||||
APP_DEV: `${PRE_APP}${exports.SEPARATOR}${PRE_DEV}`,
|
APP_DEV: `${PRE_APP}${exports.SEPARATOR}${PRE_DEV}`,
|
||||||
APP_METADATA: `${PRE_APP}${exports.SEPARATOR}metadata`,
|
APP_METADATA: `${PRE_APP}${exports.SEPARATOR}metadata`,
|
||||||
ROLE: "role",
|
ROLE: "role",
|
||||||
|
MIGRATIONS: "migrations",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.StaticDatabases = {
|
exports.StaticDatabases = {
|
||||||
|
|
|
@ -21,7 +21,7 @@ exports.createUserEmailView = async db => {
|
||||||
// if using variables in a map function need to inject them before use
|
// if using variables in a map function need to inject them before use
|
||||||
map: `function(doc) {
|
map: `function(doc) {
|
||||||
if (doc._id.startsWith("${DocumentTypes.USER}")) {
|
if (doc._id.startsWith("${DocumentTypes.USER}")) {
|
||||||
emit(doc.email, doc._id)
|
emit(doc.email.toLowerCase(), doc._id)
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Mock data
|
// Mock data
|
||||||
|
|
||||||
require("./utilities/test-config")
|
require("../../../tests/utilities/dbConfig")
|
||||||
|
|
||||||
const database = require("../../../db")
|
const database = require("../../../db")
|
||||||
const { authenticateThirdParty } = require("../third-party-common")
|
const { authenticateThirdParty } = require("../third-party-common")
|
||||||
|
@ -72,7 +72,6 @@ describe("third party common", () => {
|
||||||
|
|
||||||
const expectUserIsSynced = (user, thirdPartyUser) => {
|
const expectUserIsSynced = (user, thirdPartyUser) => {
|
||||||
expect(user.provider).toBe(thirdPartyUser.provider)
|
expect(user.provider).toBe(thirdPartyUser.provider)
|
||||||
expect(user.email).toBe(thirdPartyUser.email)
|
|
||||||
expect(user.firstName).toBe(thirdPartyUser.profile.name.givenName)
|
expect(user.firstName).toBe(thirdPartyUser.profile.name.givenName)
|
||||||
expect(user.lastName).toBe(thirdPartyUser.profile.name.familyName)
|
expect(user.lastName).toBe(thirdPartyUser.profile.name.familyName)
|
||||||
expect(user.thirdPartyProfile).toStrictEqual(thirdPartyUser.profile._json)
|
expect(user.thirdPartyProfile).toStrictEqual(thirdPartyUser.profile._json)
|
||||||
|
@ -135,6 +134,24 @@ describe("third party common", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("exists by email with different casing", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
id = generateGlobalUserID(newid()) // random id
|
||||||
|
email = thirdPartyUser.email.toUpperCase() // matching email except for casing
|
||||||
|
await createUser()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("syncs and authenticates the user", async () => {
|
||||||
|
await authenticateThirdParty(thirdPartyUser, true, done, saveUser)
|
||||||
|
|
||||||
|
const user = expectUserIsAuthenticated()
|
||||||
|
expectUserIsSynced(user, thirdPartyUser)
|
||||||
|
expectUserIsUpdated(user)
|
||||||
|
expect(user.email).toBe(thirdPartyUser.email.toUpperCase())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
describe("exists by id", () => {
|
describe("exists by id", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
id = generateGlobalUserID(thirdPartyUser.userId) // matching id
|
id = generateGlobalUserID(thirdPartyUser.userId) // matching id
|
||||||
|
|
|
@ -66,12 +66,16 @@ exports.authenticateThirdParty = async function (
|
||||||
// setup a blank user using the third party id
|
// setup a blank user using the third party id
|
||||||
dbUser = {
|
dbUser = {
|
||||||
_id: userId,
|
_id: userId,
|
||||||
|
email: thirdPartyUser.email,
|
||||||
roles: {},
|
roles: {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dbUser = await syncUser(dbUser, thirdPartyUser)
|
dbUser = await syncUser(dbUser, thirdPartyUser)
|
||||||
|
|
||||||
|
// never prompt for password reset
|
||||||
|
dbUser.forceResetPassword = false
|
||||||
|
|
||||||
// create or sync the user
|
// create or sync the user
|
||||||
let response
|
let response
|
||||||
try {
|
try {
|
||||||
|
@ -122,9 +126,6 @@ async function syncUser(user, thirdPartyUser) {
|
||||||
user.provider = thirdPartyUser.provider
|
user.provider = thirdPartyUser.provider
|
||||||
user.providerType = thirdPartyUser.providerType
|
user.providerType = thirdPartyUser.providerType
|
||||||
|
|
||||||
// email
|
|
||||||
user.email = thirdPartyUser.email
|
|
||||||
|
|
||||||
if (thirdPartyUser.profile) {
|
if (thirdPartyUser.profile) {
|
||||||
const profile = thirdPartyUser.profile
|
const profile = thirdPartyUser.profile
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
const { DocumentTypes } = require("../db/constants")
|
||||||
|
const { getGlobalDB } = require("../tenancy")
|
||||||
|
|
||||||
|
exports.MIGRATION_DBS = {
|
||||||
|
GLOBAL_DB: "GLOBAL_DB",
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.MIGRATIONS = {
|
||||||
|
USER_EMAIL_VIEW_CASING: "user_email_view_casing",
|
||||||
|
}
|
||||||
|
|
||||||
|
const DB_LOOKUP = {
|
||||||
|
[exports.MIGRATION_DBS.GLOBAL_DB]: [
|
||||||
|
exports.MIGRATIONS.USER_EMAIL_VIEW_CASING,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getMigrationsDoc = async db => {
|
||||||
|
// get the migrations doc
|
||||||
|
try {
|
||||||
|
return await db.get(DocumentTypes.MIGRATIONS)
|
||||||
|
} catch (err) {
|
||||||
|
if (err.status && err.status === 404) {
|
||||||
|
return { _id: DocumentTypes.MIGRATIONS }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.migrateIfRequired = async (migrationDb, migrationName, migrateFn) => {
|
||||||
|
try {
|
||||||
|
let db
|
||||||
|
if (migrationDb === exports.MIGRATION_DBS.GLOBAL_DB) {
|
||||||
|
db = getGlobalDB()
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unrecognised migration db [${migrationDb}]`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DB_LOOKUP[migrationDb].includes(migrationName)) {
|
||||||
|
throw new Error(
|
||||||
|
`Unrecognised migration name [${migrationName}] for db [${migrationDb}]`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = await exports.getMigrationsDoc(db)
|
||||||
|
// exit if the migration has been performed
|
||||||
|
if (doc[migrationName]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Performing migration: ${migrationName}`)
|
||||||
|
await migrateFn()
|
||||||
|
console.log(`Migration complete: ${migrationName}`)
|
||||||
|
|
||||||
|
// mark as complete
|
||||||
|
doc[migrationName] = Date.now()
|
||||||
|
await db.put(doc)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error performing migration: ${migrationName}: `, err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`migrations should match snapshot 1`] = `
|
||||||
|
Object {
|
||||||
|
"_id": "migrations",
|
||||||
|
"_rev": "1-af6c272fe081efafecd2ea49a8fcbb40",
|
||||||
|
"user_email_view_casing": 1487076708000,
|
||||||
|
}
|
||||||
|
`;
|
|
@ -0,0 +1,60 @@
|
||||||
|
require("../../tests/utilities/dbConfig")
|
||||||
|
|
||||||
|
const { migrateIfRequired, MIGRATION_DBS, MIGRATIONS, getMigrationsDoc } = require("../index")
|
||||||
|
const database = require("../../db")
|
||||||
|
const {
|
||||||
|
StaticDatabases,
|
||||||
|
} = require("../../db/utils")
|
||||||
|
|
||||||
|
Date.now = jest.fn(() => 1487076708000)
|
||||||
|
let db
|
||||||
|
|
||||||
|
describe("migrations", () => {
|
||||||
|
|
||||||
|
const migrationFunction = jest.fn()
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
db = database.getDB(StaticDatabases.GLOBAL.name)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await db.destroy()
|
||||||
|
})
|
||||||
|
|
||||||
|
const validMigration = () => {
|
||||||
|
return migrateIfRequired(MIGRATION_DBS.GLOBAL_DB, MIGRATIONS.USER_EMAIL_VIEW_CASING, migrationFunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should run a new migration", async () => {
|
||||||
|
await validMigration()
|
||||||
|
expect(migrationFunction).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should match snapshot", async () => {
|
||||||
|
await validMigration()
|
||||||
|
const doc = await getMigrationsDoc(db)
|
||||||
|
expect(doc).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should skip a previously run migration", async () => {
|
||||||
|
await validMigration()
|
||||||
|
await validMigration()
|
||||||
|
expect(migrationFunction).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject an unknown migration name", async () => {
|
||||||
|
expect(async () => {
|
||||||
|
await migrateIfRequired(MIGRATION_DBS.GLOBAL_DB, "bogus_name", migrationFunction)
|
||||||
|
}).rejects.toThrow()
|
||||||
|
expect(migrationFunction).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject an unknown database name", async () => {
|
||||||
|
expect(async () => {
|
||||||
|
await migrateIfRequired("bogus_db", MIGRATIONS.USER_EMAIL_VIEW_CASING, migrationFunction)
|
||||||
|
}).rejects.toThrow()
|
||||||
|
expect(migrationFunction).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
|
@ -1,5 +1,5 @@
|
||||||
const PouchDB = require("pouchdb")
|
const PouchDB = require("pouchdb")
|
||||||
const env = require("../../../../environment")
|
const env = require("../../environment")
|
||||||
|
|
||||||
let POUCH_DB_DEFAULTS
|
let POUCH_DB_DEFAULTS
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
const packageConfiguration = require("../../../../index")
|
const packageConfiguration = require("../../index")
|
||||||
const CouchDB = require("./db")
|
const CouchDB = require("./db")
|
||||||
packageConfiguration.init(CouchDB)
|
packageConfiguration.init(CouchDB)
|
|
@ -20,6 +20,9 @@ const { hash } = require("./hashing")
|
||||||
const userCache = require("./cache/user")
|
const userCache = require("./cache/user")
|
||||||
const env = require("./environment")
|
const env = require("./environment")
|
||||||
const { getUserSessions, invalidateSessions } = require("./security/sessions")
|
const { getUserSessions, invalidateSessions } = require("./security/sessions")
|
||||||
|
const { migrateIfRequired } = require("./migrations")
|
||||||
|
const { USER_EMAIL_VIEW_CASING } = require("./migrations").MIGRATIONS
|
||||||
|
const { GLOBAL_DB } = require("./migrations").MIGRATION_DBS
|
||||||
|
|
||||||
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
||||||
|
|
||||||
|
@ -128,10 +131,16 @@ exports.getGlobalUserByEmail = async email => {
|
||||||
throw "Must supply an email address to view"
|
throw "Must supply an email address to view"
|
||||||
}
|
}
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
|
|
||||||
|
await migrateIfRequired(GLOBAL_DB, USER_EMAIL_VIEW_CASING, async () => {
|
||||||
|
// re-create the view with latest changes
|
||||||
|
await createUserEmailView(db)
|
||||||
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let users = (
|
let users = (
|
||||||
await db.query(`database/${ViewNames.USER_BY_EMAIL}`, {
|
await db.query(`database/${ViewNames.USER_BY_EMAIL}`, {
|
||||||
key: email,
|
key: email.toLowerCase(),
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
).rows
|
).rows
|
||||||
|
|
Loading…
Reference in New Issue