Skip to content

Commit 2a8ccee

Browse files
authored
Add Google Authentication (#65)
OAuth 2.0 を用いて簡単な認証を実装しました。\ .env の内容は Discord のプライベートチャンネルで共有したものを使ってください。\ また、ログインできるメールアドレスは、特に制限していないです。
1 parent 33bf8eb commit 2a8ccee

File tree

14 files changed

+361
-4
lines changed

14 files changed

+361
-4
lines changed

.env.sample

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,14 @@ SERVER_PORT=4000 # optional, server port (alias: PORT, PUBLIC_SERVER_URL.port) (
55

66
# Secrets
77
PUBLIC_X_ANON_KEY=...
8-
X_API_KEY=...
8+
X_API_KEY=...
9+
10+
DATABASE_URL=file:local.db
11+
12+
BETTER_AUTH_SECRET=...
13+
BETTER_AUTH_URL=http://localhost:4000
14+
15+
GOOGLE_CLIENT_ID=...
16+
GOOGLE_CLIENT_SECRET=
17+
18+
PUBLIC_WEB_URL=http://localhost:3000

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ dist
1212

1313
*storybook.log
1414
storybook-static
15+
16+
local.db

bun.lock

Lines changed: 176 additions & 1 deletion
Large diffs are not rendered by default.

packages/server/app.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
11
import { cors } from "@elysiajs/cors";
22
import { Elysia } from "elysia";
3+
import { auth } from "./lib/auth.ts";
34
import { usersRouter } from "./router/users.sample.ts";
45

6+
const betterAuth = new Elysia({ name: "better-auth" })
7+
.mount(auth.handler)
8+
.macro({
9+
auth: {
10+
async resolve({ status, request: { headers } }) {
11+
const session = await auth.api.getSession({
12+
headers,
13+
});
14+
if (!session) return status(401);
15+
16+
return {
17+
user: session.user,
18+
session: session.session,
19+
};
20+
},
21+
},
22+
});
23+
524
export const app = new Elysia({
625
prefix: "/api",
726
})
27+
.use(betterAuth)
828
.use(cors())
929
.group("/users", (app) => app.use(usersRouter));
1030

packages/server/db/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { drizzle } from "drizzle-orm/libsql";
2+
3+
export const db = drizzle({
4+
connection: { url: process.env.DATABASE_URL ?? "file:local.db" },
5+
});

packages/server/db/schema.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
2+
3+
// auth
4+
export const user = sqliteTable("users", {
5+
id: text("id").primaryKey(),
6+
name: text("name").notNull(),
7+
email: text("email").notNull().unique(),
8+
emailVerified: integer("email_verified", { mode: "boolean" })
9+
.$defaultFn(() => false)
10+
.notNull(),
11+
image: text("image"),
12+
createdAt: integer("created_at", { mode: "timestamp" })
13+
.$defaultFn(() => /* @__PURE__ */ new Date())
14+
.notNull(),
15+
updatedAt: integer("updated_at", { mode: "timestamp" })
16+
.$defaultFn(() => /* @__PURE__ */ new Date())
17+
.notNull(),
18+
});
19+
20+
export const session = sqliteTable("sessions", {
21+
id: text("id").primaryKey(),
22+
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
23+
token: text("token").notNull().unique(),
24+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
25+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
26+
ipAddress: text("ip_address"),
27+
userAgent: text("user_agent"),
28+
userId: text("user_id")
29+
.notNull()
30+
.references(() => user.id, { onDelete: "cascade" }),
31+
});
32+
33+
export const account = sqliteTable("accounts", {
34+
id: text("id").primaryKey(),
35+
accountId: text("account_id").notNull(),
36+
providerId: text("provider_id").notNull(),
37+
userId: text("user_id")
38+
.notNull()
39+
.references(() => user.id, { onDelete: "cascade" }),
40+
accessToken: text("access_token"),
41+
refreshToken: text("refresh_token"),
42+
idToken: text("id_token"),
43+
accessTokenExpiresAt: integer("access_token_expires_at", {
44+
mode: "timestamp",
45+
}),
46+
refreshTokenExpiresAt: integer("refresh_token_expires_at", {
47+
mode: "timestamp",
48+
}),
49+
scope: text("scope"),
50+
password: text("password"),
51+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
52+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
53+
});
54+
55+
// export const verification = sqliteTable("verifications", {
56+
// id: text("id").primaryKey(),
57+
// identifier: text("identifier").notNull(),
58+
// value: text("value").notNull(),
59+
// expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
60+
// createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(
61+
// () => /* @__PURE__ */ new Date(),
62+
// ),
63+
// updatedAt: integer("updated_at", { mode: "timestamp" }).$defaultFn(
64+
// () => /* @__PURE__ */ new Date(),
65+
// ),
66+
// });

packages/server/drizzle.config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { defineConfig } from "drizzle-kit";
2+
3+
export default defineConfig({
4+
out: "./drizzle",
5+
schema: "./db/schema.ts",
6+
dialect: "sqlite",
7+
dbCredentials: {
8+
url: process.env.DATABASE_URL ?? "file:local.db",
9+
},
10+
});

packages/server/lib/auth.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { betterAuth } from "better-auth";
2+
import { drizzleAdapter } from "better-auth/adapters/drizzle";
3+
import { db } from "../db/index.ts";
4+
import * as schema from "../db/schema.ts";
5+
6+
export const auth = betterAuth({
7+
database: drizzleAdapter(db, {
8+
provider: "sqlite",
9+
schema: schema,
10+
}),
11+
socialProviders: {
12+
google: {
13+
clientId: process.env.GOOGLE_CLIENT_ID as string,
14+
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
15+
},
16+
},
17+
trustedOrigins: [process.env.PUBLIC_WEB_URL ?? "http://localhost:3000"],
18+
});

packages/server/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,19 @@
1010
"dev": "bun --env-file=../../.env run --watch ./serve.ts"
1111
},
1212
"devDependencies": {
13-
"@types/bun": "^1.2.18"
13+
"@types/bun": "^1.2.18",
14+
"drizzle-kit": "^0.31.4",
15+
"tsx": "^4.20.3"
1416
},
1517
"peerDependencies": {
1618
"typescript": "^5.8.3"
1719
},
1820
"dependencies": {
1921
"@elysiajs/cors": "^1.3.3",
22+
"@libsql/client": "^0.15.10",
2023
"@sinclair/typebox": "^0.34.37",
24+
"better-auth": "^1.3.1",
25+
"drizzle-orm": "^0.44.3",
2126
"elysia": "^1.3.5"
2227
}
2328
}

packages/web/src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import Home from "./pages/Home.tsx";
1414
import HowToUse from "./pages/HowToUse.tsx";
1515
import NotFound from "./pages/NotFound.tsx";
1616
import Notion from "./pages/Notion.tsx";
17+
import Profile from "./pages/Profile.tsx";
18+
import SignIn from "./pages/SignIn.tsx";
1719
import UserManagement from "./sample/UserManagement.tsx";
1820

1921
/**
@@ -65,6 +67,8 @@ export default function App() {
6567
<Route path="/how-to-use" element={<HowToUse />} />
6668
<Route path="/notion" element={<Notion />} />
6769
<Route path="/sample" element={<UserManagement />} />
70+
<Route path="/sign-in" element={<SignIn />} />
71+
<Route path="/profile" element={<Profile />} />
6872
<Route path="*" element={<NotFound />} />
6973
</Routes>
7074
<Footer />

0 commit comments

Comments
 (0)