diff --git a/CHANGELOG.md b/CHANGELOG.md
index d9e9dd28..0c0660bd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added copy button for filenames. [#328](https://github.com/sourcebot-dev/sourcebot/pull/328)
- Added development docker compose file. [#328](https://github.com/sourcebot-dev/sourcebot/pull/328)
+- Added GCP IAP JIT provisioning. [#330](https://github.com/sourcebot-dev/sourcebot/pull/330)
### Fixed
- Fixed issue with the symbol hover popover clipping at the top of the page. [#326](https://github.com/sourcebot-dev/sourcebot/pull/326)
diff --git a/docs/docs/configuration/auth/overview.mdx b/docs/docs/configuration/auth/overview.mdx
index c89299ba..19343c8d 100644
--- a/docs/docs/configuration/auth/overview.mdx
+++ b/docs/docs/configuration/auth/overview.mdx
@@ -80,6 +80,16 @@ Optional environment variables:
- `AUTH_EE_GOOGLE_CLIENT_ID`
- `AUTH_EE_GOOGLE_CLIENT_SECRET`
+### GCP IAP
+---
+
+Custom provider built to enable automatic Sourcebot account registration/login when using GCP IAP.
+
+**Required environment variables**
+- `AUTH_EE_GCP_IAP_ENABLED`
+- `AUTH_EE_GCP_IAP_AUDIENCE`
+ - This can be found by selecting the ⋮ icon next to the IAP-enabled backend service and pressing `Get JWT audience code`
+
### Okta
---
diff --git a/docs/docs/configuration/environment-variables.mdx b/docs/docs/configuration/environment-variables.mdx
index 199a214d..f59b3fe0 100644
--- a/docs/docs/configuration/environment-variables.mdx
+++ b/docs/docs/configuration/environment-variables.mdx
@@ -51,6 +51,8 @@ The following environment variables allow you to configure your Sourcebot deploy
| `AUTH_EE_OKTA_CLIENT_ID` | `-` |
The client ID for Okta SSO authentication.
|
| `AUTH_EE_OKTA_CLIENT_SECRET` | `-` | The client secret for Okta SSO authentication.
|
| `AUTH_EE_OKTA_ISSUER` | `-` | The issuer URL for Okta SSO authentication.
|
+| `AUTH_EE_GCP_IAP_ENABLED` | `false` | When enabled, allows Sourcebot to automatically register/login from a successful GCP IAP redirect
|
+| `AUTH_EE_GCP_IAP_AUDIENCE` | - | The GCP IAP audience to use when verifying JWT tokens. Must be set to enable GCP IAP JIT provisioning
|
### Review Agent Environment Variables
diff --git a/packages/web/package.json b/packages/web/package.json
index 986e18dc..4ce59550 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -114,6 +114,7 @@
"embla-carousel-react": "^8.3.0",
"escape-string-regexp": "^5.0.0",
"fuse.js": "^7.0.0",
+ "google-auth-library": "^9.15.1",
"graphql": "^16.9.0",
"http-status-codes": "^2.3.0",
"input-otp": "^1.4.2",
diff --git a/packages/web/src/app/[domain]/components/gcpIapAuth.tsx b/packages/web/src/app/[domain]/components/gcpIapAuth.tsx
new file mode 100644
index 00000000..292161d6
--- /dev/null
+++ b/packages/web/src/app/[domain]/components/gcpIapAuth.tsx
@@ -0,0 +1,26 @@
+'use client';
+
+import { signIn } from "next-auth/react";
+import { useEffect } from "react";
+
+interface GcpIapAuthProps {
+ callbackUrl?: string;
+}
+
+export const GcpIapAuth = ({ callbackUrl }: GcpIapAuthProps) => {
+ useEffect(() => {
+ signIn("gcp-iap", {
+ redirectTo: callbackUrl ?? "/"
+ }).catch((error) => {
+ console.error("Error signing in with GCP IAP:", error);
+ });
+ }, [callbackUrl]);
+
+ return (
+
+
+
Signing in with Google Cloud IAP...
+
+
+ );
+};
\ No newline at end of file
diff --git a/packages/web/src/app/[domain]/layout.tsx b/packages/web/src/app/[domain]/layout.tsx
index 8b1122a3..1bb708d8 100644
--- a/packages/web/src/app/[domain]/layout.tsx
+++ b/packages/web/src/app/[domain]/layout.tsx
@@ -17,6 +17,7 @@ import { PendingApprovalCard } from "./components/pendingApproval";
import { hasEntitlement } from "@/features/entitlements/server";
import { getPublicAccessStatus } from "@/ee/features/publicAccess/publicAccess";
import { env } from "@/env.mjs";
+import { GcpIapAuth } from "./components/gcpIapAuth";
interface LayoutProps {
children: React.ReactNode,
@@ -37,7 +38,12 @@ export default async function Layout({
if (!publicAccessEnabled) {
const session = await auth();
if (!session) {
- redirect('/login');
+ const ssoEntitlement = await hasEntitlement("sso");
+ if (ssoEntitlement && env.AUTH_EE_GCP_IAP_ENABLED && env.AUTH_EE_GCP_IAP_AUDIENCE) {
+ return ;
+ } else {
+ redirect('/login');
+ }
}
const membership = await prisma.userToOrg.findUnique({
diff --git a/packages/web/src/auth.ts b/packages/web/src/auth.ts
index f4e0aa82..55fb3891 100644
--- a/packages/web/src/auth.ts
+++ b/packages/web/src/auth.ts
@@ -5,21 +5,17 @@ import EmailProvider from "next-auth/providers/nodemailer";
import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "@/prisma";
import { env } from "@/env.mjs";
-import { OrgRole, User } from '@sourcebot/db';
+import { User } from '@sourcebot/db';
import 'next-auth/jwt';
import type { Provider } from "next-auth/providers";
import { verifyCredentialsRequestSchema } from './lib/schemas';
import { createTransport } from 'nodemailer';
import { render } from '@react-email/render';
import MagicLinkEmail from './emails/magicLinkEmail';
-import { SINGLE_TENANT_ORG_DOMAIN, SINGLE_TENANT_ORG_ID } from './lib/constants';
import bcrypt from 'bcryptjs';
-import { createAccountRequest } from './actions';
-import { getSSOProviders, handleJITProvisioning } from '@/ee/sso/sso';
+import { getSSOProviders } from '@/ee/sso/sso';
import { hasEntitlement } from '@/features/entitlements/server';
-import { isServiceError } from './lib/utils';
-import { ServiceErrorException } from './lib/serviceError';
-import { createLogger } from "@sourcebot/logger";
+import { onCreateUser } from '@/lib/authUtils';
export const runtime = 'nodejs';
@@ -37,8 +33,6 @@ declare module 'next-auth/jwt' {
}
}
-const logger = createLogger('web-auth');
-
export const getProviders = () => {
const providers: Provider[] = [];
@@ -134,91 +128,6 @@ export const getProviders = () => {
return providers;
}
-const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
- // In single-tenant mode, we assign the first user to sign
- // up as the owner of the default org.
- if (
- env.SOURCEBOT_TENANCY_MODE === 'single'
- ) {
- const defaultOrg = await prisma.org.findUnique({
- where: {
- id: SINGLE_TENANT_ORG_ID,
- },
- include: {
- members: {
- where: {
- role: {
- not: OrgRole.GUEST,
- }
- }
- },
- }
- });
-
- if (!defaultOrg) {
- throw new Error("Default org not found on single tenant user creation");
- }
-
- // We can't use the getOrgMembers action here because we're not authed yet
- const members = await prisma.userToOrg.findMany({
- where: {
- orgId: SINGLE_TENANT_ORG_ID,
- role: {
- not: OrgRole.GUEST,
- }
- },
- });
-
- // Only the first user to sign up will be an owner of the default org.
- const isFirstUser = members.length === 0;
- if (isFirstUser) {
- await prisma.$transaction(async (tx) => {
- await tx.org.update({
- where: {
- id: SINGLE_TENANT_ORG_ID,
- },
- data: {
- members: {
- create: {
- role: OrgRole.OWNER,
- user: {
- connect: {
- id: user.id,
- }
- }
- }
- }
- }
- });
-
- await tx.user.update({
- where: {
- id: user.id,
- },
- data: {
- pendingApproval: false,
- }
- });
- });
- } else {
- // TODO(auth): handle multi tenant case
- if (env.AUTH_EE_ENABLE_JIT_PROVISIONING === 'true' && hasEntitlement("sso")) {
- const res = await handleJITProvisioning(user.id!, SINGLE_TENANT_ORG_DOMAIN);
- if (isServiceError(res)) {
- logger.error(`Failed to provision user ${user.id} for org ${SINGLE_TENANT_ORG_DOMAIN}: ${res.message}`);
- throw new ServiceErrorException(res);
- }
- } else {
- const res = await createAccountRequest(user.id!, SINGLE_TENANT_ORG_DOMAIN);
- if (isServiceError(res)) {
- logger.error(`Failed to provision user ${user.id} for org ${SINGLE_TENANT_ORG_DOMAIN}: ${res.message}`);
- throw new ServiceErrorException(res);
- }
- }
- }
- }
-}
-
export const { handlers, signIn, signOut, auth } = NextAuth({
secret: env.AUTH_SECRET,
adapter: PrismaAdapter(prisma),
diff --git a/packages/web/src/ee/sso/sso.tsx b/packages/web/src/ee/sso/sso.tsx
index c3d80333..bb1ec7b9 100644
--- a/packages/web/src/ee/sso/sso.tsx
+++ b/packages/web/src/ee/sso/sso.tsx
@@ -12,7 +12,14 @@ import { OrgRole } from "@sourcebot/db";
import { getSeats, SOURCEBOT_UNLIMITED_SEATS } from "@/features/entitlements/server";
import { StatusCodes } from "http-status-codes";
import { ErrorCode } from "@/lib/errorCodes";
+import { OAuth2Client } from "google-auth-library";
import { sew } from "@/actions";
+import Credentials from "next-auth/providers/credentials";
+import type { User as AuthJsUser } from "next-auth";
+import { onCreateUser } from "@/lib/authUtils";
+import { createLogger } from "@sourcebot/logger";
+
+const logger = createLogger('web-sso');
export const getSSOProviders = (): Provider[] => {
const providers: Provider[] = [];
@@ -88,6 +95,82 @@ export const getSSOProviders = (): Provider[] => {
}));
}
+ if (env.AUTH_EE_GCP_IAP_ENABLED && env.AUTH_EE_GCP_IAP_AUDIENCE) {
+ providers.push(Credentials({
+ id: "gcp-iap",
+ name: "Google Cloud IAP",
+ credentials: {},
+ authorize: async (credentials, req) => {
+ try {
+ const iapAssertion = req.headers?.get("x-goog-iap-jwt-assertion");
+ if (!iapAssertion || typeof iapAssertion !== "string") {
+ logger.warn("No IAP assertion found in headers");
+ return null;
+ }
+
+ const oauth2Client = new OAuth2Client();
+
+ const { pubkeys } = await oauth2Client.getIapPublicKeys();
+ const ticket = await oauth2Client.verifySignedJwtWithCertsAsync(
+ iapAssertion,
+ pubkeys,
+ env.AUTH_EE_GCP_IAP_AUDIENCE,
+ ['https://cloud.google.com/iap']
+ );
+
+ const payload = ticket.getPayload();
+ if (!payload) {
+ logger.warn("Invalid IAP token payload");
+ return null;
+ }
+
+ const email = payload.email;
+ const name = payload.name || payload.email;
+ const image = payload.picture;
+
+ if (!email) {
+ logger.warn("Missing email in IAP token");
+ return null;
+ }
+
+ const existingUser = await prisma.user.findUnique({
+ where: { email }
+ });
+
+ if (!existingUser) {
+ const newUser = await prisma.user.create({
+ data: {
+ email,
+ name,
+ image,
+ }
+ });
+
+ const authJsUser: AuthJsUser = {
+ id: newUser.id,
+ email: newUser.email,
+ name: newUser.name,
+ image: newUser.image,
+ };
+
+ await onCreateUser({ user: authJsUser });
+ return authJsUser;
+ } else {
+ return {
+ id: existingUser.id,
+ email: existingUser.email,
+ name: existingUser.name,
+ image: existingUser.image,
+ };
+ }
+ } catch (error) {
+ logger.error("Error verifying IAP token:", error);
+ return null;
+ }
+ },
+ }));
+ }
+
return providers;
}
@@ -129,7 +212,7 @@ export const handleJITProvisioning = async (userId: string, domain: string): Pro
});
if (userToOrg) {
- console.warn(`JIT provisioning skipped for user ${userId} since they're already a member of org ${domain}`);
+ logger.warn(`JIT provisioning skipped for user ${userId} since they're already a member of org ${domain}`);
return true;
}
diff --git a/packages/web/src/env.mjs b/packages/web/src/env.mjs
index 60996de9..bd4af8c8 100644
--- a/packages/web/src/env.mjs
+++ b/packages/web/src/env.mjs
@@ -50,6 +50,9 @@ export const env = createEnv({
AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_SECRET: z.string().optional(),
AUTH_EE_MICROSOFT_ENTRA_ID_ISSUER: z.string().optional(),
+ AUTH_EE_GCP_IAP_ENABLED: booleanSchema.default('false'),
+ AUTH_EE_GCP_IAP_AUDIENCE: z.string().optional(),
+
DATA_CACHE_DIR: z.string(),
// Email
diff --git a/packages/web/src/lib/authUtils.ts b/packages/web/src/lib/authUtils.ts
new file mode 100644
index 00000000..6b7df3eb
--- /dev/null
+++ b/packages/web/src/lib/authUtils.ts
@@ -0,0 +1,88 @@
+import type { User as AuthJsUser } from "next-auth";
+import { env } from "@/env.mjs";
+import { prisma } from "@/prisma";
+import { OrgRole } from "@sourcebot/db";
+import { SINGLE_TENANT_ORG_DOMAIN, SINGLE_TENANT_ORG_ID } from "@/lib/constants";
+import { hasEntitlement } from "@/features/entitlements/server";
+import { isServiceError } from "@/lib/utils";
+import { ServiceErrorException } from "@/lib/serviceError";
+import { createAccountRequest } from "@/actions";
+import { handleJITProvisioning } from "@/ee/sso/sso";
+import { createLogger } from "@sourcebot/logger";
+
+const logger = createLogger('web-auth-utils');
+
+export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
+ // In single-tenant mode, we assign the first user to sign
+ // up as the owner of the default org.
+ if (
+ env.SOURCEBOT_TENANCY_MODE === 'single'
+ ) {
+ const defaultOrg = await prisma.org.findUnique({
+ where: {
+ id: SINGLE_TENANT_ORG_ID,
+ },
+ include: {
+ members: {
+ where: {
+ role: {
+ not: OrgRole.GUEST,
+ }
+ }
+ },
+ }
+ });
+
+ if (!defaultOrg) {
+ throw new Error("Default org not found on single tenant user creation");
+ }
+
+ // Only the first user to sign up will be an owner of the default org.
+ const isFirstUser = defaultOrg.members.length === 0;
+ if (isFirstUser) {
+ await prisma.$transaction(async (tx) => {
+ await tx.org.update({
+ where: {
+ id: SINGLE_TENANT_ORG_ID,
+ },
+ data: {
+ members: {
+ create: {
+ role: OrgRole.OWNER,
+ user: {
+ connect: {
+ id: user.id,
+ }
+ }
+ }
+ }
+ }
+ });
+
+ await tx.user.update({
+ where: {
+ id: user.id,
+ },
+ data: {
+ pendingApproval: false,
+ }
+ });
+ });
+ } else {
+ // TODO(auth): handle multi tenant case
+ if (env.AUTH_EE_ENABLE_JIT_PROVISIONING === 'true' && hasEntitlement("sso")) {
+ const res = await handleJITProvisioning(user.id!, SINGLE_TENANT_ORG_DOMAIN);
+ if (isServiceError(res)) {
+ logger.error(`Failed to provision user ${user.id} for org ${SINGLE_TENANT_ORG_DOMAIN}: ${res.message}`);
+ throw new ServiceErrorException(res);
+ }
+ } else {
+ const res = await createAccountRequest(user.id!, SINGLE_TENANT_ORG_DOMAIN);
+ if (isServiceError(res)) {
+ logger.error(`Failed to provision user ${user.id} for org ${SINGLE_TENANT_ORG_DOMAIN}: ${res.message}`);
+ throw new ServiceErrorException(res);
+ }
+ }
+ }
+ }
+};
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 80a79b2a..b379f320 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6002,6 +6002,7 @@ __metadata:
eslint-plugin-react: "npm:^7.35.0"
eslint-plugin-react-hooks: "npm:^4.6.2"
fuse.js: "npm:^7.0.0"
+ google-auth-library: "npm:^9.15.1"
graphql: "npm:^16.9.0"
http-status-codes: "npm:^2.3.0"
input-otp: "npm:^1.4.2"
@@ -7480,7 +7481,7 @@ __metadata:
languageName: node
linkType: hard
-"base64-js@npm:^1.3.1":
+"base64-js@npm:^1.3.0, base64-js@npm:^1.3.1":
version: 1.5.1
resolution: "base64-js@npm:1.5.1"
checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf
@@ -7517,6 +7518,13 @@ __metadata:
languageName: node
linkType: hard
+"bignumber.js@npm:^9.0.0":
+ version: 9.3.0
+ resolution: "bignumber.js@npm:9.3.0"
+ checksum: 10c0/f54a79cd6fc98552ac0510c1cd9381650870ae443bdb20ba9b98e3548188d941506ac3c22a9f9c69b2cc60da9be5700e87d3f54d2825310a8b2ae999dfd6d99d
+ languageName: node
+ linkType: hard
+
"binary-extensions@npm:^2.0.0":
version: 2.3.0
resolution: "binary-extensions@npm:2.3.0"
@@ -7628,6 +7636,13 @@ __metadata:
languageName: node
linkType: hard
+"buffer-equal-constant-time@npm:^1.0.1":
+ version: 1.0.1
+ resolution: "buffer-equal-constant-time@npm:1.0.1"
+ checksum: 10c0/fb2294e64d23c573d0dd1f1e7a466c3e978fe94a4e0f8183937912ca374619773bef8e2aceb854129d2efecbbc515bbd0cc78d2734a3e3031edb0888531bbc8e
+ languageName: node
+ linkType: hard
+
"buffer@npm:^5.5.0":
version: 5.7.1
resolution: "buffer@npm:5.7.1"
@@ -8821,6 +8836,15 @@ __metadata:
languageName: node
linkType: hard
+"ecdsa-sig-formatter@npm:1.0.11, ecdsa-sig-formatter@npm:^1.0.11":
+ version: 1.0.11
+ resolution: "ecdsa-sig-formatter@npm:1.0.11"
+ dependencies:
+ safe-buffer: "npm:^5.0.1"
+ checksum: 10c0/ebfbf19d4b8be938f4dd4a83b8788385da353d63307ede301a9252f9f7f88672e76f2191618fd8edfc2f24679236064176fab0b78131b161ee73daa37125408c
+ languageName: node
+ linkType: hard
+
"ee-first@npm:1.1.1":
version: 1.1.1
resolution: "ee-first@npm:1.1.1"
@@ -9857,6 +9881,13 @@ __metadata:
languageName: node
linkType: hard
+"extend@npm:^3.0.2":
+ version: 3.0.2
+ resolution: "extend@npm:3.0.2"
+ checksum: 10c0/73bf6e27406e80aa3e85b0d1c4fd987261e628064e170ca781125c0b635a3dabad5e05adbf07595ea0cf1e6c5396cacb214af933da7cbaf24fe75ff14818e8f9
+ languageName: node
+ linkType: hard
+
"fast-content-type-parse@npm:^2.0.0":
version: 2.0.1
resolution: "fast-content-type-parse@npm:2.0.1"
@@ -10199,6 +10230,30 @@ __metadata:
languageName: node
linkType: hard
+"gaxios@npm:^6.0.0, gaxios@npm:^6.1.1":
+ version: 6.7.1
+ resolution: "gaxios@npm:6.7.1"
+ dependencies:
+ extend: "npm:^3.0.2"
+ https-proxy-agent: "npm:^7.0.1"
+ is-stream: "npm:^2.0.0"
+ node-fetch: "npm:^2.6.9"
+ uuid: "npm:^9.0.1"
+ checksum: 10c0/53e92088470661c5bc493a1de29d05aff58b1f0009ec5e7903f730f892c3642a93e264e61904383741ccbab1ce6e519f12a985bba91e13527678b32ee6d7d3fd
+ languageName: node
+ linkType: hard
+
+"gcp-metadata@npm:^6.1.0":
+ version: 6.1.1
+ resolution: "gcp-metadata@npm:6.1.1"
+ dependencies:
+ gaxios: "npm:^6.1.1"
+ google-logging-utils: "npm:^0.0.2"
+ json-bigint: "npm:^1.0.0"
+ checksum: 10c0/71f6ad4800aa622c246ceec3955014c0c78cdcfe025971f9558b9379f4019f5e65772763428ee8c3244fa81b8631977316eaa71a823493f82e5c44d7259ffac8
+ languageName: node
+ linkType: hard
+
"gensync@npm:^1.0.0-beta.2":
version: 1.0.0-beta.2
resolution: "gensync@npm:1.0.0-beta.2"
@@ -10440,6 +10495,27 @@ __metadata:
languageName: node
linkType: hard
+"google-auth-library@npm:^9.15.1":
+ version: 9.15.1
+ resolution: "google-auth-library@npm:9.15.1"
+ dependencies:
+ base64-js: "npm:^1.3.0"
+ ecdsa-sig-formatter: "npm:^1.0.11"
+ gaxios: "npm:^6.1.1"
+ gcp-metadata: "npm:^6.1.0"
+ gtoken: "npm:^7.0.0"
+ jws: "npm:^4.0.0"
+ checksum: 10c0/6eef36d9a9cb7decd11e920ee892579261c6390104b3b24d3e0f3889096673189fe2ed0ee43fd563710e2560de98e63ad5aa4967b91e7f4e69074a422d5f7b65
+ languageName: node
+ linkType: hard
+
+"google-logging-utils@npm:^0.0.2":
+ version: 0.0.2
+ resolution: "google-logging-utils@npm:0.0.2"
+ checksum: 10c0/9a4bbd470dd101c77405e450fffca8592d1d7114f245a121288d04a957aca08c9dea2dd1a871effe71e41540d1bb0494731a0b0f6fea4358e77f06645e4268c1
+ languageName: node
+ linkType: hard
+
"gopd@npm:^1.0.1, gopd@npm:^1.2.0":
version: 1.2.0
resolution: "gopd@npm:1.2.0"
@@ -10483,6 +10559,16 @@ __metadata:
languageName: node
linkType: hard
+"gtoken@npm:^7.0.0":
+ version: 7.1.0
+ resolution: "gtoken@npm:7.1.0"
+ dependencies:
+ gaxios: "npm:^6.0.0"
+ jws: "npm:^4.0.0"
+ checksum: 10c0/0a3dcacb1a3c4578abe1ee01c7d0bf20bffe8ded3ee73fc58885d53c00f6eb43b4e1372ff179f0da3ed5cfebd5b7c6ab8ae2776f1787e90d943691b4fe57c716
+ languageName: node
+ linkType: hard
+
"has-bigints@npm:^1.0.2":
version: 1.1.0
resolution: "has-bigints@npm:1.1.0"
@@ -11323,6 +11409,15 @@ __metadata:
languageName: node
linkType: hard
+"json-bigint@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "json-bigint@npm:1.0.0"
+ dependencies:
+ bignumber.js: "npm:^9.0.0"
+ checksum: 10c0/e3f34e43be3284b573ea150a3890c92f06d54d8ded72894556357946aeed9877fd795f62f37fe16509af189fd314ab1104d0fd0f163746ad231b9f378f5b33f4
+ languageName: node
+ linkType: hard
+
"json-buffer@npm:3.0.1":
version: 3.0.1
resolution: "json-buffer@npm:3.0.1"
@@ -11431,6 +11526,27 @@ __metadata:
languageName: node
linkType: hard
+"jwa@npm:^2.0.0":
+ version: 2.0.1
+ resolution: "jwa@npm:2.0.1"
+ dependencies:
+ buffer-equal-constant-time: "npm:^1.0.1"
+ ecdsa-sig-formatter: "npm:1.0.11"
+ safe-buffer: "npm:^5.0.1"
+ checksum: 10c0/ab3ebc6598e10dc11419d4ed675c9ca714a387481466b10e8a6f3f65d8d9c9237e2826f2505280a739cf4cbcf511cb288eeec22b5c9c63286fc5a2e4f97e78cf
+ languageName: node
+ linkType: hard
+
+"jws@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "jws@npm:4.0.0"
+ dependencies:
+ jwa: "npm:^2.0.0"
+ safe-buffer: "npm:^5.0.1"
+ checksum: 10c0/f1ca77ea5451e8dc5ee219cb7053b8a4f1254a79cb22417a2e1043c1eb8a569ae118c68f24d72a589e8a3dd1824697f47d6bd4fb4bebb93a3bdf53545e721661
+ languageName: node
+ linkType: hard
+
"keyv@npm:^4.5.3":
version: 4.5.4
resolution: "keyv@npm:4.5.4"
@@ -12404,7 +12520,7 @@ __metadata:
languageName: node
linkType: hard
-"node-fetch@npm:^2.6.7, node-fetch@npm:^2.7.0":
+"node-fetch@npm:^2.6.7, node-fetch@npm:^2.6.9, node-fetch@npm:^2.7.0":
version: 2.7.0
resolution: "node-fetch@npm:2.7.0"
dependencies:
@@ -14359,7 +14475,7 @@ __metadata:
languageName: node
linkType: hard
-"safe-buffer@npm:5.2.1, safe-buffer@npm:~5.2.0":
+"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:~5.2.0":
version: 5.2.1
resolution: "safe-buffer@npm:5.2.1"
checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3
@@ -16025,7 +16141,7 @@ __metadata:
languageName: node
linkType: hard
-"uuid@npm:^9.0.0":
+"uuid@npm:^9.0.0, uuid@npm:^9.0.1":
version: 9.0.1
resolution: "uuid@npm:9.0.1"
bin: