mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Fixing some auth implementations. Removed text encoder for validate session token, unsure why it is needed.
This commit is contained in:
parent
faaf287bf0
commit
50cd97993a
8 changed files with 75 additions and 39 deletions
|
|
@ -1,7 +1,8 @@
|
||||||
|
import type { Sessions } from '$lib/server/api/databases/postgres/tables';
|
||||||
import type { Hono } from 'hono';
|
import type { Hono } from 'hono';
|
||||||
import type { PinoLogger } from 'hono-pino';
|
import type { PinoLogger } from 'hono-pino';
|
||||||
import type { Promisify, RateLimitInfo } from 'hono-rate-limiter';
|
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 = OpenAPIHono<AppBindings>;
|
||||||
export type AppOpenAPI = Hono<AppBindings>;
|
export type AppOpenAPI = Hono<AppBindings>;
|
||||||
|
|
@ -9,7 +10,7 @@ export type AppOpenAPI = Hono<AppBindings>;
|
||||||
export type AppBindings = {
|
export type AppBindings = {
|
||||||
Variables: {
|
Variables: {
|
||||||
logger: PinoLogger;
|
logger: PinoLogger;
|
||||||
session: Session | null;
|
session: Sessions | null;
|
||||||
user: User | null;
|
user: User | null;
|
||||||
rateLimit: RateLimitInfo;
|
rateLimit: RateLimitInfo;
|
||||||
rateLimitStore: {
|
rateLimitStore: {
|
||||||
|
|
@ -22,7 +23,7 @@ export type AppBindings = {
|
||||||
export type HonoTypes = {
|
export type HonoTypes = {
|
||||||
Variables: {
|
Variables: {
|
||||||
logger: PinoLogger;
|
logger: PinoLogger;
|
||||||
session: Session | null;
|
session: Sessions | null;
|
||||||
user: User | null;
|
user: User | null;
|
||||||
rateLimit: RateLimitInfo;
|
rateLimit: RateLimitInfo;
|
||||||
rateLimitStore: {
|
rateLimitStore: {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,21 @@
|
||||||
import { config } from '$lib/server/api/common/config';
|
import { config } from '$lib/server/api/common/config';
|
||||||
import env from '$lib/server/api/common/env';
|
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) {
|
export function createSessionTokenCookie(token: string, expiresAt: Date) {
|
||||||
return {
|
return {
|
||||||
name: 'session',
|
name: cookieName,
|
||||||
value: token,
|
value: token,
|
||||||
attributes: {
|
attributes: {
|
||||||
path: '/',
|
path: '/',
|
||||||
maxAge: 60 * 60 * 24 * 30,
|
maxAge: cookieMaxAge,
|
||||||
domain: env.DOMAIN,
|
domain: env.DOMAIN,
|
||||||
sameSite: 'lax',
|
sameSite: 'lax',
|
||||||
secure: config.isProduction,
|
secure: config.isProduction,
|
||||||
|
|
@ -19,7 +27,7 @@ export function createSessionTokenCookie(token: string, expiresAt: Date) {
|
||||||
|
|
||||||
export function createBlankSessionTokenCookie() {
|
export function createBlankSessionTokenCookie() {
|
||||||
return {
|
return {
|
||||||
name: 'session',
|
name: cookieName,
|
||||||
value: '',
|
value: '',
|
||||||
attributes: {
|
attributes: {
|
||||||
path: '/',
|
path: '/',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
import { Controller } from '$lib/server/api/common/types/controller';
|
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 { signinUsernameDto } from '$lib/server/api/dtos/signin-username.dto';
|
||||||
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
import { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||||
import { zValidator } from '@hono/zod-validator';
|
import { zValidator } from '@hono/zod-validator';
|
||||||
|
|
@ -29,7 +30,7 @@ export class LoginController extends Controller {
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { username, password } = c.req.valid('json');
|
const { username, password } = c.req.valid('json');
|
||||||
const session = await this.loginRequestsService.verify({ username, password }, c.req);
|
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);
|
console.log('set cookie', sessionCookie);
|
||||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||||
path: sessionCookie.attributes.path,
|
path: sessionCookie.attributes.path,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { github, google } from '$lib/server/auth';
|
||||||
import { OAuth2RequestError } from 'arctic';
|
import { OAuth2RequestError } from 'arctic';
|
||||||
import { getCookie, setCookie } from 'hono/cookie';
|
import { getCookie, setCookie } from 'hono/cookie';
|
||||||
import { TimeSpan } from 'oslo';
|
import { TimeSpan } from 'oslo';
|
||||||
|
|
||||||
import { inject, injectable } from 'tsyringe';
|
import { inject, injectable } from 'tsyringe';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
|
|
@ -48,8 +49,7 @@ export class OAuthController extends Controller {
|
||||||
const userId = await this.oauthService.handleOAuthUser(oAuthUser, 'github');
|
const userId = await this.oauthService.handleOAuthUser(oAuthUser, 'github');
|
||||||
|
|
||||||
const sessionToken = this.sessionsService.generateSessionToken();
|
const sessionToken = this.sessionsService.generateSessionToken();
|
||||||
const session = await this.sessionsService.createSession(sessionToken, userId,
|
const session = await this.sessionsService.createSession(sessionToken, userId, '', '', false, false);
|
||||||
req.);
|
|
||||||
const sessionCookie = createSessionTokenCookie(session.id, new Date(new TimeSpan(2, 'w').milliseconds()));
|
const sessionCookie = createSessionTokenCookie(session.id, new Date(new TimeSpan(2, 'w').milliseconds()));
|
||||||
|
|
||||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||||
|
|
@ -107,7 +107,7 @@ export class OAuthController extends Controller {
|
||||||
|
|
||||||
const userId = await this.oauthService.handleOAuthUser(oAuthUser, 'google');
|
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);
|
const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id);
|
||||||
|
|
||||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||||
|
|
|
||||||
|
|
@ -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 { SessionsService } from '$lib/server/api/services/sessions.service';
|
||||||
import type { MiddlewareHandler } from 'hono';
|
import type { MiddlewareHandler } from 'hono';
|
||||||
|
import { setCookie } from 'hono/cookie';
|
||||||
import { createMiddleware } from 'hono/factory';
|
import { createMiddleware } from 'hono/factory';
|
||||||
|
import { TimeSpan } from 'oslo';
|
||||||
|
import { parseCookies } from 'oslo/cookie';
|
||||||
import { verifyRequestOrigin } from 'oslo/request';
|
import { verifyRequestOrigin } from 'oslo/request';
|
||||||
import { container } from 'tsyringe';
|
import { container } from 'tsyringe';
|
||||||
import type { AppBindings } from '../common/types/hono';
|
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) => {
|
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) {
|
if (!sessionId) {
|
||||||
c.set('user', null);
|
c.set('user', null);
|
||||||
c.set('session', null);
|
c.set('session', null);
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { session, user } = await lucia.validateSession(sessionId);
|
const { session, user } = await sessionService.validateSessionToken(sessionId);
|
||||||
if (session?.fresh) {
|
if (session !== null) {
|
||||||
c.header('Set-Cookie', createSessionTokenCookie(session.id).serialize(), { append: true });
|
const sessionCookie = createSessionTokenCookie(session.id, cookieExpiresAt);
|
||||||
}
|
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||||
if (!session) {
|
path: sessionCookie.attributes.path,
|
||||||
c.header('Set-Cookie', lucia.createBlankSessionCookie().serialize(), { append: true });
|
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('session', session);
|
||||||
c.set('user', user);
|
c.set('user', user);
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
import { Unauthorized } from '$lib/server/api/common/exceptions'
|
import { Unauthorized } from '$lib/server/api/common/exceptions';
|
||||||
import type { MiddlewareHandler } from 'hono'
|
import type { Sessions } from '$lib/server/api/databases/postgres/tables';
|
||||||
import { createMiddleware } from 'hono/factory'
|
import type { MiddlewareHandler } from 'hono';
|
||||||
import type { Session, User } from 'lucia'
|
import { createMiddleware } from 'hono/factory';
|
||||||
|
import type { User } from 'lucia';
|
||||||
|
|
||||||
export const requireAuth: MiddlewareHandler<{
|
export const requireAuth: MiddlewareHandler<{
|
||||||
Variables: {
|
Variables: {
|
||||||
session: Session
|
session: Sessions;
|
||||||
user: User
|
user: User;
|
||||||
}
|
};
|
||||||
}> = createMiddleware(async (c, next) => {
|
}> = createMiddleware(async (c, next) => {
|
||||||
const user = c.var.user
|
const user = c.var.user;
|
||||||
if (!user) throw Unauthorized('You must be logged in to access this resource')
|
if (!user) throw Unauthorized('You must be logged in to access this resource');
|
||||||
return next()
|
return next();
|
||||||
})
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||||
import { Pool } from 'pg';
|
import pg from 'pg';
|
||||||
import { config } from '../common/config';
|
import { config } from '../common/config';
|
||||||
import * as schema from '../databases/postgres/tables';
|
import * as schema from '../databases/postgres/tables';
|
||||||
|
|
||||||
// create the connection
|
// create the connection
|
||||||
export const pool = new Pool({
|
export const pool = new pg.Pool({
|
||||||
user: config.postgres.user,
|
user: config.postgres.user,
|
||||||
password: config.postgres.password,
|
password: config.postgres.password,
|
||||||
host: config.postgres.host,
|
host: config.postgres.host,
|
||||||
|
|
|
||||||
|
|
@ -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 { SessionsRepository } from '$lib/server/api/repositories/sessions.repository';
|
||||||
import { sha256 } from '@oslojs/crypto/sha2';
|
import { sha256 } from '@oslojs/crypto/sha2';
|
||||||
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding';
|
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding';
|
||||||
import { TimeSpan } from 'lucia';
|
|
||||||
import { inject, injectable } from 'tsyringe';
|
import { inject, injectable } from 'tsyringe';
|
||||||
import type { Sessions, Users } from '../databases/postgres/tables';
|
import type { Sessions, Users } from '../databases/postgres/tables';
|
||||||
|
|
||||||
|
|
@ -29,7 +29,7 @@ export class SessionsService {
|
||||||
const session: Sessions = {
|
const session: Sessions = {
|
||||||
id: sessionId,
|
id: sessionId,
|
||||||
userId,
|
userId,
|
||||||
expiresAt: new Date(Date.now() + new TimeSpan(2, 'w').seconds()),
|
expiresAt: cookieExpiresAt,
|
||||||
ipCountry,
|
ipCountry,
|
||||||
ipAddress,
|
ipAddress,
|
||||||
twoFactorAuthEnabled,
|
twoFactorAuthEnabled,
|
||||||
|
|
@ -40,8 +40,8 @@ export class SessionsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async validateSessionToken(token: string): Promise<SessionValidationResult> {
|
async validateSessionToken(token: string): Promise<SessionValidationResult> {
|
||||||
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
// const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
||||||
const sessions = await this.sessionsRepository.findBySessionId(sessionId);
|
const sessions = await this.sessionsRepository.findBySessionId(token);
|
||||||
if (sessions.length < 1) {
|
if (sessions.length < 1) {
|
||||||
return {
|
return {
|
||||||
session: null,
|
session: null,
|
||||||
|
|
@ -50,16 +50,16 @@ export class SessionsService {
|
||||||
}
|
}
|
||||||
const { user, session } = sessions[0];
|
const { user, session } = sessions[0];
|
||||||
if (Date.now() >= session.expiresAt.getTime()) {
|
if (Date.now() >= session.expiresAt.getTime()) {
|
||||||
await this.sessionsRepository.deleteBySessionId(sessionId);
|
await this.sessionsRepository.deleteBySessionId(token);
|
||||||
return {
|
return {
|
||||||
session: null,
|
session: null,
|
||||||
user: null,
|
user: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Date.now() >= session.expiresAt.getTime() - new TimeSpan(1, 'w').seconds()) {
|
if (Date.now() >= session.expiresAt.getTime() - cookieExpiresMilliseconds) {
|
||||||
session.expiresAt = new Date(Date.now() + new TimeSpan(2, 'w').seconds());
|
session.expiresAt = new Date(Date.now() + halfCookieExpiresMilliseconds);
|
||||||
await this.sessionsRepository.updateSessionExpiresAt(sessionId, session.expiresAt);
|
await this.sessionsRepository.updateSessionExpiresAt(token, session.expiresAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { session, user };
|
return { session, user };
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue