mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-15 22:38:47 +00:00
/api/auth/changePassword, /api/user, /api/user/changeNickname Endpoint 구현
This commit is contained in:
@@ -4,4 +4,5 @@ export const user = sqliteTable("user", {
|
|||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||||
email: text("email").notNull().unique(),
|
email: text("email").notNull().unique(),
|
||||||
password: text("password").notNull(),
|
password: text("password").notNull(),
|
||||||
|
nickname: text("nickname").notNull(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { SqliteError } from "better-sqlite3";
|
import { SqliteError } from "better-sqlite3";
|
||||||
import { and, eq, gt, lte, isNull } from "drizzle-orm";
|
import { and, eq, ne, gt, lte, isNull } from "drizzle-orm";
|
||||||
import env from "$lib/server/loadenv";
|
import env from "$lib/server/loadenv";
|
||||||
import db from "./drizzle";
|
import db from "./drizzle";
|
||||||
import { IntegrityError } from "./error";
|
import { IntegrityError } from "./error";
|
||||||
@@ -71,6 +71,10 @@ export const deleteSession = async (sessionId: string) => {
|
|||||||
await db.delete(session).where(eq(session.id, sessionId));
|
await db.delete(session).where(eq(session.id, sessionId));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const deleteAllOtherSessions = async (userId: number, sessionId: string) => {
|
||||||
|
await db.delete(session).where(and(eq(session.userId, userId), ne(session.id, sessionId)));
|
||||||
|
};
|
||||||
|
|
||||||
export const cleanupExpiredSessions = async () => {
|
export const cleanupExpiredSessions = async () => {
|
||||||
await db.delete(session).where(lte(session.lastUsedAt, new Date(Date.now() - env.session.exp)));
|
await db.delete(session).where(lte(session.lastUsedAt, new Date(Date.now() - env.session.exp)));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,20 @@ import { eq } from "drizzle-orm";
|
|||||||
import db from "./drizzle";
|
import db from "./drizzle";
|
||||||
import { user } from "./schema";
|
import { user } from "./schema";
|
||||||
|
|
||||||
|
export const getUser = async (userId: number) => {
|
||||||
|
const users = await db.select().from(user).where(eq(user.id, userId)).limit(1);
|
||||||
|
return users[0] ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
export const getUserByEmail = async (email: string) => {
|
export const getUserByEmail = async (email: string) => {
|
||||||
const users = await db.select().from(user).where(eq(user.email, email)).limit(1);
|
const users = await db.select().from(user).where(eq(user.email, email)).limit(1);
|
||||||
return users[0] ?? null;
|
return users[0] ?? null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const setUserPassword = async (userId: number, password: string) => {
|
||||||
|
await db.update(user).set({ password }).where(eq(user.id, userId));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setUserNickname = async (userId: number, nickname: string) => {
|
||||||
|
await db.update(user).set({ nickname }).where(eq(user.id, userId));
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const changePasswordRequest = z.object({
|
||||||
|
oldPassword: z.string().trim().nonempty(),
|
||||||
|
newPassword: z.string().trim().nonempty(),
|
||||||
|
});
|
||||||
|
export type ChangePasswordRequest = z.infer<typeof changePasswordRequest>;
|
||||||
|
|
||||||
export const loginRequest = z.object({
|
export const loginRequest = z.object({
|
||||||
email: z.string().email().nonempty(),
|
email: z.string().email().nonempty(),
|
||||||
password: z.string().trim().nonempty(),
|
password: z.string().trim().nonempty(),
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ export * from "./directory";
|
|||||||
export * from "./file";
|
export * from "./file";
|
||||||
export * from "./hsk";
|
export * from "./hsk";
|
||||||
export * from "./mek";
|
export * from "./mek";
|
||||||
|
export * from "./user";
|
||||||
|
|||||||
12
src/lib/server/schemas/user.ts
Normal file
12
src/lib/server/schemas/user.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const userInfoResponse = z.object({
|
||||||
|
email: z.string().email().nonempty(),
|
||||||
|
nickname: z.string().nonempty(),
|
||||||
|
});
|
||||||
|
export type UserInfoResponse = z.infer<typeof userInfoResponse>;
|
||||||
|
|
||||||
|
export const changeNicknameRequest = z.object({
|
||||||
|
newNickname: z.string().min(2).max(8),
|
||||||
|
});
|
||||||
|
export type ChangeNicknameRequest = z.infer<typeof changeNicknameRequest>;
|
||||||
@@ -5,18 +5,46 @@ import { IntegrityError } from "$lib/server/db/error";
|
|||||||
import {
|
import {
|
||||||
upgradeSession,
|
upgradeSession,
|
||||||
deleteSession,
|
deleteSession,
|
||||||
|
deleteAllOtherSessions,
|
||||||
registerSessionUpgradeChallenge,
|
registerSessionUpgradeChallenge,
|
||||||
consumeSessionUpgradeChallenge,
|
consumeSessionUpgradeChallenge,
|
||||||
} from "$lib/server/db/session";
|
} from "$lib/server/db/session";
|
||||||
import { getUserByEmail } from "$lib/server/db/user";
|
import { getUser, getUserByEmail, setUserPassword } from "$lib/server/db/user";
|
||||||
import env from "$lib/server/loadenv";
|
import env from "$lib/server/loadenv";
|
||||||
import { startSession } from "$lib/server/modules/auth";
|
import { startSession } from "$lib/server/modules/auth";
|
||||||
import { verifySignature, generateChallenge } from "$lib/server/modules/crypto";
|
import { verifySignature, generateChallenge } from "$lib/server/modules/crypto";
|
||||||
|
|
||||||
|
const hashPassword = async (password: string) => {
|
||||||
|
return await argon2.hash(password);
|
||||||
|
};
|
||||||
|
|
||||||
const verifyPassword = async (hash: string, password: string) => {
|
const verifyPassword = async (hash: string, password: string) => {
|
||||||
return await argon2.verify(hash, password);
|
return await argon2.verify(hash, password);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const changePassword = async (
|
||||||
|
userId: number,
|
||||||
|
sessionId: string,
|
||||||
|
oldPassword: string,
|
||||||
|
newPassword: string,
|
||||||
|
) => {
|
||||||
|
if (oldPassword === newPassword) {
|
||||||
|
error(400, "Same passwords");
|
||||||
|
} else if (newPassword.length < 8) {
|
||||||
|
error(400, "Too short password");
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await getUser(userId);
|
||||||
|
if (!user) {
|
||||||
|
error(500, "Invalid session id");
|
||||||
|
} else if (!(await verifyPassword(user.password, oldPassword))) {
|
||||||
|
error(403, "Invalid password");
|
||||||
|
}
|
||||||
|
|
||||||
|
await setUserPassword(userId, await hashPassword(newPassword));
|
||||||
|
await deleteAllOtherSessions(userId, sessionId);
|
||||||
|
};
|
||||||
|
|
||||||
export const login = async (email: string, password: string, ip: string, userAgent: string) => {
|
export const login = async (email: string, password: string, ip: string, userAgent: string) => {
|
||||||
const user = await getUserByEmail(email);
|
const user = await getUserByEmail(email);
|
||||||
if (!user || !(await verifyPassword(user.password, password))) {
|
if (!user || !(await verifyPassword(user.password, password))) {
|
||||||
|
|||||||
15
src/lib/server/services/user.ts
Normal file
15
src/lib/server/services/user.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { error } from "@sveltejs/kit";
|
||||||
|
import { getUser, setUserNickname } from "$lib/server/db/user";
|
||||||
|
|
||||||
|
export const getUserInformation = async (userId: number) => {
|
||||||
|
const user = await getUser(userId);
|
||||||
|
if (!user) {
|
||||||
|
error(500, "Invalid session id");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { email: user.email, nickname: user.nickname };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const changeNickname = async (userId: number, nickname: string) => {
|
||||||
|
await setUserNickname(userId, nickname);
|
||||||
|
};
|
||||||
16
src/routes/api/auth/changePassword/+server.ts
Normal file
16
src/routes/api/auth/changePassword/+server.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { error, text } from "@sveltejs/kit";
|
||||||
|
import { authorize } from "$lib/server/modules/auth";
|
||||||
|
import { changePasswordRequest } from "$lib/server/schemas";
|
||||||
|
import { changePassword } from "$lib/server/services/auth";
|
||||||
|
import type { RequestHandler } from "./$types";
|
||||||
|
|
||||||
|
export const POST: RequestHandler = async ({ locals, request }) => {
|
||||||
|
const { sessionId, userId } = await authorize(locals, "any");
|
||||||
|
|
||||||
|
const zodRes = changePasswordRequest.safeParse(await request.json());
|
||||||
|
if (!zodRes.success) error(400, "Invalid request body");
|
||||||
|
const { oldPassword, newPassword } = zodRes.data;
|
||||||
|
|
||||||
|
await changePassword(userId, sessionId, oldPassword, newPassword);
|
||||||
|
return text("Password changed", { headers: { "Content-Type": "text/plain" } });
|
||||||
|
};
|
||||||
11
src/routes/api/user/+server.ts
Normal file
11
src/routes/api/user/+server.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { json } from "@sveltejs/kit";
|
||||||
|
import { authorize } from "$lib/server/modules/auth";
|
||||||
|
import { userInfoResponse, type UserInfoResponse } from "$lib/server/schemas";
|
||||||
|
import { getUserInformation } from "$lib/server/services/user";
|
||||||
|
import type { RequestHandler } from "./$types";
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async ({ locals }) => {
|
||||||
|
const { userId } = await authorize(locals, "any");
|
||||||
|
const { email, nickname } = await getUserInformation(userId);
|
||||||
|
return json(userInfoResponse.parse({ email, nickname } satisfies UserInfoResponse));
|
||||||
|
};
|
||||||
16
src/routes/api/user/changeNickname/+server.ts
Normal file
16
src/routes/api/user/changeNickname/+server.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { error, text } from "@sveltejs/kit";
|
||||||
|
import { authorize } from "$lib/server/modules/auth";
|
||||||
|
import { changeNicknameRequest } from "$lib/server/schemas";
|
||||||
|
import { changeNickname } from "$lib/server/services/user";
|
||||||
|
import type { RequestHandler } from "./$types";
|
||||||
|
|
||||||
|
export const POST: RequestHandler = async ({ locals, request }) => {
|
||||||
|
const { userId } = await authorize(locals, "any");
|
||||||
|
|
||||||
|
const zodRes = changeNicknameRequest.safeParse(await request.json());
|
||||||
|
if (!zodRes.success) error(400, "Invalid request body");
|
||||||
|
const { newNickname } = zodRes.data;
|
||||||
|
|
||||||
|
await changeNickname(userId, newNickname);
|
||||||
|
return text("Nickname changed", { headers: { "Content-Type": "text/plain" } });
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user