diff --git a/.changeset/stale-knives-help.md b/.changeset/stale-knives-help.md new file mode 100644 index 00000000000..c40eb6b06d9 --- /dev/null +++ b/.changeset/stale-knives-help.md @@ -0,0 +1,69 @@ +--- +'hive': major +--- + +**BREAKING** Remove support for `supertokens` service and replace it with native authentication solution. + +## Upgrade Guide + +Adjust your docker compose file like the following: +- Remove `services.supertokens` from your `docker-compose.community.yml` file +- Remove the following environment variables from the `services.server.environment` + - `SUPERTOKENS_CONNECTION_URI=` + - `SUPERTOKENS_API_KEY=` +- Set the following environment variables for `services.server.environment` + - `SUPERTOKENS_REFRESH_TOKEN_KEY=` + - `SUPERTOKENS_ACCESS_TOKEN_KEY=` + +### Set the refresh token key + +#### Extract from existing `supertokens` deployment + +This method works if you use supertokens before and want to have existing user sessions to continue working. +If you want to avoid messing with the database, you can also create a new refresh token key from scratch, the drawback is that users are forced to login again. + +Extract the refresh token key from the supertokens database +```sql +SELECT "value" FROM "supertokens_key_value" WHERE "name" = 'refresh_token_key'; +``` + +The key should look similar to this: `1000:15e5968d52a9a48921c1c63d88145441a8099b4a44248809a5e1e733411b3eeb80d87a6e10d3390468c222f6a91fef3427f8afc8b91ea1820ab10c7dfd54a268:39f72164821e08edd6ace99f3bd4e387f45fa4221fe3cd80ecfee614850bc5d647ac2fddc14462a00647fff78c22e8d01bc306a91294f5b889a90ba891bf0aa0` + +Update the docker compose `services.server.environment.SUPERTOKENS_REFRESH_TOKEN_KEY` environment variable value to this string. + +#### Create from scratch + +Run the following command to create a new refresh key from scratch: + +```sh +echo "1000:$(openssl rand -hex 64):$(openssl rand -hex 64)" +``` + +### Set the access token key + +Generate a new access token key using the following instructions: + +```sh +# 1. Generate a unique key name. 'uuidgen' is great for this. +# You can replace this with any string you like, e.g., KEY_NAME="my-app-key-1" +KEY_NAME=$(uuidgen) +# 2. Generate a 2048-bit RSA private key in PEM format, held in memory. +PRIVATE_KEY_PEM=$(openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048) +# 3. Extract the corresponding public key from the private key, also held in memory. +PUBLIC_KEY_PEM=$(echo "$PRIVATE_KEY_PEM" | openssl rsa -pubout) +# 4. Strip the headers/footers and newlines from the private key PEM +# to get just the raw Base64 data. +PRIVATE_KEY_DATA=$(echo "$PRIVATE_KEY_PEM" | awk 'NF {if (NR!=1 && $0!~/-----END/) print}' | tr -d '\n') +# 5. Do the same for the public key PEM. +PUBLIC_KEY_DATA=$(echo "$PUBLIC_KEY_PEM" | awk 'NF {if (NR!=1 && $0!~/-----END/) print}' | tr -d '\n') +# 6. Echo the final formatted string to the console. +echo "${KEY_NAME}|${PUBLIC_KEY_DATA}|${PRIVATE_KEY_DATA}" +``` + +Update the docker compose `services.server.environment.SUPERTOKENS_ACCESS_TOKEN_KEY` environment variable value to the formatted string output. + +## Conclusion + +After performing this updates you can run Hive Console without the need for the `supertokens` service. All the relevant authentication logic resides within the `server` container instead. + +Existing users in the supertokens system will continue to exist when running without the `supertokens` service. diff --git a/deployment/index.ts b/deployment/index.ts index 90128423c51..3c1d42bd7df 100644 --- a/deployment/index.ts +++ b/deployment/index.ts @@ -24,7 +24,6 @@ import { deployS3, deployS3AuditLog, deployS3Mirror } from './services/s3'; import { deploySchema } from './services/schema'; import { configureSentry } from './services/sentry'; import { configureSlackApp } from './services/slack-app'; -import { deploySuperTokens } from './services/supertokens'; import { deployTokens } from './services/tokens'; import { deployUsage } from './services/usage'; import { deployUsageIngestor } from './services/usage-ingestor'; @@ -201,7 +200,6 @@ const schemaPolicy = deploySchemaPolicy({ observability, }); -const supertokens = deploySuperTokens(postgres, { dependencies: [dbMigrations] }, environment); const zendesk = configureZendesk({ environment }); const githubApp = configureGithubApp(); const slackApp = configureSlackApp(); @@ -220,7 +218,6 @@ const graphql = deployGraphQL({ usage, cdn, commerce, - supertokens, s3, s3Mirror, s3AuditLog, diff --git a/deployment/services/db-migrations.ts b/deployment/services/db-migrations.ts index e21907d12e6..e8534527da0 100644 --- a/deployment/services/db-migrations.ts +++ b/deployment/services/db-migrations.ts @@ -47,7 +47,6 @@ export function deployDbMigrations({ // Since K8s job are immutable, we can't edit or ask K8s to re-run a Job, so we are doing a // pseudo change to an env var, which causes Pulumi to re-create the Job. IGNORE_RERUN_NONCE: force ? Date.now().toString() : '0', - SUPERTOKENS_AT_HOME: '1', }, }, [clickhouse.deployment, clickhouse.service, ...(dependencies || [])], diff --git a/deployment/services/environment.ts b/deployment/services/environment.ts index ac7aba9d772..98732a0cd03 100644 --- a/deployment/services/environment.ts +++ b/deployment/services/environment.ts @@ -48,9 +48,6 @@ export function prepareEnvironment(input: { general: { replicas: isProduction || isStaging ? 3 : 1, }, - supertokens: { - replicas: isProduction || isStaging ? 3 : 1, - }, envoy: { replicas: isProduction || isStaging ? 3 : 1, cpuLimit: isProduction ? '1500m' : '120m', diff --git a/deployment/services/graphql.ts b/deployment/services/graphql.ts index 41a07790210..cb5730c15f9 100644 --- a/deployment/services/graphql.ts +++ b/deployment/services/graphql.ts @@ -16,7 +16,6 @@ import { Redis } from './redis'; import { S3 } from './s3'; import { Schema } from './schema'; import { Sentry } from './sentry'; -import { Supertokens } from './supertokens'; import { Tokens } from './tokens'; import { Usage } from './usage'; import { Zendesk } from './zendesk'; @@ -40,7 +39,6 @@ export function deployGraphQL({ usage, commerce, dbMigrations, - supertokens, s3, s3Mirror, s3AuditLog, @@ -68,7 +66,6 @@ export function deployGraphQL({ usage: Usage; dbMigrations: DbMigrations; commerce: CommerceService; - supertokens: Supertokens; zendesk: Zendesk; docker: Docker; sentry: Sentry; @@ -144,7 +141,6 @@ export function deployGraphQL({ ZENDESK_SUPPORT: zendesk.enabled ? '1' : '0', INTEGRATION_GITHUB: '1', // Auth - SUPERTOKENS_CONNECTION_URI: supertokens.localEndpoint, AUTH_GITHUB: '1', AUTH_GOOGLE: '1', AUTH_ORGANIZATION_OIDC: '1', @@ -155,7 +151,6 @@ export function deployGraphQL({ ? observability.tracingEndpoint : '', S3_MIRROR: '1', - SUPERTOKENS_AT_HOME: '1', }, exposesMetrics: true, port: 4000, @@ -209,7 +204,6 @@ export function deployGraphQL({ .withSecret('S3_AUDIT_LOG_BUCKET_NAME', s3AuditLog.secret, 'bucket') .withSecret('S3_AUDIT_LOG_ENDPOINT', s3AuditLog.secret, 'endpoint') // Auth - .withSecret('SUPERTOKENS_API_KEY', supertokens.secret, 'apiKey') .withSecret('AUTH_GITHUB_CLIENT_ID', githubOAuthSecret, 'clientId') .withSecret('AUTH_GITHUB_CLIENT_SECRET', githubOAuthSecret, 'clientSecret') .withSecret('AUTH_GOOGLE_CLIENT_ID', googleOAuthSecret, 'clientId') diff --git a/deployment/services/supertokens.ts b/deployment/services/supertokens.ts deleted file mode 100644 index eb7ccf17f65..00000000000 --- a/deployment/services/supertokens.ts +++ /dev/null @@ -1,109 +0,0 @@ -import * as kx from '@pulumi/kubernetesx'; -import * as pulumi from '@pulumi/pulumi'; -import * as random from '@pulumi/random'; -import { serviceLocalEndpoint } from '../utils/local-endpoint'; -import { ServiceSecret } from '../utils/secrets'; -import { createService } from '../utils/service-deployment'; -import { Environment } from './environment'; -import { Postgres } from './postgres'; - -export class SupertokensSecret extends ServiceSecret<{ - apiKey: string | pulumi.Output; -}> {} - -export function deploySuperTokens( - postgres: Postgres, - resourceOptions: { - dependencies: pulumi.Resource[]; - }, - environment: Environment, -) { - const supertokensApiKey = new random.RandomPassword('supertokens-api-key', { - length: 31, - special: false, - }).result; - - const secret = new SupertokensSecret('supertokens', { - apiKey: supertokensApiKey, - }); - - const port = 3567; - const pb = new kx.PodBuilder({ - restartPolicy: 'Always', - containers: [ - { - image: 'registry.supertokens.io/supertokens/supertokens-postgresql:9.3', - name: 'supertokens', - ports: { - http: port, - }, - startupProbe: { - initialDelaySeconds: 15, - periodSeconds: 20, - failureThreshold: 5, - timeoutSeconds: 5, - httpGet: { - path: '/hello', - port, - }, - }, - readinessProbe: { - initialDelaySeconds: 5, - periodSeconds: 20, - failureThreshold: 5, - timeoutSeconds: 5, - httpGet: { - path: '/hello', - port, - }, - }, - livenessProbe: { - initialDelaySeconds: 3, - periodSeconds: 20, - failureThreshold: 10, - timeoutSeconds: 5, - httpGet: { - path: '/hello', - port, - }, - }, - env: { - POSTGRESQL_TABLE_NAMES_PREFIX: 'supertokens', - POSTGRESQL_CONNECTION_URI: { - secretKeyRef: { - name: postgres.secret.record.metadata.name, - key: 'connectionStringPostgresql', - }, - }, - API_KEYS: { - secretKeyRef: { - name: secret.record.metadata.name, - key: 'apiKey', - }, - }, - }, - }, - ], - }); - - const deployment = new kx.Deployment( - 'supertokens', - { - spec: pb.asDeploymentSpec({ replicas: environment.podsConfig.supertokens.replicas }), - }, - { - dependsOn: resourceOptions.dependencies, - }, - ); - - const service = createService('supertokens', deployment); - - return { - deployment, - service, - localEndpoint: serviceLocalEndpoint(service), - secret, - }; -} - -export type Supertokens = ReturnType; diff --git a/docker/docker-compose.community.yml b/docker/docker-compose.community.yml index eeafc15ea28..ff9a2a883e6 100644 --- a/docker/docker-compose.community.yml +++ b/docker/docker-compose.community.yml @@ -89,22 +89,6 @@ services: volumes: - './.hive/redis/db:/bitnami/redis/data' - supertokens: - image: registry.supertokens.io/supertokens/supertokens-postgresql:9.3 - depends_on: - db: - condition: service_healthy - networks: - - 'stack' - environment: - POSTGRESQL_USER: '${POSTGRES_USER}' - POSTGRESQL_PASSWORD: '${POSTGRES_PASSWORD}' - POSTGRESQL_DATABASE_NAME: '${POSTGRES_DB}' - POSTGRESQL_TABLE_NAMES_PREFIX: 'supertokens' - POSTGRESQL_HOST: db - POSTGRESQL_PORT: 5432 - API_KEYS: '${SUPERTOKENS_API_KEY}' - s3: image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z command: server /data --console-address ":9001" @@ -234,9 +218,9 @@ services: # Auth AUTH_ORGANIZATION_OIDC: '1' AUTH_REQUIRE_EMAIL_VERIFICATION: '0' - SUPERTOKENS_CONNECTION_URI: http://supertokens:3567 - SUPERTOKENS_API_KEY: '${SUPERTOKENS_API_KEY}' GRAPHQL_PUBLIC_ORIGIN: http://localhost:8082 + SUPERTOKENS_REFRESH_TOKEN_KEY: '${SUPERTOKENS_REFRESH_TOKEN_KEY}' + SUPERTOKENS_ACCESS_TOKEN_KEY: '${SUPERTOKENS_ACCESS_TOKEN_KEY}' # Tracing OPENTELEMETRY_COLLECTOR_ENDPOINT: '${OPENTELEMETRY_COLLECTOR_ENDPOINT:-}' SENTRY: '${SENTRY:-0}' diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 44f3852c133..d6333266835 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -123,25 +123,6 @@ services: volumes: - ./.hive-dev/broker/db:/var/lib/kafka/data - supertokens: - image: registry.supertokens.io/supertokens/supertokens-postgresql:9.3 - mem_limit: 300m - depends_on: - db: - condition: service_healthy - networks: - - 'stack' - ports: - - '3567:3567' - environment: - POSTGRESQL_USER: postgres - POSTGRESQL_PASSWORD: postgres - POSTGRESQL_DATABASE_NAME: registry - POSTGRESQL_TABLE_NAMES_PREFIX: 'supertokens' - POSTGRESQL_HOST: db - POSTGRESQL_PORT: 5432 - API_KEYS: bubatzbieber6942096420 - oidc-server-mock: image: ghcr.io/soluto/oidc-server-mock:0.8.6 mem_limit: 200m diff --git a/docker/docker-compose.end2end.yml b/docker/docker-compose.end2end.yml index 5f80b24b467..e45bba95bc7 100644 --- a/docker/docker-compose.end2end.yml +++ b/docker/docker-compose.end2end.yml @@ -33,10 +33,6 @@ services: networks: - 'stack' - supertokens: - ports: - - '3567:3567' - db: ports: - '5432:5432' diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 64aca349503..57484e81084 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -46,64 +46,6 @@ Add "user" field to ./docker/docker-compose.dev.yml - Open the UI (`http://localhost:3000` by default) and Sign in with any of the identity provider - Once this is done, you should be able to log in and use the project -#### Possible Issues During Setup - -If you encounter an error such as: - -``` -error: relation "supertokens_*" does not exist -``` - -it usually means that the **Supertokens database tables** were not initialized correctly. - -##### Steps to fix it - -1. **Ensure no local PostgreSQL instance is running** - - - The local PostgreSQL service on your machine might conflict with the one running in Docker. - - Stop any locally running PostgreSQL service and make sure the database used by Hive is the one - from Docker Compose. - -2. **Handle possible race conditions between `db` and `supertokens` containers** - - - This issue may occur if `supertokens` starts before the `db` container is fully initialized. - - To fix: - 1. Stop all running containers: - ```bash - docker compose -f ./docker/docker-compose.dev.yml down - ``` - 2. Start only the database: - ```bash - docker compose -f ./docker/docker-compose.dev.yml up db - ``` - 3. Wait until the database is ready (you should see “database system is ready to accept - connections” in logs). - 4. Start the `supertokens` service: - ```bash - docker compose -f ./docker/docker-compose.dev.yml up supertokens - ``` - 5. Once `supertokens` successfully creates all the tables, start the rest of the containers: - ```bash - docker compose -f ./docker/docker-compose.dev.yml up -d - ``` - -3. **If only Supertokens tables were created** - - Run the setup command again to ensure all services are initialized properly: - ```bash - pnpm local:setup - ``` - - Then restart the Hive Console using the VSCode “Start Hive” button. - -After completing these steps, reload the Hive UI at [http://localhost:3000](http://localhost:3000), -and you should be able to log in successfully. - -- Once you generate the token against your organization/personal account in hive, the same can be - added locally to `hive.json` within `packages/libraries/cli` which can be used to interact via the - hive cli with the registry (Use `http://localhost:3001/graphql` as the `registry.endpoint` value - in `hive.json`) -- Now you can use Hive locally. All other steps in this document are optional and only necessary if - you work on specific features. - ## Development Seed We have a script to feed your local instance of Hive with initial seed data. This step is optional. diff --git a/docs/architecture.puml b/docs/architecture.puml index 2f50f1aef92..41386aefb94 100644 --- a/docs/architecture.puml +++ b/docs/architecture.puml @@ -5,7 +5,6 @@ actor "CLI User" as cliuser actor "Running Server" as gqlserver component zookeeper -component supertokens queue kafka @@ -28,7 +27,6 @@ storageSvc ---d-> Postgres kafka -l-> zookeeper -app --> supertokens app --> server app --> emails @@ -49,9 +47,6 @@ server -d-> tokens server -d-> webhooks server -d-> schema server -d-> emails -server -d-> supertokens - -supertokens -> Postgres uiuser --> app cliuser --> server @@ -60,4 +55,4 @@ emails -[hidden]-> webhooks usage ----> tokens -@enduml \ No newline at end of file +@enduml diff --git a/integration-tests/.env b/integration-tests/.env index 641a2f10212..5b91792fcc0 100644 --- a/integration-tests/.env +++ b/integration-tests/.env @@ -21,6 +21,6 @@ CLICKHOUSE_ASYNC_INSERT_MAX_DATA_SIZE=1000 EXTERNAL_COMPOSITION_SECRET=secretsecret LIMIT_CACHE_UPDATE_INTERVAL_MS=2000 NODE_OPTIONS=--enable-source-maps -SUPERTOKENS_AT_HOME=0 +SUPERTOKENS_AT_HOME=1 SUPERTOKENS_REFRESH_TOKEN_KEY=1000:15e5968d52a9a48921c1c63d88145441a8099b4a44248809a5e1e733411b3eeb80d87a6e10d3390468c222f6a91fef3427f8afc8b91ea1820ab10c7dfd54a268:39f72164821e08edd6ace99f3bd4e387f45fa4221fe3cd80ecfee614850bc5d647ac2fddc14462a00647fff78c22e8d01bc306a91294f5b889a90ba891bf0aa0 SUPERTOKENS_ACCESS_TOKEN_KEY=s-aaa5da0d-8678-46ef-b56d-9cd19e1cdb5b|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 diff --git a/integration-tests/docker-compose.integration.yaml b/integration-tests/docker-compose.integration.yaml index 70ff54d9a8a..5344691e34e 100644 --- a/integration-tests/docker-compose.integration.yaml +++ b/integration-tests/docker-compose.integration.yaml @@ -165,9 +165,9 @@ services: WEB_APP_URL: '${HIVE_APP_BASE_URL}' AUTH_ORGANIZATION_OIDC: '1' AUTH_REQUIRE_EMAIL_VERIFICATION: '0' - SUPERTOKENS_CONNECTION_URI: http://supertokens:3567 - SUPERTOKENS_API_KEY: '${SUPERTOKENS_API_KEY}' GRAPHQL_PUBLIC_ORIGIN: http://localhost:8082 + SUPERTOKENS_REFRESH_TOKEN_KEY: '1000:15e5968d52a9a48921c1c63d88145441a8099b4a44248809a5e1e733411b3eeb80d87a6e10d3390468c222f6a91fef3427f8afc8b91ea1820ab10c7dfd54a268:39f72164821e08edd6ace99f3bd4e387f45fa4221fe3cd80ecfee614850bc5d647ac2fddc14462a00647fff78c22e8d01bc306a91294f5b889a90ba891bf0aa0' + SUPERTOKENS_ACCESS_TOKEN_KEY: 's-aaa5da0d-8678-46ef-b56d-9cd19e1cdb5b|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' broker: image: redpandadata/redpanda:latest @@ -210,10 +210,6 @@ services: - ./.hive/clickhouse/db:/var/lib/clickhouse - ./configs/clickhouse:/etc/clickhouse-server/conf.d - supertokens: - ports: - - '3567:3567' - usage: environment: COMMERCE_ENDPOINT: '${COMMERCE_ENDPOINT}' diff --git a/integration-tests/testkit/auth.ts b/integration-tests/testkit/auth.ts index 2a159684910..225f3831e33 100644 --- a/integration-tests/testkit/auth.ts +++ b/integration-tests/testkit/auth.ts @@ -115,103 +115,6 @@ const createSessionAtHome = async ( }; }; -const createSessionPayload = (payload: { - superTokensUserId: string; - userId: string; - oidcIntegrationId: string | null; - email: string; -}) => ({ - version: '2', - superTokensUserId: payload.superTokensUserId, - userId: payload.userId, - oidcIntegrationId: payload.oidcIntegrationId, - email: payload.email, -}); - -const CreateSessionModel = z.object({ - accessToken: z.object({ - token: z.string(), - }), - refreshToken: z.object({ - token: z.string(), - }), -}); - -const createSession = async ( - superTokensUserId: string, - email: string, - oidcIntegrationId: string | null, -) => { - try { - const graphqlAddress = await getServiceHost('server', 8082); - - const internalApi = createTRPCProxyClient({ - links: [ - httpLink({ - url: `http://${graphqlAddress}/trpc`, - fetch, - }), - ], - }); - - const ensureUserResult = await internalApi.ensureUser.mutate({ - superTokensUserId, - email, - oidcIntegrationId, - firstName: null, - lastName: null, - }); - if (!ensureUserResult.ok) { - throw new Error(ensureUserResult.reason); - } - - const sessionData = createSessionPayload({ - superTokensUserId, - userId: ensureUserResult.user.id, - oidcIntegrationId, - email, - }); - const payload = { - enableAntiCsrf: false, - userId: superTokensUserId, - userDataInDatabase: sessionData, - userDataInJWT: sessionData, - }; - - const response = await fetch( - `${ensureEnv('SUPERTOKENS_CONNECTION_URI')}/appid-public/public/recipe/session`, - { - method: 'POST', - headers: { - 'content-type': 'application/json; charset=UTF-8', - 'api-key': ensureEnv('SUPERTOKENS_API_KEY'), - rid: 'session', - 'cdi-version': '4.0', - }, - body: JSON.stringify(payload), - }, - ); - const body = await response.text(); - - if (response.status !== 200) { - throw new Error(`Create session failed. ${response.status}.\n ${body}`); - } - - const data = CreateSessionModel.parse(JSON.parse(body)); - - /** - * These are the required cookies that need to be set. - */ - return { - access_token: data.accessToken.token, - refresh_token: data.refreshToken.token, - }; - } catch (e) { - console.warn(`Failed to create session:`, e); - throw e; - } -}; - const password = 'ilikebigturtlesandicannotlie47'; const hashedPassword = await hashPassword(password); @@ -231,33 +134,15 @@ export async function authenticate( email: string, oidcIntegrationId?: string, ): Promise<{ access_token: string; refresh_token: string }> { - if (process.env.SUPERTOKENS_AT_HOME === '1') { - const supertokensStore = new SuperTokensStore(pool, new NoopLogger()); - if (!tokenResponsePromise[email]) { - tokenResponsePromise[email] = supertokensStore.createEmailPasswordUser({ - email, - passwordHash: hashedPassword, - }); - } - - const user = await tokenResponsePromise[email]!; - - return await createSessionAtHome( - supertokensStore, - user.userId, + const supertokensStore = new SuperTokensStore(pool, new NoopLogger()); + if (!tokenResponsePromise[email]) { + tokenResponsePromise[email] = supertokensStore.createEmailPasswordUser({ email, - oidcIntegrationId ?? null, - ); + passwordHash: hashedPassword, + }); } - if (!tokenResponsePromise[email]) { - tokenResponsePromise[email] = signUpUserViaEmail(email, password).then(res => ({ - email: res.user.email, - userId: res.user.id, - })); - } + const user = await tokenResponsePromise[email]!; - return tokenResponsePromise[email]!.then(data => - createSession(data.userId, data.email, oidcIntegrationId ?? null), - ); + return await createSessionAtHome(supertokensStore, user.userId, email, oidcIntegrationId ?? null); } diff --git a/package.json b/package.json index 20b43e8e1ce..0dacda7e93d 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,6 @@ "overrides.ip": "There is no update with fix for ip package, we use fork https://github.com/indutny/node-ip/issues/150#issuecomment-2325961380", "overrides.miniflare@3>undici": "To address CVE: https://github.com/graphql-hive/console/security/dependabot/439", "overrides.tar-fs": "https://github.com/graphql-hive/console/security/dependabot/290", - "overrides.nodemailer@^6.0.0": "supertokens-node override for vulnerable version", "overrides.@types/nodemailer>@aws-sdk/client-sesv2": "@types/nodemailer depends on some AWS stuff that causes the 3.x.x version to stick around. We don't need that dependency. (https://github.com/graphql-hive/console/security/dependabot/436)", "overrides.tar@6.x.x": "address https://github.com/graphql-hive/console/security/dependabot/443", "overrides.diff@<8.0.3": "address https://github.com/graphql-hive/console/security/dependabot/438", @@ -145,7 +144,6 @@ "@tailwindcss/node>tailwindcss": "4.1.18", "@tailwindcss/vite>tailwindcss": "4.1.18", "estree-util-value-to-estree": "^3.3.3", - "nodemailer@^6.0.0": "^7.0.11", "@types/nodemailer>@aws-sdk/client-sesv2": "-", "tar@6.x.x": "^7.5.3", "diff@<8.0.3": "^8.0.3", diff --git a/packages/migrations/src/actions/2024.11.11T00-00-00.supertokens-8.0.ts b/packages/migrations/src/actions/2024.11.11T00-00-00.supertokens-8.0.ts deleted file mode 100644 index 32648155cfe..00000000000 --- a/packages/migrations/src/actions/2024.11.11T00-00-00.supertokens-8.0.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { type MigrationExecutor } from '../pg-migrator'; - -export default { - name: '2024.11.11T00-00-00.supertokens-8.0.ts', - run: ({ sql }) => sql` - ALTER TABLE IF EXISTS "supertokens_user_roles" - DROP CONSTRAINT IF EXISTS "supertokens_user_roles_role_fkey"; - `, -} satisfies MigrationExecutor; diff --git a/packages/migrations/src/actions/2024.11.12T00-00-00.supertokens-9.0.ts b/packages/migrations/src/actions/2024.11.12T00-00-00.supertokens-9.0.ts deleted file mode 100644 index 20f3c17e65c..00000000000 --- a/packages/migrations/src/actions/2024.11.12T00-00-00.supertokens-9.0.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { type MigrationExecutor } from '../pg-migrator'; - -export default { - name: '2024.11.12T00-00-00.supertokens-9.0.ts', - run: ({ sql }) => sql` - ALTER TABLE IF EXISTS "supertokens_totp_user_devices" - ADD COLUMN IF NOT EXISTS "created_at" BIGINT default 0; - ALTER TABLE IF EXISTS "totp_user_devices" - ALTER COLUMN "created_at" DROP DEFAULT; - `, -} satisfies MigrationExecutor; diff --git a/packages/migrations/src/actions/2024.11.12T00-00-00.supertokens-9.1.ts b/packages/migrations/src/actions/2024.11.12T00-00-00.supertokens-9.1.ts deleted file mode 100644 index 61f0b58a0a7..00000000000 --- a/packages/migrations/src/actions/2024.11.12T00-00-00.supertokens-9.1.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { type MigrationExecutor } from '../pg-migrator'; - -export default { - name: '2024.11.12T00-00-00.supertokens-9.1.ts', - run: ({ sql }) => sql` - ALTER TABLE IF EXISTS "supertokens_tenant_configs" - ADD COLUMN IF NOT EXISTS "is_first_factors_null" BOOLEAN DEFAULT TRUE; - ALTER TABLE IF EXISTS "supertokens_tenant_configs" - ALTER COLUMN "is_first_factors_null" DROP DEFAULT; - `, -} satisfies MigrationExecutor; diff --git a/packages/migrations/src/actions/2024.11.12T00-00-00.supertokens-9.2.ts b/packages/migrations/src/actions/2024.11.12T00-00-00.supertokens-9.2.ts deleted file mode 100644 index 81d3099a82c..00000000000 --- a/packages/migrations/src/actions/2024.11.12T00-00-00.supertokens-9.2.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { type MigrationExecutor } from '../pg-migrator'; - -export default { - name: '2024.11.12T00-00-00.supertokens-9.2.ts', - run: ({ sql }) => sql` - DO $$ - BEGIN - IF (SELECT to_regclass('supertokens_user_last_active') IS NOT null) - THEN - CREATE INDEX IF NOT EXISTS "supertokens_user_last_active_last_active_time_index" - ON "supertokens_user_last_active" ("last_active_time" DESC, "app_id" DESC); - END IF; - END $$; - `, -} satisfies MigrationExecutor; diff --git a/packages/migrations/src/actions/2024.11.12T00-00-00.supertokens-9.3.ts b/packages/migrations/src/actions/2024.11.12T00-00-00.supertokens-9.3.ts deleted file mode 100644 index f64ec1c4653..00000000000 --- a/packages/migrations/src/actions/2024.11.12T00-00-00.supertokens-9.3.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { type MigrationExecutor } from '../pg-migrator'; - -export default { - name: '2024.11.12T00-00-00.supertokens-9.3.ts', - run: ({ sql }) => sql` - DO $$ - BEGIN - IF (SELECT to_regclass('supertokens_apps') IS NOT null) - THEN - CREATE TABLE IF NOT EXISTS "supertokens_oauth_clients" ( - "app_id" VARCHAR(64), - "client_id" VARCHAR(255) NOT NULL, - "is_client_credentials_only" BOOLEAN NOT NULL, - PRIMARY KEY ("app_id", "client_id"), - FOREIGN KEY("app_id") REFERENCES "supertokens_apps"("app_id") ON DELETE CASCADE - ); - CREATE TABLE IF NOT EXISTS "supertokens_oauth_sessions" ( - "gid" VARCHAR(255), - "app_id" VARCHAR(64) DEFAULT 'public', - "client_id" VARCHAR(255) NOT NULL, - "session_handle" VARCHAR(128), - "external_refresh_token" VARCHAR(255) UNIQUE, - "internal_refresh_token" VARCHAR(255) UNIQUE, - "jti" TEXT NOT NULL, - "exp" BIGINT NOT NULL, - PRIMARY KEY ("gid"), - FOREIGN KEY("app_id", "client_id") REFERENCES "supertokens_oauth_clients"("app_id", "client_id") ON DELETE CASCADE - ); - - CREATE INDEX IF NOT EXISTS "supertokens_oauth_session_exp_index" - ON "supertokens_oauth_sessions"("exp" DESC); - CREATE INDEX IF NOT EXISTS "supertokens_oauth_session_external_refresh_token_index" - ON "supertokens_oauth_sessions"("app_id", "external_refresh_token" DESC); - - CREATE TABLE IF NOT EXISTS "supertokens_oauth_m2m_tokens" ( - "app_id" VARCHAR(64) DEFAULT 'public', - "client_id" VARCHAR(255) NOT NULL, - "iat" BIGINT NOT NULL, - "exp" BIGINT NOT NULL, - PRIMARY KEY ("app_id", "client_id", "iat"), - FOREIGN KEY("app_id", "client_id") REFERENCES "supertokens_oauth_clients"("app_id", "client_id") ON DELETE CASCADE - ); - - CREATE INDEX IF NOT EXISTS "supertokens_oauth_m2m_token_iat_index" - ON "supertokens_oauth_m2m_tokens"("iat" DESC, "app_id" DESC); - CREATE INDEX IF NOT EXISTS "supertokens_oauth_m2m_token_exp_index" - ON "supertokens_oauth_m2m_tokens"("exp" DESC); - - CREATE TABLE IF NOT EXISTS "supertokens_oauth_logout_challenges" ( - "app_id" VARCHAR(64) DEFAULT 'public', - "challenge" VARCHAR(128) NOT NULL, - "client_id" VARCHAR(255) NOT NULL, - "post_logout_redirect_uri" VARCHAR(1024), - "session_handle" VARCHAR(128), - "state" VARCHAR(128), - "time_created" BIGINT NOT NULL, - PRIMARY KEY ("app_id", "challenge"), - FOREIGN KEY("app_id", "client_id") REFERENCES "supertokens_oauth_clients"("app_id", "client_id") ON DELETE CASCADE - ); - - CREATE INDEX IF NOT EXISTS "oauth_logout_challenges_time_created_index" - ON "supertokens_oauth_logout_challenges"("time_created" DESC); - END IF; - END $$; - `, -} satisfies MigrationExecutor; diff --git a/packages/migrations/src/environment.ts b/packages/migrations/src/environment.ts index c6bbc27376e..95d3e1b80de 100644 --- a/packages/migrations/src/environment.ts +++ b/packages/migrations/src/environment.ts @@ -34,7 +34,6 @@ const EnvironmentModel = zod.object({ .union([zod.literal('1'), zod.literal('0')]) .optional(), GRAPHQL_HIVE_ENVIRONMENT: emptyString(zod.enum(['prod', 'staging', 'dev']).optional()), - SUPERTOKENS_AT_HOME: zod.union([zod.literal('1'), zod.literal('0')]).optional(), }); const PostgresModel = zod.object({ @@ -115,5 +114,4 @@ export const env = { isClickHouseMigrator: base.CLICKHOUSE_MIGRATOR === 'up', isHiveCloud: base.CLICKHOUSE_MIGRATOR_GRAPHQL_HIVE_CLOUD === '1', hiveCloudEnvironment: base.GRAPHQL_HIVE_ENVIRONMENT ?? null, - useSupertokensAtHome: base.SUPERTOKENS_AT_HOME === '1', } as const; diff --git a/packages/migrations/src/run-pg-migrations.ts b/packages/migrations/src/run-pg-migrations.ts index f888f29542d..7a34bfeed21 100644 --- a/packages/migrations/src/run-pg-migrations.ts +++ b/packages/migrations/src/run-pg-migrations.ts @@ -67,7 +67,6 @@ import migration_2024_06_11T10_10_00_ms_teams_webhook from './actions/2024.06.11 import migration_2024_07_16T13_44_00_oidc_only_access from './actions/2024.07.16T13-44-00.oidc-only-access'; import migration_2024_07_17T00_00_00_app_deployments from './actions/2024.07.17T00-00-00.app-deployments'; import migration_2024_07_23T_09_36_00_schema_cleanup_tracker from './actions/2024.07.23T09.36.00.schema-cleanup-tracker'; -import { env } from './environment'; import { runMigrations } from './pg-migrator'; export const runPGMigrations = async (args: { slonik: DatabasePool; runTo?: string }) => @@ -143,11 +142,6 @@ export const runPGMigrations = async (args: { slonik: DatabasePool; runTo?: stri migration_2024_07_16T13_44_00_oidc_only_access, migration_2024_07_17T00_00_00_app_deployments, migration_2024_07_23T_09_36_00_schema_cleanup_tracker, - await import('./actions/2024.11.11T00-00-00.supertokens-8.0'), - await import('./actions/2024.11.12T00-00-00.supertokens-9.0'), - await import('./actions/2024.11.12T00-00-00.supertokens-9.1'), - await import('./actions/2024.11.12T00-00-00.supertokens-9.2'), - await import('./actions/2024.11.12T00-00-00.supertokens-9.3'), await import('./actions/2024.12.23T00-00-00.improve-version-index'), await import('./actions/2024.12.24T00-00-00.improve-version-index-2'), await import('./actions/2024.12.27T00.00.00.create-preflight-scripts'), @@ -186,8 +180,6 @@ export const runPGMigrations = async (args: { slonik: DatabasePool; runTo?: stri await import('./actions/2026.02.06T00-00-00.zendesk-unique'), await import('./actions/2026.01.30T10-00-00.oidc-require-invitation'), await import('./actions/2026.02.19T00-00-00.saved-filter-permission'), - ...(env.useSupertokensAtHome - ? [await import('./actions/2026.02.18T00-00-00.ensure-supertokens-tables')] - : []), + await import('./actions/2026.02.18T00-00-00.ensure-supertokens-tables'), ], }); diff --git a/packages/services/api/package.json b/packages/services/api/package.json index 1f4db07de7c..b950f9a11ad 100644 --- a/packages/services/api/package.json +++ b/packages/services/api/package.json @@ -72,7 +72,6 @@ "redlock": "5.0.0-beta.2", "slonik": "30.4.4", "stripe": "17.5.0", - "supertokens-node": "16.7.5", "tslib": "2.8.1", "undici": "7.18.2", "vitest": "4.0.9", diff --git a/packages/services/api/src/modules/auth/lib/supertokens-strategy.ts b/packages/services/api/src/modules/auth/lib/supertokens-strategy.ts index 9b6e5c256a2..7e70e08a87a 100644 --- a/packages/services/api/src/modules/auth/lib/supertokens-strategy.ts +++ b/packages/services/api/src/modules/auth/lib/supertokens-strategy.ts @@ -1,9 +1,7 @@ import c from 'node:crypto'; import { parse as parseCookie } from 'cookie-es'; -import SessionNode from 'supertokens-node/recipe/session/index.js'; import * as zod from 'zod'; import type { FastifyReply, FastifyRequest } from '@hive/service-common'; -import { captureException } from '@sentry/node'; import { AccessError, HiveError, OIDCRequiredError } from '../../../shared/errors'; import { isUUID } from '../../../shared/is-uuid'; import { OrganizationMembers } from '../../organization/providers/organization-members'; @@ -169,14 +167,14 @@ export class SuperTokensUserAuthNStrategy extends AuthNStrategy @@ -460,21 +448,7 @@ export async function main() { }); } - if (env.supertokens.type == 'core') { - initSupertokens({ - storage, - crypto, - logger: server.log, - redis, - taskScheduler, - broadcastLog, - }); - } - await server.register(formDataPlugin); - if (env.supertokens.type == 'core') { - await server.register(supertokensFastifyPlugin); - } await registerTRPC(server, { router: internalApiRouter, @@ -583,18 +557,16 @@ export async function main() { return; }); - if (env.supertokens.type === 'atHome') { - await registerSupertokensAtHome( - server, - storage, - registry.injector.get(TaskScheduler), - registry.injector.get(CryptoProvider), - registry.injector.get(RedisRateLimiter), - registry.injector.get(OAuthCache), - broadcastLog, - env.supertokens.secrets, - ); - } + await registerSupertokensAtHome( + server, + storage, + registry.injector.get(TaskScheduler), + registry.injector.get(CryptoProvider), + registry.injector.get(RedisRateLimiter), + registry.injector.get(OAuthCache), + broadcastLog, + env.supertokens.secrets, + ); if (env.cdn.providers.api !== null) { const s3 = { diff --git a/packages/services/server/src/supertokens-at-home.ts b/packages/services/server/src/supertokens-at-home.ts index e7cbde84d43..c1d4556b7b1 100644 --- a/packages/services/server/src/supertokens-at-home.ts +++ b/packages/services/server/src/supertokens-at-home.ts @@ -25,7 +25,8 @@ import { TaskScheduler } from '@hive/workflows/kit'; import { PasswordResetTask } from '@hive/workflows/tasks/password-reset'; import { env } from './environment'; import { createNewSession, validatePassword } from './supertokens-at-home/shared'; -import { type BroadcastOIDCIntegrationLog } from './supertokens/oidc-provider'; + +type BroadcastOIDCIntegrationLog = (oidcOrganizationId: string, message: string) => void; /** * Registers the routes of the Supertokens at Home implementation to a fastify instance. @@ -735,7 +736,7 @@ export async function registerSupertokensAtHome( const oidClientConfig = new oidClient.Configuration( { - issuer: oidcIntegration.id, + issuer: 'noop', authorization_endpoint: oidcIntegration.authorizationEndpoint, userinfo_endpoint: oidcIntegration.userinfoEndpoint, token_endpoint: oidcIntegration.tokenEndpoint, diff --git a/packages/services/server/src/supertokens.ts b/packages/services/server/src/supertokens.ts index a48748a0796..2a1c108f5e3 100644 --- a/packages/services/server/src/supertokens.ts +++ b/packages/services/server/src/supertokens.ts @@ -1,495 +1,5 @@ import type { FastifyBaseLogger } from 'fastify'; -import type Redis from 'ioredis'; -import { CryptoProvider } from 'packages/services/api/src/modules/shared/providers/crypto'; -import { OverrideableBuilder } from 'supertokens-js-override/lib/build/index.js'; -import supertokens from 'supertokens-node'; -import SessionNode from 'supertokens-node/recipe/session/index.js'; -import type { ProviderInput } from 'supertokens-node/recipe/thirdparty/types'; -import ThirdPartyEmailPasswordNode from 'supertokens-node/recipe/thirdpartyemailpassword/index.js'; -import type { TypeInput as ThirdPartEmailPasswordTypeInput } from 'supertokens-node/recipe/thirdpartyemailpassword/types'; -import type { TypeInput } from 'supertokens-node/types'; -import zod from 'zod'; import { type Storage } from '@hive/api'; -import { TaskScheduler } from '@hive/workflows/kit'; -import { PasswordResetTask } from '@hive/workflows/tasks/password-reset'; -import { createInternalApiCaller } from './api'; -import { env } from './environment'; -import { - createOIDCSuperTokensProvider, - getLoggerFromUserContext, - getOIDCSuperTokensOverrides, - type BroadcastOIDCIntegrationLog, -} from './supertokens/oidc-provider'; -import { createThirdPartyEmailPasswordNodeOktaProvider } from './supertokens/okta-provider'; - -const SuperTokensSessionPayloadV2Model = zod.object({ - version: zod.literal('2'), - superTokensUserId: zod.string(), - email: zod.string(), - userId: zod.string(), - oidcIntegrationId: zod.string().nullable(), -}); - -type SuperTokensSessionPayload = zod.TypeOf; - -export const backendConfig = (requirements: { - storage: Storage; - crypto: CryptoProvider; - logger: FastifyBaseLogger; - broadcastLog: BroadcastOIDCIntegrationLog; - redis: Redis; - taskScheduler: TaskScheduler; -}): TypeInput => { - const { logger } = requirements; - - const internalApi = createInternalApiCaller({ - storage: requirements.storage, - crypto: requirements.crypto, - }); - const providers: ProviderInput[] = []; - - if (env.auth.github) { - providers.push({ - config: { - thirdPartyId: 'github', - clients: [ - { - scope: ['read:user', 'user:email'], - clientId: env.auth.github.clientId, - clientSecret: env.auth.github.clientSecret, - }, - ], - }, - }); - } - if (env.auth.google) { - providers.push({ - config: { - thirdPartyId: 'google', - clients: [ - { - clientId: env.auth.google.clientId, - clientSecret: env.auth.google.clientSecret, - scope: [ - 'https://www.googleapis.com/auth/userinfo.email', - 'https://www.googleapis.com/auth/userinfo.profile', - 'openid', - ], - }, - ], - }, - }); - } - - if (env.auth.okta) { - providers.push(createThirdPartyEmailPasswordNodeOktaProvider(env.auth.okta)); - } - - if (env.auth.organizationOIDC) { - providers.push( - createOIDCSuperTokensProvider({ - internalApi, - broadcastLog: requirements.broadcastLog, - }), - ); - } - - logger.info('SuperTokens providers: %s', providers.map(p => p.config.thirdPartyId).join(', ')); - logger.info('SuperTokens websiteDomain: %s', env.hiveServices.webApp.url); - logger.info('SuperTokens apiDomain: %s', env.graphql.origin); - - return { - framework: 'fastify', - supertokens: { - connectionURI: env.supertokens.connectionURI ?? '', - apiKey: env.supertokens.apiKey, - }, - telemetry: false, - appInfo: { - // learn more about this on https://supertokens.com/docs/thirdpartyemailpassword/appinfo - appName: 'GraphQL Hive', - apiDomain: env.graphql.origin, - websiteDomain: env.hiveServices.webApp.url, - apiBasePath: '/auth-api', - websiteBasePath: '/auth', - }, - recipeList: [ - ThirdPartyEmailPasswordNode.init({ - providers, - signUpFeature: { - formFields: [ - { - id: 'firstName', - // optional because of OIDC integration - optional: true, - }, - { - id: 'lastName', - optional: true, - }, - ], - }, - emailDelivery: { - override: originalImplementation => ({ - ...originalImplementation, - async sendEmail(input) { - if (input.type === 'PASSWORD_RESET') { - await requirements.taskScheduler.scheduleTask(PasswordResetTask, { - user: { - id: input.user.id, - email: input.user.email, - }, - passwordResetLink: input.passwordResetLink, - }); - - return Promise.resolve(); - } - - return Promise.reject(new Error('Unsupported email type.')); - }, - }), - }, - override: composeSuperTokensOverrides([ - getEnsureUserOverrides(internalApi, requirements.redis), - env.auth.organizationOIDC ? getOIDCSuperTokensOverrides() : null, - ]), - }), - SessionNode.init({ - override: { - functions: originalImplementation => ({ - ...originalImplementation, - async createNewSession(input) { - console.log(`Creating a new session for "${input.userId}"`); - const user = await supertokens.getUser(input.userId, input.userContext); - - if (!user) { - console.log(`Failed to find user with id "${input.userId}"`); - throw new Error( - `SuperTokens: Creating a new session failed. Could not find user with id ${input.userId}.`, - ); - } - - const ensureUserResult = await internalApi.ensureUser({ - superTokensUserId: user.id, - email: user.emails[0], - oidcIntegrationId: input.userContext['oidcId'] ?? null, - firstName: null, - lastName: null, - }); - if (!ensureUserResult.ok) { - throw new SessionCreationError(ensureUserResult.reason); - } - - const payload: SuperTokensSessionPayload = { - version: '2', - superTokensUserId: input.userId, - userId: ensureUserResult.user.id, - oidcIntegrationId: input.userContext['oidcId'] ?? null, - email: user.emails[0], - }; - - input.accessTokenPayload = structuredClone(payload); - input.sessionDataInDatabase = structuredClone(payload); - - return originalImplementation.createNewSession(input); - }, - }), - }, - }), - ], - isInServerlessEnv: true, - }; -}; - -function extractIPFromUserContext(userContext: unknown): string { - const defaultIp = (userContext as any)._default.request.original.ip; - if (!env.supertokens?.rateLimit) { - return defaultIp; - } - - return ( - (userContext as any)._default.request.getHeaderValue(env.supertokens.rateLimit.ipHeaderName) ?? - defaultIp - ); -} - -function createRedisRateLimiter(redis: Redis, windowSeconds = 5 * 60, maxRequests = 10) { - async function isRateLimited(action: string, ip: string): Promise { - if (env.supertokens.rateLimit === null) { - return false; - } - - const key = `supertokens-rate-limit:${action}:${ip}`; - const current = await redis.incr(key); - if (current === 1) { - await redis.expire(key, windowSeconds); - } - if (current > maxRequests) { - return true; - } - - return false; - } - - return { isRateLimited }; -} - -const getEnsureUserOverrides = ( - internalApi: ReturnType, - redis: Redis, -): ThirdPartEmailPasswordTypeInput['override'] => ({ - apis: originalImplementation => ({ - ...originalImplementation, - emailPasswordSignUpPOST: async input => { - if (!originalImplementation.emailPasswordSignUpPOST) { - throw Error('emailPasswordSignUpPOST is not available'); - } - - const logger = getLoggerFromUserContext(input.userContext); - const ip = extractIPFromUserContext(input.userContext); - const rateLimiter = createRedisRateLimiter(redis); - - if (await rateLimiter.isRateLimited('sign-up', ip)) { - logger.debug('email password sign up rate limited (ip=%s)', ip); - return { - status: 'GENERAL_ERROR', - message: 'Please try again in a few minutes.', - }; - } - - try { - const response = await originalImplementation.emailPasswordSignUpPOST(input); - - const firstName = input.formFields.find(field => field.id === 'firstName')?.value ?? null; - const lastName = input.formFields.find(field => field.id === 'lastName')?.value ?? null; - - if (response.status === 'SIGN_UP_NOT_ALLOWED') { - return { - status: 'SIGN_UP_NOT_ALLOWED', - reason: 'Sign up not allowed.', - }; - } - - if (response.status === 'OK') { - const result = await internalApi.ensureUser({ - superTokensUserId: response.user.id, - email: response.user.emails[0], - oidcIntegrationId: null, - firstName, - lastName, - }); - - if (!result.ok) { - return { - status: 'SIGN_UP_NOT_ALLOWED', - reason: result.reason, - }; - } - } - - return response; - } catch (e) { - if (e instanceof SessionCreationError) { - return { - status: 'SIGN_UP_NOT_ALLOWED', - reason: e.reason, - }; - } - throw e; - } - }, - async emailPasswordSignInPOST(input) { - if (originalImplementation.emailPasswordSignInPOST === undefined) { - throw Error('Should never come here'); - } - - const logger = getLoggerFromUserContext(input.userContext); - const ip = extractIPFromUserContext(input.userContext); - const rateLimiter = createRedisRateLimiter(redis); - - if (await rateLimiter.isRateLimited('sign-in', ip)) { - logger.debug('email sign in rate limited (ip=%s)', ip); - return { - status: 'GENERAL_ERROR', - message: 'Please try again in a few minutes.', - }; - } - - try { - const response = await originalImplementation.emailPasswordSignInPOST(input); - - if (response.status === 'SIGN_IN_NOT_ALLOWED') { - return { - status: 'SIGN_IN_NOT_ALLOWED', - reason: 'Sign in not allowed.', - }; - } - - if (response.status === 'OK') { - const result = await internalApi.ensureUser({ - superTokensUserId: response.user.id, - email: response.user.emails[0], - oidcIntegrationId: null, - // They are not available during sign in. - firstName: null, - lastName: null, - }); - - if (!result.ok) { - return { - status: 'SIGN_IN_NOT_ALLOWED', - reason: result.reason, - }; - } - } - - return response; - } catch (e) { - if (e instanceof SessionCreationError) { - return { - status: 'SIGN_IN_NOT_ALLOWED', - reason: e.reason, - }; - } - throw e; - } - }, - async thirdPartySignInUpPOST(input) { - if (originalImplementation.thirdPartySignInUpPOST === undefined) { - throw Error('Should never come here'); - } - - function extractOidcId(args: typeof input) { - if (input.provider.id === 'oidc') { - const oidcId: unknown = args.userContext['oidcId']; - if (typeof oidcId === 'string') { - return oidcId; - } - } - return null; - } - - try { - const response = await originalImplementation.thirdPartySignInUpPOST(input); - - if (response.status === 'SIGN_IN_UP_NOT_ALLOWED') { - return { - status: 'SIGN_IN_UP_NOT_ALLOWED', - reason: 'Sign in not allowed.', - }; - } - - if (response.status === 'OK') { - const result = await internalApi.ensureUser({ - superTokensUserId: response.user.id, - email: response.user.emails[0], - oidcIntegrationId: extractOidcId(input), - // TODO: should we somehow extract the first and last name from the third party provider? - firstName: null, - lastName: null, - }); - - if (!result.ok) { - return { - status: 'SIGN_IN_UP_NOT_ALLOWED', - reason: result.reason, - }; - } - } - - return response; - } catch (e) { - if (e instanceof SessionCreationError) { - return { - status: 'SIGN_IN_UP_NOT_ALLOWED', - reason: e.reason, - }; - } - throw e; - } - }, - async passwordResetPOST(input) { - const logger = getLoggerFromUserContext(input.userContext); - const ip = extractIPFromUserContext(input.userContext); - const rateLimiter = createRedisRateLimiter(redis); - - if (await rateLimiter.isRateLimited('password-reset', ip)) { - logger.debug('password reset rate limited (ip=%s)', ip); - return { - status: 'GENERAL_ERROR', - message: 'Please try again in a few minutes.', - }; - } - - const result = await originalImplementation.passwordResetPOST!(input); - - // For security reasons we revoke all sessions when a password reset is performed. - if (result.status === 'OK' && result.user) { - await SessionNode.revokeAllSessionsForUser(result.user.id); - } - - return result; - }, - }), -}); - -const bindObjectFunctions = ( - obj: T, - bindTo: any, -) => { - return Object.fromEntries( - Object.entries(obj).map(([key, value]) => [key, value?.bind(bindTo)]), - ) as T; -}; - -/** - * Utility function for composing multiple (dynamic SuperTokens overrides). - */ -const composeSuperTokensOverrides = ( - overrides: Array, -) => ({ - apis( - originalImplementation: ReturnType< - Exclude['apis'], undefined> - >, - builder: OverrideableBuilder | undefined, - ) { - let impl = originalImplementation; - for (const override of overrides) { - if (typeof override?.apis === 'function') { - impl = bindObjectFunctions(override.apis(impl, builder), originalImplementation); - } - } - return impl; - }, - functions( - originalImplementation: ReturnType< - Exclude< - Exclude['functions'], - undefined - > - >, - ) { - let impl = originalImplementation; - for (const override of overrides) { - if (typeof override?.functions === 'function') { - impl = bindObjectFunctions(override.functions(impl), originalImplementation); - } - } - return impl; - }, -}); - -export function initSupertokens(requirements: { - storage: Storage; - crypto: CryptoProvider; - logger: FastifyBaseLogger; - broadcastLog: BroadcastOIDCIntegrationLog; - redis: Redis; - taskScheduler: TaskScheduler; -}) { - supertokens.init(backendConfig(requirements)); -} type OidcIdLookupResponse = | { @@ -525,9 +35,3 @@ export async function oidcIdLookup( id: oidcId, }; } - -class SessionCreationError extends Error { - constructor(public reason: string) { - super(reason); - } -} diff --git a/packages/services/server/src/supertokens/oidc-provider.ts b/packages/services/server/src/supertokens/oidc-provider.ts deleted file mode 100644 index e930e1f5f50..00000000000 --- a/packages/services/server/src/supertokens/oidc-provider.ts +++ /dev/null @@ -1,317 +0,0 @@ -import type { FastifyBaseLogger } from 'fastify'; -import type { FastifyRequest } from 'supertokens-node/lib/build/framework/fastify/framework'; -import type { ProviderInput } from 'supertokens-node/recipe/thirdparty/types'; -import type { TypeInput as ThirdPartEmailPasswordTypeInput } from 'supertokens-node/recipe/thirdpartyemailpassword/types'; -import zod from 'zod'; -import { createInternalApiCaller } from '../api'; - -const couldNotResolveOidcIntegrationSymbol = Symbol('could_not_resolve_oidc_integration'); - -type InternalApiCaller = ReturnType; - -export const getOIDCSuperTokensOverrides = (): ThirdPartEmailPasswordTypeInput['override'] => ({ - apis(originalImplementation) { - return { - ...originalImplementation, - async authorisationUrlGET(input) { - if (input.userContext?.[couldNotResolveOidcIntegrationSymbol] === true) { - return { - status: 'GENERAL_ERROR', - message: 'Could not find OIDC integration.', - }; - } - - return originalImplementation.authorisationUrlGET!(input); - }, - }; - }, -}); - -export type BroadcastOIDCIntegrationLog = (oidcId: string, message: string) => void; - -export function getLoggerFromUserContext(userContext: unknown): FastifyBaseLogger { - return (userContext as any)._default.request.request.log; -} - -export const createOIDCSuperTokensProvider = (args: { - internalApi: InternalApiCaller; - broadcastLog: BroadcastOIDCIntegrationLog; -}): ProviderInput => ({ - config: { - thirdPartyId: 'oidc', - }, - override(originalImplementation) { - return { - ...originalImplementation, - async getConfigForClientType(input) { - const logger = getLoggerFromUserContext(input.userContext); - logger.info('resolve config for OIDC provider.'); - const config = await getOIDCConfigFromInput(args.internalApi, logger, input); - if (!config) { - // In the next step the override `authorisationUrlGET` from `getOIDCSuperTokensOverrides` is called. - // We use the user context to return a `GENERAL_ERROR` with a human readable message. - // We cannot return an error here (except an "Unexpected error"), so we also need to return fake dat - input.userContext[couldNotResolveOidcIntegrationSymbol] = true; - - return { - thirdPartyId: 'oidc', - get clientId(): string { - throw new Error('Noop value accessed.'); - }, - }; - } - - return { - thirdPartyId: 'oidc', - clientId: config.clientId, - clientSecret: config.clientSecret, - authorizationEndpoint: config.authorizationEndpoint, - userInfoEndpoint: config.userinfoEndpoint, - tokenEndpoint: config.tokenEndpoint, - scope: ['openid', 'email', ...config.additionalScopes], - }; - }, - - async getAuthorisationRedirectURL(input) { - const logger = getLoggerFromUserContext(input.userContext); - logger.info('resolve authorization redirect url of OIDC provider.'); - const oidcConfig = await getOIDCConfigFromInput(args.internalApi, logger, input); - - if (!oidcConfig) { - // This case should never be reached (guarded by getConfigForClientType). - // We still have it for security reasons. - throw new Error('Could not find OIDC integration.'); - } - - const authorizationRedirectUrl = - await originalImplementation.getAuthorisationRedirectURL(input); - - const url = new URL(authorizationRedirectUrl.urlWithQueryParams); - url.searchParams.set('state', oidcConfig.id); - - const urlWithQueryParams = url.toString(); - - args.broadcastLog(oidcConfig.id, `redirect client to oauth provider ${urlWithQueryParams}`); - - return { - ...authorizationRedirectUrl, - urlWithQueryParams, - }; - }, - - async exchangeAuthCodeForOAuthTokens(input) { - const logger = getLoggerFromUserContext(input.userContext); - const config = await getOIDCConfigFromInput(args.internalApi, logger, input); - if (!config) { - // This case should never be reached (guarded by getConfigForClientType). - // We still have it for security reasons. - throw new Error('Could not find OIDC integration.'); - } - - logger.info('exchange auth code for oauth token (oidcId=%s)', config.id); - - args.broadcastLog( - config.id, - `attempt exchanging auth code for auth tokens on endpoint ${config.tokenEndpoint}`, - ); - - try { - // TODO: we should probably have our own custom implementation of this that uses fetch API. - // that way we can also do timeouts, retries and more detailed logging. - const result = await originalImplementation.exchangeAuthCodeForOAuthTokens(input); - args.broadcastLog( - config.id, - `successfully exchanged auth code for tokens on endpoint ${config.tokenEndpoint}`, - ); - return result; - } catch (error) { - if (error instanceof Error) { - args.broadcastLog( - config.id, - `error while exchanging auth code for tokens on endpoint ${config.tokenEndpoint}: ${error.message}`, - ); - } - throw error; - } - }, - - async getUserInfo(input) { - const logger = getLoggerFromUserContext(input.userContext); - logger.info('retrieve profile info from OIDC provider'); - const config = await getOIDCConfigFromInput(args.internalApi, logger, input); - if (!config) { - // This case should never be reached (guarded by getConfigForClientType). - // We still have it for security reasons. - throw new Error('Could not find OIDC integration.'); - } - - logger.info('fetch info for OIDC provider (oidcId=%s)', config.id); - - args.broadcastLog( - config.id, - `attempt fetching user info from endpoint with timeout 10 seconds ${config.userinfoEndpoint}`, - ); - - const abortController = new AbortController(); - - const timeout = setTimeout(() => { - abortController.abort(); - args.broadcastLog( - config.id, - `failed fetching user info from endpoint ${config.userinfoEndpoint}. Request timed out.`, - ); - }, 10_000); - - const tokenResponse = OIDCTokenSchema.parse(input.oAuthTokens); - const response = await fetch(config.userinfoEndpoint, { - headers: { - authorization: `Bearer ${tokenResponse.access_token}`, - accept: 'application/json', - 'content-type': 'application/json', - }, - signal: abortController.signal, - }); - - if (response.status !== 200) { - clearTimeout(timeout); - logger.info('received invalid status code (oidcId=%s)', config.id); - args.broadcastLog( - config.id, - `failed fetching user info from endpoint "${config.userinfoEndpoint}". Received status code ${response.status}. Expected 200.`, - ); - throw new Error("Received invalid status code. Could not retrieve user's profile info."); - } - - const body = await response.text(); - clearTimeout(timeout); - - let rawData: unknown; - - try { - rawData = JSON.parse(body); - } catch (err) { - logger.error('Could not parse JSON response from OIDC provider (oidcId=%s)', config.id); - if (err instanceof Error) { - args.broadcastLog( - config.id, - `failed parsing user info request body for response from user info endpoint "${config.userinfoEndpoint}". Error: ${err.message}.`, - ); - } - throw new Error('Could not parse JSON response.'); - } - - logger.info('retrieved profile info for provider (oidcId=%s)', config.id); - - const dataParseResult = OIDCProfileInfoSchema.safeParse(rawData); - - if (!dataParseResult.success) { - logger.error('Could not parse profile info for OIDC provider (oidcId=%s)', config.id); - logger.error('Raw data: %s', JSON.stringify(rawData)); - logger.error('Error: %s', JSON.stringify(dataParseResult.error)); - for (const issue of dataParseResult.error.issues) { - logger.debug('Issue: %s', JSON.stringify(issue)); - } - args.broadcastLog( - config.id, - `failed validating user info request body for response from user info endpoint "${config.userinfoEndpoint}". Issues: ${JSON.stringify(dataParseResult.error.issues)}`, - ); - - throw new Error('Could not parse profile info.'); - } - - args.broadcastLog( - config.id, - `successfully parsed user info request body for response from user info endpoint "${config.userinfoEndpoint}".`, - ); - - const profile = dataParseResult.data; - - // Set the oidcId to the user context so it can be used in `thirdPartySignInUpPOST` for linking the user account to the OIDC integration. - input.userContext.oidcId = config.id; - - return { - thirdPartyUserId: `${config.id}-${profile.sub}`, - email: { - id: profile.email, - isVerified: true, - }, - rawUserInfoFromProvider: { - fromIdTokenPayload: undefined, - fromUserInfoAPI: undefined, - }, - }; - }, - }; - }, -}); - -type OIDCConfig = { - id: string; - clientId: string; - clientSecret: string; - tokenEndpoint: string; - userinfoEndpoint: string; - authorizationEndpoint: string; - additionalScopes: string[]; -}; - -const OIDCProfileInfoSchema = zod.object({ - sub: zod.string(), - email: zod.string().email(), -}); - -const OIDCTokenSchema = zod.object({ access_token: zod.string() }); - -const getOIDCIdFromInput = (input: { userContext: any }, logger: FastifyBaseLogger): string => { - const fastifyRequest = input.userContext._default.request as FastifyRequest; - const originalUrl = 'http://localhost' + fastifyRequest.getOriginalURL(); - const oidcId = new URL(originalUrl).searchParams.get('oidc_id'); - - if (typeof oidcId !== 'string') { - logger.error('Invalid OIDC ID sent from client: %s', oidcId); - throw new Error('Invalid OIDC ID sent from client.'); - } - - return oidcId; -}; - -const configCache = new WeakMap(); - -/** - * Get cached OIDC config from the supertokens input. - */ -async function getOIDCConfigFromInput( - internalApi: InternalApiCaller, - logger: FastifyBaseLogger, - input: { userContext: any }, -) { - const fastifyRequest = input.userContext._default.request as FastifyRequest; - if (configCache.has(fastifyRequest)) { - return configCache.get(fastifyRequest) ?? null; - } - - const oidcIntegrationId = getOIDCIdFromInput(input, logger); - const config = await fetchOIDCConfig(internalApi, logger, oidcIntegrationId); - if (!config) { - configCache.set(fastifyRequest, null); - logger.error('Could not find OIDC integration (oidcId: %s)', oidcIntegrationId); - return null; - } - const resolvedConfig = { oidcIntegrationId, ...config }; - configCache.set(fastifyRequest, resolvedConfig); - - return resolvedConfig; -} - -const fetchOIDCConfig = async ( - internalApi: InternalApiCaller, - logger: FastifyBaseLogger, - oidcIntegrationId: string, -): Promise => { - const result = await internalApi.getOIDCIntegrationById({ oidcIntegrationId }); - if (result === null) { - logger.error('OIDC integration not found. (oidcId=%s)', oidcIntegrationId); - return null; - } - return result; -}; diff --git a/packages/services/server/src/supertokens/okta-provider.ts b/packages/services/server/src/supertokens/okta-provider.ts deleted file mode 100644 index 3d60306064b..00000000000 --- a/packages/services/server/src/supertokens/okta-provider.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { ProviderInput } from 'supertokens-node/recipe/thirdparty/types'; -import zod from 'zod'; -import { env } from '../environment'; - -type OktaConfig = Exclude<(typeof env)['auth']['okta'], null>; - -/** - * Custom (server) provider for SuperTokens in order to allow Okta users to sign in. - */ -export const createThirdPartyEmailPasswordNodeOktaProvider = ( - config: OktaConfig, -): ProviderInput => { - return { - config: { - thirdPartyId: 'okta', - clients: [ - { - clientId: config.clientId, - clientSecret: config.clientSecret, - scope: ['openid', 'email', 'profile', 'okta.users.read.self'], - }, - ], - authorizationEndpoint: `${config.endpoint}/oauth2/v1/authorize`, - tokenEndpoint: `${config.endpoint}/oauth2/v1/token`, - }, - override(originalImplementation) { - return { - ...originalImplementation, - async getUserInfo(input) { - const data = OktaAccessTokenResponseModel.parse(input.oAuthTokens); - const userData = await fetchOktaProfile(config, data.access_token); - - return { - thirdPartyUserId: userData.id, - email: { - id: userData.profile.email, - isVerified: true, - }, - rawUserInfoFromProvider: { - fromIdTokenPayload: undefined, - fromUserInfoAPI: undefined, - }, - }; - }, - }; - }, - }; -}; - -const OktaAccessTokenResponseModel = zod.object({ - access_token: zod.string(), -}); - -const OktaProfileModel = zod.object({ - id: zod.string(), - profile: zod.object({ - email: zod.string(), - }), -}); - -async function fetchOktaProfile(config: OktaConfig, accessToken: string) { - const response = await fetch(`${config.endpoint}/api/v1/users/me`, { - method: 'GET', - headers: { - 'content-type': 'application/json', - accept: 'application/json', - authorization: `Bearer ${accessToken}`, - }, - }); - - if (response.status !== 200) { - throw new Error(`Unexpected status code from Okta API: ${response.status}`); - } - - const json = await response.json(); - return OktaProfileModel.parse(json); -} diff --git a/packages/web/docs/src/content/schema-registry/self-hosting/get-started.mdx b/packages/web/docs/src/content/schema-registry/self-hosting/get-started.mdx index 97c6ffbadac..23911d5cfe7 100644 --- a/packages/web/docs/src/content/schema-registry/self-hosting/get-started.mdx +++ b/packages/web/docs/src/content/schema-registry/self-hosting/get-started.mdx @@ -43,7 +43,6 @@ components: [Azure Event Hubs](https://learn.microsoft.com/en-us/azure/event-hubs/event-hubs-about)) - [ClickHouse database](https://clickhouse.com/) - [Redis](https://redis.io/) -- [SuperTokens instance](https://supertokens.com/) - [S3 storage](https://aws.amazon.com/s3/) (or any other S3-compatible solution, like [minio](https://min.io/) or [CloudFlare R2](https://www.cloudflare.com/developer-platform/r2/)) - Hive microservices @@ -93,7 +92,6 @@ The following diagram shows the architecture of a self-hosted Hive Console insta #### Microservices -- **SuperTokens**: an open-source project used for authentication and authorization. - `webapp`: the main Hive Console web application. - `server`: the main Hive Console server, responsible for serving the GraphQL API and orchestrating calls to other services. @@ -192,7 +190,6 @@ export DOCKER_TAG=":9.4.1" # Pin this to an exact version export HIVE_ENCRYPTION_SECRET=$(openssl rand -hex 16) export HIVE_APP_BASE_URL="http://localhost:8080" export HIVE_EMAIL_FROM="no-reply@graphql-hive.com" -export SUPERTOKENS_API_KEY=$(openssl rand -hex 16) export CLICKHOUSE_USER="clickhouse" export CLICKHOUSE_PASSWORD=$(openssl rand -hex 16) export REDIS_PASSWORD=$(openssl rand -hex 16) @@ -202,6 +199,13 @@ export POSTGRES_DB="registry" export MINIO_ROOT_USER="minioadmin" export MINIO_ROOT_PASSWORD="minioadmin" export CDN_AUTH_PRIVATE_KEY=$(openssl rand -hex 16) +export SUPERTOKENS_REFRESH_TOKEN_KEY=$(echo "1000:$(openssl rand -hex 64):$(openssl rand -hex 64)") +KEY_NAME=$(uuidgen) +PRIVATE_KEY_PEM=$(openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048) +PUBLIC_KEY_PEM=$(echo "$PRIVATE_KEY_PEM" | openssl rsa -pubout) +PRIVATE_KEY_DATA=$(echo "$PRIVATE_KEY_PEM" | awk 'NF {if (NR!=1 && $0!~/-----END/) print}' | tr -d '\n') +PUBLIC_KEY_DATA=$(echo "$PUBLIC_KEY_PEM" | awk 'NF {if (NR!=1 && $0!~/-----END/) print}' | tr -d '\n') +export SUPERTOKENS_ACCESS_TOKEN_KEY=$(echo "${KEY_NAME}|${PUBLIC_KEY_DATA}|${PRIVATE_KEY_DATA}") ``` @@ -390,8 +394,6 @@ After doing your first testing with Hive Console you should consider the followi new releases and updates. - Set up a weekly reminder for updating your Hive Console instance to the latest version and applying maintenance. -- Get yourself familiar with SuperTokens and follow their changelogs in order to keep your - SuperTokens instance up-to-date. - In order to use longer retention periods e.g. for conditional breaking changes or schema explorer overviews do the following: Open the postgres database, go to the table `organization`, find your organization and change `plan_name` to ENTERPRISE, `limit_operations_monthly` to 0 and diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e087188c8b7..fdc80129784 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,7 +21,6 @@ overrides: '@tailwindcss/node>tailwindcss': 4.1.18 '@tailwindcss/vite>tailwindcss': 4.1.18 estree-util-value-to-estree: ^3.3.3 - nodemailer@^6.0.0: ^7.0.11 '@types/nodemailer>@aws-sdk/client-sesv2': '-' tar@6.x.x: ^7.5.3 diff@<8.0.3: ^8.0.3 @@ -907,9 +906,6 @@ importers: stripe: specifier: 17.5.0 version: 17.5.0 - supertokens-node: - specifier: 16.7.5 - version: 16.7.5(encoding@0.1.13) tslib: specifier: 2.8.1 version: 2.8.1 @@ -1328,12 +1324,6 @@ importers: reflect-metadata: specifier: 0.2.2 version: 0.2.2 - supertokens-js-override: - specifier: 0.0.4 - version: 0.0.4 - supertokens-node: - specifier: 16.7.5 - version: 16.7.5(encoding@0.1.13) tslib: specifier: 2.8.1 version: 2.8.1 @@ -13612,10 +13602,6 @@ packages: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} engines: {node: '>=12'} - inflation@2.1.0: - resolution: {integrity: sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==} - engines: {node: '>= 0.8.0'} - inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -14358,9 +14344,6 @@ packages: libmime@2.1.3: resolution: {integrity: sha512-ABr2f4O+K99sypmkF/yPz2aXxUFHEZzv+iUkxItCeKZWHHXdQPpDXd6rV1kBBwL4PserzLU09EIzJ2lxC9hPfQ==} - libphonenumber-js@1.12.17: - resolution: {integrity: sha512-bsxi8FoceAYR/bjHcLYc2ShJ/aVAzo5jaxAYiMHF0BD+NTp47405CGuPNKYpw+lHadN9k/ClFGc9X5vaZswIrA==} - libqp@1.1.0: resolution: {integrity: sha512-4Rgfa0hZpG++t1Vi2IiqXG9Ad1ig4QTmtuZF946QJP4bPqOYC78ixUXgz5TW/wE7lNaNKlplSYTxQ+fR2KZ0EA==} @@ -16188,9 +16171,6 @@ packages: resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} engines: {node: '>= 6'} - pkce-challenge@3.1.0: - resolution: {integrity: sha512-bQ/0XPZZ7eX+cdAkd61uYWpfMhakH3NeteUF1R8GNa+LMqX8QFAkbCLqq+AYAns1/ueACBu/BMWhrlKGrdvGZg==} - pkg-types@1.3.0: resolution: {integrity: sha512-kS7yWjVFCkIw9hqdJBoMxDdzEngmkr5FXeWZZfQ6GoYacjVnsW6l2CcYW/0ThD0vF4LPJgVYnrg4d0uuhwYQbg==} @@ -16559,9 +16539,6 @@ packages: pseudomap@1.0.2: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} - psl@1.8.0: - resolution: {integrity: sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==} - pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} @@ -16594,9 +16571,6 @@ packages: resolution: {integrity: sha512-MWkCOVIcJP9QSKU52Ngow6bsAWAPlPK2MludXvcrS2bGZSl+T1qX9MZvRIkqUIkGLJquMJHWfsT6eRqUpp4aWg==} engines: {node: '>=18'} - querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -17090,9 +17064,6 @@ packages: require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - reselect@5.1.1: resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} @@ -17277,10 +17248,6 @@ packages: scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - scmp@2.1.0: - resolution: {integrity: sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==} - deprecated: Just use Node.js's crypto.timingSafeEqual() - scroll-into-view-if-needed@3.1.0: resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} @@ -17863,9 +17830,6 @@ packages: supertokens-js-override@0.0.4: resolution: {integrity: sha512-r0JFBjkMIdep3Lbk3JA+MpnpuOtw4RSyrlRAbrzMcxwiYco3GFWl/daimQZ5b1forOiUODpOlXbSOljP/oyurg==} - supertokens-node@16.7.5: - resolution: {integrity: sha512-xrVwi0GfgLIqHKaWXEp7fCgmVQ2wTiJTHG2CQGLkGMvrO9dUcxY+H6qozn3EvyXxbkm1BmflYD8N7ILNSIbR1g==} - supertokens-web-js@0.9.0: resolution: {integrity: sha512-7DucVUWxImrcjckza0oW6tkPfMzScj8V/qiQNZeUT/EfCqIbslNSO8holBHc9Eykc0vG/CC0d9ne5TRhAmRcxg==} @@ -18298,10 +18262,6 @@ packages: tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} - twilio@4.23.0: - resolution: {integrity: sha512-LdNBQfOe0dY2oJH2sAsrxazpgfFQo5yXGxe96QA8UWB5uu+433PrUbkv8gQ5RmrRCqUTPQ0aOrIyAdBr1aB03Q==} - engines: {node: '>=14.0'} - twoslash-protocol@0.2.12: resolution: {integrity: sha512-5qZLXVYfZ9ABdjqbvPc4RWMr7PrpPaaDSeaYY55vl/w1j6H6kzsWK/urAEIXlzYlyrFmyz1UbwIt+AA0ck+wbg==} @@ -18547,9 +18507,6 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - urlpattern-polyfill@10.0.0: resolution: {integrity: sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==} @@ -19093,10 +19050,6 @@ packages: xml@1.0.1: resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==} - xmlbuilder@13.0.2: - resolution: {integrity: sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==} - engines: {node: '>=6.0'} - xorshift@1.2.0: resolution: {integrity: sha512-iYgNnGyeeJ4t6U11NpA/QiKy+PXn5Aa3Azg5qkwIFz1tBLllQrjjsk9yzD7IAK0naNU4JxdeDgqW9ov4u/hc4g==} @@ -22860,7 +22813,7 @@ snapshots: '@graphql-mesh/hmac-upstream-signature': 2.0.8(graphql@16.12.0) '@graphql-mesh/plugin-response-cache': 0.104.18(graphql@16.12.0) '@graphql-mesh/transport-common': 1.0.12(graphql@16.12.0)(pino@10.3.0) - '@graphql-mesh/types': 0.104.16(graphql@16.12.0)(ioredis@5.8.2) + '@graphql-mesh/types': 0.104.16(graphql@16.12.0) '@graphql-mesh/utils': 0.104.16(graphql@16.12.0) '@graphql-tools/batch-delegate': 10.0.8(graphql@16.12.0) '@graphql-tools/delegate': 12.0.2(graphql@16.12.0) @@ -23086,7 +23039,7 @@ snapshots: '@graphql-hive/logger': 1.0.9(pino@10.3.0) '@graphql-mesh/cross-helpers': 0.4.10(graphql@16.12.0) '@graphql-mesh/transport-common': 1.0.12(graphql@16.12.0)(pino@10.3.0) - '@graphql-mesh/types': 0.104.16(graphql@16.12.0)(ioredis@5.8.2) + '@graphql-mesh/types': 0.104.16(graphql@16.12.0) '@graphql-mesh/utils': 0.104.16(graphql@16.12.0) '@graphql-tools/utils': 10.11.0(graphql@16.12.0) '@opentelemetry/api': 1.9.0 @@ -23605,7 +23558,7 @@ snapshots: '@graphql-hive/logger': 1.0.9(pino@10.3.0) '@graphql-mesh/cross-helpers': 0.4.10(graphql@16.12.0) '@graphql-mesh/transport-common': 1.0.12(graphql@16.12.0)(pino@10.3.0) - '@graphql-mesh/types': 0.104.16(graphql@16.12.0)(ioredis@5.8.2) + '@graphql-mesh/types': 0.104.16(graphql@16.12.0) '@graphql-mesh/utils': 0.104.16(graphql@16.12.0) '@graphql-tools/batch-execute': 10.0.4(graphql@16.12.0) '@graphql-tools/delegate': 12.0.2(graphql@16.12.0) @@ -23663,7 +23616,7 @@ snapshots: '@graphql-mesh/hmac-upstream-signature@2.0.8(graphql@16.12.0)': dependencies: '@graphql-mesh/cross-helpers': 0.4.10(graphql@16.12.0) - '@graphql-mesh/types': 0.104.16(graphql@16.12.0)(ioredis@5.8.2) + '@graphql-mesh/types': 0.104.16(graphql@16.12.0) '@graphql-mesh/utils': 0.104.16(graphql@16.12.0) '@graphql-tools/executor-common': 1.0.5(graphql@16.12.0) '@graphql-tools/utils': 10.10.3(graphql@16.12.0) @@ -23790,7 +23743,7 @@ snapshots: '@envelop/response-cache': 9.0.0(@envelop/core@5.5.1)(graphql@16.12.0) '@graphql-mesh/cross-helpers': 0.4.10(graphql@16.12.0) '@graphql-mesh/string-interpolation': 0.5.9(graphql@16.12.0) - '@graphql-mesh/types': 0.104.16(graphql@16.12.0)(ioredis@5.8.2) + '@graphql-mesh/types': 0.104.16(graphql@16.12.0) '@graphql-mesh/utils': 0.104.16(graphql@16.12.0) '@graphql-tools/utils': 10.9.1(graphql@16.12.0) '@graphql-yoga/plugin-response-cache': 3.15.4(graphql-yoga@5.16.2(graphql@16.12.0))(graphql@16.12.0) @@ -23896,7 +23849,7 @@ snapshots: '@graphql-hive/logger': 1.0.9(pino@10.3.0) '@graphql-hive/pubsub': 2.1.1(ioredis@5.8.2) '@graphql-hive/signal': 2.0.0 - '@graphql-mesh/types': 0.104.16(graphql@16.12.0)(ioredis@5.8.2) + '@graphql-mesh/types': 0.104.16(graphql@16.12.0) '@graphql-tools/executor': 1.4.13(graphql@16.12.0) '@graphql-tools/executor-common': 1.0.5(graphql@16.12.0) '@graphql-tools/utils': 10.10.3(graphql@16.12.0) @@ -23995,6 +23948,21 @@ snapshots: - utf-8-validate - winston + '@graphql-mesh/types@0.104.16(graphql@16.12.0)': + dependencies: + '@graphql-hive/pubsub': 2.1.1(ioredis@5.8.2) + '@graphql-tools/batch-delegate': 10.0.5(graphql@16.12.0) + '@graphql-tools/delegate': 11.1.3(graphql@16.12.0) + '@graphql-tools/utils': 10.9.1(graphql@16.12.0) + '@graphql-typed-document-node/core': 3.2.0(graphql@16.12.0) + '@repeaterjs/repeater': 3.0.6 + '@whatwg-node/disposablestack': 0.0.6 + graphql: 16.12.0 + tslib: 2.8.1 + transitivePeerDependencies: + - '@nats-io/nats-core' + - ioredis + '@graphql-mesh/types@0.104.16(graphql@16.12.0)(ioredis@5.8.2)': dependencies: '@graphql-hive/pubsub': 2.1.1(ioredis@5.8.2) @@ -24030,7 +23998,7 @@ snapshots: '@envelop/instrumentation': 1.0.0 '@graphql-mesh/cross-helpers': 0.4.10(graphql@16.12.0) '@graphql-mesh/string-interpolation': 0.5.9(graphql@16.12.0) - '@graphql-mesh/types': 0.104.16(graphql@16.12.0)(ioredis@5.8.2) + '@graphql-mesh/types': 0.104.16(graphql@16.12.0) '@graphql-tools/batch-delegate': 10.0.5(graphql@16.12.0) '@graphql-tools/delegate': 11.1.3(graphql@16.12.0) '@graphql-tools/utils': 10.9.1(graphql@16.12.0) @@ -29959,7 +29927,7 @@ snapshots: '@slack/types': 2.16.0 '@types/node': 22.10.5 '@types/retry': 0.12.0 - axios: 1.13.5(debug@4.4.1) + axios: 1.13.5 eventemitter3: 5.0.1 form-data: 4.0.4 is-electron: 2.2.2 @@ -32534,9 +32502,9 @@ snapshots: axe-core@4.7.0: {} - axios@1.13.5(debug@4.4.1): + axios@1.13.5: dependencies: - follow-redirects: 1.15.11(debug@4.4.1) + follow-redirects: 1.15.11 form-data: 4.0.5 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -35092,9 +35060,7 @@ snapshots: fn-name@3.0.0: {} - follow-redirects@1.15.11(debug@4.4.1): - optionalDependencies: - debug: 4.4.1(supports-color@8.1.1) + follow-redirects@1.15.11: {} for-each@0.3.3: dependencies: @@ -36381,8 +36347,6 @@ snapshots: indent-string@5.0.0: {} - inflation@2.1.0: {} - inflight@1.0.6: dependencies: once: 1.4.0 @@ -37131,8 +37095,6 @@ snapshots: libbase64: 0.1.0 libqp: 1.1.0 - libphonenumber-js@1.12.17: {} - libqp@1.1.0: {} lie@3.1.1: @@ -39589,10 +39551,6 @@ snapshots: pirates@4.0.5: {} - pkce-challenge@3.1.0: - dependencies: - crypto-js: 4.2.0 - pkg-types@1.3.0: dependencies: confbox: 0.1.8 @@ -39898,8 +39856,6 @@ snapshots: pseudomap@1.0.2: {} - psl@1.8.0: {} - pump@3.0.0: dependencies: end-of-stream: 1.4.4 @@ -39929,8 +39885,6 @@ snapshots: filter-obj: 5.1.0 split-on-first: 3.0.0 - querystringify@2.2.0: {} - queue-microtask@1.2.3: {} quick-format-unescaped@4.0.4: {} @@ -40558,8 +40512,6 @@ snapshots: require-main-filename@2.0.0: {} - requires-port@1.0.0: {} - reselect@5.1.1: {} resolve-alpn@1.2.1: {} @@ -40798,8 +40750,6 @@ snapshots: scheduler@0.27.0: {} - scmp@2.1.0: {} - scroll-into-view-if-needed@3.1.0: dependencies: compute-scroll-into-view: 3.1.1 @@ -41515,24 +41465,6 @@ snapshots: supertokens-js-override@0.0.4: {} - supertokens-node@16.7.5(encoding@0.1.13): - dependencies: - content-type: 1.0.5 - cookie: 0.7.2 - cross-fetch: 3.1.8(encoding@0.1.13) - debug: 4.4.1(supports-color@8.1.1) - inflation: 2.1.0 - jose: 4.15.9 - libphonenumber-js: 1.12.17 - nodemailer: 7.0.11 - pkce-challenge: 3.1.0 - psl: 1.8.0 - supertokens-js-override: 0.0.4 - twilio: 4.23.0(debug@4.4.1) - transitivePeerDependencies: - - encoding - - supports-color - supertokens-web-js@0.9.0: dependencies: supertokens-js-override: 0.0.4 @@ -42018,20 +41950,6 @@ snapshots: tweetnacl@0.14.5: {} - twilio@4.23.0(debug@4.4.1): - dependencies: - axios: 1.13.5(debug@4.4.1) - dayjs: 1.11.13 - https-proxy-agent: 5.0.1 - jsonwebtoken: 9.0.3 - qs: 6.14.2 - scmp: 2.1.0 - url-parse: 1.5.10 - xmlbuilder: 13.0.2 - transitivePeerDependencies: - - debug - - supports-color - twoslash-protocol@0.2.12: {} twoslash@0.2.12(typescript@5.7.3): @@ -42318,11 +42236,6 @@ snapshots: dependencies: punycode: 2.1.1 - url-parse@1.5.10: - dependencies: - querystringify: 2.2.0 - requires-port: 1.0.0 - urlpattern-polyfill@10.0.0: {} urlpattern-polyfill@8.0.2: {} @@ -42943,8 +42856,6 @@ snapshots: xml@1.0.1: {} - xmlbuilder@13.0.2: {} - xorshift@1.2.0: {} xtend@4.0.2: {}