fix(auth): use public URL for magic-link redirects
CI / validate (push) Successful in 4m53s
Release / release (push) Successful in 6m30s

This commit is contained in:
dmg
2026-08-01 16:44:01 -04:00
parent 9440c651b6
commit 5e693e2cdd
5 changed files with 21 additions and 3 deletions
+3 -2
View File
@@ -3,6 +3,7 @@ import { createAuthRepository, ipObservations, recordEvent } from "@minecraft-ac
import { getClientIp } from "@minecraft-account-manager/network"; import { getClientIp } from "@minecraft-account-manager/network";
import type { NextRequest } from "next/server"; import type { NextRequest } from "next/server";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { applicationUrl } from "@/lib/application-url";
import { db } from "@/lib/database"; import { db } from "@/lib/database";
import { getIpIntelligence, toAuditIpData } from "@/lib/ip-intelligence"; import { getIpIntelligence, toAuditIpData } from "@/lib/ip-intelligence";
@@ -38,7 +39,7 @@ export async function GET(request: NextRequest) {
]); ]);
const destination = result.user.firstName ? "/account" : "/welcome"; const destination = result.user.firstName ? "/account" : "/welcome";
const response = NextResponse.redirect(new URL(destination, request.url)); const response = NextResponse.redirect(applicationUrl(destination));
response.cookies.set(SESSION_COOKIE_NAME, result.sessionToken, { response.cookies.set(SESSION_COOKIE_NAME, result.sessionToken, {
httpOnly: true, httpOnly: true,
secure: process.env.NODE_ENV === "production", secure: process.env.NODE_ENV === "production",
@@ -49,7 +50,7 @@ export async function GET(request: NextRequest) {
return response; return response;
} catch (error) { } catch (error) {
if (error instanceof InvalidLoginCodeError) { if (error instanceof InvalidLoginCodeError) {
return NextResponse.redirect(new URL("/auth/error", request.url)); return NextResponse.redirect(applicationUrl("/auth/error"));
} }
throw error; throw error;
} }
+9
View File
@@ -0,0 +1,9 @@
import { describe, expect, it } from "vitest";
import { applicationUrl } from "./application-url";
describe("applicationUrl", () => {
it("builds browser redirects from the configured public application URL", () => {
expect(applicationUrl("/welcome", "https://portal.somc.club"))
.toEqual(new URL("https://portal.somc.club/welcome"));
});
});
+4
View File
@@ -0,0 +1,4 @@
export function applicationUrl(path: string, baseUrl = process.env.APP_URL) {
if (!baseUrl) throw new Error("APP_URL is required to build public application URLs");
return new URL(path, baseUrl);
}
+1
View File
@@ -2,6 +2,7 @@
## 2026-08-01 ## 2026-08-01
* **Fix**: Build magic-link redirects from the configured public portal URL instead of the reverse proxy's internal request origin.
* **Verify**: Confirmed `v1.1.1` left all pre-existing `latest` digests unchanged while publishing versioned artifacts. * **Verify**: Confirmed `v1.1.1` left all pre-existing `latest` digests unchanged while publishing versioned artifacts.
* **Refine**: Removed mutable `latest` publication so all deployable artifacts use explicit semantic versions. * **Refine**: Removed mutable `latest` publication so all deployable artifacts use explicit semantic versions.
* **Verify**: Confirmed the `v1.1.0` Discord bot image and matching web, migration, and Velocity artifacts. * **Verify**: Confirmed the `v1.1.0` Discord bot image and matching web, migration, and Velocity artifacts.
+4 -1
View File
@@ -3,7 +3,7 @@ type: User Story
title: Authenticate with a Discord magic link title: Authenticate with a Discord magic link
description: Discord users receive private single-use links that establish secure portal sessions. description: Discord users receive private single-use links that establish secure portal sessions.
tags: [player, discord, authentication, security] tags: [player, discord, authentication, security]
timestamp: 2026-08-01T18:43:58Z timestamp: 2026-08-01T20:43:46Z
story_id: US-002 story_id: US-002
status: verified status: verified
--- ---
@@ -19,6 +19,7 @@ As a Discord community member, I want `/register` and `/account` to issue a priv
- [x] Given a login token, then it expires after ten minutes and can be consumed only once. - [x] Given a login token, then it expires after ten minutes and can be consumed only once.
- [x] Given repeated link requests, then requests are rate limited per Discord user and older active links are invalidated. - [x] Given repeated link requests, then requests are rate limited per Discord user and older active links are invalidated.
- [x] Given a valid link, when it is consumed, then the Discord user is created or refreshed and a secure seven-day session is established. - [x] Given a valid link, when it is consumed, then the Discord user is created or refreshed and a secure seven-day session is established.
- [x] Given a magic-link result behind a reverse proxy, then the browser is redirected through the configured public application URL rather than an internal container address.
- [x] Given an invalid, expired, or consumed link, then the user sees a safe recovery page instructing them to request another link. - [x] Given an invalid, expired, or consumed link, then the user sees a safe recovery page instructing them to request another link.
# Implementation # Implementation
@@ -27,10 +28,12 @@ As a Discord community member, I want `/register` and `/account` to issue a priv
- [`packages/auth/src/index.ts`](../packages/auth/src/index.ts) - [`packages/auth/src/index.ts`](../packages/auth/src/index.ts)
- [`packages/database/src/auth-repository.ts`](../packages/database/src/auth-repository.ts) - [`packages/database/src/auth-repository.ts`](../packages/database/src/auth-repository.ts)
- [`apps/web/src/app/auth/discord/route.ts`](../apps/web/src/app/auth/discord/route.ts) - [`apps/web/src/app/auth/discord/route.ts`](../apps/web/src/app/auth/discord/route.ts)
- [`apps/web/src/lib/application-url.ts`](../apps/web/src/lib/application-url.ts)
# Validation # Validation
- [`packages/auth/test/magic-link.test.ts`](../packages/auth/test/magic-link.test.ts) - [`packages/auth/test/magic-link.test.ts`](../packages/auth/test/magic-link.test.ts)
- [`apps/web/src/lib/application-url.test.ts`](../apps/web/src/lib/application-url.test.ts)
- Discord command and authentication workspaces pass TypeScript validation. - Discord command and authentication workspaces pass TypeScript validation.
# Related Stories # Related Stories