mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-12 21:08:46 +00:00
/api/auth/login Endpoint 구현
This commit is contained in:
@@ -1 +1,7 @@
|
||||
DATABASE_URL=local.db
|
||||
# Required environment variables
|
||||
JWT_SECRET=
|
||||
|
||||
# Optional environment variables
|
||||
DATABASE_URL=
|
||||
JWT_ACCESS_TOKEN_EXPIRES=
|
||||
JWT_REFRESH_TOKEN_EXPIRES=
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||
"@types/better-sqlite3": "^7.6.11",
|
||||
"@types/jsonwebtoken": "^9.0.7",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"drizzle-kit": "^0.22.0",
|
||||
"eslint": "^9.7.0",
|
||||
@@ -39,7 +40,10 @@
|
||||
"vite": "^5.4.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"argon2": "^0.41.1",
|
||||
"better-sqlite3": "^11.1.2",
|
||||
"drizzle-orm": "^0.33.0"
|
||||
"drizzle-orm": "^0.33.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"zod": "^3.24.1"
|
||||
}
|
||||
}
|
||||
|
||||
117
pnpm-lock.yaml
generated
117
pnpm-lock.yaml
generated
@@ -5,12 +5,21 @@ settings:
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
dependencies:
|
||||
argon2:
|
||||
specifier: ^0.41.1
|
||||
version: 0.41.1
|
||||
better-sqlite3:
|
||||
specifier: ^11.1.2
|
||||
version: 11.7.0
|
||||
drizzle-orm:
|
||||
specifier: ^0.33.0
|
||||
version: 0.33.0(@types/better-sqlite3@7.6.12)(better-sqlite3@11.7.0)
|
||||
jsonwebtoken:
|
||||
specifier: ^9.0.2
|
||||
version: 9.0.2
|
||||
zod:
|
||||
specifier: ^3.24.1
|
||||
version: 3.24.1
|
||||
|
||||
devDependencies:
|
||||
'@eslint/compat':
|
||||
@@ -28,6 +37,9 @@ devDependencies:
|
||||
'@types/better-sqlite3':
|
||||
specifier: ^7.6.11
|
||||
version: 7.6.12
|
||||
'@types/jsonwebtoken':
|
||||
specifier: ^9.0.7
|
||||
version: 9.0.7
|
||||
autoprefixer:
|
||||
specifier: ^10.4.20
|
||||
version: 10.4.20(postcss@8.4.49)
|
||||
@@ -887,6 +899,11 @@ packages:
|
||||
fastq: 1.18.0
|
||||
dev: true
|
||||
|
||||
/@phc/format@1.0.0:
|
||||
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/@pkgjs/parseargs@0.11.0:
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -1205,6 +1222,12 @@ packages:
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
dev: true
|
||||
|
||||
/@types/jsonwebtoken@9.0.7:
|
||||
resolution: {integrity: sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==}
|
||||
dependencies:
|
||||
'@types/node': 22.10.2
|
||||
dev: true
|
||||
|
||||
/@types/node@22.10.2:
|
||||
resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==}
|
||||
dependencies:
|
||||
@@ -1399,6 +1422,16 @@ packages:
|
||||
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
|
||||
dev: true
|
||||
|
||||
/argon2@0.41.1:
|
||||
resolution: {integrity: sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==}
|
||||
engines: {node: '>=16.17.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@phc/format': 1.0.0
|
||||
node-addon-api: 8.3.0
|
||||
node-gyp-build: 4.8.4
|
||||
dev: false
|
||||
|
||||
/argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
dev: true
|
||||
@@ -1495,6 +1528,10 @@ packages:
|
||||
update-browserslist-db: 1.1.1(browserslist@4.24.3)
|
||||
dev: true
|
||||
|
||||
/buffer-equal-constant-time@1.0.1:
|
||||
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
|
||||
dev: false
|
||||
|
||||
/buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
dev: true
|
||||
@@ -1761,6 +1798,12 @@ packages:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
dev: true
|
||||
|
||||
/ecdsa-sig-formatter@1.0.11:
|
||||
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/electron-to-chromium@1.5.76:
|
||||
resolution: {integrity: sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==}
|
||||
dev: true
|
||||
@@ -2371,6 +2414,37 @@ packages:
|
||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||
dev: true
|
||||
|
||||
/jsonwebtoken@9.0.2:
|
||||
resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==}
|
||||
engines: {node: '>=12', npm: '>=6'}
|
||||
dependencies:
|
||||
jws: 3.2.2
|
||||
lodash.includes: 4.3.0
|
||||
lodash.isboolean: 3.0.3
|
||||
lodash.isinteger: 4.0.4
|
||||
lodash.isnumber: 3.0.3
|
||||
lodash.isplainobject: 4.0.6
|
||||
lodash.isstring: 4.0.1
|
||||
lodash.once: 4.1.1
|
||||
ms: 2.1.3
|
||||
semver: 7.6.3
|
||||
dev: false
|
||||
|
||||
/jwa@1.4.1:
|
||||
resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==}
|
||||
dependencies:
|
||||
buffer-equal-constant-time: 1.0.1
|
||||
ecdsa-sig-formatter: 1.0.11
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/jws@3.2.2:
|
||||
resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==}
|
||||
dependencies:
|
||||
jwa: 1.4.1
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/keyv@4.5.4:
|
||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||
dependencies:
|
||||
@@ -2419,10 +2493,38 @@ packages:
|
||||
p-locate: 5.0.0
|
||||
dev: true
|
||||
|
||||
/lodash.includes@4.3.0:
|
||||
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
|
||||
dev: false
|
||||
|
||||
/lodash.isboolean@3.0.3:
|
||||
resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
|
||||
dev: false
|
||||
|
||||
/lodash.isinteger@4.0.4:
|
||||
resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==}
|
||||
dev: false
|
||||
|
||||
/lodash.isnumber@3.0.3:
|
||||
resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==}
|
||||
dev: false
|
||||
|
||||
/lodash.isplainobject@4.0.6:
|
||||
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
|
||||
dev: false
|
||||
|
||||
/lodash.isstring@4.0.1:
|
||||
resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
|
||||
dev: false
|
||||
|
||||
/lodash.merge@4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
dev: true
|
||||
|
||||
/lodash.once@4.1.1:
|
||||
resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
|
||||
dev: false
|
||||
|
||||
/lru-cache@10.4.3:
|
||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||
dev: true
|
||||
@@ -2489,7 +2591,6 @@ packages:
|
||||
|
||||
/ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
dev: true
|
||||
|
||||
/mz@2.7.0:
|
||||
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
|
||||
@@ -2520,6 +2621,16 @@ packages:
|
||||
semver: 7.6.3
|
||||
dev: false
|
||||
|
||||
/node-addon-api@8.3.0:
|
||||
resolution: {integrity: sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==}
|
||||
engines: {node: ^18 || ^20 || >= 21}
|
||||
dev: false
|
||||
|
||||
/node-gyp-build@4.8.4:
|
||||
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/node-releases@2.0.19:
|
||||
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
|
||||
dev: true
|
||||
@@ -3411,3 +3522,7 @@ packages:
|
||||
/zimmerframe@1.1.2:
|
||||
resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==}
|
||||
dev: true
|
||||
|
||||
/zod@3.24.1:
|
||||
resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==}
|
||||
dev: false
|
||||
|
||||
27
src/lib/server/auth.ts
Normal file
27
src/lib/server/auth.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import argon2 from "argon2";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { getUserByEmail } from "$lib/server/db/user";
|
||||
import env from "$lib/server/loadenv";
|
||||
|
||||
const verifyPassword = async (hash: string, password: string) => {
|
||||
return await argon2.verify(hash, password);
|
||||
};
|
||||
|
||||
const issueToken = (id: number, type: "access" | "refresh") => {
|
||||
return jwt.sign({ id, type }, env.jwt.secret, {
|
||||
expiresIn: type === "access" ? env.jwt.accessExp : env.jwt.refreshExp,
|
||||
});
|
||||
};
|
||||
|
||||
export const login = async (email: string, password: string) => {
|
||||
const user = await getUserByEmail(email);
|
||||
if (!user) return null;
|
||||
|
||||
const valid = await verifyPassword(user.password, password);
|
||||
if (!valid) return null;
|
||||
|
||||
return {
|
||||
accessToken: issueToken(user.id, "access"),
|
||||
refreshToken: issueToken(user.id, "refresh"),
|
||||
};
|
||||
};
|
||||
7
src/lib/server/db/drizzle.ts
Normal file
7
src/lib/server/db/drizzle.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import Database from "better-sqlite3";
|
||||
import { drizzle } from "drizzle-orm/better-sqlite3";
|
||||
import env from "$lib/server/loadenv";
|
||||
|
||||
const client = new Database(env.databaseUrl);
|
||||
|
||||
export default drizzle(client);
|
||||
@@ -1,6 +0,0 @@
|
||||
import { drizzle } from "drizzle-orm/better-sqlite3";
|
||||
import Database from "better-sqlite3";
|
||||
import { env } from "$env/dynamic/private";
|
||||
if (!env.DATABASE_URL) throw new Error("DATABASE_URL is not set");
|
||||
const client = new Database(env.DATABASE_URL);
|
||||
export const db = drizzle(client);
|
||||
8
src/lib/server/db/user.ts
Normal file
8
src/lib/server/db/user.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import db from "./drizzle";
|
||||
import { user } from "./schema";
|
||||
|
||||
export const getUserByEmail = async (email: string) => {
|
||||
const users = await db.select().from(user).where(eq(user.email, email)).execute();
|
||||
return users[0] ?? null;
|
||||
};
|
||||
12
src/lib/server/loadenv.ts
Normal file
12
src/lib/server/loadenv.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { env } from "$env/dynamic/private";
|
||||
|
||||
if (!env.JWT_SECRET) throw new Error("JWT_SECRET is not set");
|
||||
|
||||
export default {
|
||||
databaseUrl: env.DATABASE_URL || "local.db",
|
||||
jwt: {
|
||||
secret: env.JWT_SECRET,
|
||||
accessExp: env.JWT_ACCESS_TOKEN_EXPIRES || "5m",
|
||||
refreshExp: env.JWT_REFRESH_TOKEN_EXPIRES || "14d",
|
||||
},
|
||||
};
|
||||
22
src/routes/api/auth/login/+server.ts
Normal file
22
src/routes/api/auth/login/+server.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { error, json } from "@sveltejs/kit";
|
||||
import { z } from "zod";
|
||||
import { login } from "$lib/server/auth";
|
||||
import type { RequestHandler } from "./$types";
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
const zodRes = z
|
||||
.object({
|
||||
email: z.string().email().nonempty(),
|
||||
password: z.string().nonempty(),
|
||||
})
|
||||
.safeParse(await request.json());
|
||||
if (!zodRes.success) error(400, zodRes.error.message);
|
||||
|
||||
const { email, password } = zodRes.data;
|
||||
const loginRes = await login(email.trim(), password.trim());
|
||||
|
||||
if (!loginRes) error(401, "Invalid email or password");
|
||||
const { accessToken, refreshToken } = loginRes;
|
||||
|
||||
return json({ accessToken, refreshToken });
|
||||
};
|
||||
@@ -9,6 +9,7 @@
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||
|
||||
Reference in New Issue
Block a user