diff --git a/Dockerfile b/Dockerfile index 35a5f10c..22009666 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,11 +21,10 @@ ENV NEXT_PUBLIC_COMMIT_SHA=$NEXT_PUBLIC_COMMIT_SHA ENV NEXT_PUBLIC_APP_VERSION=$NEXT_PUBLIC_APP_VERSION RUN chown -R node:node . +USER node RUN npm run build EXPOSE 3000 -USER node ENV NEXT_TELEMETRY_DISABLED 1 -ENTRYPOINT ["./scripts/launch.sh"] -CMD [ "npm", "start" ] \ No newline at end of file +CMD npx prisma migrate deploy && npm start \ No newline at end of file diff --git a/scripts/launch.sh b/scripts/launch.sh deleted file mode 100644 index a47c0c8c..00000000 --- a/scripts/launch.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -set -e - -echo "Running Prisma migrations..." -npx prisma migrate deploy - -echo "Launching app..." -exec "$@" \ No newline at end of file diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts index 377c3e14..3ac1d822 100644 --- a/src/app/api/auth/[...nextauth]/route.ts +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -1,3 +1,45 @@ import { handlers } from "@src/auth"; +import { type NextRequest } from "next/server"; -export const { GET, POST } = handlers; +export const GET = handlers.GET; + +/** + * Wrap the Auth.js POST handler to fix Apple Sign In's redirect after OAuth. + * + * Apple uses response_mode=form_post, which is a cross-site POST. The browser + * does not send SameSite=Lax cookies with cross-site POSTs, so Auth.js never + * sees the callbackUrl cookie and falls back to url.origin (homepage). The + * redirect callback in auth.ts is NOT called in this path — it only runs when + * Auth.js has a callbackUrl value to validate, which it doesn't here. + * + * We intercept the 302 response before it leaves the server: if it would send + * the user to the bare homepage (no ?error param, meaning sign-in succeeded), + * we rewrite the Location to /desktop-oauth/complete. That page recovers the + * nonce from sessionStorage (stored by DesktopOAuthStart) for the Tauri flow, + * or redirects to /projects for plain web users. + */ +export async function POST(req: NextRequest, context: unknown) { + const response = await handlers.POST(req, context as never); + + if (new URL(req.url).pathname === "/api/auth/callback/apple") { + const location = response.headers.get("Location"); + if (location) { + try { + const dest = new URL(location); + const isHomepageFallback = + dest.pathname === "/" && !dest.searchParams.has("error"); + if (isHomepageFallback) { + const headers = new Headers(response.headers); + headers.set("Location", `${dest.origin}/desktop-oauth/complete`); + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers, + }); + } + } catch {} + } + } + + return response; +} diff --git a/src/auth.ts b/src/auth.ts index 21fa6406..18b64de3 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -52,17 +52,15 @@ export const { handlers, auth, signIn, signOut } = NextAuth({ ], callbacks: { // Apple uses response_mode=form_post; the cross-site POST back from - // appleid.apple.com drops the `callbackUrl` cookie (Auth.js promotes state/nonce - // to SameSite=None for form_post but not callbackUrl), so NextAuth falls back to - // the homepage — passed to this callback as either the absolute baseUrl or the - // relative "/". Send those cases to /desktop-oauth/complete, which recovers the - // nonce from sessionStorage (set by DesktopOAuthStart before the OAuth handoff) - // and finishes the desktop bridge. Web users who signed in with Apple and have - // no nonce are redirected to /projects from within that page. + // appleid.apple.com drops the `callbackUrl` cookie, so Auth.js falls back to + // url.origin and never calls this callback for that case. The actual Apple + // redirect fix lives in /api/auth/[...nextauth]/route.ts which intercepts + // the 302 response before it leaves the server. + // + // This callback still runs for explicit callbackUrl values (e.g. Google OAuth, + // magic-link flows). Apple form_post with no callbackUrl bypasses it entirely. redirect: async ({ url, baseUrl }) => { - const isHomepage = - url === baseUrl || url === `${baseUrl}/` || url === "/" || url === ""; - if (isHomepage) return `${baseUrl}/desktop-oauth/complete`; + if (url === baseUrl || url === `${baseUrl}/`) return `${baseUrl}/projects`; if (url.startsWith("/")) return `${baseUrl}${url}`; try { if (new URL(url).origin === baseUrl) return url;