Fixing some auth implementations. Removed text encoder for validate session token, unsure why it is needed.

This commit is contained in:
Bradley Shellnut 2024-11-07 17:34:13 -08:00
parent faaf287bf0
commit 50cd97993a
8 changed files with 75 additions and 39 deletions

View file

@ -1,7 +1,8 @@
import type { Sessions } from '$lib/server/api/databases/postgres/tables';
import type { Hono } from 'hono';
import type { PinoLogger } from 'hono-pino';
import type { Promisify, RateLimitInfo } from 'hono-rate-limiter';
import type { Session, User } from 'lucia';
import type { User } from 'lucia';
// export type AppOpenAPI = OpenAPIHono<AppBindings>;
export type AppOpenAPI = Hono<AppBindings>;
@ -9,7 +10,7 @@ export type AppOpenAPI = Hono<AppBindings>;
export type AppBindings = {
Variables: {
logger: PinoLogger;
session: Session | null;
session: Sessions | null;
user: User | null;
rateLimit: RateLimitInfo;
rateLimitStore: {
@ -22,7 +23,7 @@ export type AppBindings = {
export type HonoTypes = {
Variables: {
logger: PinoLogger;
session: Session | null;
session: Sessions | null;
user: User | null;
rateLimit: RateLimitInfo;
rateLimitStore: {

View file

@ -1,13 +1,21 @@
import { config } from '$lib/server/api/common/config';
import env from '$lib/server/api/common/env';
import { TimeSpan } from 'oslo';
export const cookieMaxAge = 60 * 60 * 24 * 30;
export const cookieExpiresMilliseconds = new TimeSpan(2, 'w').milliseconds();
export const cookieExpiresAt = new Date(Date.now() + cookieExpiresMilliseconds);
export const halfCookieExpiresMilliseconds = cookieExpiresMilliseconds / 2;
export const halfCookieExpiresAt = new Date(Date.now() + halfCookieExpiresMilliseconds);
export const cookieName = 'session';
export function createSessionTokenCookie(token: string, expiresAt: Date) {
return {
name: 'session',
name: cookieName,
value: token,
attributes: {
path: '/',
maxAge: 60 * 60 * 24 * 30,
maxAge: cookieMaxAge,
domain: env.DOMAIN,
sameSite: 'lax',
secure: config.isProduction,
@ -19,7 +27,7 @@ export function createSessionTokenCookie(token: string, expiresAt: Date) {
export function createBlankSessionTokenCookie() {
return {
name: 'session',
name: cookieName,
value: '',
attributes: {
path: '/',

View file

@ -1,5 +1,6 @@
import 'reflect-metadata';
import { Controller } from '$lib/server/api/common/types/controller';
import { cookieExpiresAt, createSessionTokenCookie } from '$lib/server/api/common/utils/cookies';
import { signinUsernameDto } from '$lib/server/api/dtos/signin-username.dto';
import { SessionsService } from '$lib/server/api/services/sessions.service';
import { zValidator } from '@hono/zod-validator';
@ -29,7 +30,7 @@ export class LoginController extends Controller {
async (c) => {
const { username, password } = c.req.valid('json');
const session = await this.loginRequestsService.verify({ username, password }, c.req);
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id);
const sessionCookie = createSessionTokenCookie(session.id, cookieExpiresAt);
console.log('set cookie', sessionCookie);
setCookie(c, sessionCookie.name, sessionCookie.value, {
path: sessionCookie.attributes.path,

View file

@ -8,6 +8,7 @@ import { github, google } from '$lib/server/auth';
import { OAuth2RequestError } from 'arctic';
import { getCookie, setCookie } from 'hono/cookie';
import { TimeSpan } from 'oslo';
import { inject, injectable } from 'tsyringe';
@injectable()
@ -48,8 +49,7 @@ export class OAuthController extends Controller {
const userId = await this.oauthService.handleOAuthUser(oAuthUser, 'github');
const sessionToken = this.sessionsService.generateSessionToken();
const session = await this.sessionsService.createSession(sessionToken, userId,
req.);
const session = await this.sessionsService.createSession(sessionToken, userId, '', '', false, false);
const sessionCookie = createSessionTokenCookie(session.id, new Date(new TimeSpan(2, 'w').milliseconds()));
setCookie(c, sessionCookie.name, sessionCookie.value, {
@ -107,7 +107,7 @@ export class OAuthController extends Controller {
const userId = await this.oauthService.handleOAuthUser(oAuthUser, 'google');
const session = await this.luciaService.lucia.createSession(userId, {});
const session = await this.sessionsService.createSession();
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id);
setCookie(c, sessionCookie.name, sessionCookie.value, {

View file

@ -1,7 +1,11 @@
import { createSessionTokenCookie } from '$lib/server/api/common/utils/cookies';
import 'reflect-metadata';
import { cookieExpiresAt, cookieName, createBlankSessionTokenCookie, createSessionTokenCookie } from '$lib/server/api/common/utils/cookies';
import { SessionsService } from '$lib/server/api/services/sessions.service';
import type { MiddlewareHandler } from 'hono';
import { setCookie } from 'hono/cookie';
import { createMiddleware } from 'hono/factory';
import { TimeSpan } from 'oslo';
import { parseCookies } from 'oslo/cookie';
import { verifyRequestOrigin } from 'oslo/request';
import { container } from 'tsyringe';
import type { AppBindings } from '../common/types/hono';
@ -22,19 +26,40 @@ export const verifyOrigin: MiddlewareHandler<AppBindings> = createMiddleware(asy
});
export const validateAuthSession: MiddlewareHandler<AppBindings> = createMiddleware(async (c, next) => {
const sessionId = lucia.readSessionCookie(c.req.header('Cookie') ?? '');
const cookies = parseCookies(c.req.header('Cookie') ?? '');
const sessionId = cookies.get(cookieName) ?? null;
if (!sessionId) {
c.set('user', null);
c.set('session', null);
return next();
}
const { session, user } = await lucia.validateSession(sessionId);
if (session?.fresh) {
c.header('Set-Cookie', createSessionTokenCookie(session.id).serialize(), { append: true });
}
if (!session) {
c.header('Set-Cookie', lucia.createBlankSessionCookie().serialize(), { append: true });
const { session, user } = await sessionService.validateSessionToken(sessionId);
if (session !== null) {
const sessionCookie = createSessionTokenCookie(session.id, cookieExpiresAt);
setCookie(c, sessionCookie.name, sessionCookie.value, {
path: sessionCookie.attributes.path,
maxAge:
sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds()
? sessionCookie.attributes.maxAge
: new TimeSpan(2, 'w').seconds(),
domain: sessionCookie.attributes.domain,
sameSite: sessionCookie.attributes.sameSite as any,
secure: sessionCookie.attributes.secure,
httpOnly: sessionCookie.attributes.httpOnly,
expires: sessionCookie.attributes.expires,
});
} else {
const sessionCookie = createBlankSessionTokenCookie();
setCookie(c, sessionCookie.name, sessionCookie.value, {
path: sessionCookie.attributes.path,
maxAge: sessionCookie.attributes?.maxAge,
domain: sessionCookie.attributes.domain,
sameSite: sessionCookie.attributes.sameSite as any,
secure: sessionCookie.attributes.secure,
httpOnly: sessionCookie.attributes.httpOnly,
expires: sessionCookie.attributes.expires,
});
}
c.set('session', session);
c.set('user', user);

View file

@ -1,15 +1,16 @@
import { Unauthorized } from '$lib/server/api/common/exceptions'
import type { MiddlewareHandler } from 'hono'
import { createMiddleware } from 'hono/factory'
import type { Session, User } from 'lucia'
import { Unauthorized } from '$lib/server/api/common/exceptions';
import type { Sessions } from '$lib/server/api/databases/postgres/tables';
import type { MiddlewareHandler } from 'hono';
import { createMiddleware } from 'hono/factory';
import type { User } from 'lucia';
export const requireAuth: MiddlewareHandler<{
Variables: {
session: Session
user: User
}
session: Sessions;
user: User;
};
}> = createMiddleware(async (c, next) => {
const user = c.var.user
if (!user) throw Unauthorized('You must be logged in to access this resource')
return next()
})
const user = c.var.user;
if (!user) throw Unauthorized('You must be logged in to access this resource');
return next();
});

View file

@ -1,10 +1,10 @@
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import pg from 'pg';
import { config } from '../common/config';
import * as schema from '../databases/postgres/tables';
// create the connection
export const pool = new Pool({
export const pool = new pg.Pool({
user: config.postgres.user,
password: config.postgres.password,
host: config.postgres.host,

View file

@ -1,7 +1,7 @@
import { cookieExpiresAt, cookieExpiresMilliseconds, halfCookieExpiresMilliseconds } from '$lib/server/api/common/utils/cookies';
import { SessionsRepository } from '$lib/server/api/repositories/sessions.repository';
import { sha256 } from '@oslojs/crypto/sha2';
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding';
import { TimeSpan } from 'lucia';
import { inject, injectable } from 'tsyringe';
import type { Sessions, Users } from '../databases/postgres/tables';
@ -29,7 +29,7 @@ export class SessionsService {
const session: Sessions = {
id: sessionId,
userId,
expiresAt: new Date(Date.now() + new TimeSpan(2, 'w').seconds()),
expiresAt: cookieExpiresAt,
ipCountry,
ipAddress,
twoFactorAuthEnabled,
@ -40,8 +40,8 @@ export class SessionsService {
}
async validateSessionToken(token: string): Promise<SessionValidationResult> {
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
const sessions = await this.sessionsRepository.findBySessionId(sessionId);
// const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
const sessions = await this.sessionsRepository.findBySessionId(token);
if (sessions.length < 1) {
return {
session: null,
@ -50,16 +50,16 @@ export class SessionsService {
}
const { user, session } = sessions[0];
if (Date.now() >= session.expiresAt.getTime()) {
await this.sessionsRepository.deleteBySessionId(sessionId);
await this.sessionsRepository.deleteBySessionId(token);
return {
session: null,
user: null,
};
}
if (Date.now() >= session.expiresAt.getTime() - new TimeSpan(1, 'w').seconds()) {
session.expiresAt = new Date(Date.now() + new TimeSpan(2, 'w').seconds());
await this.sessionsRepository.updateSessionExpiresAt(sessionId, session.expiresAt);
if (Date.now() >= session.expiresAt.getTime() - cookieExpiresMilliseconds) {
session.expiresAt = new Date(Date.now() + halfCookieExpiresMilliseconds);
await this.sessionsRepository.updateSessionExpiresAt(token, session.expiresAt);
}
return { session, user };