// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // What a failed sign-in becomes (spec ยง7): a redirect to the sign-in page with a code from a fixed // set, never the provider's text, and never to an address outside this app. import { forbidden, unauthenticated } from "@conusai-kit/core" import { describe, expect, it } from "vitest" import { AUTH_ERROR_MESSAGES, AUTH_ERRORS, authErrorOf, parseAuthError, safeReturnTo, signInErrorHref, } from "../apps/web/src/server/auth-errors.js" import { appCatalogues } from "../apps/web/src/ui/messages.js" const url = (query = ""): URL => new URL(`https://app.example.test/auth/callback${query}`) describe("which code a failure gets", () => { it("is `denied` when the provider said no, whatever the provider's text", () => { expect(authErrorOf(new Error("anything"), url("?error=access_denied&error_description=secret+words"))).toBe( "denied", ) }) it("is `noTransaction` for a callback no sign-in started in this browser", () => { const refusal = unauthenticated("no sign-in is in progress for this browser; start again at /auth/sign-in") expect(authErrorOf(refusal, url())).toBe("noTransaction") }) it("is `sessionExpired` for any other 401, `failed` for any other refusal of the app's own", () => { expect(authErrorOf(unauthenticated("the session ended"), url())).toBe("sessionExpired") expect(authErrorOf(forbidden("no"), url())).toBe("failed") }) it("is `failed` for a deployment that has no identity provider to complete a sign-in with", () => { expect(authErrorOf(unauthenticated("this deployment has no identity provider configured"), url())).toBe("failed") }) it("is `provider` for what is not the app's own error: the exchange with the provider failed", () => { expect(authErrorOf(new TypeError("fetch failed"), url())).toBe("provider") }) }) describe("the sign-in page's address", () => { it("carries the code and, when it is safe, where the person was going", () => { expect(signInErrorHref("denied")).toBe("/auth/sign-in?error=denied") expect(signInErrorHref("failed", "/invitations/acme/abc")).toBe( "/auth/sign-in?error=failed&return_to=%2Finvitations%2Facme%2Fabc", ) }) it.each(["https://evil.example", "//evil.example", "/\\evil.example", "javascript:alert(1)", "", null, undefined])( "never returns to %j", (target) => { expect(safeReturnTo(target)).toBe("/") expect(signInErrorHref("failed", target)).toBe("/auth/sign-in?error=failed") }, ) it("shows only the five words, not whatever the query holds", () => { expect(parseAuthError("denied")).toBe("denied") expect(parseAuthError("")).toBeUndefined() expect(parseAuthError(undefined)).toBeUndefined() }) }) describe("the words for each code", () => { it("exist in English and in Lithuanian for every code", () => { for (const code of AUTH_ERRORS) { const [module, ...rest] = (AUTH_ERROR_MESSAGES[code] as string).split(".") const key = rest.join(".") expect(appCatalogues.en?.[module as string]?.[key], `en ${code}`).toBeTruthy() expect(appCatalogues.lt?.[module as string]?.[key], `lt ${code}`).toBeTruthy() } }) })