ref: extract authentication and remove db query in it, add prom metric collection
This commit is contained in:
parent
1224039d8b
commit
84afe7c3ad
@ -16,6 +16,10 @@ spec:
|
|||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: handy-server
|
app: handy-server
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/port: "9090"
|
||||||
|
prometheus.io/path: "/metrics"
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: handy
|
- name: handy
|
||||||
|
@ -11,6 +11,7 @@ import { onShutdown } from "@/utils/shutdown";
|
|||||||
import { allocateSessionSeq, allocateUserSeq } from "@/services/seq";
|
import { allocateSessionSeq, allocateUserSeq } from "@/services/seq";
|
||||||
import { randomKeyNaked } from "@/utils/randomKeyNaked";
|
import { randomKeyNaked } from "@/utils/randomKeyNaked";
|
||||||
import { AsyncLock } from "@/utils/lock";
|
import { AsyncLock } from "@/utils/lock";
|
||||||
|
import { auth } from "@/modules/auth";
|
||||||
import {
|
import {
|
||||||
EventRouter,
|
EventRouter,
|
||||||
ClientConnection,
|
ClientConnection,
|
||||||
@ -40,7 +41,7 @@ import { activityCache } from "@/modules/sessionCache";
|
|||||||
|
|
||||||
declare module 'fastify' {
|
declare module 'fastify' {
|
||||||
interface FastifyRequest {
|
interface FastifyRequest {
|
||||||
user: Account;
|
userId: string;
|
||||||
}
|
}
|
||||||
interface FastifyInstance {
|
interface FastifyInstance {
|
||||||
authenticate: any;
|
authenticate: any;
|
||||||
@ -52,14 +53,6 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
|
|
||||||
// Configure
|
// Configure
|
||||||
log('Starting API...');
|
log('Starting API...');
|
||||||
const tokenGenerator = await privacyKit.createPersistentTokenGenerator({
|
|
||||||
service: 'handy',
|
|
||||||
seed: process.env.HANDY_MASTER_SECRET!
|
|
||||||
});
|
|
||||||
const tokenVerifier = await privacyKit.createPersistentTokenVerifier({
|
|
||||||
service: 'handy',
|
|
||||||
publicKey: tokenGenerator.publicKey
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start API
|
// Start API
|
||||||
const app = fastify({
|
const app = fastify({
|
||||||
@ -89,25 +82,14 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
}
|
}
|
||||||
|
|
||||||
const token = authHeader.substring(7);
|
const token = authHeader.substring(7);
|
||||||
const verified = await tokenVerifier.verify(token);
|
const verified = await auth.verifyToken(token);
|
||||||
if (!verified) {
|
if (!verified) {
|
||||||
log({ module: 'auth-decorator' }, `Auth failed - invalid token`);
|
log({ module: 'auth-decorator' }, `Auth failed - invalid token`);
|
||||||
return reply.code(401).send({ error: 'Invalid token' });
|
return reply.code(401).send({ error: 'Invalid token' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user from database
|
log({ module: 'auth-decorator' }, `Auth success - user: ${verified.userId}`);
|
||||||
const user = await db.account.findUnique({
|
request.userId = verified.userId;
|
||||||
where: { id: verified.user as string }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
log({ module: 'auth-decorator' }, `Auth failed - user not found: ${verified.user}`);
|
|
||||||
return reply.code(401).send({ error: 'User not found' });
|
|
||||||
}
|
|
||||||
|
|
||||||
log({ module: 'auth-decorator' }, `Auth success - user: ${user.id}`);
|
|
||||||
|
|
||||||
request.user = user;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return reply.code(401).send({ error: 'Authentication failed' });
|
return reply.code(401).send({ error: 'Authentication failed' });
|
||||||
}
|
}
|
||||||
@ -144,7 +126,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
|
|
||||||
return reply.send({
|
return reply.send({
|
||||||
success: true,
|
success: true,
|
||||||
token: await tokenGenerator.new({ user: user.id })
|
token: await auth.createToken(user.id)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -183,7 +165,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (answer.response && answer.responseAccountId) {
|
if (answer.response && answer.responseAccountId) {
|
||||||
const token = await tokenGenerator.new({ user: answer.responseAccountId!, extras: { session: answer.id } });
|
const token = await auth.createToken(answer.responseAccountId!, { session: answer.id });
|
||||||
return reply.send({
|
return reply.send({
|
||||||
state: 'authorized',
|
state: 'authorized',
|
||||||
token: token,
|
token: token,
|
||||||
@ -204,7 +186,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, async (request, reply) => {
|
}, async (request, reply) => {
|
||||||
log({ module: 'auth-response' }, `Auth response endpoint hit - user: ${request.user?.id || 'NO USER'}, publicKey: ${request.body.publicKey.substring(0, 20)}...`);
|
log({ module: 'auth-response' }, `Auth response endpoint hit - user: ${request.userId}, publicKey: ${request.body.publicKey.substring(0, 20)}...`);
|
||||||
const publicKey = privacyKit.decodeBase64(request.body.publicKey);
|
const publicKey = privacyKit.decodeBase64(request.body.publicKey);
|
||||||
const isValid = tweetnacl.box.publicKeyLength === publicKey.length;
|
const isValid = tweetnacl.box.publicKeyLength === publicKey.length;
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
@ -229,7 +211,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
if (!authRequest.response) {
|
if (!authRequest.response) {
|
||||||
await db.terminalAuthRequest.update({
|
await db.terminalAuthRequest.update({
|
||||||
where: { id: authRequest.id },
|
where: { id: authRequest.id },
|
||||||
data: { response: request.body.response, responseAccountId: request.user.id }
|
data: { response: request.body.response, responseAccountId: request.userId }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return reply.send({ success: true });
|
return reply.send({ success: true });
|
||||||
@ -268,7 +250,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (answer.response && answer.responseAccountId) {
|
if (answer.response && answer.responseAccountId) {
|
||||||
const token = await tokenGenerator.new({ user: answer.responseAccountId! });
|
const token = await auth.createToken(answer.responseAccountId!);
|
||||||
return reply.send({
|
return reply.send({
|
||||||
state: 'authorized',
|
state: 'authorized',
|
||||||
token: token,
|
token: token,
|
||||||
@ -303,7 +285,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
if (!authRequest.response) {
|
if (!authRequest.response) {
|
||||||
await db.accountAuthRequest.update({
|
await db.accountAuthRequest.update({
|
||||||
where: { id: authRequest.id },
|
where: { id: authRequest.id },
|
||||||
data: { response: request.body.response, responseAccountId: request.user.id }
|
data: { response: request.body.response, responseAccountId: request.userId }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return reply.send({ success: true });
|
return reply.send({ success: true });
|
||||||
@ -372,7 +354,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
typed.get('/v1/sessions', {
|
typed.get('/v1/sessions', {
|
||||||
preHandler: app.authenticate,
|
preHandler: app.authenticate,
|
||||||
}, async (request, reply) => {
|
}, async (request, reply) => {
|
||||||
const userId = request.user.id;
|
const userId = request.userId;
|
||||||
|
|
||||||
const sessions = await db.session.findMany({
|
const sessions = await db.session.findMany({
|
||||||
where: { accountId: userId },
|
where: { accountId: userId },
|
||||||
@ -443,7 +425,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
},
|
},
|
||||||
preHandler: app.authenticate
|
preHandler: app.authenticate
|
||||||
}, async (request, reply) => {
|
}, async (request, reply) => {
|
||||||
const userId = request.user.id;
|
const userId = request.userId;
|
||||||
const { tag, metadata } = request.body;
|
const { tag, metadata } = request.body;
|
||||||
|
|
||||||
const session = await db.session.findFirst({
|
const session = await db.session.findFirst({
|
||||||
@ -535,7 +517,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
},
|
},
|
||||||
preHandler: app.authenticate
|
preHandler: app.authenticate
|
||||||
}, async (request, reply) => {
|
}, async (request, reply) => {
|
||||||
const userId = request.user.id;
|
const userId = request.userId;
|
||||||
const { token } = request.body;
|
const { token } = request.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -578,7 +560,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
},
|
},
|
||||||
preHandler: app.authenticate
|
preHandler: app.authenticate
|
||||||
}, async (request, reply) => {
|
}, async (request, reply) => {
|
||||||
const userId = request.user.id;
|
const userId = request.userId;
|
||||||
const { token } = request.params;
|
const { token } = request.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -599,7 +581,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
typed.get('/v1/push-tokens', {
|
typed.get('/v1/push-tokens', {
|
||||||
preHandler: app.authenticate
|
preHandler: app.authenticate
|
||||||
}, async (request, reply) => {
|
}, async (request, reply) => {
|
||||||
const userId = request.user.id;
|
const userId = request.userId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const tokens = await db.accountPushToken.findMany({
|
const tokens = await db.accountPushToken.findMany({
|
||||||
@ -640,9 +622,18 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
}
|
}
|
||||||
}, async (request, reply) => {
|
}, async (request, reply) => {
|
||||||
try {
|
try {
|
||||||
|
const user = await db.account.findUnique({
|
||||||
|
where: { id: request.userId },
|
||||||
|
select: { settings: true, settingsVersion: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return reply.code(500).send({ error: 'Failed to get account settings' });
|
||||||
|
}
|
||||||
|
|
||||||
return reply.send({
|
return reply.send({
|
||||||
settings: request.user.settings,
|
settings: user.settings,
|
||||||
settingsVersion: request.user.settingsVersion
|
settingsVersion: user.settingsVersion
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return reply.code(500).send({ error: 'Failed to get account settings' });
|
return reply.code(500).send({ error: 'Failed to get account settings' });
|
||||||
@ -674,17 +665,30 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
},
|
},
|
||||||
preHandler: app.authenticate
|
preHandler: app.authenticate
|
||||||
}, async (request, reply) => {
|
}, async (request, reply) => {
|
||||||
const userId = request.user.id;
|
const userId = request.userId;
|
||||||
const { settings, expectedVersion } = request.body;
|
const { settings, expectedVersion } = request.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Get current user data for version check
|
||||||
|
const currentUser = await db.account.findUnique({
|
||||||
|
where: { id: userId },
|
||||||
|
select: { settings: true, settingsVersion: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!currentUser) {
|
||||||
|
return reply.code(500).send({
|
||||||
|
success: false,
|
||||||
|
error: 'Failed to update account settings'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Check current version
|
// Check current version
|
||||||
if (request.user.settingsVersion !== expectedVersion) {
|
if (currentUser.settingsVersion !== expectedVersion) {
|
||||||
return reply.code(200).send({
|
return reply.code(200).send({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'version-mismatch',
|
error: 'version-mismatch',
|
||||||
currentVersion: request.user.settingsVersion,
|
currentVersion: currentUser.settingsVersion,
|
||||||
currentSettings: request.user.settings
|
currentSettings: currentUser.settings
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -754,7 +758,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
},
|
},
|
||||||
preHandler: app.authenticate
|
preHandler: app.authenticate
|
||||||
}, async (request, reply) => {
|
}, async (request, reply) => {
|
||||||
const userId = request.user.id;
|
const userId = request.userId;
|
||||||
const { sessionId, startTime, endTime, groupBy } = request.body;
|
const { sessionId, startTime, endTime, groupBy } = request.body;
|
||||||
const actualGroupBy = groupBy || 'day';
|
const actualGroupBy = groupBy || 'day';
|
||||||
|
|
||||||
@ -886,7 +890,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
},
|
},
|
||||||
preHandler: app.authenticate
|
preHandler: app.authenticate
|
||||||
}, async (request, reply) => {
|
}, async (request, reply) => {
|
||||||
const userId = request.user.id;
|
const userId = request.userId;
|
||||||
const { sessionId } = request.params;
|
const { sessionId } = request.params;
|
||||||
|
|
||||||
// Verify session belongs to user
|
// Verify session belongs to user
|
||||||
@ -940,7 +944,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, async (request, reply) => {
|
}, async (request, reply) => {
|
||||||
const userId = request.user.id;
|
const userId = request.userId;
|
||||||
const { id, metadata, daemonState } = request.body;
|
const { id, metadata, daemonState } = request.body;
|
||||||
|
|
||||||
// Check if machine exists (like sessions do)
|
// Check if machine exists (like sessions do)
|
||||||
@ -1017,7 +1021,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
typed.get('/v1/machines', {
|
typed.get('/v1/machines', {
|
||||||
preHandler: app.authenticate,
|
preHandler: app.authenticate,
|
||||||
}, async (request, reply) => {
|
}, async (request, reply) => {
|
||||||
const userId = request.user.id;
|
const userId = request.userId;
|
||||||
|
|
||||||
const machines = await db.machine.findMany({
|
const machines = await db.machine.findMany({
|
||||||
where: { accountId: userId },
|
where: { accountId: userId },
|
||||||
@ -1047,7 +1051,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, async (request, reply) => {
|
}, async (request, reply) => {
|
||||||
const userId = request.user.id;
|
const userId = request.userId;
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
|
|
||||||
const machine = await db.machine.findFirst({
|
const machine = await db.machine.findFirst({
|
||||||
@ -1195,7 +1199,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const verified = await tokenVerifier.verify(token);
|
const verified = await auth.verifyToken(token);
|
||||||
if (!verified) {
|
if (!verified) {
|
||||||
log({ module: 'websocket' }, `Invalid token provided`);
|
log({ module: 'websocket' }, `Invalid token provided`);
|
||||||
socket.emit('error', { message: 'Invalid authentication token' });
|
socket.emit('error', { message: 'Invalid authentication token' });
|
||||||
@ -1203,7 +1207,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = verified.user as string;
|
const userId = verified.userId;
|
||||||
log({ module: 'websocket' }, `Token verified: ${userId}, clientType: ${clientType || 'user-scoped'}, sessionId: ${sessionId || 'none'}, machineId: ${machineId || 'none'}, socketId: ${socket.id}`);
|
log({ module: 'websocket' }, `Token verified: ${userId}, clientType: ${clientType || 'user-scoped'}, sessionId: ${sessionId || 'none'}, machineId: ${machineId || 'none'}, socketId: ${socket.id}`);
|
||||||
|
|
||||||
// Store connection based on type
|
// Store connection based on type
|
||||||
|
@ -6,6 +6,7 @@ import { startTimeout } from "./app/timeout";
|
|||||||
import { redis } from "./services/redis";
|
import { redis } from "./services/redis";
|
||||||
import { startMetricsServer } from "@/app/metrics";
|
import { startMetricsServer } from "@/app/metrics";
|
||||||
import { activityCache } from "@/modules/sessionCache";
|
import { activityCache } from "@/modules/sessionCache";
|
||||||
|
import { auth } from "./modules/auth";
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
|
||||||
@ -19,6 +20,9 @@ async function main() {
|
|||||||
});
|
});
|
||||||
await redis.ping();
|
await redis.ping();
|
||||||
|
|
||||||
|
// Initialize auth module
|
||||||
|
await auth.init();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Start
|
// Start
|
||||||
//
|
//
|
||||||
|
146
sources/modules/auth.ts
Normal file
146
sources/modules/auth.ts
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import * as privacyKit from "privacy-kit";
|
||||||
|
import { log } from "@/utils/log";
|
||||||
|
|
||||||
|
interface TokenCacheEntry {
|
||||||
|
userId: string;
|
||||||
|
extras?: any;
|
||||||
|
cachedAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AuthTokens {
|
||||||
|
generator: Awaited<ReturnType<typeof privacyKit.createPersistentTokenGenerator>>;
|
||||||
|
verifier: Awaited<ReturnType<typeof privacyKit.createPersistentTokenVerifier>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthModule {
|
||||||
|
private tokenCache = new Map<string, TokenCacheEntry>();
|
||||||
|
private tokens: AuthTokens | null = null;
|
||||||
|
|
||||||
|
async init(): Promise<void> {
|
||||||
|
if (this.tokens) {
|
||||||
|
return; // Already initialized
|
||||||
|
}
|
||||||
|
|
||||||
|
log({ module: 'auth' }, 'Initializing auth module...');
|
||||||
|
|
||||||
|
const generator = await privacyKit.createPersistentTokenGenerator({
|
||||||
|
service: 'handy',
|
||||||
|
seed: process.env.HANDY_MASTER_SECRET!
|
||||||
|
});
|
||||||
|
|
||||||
|
const verifier = await privacyKit.createPersistentTokenVerifier({
|
||||||
|
service: 'handy',
|
||||||
|
publicKey: generator.publicKey
|
||||||
|
});
|
||||||
|
|
||||||
|
this.tokens = { generator, verifier };
|
||||||
|
|
||||||
|
log({ module: 'auth' }, 'Auth module initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
async createToken(userId: string, extras?: any): Promise<string> {
|
||||||
|
if (!this.tokens) {
|
||||||
|
throw new Error('Auth module not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload: any = { user: userId };
|
||||||
|
if (extras) {
|
||||||
|
payload.extras = extras;
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = await this.tokens.generator.new(payload);
|
||||||
|
|
||||||
|
// Cache the token immediately
|
||||||
|
this.tokenCache.set(token, {
|
||||||
|
userId,
|
||||||
|
extras,
|
||||||
|
cachedAt: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyToken(token: string): Promise<{ userId: string; extras?: any } | null> {
|
||||||
|
// Check cache first
|
||||||
|
const cached = this.tokenCache.get(token);
|
||||||
|
if (cached) {
|
||||||
|
return {
|
||||||
|
userId: cached.userId,
|
||||||
|
extras: cached.extras
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache miss - verify token
|
||||||
|
if (!this.tokens) {
|
||||||
|
throw new Error('Auth module not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const verified = await this.tokens.verifier.verify(token);
|
||||||
|
if (!verified) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = verified.user as string;
|
||||||
|
const extras = verified.extras;
|
||||||
|
|
||||||
|
// Cache the result permanently
|
||||||
|
this.tokenCache.set(token, {
|
||||||
|
userId,
|
||||||
|
extras,
|
||||||
|
cachedAt: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
return { userId, extras };
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
log({ module: 'auth', level: 'error' }, `Token verification failed: ${error}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidateUserTokens(userId: string): void {
|
||||||
|
// Remove all tokens for a specific user
|
||||||
|
// This is expensive but rarely needed
|
||||||
|
for (const [token, entry] of this.tokenCache.entries()) {
|
||||||
|
if (entry.userId === userId) {
|
||||||
|
this.tokenCache.delete(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log({ module: 'auth' }, `Invalidated tokens for user: ${userId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidateToken(token: string): void {
|
||||||
|
this.tokenCache.delete(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCacheStats(): { size: number; oldestEntry: number | null } {
|
||||||
|
if (this.tokenCache.size === 0) {
|
||||||
|
return { size: 0, oldestEntry: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
let oldest = Date.now();
|
||||||
|
for (const entry of this.tokenCache.values()) {
|
||||||
|
if (entry.cachedAt < oldest) {
|
||||||
|
oldest = entry.cachedAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
size: this.tokenCache.size,
|
||||||
|
oldestEntry: oldest
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup old entries (optional - can be called periodically)
|
||||||
|
cleanup(): void {
|
||||||
|
// Note: Since tokens are cached "forever" as requested,
|
||||||
|
// we don't do automatic cleanup. This method exists if needed later.
|
||||||
|
const stats = this.getCacheStats();
|
||||||
|
log({ module: 'auth' }, `Token cache size: ${stats.size} entries`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global instance
|
||||||
|
export const auth = new AuthModule();
|
Loading…
Reference in New Issue
Block a user