Skip to content

Commit 98e3af9

Browse files
committed
fix: SameSite cookie attribute updated to 'lax' for improved security
1 parent b314600 commit 98e3af9

File tree

5 files changed

+136
-12
lines changed

5 files changed

+136
-12
lines changed

.github/copilot-instructions.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Copilot Instructions for SPURT
2+
3+
## Project Overview
4+
SPURT is a task management app built with **Next.js 15 App Router** for procrastinators who wait until the last minute. The app uses a persona system with characters that guide users through task completion.
5+
6+
## Architecture
7+
8+
### Route Structure
9+
- **App Router** with route groups:
10+
- `(auth)/` - Public authentication routes (login, OAuth callbacks)
11+
- `(protected)/` - Authenticated routes with `AuthProvider`
12+
- `(create)/` - Task creation flows (instant-create, scheduled-create)
13+
- `(root)/` - Home and main views
14+
- `action/`, `immersion/`, `retrospection/` - Task interaction flows
15+
- `my-page/` - User profile and settings
16+
- `api/` - API routes that proxy to backend (`NEXT_PUBLIC_API_URL`)
17+
18+
### Authentication Flow
19+
1. OAuth callback routes (`/oauth/callback/kakao`, `/oauth/callback/apple`) handle social login
20+
2. API routes set **httpOnly** cookies (`accessToken`, `refreshToken`, `user`) with `sameSite: "lax"` for iOS WebView compatibility
21+
3. Middleware ([middleware.ts](middleware.ts)) redirects unauthenticated users to `/login` (except open paths)
22+
4. `serverKy.ts` handles automatic token refresh on 401 responses
23+
24+
**Important**: Use `serverApi` from `src/lib/serverKy.ts` for all API calls (not client-side `ky.ts`):
25+
- Automatic Bearer token injection from server-side cookies
26+
- Token refresh on 401 with batch request optimization
27+
- Proper headers and credentials
28+
- All API routes proxy through `/api/*` endpoints using `serverApi`
29+
30+
### State Management
31+
Uses **Zustand** stores (not Redux):
32+
- `useAuthStore` - Auth loading states
33+
- `useUserStore` - User profile data
34+
- `useTaskStore` - Task state management
35+
36+
Access stores with hooks: `const { setUser } = useUserStore()`
37+
38+
### Data Fetching Patterns
39+
1. **API routes**: All use `serverApi` from [serverKy.ts](src/lib/serverKy.ts) - has access to server-side cookies
40+
2. **Client components**: Call `/api/*` routes via `fetch` (which internally use `serverApi`)
41+
3. **React Query**: Configured in [providers.tsx](src/app/providers/providers.tsx) with 60s staleTime
42+
4. **Task data transformation**: Always use `convertApiResponseToTask()` from [task.ts](src/types/task.ts) to convert API responses
43+
44+
### Multi-Step Forms
45+
Uses `@use-funnel/browser` for form flows (NOT react-hook-form):
46+
```typescript
47+
const funnel = useFunnel<{ task: string, deadlineDate: string }>({
48+
id: "instant-create",
49+
initial: { step: "taskForm", context: { task: "", deadlineDate: "" } }
50+
});
51+
52+
<funnel.Render
53+
taskForm={({ context }) => <TaskFormStep context={context} />}
54+
taskTypeInput={({ context }) => <TaskTypeStep />}
55+
/>
56+
```
57+
See [instant-create/page.tsx](src/app/(protected)/(create)/instant-create/page.tsx) for reference.
58+
59+
## Code Style & Conventions
60+
61+
### Formatting
62+
- Use **Biome** (not Prettier/ESLint alone) - `biome.json` config
63+
- Tabs for indentation
64+
- Double quotes for strings
65+
- Korean comments are acceptable (team preference)
66+
67+
### Styling
68+
- **Tailwind CSS** with custom design tokens in [tailwind.config.ts](tailwind.config.ts)
69+
- Custom typography: `text-h1`, `text-t2`, `text-b3`, `text-c1` (header/title/body/caption scales)
70+
- Custom colors: `bg-background-primary`, `text-text-normal`, `bg-component-accent-primary`
71+
- Mobile-first (viewport locked): `maximum-scale=1.0, user-scalable=no`
72+
- Use `background-primary` (not `bg-white`) for consistent theming
73+
74+
### Component Patterns
75+
- Shadcn-style components in `src/components/ui/`
76+
- Modular feature components in `src/components/` (BackHeader, DatePicker, Modal, etc.)
77+
- Client components marked with `"use client"` directive
78+
- Suspense boundaries for search params: wrap components using `useSearchParams()` in `<Suspense>`
79+
80+
### Type Safety
81+
- Strict TypeScript enabled
82+
- API response types in `src/types/` (task.ts, auth.ts, user.ts, subtask.ts)
83+
- Task status types: `"pending" | "completed" | "reflected" | "procrastinating" | "inProgress"`
84+
- Always transform API responses with typed converters
85+
86+
## Key Developer Workflows
87+
88+
### Local Development
89+
```bash
90+
npm run dev # Start dev server
91+
npm run build # Production build
92+
npm run lint # Run Biome linter
93+
npm run init:ssl # Initialize SSL for local HTTPS
94+
```
95+
96+
### FCM Push Notifications
97+
- FCM tokens managed via `src/lib/fcmToken.ts`
98+
- Device registration in `api/fcm-devices/`
99+
- Firebase messaging worker: `public/firebase-messaging-sw.js`
100+
101+
### WebView Communication
102+
- Use `useWebViewMessage` hook for native app bridge
103+
- Haptic feedback types defined in `src/types/haptic.ts`
104+
105+
### Testing New Features
106+
1. Check route group structure - auth vs protected
107+
2. Verify middleware rules for new routes
108+
3. Use service layer for API calls, not direct fetch
109+
4. Transform API responses with type converters
110+
5. Use Zustand stores for shared state
111+
112+
## Critical Files
113+
- [middleware.ts](src/middleware.ts) - Route protection logic
114+
- [serverKy.ts](src/lib/serverKy.ts) - Server-side API client with token refresh
115+
- [taskService.ts](src/services/taskService.ts) - Task data layer (calls `/api/*` endpoints)
116+
- [task.ts](src/types/task.ts) - Task type definitions & transformers
117+
- [tailwind.config.ts](tailwind.config.ts) - Design system tokens
118+
119+
## Common Pitfalls
120+
❌ Don't use client-side `ky.ts` - use `serverApi` from `serverKy.ts` in API routes
121+
❌ Don't forget to wrap `useSearchParams()` components in `<Suspense>`
122+
❌ Don't bypass API routes - always proxy through `/api/*` endpoints
123+
❌ Don't use generic color classes (`bg-white`) - use design tokens (`bg-background-primary`)
124+
❌ Don't forget to transform API task responses with `convertApiResponseToTask()`

