Files
dataroom/lib/jackson.ts
zeekay caad90bc04 rip: enterprise dirs + paywalls — full AGPL, no commercial-license code
Drop the dual-licensed `ee/` tree entirely. All features previously
locked behind the Hanzo Dataroom Commercial License are now part of the
AGPL-3.0 core and freely available — custom domains, branded viewing,
advanced analytics, watermarks, access controls, Q&A, AI vector stores,
workflows, SAML SSO, dataroom invitations, conversations, templates.

Layout changes:
* `ee/features/{ai,conversations,workflows,permissions,storage,templates,
  dataroom-invitations,access-notifications,security,conversions}/` →
  `features/<name>/` (AGPL).
* `ee/limits/` → `lib/billing/limits/` with every plan resolving to an
  unlimited profile (no users/links/documents/domains/datarooms cap,
  conversations and watermarks always on).
* `ee/stripe/` → `lib/billing/legacy/` (already a compat shim over
  `@hanzoai/commerce`; renewal-reminder webhook is now a no-op).
* `ee/features/billing/cancellation/` deleted. Pause/unpause/cancel/
  retention routes now compile against `lib/billing/cancellation.ts`
  stubs that return 410 Gone; `isTeamPaused`/`isTeamPausedById` in
  `lib/billing/paused.ts` always report active. `CancellationModal` is
  a no-op shim in `components/billing/`.
* `app/(ee)/api/*` route group folded into `app/api/*` (SAML auth,
  SCIM, AI chat, workflows, FAQ, link-upload).

Build: `npm run build` green.
2026-06-07 11:46:48 -07:00

100 lines
2.9 KiB
TypeScript

import type {
IConnectionAPIController,
IDirectorySyncController,
IOAuthController,
JacksonOption,
} from "@boxyhq/saml-jackson";
import samlJackson from "@boxyhq/saml-jackson";
import crypto from "crypto";
export const samlAudience = "https://saml.dataroom.hanzo.ai";
export { JACKSON_PRODUCT as jacksonProduct } from "@/features/security/sso/product";
// Jackson's AES-256-GCM encrypter requires exactly 32 bytes.
// Derive a stable 32-byte key from NEXTAUTH_SECRET using SHA-256.
const encryptionKey = crypto
.createHash("sha256")
.update(process.env.NEXTAUTH_SECRET || "")
.digest("base64url")
.substring(0, 32);
function sanitizePostgresUrl(rawUrl: string): string {
try {
const parsed = new URL(rawUrl);
// Some providers emit ssl cert params with value "system", which can cause
// node-postgres to attempt reading a local file literally named "system".
for (const param of ["sslcert", "sslrootcert", "sslkey", "sslcrl"]) {
const value = parsed.searchParams.get(param);
if (value?.toLowerCase() === "system") {
parsed.searchParams.delete(param);
}
}
return parsed.toString();
} catch {
// If URL parsing fails, pass through so downstream errors remain visible.
return rawUrl;
}
}
function getJacksonDbUrl(): string {
const candidates = [
process.env.POSTGRES_PRISMA_URL,
process.env.POSTGRES_PRISMA_URL_NON_POOLING,
];
const dbUrl = candidates.find((value) => typeof value === "string" && value.trim().length > 0);
if (!dbUrl) {
throw new Error(
"Missing Jackson DB URL. Set POSTGRES_PRISMA_URL or POSTGRES_PRISMA_URL_NON_POOLING.",
);
}
return sanitizePostgresUrl(dbUrl);
}
function getJacksonOptions(): JacksonOption {
return {
externalUrl: process.env.NEXTAUTH_URL || "https://dataroom.hanzo.ai",
samlPath: "/api/auth/saml/callback",
samlAudience,
db: {
engine: "sql",
type: "postgres",
url: getJacksonDbUrl(),
encryptionKey,
},
idpEnabled: true, // to allow to SSO from their IDP
scimPath: "/api/scim/v2.0",
clientSecretVerifier: process.env.NEXTAUTH_SECRET as string,
};
}
declare global {
var apiController: IConnectionAPIController | undefined;
var oauthController: IOAuthController | undefined;
var directorySyncController: IDirectorySyncController | undefined;
}
export async function jackson() {
if (
!globalThis.apiController ||
!globalThis.oauthController ||
!globalThis.directorySyncController
) {
const ret = await samlJackson(getJacksonOptions());
globalThis.apiController = ret.connectionAPIController;
globalThis.oauthController = ret.oauthController;
globalThis.directorySyncController = ret.directorySyncController;
}
return {
apiController: globalThis.apiController,
oauthController: globalThis.oauthController,
directorySyncController: globalThis.directorySyncController,
};
}