1
0
Fork 0
mirror of synced 2024-09-30 17:18:14 +13:00
budibase/packages/auth/src/middleware/passport/third-party-common.js

158 lines
3.7 KiB
JavaScript
Raw Normal View History

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.
*/
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)
// 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-09 00:12:25 +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-09 00:12:25 +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 00:12:25 +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
}
// 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)
}
/**
* @returns a user object constructed from existing and third party information
*/
function constructMergedUser(userId, existing, thirdPartyUser) {
// sync third party fields
const user = constructNewUser(userId, thirdPartyUser)
// merge existing fields
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: {},
}
if (thirdPartyUser.profile) {
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?
user.thirdPartyProfile = {
...profile._json,
}
}
2021-07-09 00:12:25 +12:00
// persist oauth tokens for future use
if (thirdPartyUser.oauth2) {
user.oauth2 = {
2021-07-09 00:12:25 +12:00
...thirdPartyUser.oauth2,
}
}
return user
}