Skip to content

Commit

Permalink
passwordログイン
Browse files Browse the repository at this point in the history
  • Loading branch information
ttizze committed Jul 21, 2024
1 parent c6c9c17 commit 06d0cbb
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 26 deletions.
30 changes: 14 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,17 @@ EveEve(Everyone Translate Everything)は、インターネットに公開さ
cd web
bun i
```
3. googleログインの設定をする必要があります。
https://console.cloud.google.com/apis
設定方法は以下のページを参考にしてください
https://developers.google.com/identity/sign-in/web/sign-in?hl=ja
https://zenn.dev/yoiyoicho/articles/c44a80e4bb4515#google%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E3%81%AE%E5%85%AC%E5%BC%8F%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88%E3%82%92%E8%AA%AD%E3%82%80

承認済みのリダイレクトURIには
http://localhost:5173/api/auth/callback/google
を設定してください

クライアントIDとクライアントシークレットを取得してください

3. 環境変数ファイルを作成し、必要な値を設定します:
```
cp .env.example .env
```
`.env` ファイルを開き、以下の変数を適切な値に設定してください:
以下のコマンドを実行してください
```
openssl rand -base64 32
```
このコマンドで生成された文字列を`.env`ファイルの`SESSION_SECRET`に設定してください:
- SESSION_SECRET
- GOOGLE_CLIENT_ID
- GOOGLE_CLIENT_SECRET

4. dockerを起動します:
```
Expand All @@ -71,12 +62,19 @@ EveEve(Everyone Translate Everything)は、インターネットに公開さ
```
bunx prisma migrate dev
```
6. 起動します:
6. seedを実行します:
```
bun run seed
```
7. 起動します:
```
bun run dev
```
6. ブラウザで `http://localhost:5173` にアクセスして、eveeve を使用開始します。
6. ブラウザで `http://localhost:5173` にアクセスして、eveeve を使用開始します:
7. ローカル開発環境では、認証プロセスが簡略化されています:
- `http://localhost:5173/auth/login` にアクセスして、[email protected]とdevpasswordでログインしてください。

注意: この簡易認証は開発環境でのみ機能し、本番環境では無効になります。本番環境では通常のGoogle認証フローが使用されます。

