Skip to content

Commit 587eebb

Browse files
authored
fix: redirect error (#3782)
1 parent 303da31 commit 587eebb

3 files changed

Lines changed: 89 additions & 3 deletions

File tree

src/betterAuth.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,9 @@ export const getBetterAuthOptions = (pool: Pool): BetterAuthOptions => {
298298
basePath: '/auth',
299299
secret: process.env.BETTER_AUTH_SECRET ?? '',
300300
trustedOrigins,
301+
onAPIError: {
302+
errorURL: `${process.env.COMMENTS_PREFIX}/callback`,
303+
},
301304
secondaryStorage: {
302305
get: (key) => singleRedisClient.get(`ba:${key}`),
303306
set: (key, value, ttl) =>

src/routes/betterAuth.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,57 @@ export const logoutBetterAuth = async (
119119
}
120120
};
121121

122+
const isOAuthCallbackPath = (url: string): boolean =>
123+
/\/auth\/callback\//.test(url);
124+
125+
const rewriteOAuthErrorRedirect = (
126+
request: FastifyRequest,
127+
response: Response,
128+
): string | undefined => {
129+
if (!isOAuthCallbackPath(request.url)) {
130+
return undefined;
131+
}
132+
133+
if (response.status < 300 || response.status >= 400) {
134+
return undefined;
135+
}
136+
137+
const location = response.headers.get('location');
138+
if (!location) {
139+
return undefined;
140+
}
141+
142+
const webappCallback = `${process.env.COMMENTS_PREFIX}/callback`;
143+
if (!webappCallback) {
144+
return undefined;
145+
}
146+
147+
if (location.startsWith(webappCallback)) {
148+
return undefined;
149+
}
150+
151+
let redirectUrl: URL;
152+
try {
153+
redirectUrl = new URL(location, `${request.protocol}://${request.host}`);
154+
} catch {
155+
return undefined;
156+
}
157+
158+
const hasError =
159+
redirectUrl.searchParams.has('error') ||
160+
redirectUrl.searchParams.get('state') === 'state_not_found';
161+
if (!hasError) {
162+
return undefined;
163+
}
164+
165+
const callbackUrl = new URL(webappCallback);
166+
redirectUrl.searchParams.forEach((value, key) => {
167+
callbackUrl.searchParams.set(key, value);
168+
});
169+
170+
return callbackUrl.toString();
171+
};
172+
122173
const betterAuthRoute = async (fastify: FastifyInstance): Promise<void> => {
123174
// Apple sends OAuth callbacks as application/x-www-form-urlencoded POSTs.
124175
// Fastify does not parse this content type by default, so collect the raw
@@ -142,6 +193,18 @@ const betterAuthRoute = async (fastify: FastifyInstance): Promise<void> => {
142193
reply,
143194
body,
144195
});
196+
197+
const rewrittenUrl = rewriteOAuthErrorRedirect(request, response);
198+
if (rewrittenUrl) {
199+
request.log.warn(
200+
{ originalLocation: response.headers.get('location') },
201+
'OAuth callback error redirect rewritten to webapp',
202+
);
203+
reply.header('location', rewrittenUrl);
204+
reply.status(302);
205+
return reply.send();
206+
}
207+
145208
return sendBetterAuthResponse(reply, response);
146209
} catch (error) {
147210
return sendBetterAuthError(

src/routes/redirects.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,29 @@ const redirectToProfileImage = async (
171171
export default async function (fastify: FastifyInstance): Promise<void> {
172172
const con = await createOrGetConnection();
173173

174-
fastify.get('/', (req, res) =>
175-
res.redirect('https://r.daily.dev/api-redirect'),
176-
);
174+
fastify.get('/', (req, res) => {
175+
// Some OAuth providers (e.g. GitHub) redirect cancellations to the
176+
// API root instead of the BetterAuth callback URL. Forward the error
177+
// params to the webapp callback so the popup can close cleanly.
178+
const { error, state } = req.query as Record<string, string>;
179+
if (error && state) {
180+
const webappOrigin = process.env.COMMENTS_PREFIX;
181+
if (webappOrigin) {
182+
req.log.warn(
183+
{ error, state },
184+
'OAuth error redirected to API root, forwarding to webapp callback',
185+
);
186+
const callbackUrl = new URL(`${webappOrigin}/callback`);
187+
const params = new URLSearchParams(req.url.split('?')[1] ?? '');
188+
params.forEach((value, key) => {
189+
callbackUrl.searchParams.set(key, value);
190+
});
191+
return res.redirect(callbackUrl.toString());
192+
}
193+
}
194+
195+
return res.redirect('https://r.daily.dev/api-redirect');
196+
});
177197
fastify.get('/landing', (req, res) => res.redirect('https://daily.dev'));
178198
fastify.get('/tos', (req, res) => res.redirect('https://daily.dev/tos'));
179199
fastify.get('/privacy', (req, res) =>

0 commit comments

Comments
 (0)