Ignore case when finding user by email

This commit is contained in:
Rory Powell 2021-10-26 12:40:30 +01:00
parent f7dd62cbc2
commit 7b8318f9db
6 changed files with 99 additions and 6 deletions

View File

@ -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 = {

View File

@ -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)
} }
}`, }`,
} }

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,65 @@
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,
],
}
const 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) => {
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}]`
)
}
return tryMigrate(db, migrationName, migrateFn)
}
const tryMigrate = async (db, migrationName, migrateFn) => {
try {
const doc = await 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
}
}

View File

@ -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