mirror of
https://github.com/lukevella/rallly.git
synced 2025-07-07 19:47:26 +02:00
🔓 Add config to secure instance from unauth users (#559)
This commit is contained in:
parent
e65c87bf04
commit
1b38a3cf76
16 changed files with 194 additions and 104 deletions
15
apps/web/declarations/environment.d.ts
vendored
15
apps/web/declarations/environment.d.ts
vendored
|
@ -10,9 +10,9 @@ declare global {
|
||||||
*/
|
*/
|
||||||
NODE_ENV: "development" | "production";
|
NODE_ENV: "development" | "production";
|
||||||
/**
|
/**
|
||||||
* Can be "false" or a relative path eg. "/new"
|
* Set to "true" to take users straight to app instead of landing page
|
||||||
*/
|
*/
|
||||||
LANDING_PAGE: string;
|
DISABLE_LANDING_PAGE?: string;
|
||||||
/**
|
/**
|
||||||
* Must be 32 characters long
|
* Must be 32 characters long
|
||||||
*/
|
*/
|
||||||
|
@ -57,6 +57,17 @@ declare global {
|
||||||
* Port number of the SMTP server
|
* Port number of the SMTP server
|
||||||
*/
|
*/
|
||||||
SMTP_PORT: string;
|
SMTP_PORT: string;
|
||||||
|
/**
|
||||||
|
* Comma separated list of email addresses that are allowed to register and login.
|
||||||
|
* If not set, all emails are allowed. Wildcard characters are supported.
|
||||||
|
*
|
||||||
|
* Example: "user@example.com, *@example.com, *@*.example.com"
|
||||||
|
*/
|
||||||
|
ALLOWED_EMAILS?: string;
|
||||||
|
/**
|
||||||
|
* "true" to require authentication for creating new polls and accessing admin pages
|
||||||
|
*/
|
||||||
|
AUTH_REQUIRED?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,8 @@ const nextConfig = {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
source: "/",
|
source: "/",
|
||||||
destination: "/home",
|
destination:
|
||||||
|
process.env.DISABLE_LANDING_PAGE === "true" ? "/new" : "/home",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
"editDetails": "Edit details",
|
"editDetails": "Edit details",
|
||||||
"editOptions": "Edit options",
|
"editOptions": "Edit options",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
|
"emailNotAllowed": "This email is not allowed.",
|
||||||
"emailPlaceholder": "jessie.smith@email.com",
|
"emailPlaceholder": "jessie.smith@email.com",
|
||||||
"endingGuestSessionNotice": "Once a guest session ends it cannot be resumed. You will not be able to edit any votes or comments you've made with this session.",
|
"endingGuestSessionNotice": "Once a guest session ends it cannot be resumed. You will not be able to edit any votes or comments you've made with this session.",
|
||||||
"endSession": "End session",
|
"endSession": "End session",
|
||||||
|
|
|
@ -182,12 +182,16 @@ export const RegisterForm: React.FunctionComponent<{
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
switch (res.code) {
|
switch (res.reason) {
|
||||||
case "userAlreadyExists":
|
case "userAlreadyExists":
|
||||||
setError("email", {
|
setError("email", {
|
||||||
message: t("userAlreadyExists"),
|
message: t("userAlreadyExists"),
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case "emailNotAllowed":
|
||||||
|
setError("email", {
|
||||||
|
message: t("emailNotAllowed"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setToken(res.token);
|
setToken(res.token);
|
||||||
|
@ -308,7 +312,22 @@ export const LoginForm: React.FunctionComponent<{
|
||||||
email: values.email,
|
email: values.email,
|
||||||
});
|
});
|
||||||
|
|
||||||
setToken(res.token);
|
if (res.ok) {
|
||||||
|
setToken(res.token);
|
||||||
|
} else {
|
||||||
|
switch (res.reason) {
|
||||||
|
case "emailNotAllowed":
|
||||||
|
setError("email", {
|
||||||
|
message: t("emailNotAllowed"),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "userNotFound":
|
||||||
|
setError("email", {
|
||||||
|
message: t("userNotFound"),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onChange={() => setToken(undefined)}
|
onChange={() => setToken(undefined)}
|
||||||
email={getValues("email")}
|
email={getValues("email")}
|
||||||
|
@ -323,13 +342,21 @@ export const LoginForm: React.FunctionComponent<{
|
||||||
email: data.email,
|
email: data.email,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.token) {
|
if (res.ok) {
|
||||||
setError("email", {
|
|
||||||
type: "not_found",
|
|
||||||
message: t("userNotFound"),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setToken(res.token);
|
setToken(res.token);
|
||||||
|
} else {
|
||||||
|
switch (res.reason) {
|
||||||
|
case "emailNotAllowed":
|
||||||
|
setError("email", {
|
||||||
|
message: t("emailNotAllowed"),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "userNotFound":
|
||||||
|
setError("email", {
|
||||||
|
message: t("userNotFound"),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|
|
@ -79,11 +79,11 @@ export const UserDropdown: React.FunctionComponent<DropdownProps> = ({
|
||||||
onClick={openLoginModal}
|
onClick={openLoginModal}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<DropdownItem
|
{user.isGuest ? (
|
||||||
icon={Logout}
|
<DropdownItem
|
||||||
label={user.isGuest ? t("app:forgetMe") : t("app:logout")}
|
icon={Logout}
|
||||||
onClick={() => {
|
label={t("app:forgetMe")}
|
||||||
if (user?.isGuest) {
|
onClick={() => {
|
||||||
modalContext.render({
|
modalContext.render({
|
||||||
title: t("app:areYouSure"),
|
title: t("app:areYouSure"),
|
||||||
description: t("app:endingGuestSessionNotice"),
|
description: t("app:endingGuestSessionNotice"),
|
||||||
|
@ -95,11 +95,11 @@ export const UserDropdown: React.FunctionComponent<DropdownProps> = ({
|
||||||
okText: t("app:endSession"),
|
okText: t("app:endSession"),
|
||||||
cancelText: t("app:cancel"),
|
cancelText: t("app:cancel"),
|
||||||
});
|
});
|
||||||
} else {
|
}}
|
||||||
logout();
|
/>
|
||||||
}
|
) : (
|
||||||
}}
|
<DropdownItem icon={Logout} href="/logout" label={t("app:logout")} />
|
||||||
/>
|
)}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { getStandardLayout } from "@/components/layouts/standard-layout";
|
||||||
import { ParticipantsProvider } from "@/components/participants-provider";
|
import { ParticipantsProvider } from "@/components/participants-provider";
|
||||||
import { Poll } from "@/components/poll";
|
import { Poll } from "@/components/poll";
|
||||||
import { PollContextProvider } from "@/components/poll-context";
|
import { PollContextProvider } from "@/components/poll-context";
|
||||||
import { withSessionSsr } from "@/utils/auth";
|
import { withAuthIfRequired, withSessionSsr } from "@/utils/auth";
|
||||||
import { trpc } from "@/utils/trpc";
|
import { trpc } from "@/utils/trpc";
|
||||||
import { withPageTranslations } from "@/utils/with-page-translations";
|
import { withPageTranslations } from "@/utils/with-page-translations";
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@ Page.getLayout = getStandardLayout;
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = withSessionSsr(
|
export const getServerSideProps: GetServerSideProps = withSessionSsr(
|
||||||
[
|
[
|
||||||
|
withAuthIfRequired,
|
||||||
withPageTranslations(["common", "app", "errors"]),
|
withPageTranslations(["common", "app", "errors"]),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { useMount } from "react-use";
|
||||||
|
|
||||||
import FullPageLoader from "../components/full-page-loader";
|
import FullPageLoader from "../components/full-page-loader";
|
||||||
import { withSession } from "../components/user-provider";
|
import { withSession } from "../components/user-provider";
|
||||||
import { withSessionSsr } from "../utils/auth";
|
import { withAuthIfRequired, withSessionSsr } from "../utils/auth";
|
||||||
import { trpc } from "../utils/trpc";
|
import { trpc } from "../utils/trpc";
|
||||||
import { withPageTranslations } from "../utils/with-page-translations";
|
import { withPageTranslations } from "../utils/with-page-translations";
|
||||||
|
|
||||||
|
@ -26,8 +26,9 @@ const Demo: NextPage = () => {
|
||||||
return <FullPageLoader>{t("creatingDemo")}</FullPageLoader>;
|
return <FullPageLoader>{t("creatingDemo")}</FullPageLoader>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = withSessionSsr(
|
export const getServerSideProps = withSessionSsr([
|
||||||
|
withAuthIfRequired,
|
||||||
withPageTranslations(["common", "app"]),
|
withPageTranslations(["common", "app"]),
|
||||||
);
|
]);
|
||||||
|
|
||||||
export default withSession(Demo);
|
export default withSession(Demo);
|
||||||
|
|
|
@ -1,37 +1,20 @@
|
||||||
import { GetServerSideProps } from "next";
|
import { GetServerSideProps } from "next";
|
||||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
|
||||||
|
|
||||||
import Home from "@/components/home";
|
import Home from "@/components/home";
|
||||||
|
import { composeGetServerSideProps } from "@/utils/auth";
|
||||||
|
import { withPageTranslations } from "@/utils/with-page-translations";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return <Home />;
|
return <Home />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async ({
|
export const getServerSideProps: GetServerSideProps = composeGetServerSideProps(
|
||||||
locale = "en",
|
async () => {
|
||||||
}) => {
|
// TODO (Luke Vella) [2023-03-14]: Remove this once we split the app from the landing page
|
||||||
if (process.env.LANDING_PAGE) {
|
if (process.env.DISABLE_LANDING_PAGE === "true") {
|
||||||
if (process.env.LANDING_PAGE === "false") {
|
return { notFound: true };
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: "/new",
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
// if starts with /, it's a relative path
|
return { props: {} };
|
||||||
if (process.env.LANDING_PAGE.startsWith("/")) {
|
},
|
||||||
return {
|
withPageTranslations(["common", "homepage"]),
|
||||||
redirect: {
|
);
|
||||||
destination: process.env.LANDING_PAGE,
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
...(await serverSideTranslations(locale, ["common", "homepage"])),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import CreatePoll from "@/components/create-poll";
|
||||||
|
|
||||||
import StandardLayout from "../components/layouts/standard-layout";
|
import StandardLayout from "../components/layouts/standard-layout";
|
||||||
import { NextPageWithLayout } from "../types";
|
import { NextPageWithLayout } from "../types";
|
||||||
import { withSessionSsr } from "../utils/auth";
|
import { withAuthIfRequired, withSessionSsr } from "../utils/auth";
|
||||||
import { withPageTranslations } from "../utils/with-page-translations";
|
import { withPageTranslations } from "../utils/with-page-translations";
|
||||||
|
|
||||||
const Page: NextPageWithLayout = () => {
|
const Page: NextPageWithLayout = () => {
|
||||||
|
@ -28,6 +28,7 @@ Page.getLayout = function getLayout(page) {
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = withSessionSsr(
|
export const getServerSideProps: GetServerSideProps = withSessionSsr([
|
||||||
|
withAuthIfRequired,
|
||||||
withPageTranslations(["common", "app"]),
|
withPageTranslations(["common", "app"]),
|
||||||
);
|
]);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { withSessionSsr } from "@/utils/auth";
|
import { withAuth, withSessionSsr } from "@/utils/auth";
|
||||||
|
|
||||||
import { getStandardLayout } from "../components/layouts/standard-layout";
|
import { getStandardLayout } from "../components/layouts/standard-layout";
|
||||||
import { Profile } from "../components/profile";
|
import { Profile } from "../components/profile";
|
||||||
|
@ -11,16 +11,9 @@ const Page: NextPageWithLayout = () => {
|
||||||
|
|
||||||
Page.getLayout = getStandardLayout;
|
Page.getLayout = getStandardLayout;
|
||||||
|
|
||||||
export const getServerSideProps = withSessionSsr(async (ctx) => {
|
export const getServerSideProps = withSessionSsr([
|
||||||
if (ctx.req.session.user.isGuest !== false) {
|
withAuth,
|
||||||
return {
|
withPageTranslations(["common", "app"]),
|
||||||
redirect: {
|
]);
|
||||||
destination: "/login",
|
|
||||||
},
|
|
||||||
props: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return withPageTranslations(["common", "app"])(ctx);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|
|
@ -15,6 +15,22 @@ import {
|
||||||
import { generateOtp } from "../../utils/nanoid";
|
import { generateOtp } from "../../utils/nanoid";
|
||||||
import { publicProcedure, router } from "../trpc";
|
import { publicProcedure, router } from "../trpc";
|
||||||
|
|
||||||
|
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.trim().replace("*", ".*"), "i");
|
||||||
|
return regex.test(email);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isAllowed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
export const auth = router({
|
export const auth = router({
|
||||||
requestRegistration: publicProcedure
|
requestRegistration: publicProcedure
|
||||||
.input(
|
.input(
|
||||||
|
@ -27,8 +43,13 @@ export const auth = router({
|
||||||
async ({
|
async ({
|
||||||
input,
|
input,
|
||||||
}): Promise<
|
}): Promise<
|
||||||
{ ok: true; token: string } | { ok: false; code: "userAlreadyExists" }
|
| { ok: true; token: string }
|
||||||
|
| { ok: false; reason: "userAlreadyExists" | "emailNotAllowed" }
|
||||||
> => {
|
> => {
|
||||||
|
if (isEmailBlocked(input.email)) {
|
||||||
|
return { ok: false, reason: "emailNotAllowed" };
|
||||||
|
}
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
@ -39,7 +60,7 @@ export const auth = router({
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
return { ok: false, code: "userAlreadyExists" };
|
return { ok: false, reason: "userAlreadyExists" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = await generateOtp();
|
const code = await generateOtp();
|
||||||
|
@ -108,36 +129,47 @@ export const auth = router({
|
||||||
email: z.string(),
|
email: z.string(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input }): Promise<{ token?: string }> => {
|
.mutation(
|
||||||
const user = await prisma.user.findUnique({
|
async ({
|
||||||
where: {
|
input,
|
||||||
email: input.email,
|
}): Promise<
|
||||||
},
|
| { ok: true; token: string }
|
||||||
});
|
| { ok: false; reason: "emailNotAllowed" | "userNotFound" }
|
||||||
|
> => {
|
||||||
|
if (isEmailBlocked(input.email)) {
|
||||||
|
return { ok: false, reason: "emailNotAllowed" };
|
||||||
|
}
|
||||||
|
|
||||||
if (!user) {
|
const user = await prisma.user.findUnique({
|
||||||
return { token: undefined };
|
where: {
|
||||||
}
|
email: input.email,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const code = await generateOtp();
|
if (!user) {
|
||||||
|
return { ok: false, reason: "userNotFound" };
|
||||||
|
}
|
||||||
|
|
||||||
const token = await createToken<LoginTokenPayload>({
|
const code = await generateOtp();
|
||||||
userId: user.id,
|
|
||||||
code,
|
|
||||||
});
|
|
||||||
|
|
||||||
await sendEmail("LoginEmail", {
|
const token = await createToken<LoginTokenPayload>({
|
||||||
to: input.email,
|
userId: user.id,
|
||||||
subject: "Login",
|
|
||||||
props: {
|
|
||||||
name: user.name,
|
|
||||||
code,
|
code,
|
||||||
magicLink: absoluteUrl(`/auth/login?token=${token}`),
|
});
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return { token };
|
await sendEmail("LoginEmail", {
|
||||||
}),
|
to: input.email,
|
||||||
|
subject: "Login",
|
||||||
|
props: {
|
||||||
|
name: user.name,
|
||||||
|
code,
|
||||||
|
magicLink: absoluteUrl(`/auth/login?token=${token}`),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ok: true, token };
|
||||||
|
},
|
||||||
|
),
|
||||||
authenticateLogin: publicProcedure
|
authenticateLogin: publicProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { createToken, EnableNotificationsTokenPayload } from "@/utils/auth";
|
||||||
import { absoluteUrl } from "../../utils/absolute-url";
|
import { absoluteUrl } from "../../utils/absolute-url";
|
||||||
import { nanoid } from "../../utils/nanoid";
|
import { nanoid } from "../../utils/nanoid";
|
||||||
import { GetPollApiResponse } from "../../utils/trpc/types";
|
import { GetPollApiResponse } from "../../utils/trpc/types";
|
||||||
import { publicProcedure, router } from "../trpc";
|
import { possiblyPublicProcedure, publicProcedure, router } from "../trpc";
|
||||||
import { comments } from "./polls/comments";
|
import { comments } from "./polls/comments";
|
||||||
import { demo } from "./polls/demo";
|
import { demo } from "./polls/demo";
|
||||||
import { participants } from "./polls/participants";
|
import { participants } from "./polls/participants";
|
||||||
|
@ -78,7 +78,7 @@ const getPollIdFromAdminUrlId = async (urlId: string) => {
|
||||||
|
|
||||||
export const polls = router({
|
export const polls = router({
|
||||||
// START LEGACY ROUTES
|
// START LEGACY ROUTES
|
||||||
create: publicProcedure
|
create: possiblyPublicProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
|
@ -168,7 +168,7 @@ export const polls = router({
|
||||||
return { id: poll.id, urlId: adminUrlId };
|
return { id: poll.id, urlId: adminUrlId };
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
update: publicProcedure
|
update: possiblyPublicProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
urlId: z.string(),
|
urlId: z.string(),
|
||||||
|
@ -222,7 +222,7 @@ export const polls = router({
|
||||||
|
|
||||||
return { ...poll };
|
return { ...poll };
|
||||||
}),
|
}),
|
||||||
delete: publicProcedure
|
delete: possiblyPublicProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
urlId: z.string(),
|
urlId: z.string(),
|
||||||
|
@ -253,7 +253,7 @@ export const polls = router({
|
||||||
comments,
|
comments,
|
||||||
verification,
|
verification,
|
||||||
// END LEGACY ROUTES
|
// END LEGACY ROUTES
|
||||||
enableNotifications: publicProcedure
|
enableNotifications: possiblyPublicProcedure
|
||||||
.input(z.object({ adminUrlId: z.string() }))
|
.input(z.object({ adminUrlId: z.string() }))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
const poll = await prisma.poll.findUnique({
|
const poll = await prisma.poll.findUnique({
|
||||||
|
@ -293,7 +293,7 @@ export const polls = router({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
getByAdminUrlId: publicProcedure
|
getByAdminUrlId: possiblyPublicProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
urlId: z.string(),
|
urlId: z.string(),
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { prisma, VoteType } from "@rallly/database";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
import { nanoid } from "../../../utils/nanoid";
|
import { nanoid } from "../../../utils/nanoid";
|
||||||
import { publicProcedure, router } from "../../trpc";
|
import { possiblyPublicProcedure, router } from "../../trpc";
|
||||||
|
|
||||||
const participantData: Array<{ name: string; votes: VoteType[] }> = [
|
const participantData: Array<{ name: string; votes: VoteType[] }> = [
|
||||||
{
|
{
|
||||||
|
@ -26,7 +26,7 @@ const participantData: Array<{ name: string; votes: VoteType[] }> = [
|
||||||
const optionValues = ["2022-12-14", "2022-12-15", "2022-12-16", "2022-12-17"];
|
const optionValues = ["2022-12-14", "2022-12-15", "2022-12-16", "2022-12-17"];
|
||||||
|
|
||||||
export const demo = router({
|
export const demo = router({
|
||||||
create: publicProcedure.mutation(async () => {
|
create: possiblyPublicProcedure.mutation(async () => {
|
||||||
const adminUrlId = await nanoid();
|
const adminUrlId = await nanoid();
|
||||||
const demoUser = { name: "John Example", email: "noreply@rallly.co" };
|
const demoUser = { name: "John Example", email: "noreply@rallly.co" };
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { initTRPC } from "@trpc/server";
|
import { initTRPC, TRPCError } from "@trpc/server";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
|
|
||||||
import { Context } from "./context";
|
import { Context } from "./context";
|
||||||
|
@ -16,4 +16,13 @@ export const publicProcedure = t.procedure;
|
||||||
|
|
||||||
export const middleware = t.middleware;
|
export const middleware = t.middleware;
|
||||||
|
|
||||||
|
const checkAuthIfRequired = middleware(async ({ ctx, next }) => {
|
||||||
|
if (process.env.AUTH_REQUIRED === "true" && ctx.session.user.isGuest) {
|
||||||
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Login is required" });
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
|
||||||
|
export const possiblyPublicProcedure = t.procedure.use(checkAuthIfRequired);
|
||||||
|
|
||||||
export const mergeRouters = t.mergeRouters;
|
export const mergeRouters = t.mergeRouters;
|
||||||
|
|
|
@ -101,6 +101,34 @@ export const composeGetServerSideProps = (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Require user to be logged in
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const withAuth: GetServerSideProps = async (ctx) => {
|
||||||
|
if (!ctx.req.session.user || ctx.req.session.user.isGuest) {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
destination: "/login",
|
||||||
|
permanent: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { props: {} };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Require user to be logged in if AUTH_REQUIRED is true
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const withAuthIfRequired: GetServerSideProps = async (ctx) => {
|
||||||
|
if (process.env.AUTH_REQUIRED === "true") {
|
||||||
|
return await withAuth(ctx);
|
||||||
|
}
|
||||||
|
return { props: {} };
|
||||||
|
};
|
||||||
|
|
||||||
export function withSessionSsr(
|
export function withSessionSsr(
|
||||||
handler: GetServerSideProps | GetServerSideProps[],
|
handler: GetServerSideProps | GetServerSideProps[],
|
||||||
options?: {
|
options?: {
|
||||||
|
|
|
@ -6,9 +6,11 @@
|
||||||
"dependsOn": ["^build"],
|
"dependsOn": ["^build"],
|
||||||
"outputs": [".next/**"],
|
"outputs": [".next/**"],
|
||||||
"env": [
|
"env": [
|
||||||
|
"ALLOWED_EMAILS",
|
||||||
|
"AUTH_REQUIRED",
|
||||||
"ANALYZE",
|
"ANALYZE",
|
||||||
"API_SECRET",
|
"API_SECRET",
|
||||||
"LANDING_PAGE",
|
"DISABLE_LANDING_PAGE",
|
||||||
"MAINTENANCE_MODE",
|
"MAINTENANCE_MODE",
|
||||||
"NEXT_PUBLIC_BASE_URL",
|
"NEXT_PUBLIC_BASE_URL",
|
||||||
"NEXT_PUBLIC_BETA",
|
"NEXT_PUBLIC_BETA",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue