2022-01-11 08:33:00 +13:00
|
|
|
const authPkg = require("@budibase/backend-core")
|
|
|
|
const { getScopedConfig } = require("@budibase/backend-core/db")
|
|
|
|
const { google } = require("@budibase/backend-core/src/middleware")
|
|
|
|
const { oidc } = require("@budibase/backend-core/src/middleware")
|
2021-05-06 02:10:28 +12:00
|
|
|
const { Configs, EmailTemplatePurpose } = require("../../../constants")
|
2021-05-05 23:11:06 +12:00
|
|
|
const { sendEmail, isEmailConfigured } = require("../../../utilities/email")
|
2021-10-14 04:23:16 +13:00
|
|
|
const {
|
|
|
|
setCookie,
|
|
|
|
getCookie,
|
|
|
|
clearCookie,
|
|
|
|
getGlobalUserByEmail,
|
|
|
|
hash,
|
|
|
|
platformLogout,
|
|
|
|
} = authPkg.utils
|
2021-12-18 03:08:48 +13:00
|
|
|
const { Cookies, Headers } = authPkg.constants
|
2021-04-22 03:42:44 +12:00
|
|
|
const { passport } = authPkg.auth
|
2021-05-06 02:10:28 +12:00
|
|
|
const { checkResetPasswordCode } = require("../../../utilities/redis")
|
2021-08-05 20:59:08 +12:00
|
|
|
const {
|
|
|
|
getGlobalDB,
|
|
|
|
getTenantId,
|
|
|
|
isMultiTenant,
|
2022-01-11 08:33:00 +13:00
|
|
|
} = require("@budibase/backend-core/tenancy")
|
2021-08-05 20:59:08 +12:00
|
|
|
const env = require("../../../environment")
|
|
|
|
|
2021-11-13 02:31:55 +13:00
|
|
|
const ssoCallbackUrl = async (config, type) => {
|
2021-08-05 22:27:51 +12:00
|
|
|
// incase there is a callback URL from before
|
|
|
|
if (config && config.callbackURL) {
|
|
|
|
return config.callbackURL
|
|
|
|
}
|
2021-11-13 02:31:55 +13:00
|
|
|
|
|
|
|
const db = getGlobalDB()
|
|
|
|
const publicConfig = await getScopedConfig(db, {
|
|
|
|
type: Configs.SETTINGS,
|
|
|
|
})
|
|
|
|
|
2021-08-05 20:59:08 +12:00
|
|
|
let callbackUrl = `/api/global/auth`
|
|
|
|
if (isMultiTenant()) {
|
|
|
|
callbackUrl += `/${getTenantId()}`
|
|
|
|
}
|
2021-11-13 02:31:55 +13:00
|
|
|
callbackUrl += `/${type}/callback`
|
|
|
|
|
|
|
|
return `${publicConfig.platformUrl}${callbackUrl}`
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.googleCallbackUrl = async config => {
|
|
|
|
return ssoCallbackUrl(config, "google")
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.oidcCallbackUrl = async config => {
|
|
|
|
return ssoCallbackUrl(config, "oidc")
|
2021-08-05 20:59:08 +12:00
|
|
|
}
|
2021-07-23 02:26:14 +12:00
|
|
|
|
2021-07-09 07:03:51 +12:00
|
|
|
async function authInternal(ctx, user, err = null, info = null) {
|
2021-04-28 04:29:05 +12:00
|
|
|
if (err) {
|
2021-07-08 22:12:34 +12:00
|
|
|
console.error("Authentication error", err)
|
2021-07-09 00:12:25 +12:00
|
|
|
return ctx.throw(403, info ? info : "Unauthorized")
|
2021-04-28 04:29:05 +12:00
|
|
|
}
|
2021-04-07 22:33:16 +12:00
|
|
|
|
2021-04-28 04:29:05 +12:00
|
|
|
if (!user) {
|
2021-07-09 00:12:25 +12:00
|
|
|
return ctx.throw(403, info ? info : "Unauthorized")
|
2021-04-28 04:29:05 +12:00
|
|
|
}
|
2021-04-12 21:47:48 +12:00
|
|
|
|
2021-12-18 03:08:48 +13:00
|
|
|
// set a cookie for browser access
|
2021-12-04 01:39:20 +13:00
|
|
|
setCookie(ctx, user.token, Cookies.Auth, { sign: false })
|
2021-12-18 03:08:48 +13:00
|
|
|
// set the token in a header as well for APIs
|
|
|
|
ctx.set(Headers.TOKEN, user.token)
|
2021-10-08 00:19:23 +13:00
|
|
|
// get rid of any app cookies on login
|
2021-10-08 05:39:44 +13:00
|
|
|
// have to check test because this breaks cypress
|
|
|
|
if (!env.isTest()) {
|
|
|
|
clearCookie(ctx, Cookies.CurrentApp)
|
|
|
|
}
|
2021-04-28 04:29:05 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
exports.authenticate = async (ctx, next) => {
|
2021-07-08 22:12:34 +12:00
|
|
|
return passport.authenticate("local", async (err, user, info) => {
|
2021-07-09 07:03:51 +12:00
|
|
|
await authInternal(ctx, user, err, info)
|
2021-04-08 02:15:05 +12:00
|
|
|
|
2021-04-12 21:47:48 +12:00
|
|
|
delete user.token
|
|
|
|
|
2021-04-16 02:57:55 +12:00
|
|
|
ctx.body = { user }
|
2021-04-02 08:34:43 +13:00
|
|
|
})(ctx, next)
|
|
|
|
}
|
2021-04-11 22:35:55 +12:00
|
|
|
|
2021-11-05 02:03:18 +13:00
|
|
|
exports.setInitInfo = ctx => {
|
|
|
|
const initInfo = ctx.request.body
|
|
|
|
setCookie(ctx, initInfo, Cookies.Init)
|
|
|
|
ctx.status = 200
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.getInitInfo = ctx => {
|
2021-11-27 03:27:37 +13:00
|
|
|
ctx.body = getCookie(ctx, Cookies.Init) || {}
|
2021-11-05 02:03:18 +13:00
|
|
|
}
|
|
|
|
|
2021-04-28 04:29:05 +12:00
|
|
|
/**
|
|
|
|
* Reset the user password, used as part of a forgotten password flow.
|
|
|
|
*/
|
|
|
|
exports.reset = async ctx => {
|
|
|
|
const { email } = ctx.request.body
|
2021-08-03 05:34:43 +12:00
|
|
|
const configured = await isEmailConfigured()
|
2021-05-05 23:11:06 +12:00
|
|
|
if (!configured) {
|
2021-05-06 02:19:44 +12:00
|
|
|
ctx.throw(
|
|
|
|
400,
|
|
|
|
"Please contact your platform administrator, SMTP is not configured."
|
|
|
|
)
|
2021-05-05 23:11:06 +12:00
|
|
|
}
|
2021-04-28 04:29:05 +12:00
|
|
|
try {
|
2021-08-03 05:34:43 +12:00
|
|
|
const user = await getGlobalUserByEmail(email)
|
2021-05-19 02:48:28 +12:00
|
|
|
// only if user exists, don't error though if they don't
|
|
|
|
if (user) {
|
2021-08-03 05:34:43 +12:00
|
|
|
await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, {
|
2021-05-19 02:48:28 +12:00
|
|
|
user,
|
|
|
|
subject: "{{ company }} platform password reset",
|
|
|
|
})
|
|
|
|
}
|
2021-04-28 04:29:05 +12:00
|
|
|
} catch (err) {
|
2021-08-05 20:59:08 +12:00
|
|
|
console.log(err)
|
2021-04-28 04:29:05 +12:00
|
|
|
// don't throw any kind of error to the user, this might give away something
|
|
|
|
}
|
2021-05-05 23:11:06 +12:00
|
|
|
ctx.body = {
|
2021-05-06 02:10:28 +12:00
|
|
|
message: "Please check your email for a reset link.",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Perform the user password update if the provided reset code is valid.
|
|
|
|
*/
|
|
|
|
exports.resetUpdate = async ctx => {
|
|
|
|
const { resetCode, password } = ctx.request.body
|
2021-05-06 02:17:15 +12:00
|
|
|
try {
|
2021-09-18 00:41:22 +12:00
|
|
|
const { userId } = await checkResetPasswordCode(resetCode)
|
2021-08-05 20:59:08 +12:00
|
|
|
const db = getGlobalDB()
|
2021-05-06 02:17:15 +12:00
|
|
|
const user = await db.get(userId)
|
|
|
|
user.password = await hash(password)
|
|
|
|
await db.put(user)
|
|
|
|
ctx.body = {
|
|
|
|
message: "password reset successfully.",
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
ctx.throw(400, "Cannot reset password.")
|
2021-05-05 23:11:06 +12:00
|
|
|
}
|
2021-04-28 04:29:05 +12:00
|
|
|
}
|
|
|
|
|
2021-04-14 00:56:28 +12:00
|
|
|
exports.logout = async ctx => {
|
2021-10-13 07:49:34 +13:00
|
|
|
await platformLogout({ ctx, userId: ctx.user._id })
|
2021-05-06 02:10:28 +12:00
|
|
|
ctx.body = { message: "User logged out." }
|
2021-04-14 00:56:28 +12:00
|
|
|
}
|
|
|
|
|
2021-04-22 22:45:22 +12:00
|
|
|
/**
|
|
|
|
* The initial call that google authentication makes to take you to the google login screen.
|
|
|
|
* On a successful login, you will be redirected to the googleAuth callback route.
|
|
|
|
*/
|
2021-04-22 08:08:04 +12:00
|
|
|
exports.googlePreAuth = async (ctx, next) => {
|
2021-08-05 20:59:08 +12:00
|
|
|
const db = getGlobalDB()
|
|
|
|
|
2021-05-06 21:51:21 +12:00
|
|
|
const config = await authPkg.db.getScopedConfig(db, {
|
2021-04-22 22:45:22 +12:00
|
|
|
type: Configs.GOOGLE,
|
2021-08-05 20:59:08 +12:00
|
|
|
workspace: ctx.query.workspace,
|
2021-04-22 22:45:22 +12:00
|
|
|
})
|
2021-11-13 02:31:55 +13:00
|
|
|
let callbackUrl = await exports.googleCallbackUrl(config)
|
2021-08-05 20:59:08 +12:00
|
|
|
const strategy = await google.strategyFactory(config, callbackUrl)
|
2021-04-22 08:08:04 +12:00
|
|
|
|
|
|
|
return passport.authenticate(strategy, {
|
|
|
|
scope: ["profile", "email"],
|
|
|
|
})(ctx, next)
|
|
|
|
}
|
2021-04-22 05:40:32 +12:00
|
|
|
|
2021-04-21 23:12:22 +12:00
|
|
|
exports.googleAuth = async (ctx, next) => {
|
2021-08-05 20:59:08 +12:00
|
|
|
const db = getGlobalDB()
|
2021-04-23 00:46:54 +12:00
|
|
|
|
2021-05-06 21:51:21 +12:00
|
|
|
const config = await authPkg.db.getScopedConfig(db, {
|
2021-04-22 22:48:37 +12:00
|
|
|
type: Configs.GOOGLE,
|
2021-08-05 20:59:08 +12:00
|
|
|
workspace: ctx.query.workspace,
|
2021-04-22 22:48:37 +12:00
|
|
|
})
|
2021-11-13 02:31:55 +13:00
|
|
|
const callbackUrl = await exports.googleCallbackUrl(config)
|
2021-08-05 20:59:08 +12:00
|
|
|
const strategy = await google.strategyFactory(config, callbackUrl)
|
2021-04-22 08:08:04 +12:00
|
|
|
|
2021-04-21 23:12:22 +12:00
|
|
|
return passport.authenticate(
|
2021-04-22 08:08:04 +12:00
|
|
|
strategy,
|
|
|
|
{ successRedirect: "/", failureRedirect: "/error" },
|
2021-07-08 22:12:34 +12:00
|
|
|
async (err, user, info) => {
|
2021-07-09 07:03:51 +12:00
|
|
|
await authInternal(ctx, user, err, info)
|
2021-04-21 23:12:22 +12:00
|
|
|
|
|
|
|
ctx.redirect("/")
|
|
|
|
}
|
|
|
|
)(ctx, next)
|
2021-04-11 22:35:55 +12:00
|
|
|
}
|
2021-06-28 02:46:04 +12:00
|
|
|
|
2021-07-16 03:20:31 +12:00
|
|
|
async function oidcStrategyFactory(ctx, configId) {
|
2021-08-05 20:59:08 +12:00
|
|
|
const db = getGlobalDB()
|
2021-07-09 00:04:04 +12:00
|
|
|
const config = await authPkg.db.getScopedConfig(db, {
|
|
|
|
type: Configs.OIDC,
|
|
|
|
group: ctx.query.group,
|
|
|
|
})
|
|
|
|
|
2021-07-14 04:07:48 +12:00
|
|
|
const chosenConfig = config.configs.filter(c => c.uuid === configId)[0]
|
2021-11-13 02:31:55 +13:00
|
|
|
let callbackUrl = await exports.oidcCallbackUrl(chosenConfig)
|
2021-07-09 00:04:04 +12:00
|
|
|
|
2021-07-14 04:07:48 +12:00
|
|
|
return oidc.strategyFactory(chosenConfig, callbackUrl)
|
2021-07-06 04:16:45 +12:00
|
|
|
}
|
2021-06-28 02:46:04 +12:00
|
|
|
|
2021-07-06 04:16:45 +12:00
|
|
|
/**
|
|
|
|
* The initial call that OIDC authentication makes to take you to the configured OIDC login screen.
|
|
|
|
* On a successful login, you will be redirected to the oidcAuth callback route.
|
|
|
|
*/
|
2021-06-28 02:46:04 +12:00
|
|
|
exports.oidcPreAuth = async (ctx, next) => {
|
2021-07-16 03:20:31 +12:00
|
|
|
const { configId } = ctx.params
|
|
|
|
const strategy = await oidcStrategyFactory(ctx, configId)
|
|
|
|
|
|
|
|
setCookie(ctx, configId, Cookies.OIDC_CONFIG)
|
2021-06-28 02:46:04 +12:00
|
|
|
|
|
|
|
return passport.authenticate(strategy, {
|
2021-07-09 02:21:54 +12:00
|
|
|
// required 'openid' scope is added by oidc strategy factory
|
2021-06-28 02:46:04 +12:00
|
|
|
scope: ["profile", "email"],
|
|
|
|
})(ctx, next)
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.oidcAuth = async (ctx, next) => {
|
2021-07-16 03:20:31 +12:00
|
|
|
const configId = getCookie(ctx, Cookies.OIDC_CONFIG)
|
|
|
|
const strategy = await oidcStrategyFactory(ctx, configId)
|
2021-06-28 02:46:04 +12:00
|
|
|
|
|
|
|
return passport.authenticate(
|
|
|
|
strategy,
|
|
|
|
{ successRedirect: "/", failureRedirect: "/error" },
|
2021-07-08 00:28:55 +12:00
|
|
|
async (err, user, info) => {
|
2021-07-09 07:03:51 +12:00
|
|
|
await authInternal(ctx, user, err, info)
|
2021-04-21 23:12:22 +12:00
|
|
|
|
|
|
|
ctx.redirect("/")
|
|
|
|
}
|
|
|
|
)(ctx, next)
|
2021-04-11 22:35:55 +12:00
|
|
|
}
|