spider-weave

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Spider Weave 🕷️

蜘蛛织网 🕷️

The spider doesn't rush. It spins one thread at a time, anchoring each carefully before moving to the next. The web grows organically—radial strands first, then the spiral, each connection tested for strength. When complete, the web catches what matters while letting the wind pass through. Authentication woven this way is strong, resilient, and beautiful in its structure.
蜘蛛从不急躁。它一次纺制一根丝线,在移动到下一根之前仔细固定每一个锚点。网络有机生长——先放射状丝线,再螺旋状丝线,每一处连接都经过强度测试。完成后的蛛网能捕获重要的事物,同时让风穿过。以这种方式编织的认证系统坚固、有韧性,结构优美。

When to Activate

激活场景

  • User asks to "add auth" or "set up authentication"
  • User says "protect this route" or "add login"
  • User calls
    /spider-weave
    or mentions spider/auth
  • Integrating OAuth (Google, GitHub, etc.)
  • Setting up session management
  • Protecting API routes
  • Adding role-based access control (RBAC)
  • Implementing PKCE flow
  • Connecting to Heartwood (GroveAuth)
Pair with:
raccoon-audit
for security review,
beaver-build
for auth testing

  • 用户要求“添加认证”或“搭建认证系统”
  • 用户提到“保护该路由”或“添加登录功能”
  • 用户调用
    /spider-weave
    或提及spider/auth
  • 集成OAuth(Google、GitHub等)
  • 搭建会话管理
  • 保护API路由
  • 添加基于角色的访问控制(RBAC)
  • 实现PKCE流程
  • 连接Heartwood(GroveAuth)
搭配使用:
raccoon-audit
用于安全审查,
beaver-build
用于认证测试

The Weave

织网流程

SPIN → CONNECT → SECURE → TEST → BIND
  ↓       ↓        ↓        ↓        ↓
Create  Link    Harden   Verify   Lock In
Threads Strands  Knots    Web     Security
SPIN → CONNECT → SECURE → TEST → BIND
  ↓       ↓        ↓        ↓        ↓
Create  Link    Harden   Verify   Lock In
Threads Strands  Knots    Web     Security

Phase 1: SPIN

阶段1:SPIN(纺制)

The spider spins the first thread, anchoring it carefully...
Create the foundational auth structure:
Choose the Auth Pattern:
PatternBest ForComplexity
Session-basedTraditional web appsMedium
JWTStateless APIs, SPAsMedium
OAuth 2.0Third-party loginHigh
PKCEMobile/SPA OAuthHigh
API KeysService-to-serviceLow
For Grove/Heartwood Integration:
typescript
// PKCE flow setup
import { generatePKCE } from '$lib/auth/pkce';

const { codeVerifier, codeChallenge } = await generatePKCE();

// Store verifier (cookie or session)
cookies.set('pkce_verifier', codeVerifier, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  maxAge: 600 // 10 minutes
});

// Redirect to Heartwood
const authUrl = new URL('https://heartwood.grove.place/oauth/authorize');
authUrl.searchParams.set('client_id', CLIENT_ID);
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
authUrl.searchParams.set('state', generateState());
Core Auth Files Structure:
src/lib/auth/
├── index.ts           # Main exports
├── types.ts           # Auth-related types
├── session.ts         # Session management
├── middleware.ts      # Route protection
├── pkce.ts           # PKCE utilities
└── client.ts         # Heartwood/OAuth client
Database Schema:
typescript
// Users table (linked to Heartwood)
export const users = sqliteTable('users', {
  id: integer('id').primaryKey(),
  heartwoodId: text('heartwood_id').unique(),
  email: text('email').unique(),
  displayName: text('display_name'),
  avatarUrl: text('avatar_url'),
  role: text('role').default('user'), // admin, user, guest
  createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
});

// Sessions (if using session-based auth)
export const sessions = sqliteTable('sessions', {
  id: text('id').primaryKey(),
  userId: integer('user_id').references(() => users.id),
  expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
});
Environment Variables:
bash
undefined
蜘蛛纺制第一根丝线,仔细固定锚点...
创建基础认证结构:
选择认证模式:
模式最佳适用场景复杂度
基于会话传统Web应用中等
JWT无状态API、单页应用(SPA)中等
OAuth 2.0第三方登录
PKCE移动应用/SPA的OAuth
API密钥服务间通信
Grove/Heartwood集成示例:
typescript
// PKCE flow setup
import { generatePKCE } from '$lib/auth/pkce';

const { codeVerifier, codeChallenge } = await generatePKCE();

// Store verifier (cookie or session)
cookies.set('pkce_verifier', codeVerifier, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  maxAge: 600 // 10 minutes
});

