Files
edr-platform/apps/edr-freight-api/src/modules/notification-inbox/ws-auth.service.ts
2026-07-06 06:51:38 +00:00

52 lines
1.9 KiB
TypeScript

import { Injectable, Logger } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { verifyToken } from "@tria-plc/api-common/utils/token";
import { ESessionStatus } from "@tria-plc/api-common/utils/enums/user.enum";
import { Session } from "@tria-plc/iamapi-common/entities/iam/user/session.entity";
/**
* Authenticates a WebSocket handshake by mirroring the HTTP JwtGuard: the access
* token payload is only a *session* pointer (`{ id: <sessionId> }`), not the
* user — so we verify the signature (`verifyToken`), then load the IAM session
* and require it to be ACTIVE and unexpired, and read the real user id out of
* `session.userInfo`. There is no context-free verifier in the auth package, so
* this lookup is unavoidable; using the typed `Session` entity (rather than raw
* SQL) keeps it column-rename-safe and consistent with the package's own model.
*
* Returns the IAM user id, or null for any invalid/expired/revoked/malformed token.
*/
@Injectable()
export class WsAuthService {
private readonly logger = new Logger(WsAuthService.name);
constructor(
@InjectRepository(Session)
private readonly sessions: Repository<Session>,
) {}
async resolveUserId(token?: string): Promise<string | null> {
if (!token) return null;
try {
const payload = verifyToken(token) as { id?: string };
const sessionId = payload?.id;
if (!sessionId) return null;
const session = await this.sessions.findOne({
where: { id: sessionId },
});
if (!session) return null;
if (session.status !== ESessionStatus.ACTIVE) return null;
if (!session.expiryTime || new Date(session.expiryTime) <= new Date()) {
return null;
}
return session.userInfo?.id ?? null;
} catch (err) {
this.logger.debug(`WS auth rejected: ${(err as Error).message}`);
return null;
}
}
}