Skip to content

Commit 8f90fc0

Browse files
committed
impl lucia-auth
1 parent 1b28d65 commit 8f90fc0

17 files changed

Lines changed: 1152 additions & 110 deletions

File tree

apps/web/components/Link.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { usePageContext } from "$/context/pageContext"
2+
import { ElementProps, unwrap } from "kaioken"
23

3-
export function Link({ href, children }: { href: string; children: string }) {
4+
export function Link({ href, children, ...rest }: ElementProps<"a">) {
45
const { urlPathname } = usePageContext()
6+
const _href = unwrap(href as any)
57
const isActive =
6-
href === "/" ? urlPathname === href : urlPathname.startsWith(href)
8+
_href === "/" ? urlPathname === _href : urlPathname.startsWith(_href)
79
return (
8-
<a href={href} className={isActive ? "is-active" : undefined}>
10+
<a href={href} className={isActive ? "is-active" : undefined} {...rest}>
911
{children}
1012
</a>
1113
)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { dbSqlite } from "../db"
2+
import { oauthAccountTable, userTable } from "../schema/lucia-auth"
3+
import { and, eq } from "drizzle-orm"
4+
5+
export async function getExistingUser(
6+
db: ReturnType<typeof dbSqlite>,
7+
username: string
8+
) {
9+
return db
10+
.select()
11+
.from(userTable)
12+
.where(eq(userTable.username, username))
13+
.get()
14+
}
15+
16+
export async function getExistingAccount(
17+
db: ReturnType<typeof dbSqlite>,
18+
providerId: string,
19+
providerUserId: number
20+
) {
21+
return db
22+
.select()
23+
.from(oauthAccountTable)
24+
.where(
25+
and(
26+
eq(oauthAccountTable.providerId, providerId),
27+
eq(oauthAccountTable.providerUserId, providerUserId)
28+
)
29+
)
30+
.get()
31+
}
32+
33+
export async function signupWithGithub(
34+
db: ReturnType<typeof dbSqlite>,
35+
userId: string,
36+
username: string,
37+
githubUserId: number
38+
) {
39+
return db.transaction(async (tx) => {
40+
await tx.insert(userTable).values({ id: userId, username: username })
41+
await tx
42+
.insert(oauthAccountTable)
43+
.values({ providerId: "github", providerUserId: githubUserId, userId })
44+
})
45+
}
46+
47+
export async function signupWithCredentials(
48+
db: ReturnType<typeof dbSqlite>,
49+
userId: string,
50+
username: string,
51+
passwordHash: string
52+
) {
53+
return db
54+
.insert(userTable)
55+
.values({ id: userId, username, password: passwordHash })
56+
.run()
57+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { int, integer, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core";
2+
3+
export const userTable = sqliteTable("users", {
4+
id: text("id").notNull().primaryKey(),
5+
username: text("username").notNull().unique(),
6+
password: text("password"),
7+
});
8+
9+
export const oauthAccountTable = sqliteTable(
10+
"oauth_accounts",
11+
{
12+
providerId: text("provider_id").notNull(),
13+
providerUserId: int("provider_user_id").notNull(),
14+
userId: text("user_id")
15+
.notNull()
16+
.references(() => userTable.id, {
17+
onUpdate: "cascade",
18+
onDelete: "cascade",
19+
}),
20+
},
21+
(table) => {
22+
return {
23+
pk: primaryKey({ columns: [table.providerId, table.providerUserId] }),
24+
};
25+
},
26+
);
27+
28+
export const sessionTable = sqliteTable("sessions", {
29+
id: text("id").notNull().primaryKey(),
30+
userId: text("user_id")
31+
.notNull()
32+
.references(() => userTable.id, {
33+
onUpdate: "cascade",
34+
onDelete: "cascade",
35+
}),
36+
expiresAt: integer("expires_at").notNull(),
37+
});
38+
39+
export type UserItem = typeof userTable.$inferSelect;
40+
export type UserInsert = typeof userTable.$inferInsert;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
CREATE TABLE `oauth_accounts` (
2+
`provider_id` text NOT NULL,
3+
`provider_user_id` integer NOT NULL,
4+
`user_id` text NOT NULL,
5+
PRIMARY KEY(`provider_id`, `provider_user_id`),
6+
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE cascade ON DELETE cascade
7+
);
8+
--> statement-breakpoint
9+
CREATE TABLE `sessions` (
10+
`id` text PRIMARY KEY NOT NULL,
11+
`user_id` text NOT NULL,
12+
`expires_at` integer NOT NULL,
13+
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE cascade ON DELETE cascade
14+
);
15+
--> statement-breakpoint
16+
CREATE TABLE `users` (
17+
`id` text PRIMARY KEY NOT NULL,
18+
`username` text NOT NULL,
19+
`password` text
20+
);
21+
--> statement-breakpoint
22+
CREATE UNIQUE INDEX `users_username_unique` ON `users` (`username`);
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
{
2+
"version": "6",
3+
"dialect": "sqlite",
4+
"id": "eee957d5-2346-4c7c-afef-30f6db86e5ed",
5+
"prevId": "11924cce-fc65-4ae1-bb84-a39756b19cc4",
6+
"tables": {
7+
"oauth_accounts": {
8+
"name": "oauth_accounts",
9+
"columns": {
10+
"provider_id": {
11+
"name": "provider_id",
12+
"type": "text",
13+
"primaryKey": false,
14+
"notNull": true,
15+
"autoincrement": false
16+
},
17+
"provider_user_id": {
18+
"name": "provider_user_id",
19+
"type": "integer",
20+
"primaryKey": false,
21+
"notNull": true,
22+
"autoincrement": false
23+
},
24+
"user_id": {
25+
"name": "user_id",
26+
"type": "text",
27+
"primaryKey": false,
28+
"notNull": true,
29+
"autoincrement": false
30+
}
31+
},
32+
"indexes": {},
33+
"foreignKeys": {
34+
"oauth_accounts_user_id_users_id_fk": {
35+
"name": "oauth_accounts_user_id_users_id_fk",
36+
"tableFrom": "oauth_accounts",
37+
"tableTo": "users",
38+
"columnsFrom": [
39+
"user_id"
40+
],
41+
"columnsTo": [
42+
"id"
43+
],
44+
"onDelete": "cascade",
45+
"onUpdate": "cascade"
46+
}
47+
},
48+
"compositePrimaryKeys": {
49+
"oauth_accounts_provider_id_provider_user_id_pk": {
50+
"columns": [
51+
"provider_id",
52+
"provider_user_id"
53+
],
54+
"name": "oauth_accounts_provider_id_provider_user_id_pk"
55+
}
56+
},
57+
"uniqueConstraints": {},
58+
"checkConstraints": {}
59+
},
60+
"sessions": {
61+
"name": "sessions",
62+
"columns": {
63+
"id": {
64+
"name": "id",
65+
"type": "text",
66+
"primaryKey": true,
67+
"notNull": true,
68+
"autoincrement": false
69+
},
70+
"user_id": {
71+
"name": "user_id",
72+
"type": "text",
73+
"primaryKey": false,
74+
"notNull": true,
75+
"autoincrement": false
76+
},
77+
"expires_at": {
78+
"name": "expires_at",
79+
"type": "integer",
80+
"primaryKey": false,
81+
"notNull": true,
82+
"autoincrement": false
83+
}
84+
},
85+
"indexes": {},
86+
"foreignKeys": {
87+
"sessions_user_id_users_id_fk": {
88+
"name": "sessions_user_id_users_id_fk",
89+
"tableFrom": "sessions",
90+
"tableTo": "users",
91+
"columnsFrom": [
92+
"user_id"
93+
],
94+
"columnsTo": [
95+
"id"
96+
],
97+
"onDelete": "cascade",
98+
"onUpdate": "cascade"
99+
}
100+
},
101+
"compositePrimaryKeys": {},
102+
"uniqueConstraints": {},
103+
"checkConstraints": {}
104+
},
105+
"users": {
106+
"name": "users",
107+
"columns": {
108+
"id": {
109+
"name": "id",
110+
"type": "text",
111+
"primaryKey": true,
112+
"notNull": true,
113+
"autoincrement": false
114+
},
115+
"username": {
116+
"name": "username",
117+
"type": "text",
118+
"primaryKey": false,
119+
"notNull": true,
120+
"autoincrement": false
121+
},
122+
"password": {
123+
"name": "password",
124+
"type": "text",
125+
"primaryKey": false,
126+
"notNull": false,
127+
"autoincrement": false
128+
}
129+
},
130+
"indexes": {
131+
"users_username_unique": {
132+
"name": "users_username_unique",
133+
"columns": [
134+
"username"
135+
],
136+
"isUnique": true
137+
}
138+
},
139+
"foreignKeys": {},
140+
"compositePrimaryKeys": {},
141+
"uniqueConstraints": {},
142+
"checkConstraints": {}
143+
},
144+
"todos": {
145+
"name": "todos",
146+
"columns": {
147+
"id": {
148+
"name": "id",
149+
"type": "integer",
150+
"primaryKey": true,
151+
"notNull": true,
152+
"autoincrement": true
153+
},
154+
"text": {
155+
"name": "text",
156+
"type": "text(50)",
157+
"primaryKey": false,
158+
"notNull": true,
159+
"autoincrement": false
160+
}
161+
},
162+
"indexes": {},
163+
"foreignKeys": {},
164+
"compositePrimaryKeys": {},
165+
"uniqueConstraints": {},
166+
"checkConstraints": {}
167+
}
168+
},
169+
"views": {},
170+
"enums": {},
171+
"_meta": {
172+
"schemas": {},
173+
"tables": {},
174+
"columns": {}
175+
},
176+
"internal": {
177+
"indexes": {}
178+
}
179+
}

apps/web/database/migrations/meta/_journal.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
"when": 1742296240256,
99
"tag": "0000_sharp_quicksilver",
1010
"breakpoints": true
11+
},
12+
{
13+
"idx": 1,
14+
"version": "6",
15+
"when": 1742340174381,
16+
"tag": "0001_loose_mimic",
17+
"breakpoints": true
1118
}
1219
]
1320
}

apps/web/global.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Session } from "@auth/core/types"
1+
import { User } from "lucia"
22
import { dbSqlite } from "./database/drizzle/db"
33