// Redirect to Heartwood
const authUrl = new URL('https://heartwood.grove.place/oauth/authorize');
authUrl.searchParams.set('client_id', CLIENT_ID);
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
authUrl.searchParams.set('state', generateState());
核心认证文件结构:
src/lib/auth/
├── index.ts           # 主导出文件
├── types.ts           # 认证相关类型定义
├── session.ts         # 会话管理
├── middleware.ts      # 路由保护中间件
├── pkce.ts           # PKCE工具类
└── client.ts         # Heartwood/OAuth客户端
数据库Schema:
typescript
// Users table (linked to Heartwood)
export const users = sqliteTable('users', {
  id: integer('id').primaryKey(),
  heartwoodId: text('heartwood_id').unique(),
  email: text('email').unique(),
  displayName: text('display_name'),
  avatarUrl: text('avatar_url'),
  role: text('role').default('user'), // admin, user, guest
  createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
});

// Sessions (if using session-based auth)
export const sessions = sqliteTable('sessions', {
  id: text('id').primaryKey(),
  userId: integer('user_id').references(() => users.id),
  expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
});
环境变量:
bash
undefined

OAuth/Heartwood

OAuth/Heartwood

HEARTWOOD_CLIENT_ID= HEARTWOOD_CLIENT_SECRET= HEARTWOOD_AUTHORIZE_URL=https://heartwood.grove.place/oauth/authorize HEARTWOOD_TOKEN_URL=https://heartwood.grove.place/oauth/token HEARTWOOD_USERINFO_URL=https://heartwood.grove.place/oauth/userinfo
HEARTWOOD_CLIENT_ID= HEARTWOOD_CLIENT_SECRET= HEARTWOOD_AUTHORIZE_URL=https://heartwood.grove.place/oauth/authorize HEARTWOOD_TOKEN_URL=https://heartwood.grove.place/oauth/token HEARTWOOD_USERINFO_URL=https://heartwood.grove.place/oauth/userinfo

App

App

AUTH_REDIRECT_URI=http://localhost:5173/auth/callback SESSION_SECRET=generate_with_openssl_rand_hex_32

**Output:** Auth infrastructure created, dependencies installed, schema defined

---
AUTH_REDIRECT_URI=http://localhost:5173/auth/callback SESSION_SECRET=generate_with_openssl_rand_hex_32

**输出结果:** 认证基础设施创建完成、依赖安装完毕、Schema定义完成

---

Phase 2: CONNECT

阶段2:CONNECT(连接)

Thread connects to thread, the web taking shape...
Link the auth system together:
OAuth Flow Implementation:
typescript
// 1. Login route - redirect to provider
// src/routes/auth/login/+server.ts
export const GET: RequestHandler = async () => {
  const { codeVerifier, codeChallenge } = generatePKCE();
  const state = generateState();
  
  // Store PKCE verifier
  cookies.set('pkce_verifier', codeVerifier, { httpOnly: true, secure: true });
  cookies.set('oauth_state', state, { httpOnly: true, secure: true });
  
  const url = new URL(HEARTWOOD_AUTHORIZE_URL);
  url.searchParams.set('client_id', HEARTWOOD_CLIENT_ID);
  url.searchParams.set('code_challenge', codeChallenge);
  url.searchParams.set('code_challenge_method', 'S256');
  url.searchParams.set('redirect_uri', AUTH_REDIRECT_URI);
  url.searchParams.set('state', state);
  
  throw redirect(302, url.toString());
};

// 2. Callback route - handle OAuth response
// src/routes/auth/callback/+server.ts
export const GET: RequestHandler = async ({ url, cookies }) => {
  const code = url.searchParams.get('code');
  const state = url.searchParams.get('state');
  const storedState = cookies.get('oauth_state');
  
  // Verify state (CSRF protection)
  if (state !== storedState) {
    throw error(400, 'Invalid state parameter');
  }
  
  // Exchange code for tokens
  const tokenResponse = await fetch(HEARTWOOD_TOKEN_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      client_id: HEARTWOOD_CLIENT_ID,
      client_secret: HEARTWOOD_CLIENT_SECRET,
      code: code!,
      code_verifier: cookies.get('pkce_verifier')!,
      redirect_uri: AUTH_REDIRECT_URI,
    }),
  });
  
  const tokens = await tokenResponse.json();
  
  // Get user info
  const userResponse = await fetch(HEARTWOOD_USERINFO_URL, {
    headers: { Authorization: `Bearer ${tokens.access_token}` },
  });
  
  const userInfo = await userResponse.json();
  
  // Create/update user in database
  const user = await upsertUser({
    heartwoodId: userInfo.sub,
    email: userInfo.email,
    displayName: userInfo.name,
    avatarUrl: userInfo.picture,
  });
  
  // Create session
  const session = await createSession(user.id);
  
  // Set session cookie
  cookies.set('session', session.id, {
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
    maxAge: 60 * 60 * 24 * 7, // 7 days
  });
  
  // Clean up PKCE cookies
  cookies.delete('pkce_verifier');
  cookies.delete('oauth_state');
  
  throw redirect(302, '/dashboard');
};
Session Management:
typescript
// src/lib/auth/session.ts
export async function createSession(userId: number): Promise<Session> {
  const sessionId = generateSecureId();
  const expiresAt = new Date(Date.now() + SESSION_DURATION);
  
  await db.insert(sessions).values({
    id: sessionId,
    userId,
    expiresAt,
  });
  
  return { id: sessionId, userId, expiresAt };
}

