mirror of
https://github.com/fosrl/pangolin.git
synced 2026-07-05 11:49:48 +00:00
Compare commits
3 Commits
feat/remem
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e37dbd640c | ||
|
|
49c2d3163e | ||
|
|
45b9e13a13 |
@@ -1,5 +1,5 @@
|
||||
# FROM node:24-slim AS base
|
||||
FROM public.ecr.aws/docker/library/node:24-slim AS base
|
||||
FROM public.ecr.aws/docker/library/node:26-slim AS base
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -33,7 +33,7 @@ FROM base AS builder
|
||||
RUN npm ci --omit=dev
|
||||
|
||||
# FROM node:24-slim AS runner
|
||||
FROM public.ecr.aws/docker/library/node:24-slim AS runner
|
||||
FROM public.ecr.aws/docker/library/node:26-slim AS runner
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:24-alpine
|
||||
FROM node:26-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -1503,7 +1503,6 @@
|
||||
"otpAuthDescription": "Enter the code from your authenticator app or one of your single-use backup codes.",
|
||||
"otpAuthSubmit": "Submit Code",
|
||||
"idpContinue": "Or continue with",
|
||||
"idpLastUsed": "Last used",
|
||||
"otpAuthBack": "Back to Password",
|
||||
"navbar": "Navigation Menu",
|
||||
"navbarDescription": "Main navigation menu for the application",
|
||||
|
||||
@@ -5,21 +5,8 @@ import { Newt } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import logger from "@server/logger";
|
||||
import { sendNewtSyncMessage } from "./sync";
|
||||
import semver from "semver";
|
||||
import { recordSitePing } from "./pingAccumulator";
|
||||
|
||||
const NEWT_SUPPORTS_SYNC_VERSION = ">=1.14.0";
|
||||
const PONG = {
|
||||
message: {
|
||||
type: "pong",
|
||||
data: {
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
},
|
||||
broadcast: false,
|
||||
excludeSender: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles ping messages from newt clients.
|
||||
*
|
||||
@@ -50,14 +37,6 @@ export const handleNewtPingMessage: MessageHandler = async (context) => {
|
||||
// cross-region latency to the database.
|
||||
recordSitePing(newt.siteId);
|
||||
|
||||
if (
|
||||
newt.version &&
|
||||
!semver.satisfies(newt.version, NEWT_SUPPORTS_SYNC_VERSION)
|
||||
) {
|
||||
// Newt does not support the sync message so not checking - stop here -
|
||||
return PONG;
|
||||
}
|
||||
|
||||
// Check config version and sync if stale.
|
||||
const configVersion = await getClientConfigVersion(newt.newtId);
|
||||
|
||||
@@ -86,5 +65,14 @@ export const handleNewtPingMessage: MessageHandler = async (context) => {
|
||||
await sendNewtSyncMessage(newt, site);
|
||||
}
|
||||
|
||||
return PONG;
|
||||
return {
|
||||
message: {
|
||||
type: "pong",
|
||||
data: {
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
},
|
||||
broadcast: false,
|
||||
excludeSender: false
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9,45 +9,45 @@ import {
|
||||
import { canCompress } from "@server/lib/clientVersionChecks";
|
||||
|
||||
export async function sendNewtSyncMessage(newt: Newt, site: Site) {
|
||||
const {
|
||||
tcpTargets,
|
||||
udpTargets,
|
||||
validHealthCheckTargets,
|
||||
browserGatewayTargets,
|
||||
remoteExitNodeSubnets
|
||||
} = await buildTargetConfigurationForNewtClient(site.siteId);
|
||||
let exitNode: ExitNode | undefined;
|
||||
if (site.exitNodeId) {
|
||||
[exitNode] = await db
|
||||
.select()
|
||||
.from(exitNodes)
|
||||
.where(eq(exitNodes.exitNodeId, site.exitNodeId))
|
||||
.limit(1);
|
||||
}
|
||||
const { peers, targets } = await buildClientConfigurationForNewtClient(
|
||||
site,
|
||||
exitNode
|
||||
);
|
||||
await sendToClient(
|
||||
newt.newtId,
|
||||
{
|
||||
type: "newt/sync",
|
||||
data: {
|
||||
proxyTargets: {
|
||||
udp: udpTargets,
|
||||
tcp: tcpTargets
|
||||
},
|
||||
healthCheckTargets: validHealthCheckTargets,
|
||||
peers: peers,
|
||||
clientTargets: targets,
|
||||
browserGatewayTargets: browserGatewayTargets,
|
||||
remoteExitNodeSubnets: remoteExitNodeSubnets
|
||||
}
|
||||
},
|
||||
{
|
||||
compress: canCompress(newt.version, "newt")
|
||||
}
|
||||
).catch((error) => {
|
||||
logger.warn(`Error sending newt sync message:`, error);
|
||||
});
|
||||
// const {
|
||||
// tcpTargets,
|
||||
// udpTargets,
|
||||
// validHealthCheckTargets,
|
||||
// browserGatewayTargets,
|
||||
// remoteExitNodeSubnets
|
||||
// } = await buildTargetConfigurationForNewtClient(site.siteId);
|
||||
// let exitNode: ExitNode | undefined;
|
||||
// if (site.exitNodeId) {
|
||||
// [exitNode] = await db
|
||||
// .select()
|
||||
// .from(exitNodes)
|
||||
// .where(eq(exitNodes.exitNodeId, site.exitNodeId))
|
||||
// .limit(1);
|
||||
// }
|
||||
// const { peers, targets } = await buildClientConfigurationForNewtClient(
|
||||
// site,
|
||||
// exitNode
|
||||
// );
|
||||
// await sendToClient(
|
||||
// newt.newtId,
|
||||
// {
|
||||
// type: "newt/sync",
|
||||
// data: {
|
||||
// proxyTargets: {
|
||||
// udp: udpTargets,
|
||||
// tcp: tcpTargets
|
||||
// },
|
||||
// healthCheckTargets: validHealthCheckTargets,
|
||||
// peers: peers,
|
||||
// clientTargets: targets,
|
||||
// browserGatewayTargets: browserGatewayTargets,
|
||||
// remoteExitNodeSubnets: remoteExitNodeSubnets
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// compress: canCompress(newt.version, "newt")
|
||||
// }
|
||||
// ).catch((error) => {
|
||||
// logger.warn(`Error sending newt sync message:`, error);
|
||||
// });
|
||||
}
|
||||
|
||||
@@ -1057,23 +1057,21 @@ export default function BillingPage() {
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Current Usage */}
|
||||
<div className="border rounded-lg p-4 md:col-span-1">
|
||||
<div className="border rounded-lg p-4">
|
||||
<div className="text-sm text-muted-foreground mb-2">
|
||||
{t("billingCurrentUsage") || "Current Usage"}
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-1">
|
||||
<div className="flex items-baseline gap-2">
|
||||
<span className="text-3xl font-semibold">
|
||||
{getUserCount()}
|
||||
</span>
|
||||
<span className="text-lg">
|
||||
{t("billingUsers") || "Users"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<span className="text-3xl font-semibold">
|
||||
{getUserCount()}
|
||||
</span>
|
||||
<span className="text-lg">
|
||||
{t("billingUsers") || "Users"}
|
||||
</span>
|
||||
{hasSubscription && getPricePerUser() > 0 && (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<div className="text-sm text-muted-foreground mt-1">
|
||||
x ${getPricePerUser()} / month = $
|
||||
{getUserCount() * getPricePerUser()} /
|
||||
month
|
||||
@@ -1083,7 +1081,7 @@ export default function BillingPage() {
|
||||
</div>
|
||||
|
||||
{/* Maximum Limits */}
|
||||
<div className="border rounded-lg p-4 md:col-span-3">
|
||||
<div className="border rounded-lg p-4">
|
||||
<div className="text-sm text-muted-foreground mb-3">
|
||||
{t("billingMaximumLimits") || "Maximum Limits"}
|
||||
</div>
|
||||
|
||||
@@ -16,11 +16,8 @@ import LoginCardHeader from "@app/components/LoginCardHeader";
|
||||
import { priv } from "@app/lib/api";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { LoginFormIDP } from "@app/components/LoginForm";
|
||||
import { ListIdpsResponse, type GetIdpResponse } from "@server/routers/idp";
|
||||
import { ListIdpsResponse } from "@server/routers/idp";
|
||||
import type { Metadata } from "next";
|
||||
import { cookies } from "next/headers";
|
||||
import { LAST_USED_IDP_COOKIE_NAME } from "@app/lib/consts";
|
||||
import z from "zod";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Log In"
|
||||
@@ -32,9 +29,8 @@ export default async function Page(props: {
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const user = await verifySession({ skipCheckVerifyEmail: true });
|
||||
|
||||
const lastUsedIdpCookie = (await cookies()).get(LAST_USED_IDP_COOKIE_NAME);
|
||||
const getUser = cache(verifySession);
|
||||
const user = await getUser({ skipCheckVerifyEmail: true });
|
||||
|
||||
const isInvite = searchParams?.redirect?.includes("/invite");
|
||||
const forceLoginParam = searchParams?.forceLogin;
|
||||
@@ -89,47 +85,19 @@ export default async function Page(props: {
|
||||
(build === "enterprise" && env.app.identityProviderMode === "org");
|
||||
|
||||
let loginIdps: LoginFormIDP[] = [];
|
||||
let lastUsedIdpForSmartLogin: (LoginFormIDP & { orgId?: string }) | null =
|
||||
null;
|
||||
if (!useSmartLogin) {
|
||||
// Load IdPs for DashboardLoginForm (OSS or org-only IdP mode)
|
||||
if (build === "oss" || env.app.identityProviderMode !== "org") {
|
||||
const idpsRes =
|
||||
await priv.get<AxiosResponse<ListIdpsResponse>>("/idp");
|
||||
const idpsRes = await cache(
|
||||
async () =>
|
||||
await priv.get<AxiosResponse<ListIdpsResponse>>("/idp")
|
||||
)();
|
||||
loginIdps = idpsRes.data.data.idps.map((idp) => ({
|
||||
idpId: idp.idpId,
|
||||
name: idp.name,
|
||||
variant: idp.type
|
||||
})) as LoginFormIDP[];
|
||||
}
|
||||
} else {
|
||||
if (lastUsedIdpCookie) {
|
||||
const lastUsedIdpSchema = z.object({
|
||||
orgId: z.string().optional(),
|
||||
idpId: z.number()
|
||||
});
|
||||
try {
|
||||
const persistedData = lastUsedIdpSchema.parse(
|
||||
JSON.parse(lastUsedIdpCookie.value)
|
||||
);
|
||||
|
||||
const idpRes = await priv.get<AxiosResponse<GetIdpResponse>>(
|
||||
`/idp/${persistedData.idpId}`
|
||||
);
|
||||
|
||||
const idp = idpRes.data.data.idp;
|
||||
|
||||
lastUsedIdpForSmartLogin = {
|
||||
idpId: idp.idpId,
|
||||
name: idp.name,
|
||||
variant: idp.type,
|
||||
orgId: persistedData.orgId,
|
||||
lastUsed: true
|
||||
};
|
||||
} catch (error) {
|
||||
// the idp might not exist or the data is malformatted, skip this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const t = await getTranslations();
|
||||
@@ -192,7 +160,6 @@ export default async function Page(props: {
|
||||
redirect={redirectUrl}
|
||||
forceLogin={forceLogin}
|
||||
defaultUser={defaultUser}
|
||||
lastUsedIdp={lastUsedIdpForSmartLogin}
|
||||
orgSignIn={
|
||||
!isInvite &&
|
||||
(build === "saas" ||
|
||||
|
||||
@@ -5,6 +5,7 @@ import UserProvider from "@app/providers/UserProvider";
|
||||
import { ListUserOrgsResponse } from "@server/routers/org";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { redirect } from "next/navigation";
|
||||
import { cache } from "react";
|
||||
import OrganizationLanding from "@app/components/OrganizationLanding";
|
||||
import { pullEnv } from "@app/lib/pullEnv";
|
||||
import { cleanRedirect } from "@app/lib/cleanRedirect";
|
||||
@@ -12,6 +13,7 @@ import { Layout } from "@app/components/Layout";
|
||||
import RedirectToOrg from "@app/components/RedirectToOrg";
|
||||
import { InitialSetupCompleteResponse } from "@server/routers/auth";
|
||||
import { cookies } from "next/headers";
|
||||
import { build } from "@server/build";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -25,7 +27,8 @@ export default async function Page(props: {
|
||||
|
||||
const env = pullEnv();
|
||||
|
||||
const user = await verifySession({ skipCheckVerifyEmail: true });
|
||||
const getUser = cache(verifySession);
|
||||
const user = await getUser({ skipCheckVerifyEmail: true });
|
||||
|
||||
let complete = false;
|
||||
try {
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import { generateOidcUrlProxy } from "@app/actions/server";
|
||||
import IdpTypeIcon from "@app/components/IdpTypeIcon";
|
||||
import { Alert, AlertDescription } from "@app/components/ui/alert";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import { cleanRedirect } from "@app/lib/cleanRedirect";
|
||||
import { LAST_USED_IDP_COOKIE_NAME } from "@app/lib/consts";
|
||||
import { setClientCookie } from "@app/lib/setClientCookie";
|
||||
import { Alert, AlertDescription } from "@app/components/ui/alert";
|
||||
import { useTranslations } from "next-intl";
|
||||
import IdpTypeIcon from "@app/components/IdpTypeIcon";
|
||||
import {
|
||||
generateOidcUrlProxy,
|
||||
type GenerateOidcUrlResponse
|
||||
} from "@app/actions/server";
|
||||
import {
|
||||
redirect as redirectTo,
|
||||
useRouter,
|
||||
useParams,
|
||||
useSearchParams
|
||||
} from "next/navigation";
|
||||
import { useEffect, useState, useTransition } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { cleanRedirect } from "@app/lib/cleanRedirect";
|
||||
|
||||
export type LoginFormIDP = {
|
||||
idpId: number;
|
||||
name: string;
|
||||
variant?: string;
|
||||
lastUsed?: boolean;
|
||||
};
|
||||
|
||||
type IdpLoginButtonsProps = {
|
||||
@@ -34,6 +35,7 @@ export default function IdpLoginButtons({
|
||||
orgId
|
||||
}: IdpLoginButtonsProps) {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const t = useTranslations();
|
||||
|
||||
const params = useSearchParams();
|
||||
@@ -50,22 +52,10 @@ export default function IdpLoginButtons({
|
||||
}
|
||||
}, []);
|
||||
|
||||
const [loading, startTransition] = useTransition();
|
||||
|
||||
async function loginWithIdp(idpId: number) {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
setClientCookie(
|
||||
LAST_USED_IDP_COOKIE_NAME,
|
||||
JSON.stringify({
|
||||
orgId,
|
||||
idpId
|
||||
}),
|
||||
{
|
||||
sameSite: "Lax"
|
||||
}
|
||||
);
|
||||
|
||||
let redirectToUrl: string | undefined;
|
||||
try {
|
||||
console.log("generating", idpId, redirect || "/", orgId);
|
||||
@@ -78,6 +68,7 @@ export default function IdpLoginButtons({
|
||||
|
||||
if (response.error) {
|
||||
setError(response.message);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -93,6 +84,7 @@ export default function IdpLoginButtons({
|
||||
"An unexpected error occurred. Please try again."
|
||||
})
|
||||
);
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
if (redirectToUrl) {
|
||||
@@ -132,38 +124,20 @@ export default function IdpLoginButtons({
|
||||
idp.variant || idp.name.toLowerCase();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-full relative"
|
||||
<Button
|
||||
key={idp.idpId}
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full inline-flex items-center space-x-2"
|
||||
onClick={() => {
|
||||
loginWithIdp(idp.idpId);
|
||||
}}
|
||||
disabled={loading}
|
||||
loading={loading}
|
||||
>
|
||||
<Button
|
||||
key={idp.idpId}
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full inline-flex items-center space-x-2 after:absolute after:inset-0 after:z-10"
|
||||
onClick={() => {
|
||||
startTransition(() =>
|
||||
loginWithIdp(idp.idpId)
|
||||
);
|
||||
}}
|
||||
disabled={loading}
|
||||
loading={loading}
|
||||
>
|
||||
<IdpTypeIcon
|
||||
type={effectiveType}
|
||||
size={16}
|
||||
/>
|
||||
<span>{idp.name}</span>
|
||||
</Button>
|
||||
|
||||
{idp.lastUsed && (
|
||||
<div className="absolute inset-0">
|
||||
<span className="absolute top-0 right-0 text-xs bg-primary text-primary-foreground rounded-bl-sm rounded-tr-sm px-2 py-0.5">
|
||||
{t("idpLastUsed")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<IdpTypeIcon type={effectiveType} size={16} />
|
||||
<span>{idp.name}</span>
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
|
||||
@@ -30,7 +30,10 @@ import Link from "next/link";
|
||||
import { GenerateOidcUrlResponse } from "@server/routers/idp";
|
||||
import { Separator } from "./ui/separator";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { generateOidcUrlProxy, loginProxy } from "@app/actions/server";
|
||||
import {
|
||||
generateOidcUrlProxy,
|
||||
loginProxy
|
||||
} from "@app/actions/server";
|
||||
import { redirect as redirectTo } from "next/navigation";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import IdpTypeIcon from "@app/components/IdpTypeIcon";
|
||||
@@ -38,13 +41,11 @@ import IdpTypeIcon from "@app/components/IdpTypeIcon";
|
||||
import { loadReoScript } from "reodotdev";
|
||||
import { build } from "@server/build";
|
||||
import MfaInputForm from "@app/components/MfaInputForm";
|
||||
import { useLocalStorage } from "@app/hooks/useLocalStorage";
|
||||
|
||||
export type LoginFormIDP = {
|
||||
idpId: number;
|
||||
name: string;
|
||||
variant?: string;
|
||||
lastUsed?: boolean;
|
||||
};
|
||||
|
||||
type LoginFormProps = {
|
||||
@@ -104,6 +105,7 @@ export default function LoginForm({
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
const formSchema = z.object({
|
||||
email: z.string().email({ message: t("emailInvalid") }),
|
||||
password: z.string().min(8, { message: t("passwordRequirementsChars") })
|
||||
@@ -128,10 +130,6 @@ export default function LoginForm({
|
||||
}
|
||||
});
|
||||
|
||||
const [lastUsedIdpId, setLastUsedIdpId] = useLocalStorage<string | null>(
|
||||
"login:last-used-idp",
|
||||
null
|
||||
);
|
||||
|
||||
async function onSubmit(values: any) {
|
||||
const { email, password } = form.getValues();
|
||||
@@ -181,7 +179,8 @@ export default function LoginForm({
|
||||
if (data.useSecurityKey) {
|
||||
setError(
|
||||
t("securityKeyRequired", {
|
||||
defaultValue: "Please use your security key to sign in."
|
||||
defaultValue:
|
||||
"Please use your security key to sign in."
|
||||
})
|
||||
);
|
||||
return;
|
||||
@@ -243,8 +242,6 @@ export default function LoginForm({
|
||||
|
||||
async function loginWithIdp(idpId: number) {
|
||||
let redirectUrl: string | undefined;
|
||||
|
||||
setLastUsedIdpId(idpId.toString());
|
||||
try {
|
||||
const data = await generateOidcUrlProxy(
|
||||
idpId,
|
||||
@@ -359,6 +356,7 @@ export default function LoginForm({
|
||||
)}
|
||||
|
||||
<div className="space-y-4">
|
||||
|
||||
{!mfaRequested && (
|
||||
<>
|
||||
<SecurityKeyAuthButton
|
||||
@@ -387,41 +385,25 @@ export default function LoginForm({
|
||||
idp.variant || idp.name.toLowerCase();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-full relative"
|
||||
<Button
|
||||
key={idp.idpId}
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full inline-flex items-center space-x-2"
|
||||
onClick={() => {
|
||||
loginWithIdp(idp.idpId);
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
key={idp.idpId}
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full inline-flex items-center space-x-2 after:absolute after:inset-0 after:z-10"
|
||||
onClick={() => {
|
||||
loginWithIdp(idp.idpId);
|
||||
}}
|
||||
>
|
||||
<IdpTypeIcon
|
||||
type={effectiveType}
|
||||
size={16}
|
||||
/>
|
||||
<span>{idp.name}</span>
|
||||
</Button>
|
||||
|
||||
{lastUsedIdpId ===
|
||||
idp.idpId.toString() && (
|
||||
<div className="absolute inset-0">
|
||||
<span className="absolute top-0 right-0 text-xs bg-primary text-primary-foreground rounded-bl-sm rounded-tr-sm px-2 py-0.5">
|
||||
{t("idpLastUsed")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<IdpTypeIcon type={effectiveType} size={16} />
|
||||
<span>{idp.name}</span>
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -27,8 +27,6 @@ import UserProfileCard from "@app/components/UserProfileCard";
|
||||
import SecurityKeyAuthButton from "@app/components/SecurityKeyAuthButton";
|
||||
import { Separator } from "@app/components/ui/separator";
|
||||
import OrgSignInLink from "@app/components/OrgSignInLink";
|
||||
import type { LoginFormIDP } from "./LoginForm";
|
||||
import IdpLoginButtons from "./IdpLoginButtons";
|
||||
|
||||
const identifierSchema = z.object({
|
||||
identifier: z.string().min(1, "Username or email is required")
|
||||
@@ -55,7 +53,6 @@ type SmartLoginFormProps = {
|
||||
forceLogin?: boolean;
|
||||
defaultUser?: string;
|
||||
orgSignIn?: OrgSignInConfig;
|
||||
lastUsedIdp?: (LoginFormIDP & { orgId?: string }) | null;
|
||||
};
|
||||
|
||||
type ViewState =
|
||||
@@ -92,8 +89,7 @@ export default function SmartLoginForm({
|
||||
redirect,
|
||||
forceLogin,
|
||||
defaultUser,
|
||||
orgSignIn,
|
||||
lastUsedIdp
|
||||
orgSignIn
|
||||
}: SmartLoginFormProps) {
|
||||
const router = useRouter();
|
||||
const { env } = useEnvContext();
|
||||
@@ -298,15 +294,6 @@ export default function SmartLoginForm({
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{lastUsedIdp && (
|
||||
<IdpLoginButtons
|
||||
idps={[lastUsedIdp]}
|
||||
orgId={lastUsedIdp.orgId}
|
||||
redirect={redirect}
|
||||
/>
|
||||
)}
|
||||
|
||||
<OrgSignInLink
|
||||
href={orgSignIn.href}
|
||||
linkText={orgSignIn.linkText}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const LAST_USED_IDP_COOKIE_NAME = "p__last_used_idp";
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Set a cookie on the client side in javascript code, not on the server
|
||||
* @param name
|
||||
* @param value
|
||||
* @param days
|
||||
* @param options
|
||||
*/
|
||||
export function setClientCookie(
|
||||
name: string,
|
||||
value: string,
|
||||
options: {
|
||||
days?: number;
|
||||
path?: string;
|
||||
secure?: boolean;
|
||||
sameSite?: "Strict" | "Lax" | "None";
|
||||
} = {}
|
||||
): void {
|
||||
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
||||
|
||||
if (options.days) {
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + options.days * 864e5);
|
||||
cookie += `; expires=${date.toUTCString()}`;
|
||||
}
|
||||
|
||||
cookie += `; path=${options.path ?? "/"}`;
|
||||
|
||||
if (options.secure) cookie += "; Secure";
|
||||
if (options.sameSite) cookie += `; SameSite=${options.sameSite}`;
|
||||
|
||||
document.cookie = cookie;
|
||||
}
|
||||
Reference in New Issue
Block a user