Merge pull request #86 from webstudio-is/auth-small-fixes

This commit is contained in:
Sara Vieira authored and GitHub committed 2022-06-30 14:43:25 +02:00
commit 9f055dace1
12 files changed
+198 -58

No files matched your search

+2 -1
View File
@@ -1,4 +1,5 @@
DATABASE_URL=postgresql://user:pass@localhost/webstudio
AUTH_SECRET="# Linux: `openssl rand -hex 32` or go to https://generate-secret.now.sh/32"
GH_CLIENT_SECRET=
GH_CLIENT_ID=
GH_CLIENT_ID=
DEV_LOGIN=true
-10
View File
@@ -3,7 +3,6 @@ on: [push]
jobs:
checks:
env:
DATABASE_URL: mongo://
NODE_ENV: test
runs-on: ${{ matrix.os }}
strategy:
@@ -13,16 +12,7 @@ jobs:
steps:
- name: Checkout repo
uses: actions/checkout@v2
# - name: Install deps and build (with cache)
# uses: bahmutov/npm-install@v1
- run: yarn install
- name: Next.js cache
uses: actions/cache@v2
with:
path: ${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-${{ runner.node }}-${{ hashFiles('**/yarn.lock') }}-nextjs
- run: yarn checks
- run: yarn build
+16 -5
View File
@@ -1,7 +1,14 @@
import { LinksFunction, MetaFunction } from "@remix-run/node";
import { Form, useLoaderData } from "@remix-run/react";
import { useState } from "react";
import { Button, Card, Flex, Heading, TextField } from "~/shared/design-system";
import {
Button,
Card,
Flex,
Heading,
Text,
TextField,
} from "~/shared/design-system";
import interStyles from "~/shared/font-faces/inter.css";
import { GoogleIcon, GithubIcon, CommitIcon } from "~/shared/icons";
@@ -24,7 +31,7 @@ export const meta: MetaFunction = () => {
return { title: "Webstudio Login" };
};
export const Login = () => {
export const Login = ({ errorMessage }: { errorMessage: string }) => {
const [isDevLogin, setIsDevLogin] = useState(false);
const loaderData = useLoaderData();
@@ -35,10 +42,14 @@ export const Login = () => {
align="center"
justify="center"
>
<Card css={{ width: "$10", padding: "$5", zoom: 1.4 }} variant="active">
<Card css={{ width: 200, padding: "$5", zoom: 1.4 }} variant="active">
<Flex direction="column" gap="2" align="center">
<Heading>Login</Heading>
{errorMessage.length ? (
<Text css={{ textAlign: "center" }} variant="red">
{errorMessage}
</Text>
) : null}
<Flex gap="2" direction="column" align="center">
<Form action="/auth/github" method="post">
<Button type="submit">
@@ -67,7 +78,7 @@ export const Login = () => {
minLength={2}
required
autoFocus
placeholder="Place your AUTH_SECRET"
placeholder="Place your auth secret here"
/>
</Form>
) : (
+19 -6
View File
@@ -1,14 +1,27 @@
import { ActionFunction } from "@remix-run/node";
import { ActionFunction, redirect } from "@remix-run/node";
import config from "~/config";
import { authenticator } from "~/services/auth.server";
import { AUTH_PROVIDERS } from "~/shared/session/useLoginErrorMessage";
export default function Dev() {
return null;
}
export const action: ActionFunction = ({ request }) => {
return authenticator.authenticate("dev", request, {
successRedirect: config.dashboardPath,
failureRedirect: config.loginPath,
});
export const action: ActionFunction = async ({ request }) => {
try {
return await authenticator.authenticate("dev", request, {
successRedirect: config.dashboardPath,
throwOnError: true,
});
} catch (error: unknown) {
// all redirects are basically errors and in that case we don't want to catch it
if (error instanceof Response) return error;
if (error instanceof Error) {
return redirect(
`${config.loginPath}?error=${AUTH_PROVIDERS.LOGIN_DEV}&message=${
error?.message || ""
}`
);
}
}
};
+29 -9
View File
@@ -1,19 +1,39 @@
import { ActionFunction } from "@remix-run/node";
import { ActionFunction, redirect } from "@remix-run/node";
import config from "~/config";
import { authenticator } from "~/services/auth.server";
import { sentryException } from "~/shared/sentry";
import { ensureUserCookie } from "~/shared/session";
import { AUTH_PROVIDERS } from "~/shared/session/useLoginErrorMessage";
export default function GH() {
return null;
}
export const action: ActionFunction = async ({ request }) => {
const { userId } = await ensureUserCookie(request);
return authenticator.authenticate("github", request, {
context: {
userId,
},
successRedirect: config.dashboardPath,
failureRedirect: config.loginPath,
});
try {
const { userId } = await ensureUserCookie(request);
return await authenticator.authenticate("github", request, {
context: {
userId,
},
successRedirect: config.dashboardPath,
throwOnError: true,
});
} catch (error: unknown) {
// all redirects are basically errors and in that case we don't want to catch it
if (error instanceof Response) return error;
if (error instanceof Error) {
sentryException({
message: error.message,
extra: {
loginMethod: AUTH_PROVIDERS.LOGIN_GITHUB,
},
});
return redirect(
`${config.loginPath}?error=${AUTH_PROVIDERS.LOGIN_GITHUB}&message=${
error?.message || ""
}`
);
}
}
};
+12 -1
View File
@@ -1,10 +1,21 @@
import { LoaderFunction } from "@remix-run/node";
import { LoaderFunction, redirect } from "@remix-run/node";
import config from "~/config";
import { authenticator } from "~/services/auth.server";
import { ensureUserCookie } from "~/shared/session";
import { AUTH_PROVIDERS } from "~/shared/session/useLoginErrorMessage";
export const loader: LoaderFunction = async ({ request }) => {
const { userId } = await ensureUserCookie(request);
const url = new URL(request.url);
const error = url.searchParams.get("error");
const error_description = url.searchParams.get("error_description");
if (error) {
return redirect(
`${config.loginPath}?error=${AUTH_PROVIDERS.LOGIN_GITHUB}&message=${
error_description || error
}`
);
}
return authenticator.authenticate("github", request, {
context: {
userId,
+28 -8
View File
@@ -1,7 +1,9 @@
import { ActionFunction, LoaderFunction, redirect } from "@remix-run/node";
import config from "~/config";
import { authenticator } from "~/services/auth.server";
import { sentryException } from "~/shared/sentry";
import { ensureUserCookie } from "~/shared/session";
import { AUTH_PROVIDERS } from "~/shared/session/useLoginErrorMessage";
export default function Google() {
return null;
@@ -10,12 +12,30 @@ export default function Google() {
export const loader: LoaderFunction = () => redirect("/login");
export const action: ActionFunction = async ({ request }) => {
const { userId } = await ensureUserCookie(request);
return authenticator.authenticate("google", request, {
context: {
userId,
},
successRedirect: config.dashboardPath,
failureRedirect: config.loginPath,
});
try {
const { userId } = await ensureUserCookie(request);
return await authenticator.authenticate("google", request, {
context: {
userId,
},
successRedirect: config.dashboardPath,
throwOnError: true,
});
} catch (error: unknown) {
// all redirects are basically errors and in that case we don't want to catch it
if (error instanceof Response) return error;
if (error instanceof Error) {
sentryException({
message: error.message,
extra: {
loginMethod: AUTH_PROVIDERS.LOGIN_GOOGLE,
},
});
return redirect(
`${config.loginPath}?error=${AUTH_PROVIDERS.LOGIN_GOOGLE}&message=${
error?.message || ""
}`
);
}
}
};
+3 -1
View File
@@ -5,6 +5,7 @@ import config from "~/config";
import env from "~/env.server";
import { Login, links } from "~/auth/";
import { useLoginErrorMessage } from "~/shared/session/useLoginErrorMessage";
export { links };
@@ -18,7 +19,8 @@ export const loader: LoaderFunction = async ({ request }) => {
};
const LoginRoute = () => {
return <Login />;
const errorMessage = useLoginErrorMessage();
return <Login errorMessage={errorMessage} />;
};
export default LoginRoute;
+4 -3
View File
@@ -11,6 +11,7 @@ import {
createOrLoginWithGoogle,
} from "~/shared/db/user.server";
import { sentryException } from "~/shared/sentry";
import { AUTH_PROVIDERS } from "~/shared/session/useLoginErrorMessage";
const url = `${
process.env.VERCEL_URL
@@ -49,8 +50,8 @@ const google = new GoogleStrategy(
export const authenticator = new Authenticator<User>(sessionStorage);
if (process.env.GH_CLIENT_ID && process.env.GH_CLIENT_SECRET) {
authenticator.use(github, "github");
authenticator.use(google, "google");
}
authenticator.use(google, "google");
if (process.env.DEV_LOGIN === "true") {
authenticator.use(
@@ -66,14 +67,14 @@ if (process.env.DEV_LOGIN === "true") {
sentryException({
message: error.message,
extra: {
loginMethod: "dev",
loginMethod: AUTH_PROVIDERS.LOGIN_DEV,
},
});
}
}
}
throw new Error("Wrong code");
throw new Error("The dev login code is incorrect");
}),
"dev"
);
+15 -14
View File
@@ -1,16 +1,17 @@
import {
PrismaClient,
Prisma,
PrismaClientKnownRequestError,
} from "@webstudio-is/sdk/lib/prisma.server";
import { PrismaClient, Prisma } from "@prisma/client";
import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
declare global {
// allow global `var` declarations
// eslint-disable-next-line no-var
var prisma: PrismaClient | undefined;
}
// this fixes the issue with `warn(prisma-client) There are already 10 instances of Prisma Client actively running.`
// explanation here
// https://www.prisma.io/docs/guides/database/troubleshooting-orm/help-articles/nextjs-prisma-client-dev-practices
export const prisma = global.prisma || new PrismaClient();
if (process.env.NODE_ENV !== "production") global.prisma = prisma;
export const prisma = new PrismaClient();
export { Prisma, PrismaClientKnownRequestError };
const main = async () => {
await prisma.$connect();
};
main().catch((error) => {
throw error;
});
@@ -0,0 +1,60 @@
import isNil from "lodash/isNil";
import { useEffect, useState } from "react";
import { useSearchParams } from "react-router-dom";
import { sentryException } from "../sentry";
export const AUTH_PROVIDERS = {
LOGIN_DEV: "login_dev",
LOGIN_GITHUB: "login_github",
LOGIN_GOOGLE: "login_google",
};
export const LOGIN_ERROR_MESSAGES = {
[AUTH_PROVIDERS.LOGIN_DEV]: "There has been an issue logging you in with dev",
[AUTH_PROVIDERS.LOGIN_GITHUB]:
"There has been an issue logging you in with Github",
[AUTH_PROVIDERS.LOGIN_GOOGLE]:
"There has been an issue logging you in with Google",
};
export const useLoginErrorMessage = (): string => {
const [searchParams, setSearchParams] = useSearchParams();
const [messageToReturn, setMessageToReturn] = useState("");
useEffect(() => {
const error = searchParams.get("error");
const message = searchParams.get("message");
const hasMessageToShow =
error !== null && !isNil(message) && message !== "";
if (hasMessageToShow) {
sentryException({
message: message,
});
setMessageToReturn(message);
setSearchParams({});
return;
}
switch (error) {
case AUTH_PROVIDERS.LOGIN_DEV:
setMessageToReturn(LOGIN_ERROR_MESSAGES[AUTH_PROVIDERS.LOGIN_DEV]);
break;
case AUTH_PROVIDERS.LOGIN_GITHUB:
setMessageToReturn(LOGIN_ERROR_MESSAGES[AUTH_PROVIDERS.LOGIN_GITHUB]);
break;
case AUTH_PROVIDERS.LOGIN_GOOGLE:
setMessageToReturn(LOGIN_ERROR_MESSAGES[AUTH_PROVIDERS.LOGIN_GOOGLE]);
break;
default:
break;
}
setSearchParams({});
}, [searchParams, setSearchParams]);
return messageToReturn;
};
+10
View File
@@ -57,3 +57,13 @@
- Click on the canvas closes breakpoints selector
- Styles added on each breakpoint are only applied to each breakpoint, verify that by resizing the canvas in preview mode
- Resizing canvas width in preview mode doesn't limit resize slider to each breakpoint
1. Auth
- Try to access the dashboard when not logged in to make sure you will be redirected to the login page
- Open the login page when logged in to make sure you are redirected to the dashboard
- Ensure you can view a built site in an incognito tab
- Ensure you can view a forked site in an incognito tab
- Click on dev login, add the AUTH_SECRET value in the `.env` value and make sure user is authenticated and redirected to the dashboard
- Click on dev login and write a wrong value to make sure you get an error message and stay in the login page.
- Click on login with GitHub and make sure that you are redirected to the dashboard
- Click on login with Google and make sure that you are redirected to the dashboard