1
0
Fork 0
mirror of synced 2024-05-02 19:42:42 +12:00
budibase/packages/worker/src/api/controllers/global/auth.ts
2022-06-23 14:29:19 +01:00

277 lines
8.3 KiB
TypeScript

const core = require("@budibase/backend-core")
const { getScopedConfig } = require("@budibase/backend-core/db")
const { google } = require("@budibase/backend-core/middleware")
const { oidc } = require("@budibase/backend-core/middleware")
const { Configs, EmailTemplatePurpose } = require("../../../constants")
const { sendEmail, isEmailConfigured } = require("../../../utilities/email")
const { setCookie, getCookie, clearCookie, hash, platformLogout } = core.utils
const { Cookies, Headers } = core.constants
const { passport } = core.auth
const { checkResetPasswordCode } = require("../../../utilities/redis")
const {
getGlobalDB,
getTenantId,
isMultiTenant,
} = require("@budibase/backend-core/tenancy")
const env = require("../../../environment")
import { events, users as usersCore, context } from "@budibase/backend-core"
import { users } from "../../../sdk"
import { User } from "@budibase/types"
const ssoCallbackUrl = async (config: any, type: any) => {
// incase there is a callback URL from before
if (config && config.callbackURL) {
return config.callbackURL
}
const db = getGlobalDB()
const publicConfig = await getScopedConfig(db, {
type: Configs.SETTINGS,
})
let callbackUrl = `/api/global/auth`
if (isMultiTenant()) {
callbackUrl += `/${getTenantId()}`
}
callbackUrl += `/${type}/callback`
return `${publicConfig.platformUrl}${callbackUrl}`
}
export const googleCallbackUrl = async (config: any) => {
return ssoCallbackUrl(config, "google")
}
export const oidcCallbackUrl = async (config: any) => {
return ssoCallbackUrl(config, "oidc")
}
async function authInternal(ctx: any, user: any, err = null, info = null) {
if (err) {
console.error("Authentication error", err)
return ctx.throw(403, info ? info : "Unauthorized")
}
if (!user) {
return ctx.throw(403, info ? info : "Unauthorized")
}
// set a cookie for browser access
setCookie(ctx, user.token, Cookies.Auth, { sign: false })
// set the token in a header as well for APIs
ctx.set(Headers.TOKEN, user.token)
// get rid of any app cookies on login
// have to check test because this breaks cypress
if (!env.isTest()) {
clearCookie(ctx, Cookies.CurrentApp)
}
}
export const authenticate = async (ctx: any, next: any) => {
return passport.authenticate(
"local",
async (err: any, user: User, info: any) => {
await authInternal(ctx, user, err, info)
await context.identity.doInUserContext(user, async () => {
await events.auth.login("local")
})
ctx.status = 200
}
)(ctx, next)
}
export const setInitInfo = (ctx: any) => {
const initInfo = ctx.request.body
setCookie(ctx, initInfo, Cookies.Init)
ctx.status = 200
}
export const getInitInfo = (ctx: any) => {
try {
ctx.body = getCookie(ctx, Cookies.Init) || {}
} catch (err) {
clearCookie(ctx, Cookies.Init)
ctx.body = {}
}
}
/**
* Reset the user password, used as part of a forgotten password flow.
*/
export const reset = async (ctx: any) => {
const { email } = ctx.request.body
const configured = await isEmailConfigured()
if (!configured) {
ctx.throw(
400,
"Please contact your platform administrator, SMTP is not configured."
)
}
try {
const user = (await usersCore.getGlobalUserByEmail(email)) as User
// only if user exists, don't error though if they don't
if (user) {
await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, {
user,
subject: "{{ company }} platform password reset",
})
await events.user.passwordResetRequested(user)
}
} catch (err) {
console.log(err)
// don't throw any kind of error to the user, this might give away something
}
ctx.body = {
message: "Please check your email for a reset link.",
}
}
/**
* Perform the user password update if the provided reset code is valid.
*/
export const resetUpdate = async (ctx: any) => {
const { resetCode, password } = ctx.request.body
try {
const { userId } = await checkResetPasswordCode(resetCode)
const db = getGlobalDB()
const user = await db.get(userId)
user.password = await hash(password)
await db.put(user)
ctx.body = {
message: "password reset successfully.",
}
// remove password from the user before sending events
delete user.password
await events.user.passwordReset(user)
} catch (err) {
console.error(err)
ctx.throw(400, "Cannot reset password.")
}
}
export const logout = async (ctx: any) => {
if (ctx.user && ctx.user._id) {
await platformLogout({ ctx, userId: ctx.user._id })
}
ctx.body = { message: "User logged out." }
}
export const datasourcePreAuth = async (ctx: any, next: any) => {
const provider = ctx.params.provider
const middleware = require(`@budibase/backend-core/middleware`)
const handler = middleware.datasource[provider]
setCookie(
ctx,
{
provider,
appId: ctx.query.appId,
datasourceId: ctx.query.datasourceId,
},
Cookies.DatasourceAuth
)
return handler.preAuth(passport, ctx, next)
}
export const datasourceAuth = async (ctx: any, next: any) => {
const authStateCookie = getCookie(ctx, Cookies.DatasourceAuth)
const provider = authStateCookie.provider
const middleware = require(`@budibase/backend-core/middleware`)
const handler = middleware.datasource[provider]
return handler.postAuth(passport, ctx, next)
}
/**
* 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.
*/
export const googlePreAuth = async (ctx: any, next: any) => {
const db = getGlobalDB()
const config = await core.db.getScopedConfig(db, {
type: Configs.GOOGLE,
workspace: ctx.query.workspace,
})
let callbackUrl = await exports.googleCallbackUrl(config)
const strategy = await google.strategyFactory(config, callbackUrl, users.save)
return passport.authenticate(strategy, {
scope: ["profile", "email"],
})(ctx, next)
}
export const googleAuth = async (ctx: any, next: any) => {
const db = getGlobalDB()
const config = await core.db.getScopedConfig(db, {
type: Configs.GOOGLE,
workspace: ctx.query.workspace,
})
const callbackUrl = await exports.googleCallbackUrl(config)
const strategy = await google.strategyFactory(config, callbackUrl, users.save)
return passport.authenticate(
strategy,
{ successRedirect: "/", failureRedirect: "/error" },
async (err: any, user: User, info: any) => {
await authInternal(ctx, user, err, info)
await context.identity.doInUserContext(user, async () => {
await events.auth.login("google-internal")
})
ctx.redirect("/")
}
)(ctx, next)
}
export const oidcStrategyFactory = async (ctx: any, configId: any) => {
const db = getGlobalDB()
const config = await core.db.getScopedConfig(db, {
type: Configs.OIDC,
group: ctx.query.group,
})
const chosenConfig = config.configs.filter((c: any) => c.uuid === configId)[0]
let callbackUrl = await exports.oidcCallbackUrl(chosenConfig)
//Remote Config
const enrichedConfig = await oidc.fetchOIDCStrategyConfig(
chosenConfig,
callbackUrl
)
return oidc.strategyFactory(enrichedConfig, users.save)
}
/**
* 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.
*/
export const oidcPreAuth = async (ctx: any, next: any) => {
const { configId } = ctx.params
const strategy = await oidcStrategyFactory(ctx, configId)
setCookie(ctx, configId, Cookies.OIDC_CONFIG)
return passport.authenticate(strategy, {
// required 'openid' scope is added by oidc strategy factory
scope: ["profile", "email", "offline_access"], //auth0 offline_access scope required for the refresh token behaviour.
})(ctx, next)
}
export const oidcAuth = async (ctx: any, next: any) => {
const configId = getCookie(ctx, Cookies.OIDC_CONFIG)
const strategy = await oidcStrategyFactory(ctx, configId)
return passport.authenticate(
strategy,
{ successRedirect: "/", failureRedirect: "/error" },
async (err: any, user: any, info: any) => {
await authInternal(ctx, user, err, info)
await context.identity.doInUserContext(user, async () => {
await events.auth.login("oidc")
})
ctx.redirect("/")
}
)(ctx, next)
}