## 貢献方法
翻訳、プログラミング、デザイン、ドキュメンテーションなど、あらゆる形の貢献を歓迎します。現在特に以下の分野での貢献を求めています:
Expand Down
98 changes: 92 additions & 6 deletions web/app/routes/auth.login.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
import { getFormProps, getInputProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { getZodConstraint } from "@conform-to/zod";
import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import { Form, useActionData } from "@remix-run/react";
import { z } from "zod";
import { Button } from "~/components/ui/button";
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from "~/components/ui/card";
import { Input } from "~/components/ui/input";
import { Label } from "~/components/ui/label";
import { Separator } from "~/components/ui/separator";
import { GoogleForm } from "../components/GoogleForm";
import { authenticator } from "../utils/auth.server";

const loginSchema = z.object({
email: z.string().email("有効なメールアドレスを入力してください"),
password: z.string().min(4, "パスワードは4文字以上である必要があります"),
});

export const loader = async ({ request }: LoaderFunctionArgs) => {
const safeUser = await authenticator.isAuthenticated(request, {
successRedirect: "/",
Expand All @@ -12,22 +32,88 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
};

export const action = async ({ request }: ActionFunctionArgs) => {
return authenticator.authenticate("google", request, {
successRedirect: "/",
failureRedirect: "/auth/login",
});
const formData = await request.clone().formData();
const intent = String(formData.get("intent"));
const submission = parseWithZod(formData, { schema: loginSchema });
try {
switch (intent) {
case "SignIn":
if (submission.status !== "success") {
return submission.reply();
}
return authenticator.authenticate("user-pass", request, {
successRedirect: "/",
});
case "SignInWithGoogle":
return authenticator.authenticate("google", request, {
successRedirect: "/",
failureRedirect: "/auth/login",
});
default:
return submission.reply({ formErrors: ["Invalid action"] });
}
} catch (error) {
return submission.reply({ formErrors: [error as string] });
}
};

const LoginPage = () => {
const lastResult = useActionData<typeof action>();
const [form, { email, password }] = useForm({
id: "login-form",
lastResult,
constraint: getZodConstraint(loginSchema),
shouldValidate: "onBlur",
shouldRevalidate: "onInput",
onValidate({ formData }) {
return parseWithZod(formData, { schema: loginSchema });
},
});

return (
<div className="container mx-auto max-w-md py-8">
<Card>
<CardHeader>
<CardTitle>Login</CardTitle>
</CardHeader>
<CardContent>
<GoogleForm />
<Form method="post" {...getFormProps(form)}>
{form.errors && (
<p className="text-red-500 text-center mt-2">invaild</p>
)}
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor={email.id}>Email</Label>
<Input {...getInputProps(email, { type: "email" })} />
{email.errors && (
<p className="text-sm text-red-500">{email.errors}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor={password.id}>Password</Label>
<Input {...getInputProps(password, { type: "password" })} />
{password.errors && (
<p className="text-sm text-red-500">{password.errors}</p>
)}
</div>
<Button
type="submit"
name="intent"
value="SignIn"
className="w-full"
>
Login
</Button>
</div>
</Form>
</CardContent>
<CardFooter className="flex flex-col space-y-4">
<Separator className="my-4" />
<div className="text-center text-sm text-gray-500 my-2">
Or continue with
</div>
<GoogleForm />
</CardFooter>
</Card>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion web/app/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import type { User } from "@prisma/client";

export type SafeUser = Omit<User, "password" | "geminiApiKey">;
export type SafeUser = Omit<User, "password" | "geminiApiKey" | "openAIApiKey" | "claudeApiKey">;
37 changes: 35 additions & 2 deletions web/app/utils/auth.server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Authenticator } from "remix-auth";
import bcrypt from "bcryptjs";
import { Authenticator, AuthorizationError } from "remix-auth";
import { FormStrategy } from "remix-auth-form";
import { GoogleStrategy } from "remix-auth-google";
import type { SafeUser } from "../types";
import { prisma } from "./prisma";
Expand All @@ -12,6 +14,37 @@ if (!SESSION_SECRET) {

const authenticator = new Authenticator<SafeUser>(sessionStorage);

const formStrategy = new FormStrategy(async ({ form }) => {
const email = form.get("email");
const password = form.get("password");

if (!(email && password)) {
throw new Error("Invalid Request");
}

const user = await prisma.user.findUnique({
where: { email: String(email) },
});
console.log(user);
if (!user) {
throw new AuthorizationError("User not found");
}

if (!user.password) {
throw new AuthorizationError("User has no password set.");
}
const passwordsMatch = await bcrypt.compare(String(password), user.password);

if (!passwordsMatch) {
throw new AuthorizationError("Invalid password");
}

const { password: _, geminiApiKey: __, openAIApiKey: ___, claudeApiKey: ____, ...safeUser } = user;
return safeUser;
});

authenticator.use(formStrategy, "user-pass");

const googleStrategy = new GoogleStrategy<SafeUser>(
{
clientID: process.env.GOOGLE_CLIENT_ID || "",
Expand All @@ -23,7 +56,7 @@ const googleStrategy = new GoogleStrategy<SafeUser>(
where: { email: profile.emails[0].value },
});
if (user) {
const { geminiApiKey, ...safeUser } = user;
const { password, geminiApiKey, openAIApiKey, claudeApiKey, ...safeUser } = user;
return safeUser as SafeUser;
}
try {
Expand Down
6 changes: 5 additions & 1 deletion web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
"dev": "remix vite:dev",
"start": "remix-serve ./build/server/index.js",
"typecheck": "tsc",
"check": "bunx @biomejs/biome check --write ."
"check": "bunx @biomejs/biome check --write .",
"seed": "NODE_ENV=development prisma db seed"
},
"prisma": {
"seed": "tsx prisma/seed.ts"
},
"dependencies": {
"@conform-to/react": "^1.1.5",
Expand Down
2 changes: 2 additions & 0 deletions web/prisma/migrations/20240721105034_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "users" ADD COLUMN "password" TEXT;
8 changes: 8 additions & 0 deletions web/prisma/migrations/20240721115647_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
Warnings:
- You are about to drop the column `password` on the `users` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "users" DROP COLUMN "password";
2 changes: 2 additions & 0 deletions web/prisma/migrations/20240721123812_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "users" ADD COLUMN "password" TEXT;
1 change: 1 addition & 0 deletions web/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ datasource db {
model User {
id Int @id @default(autoincrement())
email String @unique
password String?
name String
image String
plan String @default("free")
Expand Down
49 changes: 49 additions & 0 deletions web/prisma/seed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { PrismaClient } from '@prisma/client'
import bcrypt from 'bcryptjs'

const prisma = new PrismaClient()

async function seed() {
if (process.env.NODE_ENV !== 'development' && !process.env.ALLOW_SEEDING) {
console.log('Seeding is only allowed in development environment')
return
}

const email = '[email protected]'

// 既存のユーザーをチェック
const existingUser = await prisma.user.findUnique({
where: { email },
})

if (existingUser) {
console.log(`A user with email ${email} already exists`)
return
}

const hashedPassword = await bcrypt.hash('devpassword', 10)

const user = await prisma.user.create({
data: {
email,
name: 'Dev User',
password: hashedPassword,
image: '',
provider: 'password',
plan: 'free',
totalPoints: 0,
isAI: false,
},
})

console.log(`Created dev user with email: ${user.email}`)
}

seed()
.catch((e) => {
console.error(e)
process.exit(1)
})
.finally(async () => {
await prisma.$disconnect()
})

0 comments on commit 06d0cbb

Please sign in to comment.