44
//https://vike.dev/pageContext#custom
@@ -34,7 +34,7 @@ declare module "telefunc" {
3434
declare global {
3535
namespace Vike {
3636
interface PageContext {
37-
session?: Session | null
37+
user?: User
3838
}
3939
}
4040
}

apps/web/hono-entry.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
import "dotenv/config"
2-
import { authjsHandler, authjsSessionMiddleware } from "./server/authjs-handler"
2+
import {
3+
luciaAuthContextMiddleware,
4+
luciaAuthCookieMiddleware,
5+
luciaAuthLoginHandler,
6+
luciaAuthLogoutHandler,
7+
luciaAuthSignupHandler,
8+
luciaCsrfMiddleware,
9+
luciaDbMiddleware,
10+
luciaGithubCallbackHandler,
11+
luciaGithubLoginHandler,
12+
} from "./server/lucia-auth-handlers"
313

414
import { vikeHandler } from "./server/vike-handler"
515
import { telefuncHandler } from "./server/telefunc-handler"
@@ -11,13 +21,19 @@ const app = new Hono()
1121

1222
app.use(createMiddleware(dbMiddleware)())
1323

14-
app.use(createMiddleware(authjsSessionMiddleware)())
15-
16-
/**
17-
* Auth.js route
18-
* @link {@see https://authjs.dev/getting-started/installation}
19-
**/
20-
app.use("/api/auth/**", createHandler(authjsHandler)())
24+
app.use(createMiddleware(luciaDbMiddleware)())
25+
app.use(createMiddleware(luciaCsrfMiddleware)())
26+
app.use(createMiddleware(luciaAuthContextMiddleware)())
27+
app.use(createMiddleware(luciaAuthCookieMiddleware)())
28+
29+
app.post("/api/signup", createHandler(luciaAuthSignupHandler)())
30+
app.post("/api/login", createHandler(luciaAuthLoginHandler)())
31+
app.post("/api/logout", createHandler(luciaAuthLogoutHandler)())
32+
app.get("/api/login/github", createHandler(luciaGithubLoginHandler)())
33+
app.get(
34+
"/api/login/github/callback",
35+
createHandler(luciaGithubCallbackHandler)()
36+
)
2137

2238
app.post("/_telefunc", createHandler(telefuncHandler)())
2339

0 commit comments

Comments
 (0)