Store OIDC config in cookie instead of URL
This commit is contained in:
parent
239e39e5ed
commit
33b352c3ef
|
@ -6,6 +6,7 @@ exports.UserStatus = {
|
||||||
exports.Cookies = {
|
exports.Cookies = {
|
||||||
CurrentApp: "budibase:currentapp",
|
CurrentApp: "budibase:currentapp",
|
||||||
Auth: "budibase:auth",
|
Auth: "budibase:auth",
|
||||||
|
OIDC_CONFIG: "budibase:oidc:config",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.GlobalRoles = {
|
exports.GlobalRoles = {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import Auth0Logo from "assets/auth0-logo.png"
|
import Auth0Logo from "assets/auth0-logo.png"
|
||||||
import MicrosoftLogo from "assets/microsoft-logo.png"
|
import MicrosoftLogo from "assets/microsoft-logo.png"
|
||||||
|
|
||||||
import { admin, oidc } from "stores/portal"
|
import { oidc } from "stores/portal"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
let show = false
|
let show = false
|
||||||
|
@ -27,7 +27,8 @@
|
||||||
|
|
||||||
{#if show}
|
{#if show}
|
||||||
<ActionButton
|
<ActionButton
|
||||||
on:click={() => window.open(`/api/admin/auth/oidc/${$oidc.uuid}`, "_blank")}
|
on:click={() =>
|
||||||
|
window.open(`/api/admin/auth/oidc/configs/${$oidc.uuid}`, "_blank")}
|
||||||
>
|
>
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<img {src} alt="oidc icon" />
|
<img {src} alt="oidc icon" />
|
||||||
|
|
|
@ -79,10 +79,6 @@
|
||||||
providers.oidc?.config?.configs[0].clientID &&
|
providers.oidc?.config?.configs[0].clientID &&
|
||||||
providers.oidc?.config?.configs[0].clientSecret
|
providers.oidc?.config?.configs[0].clientSecret
|
||||||
|
|
||||||
$: oidcCallback = providers.oidc?.config.configs[0].uuid
|
|
||||||
? `/api/admin/auth/oidc/callback/${providers.oidc?.config.configs[0].uuid}`
|
|
||||||
: ""
|
|
||||||
|
|
||||||
async function uploadLogo(file) {
|
async function uploadLogo(file) {
|
||||||
let data = new FormData()
|
let data = new FormData()
|
||||||
data.append("file", file)
|
data.append("file", file)
|
||||||
|
@ -240,7 +236,7 @@
|
||||||
{/each}
|
{/each}
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<Label size="L">Callback URL</Label>
|
<Label size="L">Callback URL</Label>
|
||||||
<Input readonly bind:value={oidcCallback} />
|
<Input readonly placeholder="/api/admin/auth/oidc/callback" />
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
|
|
|
@ -4,7 +4,8 @@ const { oidc } = require("@budibase/auth/src/middleware")
|
||||||
const { Configs, EmailTemplatePurpose } = require("../../../constants")
|
const { Configs, EmailTemplatePurpose } = require("../../../constants")
|
||||||
const CouchDB = require("../../../db")
|
const CouchDB = require("../../../db")
|
||||||
const { sendEmail, isEmailConfigured } = require("../../../utilities/email")
|
const { sendEmail, isEmailConfigured } = require("../../../utilities/email")
|
||||||
const { clearCookie, getGlobalUserByEmail, hash } = authPkg.utils
|
const { setCookie, getCookie, clearCookie, getGlobalUserByEmail, hash } =
|
||||||
|
authPkg.utils
|
||||||
const { Cookies } = authPkg.constants
|
const { Cookies } = authPkg.constants
|
||||||
const { passport } = authPkg.auth
|
const { passport } = authPkg.auth
|
||||||
const { checkResetPasswordCode } = require("../../../utilities/redis")
|
const { checkResetPasswordCode } = require("../../../utilities/redis")
|
||||||
|
@ -133,9 +134,7 @@ exports.googleAuth = async (ctx, next) => {
|
||||||
)(ctx, next)
|
)(ctx, next)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function oidcStrategyFactory(ctx) {
|
async function oidcStrategyFactory(ctx, configId) {
|
||||||
const { configId } = ctx.params
|
|
||||||
|
|
||||||
const db = new CouchDB(GLOBAL_DB)
|
const db = new CouchDB(GLOBAL_DB)
|
||||||
|
|
||||||
const config = await authPkg.db.getScopedConfig(db, {
|
const config = await authPkg.db.getScopedConfig(db, {
|
||||||
|
@ -145,7 +144,7 @@ async function oidcStrategyFactory(ctx) {
|
||||||
|
|
||||||
const chosenConfig = config.configs.filter(c => c.uuid === configId)[0]
|
const chosenConfig = config.configs.filter(c => c.uuid === configId)[0]
|
||||||
|
|
||||||
const callbackUrl = `${ctx.protocol}://${ctx.host}/api/admin/auth/oidc/callback/${configId}`
|
const callbackUrl = `${ctx.protocol}://${ctx.host}/api/admin/auth/oidc/callback`
|
||||||
|
|
||||||
return oidc.strategyFactory(chosenConfig, callbackUrl)
|
return oidc.strategyFactory(chosenConfig, callbackUrl)
|
||||||
}
|
}
|
||||||
|
@ -155,7 +154,10 @@ async function oidcStrategyFactory(ctx) {
|
||||||
* On a successful login, you will be redirected to the oidcAuth callback route.
|
* On a successful login, you will be redirected to the oidcAuth callback route.
|
||||||
*/
|
*/
|
||||||
exports.oidcPreAuth = async (ctx, next) => {
|
exports.oidcPreAuth = async (ctx, next) => {
|
||||||
const strategy = await oidcStrategyFactory(ctx)
|
const { configId } = ctx.params
|
||||||
|
const strategy = await oidcStrategyFactory(ctx, configId)
|
||||||
|
|
||||||
|
setCookie(ctx, configId, Cookies.OIDC_CONFIG)
|
||||||
|
|
||||||
return passport.authenticate(strategy, {
|
return passport.authenticate(strategy, {
|
||||||
// required 'openid' scope is added by oidc strategy factory
|
// required 'openid' scope is added by oidc strategy factory
|
||||||
|
@ -164,7 +166,8 @@ exports.oidcPreAuth = async (ctx, next) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.oidcAuth = async (ctx, next) => {
|
exports.oidcAuth = async (ctx, next) => {
|
||||||
const strategy = await oidcStrategyFactory(ctx)
|
const configId = getCookie(ctx, Cookies.OIDC_CONFIG)
|
||||||
|
const strategy = await oidcStrategyFactory(ctx, configId)
|
||||||
|
|
||||||
return passport.authenticate(
|
return passport.authenticate(
|
||||||
strategy,
|
strategy,
|
||||||
|
|
|
@ -39,7 +39,7 @@ router
|
||||||
.post("/api/admin/auth/logout", authController.logout)
|
.post("/api/admin/auth/logout", authController.logout)
|
||||||
.get("/api/admin/auth/google", authController.googlePreAuth)
|
.get("/api/admin/auth/google", authController.googlePreAuth)
|
||||||
.get("/api/admin/auth/google/callback", authController.googleAuth)
|
.get("/api/admin/auth/google/callback", authController.googleAuth)
|
||||||
.get("/api/admin/auth/oidc/:configId", authController.oidcPreAuth)
|
.get("/api/admin/auth/oidc/configs/:configId", authController.oidcPreAuth)
|
||||||
.get("/api/admin/auth/oidc/callback/:configId", authController.oidcAuth)
|
.get("/api/admin/auth/oidc/callback", authController.oidcAuth)
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const setup = require("./utilities")
|
const setup = require("./utilities")
|
||||||
|
const { Cookies } = require("@budibase/auth").constants
|
||||||
|
|
||||||
jest.mock("nodemailer")
|
jest.mock("nodemailer")
|
||||||
const sendMailMock = setup.emailMock()
|
const sendMailMock = setup.emailMock()
|
||||||
|
@ -74,13 +75,13 @@ describe("/api/admin/auth", () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
expect(strategyFactory).toBeCalledWith(
|
expect(strategyFactory).toBeCalledWith(
|
||||||
chosenConfig,
|
chosenConfig,
|
||||||
`http://127.0.0.1:4003/api/admin/auth/oidc/callback/${configId}` // calculated url
|
`http://127.0.0.1:4003/api/admin/auth/oidc/callback` // calculated url
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("/api/admin/auth/oidc", () => {
|
describe("/api/admin/auth/oidc/configs", () => {
|
||||||
it("should load strategy and delegate to passport", async () => {
|
it("should load strategy and delegate to passport", async () => {
|
||||||
await request.get(`/api/admin/auth/oidc/${configId}`)
|
await request.get(`/api/admin/auth/oidc/configs/${configId}`)
|
||||||
|
|
||||||
expect(passportSpy).toBeCalledWith(mockStrategyReturn, {
|
expect(passportSpy).toBeCalledWith(mockStrategyReturn, {
|
||||||
scope: ["profile", "email"],
|
scope: ["profile", "email"],
|
||||||
|
@ -91,7 +92,8 @@ describe("/api/admin/auth", () => {
|
||||||
|
|
||||||
describe("/api/admin/auth/oidc/callback", () => {
|
describe("/api/admin/auth/oidc/callback", () => {
|
||||||
it("should load strategy and delegate to passport", async () => {
|
it("should load strategy and delegate to passport", async () => {
|
||||||
await request.get(`/api/admin/auth/oidc/callback/${configId}`)
|
await request.get(`/api/admin/auth/oidc/callback`)
|
||||||
|
.set(config.getOIDConfigCookie(configId))
|
||||||
|
|
||||||
expect(passportSpy).toBeCalledWith(mockStrategyReturn, {
|
expect(passportSpy).toBeCalledWith(mockStrategyReturn, {
|
||||||
successRedirect: "/", failureRedirect: "/error"
|
successRedirect: "/", failureRedirect: "/error"
|
||||||
|
|
|
@ -68,6 +68,12 @@ class TestConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cookieHeader(cookies) {
|
||||||
|
return {
|
||||||
|
Cookie: [cookies],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
defaultHeaders() {
|
defaultHeaders() {
|
||||||
const user = {
|
const user = {
|
||||||
_id: "us_uuid1",
|
_id: "us_uuid1",
|
||||||
|
@ -77,7 +83,7 @@ class TestConfiguration {
|
||||||
const authToken = jwt.sign(user, env.JWT_SECRET)
|
const authToken = jwt.sign(user, env.JWT_SECRET)
|
||||||
return {
|
return {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
Cookie: [`${Cookies.Auth}=${authToken}`],
|
...this.cookieHeader([`${Cookies.Auth}=${authToken}`]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,6 +162,11 @@ class TestConfiguration {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOIDConfigCookie(configId) {
|
||||||
|
const token = jwt.sign(configId, env.JWT_SECRET)
|
||||||
|
return this.cookieHeader([[`${Cookies.OIDC_CONFIG}=${token}`]])
|
||||||
|
}
|
||||||
|
|
||||||
async saveOIDCConfig() {
|
async saveOIDCConfig() {
|
||||||
await this.deleteConfig(Configs.OIDC)
|
await this.deleteConfig(Configs.OIDC)
|
||||||
const config = {
|
const config = {
|
||||||
|
|
Loading…
Reference in New Issue