Skip to content

feat: Redirect to the original page after logging in (#1582)#3492

Merged
KATO-Hiro merged 25 commits into
stagingfrom
#1582
May 8, 2026
Merged

feat: Redirect to the original page after logging in (#1582)#3492
KATO-Hiro merged 25 commits into
stagingfrom
#1582

Conversation

@KATO-Hiro

@KATO-Hiro KATO-Hiro commented May 6, 2026

Copy link
Copy Markdown
Collaborator

close #1582

Summary by CodeRabbit

リリースノート

  • 新機能

    • ログイン・サインアップ後、元のページへのリダイレクト機能を追加しました。
    • リダイレクト先のセキュリティ検証機能を実装し、不正なリダイレクトを防止します。
  • テスト

    • ログイン・サインアップのリダイレクト動作を検証する包括的なE2Eテストを追加しました。
    • 認証サービスのユニットテストカバレッジを拡充しました。
  • ドキュメント

    • セキュリティガイドラインとテストベストプラクティスを更新しました。

KATO-Hiro and others added 12 commits May 6, 2026 09:03
Reorganizes auth code into `src/features/auth/` with dedicated services and utilities:
- New services: session, admin_access (moved from routes/_utils/)
- New utilities: login, signup (auth-specific logic extraction)
- Updated routes to use new service layer (admin, login, signup pages)
- Added E2E test for post-login redirect
- Updated rules and AGENTS.md with refined guidelines

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Fix form actions URL parameter: construct from request.url in workbooks delete
- Add admin validation to tasks create/update form actions
- Move getLoggedInUser inside try-catch in users page for error handling
- Update Phase 1 checklist in review document

All unit tests pass (2224 passed). Non-null Assertion NPE fixes deferred to Phase 2
as per user preference for separate PR handling.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Form Actions: `url` Parameter Unavailable
  Form action handlers cannot receive `url` directly; construct from request.url

- Error Handling in load()
  All async operations must be inside try-catch block, not before

- Auth Audit (expanded)
  Clarify that form actions also require audit when one is protected

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Form Actions: `url` Parameter Unavailable (1 line)
- Error Handling in load() (1 line)
- Auth Audit (1 line)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Replace pure function return with Wrapper pattern:
- validateAdminStatus (pure) + ensureAdminOrRedirect (wrapper)
- Solves: Service layer purity + Route handler brevity

Benefits: No redundancy, testable, framework-independent Service layer

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Phase 1 complete:
✓ Form actions URL parameter fix
✓ POST actions admin validation
✓ getLoggedInUser error handling

Remaining: NPE fixes deferred to Phase 2

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Remove:
✓ Form Actions 未保護 (CRITICAL) - fixed in Phase 1
✓ Form actions URL parameter (CRITICAL CodeRabbit) - fixed
✓ getLoggedInUser try-catch (HIGH CodeRabbit) - fixed

Update:
- Executive Summary now shows only Phase 2+ remaining issues
- CodeRabbit sections reflect current state

Document now focuses on Phase 2 and beyond work.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Remove noise:
- ✅ Verified Strengths (already implemented)
- 🎯 Remaining Work (already done in Phase 1)
- ✅ Phase 1 完了報告 (implementation record)

Keep focus on: PR #1582 review findings and Phase 2+ schedule

Document is now a pure "issues to address" resource, not a session record.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 6, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

ログイン・サインアップ後に元のページへ戻る機能を追加。セッション管理と管理者アクセス検証の集約化、URL パラメータ redirectTo による同一オリジン検証、E2E テストの拡充、および関連ドキュメント更新を実施。

Changes

認証インフラストラクチャとセッション管理

Layer / File(s) Summary
セッション・管理者サービス
src/features/auth/services/session.ts, src/features/auth/services/admin_access.ts
セッション検証と管理者アクセス確認の集約化。getLoggedInUserensureSessionOrRedirectvalidateAdminAccess を導入し、オプショナルな url パラメータで動的なリダイレクトパス生成に対応。
ログイン・サインアップパス生成
src/features/auth/utils/login.ts, src/features/auth/utils/signup.ts
buildLoginPathbuildSignupPath ユーティリティで redirectTo クエリパラメータを URL エンコードして生成。
URL 検証
src/lib/utils/url.ts
isSameOriginRedirect を追加して Open Redirect 対策。標準 URL API でオリジン比較により同一オリジンパスのみ許可。
ユーティリティ再構成
src/lib/utils/authorship.ts
セッション管理関数を新しいサービスモジュールへ移動。ensureSessionOrRedirectgetLoggedInUser を削除。
サービステスト
src/features/auth/services/*.test.ts, src/features/auth/utils/*.test.ts, src/lib/utils/url.test.ts
セッション・管理者・ログイン・サインアップ・URL ユーティリティの包括的なテスト(リダイレクト、エラーケース、エンコーディング、origin 検証)。

ルート統合と E2E テスト

Layer / File(s) Summary
管理ルート統合
src/routes/(admin)/*/+page.server.ts, src/routes/(admin)/**/*
全管理ルートで validateAdminAccess(locals, url) による集約化。url パラメータ追加で動的リダイレクトに対応。管理フラグやロール確認ロジック削除。
認証ルート実装
src/routes/(auth)/login/+page.server.ts, src/routes/(auth)/signup/+page.server.ts
redirectTo クエリ処理。isSameOriginRedirect で同一オリジン検証後、ユーザー確認フロー後に検証済みの宛先へリダイレクト。
ユーザー・ワークブックルート更新
src/routes/problems/[slug]/+page.server.ts, src/routes/users/*/+page.server.ts, src/routes/workbooks/*/+page.server.ts
getLoggedInUser(locals, url) で新しいセッションサービスに統一。ルートの loadactionsurl パラメータ追加。
コンポーネント動的化
src/lib/components/SubmissionStatus/UpdatingDropdown.svelte, src/routes/(auth)/login/+page.svelte, src/routes/votes/[slug]/+page.svelte
buildLoginPathbuildSignupPath でリダイレクト先を考慮した動的なリンク生成。静的定数から $derived への切り替え。
E2E テストとヘルパー
e2e/helpers/auth.ts, e2e/redirect_after_login.spec.ts, e2e/*.spec.ts
新しい submitSignupFormsubmitLoginForm ヘルパー。redirect_after_login.spec.ts で login/signup フロー全般の redirectTo 保存と同一オリジン検証を網羅。他スペックではログイン URL をファジィマッチに統一。

ドキュメント・ガイダンス

Layer / File(s) Summary
プロジェクトガイド更新
.claude/rules/auth.md, .claude/rules/sveltekit.md, .claude/rules/testing*.md, .claude/rules/coding-style.md, AGENTS.md
認証・リダイレクトパターン、フォーム処理、エラーハンドリング、E2E テスト戦略、コメント方針、ユーザー同意フローなどの新規・拡張セクション。
sequenceDiagram
    participant Client as ブラウザ
    participant Protected as 保護ページ<br/>(workbooks/create)
    participant Login as ログインページ
    participant Signup as サインアップ
    participant Server as サーバー
    participant Session as セッション・DB

    Client->>Protected: アクセス
    Protected-->>Client: リダイレクト<br/>302 /login?redirectTo=%2Fworkbooks%2Fcreate

    Client->>Login: ログインフォーム表示
    Note over Login: buildLoginPath で<br/>redirectTo を動的リンク化

    Client->>Login: 認証情報送信
    Login->>Server: POST + 認証処理
    Server->>Session: セッション作成
    Server->>Server: isSameOriginRedirect<br/>で redirectTo 検証
    alt redirectTo が同一オリジン
        Server-->>Client: 303 /workbooks/create
    else redirectTo が無効
        Server-->>Client: 303 / (ホーム)
    end

    Client->>Protected: リダイレクト先へアクセス
    Protected->>Server: getLoggedInUser(locals,url)
    Server->>Session: セッション確認
    Session-->>Server: ユーザー情報取得
    Server-->>Client: ページ内容
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

ログイン後、君のいた場所へ
redirectTo が道案内
Open Redirect は一刀両断
origin チェック、堅牢に
迷わぬ UX へようこそ 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PRのタイトルは変更の主要な目的(ログイン後に元のページへリダイレクト)を簡潔に伝えており、changeset全体と関連している。
Linked Issues check ✅ Passed PR全体がリンク issue #1582の要件を満たしている。same-origin検証、redirectToパラメータ、ログイン・サインアップフローへの統合が実装されている。
Out of Scope Changes check ✅ Passed ほぼすべての変更が #1582のスコープ内。ただし複数のドキュメント更新(.claude/rules/)やテスト重構は付随的で許容範囲。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch #1582

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
src/lib/components/AuthForm.svelte (1)

89-92: 🧹 Nitpick | 🔵 Trivial

ゲストログインがリダイレクト先を無視している (FIXME)

handleLoginAsGuest は常に homeHref へ遷移するため、PR の目的(ログイン前のページへ戻る)がゲストログインでは達成されない。buildLoginPath / redirectTo の仕組みが整ったので、alternativePageLink から redirectTo パラメータを取り出して goto に渡すか、フォームの action に組み込む対応が必要。

既存の FIXME を解消する実装を生成するか、新しい Issue を作成しますか?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/AuthForm.svelte` around lines 89 - 92, handleLoginAsGuest
always navigates to homeHref, ignoring any intended post-login redirect; update
handleLoginAsGuest to extract the redirectTo value (or full path) from
alternativePageLink / buildLoginPath and pass it into goto (or construct the
login form action with that redirectTo) instead of unconditionally using
homeHref so guest login returns the user to the pre-login page; locate the
handleLoginAsGuest function and change its navigation call to use the redirect
value provided by buildLoginPath / alternativePageLink (falling back to homeHref
if missing).
src/routes/workbooks/+page.server.ts (1)

104-121: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

削除アクションが未ログインを拒否していません。

getLoggedInUser はこのPR内で nullable として使われています。今の条件だと匿名リクエストは Line 119 の権限チェックを素通りして deleteWorkBook に到達できるので、削除前に ensureSessionOrRedirect か少なくとも 401/redirect を入れる必要があります。

修正案
-import { getLoggedInUser } from '$features/auth/services/session';
+import { ensureSessionOrRedirect } from '$features/auth/services/session';
…
   delete: async ({ locals, request }) => {
     const url = new URL(request.url);
-    const loggedInUser = await getLoggedInUser(locals, url);
+    await ensureSessionOrRedirect(locals, url);
+    const loggedInUser = locals.user;-    if (loggedInUser && !canDelete(loggedInUser.id, workBook.authorId)) {
+    if (!canDelete(loggedInUser.id, workBook.authorId)) {
       error(FORBIDDEN, `問題集id: ${workBookId} にアクセスする権限がありません。`);
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/workbooks/`+page.server.ts around lines 104 - 121, The delete
handler currently allows anonymous requests through because getLoggedInUser can
be null; update the delete action in +page.server.ts to enforce authentication
before any authorization or deletion by calling ensureSessionOrRedirect(locals,
url) or returning a 401/redirect when loggedInUser is null, then use the
non-null loggedInUser for the canDelete check and before calling
workBooksCrud.deleteWorkBook; ensure you reference the existing getLoggedInUser,
canDelete, and workBooksCrud.deleteWorkBook symbols and perform the
null-check/redirect early in the delete flow.
src/routes/workbooks/[slug]/+page.server.ts (1)

35-36: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

未ログインの閲覧拒否が抜けており、未公開問題集を匿名閲覧できます。

Line 35 の条件は loggedInUser がいる場合しか効かないので、isPublished === false かつ未ログインでも本文まで到達します。匿名アクセスも含めて可視性を判定してください。

修正案
-  if (loggedInUser && !canRead(isPublished, loggedInUser.id, authorId)) {
+  if (!loggedInUser && !isPublished) {
+    error(FORBIDDEN, `問題集id: ${slug} にアクセスする権限がありません。`);
+  }
+  if (loggedInUser && !canRead(isPublished, loggedInUser.id, authorId)) {
     error(FORBIDDEN, `問題集id: ${slug} にアクセスする権限がありません。`);
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/workbooks/`[slug]/+page.server.ts around lines 35 - 36, The
current guard only runs when loggedInUser is present, allowing anonymous users
to view unpublished workbooks; change the condition so the visibility check runs
for all requests by calling canRead with the possibly-undefined user id (use
loggedInUser?.id) and reject when canRead(...) returns false — update the if
that references loggedInUser, canRead, isPublished, authorId and slug to perform
the authorization regardless of loggedInUser so anonymous access to unpublished
items is blocked.
src/features/auth/services/admin_access.ts (1)

28-47: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

admin_access は route 層へ寄せた方がいいです。

service 層から redirect() / error() を投げると、SvelteKit 依存がこのレイヤに漏れます。ここは AdminStatus のような純粋な結果だけ返し、+page.server.ts / +server.ts 側で redirect/error に変換してください。

As per coding guidelines "Service layer must return data or null; never call error() or redirect(). HTTP error translation belongs in the route handler—services must stay framework-agnostic and unit-testable."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/auth/services/admin_access.ts` around lines 28 - 47, The service
currently calls SvelteKit helpers (redirect/error) from validateAdminAccess and
validateAdminAccessForApi which leaks framework logic into the service layer;
change both functions to be pure and return an AdminStatus instead of
redirecting or throwing: update validateAdminAccess(locals, url?) to call
validateAdminStatus(locals) and return the AdminStatus (remove
redirect/buildLoginPath usage), and update validateAdminAccessForApi(locals) to
return the status (remove error(UNAUTHORIZED)/error(FORBIDDEN)); move all
redirect/error handling into the route handlers (+page.server.ts / +server.ts)
where the returned AdminStatus is translated into redirect(buildLoginPath(url))
or error(UNAUTHORIZED/FORBIDDEN).
src/routes/(admin)/account_transfer/+page.server.ts (1)

33-68: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

actions.default に管理者アクセス検証が欠落しており、認可バイパスが可能です。

load()validateAdminAccess(locals, url) で守られていますが、POST ハンドラ (actions.default) は保護されていません。SvelteKit の form actions は load を経由せず直接呼ばれるため、認証済みの一般ユーザが直接 POST リクエストを送信して taskResultService.copyTaskResults を実行できる可能性があります。

同 PR 内の他の管理ページ (tasks/+page.server.ts の create/update、vote_management/+page.server.ts の setTaskGrade など) では、各 action 内で validateAdminAccess(locals) を呼び出しており、当ファイルと一貫性が取れていません。

修正案
 export const actions: Actions = {
-  default: async ({ request }) => {
+  default: async ({ locals, request, url }) => {
+    await validateAdminAccess(locals, url);
+
     try {
       const form = await superValidate(request, zod4(accountTransferSchema));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/`(admin)/account_transfer/+page.server.ts around lines 33 - 68,
actions.default is missing an admin authorization check which lets
unauthenticated POSTs invoke taskResultService.copyTaskResults; fix by calling
the existing validateAdminAccess(locals) at the start of actions.default
(mirroring other admin handlers) and aborting/throwing if it fails before
calling taskResultService.copyTaskResults, ensuring you reference the request
handler (actions.default), validateAdminAccess, and
taskResultService.copyTaskResults to locate where to add the check.
src/routes/workbooks/create/+page.server.ts (1)

36-47: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

if (!author) は到達不能なコードです。

getLoggedInUser は、セッション欠如時に redirect() を投げる実装であり、null を返しません。テストケース(session.test.ts)および既存コメント(problems/[slug]/+page.server.ts)で確認済みです。L40 の null チェックは削除してください。

PR メッセージで「try-catch で包んだ」とありますが、このファイルには try-catch がありません。他のルート(users/[username]/+page.server.ts)では try-catch でラップされているので、整合性を取るか、コメントを追加して明記してください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/workbooks/create/`+page.server.ts around lines 36 - 47, Remove the
unreachable null-check for author in actions.default (the `if (!author)` block)
because getLoggedInUser always throws a redirect on missing session and never
returns null; then make the file consistent with other routes by either wrapping
the body of actions.default in a try-catch (as done in
users/[username]/+page.server.ts) to explicitly handle unexpected errors, or add
a brief comment above the call to getLoggedInUser explaining that it throws
redirect on missing session and therefore cannot return null; reference
getLoggedInUser and actions.default when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/rules/auth.md:
- Around line 41-49: Add mention of the signup helper to the docs: update the
"Key Files / Redirect After Login Pattern" section to include buildSignupPath
alongside buildLoginPath, noting that src/features/auth/utils/signup.ts exports
buildSignupPath which generates the signup path with the same redirectTo
behavior as buildLoginPath; ensure the pattern description also indicates
validating redirectTo with isSameOriginRedirect() as already noted for
login/signup actions.

In @.claude/rules/sveltekit.md:
- Around line 54-56: The SvelteKit docs in the "Form Actions" rule incorrectly
state that the url parameter is unavailable; update .claude/rules/sveltekit.md
to reflect that form action handlers (e.g., +page.server.ts handlers) do receive
a destructured url parameter and show a concise usage example using url (or, if
you prefer consistency, state the alternative pattern and recommend using new
URL(request.url) across the codebase); mention the specific identifiers "url",
"request", and "new URL(request.url)" so readers can map the guidance to
existing handlers.

In @.claude/rules/testing.md:
- Around line 35-43: The doc currently shows using `await
expect(ensureSessionOrRedirect(mockLocals)).resolves;` for Promise<void>, which
is incorrect because Vitest's `.resolves` requires a matcher; update the text to
state that `.resolves` must be followed by a matcher (e.g.,
`.resolves.toBeUndefined()` for Promise<void>) or suggest the alternative `await
ensureSessionOrRedirect(mockLocals);` to simply assert no throw; update the
example lines and explanatory sentence accordingly and reference the
`ensureSessionOrRedirect` Promise<void> example so readers know which snippet to
change.

In `@docs/dev-notes/2026-05-06/auth-consolidation-review/review.md`:
- Around line 196-199: 見出し「Phase 1: CRITICAL & HIGH(✅ 完了)」と未完了チェックリスト行 "- [ ]
NPE リスク → null チェックに変更(3 箇所)← Phase 2へ延期" が矛盾しているため、見出しを「Phase 1:
部分完了」に変更するか、該当チェックリスト行を Phase 2 セクションへ移動して整合性を取ってください(対象テキストは見出し文言と該当チェックリスト行)。
- Around line 195-216: Phase 1 marked the NPE items as deferred to Phase 2 but
did not create a tracked Issue/PR; create a dedicated Issue (or PR) that tracks
the HIGH NPE work (items referencing loggedInUser!.id and the Phase 2 refactors:
ensureSessionOrRedirect, getLoggedInUser return type,
validateAdminStatus/ensureAdminOrRedirect, validateAdminAccessForApi), then
update this review doc to reference that Issue/PR by number/URL and change the
checklist lines for the three null-check items to point to the new Issue/PR so
the NPE work is clearly linked and can be triaged before merge.

In `@src/features/auth/services/admin_access.test.ts`:
- Around line 50-55: The test uses an incomplete async assertion—change the
`await expect(validateAdminAccess(mockLocals)).resolves;` pattern to a complete
assertion by either adding a matcher like `resolves.toBeUndefined()` or simply
awaiting the call (`await validateAdminAccess(mockLocals)`); update both
occurrences that mock `userService.getUser` with `Roles.ADMIN` (the test around
`validateAdminAccess`) so the test actually verifies success rather than leaving
the assertion unfinished.

In `@src/features/auth/services/session.test.ts`:
- Around line 49-54: The test uses await
expect(ensureSessionOrRedirect(mockLocals)).resolves; which doesn't assert
resolution; change the assertion to include a matcher (e.g. await
expect(ensureSessionOrRedirect(mockLocals)).resolves.toBeUndefined()) so the
Promise resolution is actually verified; keep the existing verify for
mockLocals.auth.validate toHaveBeenCalledTimes(1) and update the test around
ensureSessionOrRedirect and createMockLocalsWithValidSession accordingly.

In `@src/features/auth/services/session.ts`:
- Around line 1-49: サービス層の getLoggedInUser と ensureSessionOrRedirect が直接
redirect() を投げているためフレームワーク依存になっているので、これらから redirect 呼び出しを削除してサービスは純粋にデータ(user
または null)を返すようにリファクタしてください;具体的には getLoggedInUser / ensureSessionOrRedirect
を廃止または書き換えして getSession(locals) / getCurrentUser(locals)(または
getLoggedInUserOptional)を実装し、これらは locals.auth.validate() の結果をそのまま返す(ユーザがいなければ
null);その後ルート側(+page.server.ts など)や新しい guard (例: requireLoggedInUser(event) を
src/lib/server/guards/) にて null チェックを行い、必要な場合にのみ redirect(TEMPORARY_REDIRECT,
buildLoginPath(url)) を投げるようにしてください。

In `@src/features/auth/utils/signup.ts`:
- Around line 8-14: The buildSignupPath function is API-asymmetric with
buildLoginPath (it currently accepts only string | null while buildLoginPath
accepts string | URL | null), so update buildSignupPath's signature to accept
string | URL | null and normalize a URL argument to the same string form used by
buildLoginPath (e.g., use the URL's pathname+search or equivalent) before
encoding and appending as redirectTo; alternatively, if you prefer not to change
the signature, add a clear JSDoc on buildSignupPath describing that callers must
pass "pathname+search" strings and update tests to match—make sure to reference
buildSignupPath and buildLoginPath when making the change so both APIs remain
consistent.

In `@src/lib/utils/url.test.ts`:
- Around line 30-32: Replace usage of Jest's toBeTruthy()/toBeFalsy() with
strict toBe(true)/toBe(false) in the url tests: update the runTests callback for
isValidUrl (the runTests('isValidUrl', testCases, ({ rawUrl }:
TestCaseForUrlValidation) => { ... }) call) to
expect(isValidUrl(rawUrl)).toBe(true) or .toBe(false) as appropriate, and make
the same change for isValidUrlSlug tests and any other assertions in this file
that use toBeTruthy()/toBeFalsy() (the other occurrences around the same test
blocks noted in the comment). Keep the existing test data and assertion logic
unchanged except for replacing the matcher to the strict boolean form.

In `@src/routes/`(admin)/tags/[tag_id]/+page.server.ts:
- Around line 13-17: tagService.getTag may return an empty array so reading
tags[0] can yield undefined and crash the page; update the load logic in
+page.server.ts to check the result of tagService.getTag (the tags variable) and
if tags.length === 0 (or tags[0] is falsy) throw a 404 error or perform a
redirect instead of returning tag: tags[0], ensuring any downstream code
receives a guaranteed non-null tag; reference tagService.getTag, the tags local,
and the returned tag property when implementing the guard.

In `@src/routes/`(auth)/signup/+page.server.ts:
- Line 30: The form action handler currently accepts and uses the `url`
parameter (in the `default` action) which violates the rule; replace uses of
`url` inside the `default: async ({ request, locals, url }) => {` handler by
removing `url` from the parameter list and creating `const parsedUrl = new
URL(request.url)` at the start of the handler, then update all references
`url.searchParams` -> `parsedUrl.searchParams` and `url.origin` ->
`parsedUrl.origin` (including the referenced block around the current 89-98
lines) so the action derives URL info from the request rather than the injected
`url` param.

In `@src/routes/problems/`[slug]/+page.server.ts:
- Around line 11-16: The code assumes getLoggedInUser(...) is non-null and uses
loggedInUser.id directly; instead call ensureSessionOrRedirect(locals, url) at
the start of load to enforce the protected route, then use locals.user.id for
the task lookup (replace uses of loggedInUser.id in getTaskResult and related
calls). Update the load function to call ensureSessionOrRedirect(locals, url)
before invoking crud.getTaskResult and remove the non-null assertion on
getLoggedInUser there; apply the same change for the other occurrences mentioned
(lines 21-29) that use loggedInUser.id.

In `@src/routes/users/edit/`+page.server.ts:
- Around line 41-44: The catch block that currently redirects to /login hides
downstream failures (e.g., getUser) even though ensureSessionOrRedirect already
handled auth; instead of calling redirect(buildLoginPath(...)), log the error
and surface a 500 so the guard can remain responsible for auth flows—i.e., in
the catch for the user lookup in +page.server.ts (referencing getUser,
ensureSessionOrRedirect, redirect, buildLoginPath, TEMPORARY_REDIRECT), remove
the redirect to login and either rethrow the error or raise a server 500 (use
SvelteKit's error helper or throw) after logging so actual failures are not
masked.

In `@src/routes/workbooks/edit/`[slug]/+page.server.ts:
- Around line 24-25: getLoggedInUser is guaranteed to throw redirect() and
always return a user, so remove redundant null checks and optional chaining:
replace loggedInUser?.role with loggedInUser.role when calling
isAdmin(loggedInUser.role as Roles), and remove any conditional guards like if
(loggedInUser && !canEdit(...))—call canEdit(loggedInUser, ...) directly; update
any related code blocks (e.g., the checks around canEdit and subsequent branches
between the earlier and later occurrences) to assume loggedInUser is non-null
and keep the Roles cast/use as before.

---

Outside diff comments:
In `@src/features/auth/services/admin_access.ts`:
- Around line 28-47: The service currently calls SvelteKit helpers
(redirect/error) from validateAdminAccess and validateAdminAccessForApi which
leaks framework logic into the service layer; change both functions to be pure
and return an AdminStatus instead of redirecting or throwing: update
validateAdminAccess(locals, url?) to call validateAdminStatus(locals) and return
the AdminStatus (remove redirect/buildLoginPath usage), and update
validateAdminAccessForApi(locals) to return the status (remove
error(UNAUTHORIZED)/error(FORBIDDEN)); move all redirect/error handling into the
route handlers (+page.server.ts / +server.ts) where the returned AdminStatus is
translated into redirect(buildLoginPath(url)) or error(UNAUTHORIZED/FORBIDDEN).

In `@src/lib/components/AuthForm.svelte`:
- Around line 89-92: handleLoginAsGuest always navigates to homeHref, ignoring
any intended post-login redirect; update handleLoginAsGuest to extract the
redirectTo value (or full path) from alternativePageLink / buildLoginPath and
pass it into goto (or construct the login form action with that redirectTo)
instead of unconditionally using homeHref so guest login returns the user to the
pre-login page; locate the handleLoginAsGuest function and change its navigation
call to use the redirect value provided by buildLoginPath / alternativePageLink
(falling back to homeHref if missing).

In `@src/routes/`(admin)/account_transfer/+page.server.ts:
- Around line 33-68: actions.default is missing an admin authorization check
which lets unauthenticated POSTs invoke taskResultService.copyTaskResults; fix
by calling the existing validateAdminAccess(locals) at the start of
actions.default (mirroring other admin handlers) and aborting/throwing if it
fails before calling taskResultService.copyTaskResults, ensuring you reference
the request handler (actions.default), validateAdminAccess, and
taskResultService.copyTaskResults to locate where to add the check.

In `@src/routes/workbooks/`[slug]/+page.server.ts:
- Around line 35-36: The current guard only runs when loggedInUser is present,
allowing anonymous users to view unpublished workbooks; change the condition so
the visibility check runs for all requests by calling canRead with the
possibly-undefined user id (use loggedInUser?.id) and reject when canRead(...)
returns false — update the if that references loggedInUser, canRead,
isPublished, authorId and slug to perform the authorization regardless of
loggedInUser so anonymous access to unpublished items is blocked.

In `@src/routes/workbooks/`+page.server.ts:
- Around line 104-121: The delete handler currently allows anonymous requests
through because getLoggedInUser can be null; update the delete action in
+page.server.ts to enforce authentication before any authorization or deletion
by calling ensureSessionOrRedirect(locals, url) or returning a 401/redirect when
loggedInUser is null, then use the non-null loggedInUser for the canDelete check
and before calling workBooksCrud.deleteWorkBook; ensure you reference the
existing getLoggedInUser, canDelete, and workBooksCrud.deleteWorkBook symbols
and perform the null-check/redirect early in the delete flow.

In `@src/routes/workbooks/create/`+page.server.ts:
- Around line 36-47: Remove the unreachable null-check for author in
actions.default (the `if (!author)` block) because getLoggedInUser always throws
a redirect on missing session and never returns null; then make the file
consistent with other routes by either wrapping the body of actions.default in a
try-catch (as done in users/[username]/+page.server.ts) to explicitly handle
unexpected errors, or add a brief comment above the call to getLoggedInUser
explaining that it throws redirect on missing session and therefore cannot
return null; reference getLoggedInUser and actions.default when making the
change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 43aa17d7-e54f-4eee-8f69-cae4bcb2ebe6

📥 Commits

Reviewing files that changed from the base of the PR and between 6e90b4b and a043ccb.

📒 Files selected for processing (45)
  • .claude/rules/auth.md
  • .claude/rules/coding-style.md
  • .claude/rules/sveltekit.md
  • .claude/rules/testing-e2e.md
  • .claude/rules/testing.md
  • AGENTS.md
  • docs/dev-notes/2026-05-06/auth-consolidation-review/review.md
  • e2e/helpers/auth.ts
  • e2e/redirect_after_login.spec.ts
  • e2e/votes.spec.ts
  • e2e/workbook_order.spec.ts
  • e2e/workbooks_list.spec.ts
  • src/features/auth/services/admin_access.test.ts
  • src/features/auth/services/admin_access.ts
  • src/features/auth/services/session.test.ts
  • src/features/auth/services/session.ts
  • src/features/auth/utils/login.test.ts
  • src/features/auth/utils/login.ts
  • src/features/auth/utils/signup.test.ts
  • src/features/auth/utils/signup.ts
  • src/lib/components/AuthForm.svelte
  • src/lib/components/SubmissionStatus/UpdatingDropdown.svelte
  • src/lib/utils/authorship.test.ts
  • src/lib/utils/authorship.ts
  • src/lib/utils/url.test.ts
  • src/lib/utils/url.ts
  • src/routes/(admin)/account_transfer/+page.server.ts
  • src/routes/(admin)/tags/+page.server.ts
  • src/routes/(admin)/tags/[tag_id]/+page.server.ts
  • src/routes/(admin)/tasks/+page.server.ts
  • src/routes/(admin)/tasks/[task_id]/+page.server.ts
  • src/routes/(admin)/vote_management/+page.server.ts
  • src/routes/(admin)/workbooks/order/+page.server.ts
  • src/routes/(admin)/workbooks/order/+server.ts
  • src/routes/(auth)/login/+page.server.ts
  • src/routes/(auth)/login/+page.svelte
  • src/routes/(auth)/signup/+page.server.ts
  • src/routes/problems/[slug]/+page.server.ts
  • src/routes/users/[username]/+page.server.ts
  • src/routes/users/edit/+page.server.ts
  • src/routes/votes/[slug]/+page.svelte
  • src/routes/workbooks/+page.server.ts
  • src/routes/workbooks/[slug]/+page.server.ts
  • src/routes/workbooks/create/+page.server.ts
  • src/routes/workbooks/edit/[slug]/+page.server.ts
💤 Files with no reviewable changes (1)
  • src/lib/utils/authorship.ts

Comment thread .claude/rules/auth.md
Comment thread .claude/rules/sveltekit.md Outdated
Comment thread .claude/rules/testing.md Outdated
Comment thread docs/dev-notes/2026-05-06/auth-consolidation-review/review.md Outdated
Comment thread docs/dev-notes/2026-05-06/auth-consolidation-review/review.md Outdated
Comment thread src/routes/(admin)/tags/[tag_id]/+page.server.ts Outdated
Comment thread src/routes/(auth)/signup/+page.server.ts
Comment on lines +11 to 16
export async function load({ locals, params, url }) {
// getLoggedInUser either returns a user or throws redirect(); null is unreachable.
// Non-null assertion (!) is safe here.
const loggedInUser = await getLoggedInUser(locals, url);
const taskResult = await crud.getTaskResult(params.slug as string, loggedInUser.id);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

保護ルートで nullable な取得関数を non-null 前提にしています。

このPR内では getLoggedInUser を公開ページでも使っており、ここだけ loggedInUser.id に直行するのは危険です。未ログイン時に redirect-after-login ではなく 500 になるので、ensureSessionOrRedirect(locals, url) を先に通してから locals.user.id を使ってください。

修正案
-import { getLoggedInUser } from '$features/auth/services/session';
+import { ensureSessionOrRedirect } from '$features/auth/services/session';-  const loggedInUser = await getLoggedInUser(locals, url);
-  const taskResult = await crud.getTaskResult(params.slug as string, loggedInUser.id);
+  await ensureSessionOrRedirect(locals, url);
+  const taskResult = await crud.getTaskResult(params.slug as string, locals.user.id);-    const loggedInUser = await getLoggedInUser(locals, url);
-    const userId = loggedInUser.id;
+    await ensureSessionOrRedirect(locals, url);
+    const userId = locals.user.id;

Also applies to: 21-29

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/problems/`[slug]/+page.server.ts around lines 11 - 16, The code
assumes getLoggedInUser(...) is non-null and uses loggedInUser.id directly;
instead call ensureSessionOrRedirect(locals, url) at the start of load to
enforce the protected route, then use locals.user.id for the task lookup
(replace uses of loggedInUser.id in getTaskResult and related calls). Update the
load function to call ensureSessionOrRedirect(locals, url) before invoking
crud.getTaskResult and remove the non-null assertion on getLoggedInUser there;
apply the same change for the other occurrences mentioned (lines 21-29) that use
loggedInUser.id.

Comment thread src/routes/users/edit/+page.server.ts Outdated
Comment thread src/routes/workbooks/edit/[slug]/+page.server.ts
KATO-Hiro and others added 8 commits May 6, 2026 13:37
…iew findings

- Add `validateAdminAccess(locals, url)` to account_transfer form action to
  prevent unauthenticated POST bypass (load() is not called for form actions)
- Document critical code review findings in review.md: issue resolutions,
  Phase 2 refactor risks, and corrected SvelteKit rule for url parameter

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…esolved

Form action handlers do receive `url` as part of RequestEvent; the old rule
incorrectly stated it was unavailable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Accept `string | URL | null` (matching buildLoginPath) so callers don't need
manual URL.pathname extraction. Document buildSignupPath in auth.md Key Files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…g rule

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DB errors in getUser() were masked as login redirects, hiding the real failure.
Now logs the error and returns INTERNAL_SERVER_ERROR to the caller.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/dev-notes/2026-05-06/auth-consolidation-review/review.md`:
- Around line 160-200: Fix the stray missing closing parenthesis in the Phase 2
checklist by appending the missing ")" to the "NPE リスク → null チェックに変更(3 箇所"
entry so it reads correctly, and optionally add a reference link to a tracking
Issue/PR for the HIGH NPE item (e.g., annotate the "NPE リスク" line with an
Issue/PR URL) to improve follow-up; locate the text within the Phase 2 block
header and the exact string "NPE リスク → null チェックに変更(3 箇所" to apply the edits.

In `@src/routes/users/edit/`+page.server.ts:
- Around line 20-29: The code calls locals.user without guaranteeing it exists
after ensureSessionOrRedirect, causing potential TypeErrors and an incorrect
isLoggedIn boolean; update the handler to explicitly check for locals.user (or
call the existing getLoggedInUser() helper from session.ts) before accessing
properties, bail or redirect when no user is present, and compute isLoggedIn as
a strict comparison of both defined ids (e.g., Boolean(loggedInUser && user &&
loggedInUser.id === user.id)); touch references to ensureSessionOrRedirect,
userService.getUser, locals.user, getLoggedInUser, and the isLoggedIn
calculation when making the fix.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 89684b25-3858-4b0f-a51b-faebb05263ee

📥 Commits

Reviewing files that changed from the base of the PR and between a043ccb and 27886d9.

📒 Files selected for processing (9)
  • .claude/rules/auth.md
  • .claude/rules/sveltekit.md
  • .claude/rules/testing.md
  • docs/dev-notes/2026-05-06/auth-consolidation-review/review.md
  • src/features/auth/services/admin_access.test.ts
  • src/features/auth/services/session.test.ts
  • src/features/auth/utils/signup.ts
  • src/routes/(admin)/account_transfer/+page.server.ts
  • src/routes/users/edit/+page.server.ts

Comment thread docs/dev-notes/2026-05-06/auth-consolidation-review/review.md Outdated
Comment thread src/routes/users/edit/+page.server.ts Outdated
KATO-Hiro and others added 2 commits May 6, 2026 14:12
…tion

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rs/edit load

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
….test.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@KATO-Hiro

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 6, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/dev-notes/2026-05-06/auth-consolidation-review/review.md`:
- Around line 273-274: The two relative links in
docs/dev-notes/2026-05-06/auth-consolidation-review/review.md ("../../AGENTS.md"
and "../../.claude/rules/auth.md") are incorrect from this file's location;
update them to correct root-based relative paths (for example
"../../../../AGENTS.md" and "../../../../.claude/rules/auth.md" or whatever
reaches the repository root) so the links resolve properly, and verify by
opening the updated AGENTS.md and .claude/rules/auth.md targets from the
review.md location.
- Around line 38-44: The example in the load function incorrectly calls
fail(BAD_REQUEST, ...) which is only valid in form actions; update the load
handler to throw an HTTP error instead: replace the fail(...) call with throw
error(401, 'Unauthorized') (ensure the example references the same
loggedInUser/null check and userId assignment and import error from
`@sveltejs/kit` if imports are shown) so readers see the correct load() usage.

In `@src/lib/utils/url.test.ts`:
- Line 43: The comment in src/lib/utils/url.test.ts containing "Note:
記事のセクション(`#マーク`)を含む場合は、2024年8月時点では無効なURLとして処理。ユーザの要望が多ければ、修正。" must be rewritten
in English to comply with source comment rules; locate that inline comment in
url.test.ts and replace it with an equivalent English sentence such as "Note:
URLs containing article sections (# fragments) are treated as invalid as of Aug
2024; adjust if user demand increases." ensuring the meaning is preserved and no
other logic is changed.
- Around line 217-247: Add a regression test ensuring absolute same-origin URLs
are rejected: in the test suite for isSameOriginRedirect, add a case that calls
isSameOriginRedirect('http://localhost:5174/workbooks/bfs', requestOrigin) (or
equivalent origin pair) and expects false, verifying the implementation enforces
"relative-path-only" even for same-origin absolute URLs; update the describe
block for failure cases to include this new test so regressions where absolute
same-origin URLs are allowed will be caught.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 20f772d7-604d-4e0b-8c3d-fe44587530f2

📥 Commits

Reviewing files that changed from the base of the PR and between 27886d9 and 15e8365.

📒 Files selected for processing (3)
  • docs/dev-notes/2026-05-06/auth-consolidation-review/review.md
  • src/lib/utils/url.test.ts
  • src/routes/users/edit/+page.server.ts

Comment thread docs/dev-notes/2026-05-06/auth-consolidation-review/review.md Outdated
Comment thread docs/dev-notes/2026-05-06/auth-consolidation-review/review.md Outdated
Comment thread src/lib/utils/url.test.ts
Comment thread src/lib/utils/url.test.ts
KATO-Hiro and others added 2 commits May 7, 2026 11:15
refactor: replace findMany with findUnique for getTag

Improves type safety and performance by using findUnique instead of findMany,
returning Tag | null instead of Tag[]. Updates page.server.ts to handle the
single tag result and error when tag is not found.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/routes/`(admin)/tags/[tag_id]/+page.server.ts:
- Around line 17-19: Replace the awkward English error message string used when
a tag is missing: in the block that checks the variable tag and calls
error(NOT_FOUND, 'Not found tag'), change the message to a natural phrase like
'Tag not found' so the call error(NOT_FOUND, 'Tag not found') (referencing tag,
error, and NOT_FOUND) uses conventional wording.
- Around line 14-15: Both calls to taskTagService.getTasks and tagService.getTag
can run in parallel; replace the sequential awaits for getTasks and getTag with
a Promise.all that runs taskTagService.getTasks(params.tag_id as string) and
tagService.getTag(params.tag_id as string) concurrently, then destructure the
resulting array into tasks and tag. Keep the same parameter casts and variable
names (tasks: Tasks, tag) and preserve existing error handling/logic after these
values are returned.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: da909463-8997-412e-9fb7-6eeb67b64898

📥 Commits

Reviewing files that changed from the base of the PR and between 15e8365 and b5dfe77.

📒 Files selected for processing (3)
  • src/lib/services/tags.test.ts
  • src/lib/services/tags.ts
  • src/routes/(admin)/tags/[tag_id]/+page.server.ts

Comment thread src/routes/(admin)/tags/[tag_id]/+page.server.ts
Comment thread src/routes/(admin)/tags/[tag_id]/+page.server.ts

@KATO-Hiro KATO-Hiro left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@KATO-Hiro KATO-Hiro merged commit f9473b2 into staging May 8, 2026
3 checks passed
@KATO-Hiro KATO-Hiro deleted the #1582 branch May 8, 2026 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ログイン機能: ログイン前にいたページに戻れるようにしましょう

1 participant