Skip to content
This repository was archived by the owner on Mar 27, 2026. It is now read-only.

Commit 55080b1

Browse files
authored
Merge pull request #3 from AutumnsGrove/claude/update-baseproject-scj9q
Add comprehensive skill documentation for Grove ecosystem
2 parents a0e4c42 + 5f2795b commit 55080b1

10 files changed

Lines changed: 3669 additions & 2 deletions

File tree

.claude/skills/grove-documentation/SKILL.md

Lines changed: 477 additions & 0 deletions
Large diffs are not rendered by default.

.claude/skills/grove-spec-writing/SKILL.md

Lines changed: 450 additions & 0 deletions
Large diffs are not rendered by default.

.claude/skills/grove-testing/SKILL.md

Lines changed: 437 additions & 0 deletions
Large diffs are not rendered by default.

.claude/skills/grove-ui-design/SKILL.md

Lines changed: 989 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
---
2+
name: heartwood-auth
3+
description: Integrate Heartwood (GroveAuth) authentication into Grove applications. Use when adding sign-in, protecting routes, or validating sessions in any Grove property.
4+
---
5+
6+
# Heartwood Auth Integration Skill
7+
8+
## When to Activate
9+
10+
Activate this skill when:
11+
- Adding authentication to a Grove application
12+
- Protecting admin routes
13+
- Validating user sessions
14+
- Setting up OAuth sign-in
15+
- Integrating with Heartwood (GroveAuth)
16+
17+
## Overview
18+
19+
**Heartwood** is Grove's centralized authentication service powered by Better Auth.
20+
21+
| Domain | Purpose |
22+
|--------|---------|
23+
| `heartwood.grove.place` | Frontend (login UI) |
24+
| `auth-api.grove.place` | Backend API |
25+
26+
### Key Features
27+
28+
- **OAuth Providers**: Google
29+
- **Magic Links**: Click-to-login emails via Resend
30+
- **Passkeys**: WebAuthn passwordless authentication
31+
- **KV-Cached Sessions**: Sub-100ms validation
32+
- **Cross-Subdomain SSO**: Single session across all .grove.place
33+
34+
## Integration Approaches
35+
36+
### Option A: Better Auth Client (Recommended)
37+
38+
For new integrations, use Better Auth's client library:
39+
40+
```typescript
41+
// src/lib/auth/client.ts
42+
import { createAuthClient } from 'better-auth/client';
43+
44+
export const auth = createAuthClient({
45+
baseURL: 'https://auth-api.grove.place'
46+
});
47+
48+
// Sign in with Google
49+
await auth.signIn.social({ provider: 'google' });
50+
51+
// Get current session
52+
const session = await auth.getSession();
53+
54+
// Sign out
55+
await auth.signOut();
56+
```
57+
58+
### Option B: Cookie-Based SSO (*.grove.place apps)
59+
60+
For apps on `.grove.place` subdomains, sessions work automatically via cookies:
61+
62+
```typescript
63+
// src/hooks.server.ts
64+
import type { Handle } from '@sveltejs/kit';
65+
66+
export const handle: Handle = async ({ event, resolve }) => {
67+
// Check session via Heartwood API
68+
const sessionCookie = event.cookies.get('better-auth.session_token');
69+
70+
if (sessionCookie) {
71+
try {
72+
const response = await fetch('https://auth-api.grove.place/api/auth/session', {
73+
headers: {
74+
Cookie: `better-auth.session_token=${sessionCookie}`
75+
}
76+
});
77+
78+
if (response.ok) {
79+
const data = await response.json();
80+
event.locals.user = data.user;
81+
event.locals.session = data.session;
82+
}
83+
} catch {
84+
// Session invalid or expired
85+
}
86+
}
87+
88+
return resolve(event);
89+
};
90+
```
91+
92+
### Option C: Legacy Token Flow (Backwards Compatible)
93+
94+
For existing integrations using the legacy OAuth flow:
95+
96+
```typescript
97+
// 1. Redirect to Heartwood login
98+
const params = new URLSearchParams({
99+
client_id: 'your-client-id',
100+
redirect_uri: 'https://yourapp.grove.place/auth/callback',
101+
state: crypto.randomUUID(),
102+
code_challenge: await generateCodeChallenge(verifier),
103+
code_challenge_method: 'S256'
104+
});
105+
redirect(302, `https://auth-api.grove.place/login?${params}`);
106+
107+
// 2. Exchange code for tokens (in callback route)
108+
const tokens = await fetch('https://auth-api.grove.place/token', {
109+
method: 'POST',
110+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
111+
body: new URLSearchParams({
112+
grant_type: 'authorization_code',
113+
code: code,
114+
redirect_uri: 'https://yourapp.grove.place/auth/callback',
115+
client_id: 'your-client-id',
116+
client_secret: env.HEARTWOOD_CLIENT_SECRET,
117+
code_verifier: verifier
118+
})
119+
}).then(r => r.json());
120+
121+
// 3. Verify token on protected routes
122+
const user = await fetch('https://auth-api.grove.place/verify', {
123+
headers: { Authorization: `Bearer ${tokens.access_token}` }
124+
}).then(r => r.json());
125+
```
126+
127+
## Protected Routes Pattern
128+
129+
### SvelteKit Layout Protection
130+
131+
```typescript
132+
// src/routes/admin/+layout.server.ts
133+
import { redirect } from '@sveltejs/kit';
134+
import type { LayoutServerLoad } from './$types';
135+
136+
export const load: LayoutServerLoad = async ({ locals }) => {
137+
if (!locals.user) {
138+
throw redirect(302, '/auth/login');
139+
}
140+
141+
return {
142+
user: locals.user
143+
};
144+
};
145+
```
146+
147+
### API Route Protection
148+
149+
```typescript
150+
// src/routes/api/protected/+server.ts
151+
import { json, error } from '@sveltejs/kit';
152+
import type { RequestHandler } from './$types';
153+
154+
export const GET: RequestHandler = async ({ locals }) => {
155+
if (!locals.user) {
156+
throw error(401, 'Unauthorized');
157+
}
158+
159+
return json({ message: 'Protected data', user: locals.user });
160+
};
161+
```
162+
163+
## Session Validation
164+
165+
### Via Better Auth Session Endpoint
166+
167+
```typescript
168+
async function validateSession(sessionToken: string) {
169+
const response = await fetch('https://auth-api.grove.place/api/auth/session', {
170+
headers: {
171+
Cookie: `better-auth.session_token=${sessionToken}`
172+
}
173+
});
174+
175+
if (!response.ok) return null;
176+
177+
const data = await response.json();
178+
return data.session ? data : null;
179+
}
180+
```
181+
182+
### Via Legacy Verify Endpoint
183+
184+
```typescript
185+
async function validateToken(accessToken: string) {
186+
const response = await fetch('https://auth-api.grove.place/verify', {
187+
headers: {
188+
Authorization: `Bearer ${accessToken}`
189+
}
190+
});
191+
192+
const data = await response.json();
193+
return data.active ? data : null;
194+
}
195+
```
196+
197+
## Client Registration
198+
199+
To integrate a new app with Heartwood, you need to register it as a client.
200+
201+
### 1. Generate Client Credentials
202+
203+
```bash
204+
# Generate a secure client secret
205+
openssl rand -base64 32
206+
# Example: YKzJChC3RPjZvd1f/OD5zUGAvcouOTXG7maQP1ernCg=
207+
208+
# Hash it for storage (base64url encoding)
209+
echo -n "YOUR_SECRET" | openssl dgst -sha256 -binary | base64 | tr '+/' '-_' | tr -d '='
210+
```
211+
212+
### 2. Register in Heartwood Database
213+
214+
```sql
215+
INSERT INTO clients (id, name, client_id, client_secret_hash, redirect_uris, allowed_origins)
216+
VALUES (
217+
lower(hex(randomblob(16))),
218+
'Your App Name',
219+
'your-app-id',
220+
'BASE64URL_HASHED_SECRET',
221+
'["https://yourapp.grove.place/auth/callback"]',
222+
'["https://yourapp.grove.place"]'
223+
);
224+
```
225+
226+
### 3. Set Secrets on Your App
227+
228+
```bash
229+
# Set the client secret on your app
230+
wrangler secret put HEARTWOOD_CLIENT_SECRET
231+
# Paste: YKzJChC3RPjZvd1f/OD5zUGAvcouOTXG7maQP1ernCg=
232+
```
233+
234+
## Environment Variables
235+
236+
| Variable | Description |
237+
|----------|-------------|
238+
| `HEARTWOOD_CLIENT_ID` | Your registered client ID |
239+
| `HEARTWOOD_CLIENT_SECRET` | Your client secret (never commit!) |
240+
241+
## API Endpoints Reference
242+
243+
### Better Auth Endpoints (Recommended)
244+
245+
| Method | Endpoint | Purpose |
246+
|--------|----------|---------|
247+
| POST | `/api/auth/sign-in/social` | OAuth sign-in |
248+
| POST | `/api/auth/sign-in/magic-link` | Magic link sign-in |
249+
| POST | `/api/auth/sign-in/passkey` | Passkey sign-in |
250+
| GET | `/api/auth/session` | Get current session |
251+
| POST | `/api/auth/sign-out` | Sign out |
252+
253+
### Legacy Endpoints
254+
255+
| Method | Endpoint | Purpose |
256+
|--------|----------|---------|
257+
| GET | `/login` | Login page |
258+
| POST | `/token` | Exchange code for tokens |
259+
| GET | `/verify` | Validate access token |
260+
| GET | `/userinfo` | Get user info |
261+
262+
## Best Practices
263+
264+
### DO
265+
- Use Better Auth client for new integrations
266+
- Validate sessions on every protected request
267+
- Use `httpOnly` cookies for token storage
268+
- Implement proper error handling for auth failures
269+
- Log out users gracefully when sessions expire
270+
271+
### DON'T
272+
- Store tokens in localStorage (XSS vulnerable)
273+
- Skip session validation on API routes
274+
- Hardcode client secrets
275+
- Ignore token expiration
276+
277+
## Cross-Subdomain SSO
278+
279+
All `.grove.place` apps share the same session cookie automatically:
280+
281+
```
282+
better-auth.session_token (domain=.grove.place)
283+
```
284+
285+
Once a user signs in on any Grove property, they're signed in everywhere.
286+
287+
## Troubleshooting
288+
289+
### "Session not found" errors
290+
- Check cookie domain is `.grove.place`
291+
- Verify SESSION_KV namespace is accessible
292+
- Check session hasn't expired
293+
294+
### OAuth callback errors
295+
- Verify redirect_uri matches registered client
296+
- Check client_id is correct
297+
- Ensure client_secret_hash uses base64url encoding
298+
299+
### Slow authentication
300+
- Ensure KV caching is enabled (SESSION_KV binding)
301+
- Check for cold start issues (Workers may sleep)
302+
303+
## Related Resources
304+
305+
- **Heartwood Spec**: `/Users/autumn/Documents/Projects/GroveAuth/GROVEAUTH_SPEC.md`
306+
- **Better Auth Docs**: https://better-auth.com
307+
- **Client Setup Guide**: `/Users/autumn/Documents/Projects/GroveAuth/docs/OAUTH_CLIENT_SETUP.md`

0 commit comments

Comments
 (0)