Skip to content

Commit 264783d

Browse files
committed
Route auth status through backend contract with production-safe unavailable messaging - PR_26166_142-auth-status-backend-contract
1 parent c965a7a commit 264783d

44 files changed

Lines changed: 1298 additions & 1559 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

account/create-account.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<meta charset="utf-8">
66
<meta name="viewport" content="width=device-width, initial-scale=1">
77
<title>Create Account - GameFoundryStudio</title>
8-
<meta name="description" content="Create a GameFoundryStudio account through configured DEV Supabase Auth.">
8+
<meta name="description" content="Create a GameFoundryStudio account through the configured account provider.">
99
<link rel="icon" href="/favicon.svg">
1010
<link rel="stylesheet" href="../assets/theme-v2/css/theme.css">
1111
</head>
@@ -17,7 +17,7 @@
1717
<div class="container">
1818
<div class="kicker">Account</div>
1919
<h1>Create Account</h1>
20-
<p class="lede">Create an account through external DEV Supabase Auth when it is configured.</p>
20+
<p class="lede">Create an account through the configured account provider.</p>
2121
</div>
2222
</section>
2323
<section class="section">
@@ -28,14 +28,14 @@ <h1>Create Account</h1>
2828
<div class="kicker">Account Access</div>
2929
<h2>Create Account</h2>
3030
</div>
31-
<p>Account creation is handled by external Supabase Auth when the DEV provider is configured.</p>
31+
<p>Account creation is handled by the configured account provider.</p>
3232
<form class="content-stack" data-account-auth-form="create-account">
3333
<label for="createAccountEmail">Email</label>
3434
<input id="createAccountEmail" name="email" type="email" autocomplete="email" data-account-auth-email>
3535
<label for="createAccountPassword">Password</label>
3636
<input id="createAccountPassword" name="password" type="password" autocomplete="new-password" data-account-auth-password>
3737
<button class="btn primary" type="submit" data-account-auth-submit>Create Account</button>
38-
<div class="status" role="status" data-account-auth-status>Checking DEV Supabase Auth configuration.</div>
38+
<div class="status" role="status" data-account-auth-status>Checking account availability.</div>
3939
</form>
4040
<div class="action-group">
4141
<a class="btn btn--compact" href="sign-in.html">Back to Sign In</a>

account/password-reset.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<meta charset="utf-8">
66
<meta name="viewport" content="width=device-width, initial-scale=1">
77
<title>Password Reset - GameFoundryStudio</title>
8-
<meta name="description" content="Request a GameFoundryStudio password reset through configured DEV Supabase Auth.">
8+
<meta name="description" content="Request a GameFoundryStudio password reset through the configured account provider.">
99
<link rel="icon" href="/favicon.svg">
1010
<link rel="stylesheet" href="../assets/theme-v2/css/theme.css">
1111
</head>
@@ -17,7 +17,7 @@
1717
<div class="container">
1818
<div class="kicker">Account</div>
1919
<h1>Password Reset</h1>
20-
<p class="lede">Request a password reset through external DEV Supabase Auth when it is configured.</p>
20+
<p class="lede">Request a password reset through the configured account provider.</p>
2121
</div>
2222
</section>
2323
<section class="section">
@@ -28,12 +28,12 @@ <h1>Password Reset</h1>
2828
<div class="kicker">Account Access</div>
2929
<h2>Password Reset</h2>
3030
</div>
31-
<p>Password reset email delivery is handled by external Supabase Auth when the DEV provider is configured.</p>
31+
<p>Password reset email delivery is handled by the configured account provider.</p>
3232
<form class="content-stack" data-account-auth-form="password-reset">
3333
<label for="passwordResetEmail">Email</label>
3434
<input id="passwordResetEmail" name="email" type="email" autocomplete="email" data-account-auth-email>
3535
<button class="btn primary" type="submit" data-account-auth-submit>Request Password Reset</button>
36-
<div class="status" role="status" data-account-auth-status>Checking DEV Supabase Auth configuration.</div>
36+
<div class="status" role="status" data-account-auth-status>Checking account availability.</div>
3737
</form>
3838
<div class="action-group">
3939
<a class="btn btn--compact" href="sign-in.html">Back to Sign In</a>

