diff --git a/app/config.ts b/app/config.ts
index 2bf8a9d36f..e14703c20c 100644
--- a/app/config.ts
+++ b/app/config.ts
@@ -3,6 +3,8 @@ const config = {
canvasPath: "/canvas",
designerPath: "/designer",
dashboardPath: "/dashboard",
+ loginPath: "/login",
+ logoutPath: "/logout",
};
export default config;
export type Config = typeof config;
diff --git a/app/critical-css.ts b/app/critical-css.ts
index a10c10bf04..06e22d2d86 100644
--- a/app/critical-css.ts
+++ b/app/critical-css.ts
@@ -10,6 +10,7 @@ const getCssTextFunctions = {
[config.canvasPath]: getCanvasCssText,
[config.designerPath]: getDesignerCssText,
[config.dashboardPath]: getDesignerCssText,
+ [config.loginPath]: getDesignerCssText,
};
const getCssTextFunction = (url: string) => {
diff --git a/app/routes/auth/github.tsx b/app/routes/auth/github.tsx
index f0b627356c..5b5cfd855c 100644
--- a/app/routes/auth/github.tsx
+++ b/app/routes/auth/github.tsx
@@ -1,8 +1,10 @@
-import { ActionFunction, LoaderFunction, redirect } from "@remix-run/node";
+import { ActionFunction } from "@remix-run/node";
+import config from "~/config";
import { authenticator } from "~/services/auth.server";
-export const loader: LoaderFunction = () => redirect("/login");
-
export const action: ActionFunction = ({ request }) => {
- return authenticator.authenticate("github", request);
+ return authenticator.authenticate("github", request, {
+ successRedirect: config.dashboardPath,
+ failureRedirect: config.loginPath,
+ });
};
diff --git a/app/routes/auth/github/callback.tsx b/app/routes/auth/github/callback.tsx
index 5b77ad6add..3b821aaeb3 100644
--- a/app/routes/auth/github/callback.tsx
+++ b/app/routes/auth/github/callback.tsx
@@ -1,9 +1,10 @@
import { LoaderFunction } from "@remix-run/node";
+import config from "~/config";
import { authenticator } from "~/services/auth.server";
export const loader: LoaderFunction = ({ request }) => {
return authenticator.authenticate("github", request, {
- successRedirect: "/dashboard",
- failureRedirect: "/login",
+ successRedirect: config.dashboardPath,
+ failureRedirect: config.loginPath,
});
};
diff --git a/app/routes/auth/google.tsx b/app/routes/auth/google.tsx
new file mode 100644
index 0000000000..c45f660304
--- /dev/null
+++ b/app/routes/auth/google.tsx
@@ -0,0 +1,8 @@
+import { ActionFunction, LoaderFunction, redirect } from "@remix-run/node";
+import { authenticator } from "~/services/auth.server";
+
+export const loader: LoaderFunction = () => redirect("/login");
+
+export const action: ActionFunction = ({ request }) => {
+ return authenticator.authenticate("google", request);
+};
diff --git a/app/routes/auth/google/callback.tsx b/app/routes/auth/google/callback.tsx
new file mode 100644
index 0000000000..adbafcf44e
--- /dev/null
+++ b/app/routes/auth/google/callback.tsx
@@ -0,0 +1,10 @@
+import { LoaderFunction } from "@remix-run/node";
+import config from "~/config";
+import { authenticator } from "~/services/auth.server";
+
+export const loader: LoaderFunction = ({ request }) => {
+ return authenticator.authenticate("google", request, {
+ successRedirect: config.dashboardPath,
+ failureRedirect: config.loginPath,
+ });
+};
diff --git a/app/routes/dashboard/index.tsx b/app/routes/dashboard/index.tsx
index 886434ee40..9345c2f584 100644
--- a/app/routes/dashboard/index.tsx
+++ b/app/routes/dashboard/index.tsx
@@ -31,9 +31,9 @@ export const action: ActionFunction = async ({ request }) => {
export const loader: LoaderFunction = async ({ request }) => {
const user = await authenticator.isAuthenticated(request);
- console.log(user);
+ console.log(user)
if (!user) {
- return redirect("/login");
+ return redirect(config.loginPath);
}
const { userId, headers } = await ensureUserCookie(request);
const projects = await db.project.loadManyByUserId(userId);
diff --git a/app/routes/login.tsx b/app/routes/login.tsx
index f1e543c7b4..f1efd71239 100644
--- a/app/routes/login.tsx
+++ b/app/routes/login.tsx
@@ -1,9 +1,92 @@
+import { Links, LiveReload, Meta, Scripts } from "@remix-run/react";
+import {
+ LinksFunction,
+ LoaderFunction,
+ MetaFunction,
+ redirect,
+} from "@remix-run/node";
+import { CriticalCss } from "@webstudio-is/sdk";
+import { darkTheme } from "~/shared/design-system";
import { Form } from "@remix-run/react";
+import { Button, Card, Flex, Heading } from "~/shared/design-system";
+import interStyles from "~/shared/font-faces/inter.css";
-export default function Login() {
+import { GoogleIcon, GithubIcon } from "~/shared/icons";
+import { authenticator } from "~/services/auth.server";
+import config from "~/config";
+
+export const links: LinksFunction = () => {
+ return [
+ {
+ rel: "stylesheet",
+ href: interStyles,
+ },
+ ];
+};
+
+export const meta: MetaFunction = () => {
+ return { title: "Webstudio Login" };
+};
+
+const RootDesignerRoute = () => {
return (
-
+
+
+
+
+
+
+
+
+
+
+
+
+ Login
+
+
+
+
+
+
+
+
+
+ {process.env.NODE_ENV === "development" && }
+
+
);
-}
+};
+
+export const loader: LoaderFunction = async ({ request }) => {
+ const user = await authenticator.isAuthenticated(request);
+ if (user) {
+ return redirect(config.dashboardPath);
+ }
+
+ return {};
+};
+
+export default RootDesignerRoute;
diff --git a/app/services/auth.server.ts b/app/services/auth.server.ts
index 4c4ebb7214..d7cf054928 100644
--- a/app/services/auth.server.ts
+++ b/app/services/auth.server.ts
@@ -1,28 +1,49 @@
import { User } from "@prisma/client";
import { Authenticator } from "remix-auth";
import { GitHubStrategy } from "remix-auth-github";
+import { GoogleStrategy } from "remix-auth-google";
import { sessionStorage } from "~/services/session.server";
-import { createOrLoginWithGithub } from "~/shared/db/user.server";
+import {
+ createOrLoginWithGithub,
+ createOrLoginWithGoogle,
+} from "~/shared/db/user.server";
+
+const url = `${
+ process.env.VERCEL_URL
+ ? `https://${process.env.VERCEL_URL}`
+ : "http://localhost:3000"
+}/auth`;
+
+const github = new GitHubStrategy(
+ {
+ clientID: process.env.GH_CLIENT_ID as string,
+ clientSecret: process.env.GH_CLIENT_SECRET as string,
+ callbackURL: `${url}/github/callback`,
+ },
+ async ({ profile }) => {
+ const user = createOrLoginWithGithub(profile);
+
+ return user;
+ }
+);
+
+const google = new GoogleStrategy(
+ {
+ clientID: "YOUR_CLIENT_ID",
+ clientSecret: "YOUR_CLIENT_SECRET",
+ callbackURL: `${url}/google/callback`,
+ },
+ async ({ profile }) => {
+ const user = createOrLoginWithGoogle(profile);
+
+ return user;
+ }
+);
// Create an instance of the authenticator, pass a generic with what
// strategies will return and will store in the session
export const authenticator = new Authenticator(sessionStorage);
if (process.env.GH_CLIENT_ID && process.env.GH_CLIENT_SECRET) {
- authenticator.use(
- new GitHubStrategy(
- {
- clientID: process.env.GH_CLIENT_ID as string,
- clientSecret: process.env.GH_CLIENT_SECRET as string,
- callbackURL: `https://${
- process.env.VERCEL_URL || "192.168.1.91:3000"
- }/auth/github/callback`,
- },
- async ({ profile }) => {
- const user = createOrLoginWithGithub(profile);
-
- return user;
- }
- ),
- "github"
- );
+ authenticator.use(github, "github");
+ authenticator.use(google, "google");
}
diff --git a/app/shared/db/user.server.ts b/app/shared/db/user.server.ts
index eddd8633a0..1965064bd7 100644
--- a/app/shared/db/user.server.ts
+++ b/app/shared/db/user.server.ts
@@ -1,5 +1,7 @@
import { User } from "@prisma/client";
import { GitHubProfile } from "remix-auth-github";
+import { GoogleProfile } from "remix-auth-google";
+import { ensureUserCookie } from "../session";
import { prisma } from "./prisma.server";
export const createOrLoginWithGithub = async (
@@ -24,3 +26,26 @@ export const createOrLoginWithGithub = async (
return newUser;
};
+
+export const createOrLoginWithGoogle = async (
+ profile: GoogleProfile
+): Promise => {
+ const user = await prisma.user.findUnique({
+ where: { email: profile._json.email },
+ });
+
+ if (user) {
+ return user;
+ }
+
+ const newUser = prisma.user.create({
+ data: {
+ email: profile._json.email,
+ username: profile.displayName,
+ image: profile._json.avatar_url,
+ provider: profile.provider,
+ },
+ });
+
+ return newUser;
+};
diff --git a/app/shared/icons/github.tsx b/app/shared/icons/github.tsx
new file mode 100644
index 0000000000..734c467c7f
--- /dev/null
+++ b/app/shared/icons/github.tsx
@@ -0,0 +1,24 @@
+import * as React from "react";
+import { IconProps } from "./types";
+
+export const GithubIcon = React.forwardRef(
+ ({ color = "currentColor", ...props }, forwardedRef) => {
+ return (
+
+ );
+ }
+);
+
+GithubIcon.displayName = "GithubIcon";
diff --git a/app/shared/icons/google.tsx b/app/shared/icons/google.tsx
new file mode 100644
index 0000000000..f1312f932c
--- /dev/null
+++ b/app/shared/icons/google.tsx
@@ -0,0 +1,24 @@
+import * as React from "react";
+import { IconProps } from "./types";
+
+export const GoogleIcon = React.forwardRef(
+ ({ color = "currentColor", ...props }, forwardedRef) => {
+ return (
+
+ );
+ }
+);
+
+GoogleIcon.displayName = "GoogleIcon";
diff --git a/app/shared/icons/index.ts b/app/shared/icons/index.ts
index b6af274862..09c273be06 100644
--- a/app/shared/icons/index.ts
+++ b/app/shared/icons/index.ts
@@ -4,6 +4,8 @@ export * from "./brush";
export * from "./form";
export * from "./undo";
export * from "./redo";
+export * from "./google";
+export * from "./github";
export * from "./devices";
export * from "./tablet";
export * from "./types";
diff --git a/package.json b/package.json
index 29ba5b21e5..1ca42def4c 100644
--- a/package.json
+++ b/package.json
@@ -72,6 +72,7 @@
"react-use": "^17.3.2",
"remix-auth": "^3.2.2",
"remix-auth-github": "^1.1.1",
+ "remix-auth-google": "^1.1.0",
"slugify": "^1.6.5"
},
"devDependencies": {
diff --git a/prisma/migrations/20220611090740_/migration.sql b/prisma/migrations/20220611090740_/migration.sql
new file mode 100644
index 0000000000..9449a3e7dc
--- /dev/null
+++ b/prisma/migrations/20220611090740_/migration.sql
@@ -0,0 +1,19 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `createdAt` on the `User` table. All the data in the column will be lost.
+ - You are about to drop the column `email` on the `User` table. All the data in the column will be lost.
+ - You are about to drop the column `image` on the `User` table. All the data in the column will be lost.
+ - You are about to drop the column `provider` on the `User` table. All the data in the column will be lost.
+ - You are about to drop the column `username` on the `User` table. All the data in the column will be lost.
+
+*/
+-- DropIndex
+DROP INDEX "User_email_key";
+
+-- AlterTable
+ALTER TABLE "User" DROP COLUMN "createdAt",
+DROP COLUMN "email",
+DROP COLUMN "image",
+DROP COLUMN "provider",
+DROP COLUMN "username";
diff --git a/prisma/migrations/20220611091346_add_email/migration.sql b/prisma/migrations/20220611091346_add_email/migration.sql
new file mode 100644
index 0000000000..2640ef8e8e
--- /dev/null
+++ b/prisma/migrations/20220611091346_add_email/migration.sql
@@ -0,0 +1,15 @@
+/*
+ Warnings:
+
+ - A unique constraint covering the columns `[email]` on the table `User` will be added. If there are existing duplicate values, this will fail.
+
+*/
+-- AlterTable
+ALTER TABLE "User" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ADD COLUMN "email" TEXT,
+ADD COLUMN "image" TEXT,
+ADD COLUMN "provider" TEXT,
+ADD COLUMN "username" TEXT;
+
+-- CreateIndex
+CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index f2c4830093..1eda4ff425 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -10,6 +10,16 @@ datasource db {
url = env("DATABASE_URL")
}
+model User {
+ id String @id @default(uuid())
+ email String? @unique
+ provider String?
+ image String?
+ username String?
+ createdAt DateTime @default(now())
+ projects Project[]
+}
+
model Project {
id String @id @default(uuid())
userId String
@@ -18,10 +28,7 @@ model Project {
prodTreeId String? // exists when published
devTreeId String
prodTreeIdHistory String @default("[]")
-}
-
-model User {
- id String @id @default(uuid())
+ User User @relation(fields: [userId], references: [id])
}
model Tree {
diff --git a/yarn.lock b/yarn.lock
index 16f46f60fa..ef6c2269e0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -12144,7 +12144,14 @@ remix-auth-github@^1.1.1:
remix-auth "^3.0.0"
remix-auth-oauth2 "^1.0.0"
-remix-auth-oauth2@^1.0.0:
+remix-auth-google@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/remix-auth-google/-/remix-auth-google-1.1.0.tgz#4ffdc41b180a7a622d9008e7a25b6e409dbf5f20"
+ integrity sha512-yzZ8JEiSoY/cvo5eJQBIzqpnaWOjQkYCgBklCP1QwT01sdR2F066uhV+jR9Wcx0bPCntdMG4PrytVBJzWTg+BA==
+ dependencies:
+ remix-auth-oauth2 "^1.1.0"
+
+remix-auth-oauth2@^1.0.0, remix-auth-oauth2@^1.1.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/remix-auth-oauth2/-/remix-auth-oauth2-1.2.2.tgz#bebe0ac586744ed94885bd47f6bcb710d377fede"
integrity sha512-bbQw/SFAbilSE1zrZFaJN/NM7WUkC63CKJFVz2e/Y21qqMZo5eVQXooSV6d23mABfodeVk6wthngdLAjxak6fQ==