@nebulit/embuilder 0.1.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +254 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +138 -0
- package/package.json +49 -0
- package/templates/.claude/hooks/QUICKSTART.md +256 -0
- package/templates/.claude/hooks/README.md +533 -0
- package/templates/.claude/hooks/analyze-commit.sh +22 -0
- package/templates/.claude/hooks/analyze-commit.ts +518 -0
- package/templates/.claude/hooks/analyzers/README.md +198 -0
- package/templates/.claude/hooks/analyzers/code-quality-checker.ts +154 -0
- package/templates/.claude/hooks/analyzers/code-quality.md +54 -0
- package/templates/.claude/hooks/analyzers/commit-blocker-example.ts.disabled +110 -0
- package/templates/.claude/hooks/analyzers/commit-policy.md +49 -0
- package/templates/.claude/hooks/analyzers/event-model-validator.md +49 -0
- package/templates/.claude/hooks/analyzers/event-model-validator.ts +169 -0
- package/templates/.claude/hooks/analyzers/example-logger.ts +70 -0
- package/templates/.claude/hooks/analyzers/slice-scope-validator.md +81 -0
- package/templates/.claude/hooks/check-review-result.sh +47 -0
- package/templates/.claude/hooks/prepare-review.sh +34 -0
- package/templates/.claude/hooks/review-agent-prompt.md +42 -0
- package/templates/.claude/hooks/run-review-agent.sh +124 -0
- package/templates/.claude/settings.local.json +37 -0
- package/templates/.claude/skills/help/README.md +84 -0
- package/templates/.claude/skills/help/SKILL.md +393 -0
- package/templates/.claude/skills/help/templates/demo-config.json +6753 -0
- package/templates/.claude/skills/sample-slices/SKILL.md +8 -0
- package/templates/.claude/skills/sample-slices/templates/.slices/Library/addbook/code-slice.json +124 -0
- package/templates/.claude/skills/sample-slices/templates/.slices/Library/addbook/slice.json +255 -0
- package/templates/.claude/skills/sample-slices/templates/.slices/Library/availablebooks/slice.json +107 -0
- package/templates/.claude/skills/sample-slices/templates/.slices/index.json +20 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/additem/slice.json +979 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/archiveitem/slice.json +529 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/cartitems/slice.json +1072 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/cartwithproducts/slice.json +394 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/changedprices/slice.json +88 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/changeinventory/slice.json +264 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/changeprice/slice.json +308 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/clearcart/slice.json +358 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/inventories/slice.json +203 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/publishcart/slice.json +876 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/removeitem/slice.json +560 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/submitcart/slice.json +708 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/submittedcartdata/slice.json +399 -0
- package/templates/.claude/skills/sample-slices/templates/index.json +108 -0
- package/templates/.claude/skills/slice-automation/SKILL.md +49 -0
- package/templates/.claude/skills/slice-state-change/SKILL.md +369 -0
- package/templates/.claude/skills/slice-state-change/templates/AddLocation/AddLocation.test.ts.sample +76 -0
- package/templates/.claude/skills/slice-state-change/templates/AddLocation/AddLocationCommand.ts.sample +84 -0
- package/templates/.claude/skills/slice-state-change/templates/AddLocation/routes.ts.sample +73 -0
- package/templates/.claude/skills/slice-state-change/templates/README.md +46 -0
- package/templates/.claude/skills/slice-state-view/SKILL.md +336 -0
- package/templates/.claude/skills/slice-state-view/templates/Locations/Locations.test.ts.sample +84 -0
- package/templates/.claude/skills/slice-state-view/templates/Locations/LocationsProjection.ts.sample +50 -0
- package/templates/.claude/skills/slice-state-view/templates/Locations/routes.ts.sample +46 -0
- package/templates/.claude/skills/slice-state-view/templates/README.md +109 -0
- package/templates/.claude/skills/slice-state-view/templates/Tables/Tables.test.ts.sample +104 -0
- package/templates/.claude/skills/slice-state-view/templates/Tables/TablesProjection.ts.sample +59 -0
- package/templates/.claude/skills/slice-state-view/templates/Tables/routes.ts.sample +46 -0
- package/templates/.claude/skills/slice-state-view/templates/V2__tables.sql +7 -0
- package/templates/.claude/skills/slice-state-view/templates/V8__locations.sql +7 -0
- package/templates/.claude/skills/test-analyzer/SKILL.md +373 -0
- package/templates/.claude/skills/test-analyzer/examples/specification-format.md +143 -0
- package/templates/.claude/skills/test-analyzer/examples/state-change-example.md +111 -0
- package/templates/.claude/skills/test-analyzer/examples/state-view-example.md +122 -0
- package/templates/AGENTS.md +110 -0
- package/templates/Claude.md +58 -0
- package/templates/README.md +178 -0
- package/templates/backend/.env +9 -0
- package/templates/backend/BACKEND_AUTH_SETUP.md +183 -0
- package/templates/backend/SWAGGER.md +213 -0
- package/templates/backend/eslint.config.mjs +31 -0
- package/templates/backend/flyway.conf +17 -0
- package/templates/backend/package.json +44 -0
- package/templates/backend/prd.json.example +64 -0
- package/templates/backend/public/assets/images/banner.png +0 -0
- package/templates/backend/public/assets/logo.png +0 -0
- package/templates/backend/public/file.svg +4 -0
- package/templates/backend/public/globe.svg +12 -0
- package/templates/backend/public/next.svg +6 -0
- package/templates/backend/public/vercel.svg +3 -0
- package/templates/backend/public/window.svg +5 -0
- package/templates/backend/server.ts +129 -0
- package/templates/backend/setup-env.sh +50 -0
- package/templates/backend/src/common/assertions.ts +6 -0
- package/templates/backend/src/common/db.ts +1 -0
- package/templates/backend/src/common/loadPostgresEventstore.ts +16 -0
- package/templates/backend/src/common/parseEndpoint.ts +51 -0
- package/templates/backend/src/common/replay.ts +9 -0
- package/templates/backend/src/common/routes.ts +19 -0
- package/templates/backend/src/common/testHelpers.ts +53 -0
- package/templates/backend/src/core/readmodel.ts +28 -0
- package/templates/backend/src/core/types.ts +26 -0
- package/templates/backend/src/process/process.ts +53 -0
- package/templates/backend/src/supabase/LoginHandler.ts +36 -0
- package/templates/backend/src/supabase/ProtectedPageProps.ts +21 -0
- package/templates/backend/src/supabase/README.md +171 -0
- package/templates/backend/src/supabase/api.ts +63 -0
- package/templates/backend/src/supabase/authMiddleware.ts +53 -0
- package/templates/backend/src/supabase/component.ts +12 -0
- package/templates/backend/src/supabase/requireUser.ts +72 -0
- package/templates/backend/src/supabase/serverProps.ts +25 -0
- package/templates/backend/src/supabase/staticProps.ts +10 -0
- package/templates/backend/src/swagger.ts +34 -0
- package/templates/backend/src/util/assertions.ts +6 -0
- package/templates/backend/supabase/config.toml +295 -0
- package/templates/backend/supabase/migrations/20260121155918593_catalogentries.sql.sample +23 -0
- package/templates/backend/supabase/seed.sql +1 -0
- package/templates/backend/tsconfig.json +31 -0
- package/templates/frontend/.env.development +3 -0
- package/templates/frontend/AGENTS.md +7 -0
- package/templates/frontend/README.md +73 -0
- package/templates/frontend/components.json +20 -0
- package/templates/frontend/eslint.config.js +26 -0
- package/templates/frontend/index.html +18 -0
- package/templates/frontend/package-lock.json +8347 -0
- package/templates/frontend/package.json +94 -0
- package/templates/frontend/postcss.config.js +6 -0
- package/templates/frontend/public/favicon.ico +0 -0
- package/templates/frontend/public/logo.png +0 -0
- package/templates/frontend/public/placeholder.svg +1 -0
- package/templates/frontend/public/robots.txt +14 -0
- package/templates/frontend/src/App.css +42 -0
- package/templates/frontend/src/App.tsx +47 -0
- package/templates/frontend/src/components/NavLink.tsx +28 -0
- package/templates/frontend/src/components/ProtectedRoute.tsx +24 -0
- package/templates/frontend/src/components/calendar/Calendar.tsx +302 -0
- package/templates/frontend/src/components/layout/DashboardLayout.tsx +21 -0
- package/templates/frontend/src/components/layout/Header.tsx +45 -0
- package/templates/frontend/src/components/layout/Sidebar.tsx +82 -0
- package/templates/frontend/src/components/tables/ReservationTemplates.tsx +189 -0
- package/templates/frontend/src/components/ui/accordion.tsx +52 -0
- package/templates/frontend/src/components/ui/alert-dialog.tsx +104 -0
- package/templates/frontend/src/components/ui/alert.tsx +43 -0
- package/templates/frontend/src/components/ui/aspect-ratio.tsx +5 -0
- package/templates/frontend/src/components/ui/avatar.tsx +38 -0
- package/templates/frontend/src/components/ui/badge.tsx +29 -0
- package/templates/frontend/src/components/ui/breadcrumb.tsx +90 -0
- package/templates/frontend/src/components/ui/button.tsx +47 -0
- package/templates/frontend/src/components/ui/calendar.tsx +54 -0
- package/templates/frontend/src/components/ui/card.tsx +43 -0
- package/templates/frontend/src/components/ui/carousel.tsx +224 -0
- package/templates/frontend/src/components/ui/chart.tsx +303 -0
- package/templates/frontend/src/components/ui/checkbox.tsx +26 -0
- package/templates/frontend/src/components/ui/collapsible.tsx +9 -0
- package/templates/frontend/src/components/ui/command.tsx +132 -0
- package/templates/frontend/src/components/ui/context-menu.tsx +178 -0
- package/templates/frontend/src/components/ui/dialog.tsx +95 -0
- package/templates/frontend/src/components/ui/drawer.tsx +87 -0
- package/templates/frontend/src/components/ui/dropdown-menu.tsx +179 -0
- package/templates/frontend/src/components/ui/form.tsx +129 -0
- package/templates/frontend/src/components/ui/hover-card.tsx +27 -0
- package/templates/frontend/src/components/ui/input-otp.tsx +61 -0
- package/templates/frontend/src/components/ui/input.tsx +22 -0
- package/templates/frontend/src/components/ui/label.tsx +17 -0
- package/templates/frontend/src/components/ui/menubar.tsx +207 -0
- package/templates/frontend/src/components/ui/navigation-menu.tsx +120 -0
- package/templates/frontend/src/components/ui/pagination.tsx +81 -0
- package/templates/frontend/src/components/ui/popover.tsx +29 -0
- package/templates/frontend/src/components/ui/progress.tsx +23 -0
- package/templates/frontend/src/components/ui/radio-group.tsx +36 -0
- package/templates/frontend/src/components/ui/resizable.tsx +37 -0
- package/templates/frontend/src/components/ui/scroll-area.tsx +38 -0
- package/templates/frontend/src/components/ui/select.tsx +143 -0
- package/templates/frontend/src/components/ui/separator.tsx +20 -0
- package/templates/frontend/src/components/ui/sheet.tsx +107 -0
- package/templates/frontend/src/components/ui/sidebar.tsx +637 -0
- package/templates/frontend/src/components/ui/skeleton.tsx +7 -0
- package/templates/frontend/src/components/ui/slider.tsx +23 -0
- package/templates/frontend/src/components/ui/sonner.tsx +27 -0
- package/templates/frontend/src/components/ui/stat-card.tsx +44 -0
- package/templates/frontend/src/components/ui/switch.tsx +27 -0
- package/templates/frontend/src/components/ui/table.tsx +72 -0
- package/templates/frontend/src/components/ui/tabs.tsx +53 -0
- package/templates/frontend/src/components/ui/textarea.tsx +21 -0
- package/templates/frontend/src/components/ui/toast.tsx +111 -0
- package/templates/frontend/src/components/ui/toaster.tsx +24 -0
- package/templates/frontend/src/components/ui/toggle-group.tsx +49 -0
- package/templates/frontend/src/components/ui/toggle.tsx +37 -0
- package/templates/frontend/src/components/ui/tooltip.tsx +28 -0
- package/templates/frontend/src/components/ui/use-toast.ts +3 -0
- package/templates/frontend/src/contexts/AuthContext.tsx +94 -0
- package/templates/frontend/src/contexts/RefreshContext.tsx +236 -0
- package/templates/frontend/src/hooks/api/index.ts +2 -0
- package/templates/frontend/src/hooks/api/useLocations.ts +15 -0
- package/templates/frontend/src/hooks/use-mobile.tsx +19 -0
- package/templates/frontend/src/hooks/use-toast.ts +186 -0
- package/templates/frontend/src/hooks/useApiContext.ts +11 -0
- package/templates/frontend/src/index.css +118 -0
- package/templates/frontend/src/integrations/supabase/client.ts +9 -0
- package/templates/frontend/src/lib/api-client.ts +136 -0
- package/templates/frontend/src/lib/api.ts +1028 -0
- package/templates/frontend/src/lib/utils.ts +6 -0
- package/templates/frontend/src/main.tsx +5 -0
- package/templates/frontend/src/pages/Auth.tsx +408 -0
- package/templates/frontend/src/pages/Dashboard.tsx +168 -0
- package/templates/frontend/src/pages/Menus.tsx +224 -0
- package/templates/frontend/src/pages/NotFound.tsx +24 -0
- package/templates/frontend/src/pages/Register.tsx +285 -0
- package/templates/frontend/src/test/example.test.ts +0 -0
- package/templates/frontend/src/test/setup.ts +15 -0
- package/templates/frontend/src/types/index.ts +8 -0
- package/templates/frontend/src/vite-env.d.ts +1 -0
- package/templates/frontend/tailwind.config.ts +101 -0
- package/templates/frontend/tsconfig.app.json +31 -0
- package/templates/frontend/tsconfig.json +16 -0
- package/templates/frontend/tsconfig.node.json +22 -0
- package/templates/frontend/vite.config.ts +21 -0
- package/templates/frontend/vitest.config.ts +16 -0
- package/templates/init.sh +1 -0
- package/templates/prompt.md +139 -0
- package/templates/ralph.sh +120 -0
- package/templates/server.mjs +505 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Supabase JWT Authentication for Backend API
|
|
2
|
+
|
|
3
|
+
This backend API uses Supabase JWT tokens for authentication. Clients must include a valid JWT token in the
|
|
4
|
+
`Authorization` header.
|
|
5
|
+
|
|
6
|
+
## Quick Start
|
|
7
|
+
|
|
8
|
+
1. **Set up environment variables** in `.env.local`:
|
|
9
|
+
```env
|
|
10
|
+
NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321
|
|
11
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key-here
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
2. **Start the server**: `npm run dev`
|
|
15
|
+
|
|
16
|
+
3. **Get a test JWT token**:
|
|
17
|
+
- Visit http://localhost:3000/auth/login
|
|
18
|
+
- Create an account or login
|
|
19
|
+
- Copy the JWT token displayed
|
|
20
|
+
- Use it in your API requests
|
|
21
|
+
|
|
22
|
+
## Test Login Page
|
|
23
|
+
|
|
24
|
+
A simple test login page is available at `/auth/login` that:
|
|
25
|
+
|
|
26
|
+
- Allows you to create accounts or login
|
|
27
|
+
- Displays the JWT token after authentication
|
|
28
|
+
- Provides a "Test API Call" button
|
|
29
|
+
- Shows a cURL example for testing
|
|
30
|
+
|
|
31
|
+
This page is for testing purposes only.
|
|
32
|
+
|
|
33
|
+
## How It Works
|
|
34
|
+
|
|
35
|
+
1. **Client obtains JWT token** from Supabase (via your frontend app)
|
|
36
|
+
2. **Client sends requests** with `Authorization: Bearer <jwt-token>` header
|
|
37
|
+
3. **Backend verifies JWT** using Supabase and extracts user info
|
|
38
|
+
4. **Protected routes** return user data or 401 Unauthorized
|
|
39
|
+
|
|
40
|
+
## Usage Examples
|
|
41
|
+
|
|
42
|
+
### Option 1: Using `requireUser` function
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import {requireUser} from './src/supabase/requireUser';
|
|
46
|
+
import {Request, Response} from 'express';
|
|
47
|
+
|
|
48
|
+
app.get('/api/protected', async (req: Request, res: Response) => {
|
|
49
|
+
const result = await requireUser(req, res, false);
|
|
50
|
+
|
|
51
|
+
if (result.error) {
|
|
52
|
+
return res.status(401).json({error: result.error});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const user = result.user;
|
|
56
|
+
res.json({
|
|
57
|
+
message: 'Protected data',
|
|
58
|
+
userId: user.id,
|
|
59
|
+
email: user.email
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Option 2: Using `authMiddleware`
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import {authMiddleware} from './src/supabase/authMiddleware';
|
|
68
|
+
|
|
69
|
+
app.get('/api/protected', authMiddleware, (req, res) => {
|
|
70
|
+
// User is available on req.user after middleware
|
|
71
|
+
const user = (req as any).user;
|
|
72
|
+
|
|
73
|
+
res.json({
|
|
74
|
+
message: 'Protected data',
|
|
75
|
+
user: user
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Testing with curl
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Get JWT token from your Supabase client first
|
|
84
|
+
TOKEN="your-jwt-token-here"
|
|
85
|
+
|
|
86
|
+
# Test protected endpoint
|
|
87
|
+
curl -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/user
|
|
88
|
+
|
|
89
|
+
# Expected response:
|
|
90
|
+
# {
|
|
91
|
+
# "userId": "...",
|
|
92
|
+
# "email": "user@example.com",
|
|
93
|
+
# "metadata": { ... }
|
|
94
|
+
# }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Testing with JavaScript fetch
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
const token = supabase.auth.session()?.access_token;
|
|
101
|
+
|
|
102
|
+
fetch('http://localhost:3000/api/user', {
|
|
103
|
+
headers: {
|
|
104
|
+
'Authorization': `Bearer ${token}`
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
.then(res => res.json())
|
|
108
|
+
.then(data => console.log(data));
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## API Endpoints
|
|
112
|
+
|
|
113
|
+
### `GET /api/user`
|
|
114
|
+
|
|
115
|
+
Returns current authenticated user information.
|
|
116
|
+
|
|
117
|
+
**Headers:**
|
|
118
|
+
|
|
119
|
+
- `Authorization: Bearer <jwt-token>` (required)
|
|
120
|
+
|
|
121
|
+
**Success Response (200):**
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"userId": "uuid",
|
|
126
|
+
"email": "user@example.com",
|
|
127
|
+
"metadata": { ... }
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Error Responses:**
|
|
132
|
+
|
|
133
|
+
- `401 Unauthorized`: Missing or invalid token
|
|
134
|
+
- `500 Internal Server Error`: Server error
|
|
135
|
+
|
|
136
|
+
## Files
|
|
137
|
+
|
|
138
|
+
- **`api.ts`**: Supabase client creation
|
|
139
|
+
- **`requireUser.ts`**: JWT verification function
|
|
140
|
+
- **`authMiddleware.ts`**: Express middleware for protecting routes
|
|
141
|
+
- **`README.md`**: This documentation
|
|
142
|
+
|
|
143
|
+
## Architecture
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
Client Request
|
|
147
|
+
|
|
|
148
|
+
v
|
|
149
|
+
Authorization: Bearer <JWT>
|
|
150
|
+
|
|
|
151
|
+
v
|
|
152
|
+
Express Route
|
|
153
|
+
|
|
|
154
|
+
v
|
|
155
|
+
requireUser() / authMiddleware
|
|
156
|
+
|
|
|
157
|
+
v
|
|
158
|
+
Supabase JWT Verification
|
|
159
|
+
|
|
|
160
|
+
+---> Valid: Continue with user data
|
|
161
|
+
|
|
|
162
|
+
+---> Invalid: Return 401 Unauthorized
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Security Notes
|
|
166
|
+
|
|
167
|
+
- JWT tokens are verified with Supabase on every request
|
|
168
|
+
- No session storage on the backend (stateless)
|
|
169
|
+
- Tokens expire based on Supabase configuration
|
|
170
|
+
- Always use HTTPS in production
|
|
171
|
+
- Store the anon key securely (use environment variables)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {createClient as createSupabaseClient} from '@supabase/supabase-js'
|
|
2
|
+
import {Request} from 'express'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a Supabase client for server-side API operations
|
|
6
|
+
* This client does not persist sessions and is used for JWT verification
|
|
7
|
+
*/
|
|
8
|
+
export const createServiceClient = () => {
|
|
9
|
+
console.log(process.env.SUPABASE_URL)
|
|
10
|
+
return createSupabaseClient(
|
|
11
|
+
process.env.SUPABASE_URL!,
|
|
12
|
+
process.env.SUPABASE_SECRET_KEY!
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a Supabase client for verifying JWT tokens
|
|
18
|
+
* Used in backend API endpoints
|
|
19
|
+
*/
|
|
20
|
+
export default function createClient() {
|
|
21
|
+
return createSupabaseClient(
|
|
22
|
+
process.env.SUPABASE_URL!,
|
|
23
|
+
process.env.SUPABASE_PUBLISHABLE_KEY!,
|
|
24
|
+
{
|
|
25
|
+
auth: {
|
|
26
|
+
persistSession: false,
|
|
27
|
+
autoRefreshToken: false,
|
|
28
|
+
detectSessionInUrl: false,
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Creates an authenticated Supabase client with the user's JWT token
|
|
36
|
+
* This ensures Row Level Security (RLS) policies are applied with the user's context
|
|
37
|
+
*/
|
|
38
|
+
export async function createAuthenticatedClient(req: Request) {
|
|
39
|
+
const token = req.headers.authorization?.replace('Bearer ', '') || '';
|
|
40
|
+
|
|
41
|
+
console.log(token)
|
|
42
|
+
console.log(process.env.SUPABASE_URL!)
|
|
43
|
+
const client = createSupabaseClient(
|
|
44
|
+
process.env.SUPABASE_URL!,
|
|
45
|
+
process.env.SUPABASE_PUBLISHABLE_KEY!,
|
|
46
|
+
{
|
|
47
|
+
auth: {
|
|
48
|
+
persistSession: false,
|
|
49
|
+
autoRefreshToken: false,
|
|
50
|
+
detectSessionInUrl: false,
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
if (token) {
|
|
56
|
+
await client.auth.setSession({
|
|
57
|
+
access_token: token,
|
|
58
|
+
refresh_token: '',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return client;
|
|
63
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {NextFunction, Request, Response} from 'express';
|
|
2
|
+
import {requireUser} from './requireUser';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Express middleware to protect routes with JWT authentication
|
|
6
|
+
* Verifies the JWT token from Authorization header and attaches user to request
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import {authMiddleware} from './src/supabase/authMiddleware';
|
|
11
|
+
*
|
|
12
|
+
* app.get('/api/protected', authMiddleware, (req, res) => {
|
|
13
|
+
* const user = req.user; // TypeScript: use custom type definition
|
|
14
|
+
* res.json({ message: 'Protected data', user });
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export async function authMiddleware(req: Request, res: Response, next: NextFunction) {
|
|
19
|
+
try {
|
|
20
|
+
const result = await requireUser(req, res, false);
|
|
21
|
+
|
|
22
|
+
if (result.error) {
|
|
23
|
+
return res.status(401).json({
|
|
24
|
+
error: 'Unauthorized',
|
|
25
|
+
message: result.error
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Attach user to request for downstream handlers
|
|
30
|
+
(req as any).user = result.user;
|
|
31
|
+
|
|
32
|
+
next();
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Auth middleware error:', error);
|
|
35
|
+
return res.status(500).json({
|
|
36
|
+
error: 'Internal server error',
|
|
37
|
+
message: 'Authentication failed'
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* TypeScript declaration to extend Express Request type
|
|
44
|
+
* Add this to your types file or use it directly:
|
|
45
|
+
*
|
|
46
|
+
* declare global {
|
|
47
|
+
* namespace Express {
|
|
48
|
+
* interface Request {
|
|
49
|
+
* user?: any; // Or use your User type
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
*/
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {createBrowserClient} from '@supabase/ssr'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a Supabase client for browser/client-side operations
|
|
5
|
+
* Used in React components for authentication flows
|
|
6
|
+
*/
|
|
7
|
+
export function createClient() {
|
|
8
|
+
return createBrowserClient(
|
|
9
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
10
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
|
11
|
+
)
|
|
12
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {createAuthenticatedClient} from "./api";
|
|
2
|
+
import {Request, Response} from "express"
|
|
3
|
+
|
|
4
|
+
type RequireUserResult = {
|
|
5
|
+
user: any;
|
|
6
|
+
error: null;
|
|
7
|
+
} | {
|
|
8
|
+
user: null;
|
|
9
|
+
error: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Extracts JWT token from Authorization header
|
|
14
|
+
* Supports "Bearer <token>" format
|
|
15
|
+
*/
|
|
16
|
+
function extractTokenFromHeader(req: Request): string | null {
|
|
17
|
+
const authHeader = req.headers.authorization;
|
|
18
|
+
|
|
19
|
+
if (!authHeader) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Check for "Bearer <token>" format
|
|
24
|
+
const parts = authHeader.split(' ');
|
|
25
|
+
if (parts.length === 2 && parts[0].toLowerCase() === 'bearer') {
|
|
26
|
+
return parts[1];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// If no Bearer prefix, assume the entire header is the token
|
|
30
|
+
return authHeader;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Verifies JWT token from Authorization header and returns user info
|
|
35
|
+
* This is for backend API use - does not use cookies or redirects
|
|
36
|
+
*/
|
|
37
|
+
export async function requireUser(req: Request, resp: Response, sendUnauthorized: boolean = true): Promise<RequireUserResult> {
|
|
38
|
+
const token = extractTokenFromHeader(req);
|
|
39
|
+
|
|
40
|
+
if (!token) {
|
|
41
|
+
if (sendUnauthorized) {
|
|
42
|
+
resp.status(401).json({error: 'Missing authorization token'});
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
user: null,
|
|
46
|
+
error: 'MISSING_TOKEN',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const supabase = await createAuthenticatedClient(req);
|
|
51
|
+
|
|
52
|
+
// Verify the JWT token
|
|
53
|
+
const {
|
|
54
|
+
data: {user},
|
|
55
|
+
error
|
|
56
|
+
} = await supabase.auth.getUser(token);
|
|
57
|
+
|
|
58
|
+
if (error || !user) {
|
|
59
|
+
if (sendUnauthorized) {
|
|
60
|
+
resp.status(401).json({error: 'Invalid or expired token'});
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
user: null,
|
|
64
|
+
error: error?.message || 'UNAUTHORIZED',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
user: user,
|
|
70
|
+
error: null
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {type Request, type Response} from 'express'
|
|
2
|
+
import {createServerClient, serializeCookieHeader} from '@supabase/ssr'
|
|
3
|
+
|
|
4
|
+
export function createClient(req: Request, res: Response) {
|
|
5
|
+
const supabase = createServerClient(
|
|
6
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
7
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
8
|
+
{
|
|
9
|
+
cookies: {
|
|
10
|
+
getAll() {
|
|
11
|
+
return Object.keys(req.cookies).map((name) => ({name, value: req.cookies[name] || ''}))
|
|
12
|
+
},
|
|
13
|
+
setAll(cookiesToSet: any[]) {
|
|
14
|
+
res.setHeader(
|
|
15
|
+
'Set-Cookie',
|
|
16
|
+
cookiesToSet.map(({name, value, options}: any) =>
|
|
17
|
+
serializeCookieHeader(name, value, options)
|
|
18
|
+
)
|
|
19
|
+
)
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
return supabase
|
|
25
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {createClient as createClientPrimitive} from '@supabase/supabase-js'
|
|
2
|
+
|
|
3
|
+
export function createClient() {
|
|
4
|
+
const supabase = createClientPrimitive(
|
|
5
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
6
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
return supabase
|
|
10
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import swaggerJsdoc from 'swagger-jsdoc';
|
|
2
|
+
|
|
3
|
+
const options = {
|
|
4
|
+
definition: {
|
|
5
|
+
openapi: '3.0.0',
|
|
6
|
+
info: {
|
|
7
|
+
title: 'Context API',
|
|
8
|
+
version: '1.0.0',
|
|
9
|
+
description: 'Event-driven API for shift, clerk, and task management',
|
|
10
|
+
},
|
|
11
|
+
servers: [
|
|
12
|
+
{
|
|
13
|
+
url: 'http://localhost:3000',
|
|
14
|
+
description: 'Development server',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
url: process.env.API_URL || 'http://localhost:3000',
|
|
18
|
+
description: 'Production server',
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
components: {
|
|
22
|
+
securitySchemes: {
|
|
23
|
+
bearerAuth: {
|
|
24
|
+
type: 'http',
|
|
25
|
+
scheme: 'bearer',
|
|
26
|
+
bearerFormat: 'JWT',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
apis: ['./src/slices/**/routes.ts'],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const specs = swaggerJsdoc(options);
|