diff --git a/packages/auth/package.json b/packages/auth/package.json
index 650d41b633..9155d37eeb 100644
--- a/packages/auth/package.json
+++ b/packages/auth/package.json
@@ -13,6 +13,7 @@
"koa-passport": "^4.1.4",
"lodash": "^4.17.21",
"node-fetch": "^2.6.1",
+ "@techpass/passport-openidconnect": "^0.3.0",
"passport-google-auth": "^1.0.2",
"passport-google-oauth": "^2.0.0",
"passport-jwt": "^4.0.0",
diff --git a/packages/auth/src/index.js b/packages/auth/src/index.js
index 9582d6ffd6..cb4cb8d550 100644
--- a/packages/auth/src/index.js
+++ b/packages/auth/src/index.js
@@ -2,7 +2,7 @@ const passport = require("koa-passport")
const LocalStrategy = require("passport-local").Strategy
const JwtStrategy = require("passport-jwt").Strategy
const { StaticDatabases } = require("./db/utils")
-const { jwt, local, authenticated, google, auditLog } = require("./middleware")
+const { jwt, local, authenticated, google, oidc, auditLog } = require("./middleware")
const { setDB, getDB } = require("./db")
// Strategies
@@ -44,6 +44,7 @@ module.exports = {
buildAuthMiddleware: authenticated,
passport,
google,
+ oidc,
jwt: require("jsonwebtoken"),
auditLog,
},
diff --git a/packages/auth/src/middleware/index.js b/packages/auth/src/middleware/index.js
index 2a249ce0f9..35c7d9c388 100644
--- a/packages/auth/src/middleware/index.js
+++ b/packages/auth/src/middleware/index.js
@@ -1,11 +1,13 @@
const jwt = require("./passport/jwt")
const local = require("./passport/local")
const google = require("./passport/google")
+const oidc = require("./passport/oidc")
const authenticated = require("./authenticated")
const auditLog = require("./auditLog")
module.exports = {
google,
+ oidc,
jwt,
local,
authenticated,
diff --git a/packages/auth/src/middleware/passport/oidc.js b/packages/auth/src/middleware/passport/oidc.js
new file mode 100644
index 0000000000..09c7e2a05e
--- /dev/null
+++ b/packages/auth/src/middleware/passport/oidc.js
@@ -0,0 +1,111 @@
+const env = require("../../environment")
+const jwt = require("jsonwebtoken")
+const database = require("../../db")
+const OIDCStrategy = require("@techpass/passport-openidconnect").Strategy
+const {
+ StaticDatabases,
+ generateGlobalUserID,
+ ViewNames,
+} = require("../../db/utils")
+
+// async function authenticate(token, tokenSecret, profile, done) {
+async function authenticate(issuer, sub, profile, jwtClaims, accessToken, refreshToken, idToken, params, done) {
+ // Check the user exists in the instance DB by email
+ const db = database.getDB(StaticDatabases.GLOBAL.name)
+
+ let dbUser
+
+ const userId = generateGlobalUserID(profile.id)
+
+ try {
+ // use the google profile id
+ dbUser = await db.get(userId)
+ } catch (err) {
+ const user = {
+ _id: userId,
+ provider: profile.provider,
+ roles: {},
+ ...profile._json,
+ }
+
+ // check if an account with the google email address exists locally
+ const users = await db.query(`database/${ViewNames.USER_BY_EMAIL}`, {
+ key: profile._json.email,
+ include_docs: true,
+ })
+
+ // Google user already exists by email
+ if (users.rows.length > 0) {
+ const existing = users.rows[0].doc
+
+ // remove the local account to avoid conflicts
+ await db.remove(existing._id, existing._rev)
+
+ // merge with existing account
+ user.roles = existing.roles
+ user.builder = existing.builder
+ user.admin = existing.admin
+
+ const response = await db.post(user)
+ dbUser = user
+ dbUser._rev = response.rev
+ } else {
+ return done(
+ new Error(
+ "email does not yet exist. You must set up your local budibase account first."
+ ),
+ false
+ )
+ }
+ }
+
+ // authenticate
+ const payload = {
+ userId: dbUser._id,
+ builder: dbUser.builder,
+ email: dbUser.email,
+ }
+
+ dbUser.token = jwt.sign(payload, env.JWT_SECRET, {
+ expiresIn: "1 day",
+ })
+
+ return done(null, dbUser)
+}
+
+/**
+ * Create an instance of the google passport strategy. This wrapper fetches the configuration
+ * from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport.
+ * @returns Dynamically configured Passport Google Strategy
+ */
+exports.strategyFactory = async function () {
+ try {
+
+ /*
+ const { clientID, clientSecret, callbackURL } = config
+
+ if (!clientID || !clientSecret || !callbackURL) {
+ throw new Error(
+ "Configuration invalid. Must contain google clientID, clientSecret and callbackURL"
+ )
+ }
+ */
+
+ return new OIDCStrategy(
+ {
+ issuer: "https://base.uri/auth/realms/realm_name",
+ authorizationURL: "https://base.uri/auth/realms/realm_name/protocol/openid-connect/auth",
+ tokenURL: "https://base.uri/auth/realms/realm_name/protocol/openid-connect/token",
+ userInfoURL: "https://base.uri/auth/realms/realm_name/protocol/openid-connect/userinfo",
+ clientID: "my_client_id",
+ clientSecret: "my_client_secret",
+ callbackURL: "http://localhost:10000/api/admin/auth/oidc/callback",
+ scope: "openid profile email",
+ },
+ authenticate
+ )
+ } catch (err) {
+ console.error(err)
+ throw new Error("Error constructing OIDC authentication strategy", err)
+ }
+}
diff --git a/packages/auth/yarn.lock b/packages/auth/yarn.lock
index 80625a9345..d52ce0145c 100644
--- a/packages/auth/yarn.lock
+++ b/packages/auth/yarn.lock
@@ -2,6 +2,17 @@
# yarn lockfile v1
+"@techpass/passport-openidconnect@^0.3.0":
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/@techpass/passport-openidconnect/-/passport-openidconnect-0.3.0.tgz#a60b2bbf3f262649a5a02d5d186219944acc3010"
+ integrity sha512-bVsPwl66s7J7GHxTPlW/RJYhZol9SshNznQsx83OOh9G+JWFGoeWxh+xbX+FTdJNoUvGIGbJnpWPY2wC6NOHPw==
+ dependencies:
+ base64url "^3.0.1"
+ oauth "^0.9.15"
+ passport-strategy "^1.0.0"
+ request "^2.88.0"
+ webfinger "^0.4.2"
+
ajv@^6.12.3:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
@@ -71,7 +82,7 @@ base64-js@^1.0.2, base64-js@^1.3.1:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
-base64url@3.x.x:
+base64url@3.x.x, base64url@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
@@ -574,7 +585,7 @@ oauth-sign@~0.9.0:
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
-oauth@0.9.x:
+oauth@0.9.x, oauth@^0.9.15:
version "0.9.15"
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
@@ -748,7 +759,7 @@ redis-parser@^3.0.0:
dependencies:
redis-errors "^1.0.0"
-request@^2.72.0, request@^2.74.0:
+request@^2.72.0, request@^2.74.0, request@^2.88.0:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
@@ -794,7 +805,7 @@ sax@1.2.1:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o=
-sax@>=0.6.0:
+sax@>=0.1.1, sax@>=0.6.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
@@ -829,6 +840,11 @@ standard-as-callback@^2.1.0:
resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45"
integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==
+step@0.0.x:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/step/-/step-0.0.6.tgz#143e7849a5d7d3f4a088fe29af94915216eeede2"
+ integrity sha1-FD54SaXX0/SgiP4pr5SRUhbu7eI=
+
string-template@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96"
@@ -943,11 +959,26 @@ verror@1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
+webfinger@^0.4.2:
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/webfinger/-/webfinger-0.4.2.tgz#3477a6d97799461896039fcffc650b73468ee76d"
+ integrity sha1-NHem2XeZRhiWA5/P/GULc0aO520=
+ dependencies:
+ step "0.0.x"
+ xml2js "0.1.x"
+
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
+xml2js@0.1.x:
+ version "0.1.14"
+ resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.1.14.tgz#5274e67f5a64c5f92974cd85139e0332adc6b90c"
+ integrity sha1-UnTmf1pkxfkpdM2FE54DMq3GuQw=
+ dependencies:
+ sax ">=0.1.1"
+
xml2js@0.4.19:
version "0.4.19"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
diff --git a/packages/builder/src/pages/builder/auth/_components/OIDCButton.svelte b/packages/builder/src/pages/builder/auth/_components/OIDCButton.svelte
new file mode 100644
index 0000000000..98f5d3efb9
--- /dev/null
+++ b/packages/builder/src/pages/builder/auth/_components/OIDCButton.svelte
@@ -0,0 +1,35 @@
+
+
+{#if show}
+ window.open("/api/admin/auth/oidc", "_blank")}
+ >
+
+
+{/if}
+
+
diff --git a/packages/builder/src/pages/builder/auth/login.svelte b/packages/builder/src/pages/builder/auth/login.svelte
index 9fb984c73e..3850431a0f 100644
--- a/packages/builder/src/pages/builder/auth/login.svelte
+++ b/packages/builder/src/pages/builder/auth/login.svelte
@@ -12,6 +12,7 @@
import { goto, params } from "@roxi/routify"
import { auth, organisation } from "stores/portal"
import GoogleButton from "./_components/GoogleButton.svelte"
+ import OIDCButton from "./_components/OIDCButton.svelte"
import Logo from "assets/bb-emblem.svg"
import { onMount } from "svelte"
@@ -61,6 +62,7 @@
Sign in to {company}
+
Sign in with email
diff --git a/packages/worker/package.json b/packages/worker/package.json
index d6ce2edce1..833f7b3ee9 100644
--- a/packages/worker/package.json
+++ b/packages/worker/package.json
@@ -39,6 +39,7 @@
"koa-static": "^5.0.0",
"node-fetch": "^2.6.1",
"nodemailer": "^6.5.0",
+ "@techpass/passport-openidconnect": "^0.3.0",
"passport-google-oauth": "^2.0.0",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
diff --git a/packages/worker/src/api/controllers/admin/auth.js b/packages/worker/src/api/controllers/admin/auth.js
index 5304ac85d1..374aa5c47d 100644
--- a/packages/worker/src/api/controllers/admin/auth.js
+++ b/packages/worker/src/api/controllers/admin/auth.js
@@ -1,5 +1,6 @@
const authPkg = require("@budibase/auth")
const { google } = require("@budibase/auth/src/middleware")
+const { oidc } = require("@budibase/auth/src/middleware")
const { Configs, EmailTemplatePurpose } = require("../../../constants")
const CouchDB = require("../../../db")
const { sendEmail, isEmailConfigured } = require("../../../utilities/email")
@@ -129,3 +130,27 @@ exports.googleAuth = async (ctx, next) => {
}
)(ctx, next)
}
+
+// Minimal OIDC attempt
+
+exports.oidcPreAuth = async (ctx, next) => {
+ const strategy = await oidc.strategyFactory()
+
+ return passport.authenticate(strategy, {
+ scope: ["profile", "email"],
+ })(ctx, next)
+}
+
+exports.oidcAuth = async (ctx, next) => {
+ const strategy = await oidc.strategyFactory()
+
+ return passport.authenticate(
+ strategy,
+ { successRedirect: "/", failureRedirect: "/error" },
+ async (err, user) => {
+ authInternal(ctx, user, err)
+
+ ctx.redirect("/")
+ }
+ )(ctx, next)
+}
diff --git a/packages/worker/src/api/index.js b/packages/worker/src/api/index.js
index bda57863f6..c77c70089e 100644
--- a/packages/worker/src/api/index.js
+++ b/packages/worker/src/api/index.js
@@ -25,6 +25,14 @@ const PUBLIC_ENDPOINTS = [
route: "/api/admin/auth/google/callback",
method: "GET",
},
+ {
+ route: "/api/admin/auth/oidc",
+ method: "GET",
+ },
+ {
+ route: "/api/admin/auth/oidc/callback",
+ method: "GET",
+ },
{
route: "/api/admin/auth/reset",
method: "POST",
diff --git a/packages/worker/src/api/routes/admin/auth.js b/packages/worker/src/api/routes/admin/auth.js
index 04e30fc006..27f09f74f9 100644
--- a/packages/worker/src/api/routes/admin/auth.js
+++ b/packages/worker/src/api/routes/admin/auth.js
@@ -39,5 +39,7 @@ router
.post("/api/admin/auth/logout", authController.logout)
.get("/api/admin/auth/google", authController.googlePreAuth)
.get("/api/admin/auth/google/callback", authController.googleAuth)
+ .get("/api/admin/auth/oidc", authController.oidcPreAuth)
+ .get("/api/admin/auth/oidc/callback", authController.oidcAuth)
module.exports = router
diff --git a/packages/worker/src/index.js b/packages/worker/src/index.js
index f59f8bab15..4e105a1435 100644
--- a/packages/worker/src/index.js
+++ b/packages/worker/src/index.js
@@ -5,6 +5,7 @@ require("@budibase/auth").init(CouchDB)
const Koa = require("koa")
const destroyable = require("server-destroy")
const koaBody = require("koa-body")
+const koaSession = require("koa-session")
const { passport } = require("@budibase/auth").auth
const logger = require("koa-pino-logger")
const http = require("http")
@@ -13,8 +14,11 @@ const redis = require("./utilities/redis")
const app = new Koa()
+app.keys = ['secret', 'key'];
+
// set up top level koa middleware
app.use(koaBody({ multipart: true }))
+app.use(koaSession(app))
app.use(
logger({
diff --git a/packages/worker/yarn.lock b/packages/worker/yarn.lock
index 53f10856e8..1d4227363f 100644
--- a/packages/worker/yarn.lock
+++ b/packages/worker/yarn.lock
@@ -566,6 +566,17 @@
dependencies:
defer-to-connect "^2.0.0"
+"@techpass/passport-openidconnect@^0.3.0":
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/@techpass/passport-openidconnect/-/passport-openidconnect-0.3.0.tgz#a60b2bbf3f262649a5a02d5d186219944acc3010"
+ integrity sha512-bVsPwl66s7J7GHxTPlW/RJYhZol9SshNznQsx83OOh9G+JWFGoeWxh+xbX+FTdJNoUvGIGbJnpWPY2wC6NOHPw==
+ dependencies:
+ base64url "^3.0.1"
+ oauth "^0.9.15"
+ passport-strategy "^1.0.0"
+ request "^2.88.0"
+ webfinger "^0.4.2"
+
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7":
version "7.1.14"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.14.tgz#faaeefc4185ec71c389f4501ee5ec84b170cc402"
@@ -1058,7 +1069,7 @@ base64-js@^1.0.2, base64-js@^1.3.1:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
-base64url@3.x.x:
+base64url@3.x.x, base64url@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
@@ -4183,7 +4194,7 @@ oauth-sign@~0.9.0:
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
-oauth@0.9.x:
+oauth@0.9.x, oauth@^0.9.15:
version "0.9.15"
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
@@ -4933,7 +4944,7 @@ request-promise-native@^1.0.9:
stealthy-require "^1.1.1"
tough-cookie "^2.3.3"
-request@^2.88.2:
+request@^2.88.0, request@^2.88.2:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
@@ -5080,7 +5091,7 @@ sax@1.2.1:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o=
-sax@>=0.6.0:
+sax@>=0.1.1, sax@>=0.6.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
@@ -5390,6 +5401,11 @@ stealthy-require@^1.1.1:
resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=
+step@0.0.x:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/step/-/step-0.0.6.tgz#143e7849a5d7d3f4a088fe29af94915216eeede2"
+ integrity sha1-FD54SaXX0/SgiP4pr5SRUhbu7eI=
+
string-length@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
@@ -5923,6 +5939,14 @@ walker@^1.0.7, walker@~1.0.5:
dependencies:
makeerror "1.0.x"
+webfinger@^0.4.2:
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/webfinger/-/webfinger-0.4.2.tgz#3477a6d97799461896039fcffc650b73468ee76d"
+ integrity sha1-NHem2XeZRhiWA5/P/GULc0aO520=
+ dependencies:
+ step "0.0.x"
+ xml2js "0.1.x"
+
webidl-conversions@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"
@@ -6031,6 +6055,13 @@ xml-name-validator@^3.0.0:
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
+xml2js@0.1.x:
+ version "0.1.14"
+ resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.1.14.tgz#5274e67f5a64c5f92974cd85139e0332adc6b90c"
+ integrity sha1-UnTmf1pkxfkpdM2FE54DMq3GuQw=
+ dependencies:
+ sax ">=0.1.1"
+
xml2js@0.4.19:
version "0.4.19"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"