Merge pull request #1091 from fosrl/dev

Dev
This commit is contained in:
Milo Schwartz
2025-07-18 18:53:45 -04:00
committed by GitHub
15 changed files with 128 additions and 132 deletions

View File

@@ -1,4 +1,4 @@
iame: captcha_remediation name: captcha_remediation
filters: filters:
- Alert.Remediation == true && Alert.GetScope() == "Ip" && Alert.GetScenario() contains "http" - Alert.Remediation == true && Alert.GetScope() == "Ip" && Alert.GetScenario() contains "http"
decisions: decisions:
@@ -22,4 +22,4 @@ filters:
decisions: decisions:
- type: ban - type: ban
duration: 4h duration: 4h
on_success: break on_success: break

View File

@@ -1162,7 +1162,7 @@
"selectDomainTypeCnameName": "Single Domain (CNAME)", "selectDomainTypeCnameName": "Single Domain (CNAME)",
"selectDomainTypeCnameDescription": "Just this specific domain. Use this for individual subdomains or specific domain entries.", "selectDomainTypeCnameDescription": "Just this specific domain. Use this for individual subdomains or specific domain entries.",
"selectDomainTypeWildcardName": "Wildcard Domain", "selectDomainTypeWildcardName": "Wildcard Domain",
"selectDomainTypeWildcardDescription": "This domain and its first level of subdomains.", "selectDomainTypeWildcardDescription": "This domain and its subdomains.",
"domainDelegation": "Single Domain", "domainDelegation": "Single Domain",
"selectType": "Select a type", "selectType": "Select a type",
"actions": "Actions", "actions": "Actions",

View File

