2022-11-28 18:54:04 +01:00
|
|
|
const _passport = require("koa-passport")
|
2022-01-12 12:32:14 +01:00
|
|
|
const LocalStrategy = require("passport-local").Strategy
|
|
|
|
const JwtStrategy = require("passport-jwt").Strategy
|
2023-02-21 09:23:53 +01:00
|
|
|
import { getGlobalDB } from "../context"
|
2022-06-23 15:29:19 +02:00
|
|
|
const refresh = require("passport-oauth2-refresh")
|
2023-02-21 09:23:53 +01:00
|
|
|
import { Config, Cookie } from "../constants"
|
2022-11-28 18:54:04 +01:00
|
|
|
import { getScopedConfig } from "../db"
|
2023-02-21 09:23:53 +01:00
|
|
|
import { getSessionsForUser, invalidateSessions } from "../security/sessions"
|
2022-08-25 20:41:47 +02:00
|
|
|
import {
|
2022-11-28 18:54:04 +01:00
|
|
|
jwt as jwtPassport,
|
2022-01-12 12:32:14 +01:00
|
|
|
local,
|
|
|
|
authenticated,
|
|
|
|
tenancy,
|
2022-01-25 23:54:50 +01:00
|
|
|
csrf,
|
2022-11-28 18:54:04 +01:00
|
|
|
oidc,
|
|
|
|
google,
|
|
|
|
} from "../middleware"
|
|
|
|
import { invalidateUser } from "../cache/user"
|
2023-02-21 09:23:53 +01:00
|
|
|
import { PlatformLogoutOpts, User } from "@budibase/types"
|
2022-11-28 18:54:04 +01:00
|
|
|
import { logAlert } from "../logging"
|
2023-02-21 09:23:53 +01:00
|
|
|
import * as events from "../events"
|
|
|
|
import * as userCache from "../cache/user"
|
|
|
|
import { clearCookie, getCookie } from "../utils"
|
2022-11-28 18:54:04 +01:00
|
|
|
export {
|
|
|
|
auditLog,
|
|
|
|
authError,
|
|
|
|
internalApi,
|
|
|
|
ssoCallbackUrl,
|
|
|
|
adminOnly,
|
|
|
|
builderOnly,
|
|
|
|
builderOrAdmin,
|
|
|
|
joiValidator,
|
|
|
|
google,
|
|
|
|
oidc,
|
|
|
|
} from "../middleware"
|
2023-02-21 09:23:53 +01:00
|
|
|
import { ssoSaveUserNoOp } from "../middleware/passport/sso/sso"
|
2022-11-28 18:54:04 +01:00
|
|
|
export const buildAuthMiddleware = authenticated
|
|
|
|
export const buildTenancyMiddleware = tenancy
|
|
|
|
export const buildCsrfMiddleware = csrf
|
|
|
|
export const passport = _passport
|
|
|
|
export const jwt = require("jsonwebtoken")
|
2022-07-06 12:51:48 +02:00
|
|
|
|
2022-01-12 12:32:14 +01:00
|
|
|
// Strategies
|
2022-11-28 18:54:04 +01:00
|
|
|
_passport.use(new LocalStrategy(local.options, local.authenticate))
|
|
|
|
if (jwtPassport.options.secretOrKey) {
|
|
|
|
_passport.use(new JwtStrategy(jwtPassport.options, jwtPassport.authenticate))
|
2022-11-01 13:59:10 +01:00
|
|
|
} else {
|
|
|
|
logAlert("No JWT Secret supplied, cannot configure JWT strategy")
|
|
|
|
}
|
2022-01-12 12:32:14 +01:00
|
|
|
|
2022-11-28 18:54:04 +01:00
|
|
|
_passport.serializeUser((user: User, done: any) => done(null, user))
|
2022-01-12 12:32:14 +01:00
|
|
|
|
2022-11-28 18:54:04 +01:00
|
|
|
_passport.deserializeUser(async (user: User, done: any) => {
|
2022-01-12 12:32:14 +01:00
|
|
|
const db = getGlobalDB()
|
|
|
|
|
|
|
|
try {
|
2022-08-25 20:41:47 +02:00
|
|
|
const dbUser = await db.get(user._id)
|
|
|
|
return done(null, dbUser)
|
2022-01-12 12:32:14 +01:00
|
|
|
} catch (err) {
|
2022-05-23 17:24:29 +02:00
|
|
|
console.error(`User not found`, err)
|
2022-01-12 12:32:14 +01:00
|
|
|
return done(null, false, { message: "User not found" })
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2022-08-25 20:41:47 +02:00
|
|
|
async function refreshOIDCAccessToken(
|
|
|
|
db: any,
|
|
|
|
chosenConfig: any,
|
|
|
|
refreshToken: string
|
|
|
|
) {
|
2022-07-03 22:13:15 +02:00
|
|
|
const callbackUrl = await oidc.getCallbackUrl(db, chosenConfig)
|
2022-08-25 20:41:47 +02:00
|
|
|
let enrichedConfig: any
|
|
|
|
let strategy: any
|
2022-06-23 15:29:19 +02:00
|
|
|
|
2022-07-03 22:13:15 +02:00
|
|
|
try {
|
|
|
|
enrichedConfig = await oidc.fetchStrategyConfig(chosenConfig, callbackUrl)
|
|
|
|
if (!enrichedConfig) {
|
|
|
|
throw new Error("OIDC Config contents invalid")
|
|
|
|
}
|
2023-02-21 09:23:53 +01:00
|
|
|
strategy = await oidc.strategyFactory(enrichedConfig, ssoSaveUserNoOp)
|
2022-07-03 22:13:15 +02:00
|
|
|
} catch (err) {
|
|
|
|
console.error(err)
|
|
|
|
throw new Error("Could not refresh OAuth Token")
|
|
|
|
}
|
2022-06-23 15:29:19 +02:00
|
|
|
|
2022-07-03 22:13:15 +02:00
|
|
|
refresh.use(strategy, {
|
|
|
|
setRefreshOAuth2() {
|
|
|
|
return strategy._getOAuth2Client(enrichedConfig)
|
|
|
|
},
|
|
|
|
})
|
2022-06-23 15:29:19 +02:00
|
|
|
|
2022-07-03 22:13:15 +02:00
|
|
|
return new Promise(resolve => {
|
|
|
|
refresh.requestNewAccessToken(
|
2022-11-16 18:23:12 +01:00
|
|
|
Config.OIDC,
|
2022-07-03 22:13:15 +02:00
|
|
|
refreshToken,
|
2022-08-25 20:41:47 +02:00
|
|
|
(err: any, accessToken: string, refreshToken: any, params: any) => {
|
2022-07-03 22:13:15 +02:00
|
|
|
resolve({ err, accessToken, refreshToken, params })
|
|
|
|
}
|
|
|
|
)
|
2022-06-23 15:29:19 +02:00
|
|
|
})
|
2022-07-03 22:13:15 +02:00
|
|
|
}
|
2022-06-23 15:29:19 +02:00
|
|
|
|
2022-08-25 20:41:47 +02:00
|
|
|
async function refreshGoogleAccessToken(
|
|
|
|
db: any,
|
|
|
|
config: any,
|
|
|
|
refreshToken: any
|
|
|
|
) {
|
2022-07-03 22:13:15 +02:00
|
|
|
let callbackUrl = await google.getCallbackUrl(db, config)
|
|
|
|
|
|
|
|
let strategy
|
2022-06-23 15:29:19 +02:00
|
|
|
try {
|
2023-02-21 09:23:53 +01:00
|
|
|
strategy = await google.strategyFactory(
|
|
|
|
config,
|
|
|
|
callbackUrl,
|
|
|
|
ssoSaveUserNoOp
|
|
|
|
)
|
2022-08-25 20:41:47 +02:00
|
|
|
} catch (err: any) {
|
2022-06-23 15:29:19 +02:00
|
|
|
console.error(err)
|
2022-08-25 20:41:47 +02:00
|
|
|
throw new Error(
|
|
|
|
`Error constructing OIDC refresh strategy: message=${err.message}`
|
|
|
|
)
|
2022-06-23 15:29:19 +02:00
|
|
|
}
|
|
|
|
|
2022-07-03 22:13:15 +02:00
|
|
|
refresh.use(strategy)
|
|
|
|
|
|
|
|
return new Promise(resolve => {
|
|
|
|
refresh.requestNewAccessToken(
|
2022-11-16 18:23:12 +01:00
|
|
|
Config.GOOGLE,
|
2022-07-03 22:13:15 +02:00
|
|
|
refreshToken,
|
2022-08-25 20:41:47 +02:00
|
|
|
(err: any, accessToken: string, refreshToken: string, params: any) => {
|
2022-07-03 22:13:15 +02:00
|
|
|
resolve({ err, accessToken, refreshToken, params })
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-11-28 18:54:04 +01:00
|
|
|
export async function refreshOAuthToken(
|
2022-08-25 20:41:47 +02:00
|
|
|
refreshToken: string,
|
|
|
|
configType: string,
|
|
|
|
configId: string
|
|
|
|
) {
|
2022-07-03 22:13:15 +02:00
|
|
|
const db = getGlobalDB()
|
|
|
|
|
|
|
|
const config = await getScopedConfig(db, {
|
|
|
|
type: configType,
|
|
|
|
group: {},
|
|
|
|
})
|
|
|
|
|
|
|
|
let chosenConfig = {}
|
|
|
|
let refreshResponse
|
2022-11-16 18:23:12 +01:00
|
|
|
if (configType === Config.OIDC) {
|
2022-07-03 22:13:15 +02:00
|
|
|
// configId - retrieved from cookie.
|
2022-08-25 20:41:47 +02:00
|
|
|
chosenConfig = config.configs.filter((c: any) => c.uuid === configId)[0]
|
2022-07-03 22:13:15 +02:00
|
|
|
if (!chosenConfig) {
|
|
|
|
throw new Error("Invalid OIDC configuration")
|
|
|
|
}
|
|
|
|
refreshResponse = await refreshOIDCAccessToken(
|
|
|
|
db,
|
|
|
|
chosenConfig,
|
|
|
|
refreshToken
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
chosenConfig = config
|
|
|
|
refreshResponse = await refreshGoogleAccessToken(
|
|
|
|
db,
|
|
|
|
chosenConfig,
|
|
|
|
refreshToken
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return refreshResponse
|
|
|
|
}
|
|
|
|
|
2023-02-21 09:23:53 +01:00
|
|
|
// TODO: Refactor to use user save function instead to prevent the need for
|
|
|
|
// manually saving and invalidating on callback
|
2022-11-28 18:54:04 +01:00
|
|
|
export async function updateUserOAuth(userId: string, oAuthConfig: any) {
|
2022-07-03 23:14:18 +02:00
|
|
|
const details = {
|
|
|
|
accessToken: oAuthConfig.accessToken,
|
|
|
|
refreshToken: oAuthConfig.refreshToken,
|
|
|
|
}
|
|
|
|
|
2022-07-03 22:13:15 +02:00
|
|
|
try {
|
|
|
|
const db = getGlobalDB()
|
2022-07-03 23:14:18 +02:00
|
|
|
const dbUser = await db.get(userId)
|
2022-07-03 22:13:15 +02:00
|
|
|
|
|
|
|
//Do not overwrite the refresh token if a valid one is not provided.
|
|
|
|
if (typeof details.refreshToken !== "string") {
|
|
|
|
delete details.refreshToken
|
|
|
|
}
|
|
|
|
|
2022-07-03 23:14:18 +02:00
|
|
|
dbUser.oauth2 = {
|
|
|
|
...dbUser.oauth2,
|
2022-07-03 22:13:15 +02:00
|
|
|
...details,
|
|
|
|
}
|
|
|
|
|
|
|
|
await db.put(dbUser)
|
2022-07-06 12:51:48 +02:00
|
|
|
|
|
|
|
await invalidateUser(userId)
|
2022-07-03 22:13:15 +02:00
|
|
|
} catch (e) {
|
|
|
|
console.error("Could not update OAuth details for current user", e)
|
|
|
|
}
|
2022-06-23 15:29:19 +02:00
|
|
|
}
|
2023-02-21 09:23:53 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Logs a user out from budibase. Re-used across account portal and builder.
|
|
|
|
*/
|
|
|
|
export async function platformLogout(opts: PlatformLogoutOpts) {
|
|
|
|
const ctx = opts.ctx
|
|
|
|
const userId = opts.userId
|
|
|
|
const keepActiveSession = opts.keepActiveSession
|
|
|
|
|
|
|
|
if (!ctx) throw new Error("Koa context must be supplied to logout.")
|
|
|
|
|
|
|
|
const currentSession = getCookie(ctx, Cookie.Auth)
|
|
|
|
let sessions = await getSessionsForUser(userId)
|
|
|
|
|
|
|
|
if (keepActiveSession) {
|
|
|
|
sessions = sessions.filter(
|
|
|
|
session => session.sessionId !== currentSession.sessionId
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
// clear cookies
|
|
|
|
clearCookie(ctx, Cookie.Auth)
|
|
|
|
clearCookie(ctx, Cookie.CurrentApp)
|
|
|
|
}
|
|
|
|
|
|
|
|
const sessionIds = sessions.map(({ sessionId }) => sessionId)
|
|
|
|
await invalidateSessions(userId, { sessionIds, reason: "logout" })
|
|
|
|
await events.auth.logout()
|
|
|
|
await userCache.invalidateUser(userId)
|
|
|
|
}
|