export async function validateSession(sessionId: string): Promise<User | null> {
  const session = await db.query.sessions.findFirst({
    where: eq(sessions.id, sessionId),
    with: { user: true },
  });
  
  if (!session || session.expiresAt < new Date()) {
    return null;
  }
  
  return session.user;
}

export async function invalidateSession(sessionId: string): Promise<void> {
  await db.delete(sessions).where(eq(sessions.id, sessionId));
}
Auth Store (Client-side):
typescript
// src/lib/stores/auth.ts
import { writable } from 'svelte/store';

export interface AuthState {
  user: User | null;
  loading: boolean;
}

export const auth = writable<AuthState>({
  user: null,
  loading: true,
});

export async function loadUser() {
  const response = await fetch('/api/auth/me');
  if (response.ok) {
    const user = await response.json();
    auth.set({ user, loading: false });
  } else {
    auth.set({ user: null, loading: false });
  }
}
Output: OAuth flow connected, session management working, client state ready

丝线与丝线相连,网络逐渐成型...
将认证系统各部分连接起来:
OAuth流程实现:
typescript
// 1. 登录路由 - 重定向到认证提供商
// src/routes/auth/login/+server.ts
export const GET: RequestHandler = async () => {
  const { codeVerifier, codeChallenge } = generatePKCE();
  const state = generateState();
  
  // 存储PKCE验证器
  cookies.set('pkce_verifier', codeVerifier, { httpOnly: true, secure: true });
  cookies.set('oauth_state', state, { httpOnly: true, secure: true });
  
  const url = new URL(HEARTWOOD_AUTHORIZE_URL);
  url.searchParams.set('client_id', HEARTWOOD_CLIENT_ID);
  url.searchParams.set('code_challenge', codeChallenge);
  url.searchParams.set('code_challenge_method', 'S256');
  url.searchParams.set('redirect_uri', AUTH_REDIRECT_URI);
  url.searchParams.set('state', state);
  
  throw redirect(302, url.toString());
};

// 2. 回调路由 - 处理OAuth响应
// src/routes/auth/callback/+server.ts
export const GET: RequestHandler = async ({ url, cookies }) => {
  const code = url.searchParams.get('code');
  const state = url.searchParams.get('state');
  const storedState = cookies.get('oauth_state');
  
  // 验证state(CSRF防护)
  if (state !== storedState) {
    throw error(400, 'Invalid state parameter');
  }
  
  // 用授权码交换令牌
  const tokenResponse = await fetch(HEARTWOOD_TOKEN_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      client_id: HEARTWOOD_CLIENT_ID,
      client_secret: HEARTWOOD_CLIENT_SECRET,
      code: code!,
      code_verifier: cookies.get('pkce_verifier')!,
      redirect_uri: AUTH_REDIRECT_URI,
    }),
  });
  
  const tokens = await tokenResponse.json();
  
  // 获取用户信息
  const userResponse = await fetch(HEARTWOOD_USERINFO_URL, {
    headers: { Authorization: `Bearer ${tokens.access_token}` },
  });
  
  const userInfo = await userResponse.json();
  
  // 在数据库中创建/更新用户
  const user = await upsertUser({
    heartwoodId: userInfo.sub,
    email: userInfo.email,
    displayName: userInfo.name,
    avatarUrl: userInfo.picture,
  });
  
  // 创建会话
  const session = await createSession(user.id);
  
  // 设置会话Cookie
  cookies.set('session', session.id, {
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
    maxAge: 60 * 60 * 24 * 7, // 7天
  });
  
  // 清理PKCE相关Cookie
  cookies.delete('pkce_verifier');
  cookies.delete('oauth_state');
  
  throw redirect(302, '/dashboard');
};
会话管理:
typescript
// src/lib/auth/session.ts
export async function createSession(userId: number): Promise<Session> {
  const sessionId = generateSecureId();
  const expiresAt = new Date(Date.now() + SESSION_DURATION);
  
  await db.insert(sessions).values({
    id: sessionId,
    userId,
    expiresAt,
  });
  
  return { id: sessionId, userId, expiresAt };
}

