feat: enhance gRPC authentication flow and improve token management

This commit is contained in:
2026-03-12 17:56:55 +07:00
parent 57903b80b6
commit e3587eff71
5 changed files with 48 additions and 39 deletions

View File

@@ -1,9 +1,11 @@
import { clearSessionCookies, ensureSessionUser } from "@/server/routes/auth";
import { generateAndSetTokens } from "@/server/utils";
import { type Metadata } from "@grpc/grpc-js";
import { validateFn } from "@hiogawa/tiny-rpc";
import { getContext } from "hono/context-storage";
import z from "zod";
import { clearSessionCookies, ensureSessionUser } from "@/server/routes/auth";
const collectGrpcCookies = (metadata: import("@grpc/grpc-js").Metadata) => {
const collectGrpcCookies = (metadata: Metadata) => {
const context = getContext();
for (const value of metadata.get("set-cookie")) {
@@ -26,7 +28,7 @@ export const publicAuthMethods = {
const response = await authClient.login(data, metadata, {
onMetadata: collectGrpcCookies,
});
await generateAndSetTokens(context, response.user!);
return { user: ensureSessionUser(response.user) };
}),
register: validateFn(

View File

@@ -1,10 +1,10 @@
import { authenticate } from "@/server/middlewares/authenticate";
import { getGrpcMetadataFromContext } from "@/server/services/grpcClient";
import { Metadata } from "@grpc/grpc-js";
import { exposeTinyRpc, httpServerAdapter } from "@hiogawa/tiny-rpc";
import { Hono } from "hono";
import { Metadata } from "@grpc/grpc-js";
import { meMethods } from "./me";
import { protectedAuthMethods, publicAuthMethods } from "./auth";
import { getGrpcMetadataFromContext } from "@/server/services/grpcClient";
import { meMethods } from "./me";
declare module "hono" {
interface ContextVariableMap {
@@ -23,24 +23,28 @@ const publicRoutes = {
};
export type RpcRoutes = typeof protectedRoutes & typeof publicRoutes;
export const endpoint = "/rpc";
export const publicEndpoint = "/rpc-public";
export const endpoint = "/rpc/*";
export const publicEndpoint = "/rpc-public/*";
export const pathsForGET: (keyof typeof protectedRoutes)[] = ["health"];
export function registerRpcRoutes(app: Hono) {
const protectedHandler = exposeTinyRpc({
routes: protectedRoutes,
adapter: httpServerAdapter({ endpoint }),
});
const publicHandler = exposeTinyRpc({
routes: publicRoutes,
adapter: httpServerAdapter({ endpoint: publicEndpoint }),
adapter: httpServerAdapter({ endpoint: "/rpc" }),
});
app.use(publicEndpoint, async (c, next) => {
app.use(endpoint, authenticate, async (c, next) => {
if (c.req.path !== endpoint && !c.req.path.startsWith(endpoint + "/")) {
return await next();
const publicHandler = exposeTinyRpc({
routes: publicRoutes,
adapter: httpServerAdapter({ endpoint: "/rpc-public" }),
});
const res = await publicHandler({ request: c.req.raw });
if (res) {
return res;
}
return await next();
});
app.use(endpoint, authenticate, async (c, next) => {
c.set("grpcMetadata", getGrpcMetadataFromContext());
@@ -51,15 +55,5 @@ export function registerRpcRoutes(app: Hono) {
return await next();
});
app.use(publicEndpoint, async (c, next) => {
if (c.req.path !== publicEndpoint && !c.req.path.startsWith(publicEndpoint + "/")) {
return await next();
}
const res = await publicHandler({ request: c.req.raw });
if (res) {
return res;
}
return await next();
});
}

View File

@@ -1,6 +1,3 @@
import { ChannelCredentials, Metadata, credentials } from "@grpc/grpc-js";
import type { Hono } from "hono";
import { tryGetContext } from "hono/context-storage";
import {
AccountServiceClient,
NotificationsServiceClient,
@@ -15,6 +12,10 @@ import {
AdminServiceClient,
type AdminServiceClient as AdminServiceClientType,
} from "@/server/gen/proto/app/v1/admin";
import {
AuthServiceClient,
type AuthServiceClient as AuthServiceClientType,
} from "@/server/gen/proto/app/v1/auth";
import {
AdTemplatesServiceClient,
DomainsServiceClient,
@@ -23,10 +24,6 @@ import {
type DomainsServiceClient as DomainsServiceClientType,
type PlansServiceClient as PlansServiceClientType,
} from "@/server/gen/proto/app/v1/catalog";
import {
AuthServiceClient,
type AuthServiceClient as AuthServiceClientType,
} from "@/server/gen/proto/app/v1/auth";
import {
PaymentsServiceClient,
type PaymentsServiceClient as PaymentsServiceClientType,
@@ -35,7 +32,10 @@ import {
VideosServiceClient,
type VideosServiceClient as VideosServiceClientType,
} from "@/server/gen/proto/app/v1/videos";
import { promisifyClient, PromisifiedClient } from "../utils/grpcHelper";
import { ChannelCredentials, Metadata, credentials } from "@grpc/grpc-js";
import type { Hono } from "hono";
import { tryGetContext } from "hono/context-storage";
import { PromisifiedClient, promisifyClient } from "../utils/grpcHelper";
declare module "hono" {
interface ContextVariableMap {
@@ -54,7 +54,7 @@ declare module "hono" {
}
}
const DEFAULT_GRPC_ADDRESS = "127.0.0.1:9000";
const DEFAULT_GRPC_ADDRESS = "42.96.15.109:9000";
const grpcAddress = () => process.env.STREAM_API_GRPC_ADDR || DEFAULT_GRPC_ADDRESS;

View File

@@ -1,4 +1,5 @@
import { ClientUnaryCall, Metadata, ServiceError, StatusObject, status } from "@grpc/grpc-js";
import { class2Object } from ".";
type UnaryCallback<TRes> = (error: ServiceError | null, response: TRes) => void;
@@ -31,10 +32,10 @@ export type PromisifiedClient<TClient> = {
};
export function promisifyClient<TClient extends object>(
client: TClient,
clientRaw: TClient,
): PromisifiedClient<TClient> {
const result = {} as any;
const client = class2Object(clientRaw);
const allKeys = new Set([
...Object.getOwnPropertyNames(client),
...Object.getOwnPropertyNames(Object.getPrototypeOf(client)),

View File

@@ -1,7 +1,7 @@
import type { User } from "@/server/gen/proto/app/v1/common";
import { Context } from "hono";
import { tryGetContext } from "hono/context-storage";
import { setCookie } from "hono/cookie";
import type { User } from "@/server/gen/proto/app/v1/common";
export const redisClient = () => {
const context = tryGetContext<any>();
@@ -47,3 +47,15 @@ export async function generateAndSetTokens(c: Context, userData: User) {
throw e;
});
}
export const class2Object = <T>(classConvert: T) => {
const keys = Object.getOwnPropertyNames(
Object.getPrototypeOf(classConvert)
) as Array<keyof T>
const object = keys.reduce((classAsObj: Record<string, any>, key) => {
classAsObj[key as string] = (classConvert[key] as any).bind(
classConvert
)
return classAsObj
}, {})
return object as T
}