src/app/api/auth/members/me/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export async function GET(req: NextRequest) {
4242
nextResponse.cookies.set("user", JSON.stringify(data), {
4343
httpOnly: true,
4444
secure: true,
45-
sameSite: "none",
45+
sameSite: "lax",
4646
path: "/",
4747
maxAge: 31536000,
4848
});

src/app/api/oauth/callback/apple/route.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,23 +61,23 @@ export async function POST(req: NextRequest) {
6161
nextResponse.cookies.set("accessToken", accessToken, {
6262
httpOnly: true,
6363
secure: true,
64-
sameSite: "none",
64+
sameSite: "lax",
6565
path: "/",
6666
maxAge: 60 * 60,
6767
});
6868

6969
nextResponse.cookies.set("refreshToken", refreshToken, {
7070
httpOnly: true,
7171
secure: true,
72-
sameSite: "none",
72+
sameSite: "lax",
7373
path: "/",
74-
maxAge: 60 * 60 * 24 * 7,
74+
maxAge: 60 * 60 * 24 * 30,
7575
});
7676

7777
nextResponse.cookies.set("userData", JSON.stringify(userData), {
7878
httpOnly: true,
7979
secure: true,
80-
sameSite: "none",
80+
sameSite: "lax",
8181
path: "/",
8282
maxAge: 31536000,
8383
});

src/app/api/oauth/callback/kakao/route.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,23 +55,23 @@ export async function POST(req: NextRequest) {
5555
nextResponse.cookies.set("accessToken", accessToken, {
5656
httpOnly: true,
5757
secure: true,
58-
sameSite: "none",
58+
sameSite: "lax",
5959
path: "/",
6060
maxAge: 60 * 60,
6161
});
6262

6363
nextResponse.cookies.set("refreshToken", refreshToken, {
6464
httpOnly: true,
6565
secure: true,
66-
sameSite: "none",
66+
sameSite: "lax",
6767
path: "/",
68-
maxAge: 60 * 60 * 24 * 7,
68+
maxAge: 60 * 60 * 24 * 30,
6969
});
7070

7171
nextResponse.cookies.set("user", JSON.stringify(userData), {
7272
httpOnly: true,
7373
secure: true,
74-
sameSite: "none",
74+
sameSite: "lax",
7575
path: "/",
7676
maxAge: 31536000,
7777
});

src/lib/serverKy.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,16 @@ export async function refreshTokenOnce(): Promise<string> {
3535
cookieStore.set("accessToken", accessToken, {
3636
httpOnly: true,
3737
secure: true,
38-
sameSite: "none",
38+
sameSite: "lax",
3939
path: "/",
4040
maxAge: 60 * 60,
4141
});
4242
cookieStore.set("refreshToken", newRefreshToken, {
4343
httpOnly: true,
4444
secure: true,
45-
sameSite: "none",
45+
sameSite: "lax",
4646
path: "/",
47-
maxAge: 60 * 60 * 24 * 7,
47+
maxAge: 60 * 60 * 24 * 30,
4848
});
4949

5050
return accessToken;

0 commit comments

Comments
 (0)