Problem
Two auth-flow bugs in the saas scaffold template, both found by building and running a generated saas app.
1. The dashboard renders a literal ${user?.name || user?.email} instead of the user's name. The generated app/dashboard/page.ts fetches const user = await currentUser() (so user IS in scope), but the greeting is written as an over-escaped literal, so a signed-in user sees the raw text "Welcome, ${user?.name || user?.email}!". Same over-escaping in app/dashboard/settings/page.ts for the email and name.
2. Login and signup do not land on /dashboard. After a successful credential login (the form posts to /api/auth/signin/credentials), the user is redirected to / (the createAuth default), not the dashboard. After signup, the action redirects to /login, and even then login does not reach the dashboard. So a signed-in user never automatically arrives at the protected page.
Design / approach
- Bug 1 is a pure escaping mistake. The saas page files are assembled from arrays of DOUBLE-QUOTED strings joined with
\n (not template literals), so ${...} is already emitted literally into the generated file. The author additionally escaped it to ${\${user?.name || user?.email}`}, which evaluates (in the generated html``template) to the literal text. Fix: write the interpolation plainly,${user?.name || user?.email}`.
- Bug 2 (login):
createAuth's signin honors a redirectTo field from the POSTed form data (packages/server/src/auth.js:293, const redirectTo = opts.redirectTo || (data && data.redirectTo) || '/'). Add a hidden <input type="hidden" name="redirectTo" value="/dashboard"> to the login form.
- Bug 2 (signup): auto-login on success. A page
action may return a raw Response (packages/server/src/page-action.js:226 passes it through), and signIn('credentials', { email, password }, { redirectTo: '/dashboard' }) returns a 302 with the session set-cookie. So on a successful signup, return signIn('credentials', { email, password }, { redirectTo: '/dashboard' }) instead of { success: true, redirect: '/login' }. (The signup page is elided, so importing signIn from #lib/auth.server.ts does not ship to the browser; verify webjs check stays clean.)
Implementation notes (for the implementing agent)
- Where to edit:
packages/cli/lib/create.js's saas generator, packages/cli/lib/saas-template.js:
- Dashboard greeting: line ~439 (
app/dashboard/page.ts); settings: lines ~471, ~473 (app/dashboard/settings/page.ts). Remove the \${...}`wrapper, leave${...}`.
- Login form: line ~310 area (
app/login/page.ts form) add the hidden redirectTo input.
- Signup action: line ~359-362 (
app/signup/page.ts action) import signIn from #lib/auth.server.ts and return its Response on success.
- Landmines: the file is built from double-quoted string arrays, so
${...} must NOT be escaped (that is the whole bug); a single quote inside is fine. signIn needs the credentials to match what Credentials.authorize reads (email, password). Confirm the signup page stays elided so the #lib/auth.server.ts import does not trip no-server-import-in-browser-module.
- Invariants: keep the no-JS write path working (the forms must still submit without JS); the login
redirectTo must be a same-site local path.
- Tests + docs: the generated
test/auth/auth.test.ts asserts signup is a 303 to /login (line ~255-258) and that the dashboard renders "Dashboard"; update those to the new flow (signup success is now a 302 to /dashboard with a session cookie, and assert the dashboard greets the real user name, which also counterfactually catches the literal-interpolation regression). Regenerate a saas app and verify the full signup -> dashboard and login -> dashboard flows in a browser.
Acceptance criteria
Problem
Two auth-flow bugs in the
saasscaffold template, both found by building and running a generated saas app.1. The dashboard renders a literal
${user?.name || user?.email}instead of the user's name. The generatedapp/dashboard/page.tsfetchesconst user = await currentUser()(souserIS in scope), but the greeting is written as an over-escaped literal, so a signed-in user sees the raw text "Welcome, ${user?.name || user?.email}!". Same over-escaping inapp/dashboard/settings/page.tsfor the email and name.2. Login and signup do not land on
/dashboard. After a successful credential login (the form posts to/api/auth/signin/credentials), the user is redirected to/(thecreateAuthdefault), not the dashboard. After signup, the action redirects to/login, and even then login does not reach the dashboard. So a signed-in user never automatically arrives at the protected page.Design / approach
\n(not template literals), so${...}is already emitted literally into the generated file. The author additionally escaped it to${\${user?.name || user?.email}`}, which evaluates (in the generatedhtml``template) to the literal text. Fix: write the interpolation plainly,${user?.name || user?.email}`.createAuth's signin honors aredirectTofield from the POSTed form data (packages/server/src/auth.js:293,const redirectTo = opts.redirectTo || (data && data.redirectTo) || '/'). Add a hidden<input type="hidden" name="redirectTo" value="/dashboard">to the login form.actionmay return a rawResponse(packages/server/src/page-action.js:226passes it through), andsignIn('credentials', { email, password }, { redirectTo: '/dashboard' })returns a 302 with the sessionset-cookie. So on a successful signup,return signIn('credentials', { email, password }, { redirectTo: '/dashboard' })instead of{ success: true, redirect: '/login' }. (The signup page is elided, so importingsignInfrom#lib/auth.server.tsdoes not ship to the browser; verifywebjs checkstays clean.)Implementation notes (for the implementing agent)
packages/cli/lib/create.js's saas generator,packages/cli/lib/saas-template.js:app/dashboard/page.ts); settings: lines ~471, ~473 (app/dashboard/settings/page.ts). Remove the\${...}`wrapper, leave${...}`.app/login/page.tsform) add the hiddenredirectToinput.app/signup/page.tsaction) importsignInfrom#lib/auth.server.tsand return its Response on success.${...}must NOT be escaped (that is the whole bug); a single quote inside is fine.signInneeds the credentials to match whatCredentials.authorizereads (email,password). Confirm the signup page stays elided so the#lib/auth.server.tsimport does not tripno-server-import-in-browser-module.redirectTomust be a same-site local path.test/auth/auth.test.tsasserts signup is a 303 to /login (line ~255-258) and that the dashboard renders "Dashboard"; update those to the new flow (signup success is now a 302 to /dashboard with a session cookie, and assert the dashboard greets the real user name, which also counterfactually catches the literal-interpolation regression). Regenerate a saas app and verify the full signup -> dashboard and login -> dashboard flows in a browser.Acceptance criteria
${...}text (settings too)./dashboard./dashboard.<form>path still works for both login and signup.webjs checkstays clean (signup page'ssignInimport does not ship).test/auth/auth.test.tsis updated to the new flow and passes, with the dashboard-name assertion counterfactually catching the literal-interpolation bug.