refactor and reorganize

This commit is contained in:
Milo Schwartz 2025-01-01 21:41:31 -05:00
parent 9732098799
commit 3b4a993704
No known key found for this signature in database
216 changed files with 519 additions and 2128 deletions

View file

@ -1,6 +1,3 @@
export * from "./verifySession";
export * from "./unauthorizedResponse";
import {
encodeBase32LowerCaseNoPadding,
encodeHexLowerCase,
@ -9,7 +6,7 @@ import { sha256 } from "@oslojs/crypto/sha2";
import { Session, sessions, User, users } from "@server/db/schema";
import db from "@server/db";
import { eq } from "drizzle-orm";
import config from "@server/config";
import config from "@server/lib/config";
import type { RandomReader } from "@oslojs/crypto/random";
import { generateRandomString } from "@oslojs/crypto/random";

View file

@ -5,7 +5,7 @@ import { createDate, isWithinExpirationDate, TimeSpan } from "oslo";
import { alphabet, generateRandomString, sha256 } from "oslo/crypto";
import { sendEmail } from "@server/emails";
import ResourceOTPCode from "@server/emails/templates/ResourceOTPCode";
import config from "@server/config";
import config from "@server/lib/config";
import { verifyPassword } from "./password";
import { hashPassword } from "./password";

View file

@ -4,7 +4,7 @@ import db from "@server/db";
import { users, emailVerificationCodes } from "@server/db/schema";
import { eq } from "drizzle-orm";
import { sendEmail } from "@server/emails";
import config from "@server/config";
import config from "@server/lib/config";
import { VerifyEmail } from "@server/emails/templates/VerifyEmailCode";
export async function sendEmailVerificationCode(

118
server/auth/sessions/app.ts Normal file
View file

@ -0,0 +1,118 @@
import {
encodeBase32LowerCaseNoPadding,
encodeHexLowerCase,
} from "@oslojs/encoding";
import { sha256 } from "@oslojs/crypto/sha2";
import { Session, sessions, User, users } from "@server/db/schema";
import db from "@server/db";
import { eq } from "drizzle-orm";
import config from "@server/lib/config";
import type { RandomReader } from "@oslojs/crypto/random";
import { generateRandomString } from "@oslojs/crypto/random";
export const SESSION_COOKIE_NAME = config.getRawConfig().server.session_cookie_name;
export const SESSION_COOKIE_EXPIRES = 1000 * 60 * 60 * 24 * 30;
export const SECURE_COOKIES = config.getRawConfig().server.secure_cookies;
export const COOKIE_DOMAIN = "." + config.getBaseDomain();
export function generateSessionToken(): string {
const bytes = new Uint8Array(20);
crypto.getRandomValues(bytes);
const token = encodeBase32LowerCaseNoPadding(bytes);
return token;
}
export async function createSession(
token: string,
userId: string,
): Promise<Session> {
const sessionId = encodeHexLowerCase(
sha256(new TextEncoder().encode(token)),
);
const session: Session = {
sessionId: sessionId,
userId,
expiresAt: new Date(Date.now() + SESSION_COOKIE_EXPIRES).getTime(),
};
await db.insert(sessions).values(session);
return session;
}
export async function validateSessionToken(
token: string,
): Promise<SessionValidationResult> {
const sessionId = encodeHexLowerCase(
sha256(new TextEncoder().encode(token)),
);
const result = await db
.select({ user: users, session: sessions })
.from(sessions)
.innerJoin(users, eq(sessions.userId, users.userId))
.where(eq(sessions.sessionId, sessionId));
if (result.length < 1) {
return { session: null, user: null };
}
const { user, session } = result[0];
if (Date.now() >= session.expiresAt) {
await db
.delete(sessions)
.where(eq(sessions.sessionId, session.sessionId));
return { session: null, user: null };
}
if (Date.now() >= session.expiresAt - SESSION_COOKIE_EXPIRES / 2) {
session.expiresAt = new Date(
Date.now() + SESSION_COOKIE_EXPIRES,
).getTime();
await db
.update(sessions)
.set({
expiresAt: session.expiresAt,
})
.where(eq(sessions.sessionId, session.sessionId));
}
return { session, user };
}
export async function invalidateSession(sessionId: string): Promise<void> {
await db.delete(sessions).where(eq(sessions.sessionId, sessionId));
}
export async function invalidateAllSessions(userId: string): Promise<void> {
await db.delete(sessions).where(eq(sessions.userId, userId));
}
export function serializeSessionCookie(token: string): string {
if (SECURE_COOKIES) {
return `${SESSION_COOKIE_NAME}=${token}; HttpOnly; SameSite=Strict; Max-Age=${SESSION_COOKIE_EXPIRES}; Path=/; Secure; Domain=${COOKIE_DOMAIN}`;
} else {
return `${SESSION_COOKIE_NAME}=${token}; HttpOnly; SameSite=Strict; Max-Age=${SESSION_COOKIE_EXPIRES}; Path=/; Domain=${COOKIE_DOMAIN}`;
}
}
export function createBlankSessionTokenCookie(): string {
if (SECURE_COOKIES) {
return `${SESSION_COOKIE_NAME}=; HttpOnly; SameSite=Strict; Max-Age=0; Path=/; Secure; Domain=${COOKIE_DOMAIN}`;
} else {
return `${SESSION_COOKIE_NAME}=; HttpOnly; SameSite=Strict; Max-Age=0; Path=/; Domain=${COOKIE_DOMAIN}`;
}
}
const random: RandomReader = {
read(bytes: Uint8Array): void {
crypto.getRandomValues(bytes);
},
};
export function generateId(length: number): string {
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
return generateRandomString(random, alphabet, length);
}
export function generateIdFromEntropySize(size: number): string {
const buffer = crypto.getRandomValues(new Uint8Array(size));
return encodeBase32LowerCaseNoPadding(buffer);
}
export type SessionValidationResult =
| { session: Session; user: User }
| { session: null; user: null };

View file

@ -1,6 +1,3 @@
export * from "./verifySession";
export * from "./unauthorizedResponse";
import {
encodeHexLowerCase,
} from "@oslojs/encoding";
@ -8,12 +5,8 @@ import { sha256 } from "@oslojs/crypto/sha2";
import { Newt, newts, newtSessions, NewtSession } from "@server/db/schema";
import db from "@server/db";
import { eq } from "drizzle-orm";
import config from "@server/config";
export const SESSION_COOKIE_NAME = "session";
export const SESSION_COOKIE_EXPIRES = 1000 * 60 * 60 * 24 * 30;
export const SECURE_COOKIES = config.getRawConfig().server.secure_cookies;
export const COOKIE_DOMAIN = "." + config.getBaseDomain();
export const EXPIRES = 1000 * 60 * 60 * 24 * 30;
export async function createNewtSession(
token: string,
@ -25,7 +18,7 @@ export async function createNewtSession(
const session: NewtSession = {
sessionId: sessionId,
newtId,
expiresAt: new Date(Date.now() + SESSION_COOKIE_EXPIRES).getTime(),
expiresAt: new Date(Date.now() + EXPIRES).getTime(),
};
await db.insert(newtSessions).values(session);
return session;
@ -52,9 +45,9 @@ export async function validateNewtSessionToken(
.where(eq(newtSessions.sessionId, session.sessionId));
return { session: null, newt: null };
}
if (Date.now() >= session.expiresAt - (SESSION_COOKIE_EXPIRES / 2)) {
if (Date.now() >= session.expiresAt - (EXPIRES / 2)) {
session.expiresAt = new Date(
Date.now() + SESSION_COOKIE_EXPIRES,
Date.now() + EXPIRES,
).getTime();
await db
.update(newtSessions)

View file

@ -1,15 +1,12 @@
import { encodeHexLowerCase } from "@oslojs/encoding";
import { sha256 } from "@oslojs/crypto/sha2";
import {
resourceSessions,
ResourceSession,
resources
} from "@server/db/schema";
import { resourceSessions, ResourceSession } from "@server/db/schema";
import db from "@server/db";
import { eq, and } from "drizzle-orm";
import config from "@server/config";
import config from "@server/lib/config";
export const SESSION_COOKIE_NAME = "resource_session";
export const SESSION_COOKIE_NAME =
config.getRawConfig().server.resource_session_cookie_name;
export const SESSION_COOKIE_EXPIRES = 1000 * 60 * 60 * 24 * 30;
export const SECURE_COOKIES = config.getRawConfig().server.secure_cookies;
export const COOKIE_DOMAIN = "." + config.getBaseDomain();
@ -88,18 +85,20 @@ export async function validateResourceSessionToken(
return { resourceSession: null };
} else if (
Date.now() >=
resourceSession.expiresAt - resourceSession.sessionLength / 2
resourceSession.expiresAt - resourceSession.sessionLength / 2
) {
if (!resourceSession.doNotExtend) {
resourceSession.expiresAt = new Date(
Date.now() + resourceSession.sessionLength
).getTime();
await db
.update(resourceSessions)
.set({
expiresAt: resourceSession.expiresAt
})
.where(eq(resourceSessions.sessionId, resourceSession.sessionId));
.update(resourceSessions)
.set({
expiresAt: resourceSession.expiresAt
})
.where(
eq(resourceSessions.sessionId, resourceSession.sessionId)
);
}
}

View file

@ -1,5 +1,5 @@
import { Request } from "express";
import { validateSessionToken, SESSION_COOKIE_NAME } from "@server/auth";
import { validateSessionToken, SESSION_COOKIE_NAME } from "@server/auth/sessions/app";
export async function verifySession(req: Request) {
const res = await validateSessionToken(