1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
import { createUserSchema, loginSchema } from "@kioku/shared";
import * as argon2 from "argon2";
import { eq } from "drizzle-orm";
import { Hono } from "hono";
import { sign } from "hono/jwt";
import { db, users } from "../db";
import { Errors } from "../middleware";
const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET) {
throw new Error("JWT_SECRET environment variable is required");
}
const ACCESS_TOKEN_EXPIRES_IN = 60 * 15; // 15 minutes
const auth = new Hono();
auth.post("/register", async (c) => {
const body = await c.req.json();
const parsed = createUserSchema.safeParse(body);
if (!parsed.success) {
throw Errors.validationError(parsed.error.issues[0]?.message);
}
const { username, password } = parsed.data;
// Check if username already exists
const existingUser = await db
.select({ id: users.id })
.from(users)
.where(eq(users.username, username))
.limit(1);
if (existingUser.length > 0) {
throw Errors.conflict("Username already exists", "USERNAME_EXISTS");
}
// Hash password with Argon2
const passwordHash = await argon2.hash(password);
// Create user
const [newUser] = await db
.insert(users)
.values({
username,
passwordHash,
})
.returning({
id: users.id,
username: users.username,
createdAt: users.createdAt,
});
return c.json({ user: newUser }, 201);
});
auth.post("/login", async (c) => {
const body = await c.req.json();
const parsed = loginSchema.safeParse(body);
if (!parsed.success) {
throw Errors.validationError(parsed.error.issues[0]?.message);
}
const { username, password } = parsed.data;
// Find user by username
const [user] = await db
.select({
id: users.id,
username: users.username,
passwordHash: users.passwordHash,
})
.from(users)
.where(eq(users.username, username))
.limit(1);
if (!user) {
throw Errors.unauthorized(
"Invalid username or password",
"INVALID_CREDENTIALS",
);
}
// Verify password
const isPasswordValid = await argon2.verify(user.passwordHash, password);
if (!isPasswordValid) {
throw Errors.unauthorized(
"Invalid username or password",
"INVALID_CREDENTIALS",
);
}
// Generate JWT access token
const now = Math.floor(Date.now() / 1000);
const accessToken = await sign(
{
sub: user.id,
iat: now,
exp: now + ACCESS_TOKEN_EXPIRES_IN,
},
JWT_SECRET,
);
return c.json({
accessToken,
user: {
id: user.id,
username: user.username,
},
});
});
export { auth };
|