export async function validateSession(sessionId: string): Promise<User | null> {
  const session = await db.query.sessions.findFirst({
    where: eq(sessions.id, sessionId),
    with: { user: true },
  });
  
  if (!session || session.expiresAt < new Date()) {
    return null;
  }
  
  return session.user;
}

export async function invalidateSession(sessionId: string): Promise<void> {
  await db.delete(sessions).where(eq(sessions.id, sessionId));
}
客户端认证Store:
typescript
// src/lib/stores/auth.ts
import { writable } from 'svelte/store';

export interface AuthState {
  user: User | null;
  loading: boolean;
}

export const auth = writable<AuthState>({
  user: null,
  loading: true,
});

export async function loadUser() {
  const response = await fetch('/api/auth/me');
  if (response.ok) {
    const user = await response.json();
    auth.set({ user, loading: false });
  } else {
    auth.set({ user: null, loading: false });
  }
}
输出结果: OAuth流程连接完成、会话管理正常工作、客户端状态就绪

Phase 3: SECURE

阶段3:SECURE(加固)

The spider tests each knot, ensuring the web holds...
Harden the authentication system:
Route Protection:
typescript
// src/lib/auth/middleware.ts
export function requireAuth(): Handle {
  return async ({ event, resolve }) => {
    const sessionId = event.cookies.get('session');
    
    if (!sessionId) {
      throw redirect(302, '/auth/login');
    }
    
    const user = await validateSession(sessionId);
    
    if (!user) {
      event.cookies.delete('session');
      throw redirect(302, '/auth/login');
    }
    
    event.locals.user = user;
    return resolve(event);
  };
}

// Role-based protection
export function requireRole(allowedRoles: string[]): Handle {
  return async ({ event, resolve }) => {
    const user = event.locals.user;
    
    if (!user || !allowedRoles.includes(user.role)) {
      throw error(403, 'Forbidden');
    }
    
    return resolve(event);
  };
}
Security Headers:
typescript
// src/hooks.server.ts
export const handle: Handle = async ({ event, resolve }) => {
  const response = await resolve(event);
  
  // Security headers
  response.headers.set('X-Frame-Options', 'DENY');
  response.headers.set('X-Content-Type-Options', 'nosniff');
  response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
  response.headers.set(
    'Content-Security-Policy',
    "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
  );
  
  return response;
};
CSRF Protection:
typescript
// For state-changing operations
export function validateCSRF(event: RequestEvent): void {
  const origin = event.request.headers.get('origin');
  const host = event.url.host;
  
  if (origin && new URL(origin).host !== host) {
    throw error(403, 'Invalid origin');
  }
}
Rate Limiting:
typescript
// src/lib/auth/rate-limit.ts
const attempts = new Map<string, number[]>();

export function checkRateLimit(identifier: string, maxAttempts: number = 5): boolean {
  const now = Date.now();
  const windowStart = now - 15 * 60 * 1000; // 15 minutes
  
  const userAttempts = attempts.get(identifier) || [];
  const recentAttempts = userAttempts.filter(t => t > windowStart);
  
  if (recentAttempts.length >= maxAttempts) {
    return false;
  }
  
  recentAttempts.push(now);
  attempts.set(identifier, recentAttempts);
  return true;
}
Secure Cookie Settings:
typescript
// Always use these for auth cookies
{
  httpOnly: true,    // Not accessible via JavaScript
  secure: true,      // HTTPS only in production
  sameSite: 'lax',   // CSRF protection
  maxAge: 604800,    // 7 days
  path: '/',         // Available site-wide
}
Output: Routes protected, headers set, rate limiting active, security hardened

蜘蛛测试每个节点,确保蛛网牢固...
加固认证系统:
路由保护:
typescript
// src/lib/auth/middleware.ts
export function requireAuth(): Handle {
  return async ({ event, resolve }) => {
    const sessionId = event.cookies.get('session');
    
    if (!sessionId) {
      throw redirect(302, '/auth/login');
    }
    
    const user = await validateSession(sessionId);
    
    if (!user) {
      event.cookies.delete('session');
      throw redirect(302, '/auth/login');
    }
    
    event.locals.user = user;
    return resolve(event);
  };
}

