토큰에 클라이언트 정보를 함께 저장하도록 변경

This commit is contained in:
static
2024-12-26 17:04:52 +09:00
parent fac8764572
commit 45e214d49f
5 changed files with 57 additions and 23 deletions

View File

@@ -1,27 +1,44 @@
import argon2 from "argon2"; import argon2 from "argon2";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { getClientByPubKey } from "$lib/server/db/client";
import { getUserByEmail } from "$lib/server/db/user"; import { getUserByEmail } from "$lib/server/db/user";
import env from "$lib/server/loadenv"; import env from "$lib/server/loadenv";
interface TokenData {
type: "access" | "refresh";
userId: number;
clientId?: number;
}
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);
}; };
const issueToken = (id: number, type: "access" | "refresh") => { const issueToken = (type: "access" | "refresh", userId: number, clientId?: number) => {
return jwt.sign({ id, type }, env.jwt.secret, { return jwt.sign(
expiresIn: type === "access" ? env.jwt.accessExp : env.jwt.refreshExp, {
}); type,
userId,
clientId,
} satisfies TokenData,
env.jwt.secret,
{
expiresIn: type === "access" ? env.jwt.accessExp : env.jwt.refreshExp,
},
);
}; };
export const login = async (email: string, password: string) => { export const login = async (email: string, password: string, pubKey?: string) => {
const user = await getUserByEmail(email); const user = await getUserByEmail(email);
if (!user) return null; if (!user) return null;
const valid = await verifyPassword(user.password, password); const isValid = await verifyPassword(user.password, password);
if (!valid) return null; if (!isValid) return null;
const client = pubKey ? await getClientByPubKey(pubKey) : null;
return { return {
accessToken: issueToken(user.id, "access"), accessToken: issueToken("access", user.id, client?.id),
refreshToken: issueToken(user.id, "refresh"), refreshToken: issueToken("refresh", user.id, client?.id),
}; };
}; };

View File

@@ -0,0 +1,17 @@
import { and, eq } from "drizzle-orm";
import db from "./drizzle";
import { client, userClient } from "./schema";
export const getClientByPubKey = async (pubKey: string) => {
const clients = await db.select().from(client).where(eq(client.pubKey, pubKey)).execute();
return clients[0] ?? null;
};
export const getUserClient = async (userId: number, clientId: number) => {
const userClients = await db
.select()
.from(userClient)
.where(and(eq(userClient.userId, userId), eq(userClient.clientId, clientId)))
.execute();
return userClients[0] ?? null;
};

View File

@@ -1,29 +1,29 @@
import { sqliteTable, text, integer, primaryKey } from "drizzle-orm/sqlite-core"; import { sqliteTable, text, integer, primaryKey } from "drizzle-orm/sqlite-core";
import { user } from "./user"; import { user } from "./user";
export enum UserDeviceState { export enum UserClientState {
PENDING = 0, PENDING = 0,
ACTIVE = 1, ACTIVE = 1,
} }
export const device = sqliteTable("device", { export const client = sqliteTable("client", {
id: integer("id").primaryKey(), id: integer("id").primaryKey(),
pubKey: text("pub_key").notNull().unique(), pubKey: text("public_key").notNull().unique(),
}); });
export const userDevice = sqliteTable( export const userClient = sqliteTable(
"user_device", "user_client",
{ {
userId: integer("user_id") userId: integer("user_id")
.notNull() .notNull()
.references(() => user.id), .references(() => user.id),
deviceId: integer("device_id") clientId: integer("client_id")
.notNull() .notNull()
.references(() => device.id), .references(() => client.id),
state: integer("state").notNull().default(0), state: integer("state").notNull().default(0),
encKey: text("enc_key"), encKey: text("encrypted_key"),
}, },
(t) => ({ (t) => ({
pk: primaryKey({ columns: [t.userId, t.deviceId] }), pk: primaryKey({ columns: [t.userId, t.clientId] }),
}), }),
); );

View File

@@ -1,2 +1,2 @@
export * from "./device"; export * from "./client";
export * from "./user"; export * from "./user";

View File

@@ -8,15 +8,15 @@ export const POST: RequestHandler = async ({ request }) => {
.object({ .object({
email: z.string().email().nonempty(), email: z.string().email().nonempty(),
password: z.string().nonempty(), password: z.string().nonempty(),
pubKey: z.string().nonempty().optional(),
}) })
.safeParse(await request.json()); .safeParse(await request.json());
if (!zodRes.success) error(400, zodRes.error.message); if (!zodRes.success) error(400, zodRes.error.message);
const { email, password } = zodRes.data; const { email, password, pubKey } = zodRes.data;
const loginRes = await login(email.trim(), password.trim()); const loginRes = await login(email.trim(), password.trim(), pubKey?.trim());
if (!loginRes) error(401, "Invalid email, password, or public key");
if (!loginRes) error(401, "Invalid email or password");
const { accessToken, refreshToken } = loginRes; const { accessToken, refreshToken } = loginRes;
return json({ accessToken, refreshToken }); return json({ accessToken, refreshToken });
}; };