Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 6 additions & 6 deletions .claude/rules/auth.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
description: Authentication rules
paths:
- 'src/lib/server/auth.ts'
- 'src/routes/(auth)/**'
- 'src/features/auth/**'
- 'src/routes/(admin)/**'
- 'src/features/auth/**'
- 'src/features/auth/server/**'
- 'src/hooks.server.ts'
---

Expand All @@ -31,11 +31,11 @@ Auth is a self-managed implementation (no external auth library), kept compatibl

## Key Files

- `src/lib/server/auth.ts`: `createAuthRequest` (request-scoped session handle)
- `src/lib/server/session.ts` / `src/lib/server/password.ts`: self-managed session + password crypto
- `src/features/auth/services/credentials.ts`: `registerUser` / `authenticateUser`
- `src/hooks.server.ts`: Global request handler
- `src/features/auth/services/session.ts`:
- `src/features/auth/server/auth.ts`: `createAuthRequest` (request-scoped session handle)
- `src/features/auth/server/session.ts` / `src/features/auth/server/password.ts`: self-managed session + password crypto (`random.ts` provides salt/session-id generation)
- `src/features/auth/services/credentials.ts`: `registerUser` / `authenticateUser`
- `src/features/auth/services/session_guards.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`:
Expand Down
17 changes: 14 additions & 3 deletions docs/guides/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ src/features/
- `detail/` — 詳細ページ用コンポーネント
- `shared/` — feature 内で複数ページから使うコンポーネント

**`server/` サブディレクトリ規約:**

feature 内のサーバ専用・非サービスコード(リクエストスコープのハンドル、cookie 書き込み、
crypto ユーティリティなど、`services/` の「純粋な値/`null` を返す framework 非依存」規約に
収まらないもの)は `features/{feature}/server/` に置く(`votes/server/`・`workbooks/server/`・
`auth/server/` が前例)。

- `$lib/server/` を出るため SvelteKit のクライアント import 禁止(ビルド時エラー)は**効かない**。
保護は規約ベース + 推移的依存(`$lib/server/database` の import や `node:crypto` 依存)に委ねる
- 2つ以上の feature で使うサーバコードは `src/lib/server/` に昇格させる(例: `database.ts`)

### ルート固有の `_types/` / `_utils/` ディレクトリ

SvelteKit のルートディレクトリ内で、そのページ専用の型やユーティリティを colocate するために `_types/` と `_utils/` を使う。アンダースコアプレフィックスにより SvelteKit のルーティング対象外となる。
Expand Down Expand Up @@ -147,9 +158,9 @@ src/lib/
├── clients/ # 外部 API クライアント(AtCoder Problems, AOJ)
├── components/ # 共通 UI コンポーネント(GradeLabel, TaskGradeList, TaskList, FormWrapper 等)
├── constants/ # アプリ定数
├── server/ # サーバー専用コード
│ ├── auth.ts
│ ├── database.ts
├── server/ # サーバー専用の共有インフラ
│ ├── database.ts # Prisma クライアント(14+ サービスが依存)
│ ├── tasks/ # cache.ts など複数 feature 共有のサーバ処理
│ └── services/ # 複数 feature で使うビジネスロジック
├── stores/ # 共通ストア(error_message 等)
├── types/ # 共通型定義
Expand Down
2 changes: 1 addition & 1 deletion prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
defineWorkBookFactory,
} from './.fabbrica';
import PQueue from 'p-queue';
import { hashPassword } from '../src/lib/server/password';
import { hashPassword } from '../src/features/auth/server/password';