// 基于角色的保护
export function requireRole(allowedRoles: string[]): Handle {
  return async ({ event, resolve }) => {
    const user = event.locals.user;
    
    if (!user || !allowedRoles.includes(user.role)) {
      throw error(403, 'Forbidden');
    }
    
    return resolve(event);
  };
}
安全Headers:
typescript
// src/hooks.server.ts
export const handle: Handle = async ({ event, resolve }) => {
  const response = await resolve(event);
  
  // 安全Headers
  response.headers.set('X-Frame-Options', 'DENY');
  response.headers.set('X-Content-Type-Options', 'nosniff');
  response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
  response.headers.set(
    'Content-Security-Policy',
    "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
  );
  
  return response;
};
CSRF防护:
typescript
// 针对状态变更操作
export function validateCSRF(event: RequestEvent): void {
  const origin = event.request.headers.get('origin');
  const host = event.url.host;
  
  if (origin && new URL(origin).host !== host) {
    throw error(403, 'Invalid origin');
  }
}
速率限制:
typescript
// src/lib/auth/rate-limit.ts
const attempts = new Map<string, number[]>();

export function checkRateLimit(identifier: string, maxAttempts: number = 5): boolean {
  const now = Date.now();
  const windowStart = now - 15 * 60 * 1000; // 15分钟
  
  const userAttempts = attempts.get(identifier) || [];
  const recentAttempts = userAttempts.filter(t => t > windowStart);
  
  if (recentAttempts.length >= maxAttempts) {
    return false;
  }
  
  recentAttempts.push(now);
  attempts.set(identifier, recentAttempts);
  return true;
}
安全Cookie设置:
typescript
// 认证Cookie始终使用以下配置
{
  httpOnly: true,    // 无法通过JavaScript访问
  secure: true,      // 生产环境仅通过HTTPS传输
  sameSite: 'lax',   // CSRF防护
  maxAge: 604800,    // 7天
  path: '/',         // 全站可用
}
输出结果: 路由已保护、Headers已设置、速率限制已激活、安全加固完成

Phase 4: TEST

阶段4:TEST(测试)

The spider plucks the strands, verifying the web vibrates true...
Test authentication thoroughly:
Test Coverage:
typescript
// tests/auth/oauth.test.ts
describe('OAuth Flow', () => {
  test('redirects to Heartwood with PKCE', async () => {
    const response = await request(app).get('/auth/login');
    
    expect(response.status).toBe(302);
    expect(response.headers.location).toMatch(/heartwood\.grove\.place/);
    expect(response.headers.location).toMatch(/code_challenge=/);
  });
  
  test('handles callback and creates session', async () => {
    // Mock Heartwood responses
    mockHeartwoodTokenEndpoint({ access_token: 'test-token' });
    mockHeartwoodUserInfo({ sub: '123', email: 'test@example.com' });
    
    const response = await request(app)
      .get('/auth/callback?code=valid-code&state=valid-state')
      .set('Cookie', ['oauth_state=valid-state; pkce_verifier=test-verifier']);
    
    expect(response.status).toBe(302);
    expect(response.headers.location).toBe('/dashboard');
    
    // Verify session created
    const cookies = response.headers['set-cookie'];
    expect(cookies).toMatch(/session=/);
  });
  
  test('rejects invalid state (CSRF protection)', async () => {
    const response = await request(app)
      .get('/auth/callback?code=valid-code&state=wrong-state')
      .set('Cookie', ['oauth_state=correct-state']);
    
    expect(response.status).toBe(400);
  });
});

describe('Route Protection', () => {
  test('redirects unauthenticated users', async () => {
    const response = await request(app).get('/dashboard');
    expect(response.status).toBe(302);
    expect(response.headers.location).toBe('/auth/login');
  });
  
  test('allows authenticated users', async () => {
    const session = await createTestUserAndSession();
    
    const response = await request(app)
      .get('/dashboard')
      .set('Cookie', [`session=${session.id}`]);
    
    expect(response.status).toBe(200);
  });
  
  test('enforces role restrictions', async () => {
    const user = await createTestUser({ role: 'user' });
    const session = await createSession(user.id);
    
    const response = await request(app)
      .get('/admin')
      .set('Cookie', [`session=${session.id}`]);
    
    expect(response.status).toBe(403);
  });
});
Security Testing:
typescript
// Test session fixation
test('session ID changes after login', async () => {
  const oldSession = cookies.get('session');
  await completeLoginFlow();
  const newSession = cookies.get('session');
  expect(newSession).not.toBe(oldSession);
});

