Merge branch 'dev' into feat/logo-path-in-enterprise

This commit is contained in:
Milo Schwartz
2026-02-13 17:16:25 -08:00
committed by GitHub
231 changed files with 8005 additions and 6182 deletions

View File

@@ -1,5 +1,4 @@
import { build } from "@server/build";
import { TierId } from "@server/lib/billing/tiers";
import { cache } from "react";
import { getCachedSubscription } from "./getCachedSubscription";
import { priv } from ".";
@@ -21,7 +20,7 @@ export const isOrgSubscribed = cache(async (orgId: string) => {
try {
const subRes = await getCachedSubscription(orgId);
subscribed =
subRes.data.data.tier === TierId.STANDARD &&
(subRes.data.data.tier == "tier1" || subRes.data.data.tier == "tier2" || subRes.data.data.tier == "tier3") &&
subRes.data.data.active;
} catch {}
}

View File

@@ -1,6 +1,8 @@
type CleanRedirectOptions = {
fallback?: string;
maxRedirectDepth?: number;
/** When true, preserve all query params on the path (for internal redirects). Default false. */
allowAllQueryParams?: boolean;
};
const ALLOWED_QUERY_PARAMS = new Set([
@@ -16,14 +18,18 @@ export function cleanRedirect(
input: string,
options: CleanRedirectOptions = {}
): string {
const { fallback = "/", maxRedirectDepth = 2 } = options;
const {
fallback = "/",
maxRedirectDepth = 2,
allowAllQueryParams = false
} = options;
if (!input || typeof input !== "string") {
return fallback;
}
try {
return sanitizeUrl(input, fallback, maxRedirectDepth);
return sanitizeUrl(input, fallback, maxRedirectDepth, allowAllQueryParams);
} catch {
return fallback;
}
@@ -32,7 +38,8 @@ export function cleanRedirect(
function sanitizeUrl(
input: string,
fallback: string,
remainingRedirectDepth: number
remainingRedirectDepth: number,
allowAllQueryParams: boolean = false
): string {
if (
input.startsWith("javascript:") ||
@@ -56,7 +63,7 @@ function sanitizeUrl(
const cleanParams = new URLSearchParams();
for (const [key, value] of url.searchParams.entries()) {
if (!ALLOWED_QUERY_PARAMS.has(key)) {
if (!allowAllQueryParams && !ALLOWED_QUERY_PARAMS.has(key)) {
continue;
}
@@ -68,7 +75,8 @@ function sanitizeUrl(
const cleanedRedirect = sanitizeUrl(
value,
"",
remainingRedirectDepth - 1
remainingRedirectDepth - 1,
allowAllQueryParams
);
if (cleanedRedirect) {

View File

@@ -0,0 +1,51 @@
import { cleanRedirect } from "@app/lib/cleanRedirect";
export const INTERNAL_REDIRECT_KEY = "internal_redirect";
/**
* Consumes the internal_redirect value from localStorage if present and valid
* (within TTL). Removes it from storage. Returns the path segment (with leading
* slash) to append to an orgId, or null if none/expired/invalid.
*/
export function consumeInternalRedirectPath(): string | null {
if (typeof window === "undefined") return null;
try {
const raw = window.localStorage.getItem(INTERNAL_REDIRECT_KEY);
if (raw == null || raw === "") return null;
window.localStorage.removeItem(INTERNAL_REDIRECT_KEY);
const { path: storedPath, expiresAt } = JSON.parse(raw) as {
path?: string;
expiresAt?: number;
};
if (
typeof storedPath !== "string" ||
storedPath === "" ||
typeof expiresAt !== "number" ||
Date.now() > expiresAt
) {
return null;
}
const cleaned = cleanRedirect(storedPath, {
fallback: "",
allowAllQueryParams: true
});
if (!cleaned) return null;
return cleaned.startsWith("/") ? cleaned : `/${cleaned}`;
} catch {
return null;
}
}
/**
* Returns the full redirect target for an org: either `/${orgId}` or
* `/${orgId}${path}` if a valid internal_redirect was stored. Consumes the
* stored value.
*/
export function getInternalRedirectTarget(orgId: string): string {
const path = consumeInternalRedirectPath();
return path ? `/${orgId}${path}` : `/${orgId}`;
}

View File

@@ -32,7 +32,11 @@ export function pullEnv(): Env {
process.env.NEW_RELEASES_NOTIFICATION_ENABLED === "true"
? true
: false
}
},
identityProviderMode: process.env.IDENTITY_PROVIDER_MODE as
| "org"
| "global"
| undefined
},
email: {
emailEnabled: process.env.EMAIL_ENABLED === "true" ? true : false
@@ -64,8 +68,10 @@ export function pullEnv(): Env {
process.env.FLAGS_DISABLE_PRODUCT_HELP_BANNERS === "true"
? true
: false,
useOrgOnlyIdp:
process.env.USE_ORG_ONLY_IDP === "true" ? true : false
disableEnterpriseFeatures:
process.env.DISABLE_ENTERPRISE_FEATURES === "true"
? true
: false
},
branding: {

View File

@@ -8,6 +8,7 @@ export type Env = {
product_updates: boolean;
new_releases: boolean;
};
identityProviderMode?: "global" | "org";
};
server: {
externalPort: string;
@@ -34,7 +35,7 @@ export type Env = {
hideSupporterKey: boolean;
usePangolinDns: boolean;
disableProductHelpBanners: boolean;
useOrgOnlyIdp: boolean;
disableEnterpriseFeatures: boolean;
};
branding: {
appName?: string;