import { getTaskGrade } from '../src/lib/types/task';
import type { PlacementCreate } from '../src/features/workbooks/types/workbook_placement';
Expand Down
2 changes: 1 addition & 1 deletion src/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ declare global {
namespace App {
// interface Error {}
interface Locals {
auth: import('$lib/server/auth').AuthRequest;
auth: import('$features/auth/server/auth').AuthRequest;
user: {
id: string;
name: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { Roles } from '@prisma/client';
// dev = false so `secure: !dev` resolves to true (the production-relevant cookie flag)
vi.mock('$app/environment', () => ({ dev: false }));

vi.mock('$lib/server/session', () => ({
vi.mock('./session', () => ({
SESSION_COOKIE_NAME: 'auth_session',
validateSession: vi.fn(),
}));

import { createAuthRequest } from './auth';
import { validateSession, SESSION_COOKIE_NAME } from '$lib/server/session';
import { validateSession, SESSION_COOKIE_NAME } from './session';

const mockValidateSession = validateSession as unknown as ReturnType<typeof vi.fn>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
validateSession,
type SessionCookieData,
type ValidatedSession,
} from '$lib/server/session';
} from './session';

export type AuthRequest = {
validate: () => Promise<ValidatedSession | null>;
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
import type { Roles } from '@prisma/client';

import client from '$lib/server/database';
import { generateRandomString } from '$lib/server/random';
import { generateRandomString } from './random';

// lucia v2 defaults: active 24h, idle +14d
const ACTIVE_PERIOD_MS = 1000 * 60 * 60 * 24;
Expand Down
4 changes: 2 additions & 2 deletions src/features/auth/services/credentials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ vi.mock('$lib/server/database', () => ({
},
}));

vi.mock('$lib/server/password', () => ({
vi.mock('../server/password', () => ({
hashPassword: vi.fn(),
verifyPassword: vi.fn(),
}));

import db from '$lib/server/database';
import { hashPassword, verifyPassword } from '$lib/server/password';
import { hashPassword, verifyPassword } from '../server/password';
import { registerUser, authenticateUser } from './credentials';

const mockDb = db as unknown as {
Expand Down
4 changes: 2 additions & 2 deletions src/features/auth/services/credentials.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';

import client from '$lib/server/database';
import { hashPassword, verifyPassword } from '$lib/server/password';
import { generateRandomString } from '$lib/server/random';
import { hashPassword, verifyPassword } from '../server/password';
import { generateRandomString } from '../server/random';

const USER_ID_LENGTH = 15; // lucia v2 createUser default

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ afterEach(() => {
vi.clearAllMocks();
});

import { ensureSessionOrRedirect, getLoggedInUser } from './session';
import { ensureSessionOrRedirect, getLoggedInUser } from './session_guards';

const createMockLocalsWithValidSession = (user = { id: 'test-user', name: 'Test User' }) =>
({
Expand Down
2 changes: 1 addition & 1 deletion src/hooks.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// https://tech-blog.rakus.co.jp/entry/20230209/sveltekit
import type { Handle } from '@sveltejs/kit';

import { createAuthRequest } from '$lib/server/auth';
import { createAuthRequest } from '$features/auth/server/auth';

import * as userService from '$lib/services/users';

Expand Down
1 change: 0 additions & 1 deletion src/lib/components/TagListForEdit.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
//import { ATCODER_BASE_CONTEST_URL } from '$lib/constants/urls';

import { newline } from '$lib/utils/newline';
//import { tasks } from '../server/sample_data';

//gradeでソート済みのTaskのリストと、APIから取得したtasklistを表示する
//xport let tasks: Task[];
Expand Down
48 changes: 0 additions & 48 deletions src/lib/server/sample_data.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/routes/(auth)/login/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// See src/lib/utils/auth_forms.ts for the current form handling approach.
import { fail, redirect } from '@sveltejs/kit';

import { createSession } from '$lib/server/session';
import { createSession } from '$features/auth/server/session';
import { authenticateUser } from '$features/auth/services/credentials';

import { initializeAuthForm, validateAuthFormWithFallback } from '$lib/utils/auth_forms';
Expand Down
2 changes: 1 addition & 1 deletion src/routes/(auth)/logout/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { fail, redirect } from '@sveltejs/kit';

import { invalidateSession } from '$lib/server/session';
import { invalidateSession } from '$features/auth/server/session';

import { SEE_OTHER, UNAUTHORIZED } from '$lib/constants/http-response-status-codes';
import { HOME_PAGE } from '$lib/constants/navbar-links';
Expand Down
2 changes: 1 addition & 1 deletion src/routes/(auth)/signup/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// See src/lib/utils/auth_forms.ts for the current form handling approach.
import { fail, redirect } from '@sveltejs/kit';

import { createSession } from '$lib/server/session';
import { createSession } from '$features/auth/server/session';
import { registerUser } from '$features/auth/services/credentials';

import { initializeAuthForm, validateAuthFormWithFallback } from '$lib/utils/auth_forms';
Expand Down
2 changes: 1 addition & 1 deletion src/routes/problems/[slug]/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { fail, type Actions, redirect } from '@sveltejs/kit';

import * as crud from '$lib/services/task_results';
import { getButtons } from '$lib/services/submission_status';
import { getLoggedInUser } from '$features/auth/services/session';
import { getLoggedInUser } from '$features/auth/services/session_guards';

import { BAD_REQUEST, TEMPORARY_REDIRECT } from '$lib/constants/http-response-status-codes';

Expand Down
3 changes: 1 addition & 2 deletions src/routes/users/[username]/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

import * as userService from '$lib/services/users';
import * as taskResultService from '$lib/services/task_results';
import { getLoggedInUser } from '$features/auth/services/session_guards';

import type { Roles } from '$lib/types/user';
import type { TaskResult } from '$lib/types/task';

import { getLoggedInUser } from '$features/auth/services/session';

export async function load({ locals, params, url }) {
const loggedInUser = await getLoggedInUser(locals, url);

Expand Down
3 changes: 1 addition & 2 deletions src/routes/users/edit/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import type { Roles } from '$lib/types/user';

import * as userService from '$lib/services/users';
import * as verificationService from '$features/account/services/atcoder_verification';

import { getLoggedInUser } from '$features/auth/services/session';
import { getLoggedInUser } from '$features/auth/services/session_guards';

import {
BAD_REQUEST,
Expand Down
17 changes: 8 additions & 9 deletions src/routes/workbooks/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { error, redirect } from '@sveltejs/kit';

import * as taskCrud from '$lib/services/tasks';
import { buildTaskIdsFromWorkbooks } from '$features/workbooks/utils/workbooks';
import * as taskResultsCrud from '$lib/services/task_results';
import { getLoggedInUser } from '$features/auth/services/session_guards';
import * as workBooksCrud from '$features/workbooks/services/workbooks';
import {
getWorkbooksByPlacement,
getWorkBooksCreatedByUsers,
getAvailableSolutionCategories,
getSolutionCategoryMapByWorkbookId,
} from '$features/workbooks/services/workbooks';

import { Roles } from '$lib/types/user';
import type { TaskGrade, TaskResult } from '$lib/types/task';
Expand All @@ -19,15 +25,8 @@ import {
type SolutionCategory,
} from '$features/workbooks/types/workbook_placement';

import {
getWorkbooksByPlacement,
getWorkBooksCreatedByUsers,
getAvailableSolutionCategories,
getSolutionCategoryMapByWorkbookId,
} from '$features/workbooks/services/workbooks';

import { isAdmin, canDelete } from '$lib/utils/authorship';
import { getLoggedInUser } from '$features/auth/services/session';
import { buildTaskIdsFromWorkbooks } from '$features/workbooks/utils/workbooks';
import {
parseWorkBookTab,
parseWorkBookGrade,
Expand Down
4 changes: 2 additions & 2 deletions src/routes/workbooks/[slug]/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { zod4 } from 'sveltekit-superforms/adapters';

import { Roles } from '$lib/types/user';

import { getLoggedInUser } from '$features/auth/services/session_guards';
import * as taskResultsCrud from '$lib/services/task_results';
import { getWorkbookWithAuthor } from '$features/workbooks/services/workbooks';
import * as action from '$lib/actions/update_task_result';
import { getWorkbookWithAuthor } from '$features/workbooks/services/workbooks';
import { getVoteGradeStatisticsForTaskIds } from '$features/votes/services/vote_statistics';
import { voteAbsoluteGrade as voteAbsoluteGradeAction } from '$features/votes/actions/vote_actions';
import { voteAbsoluteGradeSchema } from '$features/votes/zod/schema';

import { isAdmin, canRead } from '$lib/utils/authorship';
import { getLoggedInUser } from '$features/auth/services/session';
import { parseWorkBookId, parseWorkBookUrlSlug } from '$features/workbooks/utils/workbook';

import { BAD_REQUEST, FORBIDDEN, NOT_FOUND } from '$lib/constants/http-response-status-codes';
Expand Down
5 changes: 2 additions & 3 deletions src/routes/workbooks/create/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import { error, fail, redirect } from '@sveltejs/kit';
import { superValidate } from 'sveltekit-superforms/server';
import { zod4 } from 'sveltekit-superforms/adapters';

import { workBookSchema } from '$features/workbooks/zod/schema';

import * as tasksCrud from '$lib/services/tasks';
import { ensureSessionOrRedirect, getLoggedInUser } from '$features/auth/services/session_guards';
import * as workBooksCrud from '$features/workbooks/services/workbooks';
import { workBookSchema } from '$features/workbooks/zod/schema';

import { Roles } from '$lib/types/user';

import { ensureSessionOrRedirect, getLoggedInUser } from '$features/auth/services/session';
import {
BAD_REQUEST,
FORBIDDEN,
Expand Down
2 changes: 1 addition & 1 deletion src/routes/workbooks/edit/[slug]/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { Roles } from '$lib/types/user';
import { workBookSchema } from '$features/workbooks/zod/schema';

import * as tasksCrud from '$lib/services/tasks';
import { getLoggedInUser } from '$features/auth/services/session_guards';
import * as workBooksCrud from '$features/workbooks/services/workbooks';

import { canEdit, isAdmin } from '$lib/utils/authorship';
import { getLoggedInUser } from '$features/auth/services/session';
import { parseWorkBookId, parseWorkBookUrlSlug } from '$features/workbooks/utils/workbook';

import {
Expand Down
Loading