fix: replace explicit any types with proper TypeScript types

- Create explicit interfaces for Request extensions (AuthenticatedRequest, RequestWithHeaders)
- Add type definitions for WebSocket messages and SSH connection data
- Use generic types in DataCrypto methods instead of any return types
- Define proper interfaces for file manager data structures
- Replace catch block any types with unknown and proper type assertions
- Add HostConfig and TabData interfaces for Server component

Fixes 32 @typescript-eslint/no-explicit-any violations across 5 files
This commit is contained in:
ZacharyZcR
2025-10-09 18:23:16 +08:00
parent d7e98cda04
commit aa6473fb48
5 changed files with 295 additions and 165 deletions

View File

@@ -23,6 +23,18 @@ interface JWTPayload {
exp?: number;
}
interface AuthenticatedRequest extends Request {
userId?: string;
pendingTOTP?: boolean;
dataKey?: Buffer;
}
interface RequestWithHeaders extends Request {
headers: Request["headers"] & {
"x-forwarded-proto"?: string;
};
}
class AuthManager {
private static instance: AuthManager;
private systemCrypto: SystemCrypto;
@@ -163,7 +175,10 @@ class AuthManager {
});
}
getSecureCookieOptions(req: any, maxAge: number = 24 * 60 * 60 * 1000) {
getSecureCookieOptions(
req: RequestWithHeaders,
maxAge: number = 24 * 60 * 60 * 1000,
) {
return {
httpOnly: false,
secure: req.secure || req.headers["x-forwarded-proto"] === "https",
@@ -175,10 +190,11 @@ class AuthManager {
createAuthMiddleware() {
return async (req: Request, res: Response, next: NextFunction) => {
let token = req.cookies?.jwt;
const authReq = req as AuthenticatedRequest;
let token = authReq.cookies?.jwt;
if (!token) {
const authHeader = req.headers["authorization"];
const authHeader = authReq.headers["authorization"];
if (authHeader?.startsWith("Bearer ")) {
token = authHeader.split(" ")[1];
}
@@ -194,15 +210,16 @@ class AuthManager {
return res.status(401).json({ error: "Invalid token" });
}
(req as any).userId = payload.userId;
(req as any).pendingTOTP = payload.pendingTOTP;
authReq.userId = payload.userId;
authReq.pendingTOTP = payload.pendingTOTP;
next();
};
}
createDataAccessMiddleware() {
return async (req: Request, res: Response, next: NextFunction) => {
const userId = (req as any).userId;
const authReq = req as AuthenticatedRequest;
const userId = authReq.userId;
if (!userId) {
return res.status(401).json({ error: "Authentication required" });
}
@@ -215,7 +232,7 @@ class AuthManager {
});
}
(req as any).dataKey = dataKey;
authReq.dataKey = dataKey;
next();
};
}
@@ -256,8 +273,9 @@ class AuthManager {
return res.status(403).json({ error: "Admin access required" });
}
(req as any).userId = payload.userId;
(req as any).pendingTOTP = payload.pendingTOTP;
const authReq = req as AuthenticatedRequest;
authReq.userId = payload.userId;
authReq.pendingTOTP = payload.pendingTOTP;
next();
} catch (error) {
databaseLogger.error("Failed to verify admin privileges", error, {

View File

@@ -3,6 +3,19 @@ import { LazyFieldEncryption } from "./lazy-field-encryption.js";
import { UserCrypto } from "./user-crypto.js";
import { databaseLogger } from "./logger.js";
interface DatabaseInstance {
prepare: (sql: string) => {
all: (param?: unknown) => DatabaseRecord[];
get: (param?: unknown) => DatabaseRecord;
run: (...params: unknown[]) => unknown;
};
}
interface DatabaseRecord {
id: number | string;
[key: string]: unknown;
}
class DataCrypto {
private static userCrypto: UserCrypto;
@@ -10,13 +23,13 @@ class DataCrypto {
this.userCrypto = UserCrypto.getInstance();
}
static encryptRecord(
static encryptRecord<T extends Record<string, unknown>>(
tableName: string,
record: Record<string, unknown>,
record: T,
userId: string,
userDataKey: Buffer,
): any {
const encryptedRecord = { ...record };
): T {
const encryptedRecord: Record<string, unknown> = { ...record };
const recordId = record.id || "temp-" + Date.now();
for (const [fieldName, value] of Object.entries(record)) {
@@ -30,18 +43,18 @@ class DataCrypto {
}
}
return encryptedRecord;
return encryptedRecord as T;
}
static decryptRecord(
static decryptRecord<T extends Record<string, unknown>>(
tableName: string,
record: Record<string, unknown>,
record: T,
userId: string,
userDataKey: Buffer,
): any {
): T {
if (!record) return record;
const decryptedRecord = { ...record };
const decryptedRecord: Record<string, unknown> = { ...record };
const recordId = record.id;
for (const [fieldName, value] of Object.entries(record)) {
@@ -55,30 +68,25 @@ class DataCrypto {
}
}
return decryptedRecord;
return decryptedRecord as T;
}
static decryptRecords(
static decryptRecords<T extends Record<string, unknown>>(
tableName: string,
records: unknown[],
records: T[],
userId: string,
userDataKey: Buffer,
): unknown[] {
): T[] {
if (!Array.isArray(records)) return records;
return records.map((record) =>
this.decryptRecord(
tableName,
record as Record<string, unknown>,
userId,
userDataKey,
),
this.decryptRecord(tableName, record, userId, userDataKey),
);
}
static async migrateUserSensitiveFields(
userId: string,
userDataKey: Buffer,
db: any,
db: DatabaseInstance,
): Promise<{
migrated: boolean;
migratedTables: string[];
@@ -102,7 +110,7 @@ class DataCrypto {
const sshDataRecords = db
.prepare("SELECT * FROM ssh_data WHERE user_id = ?")
.all(userId);
.all(userId) as DatabaseRecord[];
for (const record of sshDataRecords) {
const sensitiveFields =
LazyFieldEncryption.getSensitiveFieldsForTable("ssh_data");
@@ -137,7 +145,7 @@ class DataCrypto {
const sshCredentialsRecords = db
.prepare("SELECT * FROM ssh_credentials WHERE user_id = ?")
.all(userId);
.all(userId) as DatabaseRecord[];
for (const record of sshCredentialsRecords) {
const sensitiveFields =
LazyFieldEncryption.getSensitiveFieldsForTable("ssh_credentials");
@@ -174,7 +182,7 @@ class DataCrypto {
const userRecord = db
.prepare("SELECT * FROM users WHERE id = ?")
.get(userId);
.get(userId) as DatabaseRecord | undefined;
if (userRecord) {
const sensitiveFields =
LazyFieldEncryption.getSensitiveFieldsForTable("users");
@@ -225,7 +233,7 @@ class DataCrypto {
static async reencryptUserDataAfterPasswordReset(
userId: string,
newUserDataKey: Buffer,
db: any,
db: DatabaseInstance,
): Promise<{
success: boolean;
reencryptedTables: string[];
@@ -267,17 +275,21 @@ class DataCrypto {
try {
const records = db
.prepare(`SELECT * FROM ${table} WHERE user_id = ?`)
.all(userId);
.all(userId) as DatabaseRecord[];
for (const record of records) {
const recordId = record.id.toString();
const updatedRecord: DatabaseRecord = { ...record };
let needsUpdate = false;
const updatedRecord = { ...record };
for (const fieldName of fields) {
const fieldValue = record[fieldName];
if (fieldValue && fieldValue.trim() !== "") {
if (
fieldValue &&
typeof fieldValue === "string" &&
fieldValue.trim() !== ""
) {
try {
const reencryptedValue = FieldCrypto.encryptField(
fieldValue,
@@ -389,29 +401,29 @@ class DataCrypto {
return userDataKey;
}
static encryptRecordForUser(
static encryptRecordForUser<T extends Record<string, unknown>>(
tableName: string,
record: Record<string, unknown>,
record: T,
userId: string,
): any {
): T {
const userDataKey = this.validateUserAccess(userId);
return this.encryptRecord(tableName, record, userId, userDataKey);
}
static decryptRecordForUser(
static decryptRecordForUser<T extends Record<string, unknown>>(
tableName: string,
record: Record<string, unknown>,
record: T,
userId: string,
): any {
): T {
const userDataKey = this.validateUserAccess(userId);
return this.decryptRecord(tableName, record, userId, userDataKey);
}
static decryptRecordsForUser(
static decryptRecordsForUser<T extends Record<string, unknown>>(
tableName: string,
records: unknown[],
records: T[],
userId: string,
): unknown[] {
): T[] {
const userDataKey = this.validateUserAccess(userId);
return this.decryptRecords(tableName, records, userId, userDataKey);
}