add cache to auth
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
node_modules
|
||||
.env
|
||||
dist
|
||||
test
|
||||
@@ -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
17
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
48
src/auth.ts
48
src/auth.ts
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
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({
|
||||
const userSchema = new mongoose.Schema({
|
||||
tenantId: {
|
||||
type: String,
|
||||
required: true,
|
||||
@@ -26,12 +24,18 @@ export const userModel = mongoose.model(
|
||||
},
|
||||
avatar: String,
|
||||
status: String,
|
||||
role: 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
19
src/utils/cache.ts
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user