2022-11-17 07:12:31 +13:00
|
|
|
import {
|
2023-02-21 21:23:53 +13:00
|
|
|
auth as authCore,
|
2022-11-27 03:46:01 +13:00
|
|
|
constants,
|
2022-11-17 07:12:31 +13:00
|
|
|
context,
|
2022-11-27 03:46:01 +13:00
|
|
|
db as dbCore,
|
|
|
|
events,
|
2022-11-17 07:12:31 +13:00
|
|
|
tenancy,
|
2023-02-21 21:23:53 +13:00
|
|
|
utils as utilsCore,
|
2022-11-17 07:12:31 +13:00
|
|
|
} from "@budibase/backend-core"
|
2023-02-21 21:23:53 +13:00
|
|
|
import {
|
|
|
|
ConfigType,
|
|
|
|
User,
|
|
|
|
Ctx,
|
|
|
|
LoginRequest,
|
|
|
|
SSOUser,
|
|
|
|
PasswordResetRequest,
|
|
|
|
PasswordResetUpdateRequest,
|
|
|
|
} from "@budibase/types"
|
2022-11-17 07:12:31 +13:00
|
|
|
import env from "../../../environment"
|
2022-11-27 03:46:01 +13:00
|
|
|
|
2023-02-21 21:23:53 +13:00
|
|
|
import * as authSdk from "../../../sdk/auth"
|
|
|
|
import * as userSdk from "../../../sdk/users"
|
2021-08-05 20:59:08 +12:00
|
|
|
|
2023-02-21 21:23:53 +13:00
|
|
|
const { Cookie, Header } = constants
|
|
|
|
const { passport, ssoCallbackUrl, google, oidc } = authCore
|
|
|
|
const { setCookie, getCookie, clearCookie } = utilsCore
|
2021-11-13 02:31:55 +13:00
|
|
|
|
2023-02-21 21:23:53 +13:00
|
|
|
// LOGIN / LOGOUT
|
2021-07-23 02:26:14 +12:00
|
|
|
|
2023-02-21 21:23:53 +13:00
|
|
|
async function passportCallback(
|
|
|
|
ctx: Ctx,
|
|
|
|
user: User,
|
|
|
|
err: any = null,
|
|
|
|
info: { message: string } | null = null
|
|
|
|
) {
|
2021-04-28 04:29:05 +12:00
|
|
|
if (err) {
|
2023-02-16 04:10:02 +13:00
|
|
|
console.error("Authentication error")
|
|
|
|
console.error(err)
|
|
|
|
console.trace(err)
|
2021-07-09 00:12:25 +12:00
|
|
|
return ctx.throw(403, info ? info : "Unauthorized")
|
2021-04-28 04:29:05 +12:00
|
|
|
}
|
|
|
|
if (!user) {
|
2023-02-16 04:10:02 +13:00
|
|
|
console.error("Authentication error - no user provided")
|
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
|
|
|
|
2023-02-21 21:23:53 +13:00
|
|
|
const token = await authSdk.loginUser(user)
|
|
|
|
|
2021-12-18 03:08:48 +13:00
|
|
|
// set a cookie for browser access
|
2023-02-21 21:23:53 +13:00
|
|
|
setCookie(ctx, token, Cookie.Auth, { sign: false })
|
2021-12-18 03:08:48 +13:00
|
|
|
// set the token in a header as well for APIs
|
2023-02-21 21:23:53 +13:00
|
|
|
ctx.set(Header.TOKEN, 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()) {
|
2022-11-17 06:23:12 +13:00
|
|
|
clearCookie(ctx, Cookie.CurrentApp)
|
2021-10-08 05:39:44 +13:00
|
|
|
}
|
2021-04-28 04:29:05 +12:00
|
|
|
}
|
|
|
|
|
2023-02-21 21:23:53 +13:00
|
|
|
export const login = async (ctx: Ctx<LoginRequest>, next: any) => {
|
|
|
|
const email = ctx.request.body.username
|
|
|
|
|
|
|
|
const user = await userSdk.getUserByEmail(email)
|
|
|
|
if (user && (await userSdk.isPreventSSOPasswords(user))) {
|
|
|
|
ctx.throw(400, "SSO user cannot login using password")
|
|
|
|
}
|
|
|
|
|
2022-03-18 21:01:31 +13:00
|
|
|
return passport.authenticate(
|
|
|
|
"local",
|
2022-07-04 23:54:26 +12:00
|
|
|
async (err: any, user: User, info: any) => {
|
2023-02-21 21:23:53 +13:00
|
|
|
await passportCallback(ctx, user, err, info)
|
2022-05-29 08:38:22 +12:00
|
|
|
await context.identity.doInUserContext(user, async () => {
|
2022-05-24 20:54:36 +12:00
|
|
|
await events.auth.login("local")
|
|
|
|
})
|
2022-03-18 21:01:31 +13:00
|
|
|
ctx.status = 200
|
|
|
|
}
|
|
|
|
)(ctx, next)
|
2021-04-02 08:34:43 +13:00
|
|
|
}
|
2021-04-11 22:35:55 +12:00
|
|
|
|
2023-02-21 21:23:53 +13:00
|
|
|
export const logout = async (ctx: any) => {
|
|
|
|
if (ctx.user && ctx.user._id) {
|
|
|
|
await authSdk.logout({ ctx, userId: ctx.user._id })
|
|
|
|
}
|
|
|
|
ctx.body = { message: "User logged out." }
|
|
|
|
}
|
|
|
|
|
|
|
|
// INIT
|
|
|
|
|
2022-03-18 21:01:31 +13:00
|
|
|
export const setInitInfo = (ctx: any) => {
|
2021-11-05 02:03:18 +13:00
|
|
|
const initInfo = ctx.request.body
|
2022-11-17 06:23:12 +13:00
|
|
|
setCookie(ctx, initInfo, Cookie.Init)
|
2021-11-05 02:03:18 +13:00
|
|
|
ctx.status = 200
|
|
|
|
}
|
|
|
|
|
2022-03-18 21:01:31 +13:00
|
|
|
export const getInitInfo = (ctx: any) => {
|
2022-03-15 08:05:02 +13:00
|
|
|
try {
|
2022-11-17 06:23:12 +13:00
|
|
|
ctx.body = getCookie(ctx, Cookie.Init) || {}
|
2022-03-15 08:05:02 +13:00
|
|
|
} catch (err) {
|
2022-11-17 06:23:12 +13:00
|
|
|
clearCookie(ctx, Cookie.Init)
|
2022-03-15 08:05:02 +13:00
|
|
|
ctx.body = {}
|
|
|
|
}
|
2021-11-05 02:03:18 +13:00
|
|
|
}
|
|
|
|
|
2023-02-21 21:23:53 +13:00
|
|
|
// PASSWORD MANAGEMENT
|
|
|
|
|
2021-04-28 04:29:05 +12:00
|
|
|
/**
|
|
|
|
* Reset the user password, used as part of a forgotten password flow.
|
|
|
|
*/
|
2023-02-21 21:23:53 +13:00
|
|
|
export const reset = async (ctx: Ctx<PasswordResetRequest>) => {
|
2021-04-28 04:29:05 +12:00
|
|
|
const { email } = ctx.request.body
|
2023-02-21 21:23:53 +13:00
|
|
|
|
|
|
|
await authSdk.reset(email)
|
|
|
|
|
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.
|
|
|
|
*/
|
2023-02-21 21:23:53 +13:00
|
|
|
export const resetUpdate = async (ctx: Ctx<PasswordResetUpdateRequest>) => {
|
2021-05-06 02:10:28 +12:00
|
|
|
const { resetCode, password } = ctx.request.body
|
2021-05-06 02:17:15 +12:00
|
|
|
try {
|
2023-02-21 21:23:53 +13:00
|
|
|
await authSdk.resetUpdate(resetCode, password)
|
2021-05-06 02:17:15 +12:00
|
|
|
ctx.body = {
|
|
|
|
message: "password reset successfully.",
|
|
|
|
}
|
|
|
|
} catch (err) {
|
2023-02-21 21:23:53 +13:00
|
|
|
console.warn(err)
|
|
|
|
// hide any details of the error for security
|
2021-05-06 02:17:15 +12:00
|
|
|
ctx.throw(400, "Cannot reset password.")
|
2021-05-05 23:11:06 +12:00
|
|
|
}
|
2021-04-28 04:29:05 +12:00
|
|
|
}
|
|
|
|
|
2023-02-21 21:23:53 +13:00
|
|
|
// DATASOURCE
|
2021-04-14 00:56:28 +12:00
|
|
|
|
2022-03-18 21:01:31 +13:00
|
|
|
export const datasourcePreAuth = async (ctx: any, next: any) => {
|
2022-01-18 03:52:10 +13:00
|
|
|
const provider = ctx.params.provider
|
2022-11-24 07:25:20 +13:00
|
|
|
const { middleware } = require(`@budibase/backend-core`)
|
2022-01-18 03:52:10 +13:00
|
|
|
const handler = middleware.datasource[provider]
|
2022-01-06 21:08:54 +13:00
|
|
|
|
|
|
|
setCookie(
|
|
|
|
ctx,
|
|
|
|
{
|
2022-01-18 03:52:10 +13:00
|
|
|
provider,
|
2022-01-06 21:08:54 +13:00
|
|
|
appId: ctx.query.appId,
|
|
|
|
datasourceId: ctx.query.datasourceId,
|
|
|
|
},
|
2022-11-17 06:23:12 +13:00
|
|
|
Cookie.DatasourceAuth
|
2022-01-06 21:08:54 +13:00
|
|
|
)
|
|
|
|
|
2022-01-18 03:52:10 +13:00
|
|
|
return handler.preAuth(passport, ctx, next)
|
2022-01-06 21:08:54 +13:00
|
|
|
}
|
|
|
|
|
2022-03-18 21:01:31 +13:00
|
|
|
export const datasourceAuth = async (ctx: any, next: any) => {
|
2022-11-17 06:23:12 +13:00
|
|
|
const authStateCookie = getCookie(ctx, Cookie.DatasourceAuth)
|
2022-01-18 03:52:10 +13:00
|
|
|
const provider = authStateCookie.provider
|
2022-11-24 07:25:20 +13:00
|
|
|
const { middleware } = require(`@budibase/backend-core`)
|
2022-01-18 03:52:10 +13:00
|
|
|
const handler = middleware.datasource[provider]
|
|
|
|
return handler.postAuth(passport, ctx, next)
|
2022-01-06 21:08:54 +13:00
|
|
|
}
|
|
|
|
|
2023-02-21 21:23:53 +13:00
|
|
|
// GOOGLE SSO
|
|
|
|
|
|
|
|
export async function googleCallbackUrl(config?: { callbackURL?: string }) {
|
|
|
|
return ssoCallbackUrl(tenancy.getGlobalDB(), config, ConfigType.GOOGLE)
|
|
|
|
}
|
|
|
|
|
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.
|
|
|
|
*/
|
2022-03-18 21:01:31 +13:00
|
|
|
export const googlePreAuth = async (ctx: any, next: any) => {
|
2022-11-17 07:12:31 +13:00
|
|
|
const db = tenancy.getGlobalDB()
|
2021-08-05 20:59:08 +12:00
|
|
|
|
2022-11-24 07:25:20 +13:00
|
|
|
const config = await dbCore.getScopedConfig(db, {
|
|
|
|
type: ConfigType.GOOGLE,
|
2021-08-05 20:59:08 +12:00
|
|
|
workspace: ctx.query.workspace,
|
2021-04-22 22:45:22 +12:00
|
|
|
})
|
2022-11-24 07:25:20 +13:00
|
|
|
let callbackUrl = await googleCallbackUrl(config)
|
2022-09-24 09:21:51 +12:00
|
|
|
const strategy = await google.strategyFactory(
|
|
|
|
config,
|
|
|
|
callbackUrl,
|
2023-02-21 21:23:53 +13:00
|
|
|
userSdk.save
|
2022-09-24 09:21:51 +12:00
|
|
|
)
|
2021-04-22 08:08:04 +12:00
|
|
|
|
|
|
|
return passport.authenticate(strategy, {
|
2022-07-05 00:37:56 +12:00
|
|
|
scope: ["profile", "email"],
|
2022-07-04 08:13:15 +12:00
|
|
|
accessType: "offline",
|
|
|
|
prompt: "consent",
|
2021-04-22 08:08:04 +12:00
|
|
|
})(ctx, next)
|
|
|
|
}
|
2021-04-22 05:40:32 +12:00
|
|
|
|
2023-02-21 21:23:53 +13:00
|
|
|
export const googleCallback = async (ctx: any, next: any) => {
|
2022-11-17 07:12:31 +13:00
|
|
|
const db = tenancy.getGlobalDB()
|
2021-04-23 00:46:54 +12:00
|
|
|
|
2022-11-24 07:25:20 +13:00
|
|
|
const config = await dbCore.getScopedConfig(db, {
|
|
|
|
type: ConfigType.GOOGLE,
|
2021-08-05 20:59:08 +12:00
|
|
|
workspace: ctx.query.workspace,
|
2021-04-22 22:48:37 +12:00
|
|
|
})
|
2022-11-24 07:25:20 +13:00
|
|
|
const callbackUrl = await googleCallbackUrl(config)
|
2022-09-24 09:21:51 +12:00
|
|
|
const strategy = await google.strategyFactory(
|
|
|
|
config,
|
|
|
|
callbackUrl,
|
2023-02-21 21:23:53 +13:00
|
|
|
userSdk.save
|
2022-09-24 09:21:51 +12:00
|
|
|
)
|
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" },
|
2023-02-21 21:23:53 +13:00
|
|
|
async (err: any, user: SSOUser, info: any) => {
|
|
|
|
await passportCallback(ctx, user, err, info)
|
2022-05-29 08:38:22 +12:00
|
|
|
await context.identity.doInUserContext(user, async () => {
|
|
|
|
await events.auth.login("google-internal")
|
2022-05-24 20:54:36 +12:00
|
|
|
})
|
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
|
|
|
|
2023-02-21 21:23:53 +13:00
|
|
|
// OIDC SSO
|
|
|
|
|
|
|
|
export async function oidcCallbackUrl(config?: { callbackURL?: string }) {
|
|
|
|
return ssoCallbackUrl(tenancy.getGlobalDB(), config, ConfigType.OIDC)
|
|
|
|
}
|
|
|
|
|
2022-06-24 01:29:19 +12:00
|
|
|
export const oidcStrategyFactory = async (ctx: any, configId: any) => {
|
2022-11-17 07:12:31 +13:00
|
|
|
const db = tenancy.getGlobalDB()
|
2022-11-24 07:25:20 +13:00
|
|
|
const config = await dbCore.getScopedConfig(db, {
|
|
|
|
type: ConfigType.OIDC,
|
2021-07-09 00:04:04 +12:00
|
|
|
group: ctx.query.group,
|
|
|
|
})
|
|
|
|
|
2022-03-18 21:01:31 +13:00
|
|
|
const chosenConfig = config.configs.filter((c: any) => c.uuid === configId)[0]
|
2022-11-24 07:25:20 +13:00
|
|
|
let callbackUrl = await oidcCallbackUrl(chosenConfig)
|
2021-07-09 00:04:04 +12:00
|
|
|
|
2022-06-24 01:29:19 +12:00
|
|
|
//Remote Config
|
2022-07-04 08:13:15 +12:00
|
|
|
const enrichedConfig = await oidc.fetchStrategyConfig(
|
2022-06-24 01:29:19 +12:00
|
|
|
chosenConfig,
|
|
|
|
callbackUrl
|
|
|
|
)
|
2023-02-21 21:23:53 +13:00
|
|
|
return oidc.strategyFactory(enrichedConfig, userSdk.save)
|
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.
|
|
|
|
*/
|
2022-03-18 21:01:31 +13:00
|
|
|
export const oidcPreAuth = async (ctx: any, next: any) => {
|
2021-07-16 03:20:31 +12:00
|
|
|
const { configId } = ctx.params
|
|
|
|
const strategy = await oidcStrategyFactory(ctx, configId)
|
|
|
|
|
2022-11-17 06:23:12 +13:00
|
|
|
setCookie(ctx, configId, Cookie.OIDC_CONFIG)
|
2021-06-28 02:46:04 +12:00
|
|
|
|
2022-11-17 07:12:31 +13:00
|
|
|
const db = tenancy.getGlobalDB()
|
2022-11-24 07:25:20 +13:00
|
|
|
const config = await dbCore.getScopedConfig(db, {
|
|
|
|
type: ConfigType.OIDC,
|
2022-08-18 21:59:40 +12:00
|
|
|
group: ctx.query.group,
|
|
|
|
})
|
|
|
|
|
|
|
|
const chosenConfig = config.configs.filter((c: any) => c.uuid === configId)[0]
|
|
|
|
|
|
|
|
let authScopes =
|
|
|
|
chosenConfig.scopes?.length > 0
|
|
|
|
? chosenConfig.scopes
|
|
|
|
: ["profile", "email", "offline_access"]
|
|
|
|
|
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
|
2022-08-18 21:59:40 +12:00
|
|
|
scope: authScopes,
|
2021-06-28 02:46:04 +12:00
|
|
|
})(ctx, next)
|
|
|
|
}
|
|
|
|
|
2023-02-21 21:23:53 +13:00
|
|
|
export const oidcCallback = async (ctx: any, next: any) => {
|
2022-11-17 06:23:12 +13:00
|
|
|
const configId = getCookie(ctx, Cookie.OIDC_CONFIG)
|
2021-07-16 03:20:31 +12:00
|
|
|
const strategy = await oidcStrategyFactory(ctx, configId)
|
2021-06-28 02:46:04 +12:00
|
|
|
|
|
|
|
return passport.authenticate(
|
|
|
|
strategy,
|
|
|
|
{ successRedirect: "/", failureRedirect: "/error" },
|
2023-02-21 21:23:53 +13:00
|
|
|
async (err: any, user: SSOUser, info: any) => {
|
|
|
|
await passportCallback(ctx, user, err, info)
|
2022-05-29 08:38:22 +12:00
|
|
|
await context.identity.doInUserContext(user, async () => {
|
2022-05-24 20:54:36 +12:00
|
|
|
await events.auth.login("oidc")
|
|
|
|
})
|
2021-04-21 23:12:22 +12:00
|
|
|
ctx.redirect("/")
|
|
|
|
}
|
|
|
|
)(ctx, next)
|
2021-04-11 22:35:55 +12:00
|
|
|
}
|