@@ -18,10 +18,10 @@ function createEmailClient() {
host: emailConfig.smtp_host, host: emailConfig.smtp_host,
port: emailConfig.smtp_port, port: emailConfig.smtp_port,
secure: emailConfig.smtp_secure || false, secure: emailConfig.smtp_secure || false,
auth: { auth: (emailConfig.smtp_user && emailConfig.smtp_pass) ? {
user: emailConfig.smtp_user, user: emailConfig.smtp_user,
pass: emailConfig.smtp_pass pass: emailConfig.smtp_pass
} } : null
} as SMTPTransport.Options; } as SMTPTransport.Options;
if (emailConfig.smtp_tls_reject_unauthorized !== undefined) { if (emailConfig.smtp_tls_reject_unauthorized !== undefined) {

View File

@@ -2,7 +2,7 @@ import path from "path";
import { fileURLToPath } from "url"; import { fileURLToPath } from "url";
// This is a placeholder value replaced by the build process // This is a placeholder value replaced by the build process
export const APP_VERSION = "1.7.0"; export const APP_VERSION = "1.7.3";
export const __FILENAME = fileURLToPath(import.meta.url); export const __FILENAME = fileURLToPath(import.meta.url);
export const __DIRNAME = path.dirname(__FILENAME); export const __DIRNAME = path.dirname(__FILENAME);

View File

@@ -620,8 +620,6 @@ authenticated.post(
authenticated.delete("/idp/:idpId", verifyUserIsServerAdmin, idp.deleteIdp); authenticated.delete("/idp/:idpId", verifyUserIsServerAdmin, idp.deleteIdp);
authenticated.get("/idp", verifyUserIsServerAdmin, idp.listIdps);
authenticated.get("/idp/:idpId", verifyUserIsServerAdmin, idp.getIdp); authenticated.get("/idp/:idpId", verifyUserIsServerAdmin, idp.getIdp);
authenticated.put( authenticated.put(

View File

@@ -162,6 +162,12 @@ export async function validateOidcCallback(
); );
} }
logger.debug("State verified", {
urL: ensureTrailingSlash(existingIdp.idpOidcConfig.tokenUrl),
expectedState,
state
});
const tokens = await client.validateAuthorizationCode( const tokens = await client.validateAuthorizationCode(
ensureTrailingSlash(existingIdp.idpOidcConfig.tokenUrl), ensureTrailingSlash(existingIdp.idpOidcConfig.tokenUrl),
code, code,

View File

@@ -261,14 +261,6 @@ async function createHttpResource(
) )
); );
} }
if (parsedSubdomain.data.includes(".")) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Subdomain cannot contain a dot when using wildcard domains"
)
);
}
fullDomain = `${subdomain}.${domainRes.domains.baseDomain}`; fullDomain = `${subdomain}.${domainRes.domains.baseDomain}`;
} else { } else {
fullDomain = domainRes.domains.baseDomain; fullDomain = domainRes.domains.baseDomain;

View File

@@ -297,14 +297,6 @@ async function updateHttpResource(
) )
); );
} }
if (parsedSubdomain.data.includes(".")) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Subdomain cannot contain a dot when using wildcard domains"
)
);
}
fullDomain = `${updateData.subdomain}.${domainRes.domains.baseDomain}`; fullDomain = `${updateData.subdomain}.${domainRes.domains.baseDomain}`;
} else { } else {
fullDomain = domainRes.domains.baseDomain; fullDomain = domainRes.domains.baseDomain;

View File

@@ -9,7 +9,7 @@ import {
SettingsSectionHeader, SettingsSectionHeader,
SettingsSectionTitle SettingsSectionTitle
} from "@app/components/Settings"; } from "@app/components/Settings";
import { StrategySelect } from "@app/components/StrategySelect"; import { StrategyOption, StrategySelect } from "@app/components/StrategySelect";
import HeaderTitle from "@app/components/SettingsSectionTitle"; import HeaderTitle from "@app/components/SettingsSectionTitle";
import { Button } from "@app/components/ui/button"; import { Button } from "@app/components/ui/button";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
@@ -45,15 +45,10 @@ import { createApiClient } from "@app/lib/api";
import { Checkbox } from "@app/components/ui/checkbox"; import { Checkbox } from "@app/components/ui/checkbox";
import { ListIdpsResponse } from "@server/routers/idp"; import { ListIdpsResponse } from "@server/routers/idp";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { build } from "@server/build";
type UserType = "internal" | "oidc"; type UserType = "internal" | "oidc";
interface UserTypeOption {
id: UserType;
title: string;
description: string;
}
interface IdpOption { interface IdpOption {
idpId: number; idpId: number;
name: string; name: string;
@@ -147,13 +142,20 @@ export default function Page() {
} }
}, [userType, env.email.emailEnabled, internalForm, externalForm]); }, [userType, env.email.emailEnabled, internalForm, externalForm]);
const userTypes: UserTypeOption[] = [ const [userTypes, setUserTypes] = useState<StrategyOption<string>[]>([
{ {
id: "internal", id: "internal",
title: t("userTypeInternal"), title: t("userTypeInternal"),
description: t("userTypeInternalDescription") description: t("userTypeInternalDescription"),
disabled: false
},
{
id: "oidc",
title: t("userTypeExternal"),
description: t("userTypeExternalDescription"),
disabled: true
} }
]; ]);
useEffect(() => { useEffect(() => {
if (!userType) { if (!userType) {
@@ -177,9 +179,6 @@ export default function Page() {
if (res?.status === 200) { if (res?.status === 200) {
setRoles(res.data.data.roles); setRoles(res.data.data.roles);
if (userType === "internal") {
setDataLoaded(true);
}
} }
} }
@@ -200,24 +199,32 @@ export default function Page() {
if (res?.status === 200) { if (res?.status === 200) {
setIdps(res.data.data.idps); setIdps(res.data.data.idps);
setDataLoaded(true);
if (res.data.data.idps.length) { if (res.data.data.idps.length) {
userTypes.push({ setUserTypes((prev) =>
id: "oidc", prev.map((type) => {
title: t("userTypeExternal"), if (type.id === "oidc") {
description: t("userTypeExternalDescription") return {
}); ...type,
disabled: false
};
}
return type;
})
);
} }
} }
} }
setDataLoaded(false); async function fetchInitialData() {
fetchRoles(); setDataLoaded(false);
if (userType !== "internal") { await fetchRoles();
fetchIdps(); await fetchIdps();
setDataLoaded(true);
} }
}, [userType]);
fetchInitialData();
}, []);
async function onSubmitInternal( async function onSubmitInternal(
values: z.infer<typeof internalFormSchema> values: z.infer<typeof internalFormSchema>
@@ -323,7 +330,7 @@ export default function Page() {
<div> <div>
<SettingsContainer> <SettingsContainer>
{!inviteLink && userTypes.length > 1 ? ( {!inviteLink && build !== "saas" ? (
<SettingsSection> <SettingsSection>
<SettingsSectionHeader> <SettingsSectionHeader>
<SettingsSectionTitle> <SettingsSectionTitle>
@@ -610,7 +617,7 @@ export default function Page() {
idp || null idp || null
); );
}} }}
cols={3} cols={2}
/> />
<FormMessage /> <FormMessage />
</FormItem> </FormItem>

View File

@@ -12,6 +12,7 @@ import { cleanRedirect } from "@app/lib/cleanRedirect";
import { Layout } from "@app/components/Layout"; import { Layout } from "@app/components/Layout";
import { InitialSetupCompleteResponse } from "@server/routers/auth"; import { InitialSetupCompleteResponse } from "@server/routers/auth";
import { cookies } from "next/headers"; import { cookies } from "next/headers";
import { build } from "@server/build";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
@@ -83,25 +84,27 @@ export default async function Page(props: {
if (ownedOrg) { if (ownedOrg) {
redirect(`/${ownedOrg.orgId}`); redirect(`/${ownedOrg.orgId}`);
} else { } else {
redirect("/setup"); if (!env.flags.disableUserCreateOrg || user.serverAdmin) {
redirect("/setup");
}
} }
} }
// return ( return (
// <UserProvider user={user}> <UserProvider user={user}>
// <Layout orgs={orgs} navItems={[]}> <Layout orgs={orgs} navItems={[]}>
// <div className="w-full max-w-md mx-auto md:mt-32 mt-4"> <div className="w-full max-w-md mx-auto md:mt-32 mt-4">
// <OrganizationLanding <OrganizationLanding
// disableCreateOrg={ disableCreateOrg={
// env.flags.disableUserCreateOrg && !user.serverAdmin env.flags.disableUserCreateOrg && !user.serverAdmin
// } }
// organizations={orgs.map((org) => ({ organizations={orgs.map((org) => ({
// name: org.name, name: org.name,
// id: org.orgId id: org.orgId
// }))} }))}
// /> />
// </div> </div>
// </Layout> </Layout>
// </UserProvider> </UserProvider>
// ); );
} }

