From bb2421312d3fea072c5cf7dd2c733e099a770653 Mon Sep 17 00:00:00 2001 From: Bogdan Carpusor Date: Tue, 8 Sep 2026 18:29:20 +0300 Subject: [PATCH 1/2] fix snippets --- .github/workflows/test.yml | 4 +- .../email-password/initial-setup.mdx | 8 +- .../authentication/m2m/client-credentials.mdx | 15 +- docs/authentication/m2m/legacy-flow.mdx | 32 +-- .../social/custom-invite-flow.mdx | 3 + docs/authentication/social/initial-setup.mdx | 32 ++- ...tiple-frontends-with-separate-backends.mdx | 182 +++++++++++++++--- docs/deployment/migrate-from-mysql.mdx | 2 +- docs/quickstart.mdx | 8 +- scripts/code-blocks/extract.test.ts | 7 +- scripts/code-type-checking/dart/Dockerfile | 4 +- scripts/code-type-checking/dart/pubspec.lock | 50 ++++- scripts/code-type-checking/dart/pubspec.yaml | 5 +- scripts/code-type-checking/kotlin/Dockerfile | 4 +- .../kotlin/app/build.gradle | 4 +- .../kotlin/validate-kotlin.sh | 63 +++--- .../python/requirements.txt | 1 + 17 files changed, 321 insertions(+), 103 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ab0418eec6..c64fba3141 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -189,7 +189,7 @@ jobs: shell: bash run: | languages=() - for language in javascript go python swift php java csharp; do + for language in javascript go python swift php java csharp dart kotlin; do directory="scripts/code-type-checking/$language/snippets" if [[ -d "$directory" ]] && find "$directory" -type f -print -quit | grep -q .; then languages+=("$language") @@ -217,6 +217,8 @@ jobs: scripts/code-type-checking/php/snippets scripts/code-type-checking/java/snippets scripts/code-type-checking/csharp/snippets + scripts/code-type-checking/dart/snippets + scripts/code-type-checking/kotlin/snippets if-no-files-found: error retention-days: 1 diff --git a/docs/authentication/email-password/initial-setup.mdx b/docs/authentication/email-password/initial-setup.mdx index 0a1296f2e8..2c0a29c15a 100644 --- a/docs/authentication/email-password/initial-setup.mdx +++ b/docs/authentication/email-password/initial-setup.mdx @@ -1145,8 +1145,8 @@ fileprivate class NetworkManager { ``` ```dart title="Mobile" option="mobile-frameworks:flutter" -import 'package:http/http.dart' as base_http; -import 'package:supertokens_flutter/http.dart' as supertokens_http; +import 'package:supertokens_flutter/http.dart' as http; +// SuperTokens wraps the package:http API. Future makeRequest() async { Uri uri = Uri.parse("http://localhost:3001/api"); @@ -1243,8 +1243,8 @@ fileprivate class NetworkManager { ``` ```dart title="Mobile" option="mobile-frameworks:flutter" -// Import http from the SuperTokens package -import 'package:supertokens_flutter/http.dart' as http; +import 'package:http/http.dart' as base_http; +import 'package:supertokens_flutter/http.dart' as supertokens_http; Future makeRequest() async { Uri uri = Uri.parse("http://localhost:3001/api"); diff --git a/docs/authentication/m2m/client-credentials.mdx b/docs/authentication/m2m/client-credentials.mdx index f2b7a0e441..2611382c27 100644 --- a/docs/authentication/m2m/client-credentials.mdx +++ b/docs/authentication/m2m/client-credentials.mdx @@ -310,8 +310,15 @@ async function verifySessionOrOAuthToken(req: Request, res: Response, next: Next const authorization = req.headers.authorization; if (authorization !== undefined) { + const separator = authorization.indexOf(" "); + const scheme = authorization.slice(0, separator); + const token = authorization.slice(separator + 1); + if (separator < 1 || scheme.toLowerCase() !== "bearer" || !token) { + return res.status(401).json({ message: "Unauthorized" }); + } + try { - const result = await OAuth2Provider.validateOAuth2AccessToken(match[1], { + const result = await OAuth2Provider.validateOAuth2AccessToken(token, { audience: "", clientId: "", scopes: [""], @@ -351,9 +358,13 @@ def verify_session_or_oauth_token(request: Request) -> bool: authorization = request.headers.get("authorization") if authorization is not None: + scheme, separator, token = authorization.partition(" ") + if scheme.lower() != "bearer" or not separator or not token: + raise HTTPException(status_code=401, detail="Unauthorized") + try: result = validate_oauth2_access_token( - token=match.group(1), + token=token, requirements=OAuth2TokenValidationRequirements( audience="", client_id="", diff --git a/docs/authentication/m2m/legacy-flow.mdx b/docs/authentication/m2m/legacy-flow.mdx index 639836cf5a..96e2511779 100644 --- a/docs/authentication/m2m/legacy-flow.mdx +++ b/docs/authentication/m2m/legacy-flow.mdx @@ -194,22 +194,24 @@ func createServiceAccessToken() (string, error) { from supertokens_python.recipe.jwt import asyncio from supertokens_python.recipe.jwt.interfaces import CreateJwtOkResult -response = await asyncio.create_jwt( - { - "iss": "https://auth.example.com", - "aud": "service-m2", - "sub": "service-m1", - "source": "microservice", - "token_type": "service_access", - "permissions": ["comments:write"], - }, - validity_seconds=300, - use_static_signing_key=False, -) -if not isinstance(response, CreateJwtOkResult): - raise RuntimeError("JWT creation failed") -access_token = response.jwt +async def create_service_access_token() -> str: + response = await asyncio.create_jwt( + { + "iss": "https://auth.example.com", + "aud": "service-m2", + "sub": "service-m1", + "source": "microservice", + "token_type": "service_access", + "permissions": ["comments:write"], + }, + validity_seconds=300, + use_static_signing_key=False, + ) + if not isinstance(response, CreateJwtOkResult): + raise RuntimeError("JWT creation failed") + + return response.jwt ``` diff --git a/docs/authentication/social/custom-invite-flow.mdx b/docs/authentication/social/custom-invite-flow.mdx index 02b525f05f..e23e7c153e 100644 --- a/docs/authentication/social/custom-invite-flow.mdx +++ b/docs/authentication/social/custom-invite-flow.mdx @@ -148,6 +148,7 @@ Use the check functions from the previous code snippet. The overrides reject a provider response without an email before the SDK can generate a synthetic email for a provider configured not to require one. + ```tsx check=false reason="This example omits surrounding application and SuperTokens configuration." @@ -230,8 +231,10 @@ ThirdParty.init({ ``` + Pass the `isEmailAllowed` helper from the previous step to `initThirdPartyWithInvites`, and include the returned recipe in your SuperTokens `RecipeList`. Add your provider configuration to the `TypeInput` below. + ```go import ( "errors" diff --git a/docs/authentication/social/initial-setup.mdx b/docs/authentication/social/initial-setup.mdx index 7084977f19..04a59184c9 100644 --- a/docs/authentication/social/initial-setup.mdx +++ b/docs/authentication/social/initial-setup.mdx @@ -1094,6 +1094,13 @@ async function handleGoogleCallback() { ```tsx import ThirdParty from "supertokens-node/recipe/thirdparty"; +declare function consumeAppleMobileTransaction(input: { + id: string; + appType: "android"; + clientType: string; + expectedCallback: string; +}): Promise<{ appRedirectURI: string } | undefined>; + ThirdParty.init({ override: { apis: (original) => { @@ -1161,6 +1168,8 @@ Follow Apple's official [Configure Sign in with Apple for the web](https://devel **Go** + +Pass your transaction-consumption implementation to `initThirdPartyWithAppleMobile`, and include the returned recipe in your SuperTokens `RecipeList`. The helper must return an error for missing, expired, or mismatched transactions. Add your Apple provider configuration to the `TypeInput` below. @@ -1192,8 +1201,6 @@ async function appleSignInClicked() { ``` -Pass your transaction-consumption implementation to `initThirdPartyWithAppleMobile`, and include the returned recipe in your SuperTokens `RecipeList`. The helper must return an error for missing, expired, or mismatched transactions. Add your Apple provider configuration to the `TypeInput` below. - ```go import ( "net/http" @@ -1274,10 +1281,27 @@ This only works for providers which support the [PKCE flow](https://oauth.net/2/ ```python +from dataclasses import dataclass +from typing import Any, Dict, Optional +from urllib.parse import urlencode + from supertokens_python.recipe import thirdparty from supertokens_python.recipe.thirdparty.interfaces import APIInterface, APIOptions -from typing import Dict, Any -from urllib.parse import urlencode + + +@dataclass +class AppleMobileTransaction: + app_redirect_uri: str + + +async def consume_apple_mobile_transaction( + id: str, + app_type: str, + client_type: str, + expected_callback: str, +) -> Optional[AppleMobileTransaction]: + # Atomically consume and return the matching transaction from your database. + raise NotImplementedError def override_thirdparty_apis(original_implementation: APIInterface): original_apple_redirect_post = original_implementation.apple_redirect_handler_post diff --git a/docs/authentication/unified-login/quickstart-guides/multiple-frontends-with-separate-backends.mdx b/docs/authentication/unified-login/quickstart-guides/multiple-frontends-with-separate-backends.mdx index 6e2b337081..0927a14155 100644 --- a/docs/authentication/unified-login/quickstart-guides/multiple-frontends-with-separate-backends.mdx +++ b/docs/authentication/unified-login/quickstart-guides/multiple-frontends-with-separate-backends.mdx @@ -843,20 +843,67 @@ A secure implementation must: With [passport-oauth2](https://www.passportjs.org/packages/passport-oauth2/), state protection and PKCE are opt-in. Configure both. Install server-side Express session middleware before Passport; do not use a client-side cookie session store. This example uses the separately registered `client_secret_post` client described in step 2. -```javascript +```typescript +import express, { type Request } from "express"; +import session, { type Session, type SessionData, type Store } from "express-session"; +import passport from "passport"; +import OAuth2Strategy from "passport-oauth2"; + +interface OAuthTransaction { + tenantId: string; +} + +interface OAuthResult { + tenantId: string; + oauthTokens: { + accessToken: string; + refreshToken: string; + }; +} + +interface ApplicationSessionStore { + set(sessionId: string, result: OAuthResult): void; +} + +type ApplicationSession = Session & + Partial & { + oauthTransaction?: OAuthTransaction; + }; + const CLIENT_ID = ""; const EXPECTED_AUDIENCE = ""; const EXPECTED_ISSUER = ""; const REQUIRED_SCOPES = [""]; const INTROSPECTION_URL = "/auth/oauth/introspect"; +const app = express(); +const serverSideSessionStore = app.get("serverSideSessionStore") as Store; +const applicationSessionStore = app.get("applicationSessionStore") as ApplicationSessionStore; + +function mustGetEnv(name: string): string { + const value = process.env[name]; + if (!value) throw new Error(`${name} is required`); + return value; +} + +function resolveAllowedTenant(req: Request): string { + const tenantId = typeof req.query.tenantId === "string" ? req.query.tenantId : "public"; + const allowedTenants = mustGetEnv("ALLOWED_TENANT_IDS").split(","); + if (!allowedTenants.includes(tenantId)) throw new Error("Invalid tenant"); + return tenantId; +} + +function getApplicationSession(req: Request): ApplicationSession { + return req.session as unknown as ApplicationSession; +} + class SuperTokensOAuth2Strategy extends OAuth2Strategy { - authorizationParams(options) { + authorizationParams(options: { tenantId?: string }): { tenant_id: string | undefined } { return { tenant_id: options.tenantId }; } } -async function introspectAndValidateTenant(accessToken, expectedTenant) { +async function introspectAndValidateTenant(accessToken: string, expectedTenant: string) { const response = await fetch(INTROSPECTION_URL, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, @@ -864,7 +911,14 @@ async function introspectAndValidateTenant(accessToken, expectedTenant) { }); if (!response.ok) throw new Error("OAuth introspection failed"); - const tokenInfo = await response.json(); + const tokenInfo = (await response.json()) as { + active?: boolean; + aud?: string | string[]; + tId?: string; + client_id?: string; + iss?: string; + sub?: string; + }; const audiences = Array.isArray(tokenInfo.aud) ? tokenInfo.aud : [tokenInfo.aud]; if ( tokenInfo.active !== true || @@ -875,6 +929,8 @@ async function introspectAndValidateTenant(accessToken, expectedTenant) { ) { throw new Error("OAuth token does not match the login transaction"); } + if (typeof tokenInfo.sub !== "string") throw new Error("OAuth subject missing"); + return tokenInfo; } app.use( @@ -904,12 +960,13 @@ passport.use( }, async (req, accessToken, refreshToken, params, profile, done) => { try { - const transaction = req.session.oauthTransaction; - delete req.session.oauthTransaction; + const applicationSession = getApplicationSession(req); + const transaction = applicationSession.oauthTransaction; + delete applicationSession.oauthTransaction; if (transaction === undefined) throw new Error("OAuth transaction missing"); - await introspectAndValidateTenant(accessToken, transaction.tenantId); - const user = await resolveUserFromOAuthTokens(accessToken, refreshToken); + const tokenInfo = await introspectAndValidateTenant(accessToken, transaction.tenantId); + const user = { id: tokenInfo.sub }; done(null, user, { oauthTokens: { accessToken, refreshToken }, tenantId: transaction.tenantId, @@ -923,18 +980,19 @@ passport.use( app.get("/login", (req, res, next) => { const tenantId = resolveAllowedTenant(req); - req.session.oauthTransaction = { tenantId }; - passport.authenticate("oauth2", { tenantId })(req, res, next); + getApplicationSession(req).oauthTransaction = { tenantId }; + const options = { tenantId } as passport.AuthenticateOptions & { tenantId: string }; + passport.authenticate("oauth2", options)(req, res, next); }); app.get("/oauth/callback", (req, res, next) => { - passport.authenticate("oauth2", { session: false }, (error, user, info) => { + const completeAuthentication = (error: unknown, user: Express.User | false | null, info: OAuthResult | undefined) => { if (error || !user) return next(error ?? new Error("OAuth login failed")); if (!info?.oauthTokens || typeof info.tenantId !== "string") { return next(new Error("OAuth transaction result missing")); } - req.session.regenerate((regenerateError) => { + getApplicationSession(req).regenerate((regenerateError) => { if (regenerateError) return next(regenerateError); req.logIn(user, (loginError) => { if (loginError) return next(loginError); @@ -945,7 +1003,8 @@ app.get("/oauth/callback", (req, res, next) => { res.redirect("/"); }); }); - })(req, res, next); + }; + passport.authenticate("oauth2", { session: false }, completeAuthentication)(req, res, next); }); ``` @@ -1141,10 +1200,71 @@ Use [Authlib](https://docs.authlib.org/) with a one-time server-side transaction ```python import secrets +from typing import Dict, Mapping, Optional, Protocol, TypedDict, cast import requests from authlib.common.security import generate_token -from authlib.integrations.requests_client import OAuth2Session +from authlib.integrations.requests_client import OAuth2Session # pyright: ignore[reportMissingTypeStubs] + +CLIENT_ID = "" +CLIENT_SECRET = "" +AUTHORIZATION_URL = "/auth/oauth/auth" +TOKEN_URL = "/auth/oauth/token" +INTROSPECTION_URL = "/auth/oauth/introspect" +CALLBACK_URL = "https:///oauth/callback" +EXPECTED_ISSUER = "" +EXPECTED_AUDIENCE = "" +SCOPES = ["offline_access", "", ""] +REQUIRED_SCOPES = [""] + + +class OAuthTransaction(TypedDict): + state: str + verifier: str + tenant_id: str + + +class ApplicationSession(TypedDict): + tenant_id: str + oauth_token: Mapping[str, object] + + +class TransactionStore(Protocol): + def put(self, session_id: str, transaction: OAuthTransaction) -> None: ... + + def consume(self, session_id: str) -> Optional[OAuthTransaction]: ... + + +class ApplicationSessionStore(Protocol): + def put(self, session_id: str, session: ApplicationSession) -> None: ... + + +class InvalidOAuthState(ValueError): + pass + + +def resolve_allowed_tenant() -> str: + raise NotImplementedError + + +def application_session_id() -> str: + raise NotImplementedError + + +def request_query_parameter(name: str) -> Optional[str]: + raise NotImplementedError + + +def request_url() -> str: + raise NotImplementedError + + +def rotate_application_session() -> str: + raise NotImplementedError + + +def redirect(url: str) -> str: + raise NotImplementedError def introspect_and_validate_tenant(access_token: str, expected_tenant: str) -> None: @@ -1168,7 +1288,7 @@ def introspect_and_validate_tenant(access_token: str, expected_tenant: str) -> N raise ValueError("OAuth token does not match the login transaction") -def login(): +def login(transaction_store: TransactionStore) -> str: client = OAuth2Session( CLIENT_ID, CLIENT_SECRET, @@ -1179,10 +1299,13 @@ def login(): ) verifier = generate_token(48) tenant_id = resolve_allowed_tenant() - authorization_url, state = client.create_authorization_url( - AUTHORIZATION_URL, - code_verifier=verifier, - tenant_id=tenant_id, + authorization_url, state = cast( + tuple[str, str], + client.create_authorization_url( # pyright: ignore[reportUnknownMemberType] + AUTHORIZATION_URL, + code_verifier=verifier, + tenant_id=tenant_id, + ), ) transaction_store.put( application_session_id(), @@ -1191,7 +1314,10 @@ def login(): return redirect(authorization_url) -def callback(): +def callback( + transaction_store: TransactionStore, + application_session_store: ApplicationSessionStore, +) -> str: # consume atomically reads and deletes the initiating session's transaction transaction = transaction_store.consume(application_session_id()) provided_state = request_query_parameter("state") or "" @@ -1208,12 +1334,18 @@ def callback(): redirect_uri=CALLBACK_URL, code_challenge_method="S256", ) - token = client.fetch_token( - TOKEN_URL, - authorization_response=request_url(), - code_verifier=transaction["verifier"], + token = cast( + Dict[str, object], + client.fetch_token( # pyright: ignore[reportUnknownMemberType] + TOKEN_URL, + authorization_response=request_url(), + code_verifier=transaction["verifier"], + ), ) - introspect_and_validate_tenant(token["access_token"], transaction["tenant_id"]) + access_token = token.get("access_token") + if not isinstance(access_token, str): + raise ValueError("OAuth access token missing") + introspect_and_validate_tenant(access_token, transaction["tenant_id"]) new_session_id = rotate_application_session() application_session_store.put( diff --git a/docs/deployment/migrate-from-mysql.mdx b/docs/deployment/migrate-from-mysql.mdx index 10c4ea6791..9ca8aae6cb 100644 --- a/docs/deployment/migrate-from-mysql.mdx +++ b/docs/deployment/migrate-from-mysql.mdx @@ -68,7 +68,7 @@ DB_USER = "DB_USER" DB_NAME = "DB_NAME" DB_PASS = "DB_PASS" -def run_mysql_command(query): +def run_mysql_command(query: str) -> str: """Run a mysql command and return the output""" cmd = [ "mysql", diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index f8e28c9dbd..3fd29671b0 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -1145,8 +1145,8 @@ fileprivate class NetworkManager { ``` ```dart title="Mobile" option="mobile-frameworks:flutter" -import 'package:http/http.dart' as base_http; -import 'package:supertokens_flutter/http.dart' as supertokens_http; +import 'package:supertokens_flutter/http.dart' as http; +// SuperTokens wraps the package:http API. Future makeRequest() async { Uri uri = Uri.parse("http://localhost:3001/api"); @@ -1243,8 +1243,8 @@ fileprivate class NetworkManager { ``` ```dart title="Mobile" option="mobile-frameworks:flutter" -// Import http from the SuperTokens package -import 'package:supertokens_flutter/http.dart' as http; +import 'package:http/http.dart' as base_http; +import 'package:supertokens_flutter/http.dart' as supertokens_http; Future makeRequest() async { Uri uri = Uri.parse("http://localhost:3001/api"); diff --git a/scripts/code-blocks/extract.test.ts b/scripts/code-blocks/extract.test.ts index 00fe4fac7f..270a53d17e 100644 --- a/scripts/code-blocks/extract.test.ts +++ b/scripts/code-blocks/extract.test.ts @@ -1,4 +1,4 @@ -import { mkdtemp, mkdir, readFile, symlink, writeFile } from "node:fs/promises"; +import { mkdtemp, mkdir, readFile, realpath, symlink, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; @@ -107,10 +107,11 @@ describe("code block extraction", () => { await writeFile(path.join(nested, "ignored.txt"), "ignored"); const mdxAliasPath = path.join(root, "z-alias.mdx"); await symlink(mdxPath, mdxAliasPath); + const [canonicalMarkdownPath, canonicalMdxPath] = await Promise.all([realpath(markdownPath), realpath(mdxPath)]); await expect(resolveMarkdownSourcePaths([nested, markdownPath, root, mdxPath, mdxAliasPath])).resolves.toEqual([ - markdownPath, - mdxPath, + canonicalMarkdownPath, + canonicalMdxPath, ]); }); diff --git a/scripts/code-type-checking/dart/Dockerfile b/scripts/code-type-checking/dart/Dockerfile index 7801ccff74..1beb5d71fe 100644 --- a/scripts/code-type-checking/dart/Dockerfile +++ b/scripts/code-type-checking/dart/Dockerfile @@ -10,7 +10,7 @@ RUN apt-get update && apt-get install -y \ && rm -rf /var/lib/apt/lists/* -RUN git clone https://github.com/flutter/flutter.git /flutter +RUN git clone --branch 3.24.5 --depth 1 https://github.com/flutter/flutter.git /flutter ENV PATH="/flutter/bin:$PATH" RUN flutter doctor @@ -26,4 +26,4 @@ RUN flutter pub get COPY ./snippets ./lib/snippets -RUN flutter build web -t ./lib/main.dart +RUN flutter analyze --no-fatal-infos --no-fatal-warnings ./lib/snippets diff --git a/scripts/code-type-checking/dart/pubspec.lock b/scripts/code-type-checking/dart/pubspec.lock index af7ceb297b..5770df61be 100644 --- a/scripts/code-type-checking/dart/pubspec.lock +++ b/scripts/code-type-checking/dart/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + url: "https://pub.dev" + source: hosted + version: "8.1.4" boolean_selector: dependency: transitive description: @@ -153,7 +161,7 @@ packages: source: hosted version: "0.12.0+2" http: - dependency: transitive + dependency: "direct main" description: name: http sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" @@ -176,6 +184,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -240,6 +256,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" path: dependency: transitive description: @@ -296,6 +320,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.2.4" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" quiver: dependency: transitive description: @@ -429,6 +461,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.2" + supertokens_rownd_flutter: + dependency: "direct main" + description: + name: supertokens_rownd_flutter + sha256: "010a8991218fd222146fdf652dfcc5df4055d692881ba77fdeab34e6c14eddbf" + url: "https://pub.dev" + source: hosted + version: "0.1.0" term_glyph: dependency: transitive description: @@ -469,6 +509,14 @@ packages: url: "https://pub.dev" source: hosted version: "14.2.5" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" win32: dependency: transitive description: diff --git a/scripts/code-type-checking/dart/pubspec.yaml b/scripts/code-type-checking/dart/pubspec.yaml index 51719d221e..36a803da68 100644 --- a/scripts/code-type-checking/dart/pubspec.yaml +++ b/scripts/code-type-checking/dart/pubspec.yaml @@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: '>=2.19.0 <3.0.0' + sdk: '>=3.3.0 <4.0.0' # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -39,6 +39,9 @@ dependencies: google_sign_in: ^6.1.4 sign_in_with_apple: ^5.0.0 dio: ^5.3.3 + http: ^0.13.5 + provider: ^6.1.2 + supertokens_rownd_flutter: 0.1.0 dev_dependencies: flutter_test: diff --git a/scripts/code-type-checking/kotlin/Dockerfile b/scripts/code-type-checking/kotlin/Dockerfile index 3ded70be9e..24e2d8c66f 100644 --- a/scripts/code-type-checking/kotlin/Dockerfile +++ b/scripts/code-type-checking/kotlin/Dockerfile @@ -5,6 +5,4 @@ WORKDIR /app COPY . . -COPY snippets/* ./app/src/main/java/com/example/myapplication - -RUN ./gradlew build +RUN sh ./validate-kotlin.sh diff --git a/scripts/code-type-checking/kotlin/app/build.gradle b/scripts/code-type-checking/kotlin/app/build.gradle index 61ea160d2c..78d699c45c 100644 --- a/scripts/code-type-checking/kotlin/app/build.gradle +++ b/scripts/code-type-checking/kotlin/app/build.gradle @@ -37,7 +37,7 @@ dependencies { implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' - implementation 'com.github.supertokens:supertokens-android:0.4.2' + implementation 'com.github.supertokens:supertokens-android:0.5.3' implementation 'com.google.android.gms:play-services-auth:20.3.0' implementation 'com.squareup.okhttp3:okhttp:4.10.0' implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1' @@ -45,4 +45,4 @@ dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' -} \ No newline at end of file +} diff --git a/scripts/code-type-checking/kotlin/validate-kotlin.sh b/scripts/code-type-checking/kotlin/validate-kotlin.sh index 9d1deb79cb..74bb382f48 100644 --- a/scripts/code-type-checking/kotlin/validate-kotlin.sh +++ b/scripts/code-type-checking/kotlin/validate-kotlin.sh @@ -1,45 +1,38 @@ #!/bin/sh +set -eu + snippets_dir="./snippets" -project_dir="src/main/kotlin" -output_dir="./out" +generated_source_dir="./app/src/main/java/com/example/myapplication/generated-snippet" -DEPENDENCIES="\ - org.jetbrains.kotlin:kotlin-stdlib:1.9.0 \ - com.supertokens:supertokens-android:0.5.3 \ - androidx.core:core-ktx:1.10.1 \ - androidx.appcompat:appcompat:1.6.1" +cleanup() { + rm -rf "$generated_source_dir" +} -# Make sure output directory exists -mkdir -p $output_dir +trap cleanup EXIT +mkdir -p "$generated_source_dir" echo "Starting compilation of Kotlin snippets..." -# find . -name "*.kt" | while read file; do -# kotlinc "$file" -include-runtime -d "${file%.kt}.jar" -# done - -find $snippets_dir -name "*.kt" | while read file; do - # validate_swift_file "$file" - # cp -f "$file" $project_dir/Main.kt - echo "Validating file: $file" - - kotlinc -cp $(echo $DEPENDENCIES | sed 's/ /:/g') \ - -d $output_dir \ - $file - - if [ $? -eq 0 ]; then - echo "Syntax is valid for $file." - ./gradlew clean --quiet - else - echo "Syntax check failed for $file. The Kotlin code is invalid." - exit 1 - fi +snippet_number=0 +find "$snippets_dir" -name "*.kt" -print | sort | while IFS= read -r file; do + if grep -Eq '^import (io\.rownd|io\.flutter|com\.facebook\.react|com\.reactnativerowndplugin)(\.|$)' "$file"; then + echo "Skipping unsupported external SDK snippet: $file" + continue + fi + + echo "Validating file: $file" + snippet_number=$((snippet_number + 1)) + relative_file=${file#"$snippets_dir"/} + generated_source="$generated_source_dir/$relative_file" + mkdir -p "$(dirname "$generated_source")" + { + echo "package generated.snippet$snippet_number" + echo + cat "$file" + } > "$generated_source" done -if [ $? -ne 0 ]; then - echo "Validation failed. One or more files contain syntax errors." - exit 1 -else - echo "All files are valid." -fi +./gradlew :app:compileDebugKotlin --quiet --no-daemon + +echo "All supported Kotlin snippets are valid." diff --git a/scripts/code-type-checking/python/requirements.txt b/scripts/code-type-checking/python/requirements.txt index 27291957f3..be90fc4499 100644 --- a/scripts/code-type-checking/python/requirements.txt +++ b/scripts/code-type-checking/python/requirements.txt @@ -8,6 +8,7 @@ asgiref==3.7.2 astroid==2.9.3 async-timeout==4.0.3 attrs==21.4.0 +Authlib==1.6.4 autopep8==1.5.6 black==22.3.0 certifi==2023.7.22 From 1d30cc10f9dcd90b0d9cf6fdb70fb560e20e6d35 Mon Sep 17 00:00:00 2001 From: Bogdan Carpusor Date: Tue, 8 Sep 2026 18:34:24 +0300 Subject: [PATCH 2/2] fix compatibility table --- islands/SDKCompatibilityTable.tsx | 2 +- microfrontends.jsonc | 1 - scripts/migration/routing.test.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/islands/SDKCompatibilityTable.tsx b/islands/SDKCompatibilityTable.tsx index 46e4d44173..d96ccf782c 100644 --- a/islands/SDKCompatibilityTable.tsx +++ b/islands/SDKCompatibilityTable.tsx @@ -68,7 +68,7 @@ export default function SDKCompatibilityTable() { setIsLoadingCompatibility(true); request( - `/compatibility?driver=${encodeURIComponent(backend)}&frontend=${encodeURIComponent(frontend)}`, + `/compatibility?driver=${encodeURIComponent(backend)}&frontend=${encodeURIComponent(frontend)}&plugin=&planType=FREE`, controller.signal, ) .then((result) => { diff --git a/microfrontends.jsonc b/microfrontends.jsonc index 72ba18473d..74691c66ec 100644 --- a/microfrontends.jsonc +++ b/microfrontends.jsonc @@ -17,7 +17,6 @@ "routing": [ { "group": "docs", - "flag": "docs-microfrontend", "paths": ["/docs/:path*"], }, { diff --git a/scripts/migration/routing.test.ts b/scripts/migration/routing.test.ts index b160bcaea7..01b65d1a42 100644 --- a/scripts/migration/routing.test.ts +++ b/scripts/migration/routing.test.ts @@ -27,7 +27,7 @@ describe("microfrontend routing", () => { expect(docs.packageName).toBe("@supertokens/docs"); expect(docs.assetPrefix).toBe("docs-assets"); expect(docs.routing).toEqual([ - { group: "docs", flag: "docs-microfrontend", paths: ["/docs/:path*"] }, + { group: "docs", paths: ["/docs/:path*"] }, { group: "docs-assets", paths: ["/docs-assets/:path*"] }, ]); });