add cache to auth

This commit is contained in:
2025-01-24 19:24:27 +05:30
parent 59ce61d3a6
commit 405338c314
8 changed files with 125 additions and 67 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
node_modules
.env
dist
test

View File

@@ -5,7 +5,8 @@
"main": "dist/index.js",
"scripts": {
"build": "tsc -p tsconfig.json",
"start": "tsc -w & node --watch --env-file=.env dist/index.js"
"start": "tsc -w & node --watch --env-file=.env dist/index.js",
"load_test": "export $(cat .env | xargs) && k6 run ./test/loadTest.js"
},
"keywords": [],
"author": "",
@@ -22,12 +23,14 @@
"fastify": "^5.2.0",
"fastify-type-provider-zod": "^4.0.2",
"fastify-zod": "^1.4.0",
"lru-cache": "^11.0.2",
"mongoose": "^8.9.0",
"zod": "^3.24.1"
},
"devDependencies": {
"@types/bcrypt": "^5.0.2",
"@types/node": "^22.10.2",
"k6": "^0.0.0",
"typescript": "^5.7.2"
}
}

17
pnpm-lock.yaml generated
View File

@@ -41,6 +41,9 @@ importers:
fastify-zod:
specifier: ^1.4.0
version: 1.4.0(fastify@5.2.0)
lru-cache:
specifier: ^11.0.2
version: 11.0.2
mongoose:
specifier: ^8.9.0
version: 8.9.0
@@ -54,6 +57,9 @@ importers:
'@types/node':
specifier: ^22.10.2
version: 22.10.2
k6:
specifier: ^0.0.0
version: 0.0.0
typescript:
specifier: ^5.7.2
version: 5.7.2
@@ -790,6 +796,9 @@ packages:
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
k6@0.0.0:
resolution: {integrity: sha512-GAQSWayS2+LjbH5bkRi+pMPYyP1JSp7o+4j58ANZ762N/RH/SdlAT3CHHztnn8s/xgg8kYNM24Gd2IPo9b5W+g==}
kareem@2.6.3:
resolution: {integrity: sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==}
engines: {node: '>=12.0.0'}
@@ -800,6 +809,10 @@ packages:
lower-case@2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
lru-cache@11.0.2:
resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==}
engines: {node: 20 || >=22}
make-dir@3.1.0:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
engines: {node: '>=8'}
@@ -2474,6 +2487,8 @@ snapshots:
json-schema-traverse@1.0.0: {}
k6@0.0.0: {}
kareem@2.6.3: {}
light-my-request@6.3.0:
@@ -2486,6 +2501,8 @@ snapshots:
dependencies:
tslib: 2.8.1
lru-cache@11.0.2: {}
make-dir@3.1.0:
dependencies:
semver: 6.3.1

View File

