Add support for GCP IAP JIT account provisioning (#330)

* initial gcp iap implementation

* gcp iap working

* add docs for gcp iap

* feedback

* changelog
This commit is contained in:
Michael Sukkarieh 2025-06-03 19:28:38 -07:00 committed by GitHub
parent d5c4486664
commit 9227b3caba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 345 additions and 100 deletions

View file

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Added copy button for filenames. [#328](https://github.com/sourcebot-dev/sourcebot/pull/328) - 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 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
- Fixed issue with the symbol hover popover clipping at the top of the page. [#326](https://github.com/sourcebot-dev/sourcebot/pull/326) - Fixed issue with the symbol hover popover clipping at the top of the page. [#326](https://github.com/sourcebot-dev/sourcebot/pull/326)

View file

@ -80,6 +80,16 @@ Optional environment variables:
- `AUTH_EE_GOOGLE_CLIENT_ID` - `AUTH_EE_GOOGLE_CLIENT_ID`
- `AUTH_EE_GOOGLE_CLIENT_SECRET` - `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 ### Okta
--- ---

View file

@ -51,6 +51,8 @@ The following environment variables allow you to configure your Sourcebot deploy
| `AUTH_EE_OKTA_CLIENT_ID` | `-` | <p>The client ID for Okta SSO authentication.</p> | | `AUTH_EE_OKTA_CLIENT_ID` | `-` | <p>The client ID for Okta SSO authentication.</p> |
| `AUTH_EE_OKTA_CLIENT_SECRET` | `-` | <p>The client secret for Okta SSO authentication.</p> | | `AUTH_EE_OKTA_CLIENT_SECRET` | `-` | <p>The client secret for Okta SSO authentication.</p> |
| `AUTH_EE_OKTA_ISSUER` | `-` | <p>The issuer URL for Okta SSO authentication.</p> | | `AUTH_EE_OKTA_ISSUER` | `-` | <p>The issuer URL for Okta SSO authentication.</p> |
| `AUTH_EE_GCP_IAP_ENABLED` | `false` | <p>When enabled, allows Sourcebot to automatically register/login from a successful GCP IAP redirect</p> |
| `AUTH_EE_GCP_IAP_AUDIENCE` | - | <p>The GCP IAP audience to use when verifying JWT tokens. Must be set to enable GCP IAP JIT provisioning</p> |
### Review Agent Environment Variables ### Review Agent Environment Variables

View file

@ -114,6 +114,7 @@
"embla-carousel-react": "^8.3.0", "embla-carousel-react": "^8.3.0",
"escape-string-regexp": "^5.0.0", "escape-string-regexp": "^5.0.0",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"google-auth-library": "^9.15.1",
"graphql": "^16.9.0", "graphql": "^16.9.0",
"http-status-codes": "^2.3.0", "http-status-codes": "^2.3.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",

View file

@ -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 (
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<p className="text-lg">Signing in with Google Cloud IAP...</p>
</div>
</div>
);
};

View file

@ -17,6 +17,7 @@ import { PendingApprovalCard } from "./components/pendingApproval";
import { hasEntitlement } from "@/features/entitlements/server"; import { hasEntitlement } from "@/features/entitlements/server";
import { getPublicAccessStatus } from "@/ee/features/publicAccess/publicAccess"; import { getPublicAccessStatus } from "@/ee/features/publicAccess/publicAccess";
import { env } from "@/env.mjs"; import { env } from "@/env.mjs";
import { GcpIapAuth } from "./components/gcpIapAuth";
interface LayoutProps { interface LayoutProps {
children: React.ReactNode, children: React.ReactNode,
@ -37,7 +38,12 @@ export default async function Layout({
if (!publicAccessEnabled) { if (!publicAccessEnabled) {
const session = await auth(); const session = await auth();
if (!session) { if (!session) {
redirect('/login'); const ssoEntitlement = await hasEntitlement("sso");
if (ssoEntitlement && env.AUTH_EE_GCP_IAP_ENABLED && env.AUTH_EE_GCP_IAP_AUDIENCE) {
return <GcpIapAuth callbackUrl={`/${domain}`} />;
} else {
redirect('/login');
}
} }
const membership = await prisma.userToOrg.findUnique({ const membership = await prisma.userToOrg.findUnique({

View file

@ -5,21 +5,17 @@ import EmailProvider from "next-auth/providers/nodemailer";
import { PrismaAdapter } from "@auth/prisma-adapter" import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "@/prisma"; import { prisma } from "@/prisma";
import { env } from "@/env.mjs"; import { env } from "@/env.mjs";
import { OrgRole, User } from '@sourcebot/db'; import { User } from '@sourcebot/db';
import 'next-auth/jwt'; import 'next-auth/jwt';
import type { Provider } from "next-auth/providers"; import type { Provider } from "next-auth/providers";
import { verifyCredentialsRequestSchema } from './lib/schemas'; import { verifyCredentialsRequestSchema } from './lib/schemas';
import { createTransport } from 'nodemailer'; import { createTransport } from 'nodemailer';
import { render } from '@react-email/render'; import { render } from '@react-email/render';
import MagicLinkEmail from './emails/magicLinkEmail'; import MagicLinkEmail from './emails/magicLinkEmail';
import { SINGLE_TENANT_ORG_DOMAIN, SINGLE_TENANT_ORG_ID } from './lib/constants';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import { createAccountRequest } from './actions'; import { getSSOProviders } from '@/ee/sso/sso';
import { getSSOProviders, handleJITProvisioning } from '@/ee/sso/sso';
import { hasEntitlement } from '@/features/entitlements/server'; import { hasEntitlement } from '@/features/entitlements/server';
import { isServiceError } from './lib/utils'; import { onCreateUser } from '@/lib/authUtils';
import { ServiceErrorException } from './lib/serviceError';
import { createLogger } from "@sourcebot/logger";
export const runtime = 'nodejs'; export const runtime = 'nodejs';
@ -37,8 +33,6 @@ declare module 'next-auth/jwt' {
} }
} }
const logger = createLogger('web-auth');
export const getProviders = () => { export const getProviders = () => {
const providers: Provider[] = []; const providers: Provider[] = [];
@ -134,91 +128,6 @@ export const getProviders = () => {
return providers; 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({ export const { handlers, signIn, signOut, auth } = NextAuth({
secret: env.AUTH_SECRET, secret: env.AUTH_SECRET,
adapter: PrismaAdapter(prisma), adapter: PrismaAdapter(prisma),

View file

@ -12,7 +12,14 @@ import { OrgRole } from "@sourcebot/db";
import { getSeats, SOURCEBOT_UNLIMITED_SEATS } from "@/features/entitlements/server"; import { getSeats, SOURCEBOT_UNLIMITED_SEATS } from "@/features/entitlements/server";
import { StatusCodes } from "http-status-codes"; import { StatusCodes } from "http-status-codes";
import { ErrorCode } from "@/lib/errorCodes"; import { ErrorCode } from "@/lib/errorCodes";
import { OAuth2Client } from "google-auth-library";
import { sew } from "@/actions"; 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[] => { export const getSSOProviders = (): Provider[] => {
const providers: 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; return providers;
} }
@ -129,7 +212,7 @@ export const handleJITProvisioning = async (userId: string, domain: string): Pro
}); });
if (userToOrg) { 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; return true;
} }

View file

@ -50,6 +50,9 @@ export const env = createEnv({
AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_SECRET: z.string().optional(), AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_SECRET: z.string().optional(),
AUTH_EE_MICROSOFT_ENTRA_ID_ISSUER: 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(), DATA_CACHE_DIR: z.string(),
// Email // Email

View file

@ -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);
}
}
}
}
};

124
yarn.lock
View file

@ -6002,6 +6002,7 @@ __metadata:
eslint-plugin-react: "npm:^7.35.0" eslint-plugin-react: "npm:^7.35.0"
eslint-plugin-react-hooks: "npm:^4.6.2" eslint-plugin-react-hooks: "npm:^4.6.2"
fuse.js: "npm:^7.0.0" fuse.js: "npm:^7.0.0"
google-auth-library: "npm:^9.15.1"
graphql: "npm:^16.9.0" graphql: "npm:^16.9.0"
http-status-codes: "npm:^2.3.0" http-status-codes: "npm:^2.3.0"
input-otp: "npm:^1.4.2" input-otp: "npm:^1.4.2"
@ -7480,7 +7481,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"base64-js@npm:^1.3.1": "base64-js@npm:^1.3.0, base64-js@npm:^1.3.1":
version: 1.5.1 version: 1.5.1
resolution: "base64-js@npm:1.5.1" resolution: "base64-js@npm:1.5.1"
checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf
@ -7517,6 +7518,13 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "binary-extensions@npm:^2.0.0":
version: 2.3.0 version: 2.3.0
resolution: "binary-extensions@npm:2.3.0" resolution: "binary-extensions@npm:2.3.0"
@ -7628,6 +7636,13 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "buffer@npm:^5.5.0":
version: 5.7.1 version: 5.7.1
resolution: "buffer@npm:5.7.1" resolution: "buffer@npm:5.7.1"
@ -8821,6 +8836,15 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "ee-first@npm:1.1.1":
version: 1.1.1 version: 1.1.1
resolution: "ee-first@npm:1.1.1" resolution: "ee-first@npm:1.1.1"
@ -9857,6 +9881,13 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "fast-content-type-parse@npm:^2.0.0":
version: 2.0.1 version: 2.0.1
resolution: "fast-content-type-parse@npm:2.0.1" resolution: "fast-content-type-parse@npm:2.0.1"
@ -10199,6 +10230,30 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "gensync@npm:^1.0.0-beta.2":
version: 1.0.0-beta.2 version: 1.0.0-beta.2
resolution: "gensync@npm:1.0.0-beta.2" resolution: "gensync@npm:1.0.0-beta.2"
@ -10440,6 +10495,27 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "gopd@npm:^1.0.1, gopd@npm:^1.2.0":
version: 1.2.0 version: 1.2.0
resolution: "gopd@npm:1.2.0" resolution: "gopd@npm:1.2.0"
@ -10483,6 +10559,16 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "has-bigints@npm:^1.0.2":
version: 1.1.0 version: 1.1.0
resolution: "has-bigints@npm:1.1.0" resolution: "has-bigints@npm:1.1.0"
@ -11323,6 +11409,15 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "json-buffer@npm:3.0.1":
version: 3.0.1 version: 3.0.1
resolution: "json-buffer@npm:3.0.1" resolution: "json-buffer@npm:3.0.1"
@ -11431,6 +11526,27 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "keyv@npm:^4.5.3":
version: 4.5.4 version: 4.5.4
resolution: "keyv@npm:4.5.4" resolution: "keyv@npm:4.5.4"
@ -12404,7 +12520,7 @@ __metadata:
languageName: node languageName: node
linkType: hard 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 version: 2.7.0
resolution: "node-fetch@npm:2.7.0" resolution: "node-fetch@npm:2.7.0"
dependencies: dependencies:
@ -14359,7 +14475,7 @@ __metadata:
languageName: node languageName: node
linkType: hard 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 version: 5.2.1
resolution: "safe-buffer@npm:5.2.1" resolution: "safe-buffer@npm:5.2.1"
checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3
@ -16025,7 +16141,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"uuid@npm:^9.0.0": "uuid@npm:^9.0.0, uuid@npm:^9.0.1":
version: 9.0.1 version: 9.0.1
resolution: "uuid@npm:9.0.1" resolution: "uuid@npm:9.0.1"
bin: bin: