mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 04:32:42 +00:00
fix(auth): ask the provider for a fresh sign-in when claiming a profile
This commit is contained in:
1 parent
23f008c8bb
commit
3c44f3d167
4 files changed
+72
-1
No files matched your search
@@ -40,9 +40,25 @@ vi.mock("@/lib/auth/oidc", async () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@/lib/managed-profiles/claim-cookie", async (importOriginal) => ({
|
||||
...(await importOriginal<
|
||||
typeof import("@/lib/managed-profiles/claim-cookie")
|
||||
>()),
|
||||
readClaimCookie: vi.fn(),
|
||||
}));
|
||||
vi.mock("@/lib/managed-profiles/handover", () => ({
|
||||
isHandoverUsable: vi.fn(),
|
||||
}));
|
||||
|
||||
import { GET } from "../route";
|
||||
import { readClaimCookie } from "@/lib/managed-profiles/claim-cookie";
|
||||
import { isHandoverUsable } from "@/lib/managed-profiles/handover";
|
||||
import { checkAuthSurfaceRateLimit } from "@/lib/rate-limit";
|
||||
import { getOidcConfig, discoverOidcMetadata } from "@/lib/auth/oidc";
|
||||
import {
|
||||
buildAuthorizationUrl,
|
||||
discoverOidcMetadata,
|
||||
getOidcConfig,
|
||||
} from "@/lib/auth/oidc";
|
||||
|
||||
function makeRequest(path = "/api/auth/oidc/login"): NextRequest {
|
||||
return new NextRequest(`http://localhost${path}`);
|
||||
@@ -248,6 +264,34 @@ describe("GET /api/auth/oidc/login?intent=claim (v1.43, #959)", () => {
|
||||
expect(res.cookies.get("oidc_auth_state")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("asks the provider for a fresh sign-in on a claim, and only there", async () => {
|
||||
vi.mocked(discoverOidcMetadata).mockResolvedValue({
|
||||
issuer: "https://idp.example.com",
|
||||
authorization_endpoint: "https://idp.example.com/authorize",
|
||||
token_endpoint: "https://idp.example.com/token",
|
||||
jwks_uri: "https://idp.example.com/jwks",
|
||||
});
|
||||
vi.mocked(buildAuthorizationUrl).mockReturnValue(
|
||||
"https://idp.example.com/authorize?x=1",
|
||||
);
|
||||
vi.mocked(readClaimCookie).mockReturnValue({
|
||||
handoverId: "handover-1",
|
||||
binding: "binding-1",
|
||||
} as never);
|
||||
vi.mocked(isHandoverUsable).mockResolvedValue(true);
|
||||
|
||||
await GET(makeRequest("/api/auth/oidc/login?intent=claim"));
|
||||
expect(vi.mocked(buildAuthorizationUrl).mock.calls[0]?.[0]).toMatchObject({
|
||||
forceLogin: true,
|
||||
});
|
||||
|
||||
vi.mocked(buildAuthorizationUrl).mockClear();
|
||||
await GET(makeRequest("/api/auth/oidc/login"));
|
||||
expect(vi.mocked(buildAuthorizationUrl).mock.calls[0]?.[0]).toMatchObject({
|
||||
forceLogin: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("routes the claim's other failures to the claim page too", async () => {
|
||||
vi.mocked(checkAuthSurfaceRateLimit).mockResolvedValue({
|
||||
allowed: false,
|
||||
|
||||
@@ -156,6 +156,8 @@ export const GET = apiHandler(async (req: NextRequest) => {
|
||||
nonce,
|
||||
codeChallenge,
|
||||
redirectUri,
|
||||
// A claim only; an ordinary sign-in keeps single sign-on.
|
||||
forceLogin: claimBinding !== null,
|
||||
});
|
||||
|
||||
const response = NextResponse.redirect(authorizationUrl);
|
||||
|
||||
@@ -253,6 +253,23 @@ describe("buildAuthorizationUrl", () => {
|
||||
expect(url.searchParams.get("code_challenge")).toBe("challenge-1");
|
||||
expect(url.searchParams.get("code_challenge_method")).toBe("S256");
|
||||
expect(url.searchParams.get("scope")).toBe(config.scopes);
|
||||
expect(url.searchParams.has("prompt")).toBe(false);
|
||||
});
|
||||
|
||||
it("asks for a fresh sign-in only when told to", () => {
|
||||
configureEnv();
|
||||
const url = new URL(
|
||||
buildAuthorizationUrl({
|
||||
metadata: METADATA_DOC,
|
||||
config: getOidcConfig()!,
|
||||
state: "state-1",
|
||||
nonce: "nonce-1",
|
||||
codeChallenge: "challenge-1",
|
||||
redirectUri: "https://healthlog.example.com/api/auth/oidc/callback",
|
||||
forceLogin: true,
|
||||
}),
|
||||
);
|
||||
expect(url.searchParams.get("prompt")).toBe("login");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -237,6 +237,13 @@ export function buildAuthorizationUrl(params: {
|
||||
nonce: string;
|
||||
codeChallenge: string;
|
||||
redirectUri: string;
|
||||
/**
|
||||
* Ask the provider to authenticate afresh (`prompt=login`, OpenID Connect
|
||||
* Core 3.1.2.1) instead of reusing the browser's provider session. A claim
|
||||
* hands a profile to whoever signs in, so it must be the person at the
|
||||
* keyboard now, not a session someone else left open in this browser.
|
||||
*/
|
||||
forceLogin?: boolean;
|
||||
}): string {
|
||||
const url = new URL(params.metadata.authorization_endpoint);
|
||||
url.search = new URLSearchParams({
|
||||
@@ -248,6 +255,7 @@ export function buildAuthorizationUrl(params: {
|
||||
nonce: params.nonce,
|
||||
code_challenge: params.codeChallenge,
|
||||
code_challenge_method: "S256",
|
||||
...(params.forceLogin ? { prompt: "login" } : {}),
|
||||
}).toString();
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user