@@ -3,8 +3,13 @@ import { FastifyReply, FastifyRequest } from "fastify";
import { getToken } from "./tokens/token.service";
import { Claim } from "./utils/claims";
import { OAuth2Namespace } from "@fastify/oauth2";
import { getSession } from "./auth/auth.service";
import { roles, rules } from "./utils/roles";
import { deleteSession, getSession } from "./auth/auth.service";
import { rules } from "./utils/roles";
import {
cacheSession,
getCachedSession,
removeCachedSession,
} from "./utils/cache";
export type AuthenticatedUser = {
sid?: string;
@@ -14,6 +19,7 @@ export type AuthenticatedUser = {
role?: string;
tenantId: string;
claims: Array<Claim>;
expiry?: string;
};
declare module "fastify" {
@@ -56,35 +62,21 @@ export async function authHandler(req: FastifyRequest, res: FastifyReply) {
claims: tokenInDb.claims as Array<Claim>,
};
} else {
const sessionInDb = await getSession(authHeader);
if (sessionInDb === null)
let session = getCachedSession(authHeader);
if (!session) {
session = await getSession(authHeader);
cacheSession(authHeader, session);
}
if (!session) return res.code(401).send({ error: "invalid_token" });
if (new Date() > new Date(session.expiry)) {
removeCachedSession(authHeader);
await deleteSession(authHeader);
return res.code(401).send({ error: "invalid_token" });
if (new Date() > new Date(sessionInDb.expiresAt)) {
await sessionInDb.deleteOne();
return res.code(401).send({ error: "session_expired" });
}
//@ts-ignore
if (!rules[sessionInDb.user.role]) {
return res.code(401).send({ error: "no role" });
}
req.user = {
sid: authHeader,
//@ts-ignore
type: sessionInDb.user.type,
//@ts-ignore
userId: sessionInDb.user.id,
//@ts-ignore
tenantId: sessionInDb.user.tenantId,
//@ts-ignore
orgId: sessionInDb.user.orgId,
//@ts-ignore
role: sessionInDb.user.role,
//@ts-ignore
claims: rules[sessionInDb.user.role].claims,
};
req.user = session;
}
}

View File

@@ -1,6 +1,6 @@
import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import { getUserByEmail, updateUser } from "../user/user.service";
import { createSession, getSession } from "./auth.service";
import { createSession, deleteSession, getSession } from "./auth.service";
export async function authRoutes(fastify: FastifyInstance) {
fastify.get(
@@ -55,10 +55,7 @@ export async function authRoutes(fastify: FastifyInstance) {
if (!req.headers.authorization) return res.code(200).send();
const auth = req.headers.authorization.split(" ")[1];
const sessionInDb = await getSession(auth);
if (sessionInDb === null) return res.code(200).send();
await sessionInDb.deleteOne();
await deleteSession(auth);
return res.code(200).send();
});
}

View File

@@ -1,4 +1,7 @@
import { AuthenticatedUser } from "../auth";
import { userModel } from "../user/user.schema";
import { generateToken } from "../utils/id";
import { rules } from "../utils/roles";
import { sessionModel } from "./auth.schema";
export async function createSession(userId: string, ip?: string, ua?: string) {
@@ -19,6 +22,28 @@ export async function createSession(userId: string, ip?: string, ua?: string) {
return newSession;
}
export async function getSession(sessionId: string) {
return await sessionModel.findOne({ sid: sessionId }).populate("user");
export async function getSession(
sessionId: string
): Promise<AuthenticatedUser | null> {
console.log("DB HIT");
const session = await sessionModel.findOne({ sid: sessionId });
if (session === null) return null;
const user = await userModel.findById(session.user);
if (user === null) return null;
return {
sid: session.id,
type: "user",
userId: user.id,
orgId: user.orgId ? user.orgId.toString() : null,
role: user.role,
tenantId: user.tenantId,
claims: rules[user.role].claims ?? [],
expiry: new Date(session.expiresAt).toISOString(),
};
}
export async function deleteSession(sessionId: string) {
return await sessionModel.deleteOne({ sid: sessionId });
}

View File

@@ -1,37 +1,41 @@
import { buildJsonSchemas } from "fastify-zod";
import mongoose from "mongoose";
import mongoose, { InferSchemaType } from "mongoose";
import { z } from "zod";
import { roles } from "../utils/roles";
export const userModel = mongoose.model(
"user",
new mongoose.Schema({
tenantId: {
type: String,
required: true,
},
pid: {
type: String,
unique: true,
required: true,
},
orgId: mongoose.Types.ObjectId,
firstName: String,
lastName: String,
name: String,
email: {
type: String,
unique: true,
required: true,
},
avatar: String,
status: String,
role: String,
createdAt: Date,
createdBy: mongoose.Types.ObjectId,
lastLogin: Date,
})
);
const userSchema = new mongoose.Schema({
tenantId: {
type: String,
required: true,
},
pid: {
type: String,
unique: true,
required: true,
},
orgId: mongoose.Types.ObjectId,
firstName: String,
lastName: String,
name: String,
email: {
type: String,
unique: true,
required: true,
},
avatar: String,
status: String,
role: {
type: String,
required: true,
},
createdAt: Date,
createdBy: mongoose.Types.ObjectId,
lastLogin: Date,
});
export const userModel = mongoose.model("user", userSchema);
export type User = InferSchemaType<typeof userSchema>;
const userCore = {
firstName: z.string().max(30),

19
src/utils/cache.ts Normal file
View File

@@ -0,0 +1,19 @@
import { LRUCache } from "lru-cache";
import { AuthenticatedUser } from "../auth";
const sessionCache = new LRUCache<string, AuthenticatedUser>({
max: 100,
ttl: 1000 * 60 * 60,
});
export function cacheSession(key: string, user: AuthenticatedUser) {
sessionCache.set(key, user);
}
export function getCachedSession(key: string) {
return sessionCache.get(key);
}
export function removeCachedSession(key: string) {
sessionCache.delete(key);
}