feat(domains): dual-domain cutover to app.accounted.se (#1087)
* feat(domains): dual-domain cutover to app.accounted.se The user-facing app moves to app.accounted.se while app.gnubok.se stays alive for machine traffic (MCP connectors, API keys, third-party OAuth callbacks, webhooks, crons), so no third-party callback registration is on the critical path. - next.config: host redirect app.gnubok.se -> NEXT_PUBLIC_APP_URL for page traffic only (/api, /.well-known, /_next excluded). Arms itself only once NEXT_PUBLIC_APP_URL leaves the legacy host, so merging this is inert and the cutover is a pure env flip + redeploy. - skatteverket: redirect_uri pinned via NEXT_PUBLIC_SKV_OAUTH_BASE_URL (Utvecklarportalen registration is slow to change); the OAuth callback now resolves the flow from the state token + stored oauth_user_id via the service client instead of session cookies, which no longer exist on the OAuth host. Legacy same-domain flows fall back to the session. - popup listeners (SkatteverketConnectPanel, AGIPanel) accept postMessage from the pinned OAuth origin; event.source identity check unchanged. - /.well-known discovery docs reflect the allowlisted request host so existing MCP connectors on app.gnubok.se keep a self-consistent issuer/resource after the flip; spoofed hosts fall back to canonical. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: log dual-domain cutover decision Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(review): recency-bound SKV state lookup, exact localhost match in discovery allowlist - The oauth_state lookup now only considers rows updated in the last 10 minutes: bounds how long a leaked/phished authorize URL stays completable, keeps the row set far below PostgREST's 1000-row cap, and surfaces query errors instead of misreporting them as CSRF. - resolveDiscoveryBaseUrl matches localhost/127.0.0.1 exactly; the prefix check reflected spoofed hosts like localhost.evil.example. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
3d97b95197
commit
b420f3e1d9
@@ -1,12 +1,18 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { PUBLIC_OAUTH_METADATA_SCOPES } from '@/lib/auth/api-keys'
|
||||
import { resolveDiscoveryBaseUrl } from '@/lib/api/v1/base-url'
|
||||
|
||||
/**
|
||||
* RFC 8414: OAuth 2.0 Authorization Server Metadata.
|
||||
* Tells MCP clients where the authorize/token endpoints are.
|
||||
*
|
||||
* The issuer reflects the (allowlisted) request host: clients that
|
||||
* connected via the legacy app.gnubok.se domain must keep seeing a
|
||||
* self-consistent issuer there, or their issuer validation breaks on
|
||||
* re-auth after the app.accounted.se cutover.
|
||||
*/
|
||||
export async function GET() {
|
||||
const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
|
||||
export async function GET(request: Request) {
|
||||
const appUrl = resolveDiscoveryBaseUrl(request)
|
||||
|
||||
return NextResponse.json({
|
||||
issuer: appUrl,
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { resolveDiscoveryBaseUrl } from '@/lib/api/v1/base-url'
|
||||
|
||||
/**
|
||||
* RFC 9728: Protected Resource Metadata.
|
||||
* Tells MCP clients which authorization server to use.
|
||||
*
|
||||
* The resource/AS URLs reflect the (allowlisted) request host: MCP clients
|
||||
* validate the advertised resource against the server URL they were
|
||||
* configured with, and existing connectors point at the legacy
|
||||
* app.gnubok.se domain after the app.accounted.se cutover.
|
||||
*/
|
||||
export async function GET() {
|
||||
const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
|
||||
export async function GET(request: Request) {
|
||||
const appUrl = resolveDiscoveryBaseUrl(request)
|
||||
|
||||
return NextResponse.json({
|
||||
resource: `${appUrl}/api/extensions/ext/mcp-server/mcp`,
|
||||
|
||||
Reference in New Issue
Block a user