account/sign-in.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ <h2>Welcome Back</h2>
3838
<a class="btn" href="create-account.html" data-login-create-account>Create Account</a>
3939
<a class="btn btn--compact" href="password-reset.html" data-login-lost-password>Password Reset</a>
4040
</div>
41-
<div class="status" role="status" data-login-status>Checking DEV Supabase Auth configuration.</div>
41+
<div class="status" role="status" data-login-status>Checking account availability.</div>
4242
</form>
4343
<div class="action-group">
4444
<a class="btn btn--compact" href="toolbox/index.html" data-login-continue>Continue Browsing</a>

assets/theme-v2/js/account-auth-actions.js

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ const passwordField = document.querySelector("[data-account-auth-password]");
44
const statusField = document.querySelector("[data-account-auth-status]");
55
const submitButton = document.querySelector("[data-account-auth-submit]");
66
const action = form?.getAttribute("data-account-auth-form") || "";
7-
let supabaseStatus = null;
7+
const AUTH_UNAVAILABLE_MESSAGE = "The site is currently unavailable. Please try again later.";
8+
let authStatus = null;
89

910
function setStatus(message) {
1011
if (statusField) {
@@ -31,47 +32,47 @@ async function readJson(response, fallbackMessage) {
3132
payload = null;
3233
}
3334
if (!response.ok || payload?.ok === false) {
34-
throw new Error(payload?.error || fallbackMessage);
35+
throw new Error(fallbackMessage);
3536
}
3637
return payload?.data || {};
3738
}
3839

39-
async function requestSupabaseAuth(path, options = {}) {
40-
const response = await fetch(`/api/auth/dev/supabase/${path}`, {
40+
async function requestAccountAuth(path, options = {}) {
41+
const response = await fetch(`/api/auth/${path}`, {
4142
body: options.body ? JSON.stringify(options.body) : undefined,
4243
headers: options.body ? { "content-type": "application/json" } : undefined,
4344
method: options.method || "GET",
4445
});
45-
return readJson(response, "DEV Supabase Auth API is unavailable. Start the API-backed local server.");
46+
return readJson(response, AUTH_UNAVAILABLE_MESSAGE);
4647
}
4748

4849
function unavailableMessage(status) {
49-
return status?.message || "DEV Supabase Auth is unavailable. Configure GAMEFOUNDRY_SUPABASE_URL and GAMEFOUNDRY_SUPABASE_ANON_KEY on the local server.";
50+
return status?.message || AUTH_UNAVAILABLE_MESSAGE;
5051
}
5152

52-
async function refreshSupabaseStatus() {
53+
async function refreshAccountAuthStatus() {
5354
if (isStaticOnlyLocalEntrypoint()) {
54-
supabaseStatus = {
55+
authStatus = {
5556
ready: false,
56-
message: "Start the DEV API server to use Supabase Auth. Guest browsing remains available.",
57+
message: AUTH_UNAVAILABLE_MESSAGE,
5758
};
5859
setFormEnabled(false);
59-
setStatus(supabaseStatus.message);
60-
return supabaseStatus;
60+
setStatus(authStatus.message);
61+
return authStatus;
6162
}
6263
try {
63-
supabaseStatus = await requestSupabaseAuth("status");
64-
setFormEnabled(Boolean(supabaseStatus.ready));
65-
setStatus(supabaseStatus.ready ? "DEV Supabase Auth is ready." : unavailableMessage(supabaseStatus));
64+
authStatus = await requestAccountAuth("status");
65+
setFormEnabled(Boolean(authStatus.ready));
66+
setStatus(authStatus.ready ? "Account service is available." : unavailableMessage(authStatus));
6667
} catch (error) {
67-
supabaseStatus = {
68+
authStatus = {
6869
ready: false,
69-
message: error instanceof Error ? error.message : String(error || "DEV Supabase Auth status failed."),
70+
message: error instanceof Error ? error.message : AUTH_UNAVAILABLE_MESSAGE,
7071
};
7172
setFormEnabled(false);
72-
setStatus(supabaseStatus.message);
73+
setStatus(authStatus.message);
7374
}
74-
return supabaseStatus;
75+
return authStatus;
7576
}
7677

7778
function actionEndpoint() {
@@ -86,7 +87,7 @@ function actionEndpoint() {
8687

8788
form?.addEventListener("submit", (event) => {
8889
event.preventDefault();
89-
Promise.resolve(supabaseStatus || refreshSupabaseStatus())
90+
Promise.resolve(authStatus || refreshAccountAuthStatus())
9091
.then((status) => {
9192
if (!status?.ready) {
9293
setStatus(unavailableMessage(status));
@@ -96,7 +97,7 @@ form?.addEventListener("submit", (event) => {
9697
if (!endpoint) {
9798
throw new Error("Unknown account action.");
9899
}
99-
return requestSupabaseAuth(endpoint, {
100+
return requestAccountAuth(endpoint, {
100101
body: {
101102
email: emailField?.value || "",
102103
password: passwordField?.value || "",
@@ -106,12 +107,12 @@ form?.addEventListener("submit", (event) => {
106107
})
107108
.then((result) => {
108109
if (result) {
109-
setStatus(result.message || "Supabase Auth action completed.");
110+
setStatus(result.message || "Account action completed.");
110111
}
111112
})
112113
.catch((error) => {
113-
setStatus(error instanceof Error ? error.message : String(error || "Supabase Auth action failed."));
114+
setStatus(error instanceof Error ? error.message : AUTH_UNAVAILABLE_MESSAGE);
114115
});
115116
});
116117

117-
refreshSupabaseStatus();
118+
refreshAccountAuthStatus();

assets/theme-v2/js/login-session.js

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ const form = document.querySelector("[data-login-form]");
33
const identityField = document.querySelector("[data-login-identity]");
44
const passwordField = document.querySelector("[data-login-password]");
55
const statusField = document.querySelector("[data-login-status]");
6-
let supabaseStatus = null;
6+
const AUTH_UNAVAILABLE_MESSAGE = "The site is currently unavailable. Please try again later.";
7+
let authStatus = null;
78

89
function rootPrefix() {
910
const rootSegments = new Set([
@@ -57,44 +58,44 @@ async function readJson(response, fallbackMessage) {
5758
payload = null;
5859
}
5960
if (!response.ok || payload?.ok === false) {
60-
throw new Error(payload?.error || fallbackMessage);
61+
throw new Error(fallbackMessage);
6162
}
6263
return payload?.data || {};
6364
}
6465

65-
async function requestSupabaseAuth(path, options = {}) {
66-
const response = await fetch(`/api/auth/dev/supabase/${path}`, {
66+
async function requestAccountAuth(path, options = {}) {
67+
const response = await fetch(`/api/auth/${path}`, {
6768
body: options.body ? JSON.stringify(options.body) : undefined,
6869
headers: options.body ? { "content-type": "application/json" } : undefined,
6970
method: options.method || "GET",
7071
});
71-
return readJson(response, "DEV Supabase Auth API is unavailable. Start the API-backed local server.");
72+
return readJson(response, AUTH_UNAVAILABLE_MESSAGE);
7273
}
7374

7475
function unavailableMessage(status) {
75-
return status?.message || "DEV Supabase Auth is unavailable. Configure GAMEFOUNDRY_SUPABASE_URL and GAMEFOUNDRY_SUPABASE_ANON_KEY on the local server.";
76+
return status?.message || AUTH_UNAVAILABLE_MESSAGE;
7677
}
7778

78-
async function refreshSupabaseStatus() {
79+
async function refreshAccountAuthStatus() {
7980
if (isStaticOnlyLocalEntrypoint()) {
80-
supabaseStatus = {
81+
authStatus = {
8182
ready: false,
82-
message: "Start the DEV API server to use Supabase Auth. Guest browsing remains available.",
83+
message: AUTH_UNAVAILABLE_MESSAGE,
8384
};
84-
setStatus(supabaseStatus.message);
85-
return supabaseStatus;
85+
setStatus(authStatus.message);
86+
return authStatus;
8687
}
8788
try {
88-
supabaseStatus = await requestSupabaseAuth("status");
89-
setStatus(supabaseStatus.ready ? "DEV Supabase Auth is ready for sign-in." : unavailableMessage(supabaseStatus));
89+
authStatus = await requestAccountAuth("status");
90+
setStatus(authStatus.ready ? "Account service is available." : unavailableMessage(authStatus));
9091
} catch (error) {
91-
supabaseStatus = {
92+
authStatus = {
9293
ready: false,
93-
message: error instanceof Error ? error.message : String(error || "DEV Supabase Auth status failed."),
94+
message: error instanceof Error ? error.message : AUTH_UNAVAILABLE_MESSAGE,
9495
};
95-
setStatus(supabaseStatus.message);
96+
setStatus(authStatus.message);
9697
}
97-
return supabaseStatus;
98+
return authStatus;
9899
}
99100

100101
if (continueLink) {
@@ -103,13 +104,13 @@ if (continueLink) {
103104

104105
form?.addEventListener("submit", (event) => {
105106
event.preventDefault();
106-
Promise.resolve(refreshSupabaseStatus())
107+
Promise.resolve(refreshAccountAuthStatus())
107108
.then((status) => {
108109
if (!status?.ready) {
109110
setStatus(unavailableMessage(status));
110111
return null;
111112
}
112-
return requestSupabaseAuth("sign-in", {
113+
return requestAccountAuth("sign-in", {
113114
body: {
114115
identity: identityField?.value || "",
115116
password: passwordField?.value || "",
@@ -119,10 +120,10 @@ form?.addEventListener("submit", (event) => {
119120
})
120121
.then((result) => {
121122
if (result) {
122-
setStatus(result.message || "Supabase Auth sign-in completed.");
123+
setStatus(result.message || "Account authentication completed.");
123124
}
124125
})
125126
.catch((error) => {
126-
setStatus(error instanceof Error ? error.message : String(error || "Supabase Auth sign-in failed."));
127+
setStatus(error instanceof Error ? error.message : AUTH_UNAVAILABLE_MESSAGE);
127128
});
128129
});
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# PR_26166_142-auth-status-backend-contract
2+
3+
## Branch Validation
4+
5+
- Current branch: `main`
6+
- Expected branch: `main`
7+
- Result: PASS
8+
9+
## Summary
10+
11+
Fixed the auth status/check flow so Sign In, Create Account, and Password Reset use the shared backend contract:
12+
13+
- `GET /api/auth/status`
14+
- `POST /api/auth/sign-in`
15+
- `POST /api/auth/create-account`
16+
- `POST /api/auth/password-reset`
17+
18+
The browser no longer calls environment-specific auth routes. The Local API router decides the configured auth adapter and returns production-safe availability text to the UI when auth is missing or unavailable.
19+
20+
Delta ZIP: `tmp/PR_26166_142-auth-status-backend-contract_delta.zip`
21+
22+
## Requirement Checklist
23+
24+
| Requirement | Result | Evidence |
25+
| --- | --- | --- |
26+
| Use `main` only | PASS | `git branch --show-current` returned `main`. |
27+
| Read `docs_build/dev/PROJECT_INSTRUCTIONS.md` | PASS | Read before edits. |
28+
| Browser pages must not call environment-specific/missing API routes | PASS | `assets/theme-v2/js/login-session.js` and `assets/theme-v2/js/account-auth-actions.js` call `/api/auth/...`. |
29+
| Password Reset/Create Account must not show `Unknown API route` | PASS | Both pages now use `/api/auth/status`; Playwright validates the generic unavailable state. |
30+
| All environments call the same backend API contract | PASS | Browser calls only `/api/auth/status`, `/api/auth/sign-in`, `/api/auth/create-account`, `/api/auth/password-reset`. |
31+
| Backend/API decides auth/db adapter | PASS | `src/dev-runtime/server/local-api-router.mjs` resolves provider status in `authStatus()` and action adapters in `authAdapterForAction()`. |
32+
| Add/correct backend route handling for auth status/config availability | PASS | Added generic `/api/auth/*` route handling. |
33+
| Missing/unreachable backend shows production-safe message | PASS | UI and backend use `The site is currently unavailable. Please try again later.` |
34+
| Remove user-facing DEV wording | PASS | Account pages/auth scripts no longer contain user-facing DEV/Supabase/env wording. |
35+
| Do not activate product-table migration | PASS | No product-table migration changes. |
36+
| Do not add secrets or `.env` files | PASS | No secrets or `.env` files changed. |
37+
| No silent fallback | PASS | Missing/unavailable auth reports unavailable; no fallback auth path is triggered. |
38+
| No browser-owned auth/product data | PASS | Browser invokes backend API only; no browser-owned auth/product persistence added. |
39+
40+
## Search Evidence
41+
42+
- `rg -n "/api/auth/dev/supabase" account assets/theme-v2/js src/dev-runtime/server` returned no active page/script/server matches.
43+
- `rg -n "DEV|Supabase|GAMEFOUNDRY_|Unknown API route" account assets/theme-v2/js/login-session.js assets/theme-v2/js/account-auth-actions.js` returned no auth UI/script matches; the only output was unrelated `DEVICE_POLL_INTERVAL_MS` in `account/user-controls-page.js`.
44+
- `rg -n "authStatus\\(|authSignIn\\(|authCreateAccount\\(|authPasswordReset\\(" src/dev-runtime/server/local-api-router.mjs` shows generic backend auth handlers.
45+
- The generic `Unknown API route` server fallback remains for unrelated unknown API paths, but Create Account and Password Reset no longer call missing routes.
46+
47+
## Changed Files
48+
49+
- `account/sign-in.html`
50+
- `account/create-account.html`
51+
- `account/password-reset.html`
52+
- `assets/theme-v2/js/login-session.js`
53+
- `assets/theme-v2/js/account-auth-actions.js`
54+
- `src/dev-runtime/server/local-api-router.mjs`
55+
- `tests/dev-runtime/SupabaseProviderContractStub.test.mjs`
56+
- `tests/playwright/tools/LoginSessionMode.spec.mjs`
57+
- `tests/playwright/tools/StaticOnlyLoginApiRequired.spec.mjs`
58+
- `tests/playwright/tools/ApiStaticRouteRecovery.spec.mjs`
59+
- `docs_build/dev/reports/playwright_v8_coverage_report.txt`
60+
- `docs_build/dev/reports/coverage_changed_js_guardrail.txt`
61+
- Workspace validation reports refreshed by `npm run test:workspace-v2`.
62+
63+
## Impacted Lanes
64+
65+
- runtime: auth/session browser and Local API route behavior.
66+
- integration: Sign In, Create Account, Password Reset unavailable/configured states.
67+
- contract: Supabase provider adapter contract stub route checks.
68+
69+
## Skipped Lanes
70+
71+
- samples: SKIP. The PR only changes account auth status/API contract wiring and does not touch samples or sample runtime behavior.
72+
- full samples smoke: SKIP per request.
73+
74+
## Validation Performed
75+
76+
- PASS: `node --check assets/theme-v2/js/login-session.js`
77+
- PASS: `node --check assets/theme-v2/js/account-auth-actions.js`
78+
- PASS: `node --check src/dev-runtime/server/local-api-router.mjs`
79+
- PASS: `node --check tests/dev-runtime/SupabaseProviderContractStub.test.mjs`
80+
- PASS: `node --test tests/dev-runtime/SupabaseProviderContractStub.test.mjs`
81+
- PASS: `npx playwright test tests/playwright/tools/LoginSessionMode.spec.mjs tests/playwright/tools/StaticOnlyLoginApiRequired.spec.mjs tests/playwright/tools/ApiStaticRouteRecovery.spec.mjs`
82+
- PASS: `npm run test:workspace-v2`
83+
- PASS: final targeted Playwright rerun for focused V8 coverage.
84+
- PASS: `git diff --check` with line-ending warnings only.
85+
86+
## Playwright Result
87+
88+
- PASS: 13 targeted auth/session Playwright tests passed.
89+
- PASS: `npm run test:workspace-v2` passed 5 workspace-contract tests.
90+
91+
## V8 Coverage
92+
93+
- PASS/WARN: `docs_build/dev/reports/playwright_v8_coverage_report.txt` generated.
94+
- `assets/theme-v2/js/account-auth-actions.js`: 92% advisory coverage.
95+
- `assets/theme-v2/js/login-session.js`: 93% advisory coverage.
96+
- `src/dev-runtime/server/local-api-router.mjs`: WARN because browser V8 does not collect server-side Node modules; covered by Node contract test and Playwright route checks.
97+
98+
## Manual Validation Notes
99+
100+
1. Open `account/sign-in.html`.
101+
2. With auth unavailable, click `Sign In`; status should show `The site is currently unavailable. Please try again later.`
102+
3. Open `account/create-account.html`; status should resolve through `/api/auth/status` and must not show `Unknown API route`.
103+
4. Open `account/password-reset.html`; status should resolve through `/api/auth/status` and must not show `Unknown API route`.
104+
5. With Supabase auth env configured, Sign In/Create Account/Password Reset use the external auth adapter while product data remains Local DB.

0 commit comments

Comments
 (0)