diff --git a/server/api/[server]/oauth/[origin].ts b/server/api/[server]/oauth/[origin].ts index 66833146..c5555354 100644 --- a/server/api/[server]/oauth/[origin].ts +++ b/server/api/[server]/oauth/[origin].ts @@ -1,6 +1,6 @@ import { stringifyQuery } from 'ufo' -import { defaultUserAgent } from '~~/server/utils/shared' +import { defaultUserAgent, invalidateApp } from '~~/server/utils/shared' export default defineEventHandler(async (event) => { let { server, origin } = getRouterParams(event) @@ -43,7 +43,51 @@ export default defineEventHandler(async (event) => { const url = `/signin/callback?${stringifyQuery({ server, token: result.access_token, vapid_key: app.vapid_key })}` await sendRedirect(event, url, 302) } - catch { + catch (error: any) { + // Check for invalid client error (OAuth app deleted) + if (error?.data?.error === 'invalid_client' + || (error?.statusCode === 401 && error?.data?.error_description?.includes('Client authentication failed'))) { + // Invalidate cached app and retry once + await invalidateApp(origin, server) + + try { + const newApp = await getApp(origin, server) + if (!newApp) { + throw createError({ + statusCode: 400, + statusMessage: `Failed to re-register app for server: ${server}`, + }) + } + + const retryResult: any = await $fetch(`https://${server}/oauth/token`, { + method: 'POST', + headers: { + 'user-agent': defaultUserAgent, + }, + body: { + client_id: newApp.client_id, + client_secret: newApp.client_secret, + redirect_uri: getRedirectURI(origin, server), + grant_type: 'authorization_code', + code, + scope: 'read write follow push', + }, + retry: 1, + }) + + const url = `/signin/callback?${stringifyQuery({ server, token: retryResult.access_token, vapid_key: newApp.vapid_key })}` + await sendRedirect(event, url, 302) + return + } + catch { + throw createError({ + statusCode: 400, + statusMessage: 'OAuth application recovery failed. Please try again.', + }) + } + } + + // Other errors (network, invalid code, etc.) throw createError({ statusCode: 400, statusMessage: 'Could not complete log in.', diff --git a/server/utils/shared.ts b/server/utils/shared.ts index 35ef064b..54faa53c 100644 --- a/server/utils/shared.ts +++ b/server/utils/shared.ts @@ -111,6 +111,12 @@ export async function deleteApp(server: string) { await storage.removeItem(key) } +export async function invalidateApp(origin: string, server: string) { + const host = origin.replace(/^https?:\/\//, '').replace(/\W/g, '-').replace(/\?.*$/, '') + const key = `servers:v4:${server}:${host}.json`.toLowerCase() + await storage.removeItem(key) +} + export async function listServers() { const keys = await storage.getKeys('servers:v4:') const servers = new Set()