Source

services/auth.service.js

import { env } from "@config/env";
import grpc from "@grpc/grpc-js";
import protoLoader from "@grpc/proto-loader";
import { getLogger } from "@utils/asyncLocalStorage";
import path, { dirname } from "node:path";
import { fileURLToPath } from "node:url";
/**
 * Auth gRPC Service
 * @category Services
 */
export class AuthGrpcService {
    client = null;
    /**
     * Constructor
     * @category Services
     * @param {string} accessToken - The access token to authenticate the client.
     */
    constructor(accessToken) {
        if (!this.client) {
            const __filename = fileURLToPath(import.meta.url);
            const __dirname = dirname(__filename);
            const PROTO_PATH = path.resolve(__dirname, "../grpc/auth.proto");
            const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
                keepCase: true,
                longs: String,
                enums: String,
                defaults: false,
                oneofs: true,
            });
            const loaded = grpc.loadPackageDefinition(packageDefinition);
            this.client = new loaded.auth.Auth(`${env.AUTH_GRPC_HOST}:${env.AUTH_GRPC_PORT}`, grpc.credentials.createInsecure(), {
                interceptors: [this.authInterceptor(accessToken || null)],
            });
        }
    }
    /**
     * Creates a gRPC interceptor to add an authorization token to request metadata.
     * @category Services
     * @param {string | null} token - The Bearer token to attach to outgoing gRPC requests. If `null`, no authorization header is added.
     * @returns {Function} A gRPC interceptor function that can be used with gRPC clients.
     */
    authInterceptor(token) {
        return function (options, nextCall) {
            return new grpc.InterceptingCall(nextCall(options), {
                start: function (metadata, listener, next) {
                    if (token) {
                        metadata.add("authorization", `Bearer ${token}`);
                    }
                    next(metadata, listener);
                },
            });
        };
    }
    /**
     * Fetches a user by ID via the Auth gRPC service.
     * @category Services
     * @param {string} userId - The unique identifier of the user to fetch.
     * @returns {Promise<User>} - The user object corresponding to the given ID.
     * @throws {Error} If the user is not found or if the gRPC request fails.
     */
    async getUserById(userId) {
        return new Promise((resolve, reject) => {
            const logger = getLogger();
            const client = this.client;
            client?.getUserById({ id: userId }, (error, response) => {
                if (error) {
                    logger.error({ error, userId }, "gRPC getUserById failed");
                    reject(error);
                    return;
                }
                if (!response?.user) {
                    reject(new Error("User not found"));
                    return;
                }
                resolve(response.user);
            });
        });
    }
}