View File

@@ -179,7 +179,7 @@ export default function DomainPicker({
}); });
} }
} else if (orgDomain.type === "wildcard") { } else if (orgDomain.type === "wildcard") {
// For wildcard domains, allow the base domain or one level up // For wildcard domains, allow the base domain or multiple levels up
const userInputLower = userInput.toLowerCase(); const userInputLower = userInput.toLowerCase();
const baseDomainLower = orgDomain.baseDomain.toLowerCase(); const baseDomainLower = orgDomain.baseDomain.toLowerCase();
@@ -194,24 +194,22 @@ export default function DomainPicker({
domainId: orgDomain.domainId domainId: orgDomain.domainId
}); });
} }
// Check if user input is one level up (subdomain.baseDomain) // Check if user input ends with the base domain (allows multiple level subdomains)
else if (userInputLower.endsWith(`.${baseDomainLower}`)) { else if (userInputLower.endsWith(`.${baseDomainLower}`)) {
const subdomain = userInputLower.slice( const subdomain = userInputLower.slice(
0, 0,
-(baseDomainLower.length + 1) -(baseDomainLower.length + 1)
); );
// Only allow one level up (no dots in subdomain) // Allow multiple levels (subdomain can contain dots)
if (!subdomain.includes(".")) { options.push({
options.push({ id: `org-${orgDomain.domainId}`,
id: `org-${orgDomain.domainId}`, domain: userInput,
domain: userInput, type: "organization",
type: "organization", verified: orgDomain.verified,
verified: orgDomain.verified, domainType: "wildcard",
domainType: "wildcard", domainId: orgDomain.domainId,
domainId: orgDomain.domainId, subdomain: subdomain
subdomain: subdomain });
});
}
} }
} }
}); });
@@ -320,7 +318,7 @@ export default function DomainPicker({
setUserInput(validInput); setUserInput(validInput);
}} }}
/> />
<p className="text-xs text-muted-foreground"> <p className="text-sm text-muted-foreground">
{build === "saas" {build === "saas"
? t("domainPickerDescriptionSaas") ? t("domainPickerDescriptionSaas")
: t("domainPickerDescription")} : t("domainPickerDescription")}
@@ -328,42 +326,44 @@ export default function DomainPicker({
</div> </div>
{/* Tabs and Sort Toggle */} {/* Tabs and Sort Toggle */}
<div className="flex justify-between items-center"> {build === "saas" && (
<Tabs <div className="flex justify-between items-center">
value={activeTab} <Tabs
onValueChange={(value) => value={activeTab}
setActiveTab( onValueChange={(value) =>
value as "all" | "organization" | "provided" setActiveTab(
) value as "all" | "organization" | "provided"
} )
> }
<TabsList> >
<TabsTrigger value="all"> <TabsList>
{t("domainPickerTabAll")} <TabsTrigger value="all">
</TabsTrigger> {t("domainPickerTabAll")}
<TabsTrigger value="organization">
{t("domainPickerTabOrganization")}
</TabsTrigger>
{build == "saas" && (
<TabsTrigger value="provided">
{t("domainPickerTabProvided")}
</TabsTrigger> </TabsTrigger>
)} <TabsTrigger value="organization">
</TabsList> {t("domainPickerTabOrganization")}
</Tabs> </TabsTrigger>
<Button {build == "saas" && (
variant="outline" <TabsTrigger value="provided">
size="sm" {t("domainPickerTabProvided")}
onClick={() => </TabsTrigger>
setSortOrder(sortOrder === "asc" ? "desc" : "asc") )}
} </TabsList>
> </Tabs>
<ArrowUpDown className="h-4 w-4 mr-2" /> <Button
{sortOrder === "asc" variant="outline"
? t("domainPickerSortAsc") size="sm"
: t("domainPickerSortDesc")} onClick={() =>
</Button> setSortOrder(sortOrder === "asc" ? "desc" : "asc")
</div> }
>
<ArrowUpDown className="h-4 w-4 mr-2" />
{sortOrder === "asc"
? t("domainPickerSortAsc")
: t("domainPickerSortDesc")}
</Button>
</div>
)}
{/* Loading State */} {/* Loading State */}
{isChecking && ( {isChecking && (

View File

@@ -41,35 +41,31 @@ export default function OrganizationLanding({
function getDescriptionText() { function getDescriptionText() {
if (organizations.length === 0) { if (organizations.length === 0) {
if (!disableCreateOrg) { if (!disableCreateOrg) {
return t('componentsErrorNoMemberCreate'); return t("componentsErrorNoMemberCreate");
} else { } else {
return t('componentsErrorNoMember'); return t("componentsErrorNoMember");
} }
} }
return t('componentsMember', {count: organizations.length}); return t("componentsMember", { count: organizations.length });
} }
return ( return (
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>{t('welcome')}</CardTitle> <CardTitle>{t("welcome")}</CardTitle>
<CardDescription>{getDescriptionText()}</CardDescription> <CardDescription>{getDescriptionText()}</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{organizations.length === 0 ? ( {organizations.length === 0 ? (
disableCreateOrg ? ( !disableCreateOrg && (
<p className="text-center text-muted-foreground">
t('componentsErrorNoMember')
</p>
) : (
<Link href="/setup"> <Link href="/setup">
<Button <Button
className="w-full h-auto py-3 text-lg" className="w-full h-auto py-3 text-lg"
size="lg" size="lg"
> >
<Plus className="mr-2 h-5 w-5" /> <Plus className="mr-2 h-5 w-5" />
{t('componentsCreateOrg')} {t("componentsCreateOrg")}
</Button> </Button>
</Link> </Link>
) )

View File

@@ -103,8 +103,10 @@ export default function SecurityKeyForm({
}); });
useEffect(() => { useEffect(() => {
loadSecurityKeys(); if (open) {
}, []); loadSecurityKeys();
}
}, [open]);
const registerSchema = z.object({ const registerSchema = z.object({
name: z.string().min(1, { message: t("securityKeyNameRequired") }), name: z.string().min(1, { message: t("securityKeyNameRequired") }),

View File

@@ -4,7 +4,7 @@ import { cn } from "@app/lib/cn";
import { RadioGroup, RadioGroupItem } from "./ui/radio-group"; import { RadioGroup, RadioGroupItem } from "./ui/radio-group";
import { useState } from "react"; import { useState } from "react";
interface StrategyOption<TValue extends string> { export interface StrategyOption<TValue extends string> {
id: TValue; id: TValue;
title: string; title: string;
description: string; description: string;

View File

@@ -18,7 +18,7 @@ function PopoverTrigger({
function PopoverContent({ function PopoverContent({
className, className,
align = "center", align = "start",
sideOffset = 4, sideOffset = 4,
...props ...props
}: React.ComponentProps<typeof PopoverPrimitive.Content>) { }: React.ComponentProps<typeof PopoverPrimitive.Content>) {