// Test cookie security
test('auth cookies have secure attributes', async () => {
  const response = await completeLoginFlow();
  const cookies = response.headers['set-cookie'];
  
  expect(cookies).toMatch(/HttpOnly/);
  expect(cookies).toMatch(/SameSite=/);
});
Output: All auth flows tested, security verified, edge cases covered

蜘蛛拨动丝线,验证蛛网振动正常...
全面测试认证系统:
测试覆盖:
typescript
// tests/auth/oauth.test.ts
describe('OAuth Flow', () => {
  test('redirects to Heartwood with PKCE', async () => {
    const response = await request(app).get('/auth/login');
    
    expect(response.status).toBe(302);
    expect(response.headers.location).toMatch(/heartwood\.grove\.place/);
    expect(response.headers.location).toMatch(/code_challenge=/);
  });
  
  test('handles callback and creates session', async () => {
    // 模拟Heartwood响应
    mockHeartwoodTokenEndpoint({ access_token: 'test-token' });
    mockHeartwoodUserInfo({ sub: '123', email: 'test@example.com' });
    
    const response = await request(app)
      .get('/auth/callback?code=valid-code&state=valid-state')
      .set('Cookie', ['oauth_state=valid-state; pkce_verifier=test-verifier']);
    
    expect(response.status).toBe(302);
    expect(response.headers.location).toBe('/dashboard');
    
    // 验证会话已创建
    const cookies = response.headers['set-cookie'];
    expect(cookies).toMatch(/session=/);
  });
  
  test('rejects invalid state (CSRF protection)', async () => {
    const response = await request(app)
      .get('/auth/callback?code=valid-code&state=wrong-state')
      .set('Cookie', ['oauth_state=correct-state']);
    
    expect(response.status).toBe(400);
  });
});

describe('Route Protection', () => {
  test('redirects unauthenticated users', async () => {
    const response = await request(app).get('/dashboard');
    expect(response.status).toBe(302);
    expect(response.headers.location).toBe('/auth/login');
  });
  
  test('allows authenticated users', async () => {
    const session = await createTestUserAndSession();
    
    const response = await request(app)
      .get('/dashboard')
      .set('Cookie', [`session=${session.id}`]);
    
    expect(response.status).toBe(200);
  });
  
  test('enforces role restrictions', async () => {
    const user = await createTestUser({ role: 'user' });
    const session = await createSession(user.id);
    
    const response = await request(app)
      .get('/admin')
      .set('Cookie', [`session=${session.id}`]);
    
    expect(response.status).toBe(403);
  });
});
安全测试:
typescript
// 测试会话固定攻击
test('session ID changes after login', async () => {
  const oldSession = cookies.get('session');
  await completeLoginFlow();
  const newSession = cookies.get('session');
  expect(newSession).not.toBe(oldSession);
});

// 测试Cookie安全性
test('auth cookies have secure attributes', async () => {
  const response = await completeLoginFlow();
  const cookies = response.headers['set-cookie'];
  
  expect(cookies).toMatch(/HttpOnly/);
  expect(cookies).toMatch(/SameSite=/);
});
输出结果: 所有认证流程测试完成、安全验证通过、边缘场景覆盖

Phase 5: BIND

阶段5:BIND(绑定)

The web is complete, each strand bound tight, ready to catch what comes...
Finalize and lock in the authentication:
Integration Checklist:
  • Login flow works end-to-end
  • Logout clears session
  • Protected routes redirect unauthenticated users
  • Session expires correctly
  • Token refresh works (if applicable)
  • Error messages don't leak sensitive info
  • Rate limiting prevents brute force
  • CSRF protection active
  • Security headers set
  • Cookies configured securely
User Experience Polish:
svelte
<!-- Login button states -->
<script>
  let loading = $state(false);
  let error = $state('');
</script>

