mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-08 13:05:08 +09:00
Merge pull request #86 from webstudio-is/auth-small-fixes
This commit is contained in:
12 files changed
+198
-58
No files matched your search
+2
-1
@@ -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
|
||||
@@ -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
@@ -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
@@ -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 || ""
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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 || ""
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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 || ""
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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
|
||||
Reference in new issue
Block a user