mirror of
https://github.com/lukevella/rallly.git
synced 2025-06-02 02:31:53 +02:00
⬆️ Bump vitest from 2.1.1 to 2.1.9 (#1548)
Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Luke Vella <me@lukevella.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
parent
480f1a03d1
commit
ffafcbdb43
46 changed files with 729 additions and 738 deletions
|
@ -4,6 +4,6 @@ NEXTAUTH_URL=$NEXT_PUBLIC_BASE_URL
|
||||||
SECRET_PASSWORD=abcdef1234567890abcdef1234567890
|
SECRET_PASSWORD=abcdef1234567890abcdef1234567890
|
||||||
DATABASE_URL=postgres://postgres:postgres@localhost:5450/rallly
|
DATABASE_URL=postgres://postgres:postgres@localhost:5450/rallly
|
||||||
SUPPORT_EMAIL=support@rallly.co
|
SUPPORT_EMAIL=support@rallly.co
|
||||||
SMTP_HOST=localhost
|
SMTP_HOST=0.0.0.0
|
||||||
SMTP_PORT=1025
|
SMTP_PORT=1025
|
||||||
QUICK_CREATE_ENABLED=true
|
QUICK_CREATE_ENABLED=true
|
1
apps/web/declarations/next-auth.d.ts
vendored
1
apps/web/declarations/next-auth.d.ts
vendored
|
@ -20,6 +20,7 @@ declare module "next-auth" {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface User extends DefaultUser {
|
interface User extends DefaultUser {
|
||||||
|
id: string;
|
||||||
locale?: string | null;
|
locale?: string | null;
|
||||||
timeZone?: string | null;
|
timeZone?: string | null;
|
||||||
timeFormat?: TimeFormat | null;
|
timeFormat?: TimeFormat | null;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const typescriptTransform = require("i18next-scanner-typescript");
|
const typescriptTransform = require("i18next-scanner-typescript");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
input: ["src/**/*.{ts,tsx}", "!src/auth.ts"],
|
input: ["src/**/*.{ts,tsx}", "!src/next-auth*.ts"],
|
||||||
options: {
|
options: {
|
||||||
nsSeparator: false,
|
nsSeparator: false,
|
||||||
defaultNs: "app",
|
defaultNs: "app",
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
"docker:start": "./scripts/docker-start.sh"
|
"docker:start": "./scripts/docker-start.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/prisma-adapter": "^1.0.3",
|
"@auth/prisma-adapter": "^2.7.4",
|
||||||
"@aws-sdk/client-s3": "^3.645.0",
|
"@aws-sdk/client-s3": "^3.645.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.645.0",
|
"@aws-sdk/s3-request-presigner": "^3.645.0",
|
||||||
"@hookform/resolvers": "^3.3.1",
|
"@hookform/resolvers": "^3.3.1",
|
||||||
|
@ -67,7 +67,7 @@
|
||||||
"lucide-react": "^0.387.0",
|
"lucide-react": "^0.387.0",
|
||||||
"micro": "^10.0.1",
|
"micro": "^10.0.1",
|
||||||
"nanoid": "^5.0.9",
|
"nanoid": "^5.0.9",
|
||||||
"next-auth": "^4.24.5",
|
"next-auth": "^5.0.0-beta.25",
|
||||||
"next-i18next": "^13.0.3",
|
"next-i18next": "^13.0.3",
|
||||||
"php-serialize": "^4.1.1",
|
"php-serialize": "^4.1.1",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
|
@ -95,7 +95,7 @@
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"i18next-scanner": "^4.2.0",
|
"i18next-scanner": "^4.2.0",
|
||||||
"i18next-scanner-typescript": "^1.1.1",
|
"i18next-scanner-typescript": "^1.1.1",
|
||||||
"vitest": "^2.1.1",
|
"vitest": "^2.1.9",
|
||||||
"wait-on": "^6.0.1"
|
"wait-on": "^6.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@
|
||||||
"emailNotAllowed": "This email is not allowed.",
|
"emailNotAllowed": "This email is not allowed.",
|
||||||
"emailPlaceholder": "jessie.smith@example.com",
|
"emailPlaceholder": "jessie.smith@example.com",
|
||||||
"exportToCsv": "Export to CSV",
|
"exportToCsv": "Export to CSV",
|
||||||
"forgetMe": "Forget me",
|
|
||||||
"guest": "Guest",
|
"guest": "Guest",
|
||||||
"ifNeedBe": "If need be",
|
"ifNeedBe": "If need be",
|
||||||
"location": "Location",
|
"location": "Location",
|
||||||
|
@ -199,9 +198,6 @@
|
||||||
"pollStatusFinalized": "Finalized",
|
"pollStatusFinalized": "Finalized",
|
||||||
"share": "Share",
|
"share": "Share",
|
||||||
"noParticipants": "No participants",
|
"noParticipants": "No participants",
|
||||||
"userId": "User ID",
|
|
||||||
"aboutGuest": "Guest User",
|
|
||||||
"aboutGuestDescription": "Profile settings are not available for guest users. <0>Sign in</0> to your existing account or <1>create a new account</1> to customize your profile.",
|
|
||||||
"logoutDescription": "Sign out of your existing session",
|
"logoutDescription": "Sign out of your existing session",
|
||||||
"events": "Events",
|
"events": "Events",
|
||||||
"inviteParticipantsDescription": "Copy and share the invite link to start gathering responses from your participants.",
|
"inviteParticipantsDescription": "Copy and share the invite link to start gathering responses from your participants.",
|
||||||
|
@ -305,5 +301,6 @@
|
||||||
"registerVerifyDescription": "Check your email for the verification code",
|
"registerVerifyDescription": "Check your email for the verification code",
|
||||||
"loginVerifyTitle": "Finish Logging In",
|
"loginVerifyTitle": "Finish Logging In",
|
||||||
"loginVerifyDescription": "Check your email for the verification code",
|
"loginVerifyDescription": "Check your email for the verification code",
|
||||||
"createAccount": "Create Account"
|
"createAccount": "Create Account",
|
||||||
|
"loginMagicLinkError": "This link is invalid or expired. Please request a new link."
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ export function DeleteAccountDialog({
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
posthog?.capture("delete account");
|
posthog?.capture("delete account");
|
||||||
signOut({
|
signOut({
|
||||||
callbackUrl: "/login",
|
redirectTo: "/login",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,10 +4,12 @@ import { Button } from "@rallly/ui/button";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
import { OptimizedAvatarImage } from "@/components/optimized-avatar-image";
|
import { OptimizedAvatarImage } from "@/components/optimized-avatar-image";
|
||||||
import { Skeleton } from "@/components/skeleton";
|
import { Skeleton } from "@/components/skeleton";
|
||||||
import { Trans } from "@/components/trans";
|
import { Trans } from "@/components/trans";
|
||||||
|
import { useTranslation } from "@/i18n/client";
|
||||||
import { trpc } from "@/trpc/client";
|
import { trpc } from "@/trpc/client";
|
||||||
|
|
||||||
type PageProps = { magicLink: string; email: string };
|
type PageProps = { magicLink: string; email: string };
|
||||||
|
@ -15,6 +17,9 @@ type PageProps = { magicLink: string; email: string };
|
||||||
export const LoginPage = ({ magicLink, email }: PageProps) => {
|
export const LoginPage = ({ magicLink, email }: PageProps) => {
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const posthog = usePostHog();
|
const posthog = usePostHog();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [error, setError] = React.useState<string | null>(null);
|
||||||
|
|
||||||
const magicLinkFetch = useMutation({
|
const magicLinkFetch = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
const res = await fetch(magicLink);
|
const res = await fetch(magicLink);
|
||||||
|
@ -31,9 +36,15 @@ export const LoginPage = ({ magicLink, email }: PageProps) => {
|
||||||
name: updatedSession.user.name,
|
name: updatedSession.user.name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
router.push(data.url);
|
||||||
|
} else {
|
||||||
|
setError(
|
||||||
|
t("loginMagicLinkError", {
|
||||||
|
defaultValue:
|
||||||
|
"This link is invalid or expired. Please request a new link.",
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
router.push(data.url);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const { data } = trpc.user.getByEmail.useQuery({ email });
|
const { data } = trpc.user.getByEmail.useQuery({ email });
|
||||||
|
@ -72,6 +83,7 @@ export const LoginPage = ({ magicLink, email }: PageProps) => {
|
||||||
<Trans i18nKey="login" defaults="Login" />
|
<Trans i18nKey="login" defaults="Login" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
{error && <p className="text-destructive text-sm">{error}</p>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,18 +4,24 @@ import { prisma } from "@rallly/database";
|
||||||
import { cookies } from "next/headers";
|
import { cookies } from "next/headers";
|
||||||
|
|
||||||
export async function setVerificationEmail(email: string) {
|
export async function setVerificationEmail(email: string) {
|
||||||
const count = await prisma.user.count({
|
const user = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
email,
|
email,
|
||||||
},
|
},
|
||||||
|
select: {
|
||||||
|
email: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
cookies().set("verification-email", email, {
|
if (user) {
|
||||||
httpOnly: true,
|
cookies().set("verification-email", user.email, {
|
||||||
secure: process.env.NODE_ENV === "production",
|
httpOnly: true,
|
||||||
sameSite: "lax",
|
secure: process.env.NODE_ENV === "production",
|
||||||
maxAge: 15 * 60,
|
sameSite: "lax",
|
||||||
});
|
maxAge: 15 * 60,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return count > 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,13 +53,13 @@ export function LoginWithEmailForm() {
|
||||||
if (doesExist) {
|
if (doesExist) {
|
||||||
await signIn("email", {
|
await signIn("email", {
|
||||||
email: identifier,
|
email: identifier,
|
||||||
callbackUrl: searchParams?.get("callbackUrl") ?? undefined,
|
redirectTo: searchParams?.get("redirectTo") ?? undefined,
|
||||||
redirect: false,
|
redirect: false,
|
||||||
});
|
});
|
||||||
// redirect to verify page with callbackUrl
|
// redirect to verify page with redirectTo
|
||||||
router.push(
|
router.push(
|
||||||
`/login/verify?callbackUrl=${encodeURIComponent(
|
`/login/verify?redirectTo=${encodeURIComponent(
|
||||||
searchParams?.get("callbackUrl") ?? "",
|
searchParams?.get("redirectTo") ?? "",
|
||||||
)}`,
|
)}`,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -6,16 +6,16 @@ import { Trans } from "@/components/trans";
|
||||||
|
|
||||||
export async function LoginWithOIDC({
|
export async function LoginWithOIDC({
|
||||||
name,
|
name,
|
||||||
callbackUrl,
|
redirectTo,
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
callbackUrl?: string;
|
redirectTo?: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
signIn("oidc", {
|
signIn("oidc", {
|
||||||
callbackUrl,
|
redirectTo: redirectTo,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
variant="link"
|
variant="link"
|
||||||
|
|
|
@ -15,7 +15,7 @@ function SSOImage({ provider }: { provider: string }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provider === "azure-ad") {
|
if (provider === "microsoft-entra-id") {
|
||||||
return (
|
return (
|
||||||
<Image
|
<Image
|
||||||
src="/static/microsoft.svg"
|
src="/static/microsoft.svg"
|
||||||
|
@ -40,11 +40,11 @@ function SSOImage({ provider }: { provider: string }) {
|
||||||
export function SSOProvider({
|
export function SSOProvider({
|
||||||
providerId,
|
providerId,
|
||||||
name,
|
name,
|
||||||
callbackUrl,
|
redirectTo,
|
||||||
}: {
|
}: {
|
||||||
providerId: string;
|
providerId: string;
|
||||||
name: string;
|
name: string;
|
||||||
callbackUrl?: string;
|
redirectTo?: string;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
|
@ -58,7 +58,7 @@ export function SSOProvider({
|
||||||
key={providerId}
|
key={providerId}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
signIn(providerId, {
|
signIn(providerId, {
|
||||||
callbackUrl,
|
redirectTo: redirectTo,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Trans } from "react-i18next/TransWithoutContext";
|
import { Trans } from "react-i18next/TransWithoutContext";
|
||||||
|
|
||||||
import { getOAuthProviders } from "@/auth";
|
import { GoogleProvider } from "@/auth/providers/google";
|
||||||
|
import { MicrosoftProvider } from "@/auth/providers/microsoft";
|
||||||
|
import { OIDCProvider } from "@/auth/providers/oidc";
|
||||||
import { getTranslation } from "@/i18n/server";
|
import { getTranslation } from "@/i18n/server";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -22,20 +24,14 @@ export default async function LoginPage({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
searchParams?: {
|
searchParams?: {
|
||||||
callbackUrl?: string;
|
redirectTo?: string;
|
||||||
};
|
};
|
||||||
}) {
|
}) {
|
||||||
const { t } = await getTranslation();
|
const { t } = await getTranslation();
|
||||||
const oAuthProviders = getOAuthProviders();
|
|
||||||
|
|
||||||
const hasAlternateLoginMethods = oAuthProviders.length > 0;
|
const oidcProvider = OIDCProvider();
|
||||||
|
const socialProviders = [GoogleProvider(), MicrosoftProvider()];
|
||||||
const oidcProvider = oAuthProviders.find(
|
const hasAlternateLoginMethods = socialProviders.length > 0 || !!oidcProvider;
|
||||||
(provider) => provider.id === "oidc",
|
|
||||||
);
|
|
||||||
const socialProviders = oAuthProviders.filter(
|
|
||||||
(provider) => provider.id !== "oidc",
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthPageContainer>
|
<AuthPageContainer>
|
||||||
|
@ -58,19 +54,21 @@ export default async function LoginPage({
|
||||||
{oidcProvider ? (
|
{oidcProvider ? (
|
||||||
<LoginWithOIDC
|
<LoginWithOIDC
|
||||||
name={oidcProvider.name}
|
name={oidcProvider.name}
|
||||||
callbackUrl={searchParams?.callbackUrl}
|
redirectTo={searchParams?.redirectTo}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{socialProviders ? (
|
{socialProviders ? (
|
||||||
<div className="grid gap-4">
|
<div className="grid gap-4">
|
||||||
{socialProviders.map((provider) => (
|
{socialProviders.map((provider) =>
|
||||||
<SSOProvider
|
provider ? (
|
||||||
key={provider.id}
|
<SSOProvider
|
||||||
providerId={provider.id}
|
key={provider.id}
|
||||||
name={provider.name}
|
providerId={provider.id}
|
||||||
callbackUrl={searchParams?.callbackUrl}
|
name={provider.options?.name || provider.name}
|
||||||
/>
|
redirectTo={searchParams?.redirectTo}
|
||||||
))}
|
/>
|
||||||
|
) : null,
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</AuthPageContent>
|
</AuthPageContent>
|
||||||
|
|
|
@ -50,7 +50,7 @@ export function OTPForm({ email }: { email: string }) {
|
||||||
message: t("wrongVerificationCode"),
|
message: t("wrongVerificationCode"),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
window.location.href = searchParams?.get("callbackUrl") ?? "/";
|
window.location.href = searchParams?.get("redirectTo") ?? "/";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ export function OTPForm({ token }: { token: string }) {
|
||||||
|
|
||||||
signIn("registration-token", {
|
signIn("registration-token", {
|
||||||
token,
|
token,
|
||||||
callbackUrl: searchParams?.get("callbackUrl") ?? "/",
|
redirectTo: searchParams?.get("redirectTo") ?? "/",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,8 @@ import React from "react";
|
||||||
|
|
||||||
import { TimeZoneChangeDetector } from "@/app/[locale]/timezone-change-detector";
|
import { TimeZoneChangeDetector } from "@/app/[locale]/timezone-change-detector";
|
||||||
import { Providers } from "@/app/providers";
|
import { Providers } from "@/app/providers";
|
||||||
import { getServerSession } from "@/auth";
|
|
||||||
import { SessionProvider } from "@/auth/session-provider";
|
import { SessionProvider } from "@/auth/session-provider";
|
||||||
|
import { auth } from "@/next-auth";
|
||||||
|
|
||||||
const inter = Inter({
|
const inter = Inter({
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
|
@ -30,7 +30,7 @@ export default async function Root({
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
params: { locale: string };
|
params: { locale: string };
|
||||||
}) {
|
}) {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang={locale} className={inter.className}>
|
<html lang={locale} className={inter.className}>
|
||||||
|
|
6
apps/web/src/app/api/auth/[...nextauth]/route.ts
Normal file
6
apps/web/src/app/api/auth/[...nextauth]/route.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { withPosthog } from "@rallly/posthog/server";
|
||||||
|
|
||||||
|
import { handlers } from "@/next-auth";
|
||||||
|
|
||||||
|
export const GET = withPosthog(handlers.GET);
|
||||||
|
export const POST = withPosthog(handlers.POST);
|
|
@ -3,7 +3,7 @@ import { cookies } from "next/headers";
|
||||||
import type { NextRequest } from "next/server";
|
import type { NextRequest } from "next/server";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
import { getServerSession } from "@/auth";
|
import { auth } from "@/next-auth";
|
||||||
import type { DisableNotificationsPayload } from "@/trpc/types";
|
import type { DisableNotificationsPayload } from "@/trpc/types";
|
||||||
import { decryptToken } from "@/utils/session";
|
import { decryptToken } from "@/utils/session";
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ export const GET = async (req: NextRequest) => {
|
||||||
return NextResponse.redirect(new URL("/login", req.url));
|
return NextResponse.redirect(new URL("/login", req.url));
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
|
|
||||||
if (!session || !session.user?.email) {
|
if (!session || !session.user?.email) {
|
||||||
return NextResponse.redirect(new URL("/login", req.url));
|
return NextResponse.redirect(new URL("/login", req.url));
|
||||||
|
|
|
@ -5,7 +5,7 @@ import type { NextRequest } from "next/server";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { getServerSession } from "@/auth";
|
import { auth } from "@/next-auth";
|
||||||
|
|
||||||
const inputSchema = z.object({
|
const inputSchema = z.object({
|
||||||
period: z.enum(["monthly", "yearly"]).optional(),
|
period: z.enum(["monthly", "yearly"]).optional(),
|
||||||
|
@ -14,7 +14,7 @@ const inputSchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
const userSession = await getServerSession();
|
const userSession = await auth();
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
const { period = "monthly", return_path } = inputSchema.parse(
|
const { period = "monthly", return_path } = inputSchema.parse(
|
||||||
Object.fromEntries(formData.entries()),
|
Object.fromEntries(formData.entries()),
|
||||||
|
|
|
@ -5,7 +5,7 @@ import * as Sentry from "@sentry/nextjs";
|
||||||
import type { NextRequest } from "next/server";
|
import type { NextRequest } from "next/server";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
import { getServerSession } from "@/auth";
|
import { auth } from "@/next-auth";
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
const sessionId = request.nextUrl.searchParams.get("session_id");
|
const sessionId = request.nextUrl.searchParams.get("session_id");
|
||||||
|
@ -32,7 +32,7 @@ export async function GET(request: NextRequest) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const userSession = await getServerSession();
|
const userSession = await auth();
|
||||||
if (!userSession?.user || userSession.user.email === null) {
|
if (!userSession?.user || userSession.user.email === null) {
|
||||||
Sentry.captureException(new Error("User not logged in"));
|
Sentry.captureException(new Error("User not logged in"));
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { ipAddress } from "@vercel/functions";
|
||||||
import type { NextRequest } from "next/server";
|
import type { NextRequest } from "next/server";
|
||||||
|
|
||||||
import { getLocaleFromHeader } from "@/app/guest";
|
import { getLocaleFromHeader } from "@/app/guest";
|
||||||
import { getServerSession } from "@/auth";
|
import { auth } from "@/next-auth";
|
||||||
import type { TRPCContext } from "@/trpc/context";
|
import type { TRPCContext } from "@/trpc/context";
|
||||||
import { appRouter } from "@/trpc/routers";
|
import { appRouter } from "@/trpc/routers";
|
||||||
import { getEmailClient } from "@/utils/emails";
|
import { getEmailClient } from "@/utils/emails";
|
||||||
|
@ -15,7 +15,7 @@ const handler = (req: NextRequest) => {
|
||||||
req,
|
req,
|
||||||
router: appRouter,
|
router: appRouter,
|
||||||
createContext: async () => {
|
createContext: async () => {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
const locale = await getLocaleFromHeader(req);
|
const locale = await getLocaleFromHeader(req);
|
||||||
const user = session?.user
|
const user = session?.user
|
||||||
? {
|
? {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { cookies } from "next/headers";
|
||||||
import type { NextRequest } from "next/server";
|
import type { NextRequest } from "next/server";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
import { getServerSession } from "@/auth";
|
import { auth } from "@/next-auth";
|
||||||
import { decryptToken } from "@/utils/session";
|
import { decryptToken } from "@/utils/session";
|
||||||
|
|
||||||
type EmailChangePayload = {
|
type EmailChangePayload = {
|
||||||
|
@ -50,11 +50,11 @@ export const GET = async (request: NextRequest) => {
|
||||||
return NextResponse.json({ error: "No token provided" }, { status: 400 });
|
return NextResponse.json({ error: "No token provided" }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
|
|
||||||
if (!session?.user || !session.user.email) {
|
if (!session?.user || !session.user.email) {
|
||||||
return NextResponse.redirect(
|
return NextResponse.redirect(
|
||||||
new URL(`/login?callbackUrl=${request.url}`, request.url),
|
new URL(`/login?redirectTo=${request.url}`, request.url),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,371 +0,0 @@
|
||||||
import { prisma } from "@rallly/database";
|
|
||||||
import { posthog } from "@rallly/posthog/server";
|
|
||||||
import { absoluteUrl } from "@rallly/utils/absolute-url";
|
|
||||||
import { generateOtp, randomid } from "@rallly/utils/nanoid";
|
|
||||||
import type {
|
|
||||||
GetServerSidePropsContext,
|
|
||||||
NextApiRequest,
|
|
||||||
NextApiResponse,
|
|
||||||
} from "next";
|
|
||||||
import type { NextAuthOptions, User } from "next-auth";
|
|
||||||
import NextAuth, {
|
|
||||||
getServerSession as getServerSessionWithOptions,
|
|
||||||
} from "next-auth/next";
|
|
||||||
import AzureADProvider from "next-auth/providers/azure-ad";
|
|
||||||
import CredentialsProvider from "next-auth/providers/credentials";
|
|
||||||
import EmailProvider from "next-auth/providers/email";
|
|
||||||
import GoogleProvider from "next-auth/providers/google";
|
|
||||||
import type { Provider } from "next-auth/providers/index";
|
|
||||||
|
|
||||||
import { env } from "@/env";
|
|
||||||
import type { RegistrationTokenPayload } from "@/trpc/types";
|
|
||||||
import { getEmailClient } from "@/utils/emails";
|
|
||||||
import { getValueByPath } from "@/utils/get-value-by-path";
|
|
||||||
import { decryptToken } from "@/utils/session";
|
|
||||||
|
|
||||||
import { CustomPrismaAdapter } from "./auth/custom-prisma-adapter";
|
|
||||||
import { mergeGuestsIntoUser } from "./auth/merge-user";
|
|
||||||
|
|
||||||
const providers: Provider[] = [
|
|
||||||
// When a user registers, we don't want to go through the email verification process
|
|
||||||
// so this provider allows us exchange the registration token for a session token
|
|
||||||
CredentialsProvider({
|
|
||||||
id: "registration-token",
|
|
||||||
name: "Registration Token",
|
|
||||||
credentials: {
|
|
||||||
token: {
|
|
||||||
label: "Token",
|
|
||||||
type: "text",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
async authorize(credentials) {
|
|
||||||
if (credentials?.token) {
|
|
||||||
const payload = await decryptToken<RegistrationTokenPayload>(
|
|
||||||
credentials.token,
|
|
||||||
);
|
|
||||||
if (payload) {
|
|
||||||
const user = await prisma.user.findUnique({
|
|
||||||
where: {
|
|
||||||
email: payload.email,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
email: true,
|
|
||||||
name: true,
|
|
||||||
locale: true,
|
|
||||||
timeFormat: true,
|
|
||||||
timeZone: true,
|
|
||||||
image: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (user) {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
CredentialsProvider({
|
|
||||||
id: "guest",
|
|
||||||
name: "Guest",
|
|
||||||
credentials: {},
|
|
||||||
async authorize() {
|
|
||||||
return {
|
|
||||||
id: `user-${randomid()}`,
|
|
||||||
email: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
EmailProvider({
|
|
||||||
server: "",
|
|
||||||
from: process.env.NOREPLY_EMAIL,
|
|
||||||
generateVerificationToken() {
|
|
||||||
return generateOtp();
|
|
||||||
},
|
|
||||||
async sendVerificationRequest({ identifier: email, token, url }) {
|
|
||||||
const user = await prisma.user.findUnique({
|
|
||||||
where: {
|
|
||||||
email,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
name: true,
|
|
||||||
locale: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (user) {
|
|
||||||
await getEmailClient(user.locale ?? undefined).sendTemplate(
|
|
||||||
"LoginEmail",
|
|
||||||
{
|
|
||||||
to: email,
|
|
||||||
props: {
|
|
||||||
magicLink: absoluteUrl("/auth/login", {
|
|
||||||
magicLink: url,
|
|
||||||
}),
|
|
||||||
code: token,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
// If we have an OAuth provider configured, we add it to the list of providers
|
|
||||||
if (
|
|
||||||
process.env.OIDC_DISCOVERY_URL &&
|
|
||||||
process.env.OIDC_CLIENT_ID &&
|
|
||||||
process.env.OIDC_CLIENT_SECRET
|
|
||||||
) {
|
|
||||||
providers.push({
|
|
||||||
id: "oidc",
|
|
||||||
name: process.env.OIDC_NAME ?? "OpenID Connect",
|
|
||||||
type: "oauth",
|
|
||||||
wellKnown: process.env.OIDC_DISCOVERY_URL,
|
|
||||||
authorization: { params: { scope: "openid email profile" } },
|
|
||||||
clientId: process.env.OIDC_CLIENT_ID,
|
|
||||||
clientSecret: process.env.OIDC_CLIENT_SECRET,
|
|
||||||
idToken: true,
|
|
||||||
checks: ["state"],
|
|
||||||
allowDangerousEmailAccountLinking: true,
|
|
||||||
profile(profile) {
|
|
||||||
return {
|
|
||||||
id: profile.sub,
|
|
||||||
name: getValueByPath(profile, env.OIDC_NAME_CLAIM_PATH),
|
|
||||||
email: getValueByPath(profile, env.OIDC_EMAIL_CLAIM_PATH),
|
|
||||||
image: getValueByPath(profile, env.OIDC_PICTURE_CLAIM_PATH),
|
|
||||||
} as User;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) {
|
|
||||||
providers.push(
|
|
||||||
GoogleProvider({
|
|
||||||
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
||||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
||||||
allowDangerousEmailAccountLinking: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
process.env.MICROSOFT_TENANT_ID &&
|
|
||||||
process.env.MICROSOFT_CLIENT_ID &&
|
|
||||||
process.env.MICROSOFT_CLIENT_SECRET
|
|
||||||
) {
|
|
||||||
providers.push(
|
|
||||||
AzureADProvider({
|
|
||||||
name: "Microsoft",
|
|
||||||
tenantId: process.env.MICROSOFT_TENANT_ID,
|
|
||||||
clientId: process.env.MICROSOFT_CLIENT_ID,
|
|
||||||
clientSecret: process.env.MICROSOFT_CLIENT_SECRET,
|
|
||||||
wellKnown:
|
|
||||||
"https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const getAuthOptions = (...args: GetServerSessionParams) =>
|
|
||||||
({
|
|
||||||
adapter: CustomPrismaAdapter(prisma, {
|
|
||||||
migrateData: async (userId) => {
|
|
||||||
const session = await getServerSession(...args);
|
|
||||||
if (session?.user && session.user.email === null) {
|
|
||||||
await mergeGuestsIntoUser(userId, [session.user.id]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
secret: process.env.SECRET_PASSWORD,
|
|
||||||
session: {
|
|
||||||
strategy: "jwt",
|
|
||||||
},
|
|
||||||
providers: providers,
|
|
||||||
pages: {
|
|
||||||
signIn: "/login",
|
|
||||||
verifyRequest: "/login/verify",
|
|
||||||
error: "/auth/error",
|
|
||||||
},
|
|
||||||
events: {
|
|
||||||
signIn({ user, account }) {
|
|
||||||
posthog?.capture({
|
|
||||||
distinctId: user.id,
|
|
||||||
event: "login",
|
|
||||||
properties: {
|
|
||||||
method: account?.provider,
|
|
||||||
$set: {
|
|
||||||
name: user.name,
|
|
||||||
email: user.email,
|
|
||||||
timeZone: user.timeZone,
|
|
||||||
locale: user.locale,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
callbacks: {
|
|
||||||
async signIn({ user, email, profile }) {
|
|
||||||
const distinctId = user.id;
|
|
||||||
// prevent sign in if email is not verified
|
|
||||||
if (
|
|
||||||
profile &&
|
|
||||||
"email_verified" in profile &&
|
|
||||||
profile.email_verified === false
|
|
||||||
) {
|
|
||||||
posthog?.capture({
|
|
||||||
distinctId,
|
|
||||||
event: "login failed",
|
|
||||||
properties: {
|
|
||||||
reason: "email not verified",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Make sure email is allowed
|
|
||||||
if (user.email) {
|
|
||||||
const isBlocked = isEmailBlocked(user.email);
|
|
||||||
if (isBlocked) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For now, we don't allow users to login unless they have
|
|
||||||
// registered an account. This is just because we need a name
|
|
||||||
// to display on the dashboard. The flow can be modified so that
|
|
||||||
// the name is requested after the user has logged in.
|
|
||||||
if (email?.verificationRequest) {
|
|
||||||
const isRegisteredUser =
|
|
||||||
(await prisma.user.count({
|
|
||||||
where: {
|
|
||||||
email: user.email as string,
|
|
||||||
},
|
|
||||||
})) > 0;
|
|
||||||
|
|
||||||
return isRegisteredUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
// when we login with a social account for the first time, the user is not created yet
|
|
||||||
// and the user id will be the same as the provider account id
|
|
||||||
// we handle this case the the prisma adapter when we link accounts
|
|
||||||
const isInitialSocialLogin = user.id === profile?.sub;
|
|
||||||
|
|
||||||
if (!isInitialSocialLogin) {
|
|
||||||
// merge guest user into newly logged in user
|
|
||||||
const session = await getServerSession(...args);
|
|
||||||
if (session?.user && !session.user.email) {
|
|
||||||
await mergeGuestsIntoUser(user.id, [session.user.id]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
async jwt({ token, session }) {
|
|
||||||
if (session) {
|
|
||||||
token.locale = session.locale;
|
|
||||||
token.timeFormat = session.timeFormat;
|
|
||||||
token.timeZone = session.timeZone;
|
|
||||||
token.weekStart = session.weekStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
return token;
|
|
||||||
},
|
|
||||||
async session({ session, token }) {
|
|
||||||
if (!token.sub) {
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token.sub?.startsWith("user-")) {
|
|
||||||
session.user = {
|
|
||||||
id: token.sub as string,
|
|
||||||
locale: token.locale,
|
|
||||||
timeFormat: token.timeFormat,
|
|
||||||
timeZone: token.timeZone,
|
|
||||||
weekStart: token.weekStart,
|
|
||||||
};
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
|
||||||
where: {
|
|
||||||
id: token.sub as string,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
timeFormat: true,
|
|
||||||
timeZone: true,
|
|
||||||
locale: true,
|
|
||||||
weekStart: true,
|
|
||||||
email: true,
|
|
||||||
image: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (user) {
|
|
||||||
session.user = {
|
|
||||||
id: user.id,
|
|
||||||
name: user.name,
|
|
||||||
email: user.email,
|
|
||||||
image: user.image,
|
|
||||||
locale: user.locale,
|
|
||||||
timeFormat: user.timeFormat,
|
|
||||||
timeZone: user.timeZone,
|
|
||||||
weekStart: user.weekStart,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return session;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}) satisfies NextAuthOptions;
|
|
||||||
|
|
||||||
type GetServerSessionParams =
|
|
||||||
| [GetServerSidePropsContext["req"], GetServerSidePropsContext["res"]]
|
|
||||||
| [NextApiRequest, NextApiResponse]
|
|
||||||
| [];
|
|
||||||
|
|
||||||
export async function getServerSession(...args: GetServerSessionParams) {
|
|
||||||
return await getServerSessionWithOptions(...args, getAuthOptions(...args));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function AuthApiRoute(req: NextApiRequest, res: NextApiResponse) {
|
|
||||||
const authOptions = getAuthOptions(req, res);
|
|
||||||
return NextAuth(req, res, authOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isEmailBlocked = (email: string) => {
|
|
||||||
if (process.env.ALLOWED_EMAILS) {
|
|
||||||
const allowedEmails = process.env.ALLOWED_EMAILS.split(",");
|
|
||||||
// Check whether the email matches enough of the patterns specified in ALLOWED_EMAILS
|
|
||||||
const isAllowed = allowedEmails.some((allowedEmail) => {
|
|
||||||
const regex = new RegExp(
|
|
||||||
`^${allowedEmail
|
|
||||||
.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
|
|
||||||
.replaceAll(/[*]/g, ".*")}$`,
|
|
||||||
);
|
|
||||||
return regex.test(email);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isAllowed) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function getOAuthProviders(): {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}[] {
|
|
||||||
return providers
|
|
||||||
.filter((provider) => provider.type === "oauth")
|
|
||||||
.map((provider) => {
|
|
||||||
return {
|
|
||||||
id: provider.id,
|
|
||||||
name: provider.options?.name || provider.name,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -10,19 +10,18 @@
|
||||||
* See: https://github.com/lukevella/rallly/issues/949
|
* See: https://github.com/lukevella/rallly/issues/949
|
||||||
*/
|
*/
|
||||||
import { PrismaAdapter } from "@auth/prisma-adapter";
|
import { PrismaAdapter } from "@auth/prisma-adapter";
|
||||||
import type { ExtendedPrismaClient, PrismaClient } from "@rallly/database";
|
import { prisma } from "@rallly/database";
|
||||||
import type { Adapter, AdapterAccount } from "next-auth/adapters";
|
import type { Adapter } from "next-auth/adapters";
|
||||||
|
|
||||||
export function CustomPrismaAdapter(
|
export function CustomPrismaAdapter(options: {
|
||||||
client: ExtendedPrismaClient,
|
migrateData: (userId: string) => Promise<void>;
|
||||||
options: { migrateData: (userId: string) => Promise<void> },
|
}) {
|
||||||
) {
|
const adapter = PrismaAdapter(prisma);
|
||||||
const adapter = PrismaAdapter(client as PrismaClient);
|
|
||||||
return {
|
return {
|
||||||
...adapter,
|
...adapter,
|
||||||
linkAccount: async (account: AdapterAccount) => {
|
linkAccount: async (account) => {
|
||||||
await options.migrateData(account.userId);
|
await options.migrateData(account.userId);
|
||||||
return (await client.account.create({
|
return prisma.account.create({
|
||||||
data: {
|
data: {
|
||||||
userId: account.userId,
|
userId: account.userId,
|
||||||
type: account.type,
|
type: account.type,
|
||||||
|
@ -36,7 +35,7 @@ export function CustomPrismaAdapter(
|
||||||
scope: account.scope as string,
|
scope: account.scope as string,
|
||||||
session_state: account.session_state as string,
|
session_state: account.session_state as string,
|
||||||
},
|
},
|
||||||
})) as AdapterAccount;
|
});
|
||||||
},
|
},
|
||||||
} satisfies Adapter;
|
} as Adapter;
|
||||||
}
|
}
|
11
apps/web/src/auth/get-optional-providers.ts
Normal file
11
apps/web/src/auth/get-optional-providers.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import type { Provider } from "next-auth/providers/index";
|
||||||
|
|
||||||
|
import { GoogleProvider } from "./providers/google";
|
||||||
|
import { MicrosoftProvider } from "./providers/microsoft";
|
||||||
|
import { OIDCProvider } from "./providers/oidc";
|
||||||
|
|
||||||
|
export function getOptionalProviders() {
|
||||||
|
return [OIDCProvider(), GoogleProvider(), MicrosoftProvider()].filter(
|
||||||
|
Boolean,
|
||||||
|
) as Provider[];
|
||||||
|
}
|
19
apps/web/src/auth/is-email-blocked.ts
Normal file
19
apps/web/src/auth/is-email-blocked.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
export const isEmailBlocked = (email: string) => {
|
||||||
|
if (process.env.ALLOWED_EMAILS) {
|
||||||
|
const allowedEmails = process.env.ALLOWED_EMAILS.split(",");
|
||||||
|
// Check whether the email matches enough of the patterns specified in ALLOWED_EMAILS
|
||||||
|
const isAllowed = allowedEmails.some((allowedEmail) => {
|
||||||
|
const regex = new RegExp(
|
||||||
|
`^${allowedEmail
|
||||||
|
.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
|
||||||
|
.replaceAll(/[*]/g, ".*")}$`,
|
||||||
|
);
|
||||||
|
return regex.test(email);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isAllowed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
41
apps/web/src/auth/providers/email.ts
Normal file
41
apps/web/src/auth/providers/email.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { prisma } from "@rallly/database";
|
||||||
|
import { absoluteUrl } from "@rallly/utils/absolute-url";
|
||||||
|
import { generateOtp } from "@rallly/utils/nanoid";
|
||||||
|
import NodemailerProvider from "next-auth/providers/nodemailer";
|
||||||
|
|
||||||
|
import { getEmailClient } from "@/utils/emails";
|
||||||
|
|
||||||
|
export const EmailProvider = NodemailerProvider({
|
||||||
|
server: "none", // This value is required even though we don't need it
|
||||||
|
from: process.env.NOREPLY_EMAIL,
|
||||||
|
id: "email",
|
||||||
|
generateVerificationToken() {
|
||||||
|
return generateOtp();
|
||||||
|
},
|
||||||
|
async sendVerificationRequest({ identifier: email, token, url }) {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
email,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
locale: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
await getEmailClient(user.locale ?? undefined).sendTemplate(
|
||||||
|
"LoginEmail",
|
||||||
|
{
|
||||||
|
to: email,
|
||||||
|
props: {
|
||||||
|
magicLink: absoluteUrl("/auth/login", {
|
||||||
|
magicLink: url,
|
||||||
|
}),
|
||||||
|
code: token,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
11
apps/web/src/auth/providers/google.ts
Normal file
11
apps/web/src/auth/providers/google.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import BaseGoogleProvider from "next-auth/providers/google";
|
||||||
|
|
||||||
|
export function GoogleProvider() {
|
||||||
|
if (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) {
|
||||||
|
return BaseGoogleProvider({
|
||||||
|
clientId: process.env.GOOGLE_CLIENT_ID,
|
||||||
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||||||
|
allowDangerousEmailAccountLinking: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
14
apps/web/src/auth/providers/guest.ts
Normal file
14
apps/web/src/auth/providers/guest.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { randomid } from "@rallly/utils/nanoid";
|
||||||
|
import CredentialsProvider from "next-auth/providers/credentials";
|
||||||
|
|
||||||
|
export const GuestProvider = CredentialsProvider({
|
||||||
|
id: "guest",
|
||||||
|
name: "Guest",
|
||||||
|
credentials: {},
|
||||||
|
async authorize() {
|
||||||
|
return {
|
||||||
|
id: `user-${randomid()}`,
|
||||||
|
email: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
17
apps/web/src/auth/providers/microsoft.ts
Normal file
17
apps/web/src/auth/providers/microsoft.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import MicrosoftEntraID from "next-auth/providers/microsoft-entra-id";
|
||||||
|
|
||||||
|
export function MicrosoftProvider() {
|
||||||
|
if (
|
||||||
|
process.env.MICROSOFT_TENANT_ID &&
|
||||||
|
process.env.MICROSOFT_CLIENT_ID &&
|
||||||
|
process.env.MICROSOFT_CLIENT_SECRET
|
||||||
|
) {
|
||||||
|
return MicrosoftEntraID({
|
||||||
|
name: "Microsoft",
|
||||||
|
clientId: process.env.MICROSOFT_CLIENT_ID,
|
||||||
|
clientSecret: process.env.MICROSOFT_CLIENT_SECRET,
|
||||||
|
wellKnown:
|
||||||
|
"https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
34
apps/web/src/auth/providers/oidc.ts
Normal file
34
apps/web/src/auth/providers/oidc.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import type { User } from "next-auth";
|
||||||
|
import type { OIDCConfig } from "next-auth/providers/index";
|
||||||
|
|
||||||
|
import { env } from "@/env";
|
||||||
|
import { getValueByPath } from "@/utils/get-value-by-path";
|
||||||
|
|
||||||
|
export const OIDCProvider = () => {
|
||||||
|
if (
|
||||||
|
process.env.OIDC_DISCOVERY_URL &&
|
||||||
|
process.env.OIDC_CLIENT_ID &&
|
||||||
|
process.env.OIDC_CLIENT_SECRET
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
id: "oidc",
|
||||||
|
name: process.env.OIDC_NAME ?? "OpenID Connect",
|
||||||
|
type: "oidc",
|
||||||
|
wellKnown: process.env.OIDC_DISCOVERY_URL,
|
||||||
|
authorization: { params: { scope: "openid email profile" } },
|
||||||
|
clientId: process.env.OIDC_CLIENT_ID,
|
||||||
|
clientSecret: process.env.OIDC_CLIENT_SECRET,
|
||||||
|
idToken: true,
|
||||||
|
checks: ["state"],
|
||||||
|
allowDangerousEmailAccountLinking: true,
|
||||||
|
profile(profile) {
|
||||||
|
return {
|
||||||
|
id: profile.sub,
|
||||||
|
name: getValueByPath(profile, env.OIDC_NAME_CLAIM_PATH),
|
||||||
|
email: getValueByPath(profile, env.OIDC_EMAIL_CLAIM_PATH),
|
||||||
|
image: getValueByPath(profile, env.OIDC_PICTURE_CLAIM_PATH),
|
||||||
|
} as User;
|
||||||
|
},
|
||||||
|
} satisfies OIDCConfig<Record<string, unknown>>;
|
||||||
|
}
|
||||||
|
};
|
47
apps/web/src/auth/providers/registration-token.ts
Normal file
47
apps/web/src/auth/providers/registration-token.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import { prisma } from "@rallly/database";
|
||||||
|
import CredentialsProvider from "next-auth/providers/credentials";
|
||||||
|
|
||||||
|
import type { RegistrationTokenPayload } from "@/trpc/types";
|
||||||
|
import { decryptToken } from "@/utils/session";
|
||||||
|
|
||||||
|
// When a user registers, we don't want to go through the email verification process
|
||||||
|
// so this provider allows us exchange the registration token for a session token
|
||||||
|
export const RegistrationTokenProvider = CredentialsProvider({
|
||||||
|
id: "registration-token",
|
||||||
|
name: "Registration Token",
|
||||||
|
credentials: {
|
||||||
|
token: {
|
||||||
|
label: "Token",
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async authorize(credentials) {
|
||||||
|
if (credentials?.token) {
|
||||||
|
const payload = await decryptToken<RegistrationTokenPayload>(
|
||||||
|
credentials.token as string,
|
||||||
|
);
|
||||||
|
if (payload) {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
email: payload.email,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
locale: true,
|
||||||
|
timeFormat: true,
|
||||||
|
timeZone: true,
|
||||||
|
image: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
});
|
|
@ -12,7 +12,7 @@ export const LoginLink = React.forwardRef<
|
||||||
<Link
|
<Link
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
href={`/login?callbackUrl=${encodeURIComponent(pathname)}`}
|
href={`/login?redirectTo=${encodeURIComponent(pathname)}`}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -18,7 +18,7 @@ export const LanguageSelect: React.FunctionComponent<{
|
||||||
return (
|
return (
|
||||||
<Select value={value} onValueChange={onChange}>
|
<Select value={value} onValueChange={onChange}>
|
||||||
<SelectTrigger asChild className={className}>
|
<SelectTrigger asChild className={className}>
|
||||||
<Button variant="ghost">
|
<Button>
|
||||||
<Icon>
|
<Icon>
|
||||||
<GlobeIcon />
|
<GlobeIcon />
|
||||||
</Icon>
|
</Icon>
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const RegisterLink = React.forwardRef<
|
||||||
onClick={async (e) => {
|
onClick={async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
props.onClick?.(e);
|
props.onClick?.(e);
|
||||||
router.push("/register?callbackUrl=" + encodeURIComponent(pathname));
|
router.push("/register?redirectTo=" + encodeURIComponent(pathname));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { prisma } from "@rallly/database";
|
import { prisma } from "@rallly/database";
|
||||||
|
|
||||||
import { getServerSession } from "@/auth";
|
import { auth } from "@/next-auth";
|
||||||
|
|
||||||
export async function getGuestPolls() {
|
export async function getGuestPolls() {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
const user = session?.user;
|
const user = session?.user;
|
||||||
const guestId = !user?.email ? user?.id : null;
|
const guestId = !user?.email ? user?.id : null;
|
||||||
|
|
||||||
|
|
|
@ -1,97 +1,47 @@
|
||||||
import languages from "@rallly/languages";
|
import languages from "@rallly/languages";
|
||||||
import { withPostHog } from "@rallly/posthog/next/middleware";
|
import { withPostHog } from "@rallly/posthog/next/middleware";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import withAuth from "next-auth/middleware";
|
import NextAuth from "next-auth";
|
||||||
|
|
||||||
import { getLocaleFromHeader } from "@/app/guest";
|
import { getLocaleFromHeader } from "@/app/guest";
|
||||||
import { isSelfHosted } from "@/utils/constants";
|
import { nextAuthConfig } from "@/next-auth.config";
|
||||||
|
|
||||||
|
const { auth } = NextAuth(nextAuthConfig);
|
||||||
|
|
||||||
const supportedLocales = Object.keys(languages);
|
const supportedLocales = Object.keys(languages);
|
||||||
|
|
||||||
const publicRoutes = [
|
export default auth(async (req) => {
|
||||||
"/login",
|
const { nextUrl } = req;
|
||||||
"/register",
|
const newUrl = nextUrl.clone();
|
||||||
"/invite/",
|
|
||||||
"/poll/",
|
|
||||||
"/auth/login",
|
|
||||||
];
|
|
||||||
|
|
||||||
if (process.env.QUICK_CREATE_ENABLED === "true") {
|
const isLoggedIn = req.auth?.user?.email;
|
||||||
publicRoutes.push("/quick-create", "/new");
|
|
||||||
}
|
|
||||||
|
|
||||||
export const middleware = withAuth(
|
// if the user is already logged in, don't let them access the login page
|
||||||
async function middleware(req) {
|
if (/^\/(login)/.test(newUrl.pathname) && isLoggedIn) {
|
||||||
const { nextUrl } = req;
|
newUrl.pathname = "/";
|
||||||
const newUrl = nextUrl.clone();
|
return NextResponse.redirect(newUrl);
|
||||||
|
}
|
||||||
|
|
||||||
const isLoggedIn = req.nextauth.token?.email;
|
// Check if locale is specified in cookie
|
||||||
// set x-pathname header to the pathname
|
let locale = req.auth?.user?.locale;
|
||||||
|
if (locale && supportedLocales.includes(locale)) {
|
||||||
|
newUrl.pathname = `/${locale}${newUrl.pathname}`;
|
||||||
|
} else {
|
||||||
|
// Check if locale is specified in header
|
||||||
|
locale = await getLocaleFromHeader(req);
|
||||||
|
|
||||||
// if the user is already logged in, don't let them access the login page
|
newUrl.pathname = `/${locale}${newUrl.pathname}`;
|
||||||
if (/^\/(login)/.test(newUrl.pathname) && isLoggedIn) {
|
}
|
||||||
newUrl.pathname = "/";
|
|
||||||
return NextResponse.redirect(newUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the user is not logged in and the page is not public, redirect to login
|
const res = NextResponse.rewrite(newUrl);
|
||||||
if (
|
res.headers.set("x-pathname", newUrl.pathname);
|
||||||
!isLoggedIn &&
|
|
||||||
!publicRoutes.some((route) => newUrl.pathname.startsWith(route))
|
|
||||||
) {
|
|
||||||
if (newUrl.pathname !== "/") {
|
|
||||||
newUrl.searchParams.set("callbackUrl", newUrl.pathname);
|
|
||||||
}
|
|
||||||
newUrl.pathname = "/login";
|
|
||||||
return NextResponse.redirect(newUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if locale is specified in cookie
|
if (req.auth?.user?.id) {
|
||||||
let locale = req.nextauth.token?.locale;
|
await withPostHog(res, { distinctID: req.auth.user.id });
|
||||||
if (locale && supportedLocales.includes(locale)) {
|
}
|
||||||
newUrl.pathname = `/${locale}${newUrl.pathname}`;
|
|
||||||
} else {
|
|
||||||
// Check if locale is specified in header
|
|
||||||
locale = await getLocaleFromHeader(req);
|
|
||||||
|
|
||||||
newUrl.pathname = `/${locale}${newUrl.pathname}`;
|
return res;
|
||||||
}
|
});
|
||||||
|
|
||||||
const res = NextResponse.rewrite(newUrl);
|
|
||||||
res.headers.set("x-pathname", newUrl.pathname);
|
|
||||||
|
|
||||||
if (req.nextauth.token) {
|
|
||||||
await withPostHog(res, { distinctID: req.nextauth.token.sub });
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
secret: process.env.SECRET_PASSWORD,
|
|
||||||
callbacks: {
|
|
||||||
authorized: ({ token, req }) => {
|
|
||||||
const nextUrl = req.nextUrl;
|
|
||||||
const isGuest = !token?.email;
|
|
||||||
if (
|
|
||||||
isSelfHosted &&
|
|
||||||
isGuest &&
|
|
||||||
!(
|
|
||||||
nextUrl.pathname.startsWith("/invite") ||
|
|
||||||
nextUrl.pathname.startsWith("/login") ||
|
|
||||||
nextUrl.pathname.startsWith("/register") ||
|
|
||||||
nextUrl.pathname.startsWith("/auth") ||
|
|
||||||
nextUrl.pathname.startsWith("/p/")
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// limit which pages guests can access for self-hosted instances
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: ["/((?!api|_next/static|_next/image|static|.*\\.).*)"],
|
matcher: ["/((?!api|_next/static|_next/image|static|.*\\.).*)"],
|
||||||
|
|
50
apps/web/src/next-auth.config.ts
Normal file
50
apps/web/src/next-auth.config.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import type { NextAuthConfig } from "next-auth";
|
||||||
|
|
||||||
|
import { env } from "@/env";
|
||||||
|
import { isQuickCreateEnabled } from "@/features/quick-create/constants";
|
||||||
|
|
||||||
|
const publicRoutes = ["/login", "/register", "/invite/", "/poll/", "/auth"];
|
||||||
|
|
||||||
|
if (isQuickCreateEnabled) {
|
||||||
|
publicRoutes.push("/quick-create", "/new");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We split the next-auth config so that we can create an edge compatible instance that is
|
||||||
|
* used in middleware.
|
||||||
|
*/
|
||||||
|
export const nextAuthConfig = {
|
||||||
|
session: {
|
||||||
|
strategy: "jwt",
|
||||||
|
},
|
||||||
|
secret: env.SECRET_PASSWORD,
|
||||||
|
providers: [],
|
||||||
|
callbacks: {
|
||||||
|
async session({ session, token }) {
|
||||||
|
session.user.id = token.sub as string;
|
||||||
|
session.user.email = token.email as string;
|
||||||
|
session.user.locale = token.locale;
|
||||||
|
session.user.timeFormat = token.timeFormat;
|
||||||
|
session.user.timeZone = token.timeZone;
|
||||||
|
session.user.weekStart = token.weekStart;
|
||||||
|
return session;
|
||||||
|
},
|
||||||
|
async authorized({ request, auth }) {
|
||||||
|
const { nextUrl } = request;
|
||||||
|
const isLoggedIn = !!auth?.user?.email;
|
||||||
|
const isPublicRoute = publicRoutes.some((route) =>
|
||||||
|
nextUrl.pathname.startsWith(route),
|
||||||
|
);
|
||||||
|
if (isLoggedIn || isPublicRoute) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const redirectUrl = new URL("/login", request.url);
|
||||||
|
if (nextUrl.pathname !== "/") {
|
||||||
|
const redirectPath = nextUrl.pathname + nextUrl.search;
|
||||||
|
redirectUrl.searchParams.set("redirectTo", redirectPath);
|
||||||
|
}
|
||||||
|
return NextResponse.redirect(redirectUrl);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies NextAuthConfig;
|
169
apps/web/src/next-auth.ts
Normal file
169
apps/web/src/next-auth.ts
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
import { prisma } from "@rallly/database";
|
||||||
|
import { posthog } from "@rallly/posthog/server";
|
||||||
|
import NextAuth from "next-auth";
|
||||||
|
import type { Provider } from "next-auth/providers";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { CustomPrismaAdapter } from "./auth/adapters/prisma";
|
||||||
|
import { isEmailBlocked } from "./auth/is-email-blocked";
|
||||||
|
import { mergeGuestsIntoUser } from "./auth/merge-user";
|
||||||
|
import { EmailProvider } from "./auth/providers/email";
|
||||||
|
import { GoogleProvider } from "./auth/providers/google";
|
||||||
|
import { GuestProvider } from "./auth/providers/guest";
|
||||||
|
import { MicrosoftProvider } from "./auth/providers/microsoft";
|
||||||
|
import { OIDCProvider } from "./auth/providers/oidc";
|
||||||
|
import { RegistrationTokenProvider } from "./auth/providers/registration-token";
|
||||||
|
import { nextAuthConfig } from "./next-auth.config";
|
||||||
|
|
||||||
|
const sessionUpdateSchema = z.object({
|
||||||
|
locale: z.string().nullish(),
|
||||||
|
timeFormat: z.enum(["12h", "24h"]).nullish(),
|
||||||
|
timeZone: z.string().nullish(),
|
||||||
|
weekStart: z.number().nullish(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { auth, handlers, signIn, signOut } = NextAuth({
|
||||||
|
...nextAuthConfig,
|
||||||
|
adapter: CustomPrismaAdapter({
|
||||||
|
migrateData: async (userId) => {
|
||||||
|
const session = await auth();
|
||||||
|
if (session?.user && session.user.email === null) {
|
||||||
|
await mergeGuestsIntoUser(userId, [session.user.id]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
providers: [
|
||||||
|
RegistrationTokenProvider,
|
||||||
|
EmailProvider,
|
||||||
|
GuestProvider,
|
||||||
|
...([GoogleProvider(), OIDCProvider(), MicrosoftProvider()].filter(
|
||||||
|
Boolean,
|
||||||
|
) as Provider[]),
|
||||||
|
],
|
||||||
|
pages: {
|
||||||
|
signIn: "/login",
|
||||||
|
verifyRequest: "/login/verify",
|
||||||
|
error: "/auth/error",
|
||||||
|
},
|
||||||
|
session: {
|
||||||
|
strategy: "jwt",
|
||||||
|
},
|
||||||
|
events: {
|
||||||
|
signIn({ user, account }) {
|
||||||
|
if (user.id) {
|
||||||
|
posthog?.capture({
|
||||||
|
distinctId: user.id,
|
||||||
|
event: "login",
|
||||||
|
properties: {
|
||||||
|
method: account?.provider,
|
||||||
|
$set: {
|
||||||
|
name: user.name,
|
||||||
|
email: user.email,
|
||||||
|
timeZone: user.timeZone,
|
||||||
|
locale: user.locale,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
callbacks: {
|
||||||
|
...nextAuthConfig.callbacks,
|
||||||
|
async signIn({ user, email, profile }) {
|
||||||
|
const distinctId = user.id;
|
||||||
|
// prevent sign in if email is not verified
|
||||||
|
if (
|
||||||
|
profile &&
|
||||||
|
"email_verified" in profile &&
|
||||||
|
profile.email_verified === false &&
|
||||||
|
distinctId
|
||||||
|
) {
|
||||||
|
posthog?.capture({
|
||||||
|
distinctId,
|
||||||
|
event: "login failed",
|
||||||
|
properties: {
|
||||||
|
reason: "email not verified",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Make sure email is allowed
|
||||||
|
if (user.email) {
|
||||||
|
const isBlocked = isEmailBlocked(user.email);
|
||||||
|
if (isBlocked) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now, we don't allow users to login unless they have
|
||||||
|
// registered an account. This is just because we need a name
|
||||||
|
// to display on the dashboard. The flow can be modified so that
|
||||||
|
// the name is requested after the user has logged in.
|
||||||
|
if (email?.verificationRequest) {
|
||||||
|
const isRegisteredUser =
|
||||||
|
(await prisma.user.count({
|
||||||
|
where: {
|
||||||
|
email: user.email as string,
|
||||||
|
},
|
||||||
|
})) > 0;
|
||||||
|
|
||||||
|
return isRegisteredUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
// when we login with a social account for the first time, the user is not created yet
|
||||||
|
// and the user id will be the same as the provider account id
|
||||||
|
// we handle this case the the prisma adapter when we link accounts
|
||||||
|
const isInitialSocialLogin = user.id === profile?.sub;
|
||||||
|
|
||||||
|
if (!isInitialSocialLogin) {
|
||||||
|
// merge guest user into newly logged in user
|
||||||
|
const session = await auth();
|
||||||
|
if (user.id && session?.user && !session.user.email) {
|
||||||
|
await mergeGuestsIntoUser(user.id, [session.user.id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
async jwt({ token, session, trigger }) {
|
||||||
|
if (trigger === "update") {
|
||||||
|
const parsed = sessionUpdateSchema.safeParse(session);
|
||||||
|
if (parsed.success) {
|
||||||
|
Object.entries(parsed.data).forEach(([key, value]) => {
|
||||||
|
token[key] = value;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error(parsed.error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const userId = token.sub;
|
||||||
|
const isGuest = userId?.startsWith("guest-");
|
||||||
|
|
||||||
|
if (userId && !isGuest) {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
email: true,
|
||||||
|
locale: true,
|
||||||
|
timeFormat: true,
|
||||||
|
timeZone: true,
|
||||||
|
weekStart: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
token.email = user.email;
|
||||||
|
token.locale = user.locale;
|
||||||
|
token.timeFormat = user.timeFormat;
|
||||||
|
token.timeZone = user.timeZone;
|
||||||
|
token.weekStart = user.weekStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,14 +0,0 @@
|
||||||
import { posthogApiHandler } from "@rallly/posthog/server";
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
|
||||||
|
|
||||||
import { AuthApiRoute } from "@/auth";
|
|
||||||
import { composeApiHandlers } from "@/utils/next";
|
|
||||||
|
|
||||||
export default async function auth(req: NextApiRequest, res: NextApiResponse) {
|
|
||||||
if (req.method === "HEAD") {
|
|
||||||
res.status(200).end();
|
|
||||||
res.setHeader("Content-Length", "0");
|
|
||||||
} else {
|
|
||||||
return composeApiHandlers(AuthApiRoute, posthogApiHandler)(req, res);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,7 +4,7 @@ import { generateOtp } from "@rallly/utils/nanoid";
|
||||||
import * as Sentry from "@sentry/nextjs";
|
import * as Sentry from "@sentry/nextjs";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { isEmailBlocked } from "@/auth";
|
import { isEmailBlocked } from "@/auth/is-email-blocked";
|
||||||
import { mergeGuestsIntoUser } from "@/auth/merge-user";
|
import { mergeGuestsIntoUser } from "@/auth/merge-user";
|
||||||
import { getEmailClient } from "@/utils/emails";
|
import { getEmailClient } from "@/utils/emails";
|
||||||
import { createToken, decryptToken } from "@/utils/session";
|
import { createToken, decryptToken } from "@/utils/session";
|
||||||
|
|
|
@ -2,14 +2,14 @@ import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
|
|
||||||
import { getServerSession } from "@/auth";
|
import { auth } from "@/next-auth";
|
||||||
import { getEmailClient } from "@/utils/emails";
|
import { getEmailClient } from "@/utils/emails";
|
||||||
|
|
||||||
import type { TRPCContext } from "../context";
|
import type { TRPCContext } from "../context";
|
||||||
import { appRouter } from "../routers";
|
import { appRouter } from "../routers";
|
||||||
|
|
||||||
async function createContext(): Promise<TRPCContext> {
|
async function createContext(): Promise<TRPCContext> {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
return {
|
return {
|
||||||
user: session?.user
|
user: session?.user
|
||||||
? {
|
? {
|
||||||
|
|
|
@ -31,6 +31,6 @@ test.describe.serial(() => {
|
||||||
|
|
||||||
deletePollDialog.getByRole("button", { name: "delete" }).click();
|
deletePollDialog.getByRole("button", { name: "delete" }).click();
|
||||||
|
|
||||||
await expect(page).toHaveURL("/login?callbackUrl=%2Fpolls");
|
await expect(page).toHaveURL("/login?redirectTo=%2Fpolls");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -52,11 +52,12 @@
|
||||||
"tailwindcss": "^3.4.4",
|
"tailwindcss": "^3.4.4",
|
||||||
"turbo": "^2.1.1",
|
"turbo": "^2.1.1",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vitest": "^2.1.1",
|
"vitest": "^2.1.9",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "20.x"
|
"node": "20.x"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.22"
|
"packageManager": "yarn@1.22.22",
|
||||||
|
"dependencies": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { waitUntil } from "@vercel/functions";
|
import { waitUntil } from "@vercel/functions";
|
||||||
|
import type { NextRequest } from "next/server";
|
||||||
import { PostHog } from "posthog-node";
|
import { PostHog } from "posthog-node";
|
||||||
|
|
||||||
function PostHogClient() {
|
function PostHogClient() {
|
||||||
|
@ -21,3 +22,10 @@ export function posthogApiHandler() {
|
||||||
console.error("Failed to flush PostHog events:", error);
|
console.error("Failed to flush PostHog events:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export function withPosthog(handler: (req: NextRequest) => Promise<Response>) {
|
||||||
|
return async (req: NextRequest) => {
|
||||||
|
const res = await handler(req);
|
||||||
|
posthogApiHandler();
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,6 @@
|
||||||
"nanoid": "^5.0.9"
|
"nanoid": "^5.0.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vitest": "^2.1.1"
|
"vitest": "^2.1.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
353
yarn.lock
353
yarn.lock
|
@ -20,24 +20,36 @@
|
||||||
"@jridgewell/gen-mapping" "^0.1.0"
|
"@jridgewell/gen-mapping" "^0.1.0"
|
||||||
"@jridgewell/trace-mapping" "^0.3.9"
|
"@jridgewell/trace-mapping" "^0.3.9"
|
||||||
|
|
||||||
"@auth/core@0.16.1":
|
"@auth/core@0.37.2":
|
||||||
version "0.16.1"
|
version "0.37.2"
|
||||||
resolved "https://registry.npmjs.org/@auth/core/-/core-0.16.1.tgz"
|
resolved "https://registry.yarnpkg.com/@auth/core/-/core-0.37.2.tgz#0db8a94a076846bd88eb7f9273618513e2285cb2"
|
||||||
integrity sha512-V+YifnjpyOadiiTbxfYDV2xYWo8xpKNtwYVskAEKUSwMvE0FlSlP+10QGBpf0axS/AJFOO61IR6GncFF/IOrHQ==
|
integrity sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@panva/hkdf" "^1.0.4"
|
"@panva/hkdf" "^1.2.1"
|
||||||
cookie "0.5.0"
|
"@types/cookie" "0.6.0"
|
||||||
jose "^4.11.1"
|
cookie "0.7.1"
|
||||||
oauth4webapi "^2.0.6"
|
jose "^5.9.3"
|
||||||
|
oauth4webapi "^3.0.0"
|
||||||
preact "10.11.3"
|
preact "10.11.3"
|
||||||
preact-render-to-string "5.2.3"
|
preact-render-to-string "5.2.3"
|
||||||
|
|
||||||
"@auth/prisma-adapter@^1.0.3":
|
"@auth/core@0.37.4":
|
||||||
version "1.0.3"
|
version "0.37.4"
|
||||||
resolved "https://registry.npmjs.org/@auth/prisma-adapter/-/prisma-adapter-1.0.3.tgz"
|
resolved "https://registry.yarnpkg.com/@auth/core/-/core-0.37.4.tgz#c51410aa7d0997fa22a07a196d2c21c8b1bca71b"
|
||||||
integrity sha512-AMwQbO7OiBYRCA6VNfv9CpcpiRh0BP4EKhPdtO+pom9Uhuor2ioE4IqvhUfJyBkSjAP2Gt9WbKqr9kzL9LrtIg==
|
integrity sha512-HOXJwXWXQRhbBDHlMU0K/6FT1v+wjtzdKhsNg0ZN7/gne6XPsIrjZ4daMcFnbq0Z/vsAbYBinQhhua0d77v7qw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@auth/core" "0.16.1"
|
"@panva/hkdf" "^1.2.1"
|
||||||
|
jose "^5.9.6"
|
||||||
|
oauth4webapi "^3.1.1"
|
||||||
|
preact "10.24.3"
|
||||||
|
preact-render-to-string "6.5.11"
|
||||||
|
|
||||||
|
"@auth/prisma-adapter@^2.7.4":
|
||||||
|
version "2.7.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@auth/prisma-adapter/-/prisma-adapter-2.7.4.tgz#4890be47a9f227f449832302d955c565c02879ee"
|
||||||
|
integrity sha512-3T/X94R9J1sxOLQtsD3ijIZ0JGHPXlZQxRr/8NpnZBJ3KGxun/mNsZ1MwMRhTxy0mmn9JWXk7u9+xCcVn0pu3A==
|
||||||
|
dependencies:
|
||||||
|
"@auth/core" "0.37.4"
|
||||||
|
|
||||||
"@aws-crypto/crc32@3.0.0":
|
"@aws-crypto/crc32@3.0.0":
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
|
@ -3378,10 +3390,10 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@opentelemetry/core" "^1.1.0"
|
"@opentelemetry/core" "^1.1.0"
|
||||||
|
|
||||||
"@panva/hkdf@^1.0.2", "@panva/hkdf@^1.0.4":
|
"@panva/hkdf@^1.2.1":
|
||||||
version "1.1.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.1.1.tgz"
|
resolved "https://registry.yarnpkg.com/@panva/hkdf/-/hkdf-1.2.1.tgz#cb0d111ef700136f4580349ff0226bf25c853f23"
|
||||||
integrity sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==
|
integrity sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==
|
||||||
|
|
||||||
"@peculiar/asn1-schema@^2.1.6", "@peculiar/asn1-schema@^2.3.0":
|
"@peculiar/asn1-schema@^2.1.6", "@peculiar/asn1-schema@^2.3.0":
|
||||||
version "2.3.3"
|
version "2.3.3"
|
||||||
|
@ -6135,6 +6147,11 @@
|
||||||
resolved "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.5.tgz"
|
resolved "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.5.tgz"
|
||||||
integrity sha512-v6LCdKfK6BwcqMo+wYW05rLS12S0ZO0Fl4w1h4aaZMD7bqT3gVUns6FvLJKGZHQmYn3SX55JWGpziwJRwVgutA==
|
integrity sha512-v6LCdKfK6BwcqMo+wYW05rLS12S0ZO0Fl4w1h4aaZMD7bqT3gVUns6FvLJKGZHQmYn3SX55JWGpziwJRwVgutA==
|
||||||
|
|
||||||
|
"@types/cookie@0.6.0":
|
||||||
|
version "0.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5"
|
||||||
|
integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==
|
||||||
|
|
||||||
"@types/cookie@^0.4.1":
|
"@types/cookie@^0.4.1":
|
||||||
version "0.4.1"
|
version "0.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d"
|
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d"
|
||||||
|
@ -6696,63 +6713,63 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@upstash/redis" "^1.31.3"
|
"@upstash/redis" "^1.31.3"
|
||||||
|
|
||||||
"@vitest/expect@2.1.1":
|
"@vitest/expect@2.1.9":
|
||||||
version "2.1.1"
|
version "2.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.1.tgz#907137a86246c5328929d796d741c4e95d1ee19d"
|
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.9.tgz#b566ea20d58ea6578d8dc37040d6c1a47ebe5ff8"
|
||||||
integrity sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==
|
integrity sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vitest/spy" "2.1.1"
|
"@vitest/spy" "2.1.9"
|
||||||
"@vitest/utils" "2.1.1"
|
"@vitest/utils" "2.1.9"
|
||||||
chai "^5.1.1"
|
chai "^5.1.2"
|
||||||
tinyrainbow "^1.2.0"
|
tinyrainbow "^1.2.0"
|
||||||
|
|
||||||
"@vitest/mocker@2.1.1":
|
"@vitest/mocker@2.1.9":
|
||||||
version "2.1.1"
|
version "2.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.1.tgz#3e37c80ac267318d4aa03c5073a017d148dc8e67"
|
resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.9.tgz#36243b27351ca8f4d0bbc4ef91594ffd2dc25ef5"
|
||||||
integrity sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA==
|
integrity sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vitest/spy" "^2.1.0-beta.1"
|
"@vitest/spy" "2.1.9"
|
||||||
estree-walker "^3.0.3"
|
estree-walker "^3.0.3"
|
||||||
magic-string "^0.30.11"
|
magic-string "^0.30.12"
|
||||||
|
|
||||||
"@vitest/pretty-format@2.1.1", "@vitest/pretty-format@^2.1.1":
|
"@vitest/pretty-format@2.1.9", "@vitest/pretty-format@^2.1.9":
|
||||||
version "2.1.1"
|
version "2.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.1.tgz#fea25dd4e88c3c1329fbccd1d16b1d607eb40067"
|
resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.9.tgz#434ff2f7611689f9ce70cd7d567eceb883653fdf"
|
||||||
integrity sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==
|
integrity sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
tinyrainbow "^1.2.0"
|
tinyrainbow "^1.2.0"
|
||||||
|
|
||||||
"@vitest/runner@2.1.1":
|
"@vitest/runner@2.1.9":
|
||||||
version "2.1.1"
|
version "2.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.1.tgz#f3b1fbc3c109fc44e2cceecc881344453f275559"
|
resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.9.tgz#cc18148d2d797fd1fd5908d1f1851d01459be2f6"
|
||||||
integrity sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA==
|
integrity sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vitest/utils" "2.1.1"
|
"@vitest/utils" "2.1.9"
|
||||||
pathe "^1.1.2"
|
pathe "^1.1.2"
|
||||||
|
|
||||||
"@vitest/snapshot@2.1.1":
|
"@vitest/snapshot@2.1.9":
|
||||||
version "2.1.1"
|
version "2.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.1.tgz#38ef23104e90231fea5540754a19d8468afbba66"
|
resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.9.tgz#24260b93f798afb102e2dcbd7e61c6dfa118df91"
|
||||||
integrity sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw==
|
integrity sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vitest/pretty-format" "2.1.1"
|
"@vitest/pretty-format" "2.1.9"
|
||||||
magic-string "^0.30.11"
|
magic-string "^0.30.12"
|
||||||
pathe "^1.1.2"
|
pathe "^1.1.2"
|
||||||
|
|
||||||
"@vitest/spy@2.1.1", "@vitest/spy@^2.1.0-beta.1":
|
"@vitest/spy@2.1.9":
|
||||||
version "2.1.1"
|
version "2.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.1.tgz#20891f7421a994256ea0d739ed72f05532c78488"
|
resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.9.tgz#cb28538c5039d09818b8bfa8edb4043c94727c60"
|
||||||
integrity sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g==
|
integrity sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
tinyspy "^3.0.0"
|
tinyspy "^3.0.2"
|
||||||
|
|
||||||
"@vitest/utils@2.1.1":
|
"@vitest/utils@2.1.9":
|
||||||
version "2.1.1"
|
version "2.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.1.tgz#284d016449ecb4f8704d198d049fde8360cc136e"
|
resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.9.tgz#4f2486de8a54acf7ecbf2c5c24ad7994a680a6c1"
|
||||||
integrity sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==
|
integrity sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vitest/pretty-format" "2.1.1"
|
"@vitest/pretty-format" "2.1.9"
|
||||||
loupe "^3.1.1"
|
loupe "^3.1.2"
|
||||||
tinyrainbow "^1.2.0"
|
tinyrainbow "^1.2.0"
|
||||||
|
|
||||||
"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1":
|
"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1":
|
||||||
|
@ -7528,19 +7545,19 @@ camelcase@^6.2.0:
|
||||||
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
|
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
|
||||||
|
|
||||||
caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001580, caniuse-lite@^1.0.30001629:
|
caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001580, caniuse-lite@^1.0.30001629:
|
||||||
version "1.0.30001636"
|
version "1.0.30001697"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz#b15f52d2bdb95fad32c2f53c0b68032b85188a78"
|
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001697.tgz"
|
||||||
integrity sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==
|
integrity sha512-GwNPlWJin8E+d7Gxq96jxM6w0w+VFeyyXRsjU58emtkYqnbwHqXm5uT2uCmO0RQE9htWknOP4xtBlLmM/gWxvQ==
|
||||||
|
|
||||||
ccount@^2.0.0:
|
ccount@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz"
|
resolved "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz"
|
||||||
integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==
|
integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==
|
||||||
|
|
||||||
chai@^5.1.1:
|
chai@^5.1.2:
|
||||||
version "5.1.1"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.1.tgz#f035d9792a22b481ead1c65908d14bb62ec1c82c"
|
resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.2.tgz#3afbc340b994ae3610ca519a6c70ace77ad4378d"
|
||||||
integrity sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==
|
integrity sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==
|
||||||
dependencies:
|
dependencies:
|
||||||
assertion-error "^2.0.1"
|
assertion-error "^2.0.1"
|
||||||
check-error "^2.1.1"
|
check-error "^2.1.1"
|
||||||
|
@ -7862,7 +7879,12 @@ convert-source-map@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
||||||
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
||||||
|
|
||||||
cookie@0.5.0, cookie@^0.5.0:
|
cookie@0.7.1:
|
||||||
|
version "0.7.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9"
|
||||||
|
integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==
|
||||||
|
|
||||||
|
cookie@^0.5.0:
|
||||||
version "0.5.0"
|
version "0.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
|
||||||
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
|
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
|
||||||
|
@ -8058,12 +8080,12 @@ debounce@2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/debounce/-/debounce-2.0.0.tgz#b2f914518a1481466f4edaee0b063e4d473ad549"
|
resolved "https://registry.yarnpkg.com/debounce/-/debounce-2.0.0.tgz#b2f914518a1481466f4edaee0b063e4d473ad549"
|
||||||
integrity sha512-xRetU6gL1VJbs85Mc4FoEGSjQxzpdxRyFhe3lmWFyy2EzydIcD4xzUvRJMD+NPDfMwKNhxa3PvsIOU32luIWeA==
|
integrity sha512-xRetU6gL1VJbs85Mc4FoEGSjQxzpdxRyFhe3lmWFyy2EzydIcD4xzUvRJMD+NPDfMwKNhxa3PvsIOU32luIWeA==
|
||||||
|
|
||||||
debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
|
debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7:
|
||||||
version "4.3.4"
|
version "4.4.0"
|
||||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
|
||||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "2.1.2"
|
ms "^2.1.3"
|
||||||
|
|
||||||
debug@^3.2.7:
|
debug@^3.2.7:
|
||||||
version "3.2.7"
|
version "3.2.7"
|
||||||
|
@ -8072,20 +8094,13 @@ debug@^3.2.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "^2.1.1"
|
ms "^2.1.1"
|
||||||
|
|
||||||
debug@^4.3.1:
|
debug@~4.3.1, debug@~4.3.2:
|
||||||
version "4.3.5"
|
version "4.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e"
|
||||||
integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==
|
integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "2.1.2"
|
ms "2.1.2"
|
||||||
|
|
||||||
debug@^4.3.6:
|
|
||||||
version "4.3.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
|
|
||||||
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
|
|
||||||
dependencies:
|
|
||||||
ms "^2.1.3"
|
|
||||||
|
|
||||||
decamelize-keys@^1.1.0:
|
decamelize-keys@^1.1.0:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8"
|
resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8"
|
||||||
|
@ -8563,6 +8578,11 @@ es-module-lexer@^1.2.1:
|
||||||
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.4.1.tgz#41ea21b43908fe6a287ffcbe4300f790555331f5"
|
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.4.1.tgz#41ea21b43908fe6a287ffcbe4300f790555331f5"
|
||||||
integrity sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==
|
integrity sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==
|
||||||
|
|
||||||
|
es-module-lexer@^1.5.4:
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.6.0.tgz#da49f587fd9e68ee2404fe4e256c0c7d3a81be21"
|
||||||
|
integrity sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==
|
||||||
|
|
||||||
es-set-tostringtag@^2.0.1:
|
es-set-tostringtag@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz"
|
resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz"
|
||||||
|
@ -9058,6 +9078,11 @@ events@^3.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
|
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
|
||||||
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
|
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
|
||||||
|
|
||||||
|
expect-type@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.1.0.tgz#a146e414250d13dfc49eafcfd1344a4060fa4c75"
|
||||||
|
integrity sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==
|
||||||
|
|
||||||
extend-shallow@^2.0.1:
|
extend-shallow@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz"
|
resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz"
|
||||||
|
@ -10393,12 +10418,7 @@ joi@^17.6.0:
|
||||||
"@sideway/formula" "^3.0.1"
|
"@sideway/formula" "^3.0.1"
|
||||||
"@sideway/pinpoint" "^2.0.0"
|
"@sideway/pinpoint" "^2.0.0"
|
||||||
|
|
||||||
jose@^4.11.1, jose@^4.11.4, jose@^4.15.1:
|
jose@^5.2.3, jose@^5.9.3, jose@^5.9.6:
|
||||||
version "4.15.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.5.tgz#6475d0f467ecd3c630a1b5dadd2735a7288df706"
|
|
||||||
integrity sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==
|
|
||||||
|
|
||||||
jose@^5.2.3:
|
|
||||||
version "5.9.6"
|
version "5.9.6"
|
||||||
resolved "https://registry.yarnpkg.com/jose/-/jose-5.9.6.tgz#77f1f901d88ebdc405e57cce08d2a91f47521883"
|
resolved "https://registry.yarnpkg.com/jose/-/jose-5.9.6.tgz#77f1f901d88ebdc405e57cce08d2a91f47521883"
|
||||||
integrity sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==
|
integrity sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==
|
||||||
|
@ -10651,13 +10671,18 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
js-tokens "^3.0.0 || ^4.0.0"
|
js-tokens "^3.0.0 || ^4.0.0"
|
||||||
|
|
||||||
loupe@^3.1.0, loupe@^3.1.1:
|
loupe@^3.1.0:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.1.tgz#71d038d59007d890e3247c5db97c1ec5a92edc54"
|
resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.1.tgz#71d038d59007d890e3247c5db97c1ec5a92edc54"
|
||||||
integrity sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==
|
integrity sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==
|
||||||
dependencies:
|
dependencies:
|
||||||
get-func-name "^2.0.1"
|
get-func-name "^2.0.1"
|
||||||
|
|
||||||
|
loupe@^3.1.2:
|
||||||
|
version "3.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.3.tgz#042a8f7986d77f3d0f98ef7990a2b2fef18b0fd2"
|
||||||
|
integrity sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==
|
||||||
|
|
||||||
lru-cache@^10.2.0:
|
lru-cache@^10.2.0:
|
||||||
version "10.2.2"
|
version "10.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878"
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878"
|
||||||
|
@ -10699,20 +10724,13 @@ magic-string@0.30.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
"@jridgewell/sourcemap-codec" "^1.4.15"
|
"@jridgewell/sourcemap-codec" "^1.4.15"
|
||||||
|
|
||||||
magic-string@^0.30.11:
|
magic-string@^0.30.12, magic-string@^0.30.3:
|
||||||
version "0.30.11"
|
version "0.30.17"
|
||||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.11.tgz#301a6f93b3e8c2cb13ac1a7a673492c0dfd12954"
|
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453"
|
||||||
integrity sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==
|
integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@jridgewell/sourcemap-codec" "^1.5.0"
|
"@jridgewell/sourcemap-codec" "^1.5.0"
|
||||||
|
|
||||||
magic-string@^0.30.3:
|
|
||||||
version "0.30.10"
|
|
||||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e"
|
|
||||||
integrity sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==
|
|
||||||
dependencies:
|
|
||||||
"@jridgewell/sourcemap-codec" "^1.4.15"
|
|
||||||
|
|
||||||
map-obj@^1.0.0:
|
map-obj@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
|
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
|
||||||
|
@ -11356,20 +11374,12 @@ neverthrow@^7.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/neverthrow/-/neverthrow-7.2.0.tgz#76fa0a6cf1f6d59f0770df461c92b8b270910694"
|
resolved "https://registry.yarnpkg.com/neverthrow/-/neverthrow-7.2.0.tgz#76fa0a6cf1f6d59f0770df461c92b8b270910694"
|
||||||
integrity sha512-iGBUfFB7yPczHHtA8dksKTJ9E8TESNTAx1UQWW6TzMF280vo9jdPYpLUXrMN1BCkPdHFdNG3fxOt2CUad8KhAw==
|
integrity sha512-iGBUfFB7yPczHHtA8dksKTJ9E8TESNTAx1UQWW6TzMF280vo9jdPYpLUXrMN1BCkPdHFdNG3fxOt2CUad8KhAw==
|
||||||
|
|
||||||
next-auth@^4.24.5:
|
next-auth@^5.0.0-beta.25:
|
||||||
version "4.24.5"
|
version "5.0.0-beta.25"
|
||||||
resolved "https://registry.yarnpkg.com/next-auth/-/next-auth-4.24.5.tgz#1fd1bfc0603c61fd2ba6fd81b976af690edbf07e"
|
resolved "https://registry.yarnpkg.com/next-auth/-/next-auth-5.0.0-beta.25.tgz#3a9f9734e1d8fa5ced545360f1afc24862cb92d5"
|
||||||
integrity sha512-3RafV3XbfIKk6rF6GlLE4/KxjTcuMCifqrmD+98ejFq73SRoj2rmzoca8u764977lH/Q7jo6Xu6yM+Re1Mz/Og==
|
integrity sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.20.13"
|
"@auth/core" "0.37.2"
|
||||||
"@panva/hkdf" "^1.0.2"
|
|
||||||
cookie "^0.5.0"
|
|
||||||
jose "^4.11.4"
|
|
||||||
oauth "^0.9.15"
|
|
||||||
openid-client "^5.4.0"
|
|
||||||
preact "^10.6.3"
|
|
||||||
preact-render-to-string "^5.1.19"
|
|
||||||
uuid "^8.3.2"
|
|
||||||
|
|
||||||
next-i18next@^13.0.3:
|
next-i18next@^13.0.3:
|
||||||
version "13.1.6"
|
version "13.1.6"
|
||||||
|
@ -11535,26 +11545,16 @@ nth-check@^2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
boolbase "^1.0.0"
|
boolbase "^1.0.0"
|
||||||
|
|
||||||
oauth4webapi@^2.0.6:
|
oauth4webapi@^3.0.0, oauth4webapi@^3.1.1:
|
||||||
version "2.3.0"
|
version "3.1.4"
|
||||||
resolved "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.3.0.tgz"
|
resolved "https://registry.yarnpkg.com/oauth4webapi/-/oauth4webapi-3.1.4.tgz#50695385cea8e7a43f3e2e23bc33ea27faece4a7"
|
||||||
integrity sha512-JGkb5doGrwzVDuHwgrR4nHJayzN4h59VCed6EW8Tql6iHDfZIabCJvg6wtbn5q6pyB2hZruI3b77Nudvq7NmvA==
|
integrity sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg==
|
||||||
|
|
||||||
oauth@^0.9.15:
|
|
||||||
version "0.9.15"
|
|
||||||
resolved "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz"
|
|
||||||
integrity sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==
|
|
||||||
|
|
||||||
object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
|
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
|
||||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||||
|
|
||||||
object-hash@^2.2.0:
|
|
||||||
version "2.2.0"
|
|
||||||
resolved "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz"
|
|
||||||
integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==
|
|
||||||
|
|
||||||
object-hash@^3.0.0:
|
object-hash@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz"
|
resolved "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz"
|
||||||
|
@ -11648,11 +11648,6 @@ obuf@~1.1.2:
|
||||||
resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
|
resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
|
||||||
integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
|
integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
|
||||||
|
|
||||||
oidc-token-hash@^5.0.3:
|
|
||||||
version "5.0.3"
|
|
||||||
resolved "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz"
|
|
||||||
integrity sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==
|
|
||||||
|
|
||||||
once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0:
|
once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
|
resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
|
||||||
|
@ -11672,16 +11667,6 @@ opener@^1.5.2:
|
||||||
resolved "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz"
|
resolved "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz"
|
||||||
integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==
|
integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==
|
||||||
|
|
||||||
openid-client@^5.4.0:
|
|
||||||
version "5.6.0"
|
|
||||||
resolved "https://registry.npmjs.org/openid-client/-/openid-client-5.6.0.tgz"
|
|
||||||
integrity sha512-uFTkN/iqgKvSnmpVAS/T6SNThukRMBcmymTQ71Ngus1F60tdtKVap7zCrleocY+fogPtpmoxi5Q1YdrgYuTlkA==
|
|
||||||
dependencies:
|
|
||||||
jose "^4.15.1"
|
|
||||||
lru-cache "^6.0.0"
|
|
||||||
object-hash "^2.2.0"
|
|
||||||
oidc-token-hash "^5.0.3"
|
|
||||||
|
|
||||||
optionator@^0.9.3:
|
optionator@^0.9.3:
|
||||||
version "0.9.3"
|
version "0.9.3"
|
||||||
resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz"
|
resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz"
|
||||||
|
@ -12154,28 +12139,26 @@ preact-render-to-string@5.2.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
pretty-format "^3.8.0"
|
pretty-format "^3.8.0"
|
||||||
|
|
||||||
preact-render-to-string@^5.1.19:
|
preact-render-to-string@6.5.11:
|
||||||
version "5.2.6"
|
version "6.5.11"
|
||||||
resolved "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz"
|
resolved "https://registry.yarnpkg.com/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz#467e69908a453497bb93d4d1fc35fb749a78e027"
|
||||||
integrity sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==
|
integrity sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==
|
||||||
dependencies:
|
|
||||||
pretty-format "^3.8.0"
|
|
||||||
|
|
||||||
preact@10.11.3:
|
preact@10.11.3:
|
||||||
version "10.11.3"
|
version "10.11.3"
|
||||||
resolved "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz"
|
resolved "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz"
|
||||||
integrity sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==
|
integrity sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==
|
||||||
|
|
||||||
|
preact@10.24.3:
|
||||||
|
version "10.24.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/preact/-/preact-10.24.3.tgz#086386bd47071e3b45410ef20844c21e23828f64"
|
||||||
|
integrity sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==
|
||||||
|
|
||||||
preact@^10.19.3:
|
preact@^10.19.3:
|
||||||
version "10.19.3"
|
version "10.19.3"
|
||||||
resolved "https://registry.yarnpkg.com/preact/-/preact-10.19.3.tgz#7a7107ed2598a60676c943709ea3efb8aaafa899"
|
resolved "https://registry.yarnpkg.com/preact/-/preact-10.19.3.tgz#7a7107ed2598a60676c943709ea3efb8aaafa899"
|
||||||
integrity sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==
|
integrity sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==
|
||||||
|
|
||||||
preact@^10.6.3:
|
|
||||||
version "10.18.1"
|
|
||||||
resolved "https://registry.npmjs.org/preact/-/preact-10.18.1.tgz"
|
|
||||||
integrity sha512-mKUD7RRkQQM6s7Rkmi7IFkoEHjuFqRQUaXamO61E6Nn7vqF/bo7EZCmSyrUnp2UWHw0O7XjZ2eeXis+m7tf4lg==
|
|
||||||
|
|
||||||
prelude-ls@^1.2.1:
|
prelude-ls@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
|
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
|
||||||
|
@ -13280,10 +13263,10 @@ stacktrace-parser@0.1.10, stacktrace-parser@^0.1.10:
|
||||||
resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz"
|
resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz"
|
||||||
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
|
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
|
||||||
|
|
||||||
std-env@^3.7.0:
|
std-env@^3.8.0:
|
||||||
version "3.7.0"
|
version "3.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2"
|
resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.0.tgz#b56ffc1baf1a29dcc80a3bdf11d7fca7c315e7d5"
|
||||||
integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==
|
integrity sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==
|
||||||
|
|
||||||
stream-shift@^1.0.0:
|
stream-shift@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
|
@ -13736,22 +13719,22 @@ tinybench@^2.9.0:
|
||||||
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b"
|
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b"
|
||||||
integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==
|
integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==
|
||||||
|
|
||||||
tinyexec@^0.3.0:
|
tinyexec@^0.3.1:
|
||||||
version "0.3.0"
|
version "0.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.0.tgz#ed60cfce19c17799d4a241e06b31b0ec2bee69e6"
|
resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2"
|
||||||
integrity sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==
|
integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==
|
||||||
|
|
||||||
tinypool@^1.0.0:
|
tinypool@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.1.tgz#c64233c4fac4304e109a64340178760116dbe1fe"
|
resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.2.tgz#706193cc532f4c100f66aa00b01c42173d9051b2"
|
||||||
integrity sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==
|
integrity sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==
|
||||||
|
|
||||||
tinyrainbow@^1.2.0:
|
tinyrainbow@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5"
|
resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5"
|
||||||
integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==
|
integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==
|
||||||
|
|
||||||
tinyspy@^3.0.0:
|
tinyspy@^3.0.2:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a"
|
resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a"
|
||||||
integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==
|
integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==
|
||||||
|
@ -14337,13 +14320,14 @@ vinyl@^2.0.0, vinyl@^2.2.0:
|
||||||
remove-trailing-separator "^1.0.1"
|
remove-trailing-separator "^1.0.1"
|
||||||
replace-ext "^1.0.0"
|
replace-ext "^1.0.0"
|
||||||
|
|
||||||
vite-node@2.1.1:
|
vite-node@2.1.9:
|
||||||
version "2.1.1"
|
version "2.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.1.tgz#7d46f623c04dfed6df34e7127711508a3386fa1c"
|
resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.9.tgz#549710f76a643f1c39ef34bdb5493a944e4f895f"
|
||||||
integrity sha512-N/mGckI1suG/5wQI35XeR9rsMsPqKXzq1CdUndzVstBj/HvyxxGctwnK6WX43NGt5L3Z5tcRf83g4TITKJhPrA==
|
integrity sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==
|
||||||
dependencies:
|
dependencies:
|
||||||
cac "^6.7.14"
|
cac "^6.7.14"
|
||||||
debug "^4.3.6"
|
debug "^4.3.7"
|
||||||
|
es-module-lexer "^1.5.4"
|
||||||
pathe "^1.1.2"
|
pathe "^1.1.2"
|
||||||
vite "^5.0.0"
|
vite "^5.0.0"
|
||||||
|
|
||||||
|
@ -14358,29 +14342,30 @@ vite@^5.0.0:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.3"
|
fsevents "~2.3.3"
|
||||||
|
|
||||||
vitest@^2.1.1:
|
vitest@^2.1.9:
|
||||||
version "2.1.1"
|
version "2.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.1.tgz#24a6f6f5d894509f10685b82de008c507faacbb1"
|
resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.9.tgz#7d01ffd07a553a51c87170b5e80fea3da7fb41e7"
|
||||||
integrity sha512-97We7/VC0e9X5zBVkvt7SGQMGrRtn3KtySFQG5fpaMlS+l62eeXRQO633AYhSTC3z7IMebnPPNjGXVGNRFlxBA==
|
integrity sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vitest/expect" "2.1.1"
|
"@vitest/expect" "2.1.9"
|
||||||
"@vitest/mocker" "2.1.1"
|
"@vitest/mocker" "2.1.9"
|
||||||
"@vitest/pretty-format" "^2.1.1"
|
"@vitest/pretty-format" "^2.1.9"
|
||||||
"@vitest/runner" "2.1.1"
|
"@vitest/runner" "2.1.9"
|
||||||
"@vitest/snapshot" "2.1.1"
|
"@vitest/snapshot" "2.1.9"
|
||||||
"@vitest/spy" "2.1.1"
|
"@vitest/spy" "2.1.9"
|
||||||
"@vitest/utils" "2.1.1"
|
"@vitest/utils" "2.1.9"
|
||||||
chai "^5.1.1"
|
chai "^5.1.2"
|
||||||
debug "^4.3.6"
|
debug "^4.3.7"
|
||||||
magic-string "^0.30.11"
|
expect-type "^1.1.0"
|
||||||
|
magic-string "^0.30.12"
|
||||||
pathe "^1.1.2"
|
pathe "^1.1.2"
|
||||||
std-env "^3.7.0"
|
std-env "^3.8.0"
|
||||||
tinybench "^2.9.0"
|
tinybench "^2.9.0"
|
||||||
tinyexec "^0.3.0"
|
tinyexec "^0.3.1"
|
||||||
tinypool "^1.0.0"
|
tinypool "^1.0.1"
|
||||||
tinyrainbow "^1.2.0"
|
tinyrainbow "^1.2.0"
|
||||||
vite "^5.0.0"
|
vite "^5.0.0"
|
||||||
vite-node "2.1.1"
|
vite-node "2.1.9"
|
||||||
why-is-node-running "^2.3.0"
|
why-is-node-running "^2.3.0"
|
||||||
|
|
||||||
void-elements@3.1.0:
|
void-elements@3.1.0:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue