kopia lustrzana https://github.com/elk-zone/elk
fix: auto-recover from OAuth app deletion after token revocation (#2422)
When users revoke OAuth access on their Mastodon server, the OAuth application gets deleted but remains cached in Elk. This causes login failures with 'Client authentication failed due to unknown client'. This fix adds automatic detection and recovery: - Detects specific invalid_client errors (401 status) - Automatically invalidates stale cached OAuth credentials - Creates fresh OAuth application and retries seamlessly - Single retry prevents infinite loops - Preserves existing error handling for other failures Changes: - Add invalidateApp() function to server/utils/shared.ts - Enhanced error handling in server/api/[server]/oauth/[origin].ts - Backward compatible, zero breaking changes Fixes #2422pull/3382/head
rodzic
589fef0446
commit
a6e6815b7b
|
@ -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.',
|
||||
|
|
|
@ -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<string>()
|
||||
|
|
Ładowanie…
Reference in New Issue