-
save(google)}>Save
+ {/if}
+ {#if providers.oidc}
+
+
+
+
+
+ To allow users to authenticate using OIDC, fill out the fields below.
+
+
+
+ {#each OIDCConfigFields.Oidc as field}
+
+ {OIDCConfigLabels.Oidc[field]}
+
+
+ {/each}
+
+ Callback URL
+
+
+
+
+ To customize your login button, fill out the fields below.
+
+
+ Name
+
+
+
+ Icon
+ e.detail === "Upload" && fileinput.click()}
+ />
+
+ onFileSelected(e)}
+ bind:this={fileinput}
+ />
+
+
{/if}
diff --git a/packages/builder/src/stores/portal/index.js b/packages/builder/src/stores/portal/index.js
index 5bebb2aa7a..e2736cfa58 100644
--- a/packages/builder/src/stores/portal/index.js
+++ b/packages/builder/src/stores/portal/index.js
@@ -4,3 +4,4 @@ export { admin } from "./admin"
export { apps } from "./apps"
export { email } from "./email"
export { auth } from "./auth"
+export { oidc } from "./oidc"
diff --git a/packages/builder/src/stores/portal/oidc.js b/packages/builder/src/stores/portal/oidc.js
new file mode 100644
index 0000000000..d8d06f12a9
--- /dev/null
+++ b/packages/builder/src/stores/portal/oidc.js
@@ -0,0 +1,33 @@
+import { writable } from "svelte/store"
+import api from "builderStore/api"
+
+const OIDC_CONFIG = {
+ logo: undefined,
+ name: undefined,
+ uuid: undefined,
+}
+
+export function createOidcStore() {
+ const store = writable(OIDC_CONFIG)
+ const { set, subscribe } = store
+
+ async function init() {
+ const res = await api.get(`/api/admin/configs/publicOidc`)
+ const json = await res.json()
+
+ if (json.status === 400) {
+ set(OIDC_CONFIG)
+ } else {
+ // Just use the first config for now. We will be support multiple logins buttons later on.
+ set(...json)
+ }
+ }
+
+ return {
+ subscribe,
+ set,
+ init,
+ }
+}
+
+export const oidc = createOidcStore()
diff --git a/packages/builder/src/stores/portal/organisation.js b/packages/builder/src/stores/portal/organisation.js
index 7e6a777cd4..71c0be4b4d 100644
--- a/packages/builder/src/stores/portal/organisation.js
+++ b/packages/builder/src/stores/portal/organisation.js
@@ -6,6 +6,8 @@ const DEFAULT_CONFIG = {
logoUrl: undefined,
docsUrl: undefined,
company: "Budibase",
+ oidc: undefined,
+ google: undefined,
}
export function createOrganisationStore() {
diff --git a/packages/worker/package.json b/packages/worker/package.json
index 958087ac0a..c50a6bf096 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 0c2405b728..2a641e6194 100644
--- a/packages/worker/src/api/controllers/admin/auth.js
+++ b/packages/worker/src/api/controllers/admin/auth.js
@@ -1,25 +1,28 @@
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")
-const { clearCookie, getGlobalUserByEmail, hash } = authPkg.utils
+const { setCookie, getCookie, clearCookie, getGlobalUserByEmail, hash } =
+ authPkg.utils
const { Cookies } = authPkg.constants
const { passport } = authPkg.auth
const { checkResetPasswordCode } = require("../../../utilities/redis")
const GLOBAL_DB = authPkg.StaticDatabases.GLOBAL.name
-async function authInternal(ctx, user, err = null) {
+async function authInternal(ctx, user, err = null, info = null) {
if (err) {
- return ctx.throw(403, "Unauthorized")
+ console.error("Authentication error", err)
+ return ctx.throw(403, info ? info : "Unauthorized")
}
const expires = new Date()
expires.setDate(expires.getDate() + 1)
if (!user) {
- return ctx.throw(403, "Unauthorized")
+ return ctx.throw(403, info ? info : "Unauthorized")
}
// just store the user ID
@@ -32,8 +35,8 @@ async function authInternal(ctx, user, err = null) {
}
exports.authenticate = async (ctx, next) => {
- return passport.authenticate("local", async (err, user) => {
- await authInternal(ctx, user, err)
+ return passport.authenticate("local", async (err, user, info) => {
+ await authInternal(ctx, user, err, info)
delete user.token
@@ -123,8 +126,54 @@ exports.googleAuth = async (ctx, next) => {
return passport.authenticate(
strategy,
{ successRedirect: "/", failureRedirect: "/error" },
- async (err, user) => {
- await authInternal(ctx, user, err)
+ async (err, user, info) => {
+ await authInternal(ctx, user, err, info)
+
+ ctx.redirect("/")
+ }
+ )(ctx, next)
+}
+
+async function oidcStrategyFactory(ctx, configId) {
+ const db = new CouchDB(GLOBAL_DB)
+
+ const config = await authPkg.db.getScopedConfig(db, {
+ type: Configs.OIDC,
+ group: ctx.query.group,
+ })
+
+ const chosenConfig = config.configs.filter(c => c.uuid === configId)[0]
+
+ const callbackUrl = `${ctx.protocol}://${ctx.host}/api/admin/auth/oidc/callback`
+
+ return oidc.strategyFactory(chosenConfig, callbackUrl)
+}
+
+/**
+ * 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.
+ */
+exports.oidcPreAuth = async (ctx, next) => {
+ 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"],
+ })(ctx, next)
+}
+
+exports.oidcAuth = async (ctx, next) => {
+ const configId = getCookie(ctx, Cookies.OIDC_CONFIG)
+ const strategy = await oidcStrategyFactory(ctx, configId)
+
+ return passport.authenticate(
+ strategy,
+ { successRedirect: "/", failureRedirect: "/error" },
+ async (err, user, info) => {
+ await authInternal(ctx, user, err, info)
ctx.redirect("/")
}
diff --git a/packages/worker/src/api/controllers/admin/configs.js b/packages/worker/src/api/controllers/admin/configs.js
index b93bd22c80..7dfb5b75be 100644
--- a/packages/worker/src/api/controllers/admin/configs.js
+++ b/packages/worker/src/api/controllers/admin/configs.js
@@ -98,23 +98,73 @@ exports.find = async function (ctx) {
}
}
-exports.publicSettings = async function (ctx) {
+exports.publicOidc = async function (ctx) {
const db = new CouchDB(GLOBAL_DB)
try {
// Find the config with the most granular scope based on context
- const config = await getScopedFullConfig(db, {
- type: Configs.SETTINGS,
+ const oidcConfig = await getScopedFullConfig(db, {
+ type: Configs.OIDC,
})
- if (!config) {
+
+ if (!oidcConfig) {
ctx.body = {}
} else {
- ctx.body = config
+ const partialOidcCofig = oidcConfig.config.configs.map(config => {
+ return {
+ logo: config.logo,
+ name: config.name,
+ uuid: config.uuid,
+ }
+ })
+ ctx.body = partialOidcCofig
}
} catch (err) {
ctx.throw(err.status, err)
}
}
+exports.publicSettings = async function (ctx) {
+ const db = new CouchDB(GLOBAL_DB)
+
+ try {
+ // Find the config with the most granular scope based on context
+ const publicConfig = await getScopedFullConfig(db, {
+ type: Configs.SETTINGS,
+ })
+
+ const googleConfig = await getScopedFullConfig(db, {
+ type: Configs.GOOGLE,
+ })
+
+ const oidcConfig = await getScopedFullConfig(db, {
+ type: Configs.OIDC,
+ })
+
+ let config = {}
+ if (!publicConfig) {
+ config = {
+ config: {},
+ }
+ } else {
+ config = publicConfig
+ }
+
+ config.config.google = !googleConfig
+ ? !!googleConfig
+ : !googleConfig.config.activated
+ ? false
+ : true
+ config.config.oidc = !oidcConfig
+ ? !!oidcConfig
+ : !oidcConfig.config.configs[0].activated
+ ? false
+ : true
+ ctx.body = config
+ } catch (err) {
+ ctx.throw(err.status, err)
+ }
+}
+
exports.upload = async function (ctx) {
if (ctx.request.files == null || ctx.request.files.file.length > 1) {
ctx.throw(400, "One file must be uploaded.")
@@ -122,12 +172,8 @@ exports.upload = async function (ctx) {
const file = ctx.request.files.file
const { type, name } = ctx.params
- const fileExtension = [...file.name.split(".")].pop()
- // filenames converted to UUIDs so they are unique
- const processedFileName = `${name}.${fileExtension}`
-
const bucket = ObjectStoreBuckets.GLOBAL
- const key = `${type}/${processedFileName}`
+ const key = `${type}/${name}`
await upload({
bucket,
filename: key,
@@ -146,7 +192,7 @@ exports.upload = async function (ctx) {
}
}
const url = `/${bucket}/${key}`
- cfgStructure.config[`${name}Url`] = url
+ cfgStructure.config[`${name}`] = url
// write back to db with url updated
await db.put(cfgStructure)
@@ -184,10 +230,14 @@ exports.configChecklist = async function (ctx) {
})
// They have set up Google Auth
- const oauthConfig = await getScopedFullConfig(db, {
+ const googleConfig = await getScopedFullConfig(db, {
type: Configs.GOOGLE,
})
+ // They have set up OIDC
+ const oidcConfig = await getScopedFullConfig(db, {
+ type: Configs.OIDC,
+ })
// They have set up an admin user
const users = await db.allDocs(
getGlobalUserParams(null, {
@@ -200,7 +250,7 @@ exports.configChecklist = async function (ctx) {
apps: appDbNames.length,
smtp: !!smtpConfig,
adminUser,
- oauth: !!oauthConfig,
+ sso: !!googleConfig || !!oidcConfig,
}
} catch (err) {
ctx.throw(err.status, err)
diff --git a/packages/worker/src/api/index.js b/packages/worker/src/api/index.js
index bda57863f6..39ae320cc6 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",
@@ -41,6 +49,10 @@ const PUBLIC_ENDPOINTS = [
route: "/api/admin/configs/public",
method: "GET",
},
+ {
+ route: "/api/admin/configs/publicOidc",
+ method: "GET",
+ },
]
const router = new Router()
diff --git a/packages/worker/src/api/routes/admin/auth.js b/packages/worker/src/api/routes/admin/auth.js
index 04e30fc006..9a7ef5ebac 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/configs/:configId", authController.oidcPreAuth)
+ .get("/api/admin/auth/oidc/callback", authController.oidcAuth)
module.exports = router
diff --git a/packages/worker/src/api/routes/admin/configs.js b/packages/worker/src/api/routes/admin/configs.js
index 8056ad8cbd..6873d82757 100644
--- a/packages/worker/src/api/routes/admin/configs.js
+++ b/packages/worker/src/api/routes/admin/configs.js
@@ -3,7 +3,7 @@ const controller = require("../../controllers/admin/configs")
const joiValidator = require("../../../middleware/joi-validator")
const adminOnly = require("../../../middleware/adminOnly")
const Joi = require("joi")
-const { Configs, ConfigUploads } = require("../../../constants")
+const { Configs } = require("../../../constants")
const router = Router()
@@ -38,6 +38,24 @@ function googleValidation() {
clientID: Joi.string().required(),
clientSecret: Joi.string().required(),
callbackURL: Joi.string().required(),
+ activated: Joi.boolean().required(),
+ }).unknown(true)
+}
+
+function oidcValidation() {
+ // prettier-ignore
+ return Joi.object({
+ configs: Joi.array().items(
+ Joi.object({
+ clientID: Joi.string().required(),
+ clientSecret: Joi.string().required(),
+ configUrl: Joi.string().required(),
+ logo: Joi.string().allow("", null),
+ name: Joi.string().allow("", null),
+ uuid: Joi.string().required(),
+ activated: Joi.boolean().required(),
+ })
+ ).required(true)
}).unknown(true)
}
@@ -54,7 +72,8 @@ function buildConfigSaveValidation() {
{ is: Configs.SMTP, then: smtpValidation() },
{ is: Configs.SETTINGS, then: settingValidation() },
{ is: Configs.ACCOUNT, then: Joi.object().unknown(true) },
- { is: Configs.GOOGLE, then: googleValidation() }
+ { is: Configs.GOOGLE, then: googleValidation() },
+ { is: Configs.OIDC, then: oidcValidation() }
],
}),
}).required(),
@@ -65,7 +84,7 @@ function buildUploadValidation() {
// prettier-ignore
return joiValidator.params(Joi.object({
type: Joi.string().valid(...Object.values(Configs)).required(),
- name: Joi.string().valid(...Object.values(ConfigUploads)).required(),
+ name: Joi.string().required(),
}).required())
}
@@ -83,7 +102,7 @@ router
buildConfigSaveValidation(),
controller.save
)
- .delete("/api/admin/configs/:id", adminOnly, controller.destroy)
+ .delete("/api/admin/configs/:id/:rev", adminOnly, controller.destroy)
.get("/api/admin/configs", controller.fetch)
.get("/api/admin/configs/checklist", controller.configChecklist)
.get(
@@ -92,6 +111,7 @@ router
controller.fetch
)
.get("/api/admin/configs/public", controller.publicSettings)
+ .get("/api/admin/configs/publicOidc", controller.publicOidc)
.get("/api/admin/configs/:type", buildConfigGetValidation(), controller.find)
.post(
"/api/admin/configs/upload/:type/:name",
diff --git a/packages/worker/src/api/routes/tests/auth.spec.js b/packages/worker/src/api/routes/tests/auth.spec.js
index 8b9b699839..ceccf7edaf 100644
--- a/packages/worker/src/api/routes/tests/auth.spec.js
+++ b/packages/worker/src/api/routes/tests/auth.spec.js
@@ -1,4 +1,5 @@
const setup = require("./utilities")
+const { Cookies } = require("@budibase/auth").constants
jest.mock("nodemailer")
const sendMailMock = setup.emailMock()
@@ -14,6 +15,10 @@ describe("/api/admin/auth", () => {
afterAll(setup.afterAll)
+ afterEach(() => {
+ jest.clearAllMocks()
+ })
+
it("should be able to generate password reset email", async () => {
// initially configure settings
await config.saveSmtpConfig()
@@ -46,4 +51,56 @@ describe("/api/admin/auth", () => {
.expect(200)
expect(res.body).toEqual({ message: "password reset successfully." })
})
+
+ describe("oidc", () => {
+ const auth = require("@budibase/auth").auth
+
+ // mock the oidc strategy implementation and return value
+ strategyFactory = jest.fn()
+ mockStrategyReturn = jest.fn()
+ strategyFactory.mockReturnValue(mockStrategyReturn)
+ auth.oidc.strategyFactory = strategyFactory
+
+ const passportSpy = jest.spyOn(auth.passport, "authenticate")
+ let oidcConf
+ let chosenConfig
+ let configId
+
+ beforeEach(async () => {
+ oidcConf = await config.saveOIDCConfig()
+ chosenConfig = oidcConf.config.configs[0]
+ configId = chosenConfig.uuid
+ })
+
+ afterEach(() => {
+ expect(strategyFactory).toBeCalledWith(
+ chosenConfig,
+ `http://127.0.0.1:4003/api/admin/auth/oidc/callback` // calculated url
+ )
+ })
+
+ describe("/api/admin/auth/oidc/configs", () => {
+ it("should load strategy and delegate to passport", async () => {
+ await request.get(`/api/admin/auth/oidc/configs/${configId}`)
+
+ expect(passportSpy).toBeCalledWith(mockStrategyReturn, {
+ scope: ["profile", "email"],
+ })
+ expect(passportSpy.mock.calls.length).toBe(1);
+ })
+ })
+
+ describe("/api/admin/auth/oidc/callback", () => {
+ it("should load strategy and delegate to passport", async () => {
+ await request.get(`/api/admin/auth/oidc/callback`)
+ .set(config.getOIDConfigCookie(configId))
+
+ expect(passportSpy).toBeCalledWith(mockStrategyReturn, {
+ successRedirect: "/", failureRedirect: "/error"
+ }, expect.anything())
+ expect(passportSpy.mock.calls.length).toBe(1);
+ })
+ })
+
+ })
})
\ No newline at end of file
diff --git a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js
index c205a45e38..812dbe51e2 100644
--- a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js
+++ b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js
@@ -6,6 +6,7 @@ const { Cookies } = require("@budibase/auth").constants
const { Configs, LOGO_URL } = require("../../../../constants")
const { getGlobalUserByEmail } = require("@budibase/auth").utils
const { createASession } = require("@budibase/auth/sessions")
+const { newid } = require("../../../../../../auth/src/hashing")
class TestConfiguration {
constructor(openServer = true) {
@@ -67,6 +68,12 @@ class TestConfiguration {
}
}
+ cookieHeader(cookies) {
+ return {
+ Cookie: [cookies],
+ }
+ }
+
defaultHeaders() {
const user = {
_id: "us_uuid1",
@@ -76,7 +83,7 @@ class TestConfiguration {
const authToken = jwt.sign(user, env.JWT_SECRET)
return {
Accept: "application/json",
- Cookie: [`${Cookies.Auth}=${authToken}`],
+ ...this.cookieHeader([`${Cookies.Auth}=${authToken}`]),
}
}
@@ -155,6 +162,33 @@ class TestConfiguration {
)
}
+ getOIDConfigCookie(configId) {
+ const token = jwt.sign(configId, env.JWT_SECRET)
+ return this.cookieHeader([[`${Cookies.OIDC_CONFIG}=${token}`]])
+ }
+
+ async saveOIDCConfig() {
+ await this.deleteConfig(Configs.OIDC)
+ const config = {
+ type: Configs.OIDC,
+ config: {
+ configs: [
+ {
+ configUrl: "http://someconfigurl",
+ clientID: "clientId",
+ clientSecret: "clientSecret",
+ logo: "Microsoft",
+ name: "Active Directory",
+ uuid: newid(),
+ },
+ ],
+ },
+ }
+
+ await this._req(config, null, controllers.config.save)
+ return config
+ }
+
async saveSmtpConfig() {
await this.deleteConfig(Configs.SMTP)
await this._req(
diff --git a/packages/worker/src/constants/index.js b/packages/worker/src/constants/index.js
index aec864be97..231ff37ee2 100644
--- a/packages/worker/src/constants/index.js
+++ b/packages/worker/src/constants/index.js
@@ -16,6 +16,7 @@ exports.Configs = Configs
exports.ConfigUploads = {
LOGO: "logo",
+ OIDC_LOGO: "oidc_logo",
}
const TemplateTypes = {
diff --git a/packages/worker/src/index.js b/packages/worker/src/index.js
index f59f8bab15..8af1380552 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"