401 reauth experiments

This commit is contained in:
Igor Loskutov
2025-09-05 14:20:00 -04:00
parent 01c969b8a9
commit 2e94f4ccbe
4 changed files with 79 additions and 14 deletions

View File

@@ -11,6 +11,9 @@ import {
import createFetchClient from "openapi-react-query";
import { assertExistsAndNonEmptyString } from "./utils";
import { isBuildPhase } from "./next";
import { Session } from "next-auth";
import { assertCustomSession } from "./types";
import { HttpMethod, PathsWithMethod } from "openapi-typescript-helpers";
const API_URL = !isBuildPhase
? assertExistsAndNonEmptyString(process.env.NEXT_PUBLIC_API_URL)
@@ -25,12 +28,20 @@ export const client = createClient<paths>({
export const $api = createFetchClient<paths>(client);
let currentAuthToken: string | null | undefined = null;
let refreshAuthCallback: (() => Promise<Session | null>) | null = null;
const injectAuth = (request: Request, accessToken: string | null) => {
if (accessToken) {
request.headers.set("Authorization", `Bearer ${currentAuthToken}`);
} else {
request.headers.delete("Authorization");
}
return request;
};
client.use({
onRequest({ request }) {
if (currentAuthToken) {
request.headers.set("Authorization", `Bearer ${currentAuthToken}`);
}
request = injectAuth(request, currentAuthToken || null);
// XXX Only set Content-Type if not already set (FormData will set its own boundary)
// This is a work around for uploading file, we're passing a formdata
// but the content type was still application/json
@@ -44,7 +55,46 @@ client.use({
},
});
client.use({
async onResponse({ response, request, params, schemaPath }) {
if (response.status === 401) {
console.log(
"response.status is 401!",
refreshAuthCallback,
request,
schemaPath,
);
}
if (response.status === 401 && refreshAuthCallback) {
try {
const session = await refreshAuthCallback();
if (!session) {
console.warn("Token refresh failed, no session returned");
return response;
}
const customSession = assertCustomSession(session);
currentAuthToken = customSession.accessToken;
const r = await client.request(
request.method as HttpMethod,
schemaPath as PathsWithMethod<paths, HttpMethod>,
...params,
);
return r.response;
} catch (error) {
console.error("Token refresh failed during 401 retry:", error);
}
}
return response;
},
});
// the function contract: lightweight, idempotent
export const configureApiAuth = (token: string | null | undefined) => {
currentAuthToken = token;
};
export const configureApiAuthRefresh = (
callback: (() => Promise<Session | null>) | null,
) => {
refreshAuthCallback = callback;
};