mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-23 07:34:07 -05:00
Migrate log in link DB functions to Kysely
This commit is contained in:
parent
a1257b7c00
commit
c0692f1081
69
app/features/auth/LogInLinkRepository.server.test.ts
Normal file
69
app/features/auth/LogInLinkRepository.server.test.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
||||
import { dbInsertUsers, dbReset } from "~/utils/Test";
|
||||
import * as LogInLinkRepository from "./LogInLinkRepository.server";
|
||||
|
||||
describe("create", () => {
|
||||
beforeEach(async () => {
|
||||
await dbInsertUsers(1);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("creates a login link with correct userId", async () => {
|
||||
const link = await LogInLinkRepository.create(1);
|
||||
|
||||
expect(link.userId).toBe(1);
|
||||
});
|
||||
|
||||
test("creates a login link with future expiration", async () => {
|
||||
const beforeCreation = Math.floor(Date.now() / 1000);
|
||||
const link = await LogInLinkRepository.create(1);
|
||||
|
||||
expect(link.expiresAt).toBeGreaterThan(beforeCreation);
|
||||
});
|
||||
});
|
||||
|
||||
describe("del", () => {
|
||||
beforeEach(async () => {
|
||||
await dbInsertUsers(1);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("deletes a login link by code", async () => {
|
||||
const link = await LogInLinkRepository.create(1);
|
||||
|
||||
await LogInLinkRepository.del(link.code);
|
||||
|
||||
const result = await LogInLinkRepository.findValidByCode(link.code);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("findValidByCode", () => {
|
||||
beforeEach(async () => {
|
||||
await dbInsertUsers(1);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("returns userId for valid code", async () => {
|
||||
const link = await LogInLinkRepository.create(1);
|
||||
|
||||
const result = await LogInLinkRepository.findValidByCode(link.code);
|
||||
|
||||
expect(result?.userId).toBe(1);
|
||||
});
|
||||
|
||||
test("returns undefined for non-existent code", async () => {
|
||||
const result = await LogInLinkRepository.findValidByCode("nonexistent1");
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
37
app/features/auth/LogInLinkRepository.server.ts
Normal file
37
app/features/auth/LogInLinkRepository.server.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { add } from "date-fns";
|
||||
import { nanoid } from "nanoid";
|
||||
import { db } from "~/db/sql";
|
||||
import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
|
||||
const LOG_IN_LINK_LENGTH = 12;
|
||||
const LOG_IN_LINK_VALID_FOR_MINUTES = 10;
|
||||
|
||||
/** Creates a new login link for a user with 10-minute expiration */
|
||||
export function create(userId: number) {
|
||||
return db
|
||||
.insertInto("LogInLink")
|
||||
.values({
|
||||
code: nanoid(LOG_IN_LINK_LENGTH),
|
||||
expiresAt: dateToDatabaseTimestamp(
|
||||
add(new Date(), { minutes: LOG_IN_LINK_VALID_FOR_MINUTES }),
|
||||
),
|
||||
userId,
|
||||
})
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
/** Deletes a login link by its code */
|
||||
export function del(code: string) {
|
||||
return db.deleteFrom("LogInLink").where("code", "=", code).execute();
|
||||
}
|
||||
|
||||
/** Finds a valid (non-expired) login link by code, returns userId if valid */
|
||||
export function findValidByCode(code: string) {
|
||||
return db
|
||||
.selectFrom("LogInLink")
|
||||
.select("userId")
|
||||
.where("code", "=", code)
|
||||
.where("expiresAt", ">", databaseTimestampNow())
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
|
@ -12,9 +12,7 @@ import {
|
|||
parseSearchParams,
|
||||
} from "~/utils/remix.server";
|
||||
import { ADMIN_PAGE, authErrorUrl } from "~/utils/urls";
|
||||
import { createLogInLink } from "../queries/createLogInLink.server";
|
||||
import { deleteLogInLinkByCode } from "../queries/deleteLogInLinkByCode.server";
|
||||
import { userIdByLogInLinkCode } from "../queries/userIdByLogInLinkCode.server";
|
||||
import * as LogInLinkRepository from "../LogInLinkRepository.server";
|
||||
import {
|
||||
authenticator,
|
||||
IMPERSONATED_SESSION_KEY,
|
||||
|
|
@ -141,7 +139,7 @@ export const createLogInLinkAction: ActionFunction = async ({ request }) => {
|
|||
|
||||
if (data.updateOnly === "true") return null;
|
||||
|
||||
const createdLink = createLogInLink(user.id);
|
||||
const createdLink = await LogInLinkRepository.create(user.id);
|
||||
|
||||
return {
|
||||
code: createdLink.code,
|
||||
|
|
@ -169,10 +167,11 @@ export const logInViaLinkLoader: LoaderFunction = async ({ request }) => {
|
|||
throw redirect("/");
|
||||
}
|
||||
|
||||
const userId = userIdByLogInLinkCode(data.code);
|
||||
if (!userId) {
|
||||
const result = await LogInLinkRepository.findValidByCode(data.code);
|
||||
if (!result) {
|
||||
throw new Response("Invalid log in link", { status: 400 });
|
||||
}
|
||||
const userId = result.userId;
|
||||
|
||||
const session = await authSessionStorage.getSession(
|
||||
request.headers.get("Cookie"),
|
||||
|
|
@ -180,7 +179,7 @@ export const logInViaLinkLoader: LoaderFunction = async ({ request }) => {
|
|||
|
||||
session.set(SESSION_KEY, userId);
|
||||
|
||||
deleteLogInLinkByCode(data.code);
|
||||
await LogInLinkRepository.del(data.code);
|
||||
|
||||
throw redirect("/", {
|
||||
headers: { "Set-Cookie": await authSessionStorage.commitSession(session) },
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
import { nanoid } from "nanoid";
|
||||
import { sql } from "~/db/sql";
|
||||
import type { Tables } from "~/db/tables";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
|
||||
const stm = sql.prepare(/* sql */ `
|
||||
insert into "LogInLink" (
|
||||
"userId",
|
||||
"expiresAt",
|
||||
"code"
|
||||
) values (
|
||||
@userId,
|
||||
@expiresAt,
|
||||
@code
|
||||
) returning *
|
||||
`);
|
||||
|
||||
// 10 minutes
|
||||
const LOG_IN_LINK_VALID_FOR = 10 * 60 * 1000;
|
||||
const LOG_IN_LINK_LENGTH = 12;
|
||||
|
||||
export function createLogInLink(userId: number) {
|
||||
return stm.get({
|
||||
userId,
|
||||
expiresAt: dateToDatabaseTimestamp(
|
||||
new Date(Date.now() + LOG_IN_LINK_VALID_FOR),
|
||||
),
|
||||
code: nanoid(LOG_IN_LINK_LENGTH),
|
||||
}) as Tables["LogInLink"];
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { sql } from "~/db/sql";
|
||||
|
||||
const stm = sql.prepare(/* sql */ `
|
||||
delete from "LogInLink"
|
||||
where "code" = @code
|
||||
`);
|
||||
|
||||
export function deleteLogInLinkByCode(code: string) {
|
||||
return stm.run({ code });
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import { sql } from "~/db/sql";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
|
||||
const stm = sql.prepare(/* sql */ `
|
||||
select "userId"
|
||||
from "LogInLink"
|
||||
where "code" = @code
|
||||
and "expiresAt" > @now
|
||||
`);
|
||||
|
||||
export function userIdByLogInLinkCode(code: string) {
|
||||
return (
|
||||
stm.get({
|
||||
code,
|
||||
now: dateToDatabaseTimestamp(new Date()),
|
||||
}) as any
|
||||
)?.userId as number | undefined;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user