Skip to content
Open
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
11 changes: 7 additions & 4 deletions .claude/rules/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ paths:

# Authentication

## Lucia v2
## Self-Managed Session Auth

- Session validation in `src/hooks.server.ts`
- Session data attached to `event.locals.user`
Auth is a self-managed implementation (no external auth library), kept compatible with the retired lucia v2 so existing sessions and password hashes stay valid. See `## Key Files` for the module map.

- Session validation in `src/hooks.server.ts`; session data attached to `event.locals.user`
- User properties: `id`, `name`, `role`, `atcoder_name`, `is_validated`

## Protected Routes
Expand All @@ -30,7 +31,9 @@ paths:

## Key Files

- `src/lib/server/auth.ts`: Lucia configuration
- `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`:
- `getLoggedInUser(locals, url?)` — returns logged-in user or redirects to `/login`
Expand Down
9 changes: 9 additions & 0 deletions .claude/rules/coding-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ Delete function only if: (1) zero callers, (2) replacement exists, (3) dependent

Before removing an import, grep the entire file for all usages — removing one call site doesn't mean no others exist.

## Residual-Reference Sweeps

When removing a dependency or renaming a symbol, sweep the **whole repo**, not just `src`:

- `grep --include='*.{ts,svelte,md}'` does NOT brace-expand — it silently matches nothing.
Use `rg -ni 'name' -g '*.ts' -g '*.svelte' -g '*.md'` instead.
- Include root files (CONTRIBUTING.md, README), `e2e/`, and config (`vite.config.ts`) —
not only `src prisma .claude`.

## Documentation

- **Plans/dev-notes**: Japanese
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
- [Vitest](https://vitest.dev/): 単体テスト (ユーティリティ、コンポーネント)
- [Playwright](https://playwright.dev/): e2eテスト
- [Nock](https://github.com/nock/nock): API 統合テスト用の HTTP モック
- 認証ライブラリ
- [Lucia](https://lucia-auth.com/)
- 認証
- 自前実装(セッション管理・パスワードハッシュ)。旧 [Lucia](https://lucia-auth.com/) v2 のセッション / パスワード形式に準拠(互換性維持のため、cookie 名・ハッシュ形式は安易に変更しないこと)
- ORM
- [Prisma](https://www.prisma.io/)
- バリデーション
Expand Down
6 changes: 3 additions & 3 deletions e2e/problems_cache.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ test.describe('anonymous /problems response', () => {
expect(headers['cache-control']).toContain('stale-while-revalidate=600');

// set-cookie makes a response ineligible for CDN caching.
// Lucia must not attach a session cookie to anonymous requests.
// If this assertion fails after a Lucia upgrade, verify it does not
// set cookies on unauthenticated requests.
// The auth layer must not attach a session cookie to anonymous requests.
// If this assertion fails, verify the session handling does not set cookies
// on unauthenticated requests.
expect(headers['set-cookie']).toBeUndefined();
});
});
Expand Down
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,10 @@
"dependencies": {
"@dnd-kit/helpers": "0.5.0",
"@dnd-kit/svelte": "0.5.0",
"@lucia-auth/adapter-prisma": "3.0.2",
"@lucide/svelte": "1.24.0",
"@prisma/client": "5.22.0",
"@types/node": "25.9.5",
"debug": "4.4.3",
"lucia": "2.7.7",
"p-queue": "9.3.1",
"playwright": "1.61.1",
"svelte-eslint-parser": "1.8.0",
Expand Down
24 changes: 0 additions & 24 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 3 additions & 7 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,9 @@ datasource db {
directUrl = env("DIRECT_URL")
}

// Note: Lucia v3 + v3 Prisma adaperでは、全てのフィールドがキャメルケースであることが要求されている。
// 段階的に移行を進めるため、modelを追加・修正する場合は、キャメルケースで記述する。
// See:
// https://lucia-auth.com/upgrade-v3/prisma/postgresql

// See:
// https://lucia-auth.com/database-adapters/prisma/
// Note: write new models and fields in camelCase.
// The snake_case fields on User / Session / Key are legacy (from the old lucia adapter);
// kept as-is to avoid a DB migration.
model User {
id String @id @unique
// here you can add custom fields for your user
Expand Down
11 changes: 4 additions & 7 deletions 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 { generateLuciaPasswordHash } from 'lucia/utils';
import { hashPassword } from '../src/lib/server/password';

import { getTaskGrade } from '../src/lib/types/task';
import type { PlacementCreate } from '../src/features/workbooks/types/workbook_placement';
Expand Down Expand Up @@ -62,7 +62,6 @@ const QUEUE_CONCURRENCY = {

// See:
// https://github.com/TeemuKoivisto/sveltekit-monorepo-template/blob/main/packages/db/prisma/seed.ts
// https://lucia-auth.com/basics/keys/#password-hashing
// https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#findunique
// https://github.com/sindresorhus/p-queue
async function main() {
Expand Down Expand Up @@ -119,8 +118,6 @@ async function addUsers() {
console.log('Finished adding users.');
}

// See:
// https://lucia-auth.com/reference/lucia/modules/utils/#generateluciapasswordhash
async function addUser(
user: (typeof users)[number],
password: string,
Expand All @@ -132,12 +129,12 @@ async function addUser(
username: user.name,
role: user.role,
});
const hashed_password = await generateLuciaPasswordHash(password);
const hashedPassword = await hashPassword(password);

await keyFactory.create({
user: { connect: currentUser },
id: 'username:' + user.name.toLocaleLowerCase(),
hashed_password: hashed_password,
id: 'username:' + user.name.toLowerCase(),
hashed_password: hashedPassword,
});
}

Expand Down
16 changes: 1 addition & 15 deletions 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('lucia').AuthRequest;
auth: import('$lib/server/auth').AuthRequest;
user: {
id: string;
name: string;
Expand All @@ -21,19 +21,5 @@ declare global {
}
}

// See:
// https://lucia-auth.com/getting-started/sveltekit/
/// <reference types="lucia" />
declare global {
namespace Lucia {
type Auth = import('$lib/server/auth').Auth;
type UserAttributes = {
username: string;
role: Roles;
};
// type SessionAttributes = {};
}
}

// THIS IS IMPORTANT!!!
export {};
3 changes: 2 additions & 1 deletion src/features/account/services/atcoder_verification.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ type UserWithAtCoderAccount = Awaited<ReturnType<typeof prisma.user.findUniqueOr
// ---------------------------------------------------------------------------

const SAMPLE_TIMESTAMP = new Date('2024-01-01T00:00:00Z');
// Lucia (auth library) auto-generates User.id in UUID format
// The auth service generates User.id as a 15-char [a-z0-9] random string; this fixture predates
// that and uses a UUID, which is fine here since these tests never assert the id format.
const SAMPLE_USER_ID = '550e8400-e29b-41d4-a716-446655440000';
const SAMPLE_USERNAME = 'alice';
const SAMPLE_HANDLE = 'alice_ac';
Expand Down
Loading
Loading