winofy-front/src/lib/auth/gooyal.ts
2026-08-31 16:03:11 +03:30

67 lines
2.2 KiB
TypeScript

import "server-only";
import { logApiRequest, logApiResponse } from "@/lib/api/dev-logger";
const ACCOUNTS_BASE_URL = process.env.GOOYAL_ACCOUNTS_BASE_URL!;
const CLIENT_ID = process.env.GOOYAL_CLIENT_ID!;
const CLIENT_SECRET = process.env.GOOYAL_CLIENT_SECRET!;
function basicAuthHeader() {
return "Basic " + Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64");
}
const ERROR_MESSAGES_FA: Record<string, string> = {
invalid_grant: "کد تایید نامعتبر یا منقضی شده است.",
invalid_client: "خطا در پیکربندی سرویس احراز هویت. لطفاً بعداً تلاش کنید.",
invalid_request: "درخواست نامعتبر است.",
unsupported_grant_type: "خطا در پیکربندی سرویس احراز هویت.",
};
export class GooyalAuthError extends Error {
constructor(
public status: number,
public code: string,
description?: string,
) {
super(ERROR_MESSAGES_FA[code] ?? description ?? code);
}
}
export interface GooyalTokenResponse {
access_token: string;
refresh_token?: string;
token_type: string;
expires_in: number;
scope: string;
}
export async function refreshAccessToken(refreshToken: string): Promise<GooyalTokenResponse> {
const url = `${ACCOUNTS_BASE_URL}/oauth2/token/`;
const body = new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
});
// Never print real tokens to the terminal — log the request shape, not the secret values.
logApiRequest("gooyal", "POST", url, { grant_type: "refresh_token", refresh_token: "<redacted>" });
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: basicAuthHeader(),
"Content-Type": "application/x-www-form-urlencoded",
},
body: body.toString(),
cache: "no-store",
});
const data = await res.json().catch(() => ({}));
logApiResponse("gooyal", "POST", url, res.status, {
...data,
access_token: data.access_token ? "<redacted>" : undefined,
refresh_token: data.refresh_token ? "<redacted>" : undefined,
});
if (!res.ok) {
throw new GooyalAuthError(res.status, data.error ?? "refresh_failed", data.error_description);
}
return data;
}