{#if error}
  <div role="alert" class="error">
    {error}
  </div>
{/if}

<button 
  on:click={handleLogin} 
  disabled={loading}
  aria-busy={loading}
>
  {#if loading}
    <span class="spinner" aria-hidden="true" />
    Connecting...
  {:else}
    Sign in with Heartwood
  {/if}
</button>
Monitoring & Logging:
typescript
// Log auth events (without sensitive data)
logger.info('User authenticated', {
  userId: user.id,
  provider: 'heartwood',
  ip: event.getClientAddress(),
});

// Alert on suspicious activity
if (failedAttempts > 10) {
  logger.warn('Potential brute force attack', {
    identifier,
    attempts: failedAttempts,
  });
}
Documentation:
markdown
undefined
蛛网已完成,每根丝线都紧密绑定,准备好迎接一切...
最终完成并锁定认证系统:
集成检查清单:
  • 登录流程端到端正常工作
  • 登出操作会清除会话
  • 受保护路由会重定向未认证用户
  • 会话会正确过期
  • 令牌刷新功能正常(如适用)
  • 错误信息不会泄露敏感信息
  • 速率限制可防止暴力破解
  • CSRF防护已激活
  • 安全Headers已设置
  • Cookie配置安全
用户体验优化:
svelte
<!-- 登录按钮状态 -->
<script>
  let loading = $state(false);
  let error = $state('');
</script>

{#if error}
  <div role="alert" class="error">
    {error}
  </div>
{/if}

<button 
  on:click={handleLogin} 
  disabled={loading}
  aria-busy={loading}
>
  {#if loading}
    <span class="spinner" aria-hidden="true" />
    连接中...
  {:else}
    使用Heartwood登录
  {/if}
</button>
监控与日志:
typescript
// 记录认证事件(不包含敏感数据)
logger.info('User authenticated', {
  userId: user.id,
  provider: 'heartwood',
  ip: event.getClientAddress(),
});

// 对可疑活动发出警报
if (failedAttempts > 10) {
  logger.warn('Potential brute force attack', {
    identifier,
    attempts: failedAttempts,
  });
}
文档:
markdown
undefined

Authentication System

认证系统

Architecture

架构

  • OAuth 2.0 with PKCE for secure token exchange
  • Session-based auth for web app
  • Heartwood (GroveAuth) as identity provider
  • 采用OAuth 2.0 + PKCE实现安全令牌交换
  • Web应用使用基于会话的认证
  • 身份提供商为Heartwood(GroveAuth)

Flow

流程

  1. User clicks "Sign in" → Redirect to Heartwood
  2. User authenticates with Heartwood
  3. Heartwood redirects back with auth code
  4. App exchanges code for tokens
  5. App creates session, sets cookie
  6. User is authenticated
  1. 用户点击“登录” → 重定向到Heartwood
  2. 用户在Heartwood完成认证
  3. Heartwood重定向回应用并携带授权码
  4. 应用用授权码交换令牌
  5. 应用创建会话并设置Cookie
  6. 用户完成认证

Protected Routes

受保护路由

Add to
src/routes/protected/+page.server.ts
:
typescript
export const load = async ({ locals }) => {
  if (!locals.user) {
    throw redirect(302, '/auth/login');
  }
  return { user: locals.user };
};
src/routes/protected/+page.server.ts
中添加:
typescript
export const load = async ({ locals }) => {
  if (!locals.user) {
    throw redirect(302, '/auth/login');
  }
  return { user: locals.user };
};

Environment Variables

环境变量

See
.env.example
for required variables.

**Completion Report:**

```markdown
参考
.env.example
获取所需变量。

**完成报告:**

```markdown

🕷️ SPIDER WEAVE COMPLETE

🕷️ 蜘蛛织网完成

Auth System Integrated

认证系统集成情况

  • Provider: Heartwood (GroveAuth)
  • Flow: OAuth 2.0 + PKCE
  • Session: Cookie-based, 7-day expiry
  • 提供商:Heartwood(GroveAuth)
  • 流程:OAuth 2.0 + PKCE
  • 会话:基于Cookie,7天有效期

Files Created

创建的文件

  • src/lib/auth/
    (6 files)
  • src/routes/auth/login/+server.ts
  • src/routes/auth/callback/+server.ts
  • src/routes/auth/logout/+server.ts
  • src/lib/stores/auth.ts
  • src/lib/auth/
    (6个文件)
  • src/routes/auth/login/+server.ts
  • src/routes/auth/callback/+server.ts
  • src/routes/auth/logout/+server.ts
  • src/lib/stores/auth.ts

Security Features

安全特性

  • ✅ PKCE for OAuth
  • ✅ CSRF protection
  • ✅ Rate limiting (5 attempts / 15 min)
  • ✅ Secure cookie attributes
  • ✅ Security headers
  • ✅ Role-based access control
  • ✅ OAuth采用PKCE
  • ✅ CSRF防护
  • ✅ 速率限制(15分钟内最多5次尝试)
  • ✅ Cookie安全配置
  • ✅ 安全Headers
  • ✅ 基于角色的访问控制

Tests

测试情况

  • 15 unit tests
  • 8 integration tests
  • 100% pass rate
The web is woven. The system is secure. 🕷️

**Output:** Auth system complete, tested, documented, monitoring in place

---
  • 15个单元测试
  • 8个集成测试
  • 100%通过率
蛛网已织就,系统安全就绪。 🕷️

**输出结果:** 认证系统完成、测试通过、文档齐全、监控已部署

---

Spider Rules

蜘蛛准则

Patience

耐心

Weave one thread at a time. Don't rush to connect everything at once. Each strand must be secure before adding the next.
一次纺制一根丝线。不要急于连接所有部分。每根丝线都必须先固定牢固,再添加下一根。

Precision

精准

Small mistakes in auth have big consequences. Verify every redirect, check every token, validate every session.
认证中的小错误会导致严重后果。验证每一次重定向、检查每一个令牌、确认每一个会话。

Completeness

完整

A web with holes catches nothing. Test the error paths, the edge cases, the failure modes. Security is only as strong as the weakest strand.
有漏洞的蛛网什么也捕获不到。测试错误路径、边缘场景、失败模式。安全强度取决于最薄弱的环节。

Communication

沟通

Use weaving metaphors:
  • "Spinning the threads..." (creating foundations)
  • "Connecting the strands..." (linking components)
  • "Testing the knots..." (security hardening)
  • "The web holds..." (verification complete)

使用织网相关的隐喻:
  • “纺制丝线中...”(创建基础结构)
  • “连接链路中...”(关联组件)
  • “测试节点中...”(安全加固)
  • “蛛网牢固...”(验证完成)

Anti-Patterns

反模式

The spider does NOT:
  • Store passwords in plain text (ever)
  • Skip PKCE in OAuth flows
  • Trust user input without validation
  • Leave default secrets in configuration
  • Ignore session expiration
  • Log sensitive data (tokens, passwords)

蜘蛛绝不会:
  • 明文存储密码(永远不要)
  • 在OAuth流程中跳过PKCE
  • 未经验证就信任用户输入
  • 在配置中保留默认密钥
  • 忽略会话过期
  • 记录敏感数据(令牌、密码)

Example Weave

织网示例

User: "Add GitHub OAuth login"
Spider flow:
  1. 🕷️ SPIN — "Create OAuth app in GitHub, generate client credentials, set up PKCE utilities, create auth endpoints structure"
  2. 🕷️ CONNECT — "Implement /auth/github/login redirect, /auth/github/callback handler, user upsert logic, session creation"
  3. 🕷️ SECURE — "Add CSRF state validation, secure cookie settings, rate limiting on auth endpoints, role assignment for new users"
  4. 🕷️ TEST — "Test OAuth flow, callback handling, session creation, protected route access, error cases (denied permissions)"
  5. 🕷️ BIND — "Add login button to UI, error state handling, loading states, documentation, monitoring"

用户需求: “添加GitHub OAuth登录”
蜘蛛流程:
  1. 🕷️ SPIN — “在GitHub创建OAuth应用、生成客户端凭证、搭建PKCE工具类、创建认证端点结构”
  2. 🕷️ CONNECT — “实现/auth/github/login重定向、/auth/github/callback处理器、用户更新逻辑、会话创建”
  3. 🕷️ SECURE — “添加CSRF状态验证、安全Cookie配置、认证端点速率限制、新用户角色分配”
  4. 🕷️ TEST — “测试OAuth流程、回调处理、会话创建、受保护路由访问、错误场景(权限被拒绝)”
  5. 🕷️ BIND — “在UI中添加登录按钮、错误状态处理、加载状态、文档、监控”

Quick Decision Guide

快速决策指南

SituationApproach
Simple app, internal usersSession-based auth
Public app, social loginOAuth 2.0 + PKCE
API for mobile/SPAJWT with refresh tokens
Service-to-serviceAPI keys with IP allowlist
Grove ecosystemHeartwood integration

场景方案
简单应用、内部用户基于会话的认证
公开应用、社交登录OAuth 2.0 + PKCE
面向移动/SPA的API带刷新令牌的JWT
服务间通信带IP白名单的API密钥
Grove生态系统Heartwood集成

Integration with Other Skills

与其他技能的集成

Before Weaving:
  • eagle-architect
    — For auth system design
  • swan-design
    — For auth flow specifications
During Weaving:
  • elephant-build
    — For multi-file auth implementation
  • raccoon-audit
    — For security review
After Weaving:
  • beaver-build
    — For auth testing
  • deer-sense
    — For accessibility audit of login UI

A well-woven web catches intruders while letting friends pass through. 🕷️
织网前:
  • eagle-architect
    — 用于认证系统设计
  • swan-design
    — 用于认证流程规范
织网中:
  • elephant-build
    — 用于多文件认证实现
  • raccoon-audit
    — 用于安全审查
织网后:
  • beaver-build
    — 用于认证测试
  • deer-sense
    — 用于登录UI的可访问性审查

一张编织精良的蛛网能捕获入侵者,同时让友方通行。 🕷️