mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-27 23:36:39 +00:00
add identity provider mode setting
This commit is contained in:
@@ -79,6 +79,7 @@ export const subscriptionItems = sqliteTable("subscriptionItems", {
|
|||||||
subscriptionItemId: integer("subscriptionItemId").primaryKey({
|
subscriptionItemId: integer("subscriptionItemId").primaryKey({
|
||||||
autoIncrement: true
|
autoIncrement: true
|
||||||
}),
|
}),
|
||||||
|
stripeSubscriptionItemId: text("stripeSubscriptionItemId"),
|
||||||
subscriptionId: text("subscriptionId")
|
subscriptionId: text("subscriptionId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => subscriptions.subscriptionId, {
|
.references(() => subscriptions.subscriptionId, {
|
||||||
|
|||||||
@@ -65,6 +65,11 @@ export class PrivateConfig {
|
|||||||
this.rawPrivateConfig.branding?.logo?.dark_path || undefined;
|
this.rawPrivateConfig.branding?.logo?.dark_path || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.rawPrivateConfig.app.identity_provider_mode) {
|
||||||
|
process.env.IDENTITY_PROVIDER_MODE =
|
||||||
|
this.rawPrivateConfig.app.identity_provider_mode;
|
||||||
|
}
|
||||||
|
|
||||||
process.env.BRANDING_LOGO_AUTH_WIDTH = this.rawPrivateConfig.branding
|
process.env.BRANDING_LOGO_AUTH_WIDTH = this.rawPrivateConfig.branding
|
||||||
?.logo?.auth_page?.width
|
?.logo?.auth_page?.width
|
||||||
? this.rawPrivateConfig.branding?.logo?.auth_page?.width.toString()
|
? this.rawPrivateConfig.branding?.logo?.auth_page?.width.toString()
|
||||||
@@ -129,10 +134,8 @@ export class PrivateConfig {
|
|||||||
process.env.USE_PANGOLIN_DNS =
|
process.env.USE_PANGOLIN_DNS =
|
||||||
this.rawPrivateConfig.flags.use_pangolin_dns.toString();
|
this.rawPrivateConfig.flags.use_pangolin_dns.toString();
|
||||||
}
|
}
|
||||||
if (this.rawPrivateConfig.flags.use_org_only_idp) {
|
|
||||||
process.env.USE_ORG_ONLY_IDP =
|
console.log(this.rawPrivateConfig.app.identity_provider_mode);
|
||||||
this.rawPrivateConfig.flags.use_org_only_idp.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRawPrivateConfig() {
|
public getRawPrivateConfig() {
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export const privateConfigSchema = z.object({
|
|||||||
app: z
|
app: z
|
||||||
.object({
|
.object({
|
||||||
region: z.string().optional().default("default"),
|
region: z.string().optional().default("default"),
|
||||||
base_domain: z.string().optional()
|
base_domain: z.string().optional(),
|
||||||
|
identity_provider_mode: z.enum(["global", "org"]).optional()
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.default({
|
.default({
|
||||||
@@ -95,7 +96,7 @@ export const privateConfigSchema = z.object({
|
|||||||
.object({
|
.object({
|
||||||
enable_redis: z.boolean().optional().default(false),
|
enable_redis: z.boolean().optional().default(false),
|
||||||
use_pangolin_dns: z.boolean().optional().default(false),
|
use_pangolin_dns: z.boolean().optional().default(false),
|
||||||
use_org_only_idp: z.boolean().optional().default(false),
|
use_org_only_idp: z.boolean().optional()
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
@@ -181,7 +182,29 @@ export const privateConfigSchema = z.object({
|
|||||||
// localFilePath: z.string().optional()
|
// localFilePath: z.string().optional()
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
});
|
})
|
||||||
|
.transform((data) => {
|
||||||
|
// this to maintain backwards compatibility with the old config file
|
||||||
|
const identityProviderMode = data.app?.identity_provider_mode;
|
||||||
|
const useOrgOnlyIdp = data.flags?.use_org_only_idp;
|
||||||
|
|
||||||
|
if (identityProviderMode !== undefined) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
if (useOrgOnlyIdp === true) {
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
app: { ...data.app, identity_provider_mode: "org" as const }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (useOrgOnlyIdp === false) {
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
app: { ...data.app, identity_provider_mode: "global" as const }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
|
||||||
export function readPrivateConfigFile() {
|
export function readPrivateConfigFile() {
|
||||||
if (build == "oss") {
|
if (build == "oss") {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import config from "@server/lib/config";
|
|||||||
import { CreateOrgIdpResponse } from "@server/routers/orgIdp/types";
|
import { CreateOrgIdpResponse } from "@server/routers/orgIdp/types";
|
||||||
import { isSubscribed } from "#private/lib/isSubscribed";
|
import { isSubscribed } from "#private/lib/isSubscribed";
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
|
import privateConfig from "#private/lib/config";
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({ orgId: z.string().nonempty() });
|
const paramsSchema = z.strictObject({ orgId: z.string().nonempty() });
|
||||||
|
|
||||||
@@ -92,6 +93,18 @@ export async function createOrgOidcIdp(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
privateConfig.getRawPrivateConfig().app.identity_provider_mode !==
|
||||||
|
"org"
|
||||||
|
) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { fromError } from "zod-validation-error";
|
|||||||
import { idp, idpOidcConfig, idpOrg } from "@server/db";
|
import { idp, idpOidcConfig, idpOrg } from "@server/db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
import privateConfig from "#private/lib/config";
|
||||||
|
|
||||||
const paramsSchema = z
|
const paramsSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -59,6 +60,18 @@ export async function deleteOrgIdp(
|
|||||||
|
|
||||||
const { idpId } = parsedParams.data;
|
const { idpId } = parsedParams.data;
|
||||||
|
|
||||||
|
if (
|
||||||
|
privateConfig.getRawPrivateConfig().app.identity_provider_mode !==
|
||||||
|
"org"
|
||||||
|
) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if IDP exists
|
// Check if IDP exists
|
||||||
const [existingIdp] = await db
|
const [existingIdp] = await db
|
||||||
.select()
|
.select()
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { encrypt } from "@server/lib/crypto";
|
|||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
import { isSubscribed } from "#private/lib/isSubscribed";
|
import { isSubscribed } from "#private/lib/isSubscribed";
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
|
import privateConfig from "#private/lib/config";
|
||||||
|
|
||||||
const paramsSchema = z
|
const paramsSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -97,6 +98,18 @@ export async function updateOrgOidcIdp(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
privateConfig.getRawPrivateConfig().app.identity_provider_mode !==
|
||||||
|
"org"
|
||||||
|
) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const { idpId, orgId } = parsedParams.data;
|
const { idpId, orgId } = parsedParams.data;
|
||||||
const {
|
const {
|
||||||
clientId,
|
clientId,
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export default function ResourceAuthenticationPage() {
|
|||||||
const { data: orgIdps = [], isLoading: isLoadingOrgIdps } = useQuery(
|
const { data: orgIdps = [], isLoading: isLoadingOrgIdps } = useQuery(
|
||||||
orgQueries.identityProviders({
|
orgQueries.identityProviders({
|
||||||
orgId: org.org.orgId,
|
orgId: org.org.orgId,
|
||||||
useOrgOnlyIdp: env.flags.useOrgOnlyIdp
|
useOrgOnlyIdp: env.app.identityProviderMode === "org"
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -76,12 +76,13 @@ export default async function Page(props: {
|
|||||||
|
|
||||||
// Only use SmartLoginForm if NOT (OSS build OR org-only IdP enabled)
|
// Only use SmartLoginForm if NOT (OSS build OR org-only IdP enabled)
|
||||||
const useSmartLogin =
|
const useSmartLogin =
|
||||||
build === "saas" || (build === "enterprise" && env.flags.useOrgOnlyIdp);
|
build === "saas" ||
|
||||||
|
(build === "enterprise" && env.app.identityProviderMode === "org");
|
||||||
|
|
||||||
let loginIdps: LoginFormIDP[] = [];
|
let loginIdps: LoginFormIDP[] = [];
|
||||||
if (!useSmartLogin) {
|
if (!useSmartLogin) {
|
||||||
// Load IdPs for DashboardLoginForm (OSS or org-only IdP mode)
|
// Load IdPs for DashboardLoginForm (OSS or org-only IdP mode)
|
||||||
if (build === "oss" || !env.flags.useOrgOnlyIdp) {
|
if (build === "oss" || env.app.identityProviderMode !== "org") {
|
||||||
const idpsRes = await cache(
|
const idpsRes = await cache(
|
||||||
async () =>
|
async () =>
|
||||||
await priv.get<AxiosResponse<ListIdpsResponse>>("/idp")
|
await priv.get<AxiosResponse<ListIdpsResponse>>("/idp")
|
||||||
@@ -165,7 +166,8 @@ export default async function Page(props: {
|
|||||||
forceLogin={forceLogin}
|
forceLogin={forceLogin}
|
||||||
showOrgLogin={
|
showOrgLogin={
|
||||||
!isInvite &&
|
!isInvite &&
|
||||||
(build === "saas" || env.flags.useOrgOnlyIdp)
|
(build === "saas" ||
|
||||||
|
env.app.identityProviderMode === "org")
|
||||||
}
|
}
|
||||||
searchParams={searchParams}
|
searchParams={searchParams}
|
||||||
defaultUser={defaultUser}
|
defaultUser={defaultUser}
|
||||||
@@ -188,7 +190,8 @@ export default async function Page(props: {
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isInvite && (build === "saas" || env.flags.useOrgOnlyIdp) ? (
|
{!isInvite &&
|
||||||
|
(build === "saas" || env.app.identityProviderMode === "org") ? (
|
||||||
<OrgSignInLink
|
<OrgSignInLink
|
||||||
href={`/auth/org${buildQueryString(searchParams)}`}
|
href={`/auth/org${buildQueryString(searchParams)}`}
|
||||||
linkText={t("orgAuthSignInToOrg")}
|
linkText={t("orgAuthSignInToOrg")}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export default async function OrgAuthPage(props: {
|
|||||||
|
|
||||||
const env = pullEnv();
|
const env = pullEnv();
|
||||||
|
|
||||||
if (build !== "saas" && !env.flags.useOrgOnlyIdp) {
|
if (build !== "saas" && env.app.identityProviderMode !== "org") {
|
||||||
const queryString = new URLSearchParams(searchParams as any).toString();
|
const queryString = new URLSearchParams(searchParams as any).toString();
|
||||||
redirect(`/auth/login${queryString ? `?${queryString}` : ""}`);
|
redirect(`/auth/login${queryString ? `?${queryString}` : ""}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default async function OrgAuthPage(props: {
|
|||||||
|
|
||||||
const env = pullEnv();
|
const env = pullEnv();
|
||||||
|
|
||||||
if (build !== "saas" && !env.flags.useOrgOnlyIdp) {
|
if (build !== "saas" && env.app.identityProviderMode !== "org") {
|
||||||
redirect("/");
|
redirect("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ export default async function ResourceAuthPage(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let loginIdps: LoginFormIDP[] = [];
|
let loginIdps: LoginFormIDP[] = [];
|
||||||
if (build === "saas" || env.flags.useOrgOnlyIdp) {
|
if (build === "saas" || env.app.identityProviderMode === "org") {
|
||||||
if (subscribed) {
|
if (subscribed) {
|
||||||
const idpsRes = await cache(
|
const idpsRes = await cache(
|
||||||
async () =>
|
async () =>
|
||||||
|
|||||||
@@ -124,7 +124,8 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [
|
|||||||
// PaidFeaturesAlert
|
// PaidFeaturesAlert
|
||||||
...((build === "oss" && !env?.flags.disableEnterpriseFeatures) ||
|
...((build === "oss" && !env?.flags.disableEnterpriseFeatures) ||
|
||||||
build === "saas" ||
|
build === "saas" ||
|
||||||
env?.flags.useOrgOnlyIdp
|
env?.app.identityProviderMode === "org" ||
|
||||||
|
env?.app.identityProviderMode === undefined
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
title: "sidebarIdentityProviders",
|
title: "sidebarIdentityProviders",
|
||||||
@@ -251,7 +252,9 @@ export const adminNavSections = (env?: Env): SidebarNavSection[] => [
|
|||||||
href: "/admin/api-keys",
|
href: "/admin/api-keys",
|
||||||
icon: <KeyRound className="size-4 flex-none" />
|
icon: <KeyRound className="size-4 flex-none" />
|
||||||
},
|
},
|
||||||
...(build === "oss" || !env?.flags.useOrgOnlyIdp
|
...(build === "oss" ||
|
||||||
|
env?.app.identityProviderMode === "global" ||
|
||||||
|
env?.app.identityProviderMode === undefined
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
title: "sidebarIdentityProviders",
|
title: "sidebarIdentityProviders",
|
||||||
|
|||||||
@@ -322,7 +322,7 @@ export default function AuthPageBrandingForm({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{build === "saas" ||
|
{build === "saas" ||
|
||||||
env.env.flags.useOrgOnlyIdp ? (
|
env.env.app.identityProviderMode === "org" ? (
|
||||||
<>
|
<>
|
||||||
<div className="mt-3 mb-6">
|
<div className="mt-3 mb-6">
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ function getActionsCategories(root: boolean) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (root || build === "saas" || env.flags.useOrgOnlyIdp) {
|
if (root || build === "saas" || env.app.identityProviderMode === "org") {
|
||||||
actionsByCategory["Identity Provider (IDP)"] = {
|
actionsByCategory["Identity Provider (IDP)"] = {
|
||||||
[t("actionCreateIdp")]: "createIdp",
|
[t("actionCreateIdp")]: "createIdp",
|
||||||
[t("actionUpdateIdp")]: "updateIdp",
|
[t("actionUpdateIdp")]: "updateIdp",
|
||||||
|
|||||||
@@ -204,7 +204,9 @@ export default function SignupForm({
|
|||||||
? env.branding.logo?.authPage?.height || 44
|
? env.branding.logo?.authPage?.height || 44
|
||||||
: 44;
|
: 44;
|
||||||
|
|
||||||
const showOrgBanner = fromSmartLogin && (build === "saas" || env.flags.useOrgOnlyIdp);
|
const showOrgBanner =
|
||||||
|
fromSmartLogin &&
|
||||||
|
(build === "saas" || env.app.identityProviderMode === "org");
|
||||||
const orgBannerHref = redirect
|
const orgBannerHref = redirect
|
||||||
? `/auth/org?redirect=${encodeURIComponent(redirect)}`
|
? `/auth/org?redirect=${encodeURIComponent(redirect)}`
|
||||||
: "/auth/org";
|
: "/auth/org";
|
||||||
@@ -226,388 +228,398 @@ export default function SignupForm({
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
<Card className="w-full max-w-md">
|
<Card className="w-full max-w-md">
|
||||||
<CardHeader className="border-b">
|
<CardHeader className="border-b">
|
||||||
<div className="flex flex-row items-center justify-center">
|
<div className="flex flex-row items-center justify-center">
|
||||||
<BrandingLogo height={logoHeight} width={logoWidth} />
|
<BrandingLogo height={logoHeight} width={logoWidth} />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center space-y-1 pt-3">
|
<div className="text-center space-y-1 pt-3">
|
||||||
<p className="text-muted-foreground">{getSubtitle()}</p>
|
<p className="text-muted-foreground">{getSubtitle()}</p>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="pt-6">
|
<CardContent className="pt-6">
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="space-y-4"
|
className="space-y-4"
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="email"
|
name="email"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t("email")}</FormLabel>
|
<FormLabel>{t("email")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
disabled={!!emailParam}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="password"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<FormLabel>{t("password")}</FormLabel>
|
|
||||||
{passwordStrength.strength ===
|
|
||||||
"strong" && (
|
|
||||||
<Check className="h-4 w-4 text-green-500" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<div className="relative">
|
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
|
||||||
{...field}
|
{...field}
|
||||||
onChange={(e) => {
|
disabled={!!emailParam}
|
||||||
field.onChange(e);
|
|
||||||
setPasswordValue(
|
|
||||||
e.target.value
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
className={cn(
|
|
||||||
passwordStrength.strength ===
|
|
||||||
"strong" &&
|
|
||||||
"border-green-500 focus-visible:ring-green-500",
|
|
||||||
passwordStrength.strength ===
|
|
||||||
"medium" &&
|
|
||||||
"border-yellow-500 focus-visible:ring-yellow-500",
|
|
||||||
passwordStrength.strength ===
|
|
||||||
"weak" &&
|
|
||||||
passwordValue.length >
|
|
||||||
0 &&
|
|
||||||
"border-red-500 focus-visible:ring-red-500"
|
|
||||||
)}
|
|
||||||
autoComplete="new-password"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</FormControl>
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
{passwordValue.length > 0 && (
|
|
||||||
<div className="space-y-3 mt-2">
|
|
||||||
{/* Password Strength Meter */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="text-sm font-medium text-foreground">
|
|
||||||
{t("passwordStrength")}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"text-sm font-semibold",
|
|
||||||
passwordStrength.strength ===
|
|
||||||
"strong" &&
|
|
||||||
"text-green-600 dark:text-green-400",
|
|
||||||
passwordStrength.strength ===
|
|
||||||
"medium" &&
|
|
||||||
"text-yellow-600 dark:text-yellow-400",
|
|
||||||
passwordStrength.strength ===
|
|
||||||
"weak" &&
|
|
||||||
"text-red-600 dark:text-red-400"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
`passwordStrength${passwordStrength.strength.charAt(0).toUpperCase() + passwordStrength.strength.slice(1)}`
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Progress
|
|
||||||
value={
|
|
||||||
passwordStrength.percentage
|
|
||||||
}
|
|
||||||
className="h-2"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Requirements Checklist */}
|
|
||||||
<div className="bg-muted rounded-lg p-3 space-y-2">
|
|
||||||
<div className="text-sm font-medium text-foreground mb-2">
|
|
||||||
{t("passwordRequirements")}
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 gap-1.5">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{passwordStrength
|
|
||||||
.requirements
|
|
||||||
.length ? (
|
|
||||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
|
||||||
) : (
|
|
||||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
|
||||||
)}
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"text-sm",
|
|
||||||
passwordStrength
|
|
||||||
.requirements
|
|
||||||
.length
|
|
||||||
? "text-green-600 dark:text-green-400"
|
|
||||||
: "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
"passwordRequirementLengthText"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{passwordStrength
|
|
||||||
.requirements
|
|
||||||
.uppercase ? (
|
|
||||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
|
||||||
) : (
|
|
||||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
|
||||||
)}
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"text-sm",
|
|
||||||
passwordStrength
|
|
||||||
.requirements
|
|
||||||
.uppercase
|
|
||||||
? "text-green-600 dark:text-green-400"
|
|
||||||
: "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
"passwordRequirementUppercaseText"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{passwordStrength
|
|
||||||
.requirements
|
|
||||||
.lowercase ? (
|
|
||||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
|
||||||
) : (
|
|
||||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
|
||||||
)}
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"text-sm",
|
|
||||||
passwordStrength
|
|
||||||
.requirements
|
|
||||||
.lowercase
|
|
||||||
? "text-green-600 dark:text-green-400"
|
|
||||||
: "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
"passwordRequirementLowercaseText"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{passwordStrength
|
|
||||||
.requirements
|
|
||||||
.number ? (
|
|
||||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
|
||||||
) : (
|
|
||||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
|
||||||
)}
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"text-sm",
|
|
||||||
passwordStrength
|
|
||||||
.requirements
|
|
||||||
.number
|
|
||||||
? "text-green-600 dark:text-green-400"
|
|
||||||
: "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
"passwordRequirementNumberText"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{passwordStrength
|
|
||||||
.requirements
|
|
||||||
.special ? (
|
|
||||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
|
||||||
) : (
|
|
||||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
|
||||||
)}
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"text-sm",
|
|
||||||
passwordStrength
|
|
||||||
.requirements
|
|
||||||
.special
|
|
||||||
? "text-green-600 dark:text-green-400"
|
|
||||||
: "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
"passwordRequirementSpecialText"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Only show FormMessage when not showing our custom requirements */}
|
|
||||||
{passwordValue.length === 0 && (
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
)}
|
</FormItem>
|
||||||
</FormItem>
|
)}
|
||||||
)}
|
/>
|
||||||
/>
|
<FormField
|
||||||
<FormField
|
control={form.control}
|
||||||
control={form.control}
|
name="password"
|
||||||
name="confirmPassword"
|
render={({ field }) => (
|
||||||
render={({ field }) => (
|
<FormItem>
|
||||||
<FormItem>
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<FormLabel>
|
||||||
<FormLabel>
|
{t("password")}
|
||||||
{t("confirmPassword")}
|
</FormLabel>
|
||||||
</FormLabel>
|
{passwordStrength.strength ===
|
||||||
{doPasswordsMatch && (
|
"strong" && (
|
||||||
<Check className="h-4 w-4 text-green-500" />
|
<Check className="h-4 w-4 text-green-500" />
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<div className="relative">
|
|
||||||
<Input
|
|
||||||
type="password"
|
|
||||||
{...field}
|
|
||||||
onChange={(e) => {
|
|
||||||
field.onChange(e);
|
|
||||||
setConfirmPasswordValue(
|
|
||||||
e.target.value
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
className={cn(
|
|
||||||
doPasswordsMatch &&
|
|
||||||
"border-green-500 focus-visible:ring-green-500",
|
|
||||||
confirmPasswordValue.length >
|
|
||||||
0 &&
|
|
||||||
!doPasswordsMatch &&
|
|
||||||
"border-red-500 focus-visible:ring-red-500"
|
|
||||||
)}
|
|
||||||
autoComplete="new-password"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
<FormControl>
|
||||||
{confirmPasswordValue.length > 0 &&
|
<div className="relative">
|
||||||
!doPasswordsMatch && (
|
<Input
|
||||||
<p className="text-sm text-red-600 mt-1">
|
type="password"
|
||||||
{t("passwordsDoNotMatch")}
|
{...field}
|
||||||
</p>
|
onChange={(e) => {
|
||||||
)}
|
field.onChange(e);
|
||||||
{/* Only show FormMessage when field is empty */}
|
setPasswordValue(
|
||||||
{confirmPasswordValue.length === 0 && (
|
e.target.value
|
||||||
<FormMessage />
|
|
||||||
)}
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{build === "saas" && (
|
|
||||||
<>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="agreeToTerms"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex flex-row items-center">
|
|
||||||
<FormControl>
|
|
||||||
<Checkbox
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={(
|
|
||||||
checked
|
|
||||||
) => {
|
|
||||||
field.onChange(checked);
|
|
||||||
handleTermsChange(
|
|
||||||
checked as boolean
|
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
className={cn(
|
||||||
</FormControl>
|
passwordStrength.strength ===
|
||||||
<div className="leading-none">
|
"strong" &&
|
||||||
<FormLabel className="text-sm font-normal">
|
"border-green-500 focus-visible:ring-green-500",
|
||||||
<div>
|
passwordStrength.strength ===
|
||||||
{t(
|
"medium" &&
|
||||||
"signUpTerms.IAgreeToThe"
|
"border-yellow-500 focus-visible:ring-yellow-500",
|
||||||
)}{" "}
|
passwordStrength.strength ===
|
||||||
<a
|
"weak" &&
|
||||||
href="https://pangolin.net/terms-of-service.html"
|
passwordValue.length >
|
||||||
target="_blank"
|
0 &&
|
||||||
rel="noopener noreferrer"
|
"border-red-500 focus-visible:ring-red-500"
|
||||||
className="text-primary hover:underline"
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
"signUpTerms.termsOfService"
|
|
||||||
)}{" "}
|
|
||||||
</a>
|
|
||||||
{t("signUpTerms.and")}{" "}
|
|
||||||
<a
|
|
||||||
href="https://pangolin.net/privacy-policy.html"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-primary hover:underline"
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
"signUpTerms.privacyPolicy"
|
|
||||||
)}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</FormLabel>
|
|
||||||
<FormMessage />
|
|
||||||
</div>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="marketingEmailConsent"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex flex-row items-start">
|
|
||||||
<FormControl>
|
|
||||||
<Checkbox
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={
|
|
||||||
field.onChange
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<div className="leading-none">
|
|
||||||
<FormLabel className="text-sm font-normal">
|
|
||||||
{t(
|
|
||||||
"signUpMarketing.keepMeInTheLoop"
|
|
||||||
)}
|
)}
|
||||||
</FormLabel>
|
autoComplete="new-password"
|
||||||
<FormMessage />
|
/>
|
||||||
</div>
|
</div>
|
||||||
</FormItem>
|
</FormControl>
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{error && (
|
{passwordValue.length > 0 && (
|
||||||
<Alert variant="destructive">
|
<div className="space-y-3 mt-2">
|
||||||
<AlertDescription>{error}</AlertDescription>
|
{/* Password Strength Meter */}
|
||||||
</Alert>
|
<div className="space-y-2">
|
||||||
)}
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm font-medium text-foreground">
|
||||||
|
{t(
|
||||||
|
"passwordStrength"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-sm font-semibold",
|
||||||
|
passwordStrength.strength ===
|
||||||
|
"strong" &&
|
||||||
|
"text-green-600 dark:text-green-400",
|
||||||
|
passwordStrength.strength ===
|
||||||
|
"medium" &&
|
||||||
|
"text-yellow-600 dark:text-yellow-400",
|
||||||
|
passwordStrength.strength ===
|
||||||
|
"weak" &&
|
||||||
|
"text-red-600 dark:text-red-400"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
`passwordStrength${passwordStrength.strength.charAt(0).toUpperCase() + passwordStrength.strength.slice(1)}`
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Progress
|
||||||
|
value={
|
||||||
|
passwordStrength.percentage
|
||||||
|
}
|
||||||
|
className="h-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button type="submit" className="w-full">
|
{/* Requirements Checklist */}
|
||||||
{t("createAccount")}
|
<div className="bg-muted rounded-lg p-3 space-y-2">
|
||||||
</Button>
|
<div className="text-sm font-medium text-foreground mb-2">
|
||||||
</form>
|
{t(
|
||||||
</Form>
|
"passwordRequirements"
|
||||||
</CardContent>
|
)}
|
||||||
</Card>
|
</div>
|
||||||
|
<div className="grid grid-cols-1 gap-1.5">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{passwordStrength
|
||||||
|
.requirements
|
||||||
|
.length ? (
|
||||||
|
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||||
|
) : (
|
||||||
|
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-sm",
|
||||||
|
passwordStrength
|
||||||
|
.requirements
|
||||||
|
.length
|
||||||
|
? "text-green-600 dark:text-green-400"
|
||||||
|
: "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"passwordRequirementLengthText"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{passwordStrength
|
||||||
|
.requirements
|
||||||
|
.uppercase ? (
|
||||||
|
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||||
|
) : (
|
||||||
|
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-sm",
|
||||||
|
passwordStrength
|
||||||
|
.requirements
|
||||||
|
.uppercase
|
||||||
|
? "text-green-600 dark:text-green-400"
|
||||||
|
: "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"passwordRequirementUppercaseText"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{passwordStrength
|
||||||
|
.requirements
|
||||||
|
.lowercase ? (
|
||||||
|
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||||
|
) : (
|
||||||
|
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-sm",
|
||||||
|
passwordStrength
|
||||||
|
.requirements
|
||||||
|
.lowercase
|
||||||
|
? "text-green-600 dark:text-green-400"
|
||||||
|
: "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"passwordRequirementLowercaseText"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{passwordStrength
|
||||||
|
.requirements
|
||||||
|
.number ? (
|
||||||
|
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||||
|
) : (
|
||||||
|
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-sm",
|
||||||
|
passwordStrength
|
||||||
|
.requirements
|
||||||
|
.number
|
||||||
|
? "text-green-600 dark:text-green-400"
|
||||||
|
: "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"passwordRequirementNumberText"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{passwordStrength
|
||||||
|
.requirements
|
||||||
|
.special ? (
|
||||||
|
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||||
|
) : (
|
||||||
|
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-sm",
|
||||||
|
passwordStrength
|
||||||
|
.requirements
|
||||||
|
.special
|
||||||
|
? "text-green-600 dark:text-green-400"
|
||||||
|
: "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"passwordRequirementSpecialText"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Only show FormMessage when not showing our custom requirements */}
|
||||||
|
{passwordValue.length === 0 && (
|
||||||
|
<FormMessage />
|
||||||
|
)}
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="confirmPassword"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<FormLabel>
|
||||||
|
{t("confirmPassword")}
|
||||||
|
</FormLabel>
|
||||||
|
{doPasswordsMatch && (
|
||||||
|
<Check className="h-4 w-4 text-green-500" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
{...field}
|
||||||
|
onChange={(e) => {
|
||||||
|
field.onChange(e);
|
||||||
|
setConfirmPasswordValue(
|
||||||
|
e.target.value
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
doPasswordsMatch &&
|
||||||
|
"border-green-500 focus-visible:ring-green-500",
|
||||||
|
confirmPasswordValue.length >
|
||||||
|
0 &&
|
||||||
|
!doPasswordsMatch &&
|
||||||
|
"border-red-500 focus-visible:ring-red-500"
|
||||||
|
)}
|
||||||
|
autoComplete="new-password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
{confirmPasswordValue.length > 0 &&
|
||||||
|
!doPasswordsMatch && (
|
||||||
|
<p className="text-sm text-red-600 mt-1">
|
||||||
|
{t("passwordsDoNotMatch")}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{/* Only show FormMessage when field is empty */}
|
||||||
|
{confirmPasswordValue.length === 0 && (
|
||||||
|
<FormMessage />
|
||||||
|
)}
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{build === "saas" && (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="agreeToTerms"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center">
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={(
|
||||||
|
checked
|
||||||
|
) => {
|
||||||
|
field.onChange(
|
||||||
|
checked
|
||||||
|
);
|
||||||
|
handleTermsChange(
|
||||||
|
checked as boolean
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className="leading-none">
|
||||||
|
<FormLabel className="text-sm font-normal">
|
||||||
|
<div>
|
||||||
|
{t(
|
||||||
|
"signUpTerms.IAgreeToThe"
|
||||||
|
)}{" "}
|
||||||
|
<a
|
||||||
|
href="https://pangolin.net/terms-of-service.html"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"signUpTerms.termsOfService"
|
||||||
|
)}{" "}
|
||||||
|
</a>
|
||||||
|
{t(
|
||||||
|
"signUpTerms.and"
|
||||||
|
)}{" "}
|
||||||
|
<a
|
||||||
|
href="https://pangolin.net/privacy-policy.html"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"signUpTerms.privacyPolicy"
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</FormLabel>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="marketingEmailConsent"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-start">
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={
|
||||||
|
field.onChange
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className="leading-none">
|
||||||
|
<FormLabel className="text-sm font-normal">
|
||||||
|
{t(
|
||||||
|
"signUpMarketing.keepMeInTheLoop"
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertDescription>{error}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button type="submit" className="w-full">
|
||||||
|
{t("createAccount")}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,11 @@ export function pullEnv(): Env {
|
|||||||
process.env.NEW_RELEASES_NOTIFICATION_ENABLED === "true"
|
process.env.NEW_RELEASES_NOTIFICATION_ENABLED === "true"
|
||||||
? true
|
? true
|
||||||
: false
|
: false
|
||||||
}
|
},
|
||||||
|
identityProviderMode: process.env.IDENTITY_PROVIDER_MODE as
|
||||||
|
| "org"
|
||||||
|
| "global"
|
||||||
|
| undefined
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
emailEnabled: process.env.EMAIL_ENABLED === "true" ? true : false
|
emailEnabled: process.env.EMAIL_ENABLED === "true" ? true : false
|
||||||
@@ -64,8 +68,6 @@ export function pullEnv(): Env {
|
|||||||
process.env.FLAGS_DISABLE_PRODUCT_HELP_BANNERS === "true"
|
process.env.FLAGS_DISABLE_PRODUCT_HELP_BANNERS === "true"
|
||||||
? true
|
? true
|
||||||
: false,
|
: false,
|
||||||
useOrgOnlyIdp:
|
|
||||||
process.env.USE_ORG_ONLY_IDP === "true" ? true : false,
|
|
||||||
disableEnterpriseFeatures:
|
disableEnterpriseFeatures:
|
||||||
process.env.DISABLE_ENTERPRISE_FEATURES === "true"
|
process.env.DISABLE_ENTERPRISE_FEATURES === "true"
|
||||||
? true
|
? true
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export type Env = {
|
|||||||
product_updates: boolean;
|
product_updates: boolean;
|
||||||
new_releases: boolean;
|
new_releases: boolean;
|
||||||
};
|
};
|
||||||
|
identityProviderMode?: "global" | "org";
|
||||||
};
|
};
|
||||||
server: {
|
server: {
|
||||||
externalPort: string;
|
externalPort: string;
|
||||||
@@ -34,7 +35,6 @@ export type Env = {
|
|||||||
hideSupporterKey: boolean;
|
hideSupporterKey: boolean;
|
||||||
usePangolinDns: boolean;
|
usePangolinDns: boolean;
|
||||||
disableProductHelpBanners: boolean;
|
disableProductHelpBanners: boolean;
|
||||||
useOrgOnlyIdp: boolean;
|
|
||||||
disableEnterpriseFeatures: boolean;
|
disableEnterpriseFeatures: boolean;
|
||||||
};
|
};
|
||||||
branding: {
|
branding: {
|
||||||
|
|||||||
Reference in New Issue
Block a user