Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
00707f4
refactor: consolidate authentication logic into feature services
KATO-Hiro May 6, 2026
a935be6
chore: remove old plans (#1582)
KATO-Hiro May 6, 2026
02b00be
chore: add comments (#1582)
KATO-Hiro May 6, 2026
3c85b33
chore: update AGENTS.md (#1582)
KATO-Hiro May 6, 2026
15b3434
fix: auth consolidation Phase 1 (security fixes excluding NPE)
KATO-Hiro May 6, 2026
00068ad
docs: add SvelteKit form actions and error handling rules
KATO-Hiro May 6, 2026
53510c5
docs: simplify SvelteKit rules to essence only
KATO-Hiro May 6, 2026
7b7efe5
docs: update Phase 2 Service refactor approach to Wrapper pattern
KATO-Hiro May 6, 2026
0b9ec34
chore: fix format (#1582)
KATO-Hiro May 6, 2026
4933a5c
docs: remove completed Phase 1 tasks from checklist
KATO-Hiro May 6, 2026
9de77f2
docs: remove Phase 1 completed findings from review document
KATO-Hiro May 6, 2026
a043ccb
docs: remove completed work and implementation details from review
KATO-Hiro May 6, 2026
ae52cff
fix: add admin auth check to account transfer action and document rev…
KATO-Hiro May 6, 2026
12a53db
docs: mark Issue 3 as resolved and add completion checklist in review
KATO-Hiro May 6, 2026
00d42d3
docs: fix sveltekit rule for form action url param and mark Issue 4 r…
KATO-Hiro May 6, 2026
6cbfe41
fix: align buildSignupPath signature with buildLoginPath and document it
KATO-Hiro May 6, 2026
b6f67b2
test: fix Promise<void> assertions and document the pattern in testin…
KATO-Hiro May 6, 2026
d1a30fe
fix: replace misleading login redirect with 500 error in users/edit load
KATO-Hiro May 6, 2026
75e90a5
docs: condense resolved issues in review to summaries only
KATO-Hiro May 6, 2026
27886d9
chore: fix format (#1582)
KATO-Hiro May 6, 2026
9702ac3
docs: add SvelteKit layout-based alternative for admin auth consolida…
KATO-Hiro May 6, 2026
5fbbc41
refactor: replace ensureSessionOrRedirect with getLoggedInUser in use…
KATO-Hiro May 6, 2026
15e8365
test: replace toBeTruthy/toBeFalsy with toBe(true)/toBe(false) in url…
KATO-Hiro May 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions .claude/rules/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Authentication rules
paths:
- 'src/lib/server/auth.ts'
- 'src/routes/(auth)/**'
- 'src/routes/(admin)/_utils/auth.ts'
- 'src/features/auth/**'
- 'src/routes/(admin)/**'
- 'src/hooks.server.ts'
---
Expand Down Expand Up @@ -32,13 +32,26 @@ paths:

- `src/lib/server/auth.ts`: Lucia configuration
- `src/hooks.server.ts`: Global request handler
- `src/routes/(admin)/_utils/auth.ts`:
- `validateAdminAccess(locals)` — for page routes; redirects to `/login` for both unauthenticated and non-admin users (do not use in `+server.ts`)
- `src/features/auth/services/session.ts`:
- `getLoggedInUser(locals, url?)` — returns logged-in user or redirects to `/login`
- `ensureSessionOrRedirect(locals, url?)` — guard-only; redirects if no session
- `src/features/auth/services/admin_access.ts`:
- `validateAdminAccess(locals, url?)` — for page routes; redirects to `/login` for both unauthenticated and non-admin users (do not use in `+server.ts`)
- `validateAdminAccessForApi(locals)` — for API routes (`+server.ts`); throws `error(401)` if unauthenticated, `error(403)` if not admin
- `src/features/auth/utils/login.ts`: `buildLoginPath(url?)` — generates `/login` or `/login?redirectTo=...`
- `src/features/auth/utils/signup.ts`: `buildSignupPath(url?)` — generates `/signup` or `/signup?redirectTo=...` (same signature as `buildLoginPath`)
- `prisma/schema.prisma`: User, Session, Key models

## Redirect After Login Pattern

After login/signup, redirect users back to their original intended page instead of hardcoding `/`.

- Protected `load()`: pass `url` to `getLoggedInUser(locals, url)` — appends `?redirectTo=` automatically
- Login/signup actions: validate `redirectTo` with `isSameOriginRedirect()` before using (see `src/lib/utils/url.ts`)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## Security

- Never expose session secrets in client code
- Use HTTPS in production
- Validate all user inputs server-side
- **SvelteKit's `redirect()` does not validate origin** — always validate user-provided redirect destinations with `isSameOriginRedirect()`
9 changes: 9 additions & 0 deletions .claude/rules/coding-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,12 @@ Delete function only if: (1) zero callers, (2) replacement exists, (3) dependent
- No Prisma imports in route handlers
- Validate input at system boundaries
- Return safe defaults on service errors

## Comments: Why, Not What

Comment the **why** (non-obvious reason), not the **what** (code already says).

- Bad: `// increment counter`
- Good: `// use task_id as deterministic ID to avoid SSR/hydration mismatch`

Keep to one line. If longer, refactor code (naming, structure) instead.
28 changes: 20 additions & 8 deletions .claude/rules/sveltekit.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ paths:
- Page routes: use `redirect()` to navigate
- API routes (`+server.ts`): use `error()` — `redirect()` returns 3xx; `fetch` follows and gets HTML not JSON

## User-Provided Redirect Destinations

**SvelteKit's `redirect()` does NOT validate origin.** If the redirect destination comes from user input (URL query params, form data), always validate with `isSameOriginRedirect(redirectTo, url.origin)` from `src/lib/utils/url.ts` before redirecting.

## Internal Navigation: `resolve()`

All internal navigation must use `resolve()` from `$app/paths`:
Expand Down Expand Up @@ -47,6 +51,20 @@ if (!(Object.values(TaskGrade) as string[]).includes(gradeRaw)) {
const grade = gradeRaw as TaskGrade; // Safe now
```

## Form Actions: `url` Parameter

Form action handlers receive the full `RequestEvent`, including `url`. Destructure it directly:

```typescript
export const actions = {
default: async ({ request, locals, url }) => {
const redirectTo = url.searchParams.get('redirectTo');
},
};
```

`url` is guaranteed non-null. `new URL(request.url)` is an equivalent alternative but unnecessary when `url` is already available.

## Page Component Props

`+page.svelte` accepts only `data` and `form` props (`svelte/valid-prop-names-in-kit-pages`).
Expand Down Expand Up @@ -81,17 +99,11 @@ Consume with `$derived` in `+page.svelte` to sync after `load()` re-runs.

## Error Handling in load()

Wrap service calls in try-catch, return safe defaults:

```typescript
const data = await service.fetch(...).catch(() => []);
```

Prevents single service error from crashing entire page.
All async operations must be inside the try-catch block, not before it. Errors from async calls outside propagate unhandled.

## Auth Audit

When adding guard to one action in `+page.server.ts`, audit all other actions. Asymmetric guards (some protected, others not) are a recurring vulnerability.
When protecting one action (in `load()` or form actions), audit all others. Asymmetric guards are a critical vulnerability.

## success Flag & message Consistency

Expand Down
33 changes: 33 additions & 0 deletions .claude/rules/testing-e2e.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,43 @@ Prefer in order:
- `page.pause()` for interactive debugging
- Logs: `page.on('console', msg => console.log(msg.text()))`

## Multi-Route Infrastructure Changes: Test Ordering

When implementing infrastructure changes that span multiple routes (e.g., redirect behavior, auth flow), write E2E tests **after** the core mechanism is implemented but **before** applying it to all routes:

1. **Implement core mechanism** (unit-tested) — e.g., `isSameOriginRedirect()`, `redirect()` logic
2. **Write E2E tests** (RED state) — tests fail until all routes are updated
3. **Apply to all routes** (GREEN state) — update each load/action function to use the mechanism

**Why this order?**

- If you write E2E tests before the mechanism exists, they'll fail immediately and block progress
- E2E tests verify integration end-to-end; unit tests verify the building blocks
- The RED→GREEN cycle for E2E tests spans multiple phases, unlike unit tests which turn GREEN immediately

**Example:** Redirect After Login pattern

- Phase 1-2: Unit test `isSameOriginRedirect()` (GREEN)
- Phase 3-4: Unit test auth helpers (GREEN)
- Phase 5-6: Implement login/signup actions (GREEN)
- Phase 7: Write E2E tests for all routes (RED)
- Phase 8-9: Update all load functions to pass `url` (GREEN)

## E2E Lessons from Votes Tests

Recent fixes (detailed in plan.md):

- Dropdown selectors drift when UI changes; re-verify on refactors
- Abort signal prevents stale response race; update signal if fetch signature changes
- Toast messages are temporary; use waiter pattern, not fixed delay

## Timeout: Element Not Found

When `page.locator()` or `getByRole()` times out:

1. Verify element exists in DevTools
2. Check ARIA roles in component UI libraries often lack semantic role definitions
3. If `role="menuitem"` fails: use href pattern or functional marker instead (e.g., `a[href^="/login?redirectTo"]` vs navbar's `/login`)
4. Never use `.first()` / `.last()` (DOM order is unstable)

**Example:** `getByRole('menuitem')` times out because DropdownItem renders as `<li><a>` without the role. Use `a[href^="/login?redirectTo"]` to distinguish dropdown from navbar.
12 changes: 12 additions & 0 deletions .claude/rules/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ E2E files: **must** use `.spec.ts` extension (`.test.ts` not detected).
- Use `toBe(true)` / `toBe(false)` not `toBeTruthy()` / `toBeFalsy()`
- DB query tests: assert `orderBy`, `include` with `expect.objectContaining`, not just `where`
- Enum membership: `Object.hasOwn(Enum, value)` not `in` (avoids prototype chain)
- `Promise<void>`: `.resolves` requires a matcher — without one it does not assert and becomes a false-positive; use `await fn()` to assert no throw, or `.resolves.toBeUndefined()` for explicit form

```typescript
// ✓ Preferred: simplest way to assert Promise<void> does not throw
await ensureSessionOrRedirect(mockLocals);

// ✓ Also correct: explicit resolves form
await expect(ensureSessionOrRedirect(mockLocals)).resolves.toBeUndefined();

// ✗ Wrong: .resolves without a matcher does not assert anything (false-positive)
await expect(ensureSessionOrRedirect(mockLocals)).resolves;
```

### Describe Organization

Expand Down
28 changes: 28 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,34 @@ Always prefer simplicity over pathological correctness. YAGNI, KISS, DRY. No bac
5. After all phases complete (feature and refactor branches only — not hotfixes or dependency bumps): run a mandatory refactor cycle. Write to `plan.md`: novel lessons (implementation blockers, non-obvious patterns not already in rules) and remaining tasks. Discard `phase-N.md` files. Run `coderabbit review --plain`; write all findings of `critical` / `high` / `potential_issue` (medium) to a `## CodeRabbit Findings` section in `plan.md`. The user decides which to fix before opening a PR; do not fix any finding unilaterally. `nitpick` findings defer to PR CI.
6. Run `/session-close` at the end of each session: updates plan checklist, proposes rule/skill additions, checks for bloat, and detects repeated instructions

**Plan Approval ≠ Implementation Start:** Generating a plan (`/writing-plans`) does NOT authorize implementation. Always:

- Wait for explicit user consent ("let's start", "implement", etc.)
- Use AskUserQuestion before starting if requirements are ambiguous (data model, preferences, test scope, etc.)

## Working with the User

### Communication

- **No social pleasantries:** Skip opening remarks like "お疲れ様です", "了解しました", "ありがとうございます"
- **Direct & concise:** Lead with substance (findings, next steps, decisions)
- **Task-focused:** Avoid flattery, apologizing, or performative agreement
- **Respect the work:** Speak plainly about tradeoffs and real issues; don't sugar-coat

### Responding to Feedback

- **Verify before accepting:** Check for misunderstanding, missing context, or counterpoints
- **Question, don't defer:** Ask clarifying questions or offer alternatives with reasoning—don't blindly follow

### Critical Review as Default

When proposing approaches, designs, or solutions:

- **Lead with the proposal:** State the recommendation clearly
- **Then critique it:** Point out tradeoffs, risks, limitations, and when it fails
- **Offer alternatives:** Present 2–3 viable options with reasoning for each
- **Let user decide:** Don't pretend one option is obviously best if it isn't

## Tech Stack

SvelteKit 2 + Svelte 5 (Runes) + TypeScript | PostgreSQL + Prisma | Flowbite Svelte + Tailwind 4 | Vitest + Playwright | oxlint (JS/TS) + ESLint (Svelte)
Expand Down
Loading
Loading