2021-07-08 22:12:34 +12:00
|
|
|
const env = require("../../environment")
|
|
|
|
const jwt = require("jsonwebtoken")
|
|
|
|
const database = require("../../db")
|
|
|
|
const {
|
|
|
|
StaticDatabases,
|
|
|
|
generateGlobalUserID,
|
|
|
|
ViewNames,
|
|
|
|
} = require("../../db/utils")
|
|
|
|
const { authError } = require("./utils")
|
|
|
|
|
|
|
|
/**
|
2021-07-09 00:12:25 +12:00
|
|
|
* Common authentication logic for third parties. e.g. OAuth, OIDC.
|
2021-07-08 22:12:34 +12:00
|
|
|
*/
|
|
|
|
exports.authenticateThirdParty = async function (
|
2021-07-09 00:12:25 +12:00
|
|
|
thirdPartyUser,
|
|
|
|
requireLocalAccount = true,
|
|
|
|
done
|
|
|
|
) {
|
|
|
|
if (!thirdPartyUser.provider)
|
|
|
|
return authError(done, "third party user provider required")
|
|
|
|
if (!thirdPartyUser.userId)
|
|
|
|
return authError(done, "third party user id required")
|
|
|
|
if (!thirdPartyUser.email)
|
|
|
|
return authError(done, "third party user email required")
|
|
|
|
|
|
|
|
const db = database.getDB(StaticDatabases.GLOBAL.name)
|
|
|
|
|
|
|
|
let dbUser
|
|
|
|
|
|
|
|
// use the third party id
|
|
|
|
const userId = generateGlobalUserID(thirdPartyUser.userId)
|
|
|
|
|
2021-07-09 03:11:48 +12:00
|
|
|
// try to load by id
|
2021-07-09 00:12:25 +12:00
|
|
|
try {
|
|
|
|
dbUser = await db.get(userId)
|
|
|
|
} catch (err) {
|
|
|
|
// abort when not 404 error
|
|
|
|
if (!err.status || err.status !== 404) {
|
|
|
|
return authError(
|
|
|
|
done,
|
|
|
|
"Unexpected error when retrieving existing user",
|
|
|
|
err
|
|
|
|
)
|
2021-07-08 22:12:34 +12:00
|
|
|
}
|
2021-07-09 03:11:48 +12:00
|
|
|
}
|
2021-07-09 00:12:25 +12:00
|
|
|
|
2021-07-09 03:11:48 +12:00
|
|
|
// fallback to loading by email
|
|
|
|
if (!dbUser) {
|
2021-07-09 00:12:25 +12:00
|
|
|
const users = await db.query(`database/${ViewNames.USER_BY_EMAIL}`, {
|
|
|
|
key: thirdPartyUser.email,
|
|
|
|
include_docs: true,
|
2021-07-08 22:12:34 +12:00
|
|
|
})
|
2021-07-09 00:12:25 +12:00
|
|
|
|
2021-07-09 03:11:48 +12:00
|
|
|
if (users.rows.length > 0) {
|
|
|
|
dbUser = users.rows[0].doc
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// exit early if there is still no user and auto creation is disabled
|
|
|
|
if (!dbUser && requireLocalAccount ) {
|
|
|
|
if (requireLocalAccount) {
|
2021-07-09 00:12:25 +12:00
|
|
|
return authError(
|
|
|
|
done,
|
|
|
|
"Email does not yet exist. You must set up your local budibase account first."
|
|
|
|
)
|
|
|
|
}
|
2021-07-09 03:11:48 +12:00
|
|
|
}
|
2021-07-09 00:12:25 +12:00
|
|
|
|
2021-07-09 03:11:48 +12:00
|
|
|
let user
|
|
|
|
// first time creation
|
|
|
|
if (!dbUser) {
|
|
|
|
user = constructNewUser(userId, thirdPartyUser)
|
|
|
|
} else {
|
|
|
|
// existing user
|
|
|
|
user = constructMergedUser(userId, dbUser, thirdPartyUser)
|
|
|
|
await db.remove(dbUser._id, dbUser._rev)
|
2021-07-09 00:12:25 +12:00
|
|
|
}
|
|
|
|
|
2021-07-09 03:11:48 +12:00
|
|
|
// create or sync the user
|
|
|
|
const response = await db.post(user)
|
|
|
|
dbUser = user
|
|
|
|
dbUser._rev = response.rev
|
|
|
|
|
2021-07-09 00:12:25 +12:00
|
|
|
// authenticate
|
|
|
|
const payload = {
|
|
|
|
userId: dbUser._id,
|
|
|
|
builder: dbUser.builder,
|
|
|
|
email: dbUser.email,
|
|
|
|
}
|
|
|
|
|
|
|
|
dbUser.token = jwt.sign(payload, env.JWT_SECRET, {
|
|
|
|
expiresIn: "1 day",
|
|
|
|
})
|
|
|
|
|
|
|
|
return done(null, dbUser)
|
2021-07-08 22:12:34 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @returns a user object constructed from existing and third party information
|
|
|
|
*/
|
|
|
|
function constructMergedUser(userId, existing, thirdPartyUser) {
|
2021-07-09 03:11:48 +12:00
|
|
|
// sync third party fields
|
2021-07-08 22:12:34 +12:00
|
|
|
const user = constructNewUser(userId, thirdPartyUser)
|
|
|
|
|
2021-07-09 03:11:48 +12:00
|
|
|
// merge existing fields
|
2021-07-08 22:12:34 +12:00
|
|
|
user.roles = existing.roles
|
|
|
|
user.builder = existing.builder
|
|
|
|
user.admin = existing.admin
|
|
|
|
|
|
|
|
return user
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @returns a user object constructed from third party information
|
|
|
|
*/
|
|
|
|
function constructNewUser(userId, thirdPartyUser) {
|
|
|
|
const user = {
|
|
|
|
_id: userId,
|
|
|
|
provider: thirdPartyUser.provider,
|
|
|
|
providerType: thirdPartyUser.providerType,
|
2021-07-08 22:54:16 +12:00
|
|
|
email: thirdPartyUser.email,
|
2021-07-09 00:12:25 +12:00
|
|
|
roles: {},
|
2021-07-08 22:12:34 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
if (thirdPartyUser.profile) {
|
2021-07-09 03:11:48 +12:00
|
|
|
const profile = thirdPartyUser.profile
|
|
|
|
|
|
|
|
if (profile.name) {
|
|
|
|
const name = profile.name
|
|
|
|
// first name
|
|
|
|
if (name.givenName) {
|
|
|
|
user.firstName = name.givenName
|
|
|
|
}
|
|
|
|
// last name
|
|
|
|
if (name.familyName) {
|
|
|
|
user.lastName = name.familyName
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// profile
|
|
|
|
// @reviewers: Historically stored at the root level of the user
|
|
|
|
// Nest to prevent conflicts with future fields
|
|
|
|
// Is this okay to change?
|
2021-07-08 22:12:34 +12:00
|
|
|
user.thirdPartyProfile = {
|
2021-07-09 03:11:48 +12:00
|
|
|
...profile._json,
|
2021-07-08 22:12:34 +12:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-09 00:12:25 +12:00
|
|
|
// persist oauth tokens for future use
|
2021-07-08 22:12:34 +12:00
|
|
|
if (thirdPartyUser.oauth2) {
|
|
|
|
user.oauth2 = {
|
2021-07-09 00:12:25 +12:00
|
|
|
...thirdPartyUser.oauth2,
|
2021-07-08 22:12:34 +12:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return user
|
|
|
|
}
|