@xenterprises/fastify-xconfig 1.0.2 → 1.1.0
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 +171 -18
- package/dist/utils/randomUUID.d.ts +1 -1
- package/dist/xConfig.d.ts +1 -2
- package/dist/xConfig.js +2 -7
- package/dist/xConfig.js.map +1 -1
- package/package.json +23 -42
- package/server/app.js +78 -0
- package/src/auth/admin.js +181 -0
- package/src/auth/portal.js +177 -0
- package/src/integrations/cloudinary.js +98 -0
- package/src/integrations/geocode.js +43 -0
- package/src/integrations/prisma.js +30 -0
- package/src/integrations/sendgrid.js +58 -0
- package/src/integrations/twilio.js +146 -0
- package/src/lifecycle/xFastifyAfter.js +27 -0
- package/src/middleware/bugsnag.js +10 -0
- package/src/middleware/cors.js +10 -0
- package/src/middleware/fancyErrors.js +26 -0
- package/src/middleware/multipart.js +6 -0
- package/src/middleware/rateLimit.js +6 -0
- package/src/middleware/underPressure.js +6 -0
- package/src/utils/colorize.js +37 -0
- package/src/utils/cookie.js +5 -0
- package/src/utils/formatBytes.js +16 -0
- package/src/utils/health.js +126 -0
- package/src/utils/xEcho.js +12 -0
- package/src/utils/xSlugify.js +20 -0
- package/src/utils/xUUID.js +14 -0
- package/src/xConfig.js +117 -0
- package/test/index.js +17 -0
- package/tsconfig.json +9 -11
- package/xConfigReference.js +1526 -0
- package/xConfigWorkingList.js +720 -0
- package/.github/workflows/ci.yml +0 -19
- package/.taprc +0 -3
- package/example.ts +0 -13
- package/src/xConfig.ts +0 -20
- package/test/index.test-d.ts +0 -13
- package/test/index.test.js +0 -14
- package/test/xConfig.js +0 -115
- /package/{src → ts-reference}/integrations/cloudinary.ts +0 -0
- /package/{src → ts-reference}/integrations/prisma.ts +0 -0
- /package/{src → ts-reference}/integrations/sendgrid.ts +0 -0
- /package/{src → ts-reference}/integrations/stripe.ts +0 -0
- /package/{src → ts-reference}/integrations/twilio.ts +0 -0
- /package/{src → ts-reference}/middleware/bugsnag.ts +0 -0
- /package/{src → ts-reference}/middleware/cors.ts +0 -0
- /package/{src → ts-reference}/middleware/errorHandler.ts +0 -0
- /package/{src → ts-reference}/middleware/multipart.ts +0 -0
- /package/{src → ts-reference}/middleware/rateLimit.ts +0 -0
- /package/{src → ts-reference}/middleware/underPressure.ts +0 -0
- /package/{src → ts-reference}/utils/colorize.ts +0 -0
- /package/{src → ts-reference}/utils/formatBytes.ts +0 -0
- /package/{src → ts-reference}/utils/randomUUID.ts +0 -0
- /package/{src → ts-reference}/utils/statAsync.ts +0 -0
package/README.md
CHANGED
|
@@ -1,29 +1,182 @@
|
|
|
1
|
-
#
|
|
1
|
+
# xConfig - Fastify Configuration Plugin
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
/workflows/CI%20workflow/badge.svg)
|
|
3
|
+
**xConfig** is a Fastify configuration plugin designed for setting up middleware, services, and route handling.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
This plugin provides a comprehensive setup for configuring various services such as Prisma, SendGrid, Twilio, Cloudinary, CORS, rate limiting, authentication, and more within a Fastify instance. It centralizes configuration, allowing for easy customization and scaling.
|
|
7
6
|
|
|
8
|
-
##
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
## Key Features
|
|
8
|
+
|
|
9
|
+
- Handles CORS, rate-limiting, multipart handling, and error handling out of the box.
|
|
10
|
+
- Integrates with third-party services such as SendGrid, Twilio, Cloudinary, Stripe, and Bugsnag.
|
|
11
|
+
- Provides authentication setup for both Admin and User, with Stack Auth integration.
|
|
12
|
+
- Includes Prisma database connection and gracefully handles disconnecting on server shutdown.
|
|
13
|
+
- Adds health check routes and resource usage monitoring, with environment validation.
|
|
14
|
+
- Customizes route listing and applies various performance and security options.
|
|
12
15
|
|
|
13
16
|
## Usage
|
|
14
|
-
Require `__MY_PLUGIN__` and register.
|
|
15
|
-
```js
|
|
16
|
-
const fastify = require('fastify')()
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
This plugin should be registered in your Fastify instance with custom options provided for each service. It ensures proper initialization of services like SendGrid, Twilio, and Prisma.
|
|
19
|
+
|
|
20
|
+
### Example
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import Fastify from "fastify";
|
|
24
|
+
import xConfig from "./path/to/xConfig";
|
|
21
25
|
|
|
22
|
-
fastify
|
|
26
|
+
const fastify = Fastify();
|
|
27
|
+
fastify.register(xConfig, {
|
|
28
|
+
prisma: { active: true },
|
|
29
|
+
sendGrid: { apiKey: "your-sendgrid-api-key" },
|
|
30
|
+
twilio: {
|
|
31
|
+
accountSid: "your-account-sid",
|
|
32
|
+
authToken: "your-auth-token",
|
|
33
|
+
phoneNumber: "your-twilio-number",
|
|
34
|
+
},
|
|
35
|
+
cloudinary: {
|
|
36
|
+
cloudName: "your-cloud-name",
|
|
37
|
+
apiKey: "your-api-key",
|
|
38
|
+
apiSecret: "your-api-secret",
|
|
39
|
+
},
|
|
40
|
+
auth: {
|
|
41
|
+
excludedPaths: ["/public", "/portal/auth/register"],
|
|
42
|
+
admin: {
|
|
43
|
+
active: true,
|
|
44
|
+
stackProjectId: "your-admin-stack-project-id",
|
|
45
|
+
publishableKey: "your-admin-stack-publishable-key",
|
|
46
|
+
secretKey: "your-admin-stack-secret-key",
|
|
47
|
+
},
|
|
48
|
+
user: {
|
|
49
|
+
active: true,
|
|
50
|
+
stackProjectId: "your-user-stack-project-id",
|
|
51
|
+
publishableKey: "your-user-stack-publishable-key",
|
|
52
|
+
secretKey: "your-user-stack-secret-key",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
cors: { origin: ["https://your-frontend.com"], credentials: true },
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
fastify.listen({ port: 3000 });
|
|
23
59
|
```
|
|
24
60
|
|
|
25
|
-
##
|
|
61
|
+
## Parameters
|
|
62
|
+
|
|
63
|
+
- **prisma**: Prisma Client configuration (optional).
|
|
64
|
+
- **sendGrid**: SendGrid configuration for email services (optional).
|
|
65
|
+
- **twilio**: Twilio configuration for SMS services (optional).
|
|
66
|
+
- **cloudinary**: Cloudinary configuration for media upload services (optional).
|
|
67
|
+
- **auth**: Authentication configuration for Admin and User with Stack Auth integration (optional).
|
|
68
|
+
- **cors**: CORS configuration (optional).
|
|
69
|
+
- **rateLimit**: Rate-limiting options (optional).
|
|
70
|
+
- **multipart**: Multipart handling options for file uploads (optional).
|
|
71
|
+
- **bugsnag**: Bugsnag error reporting configuration (optional).
|
|
72
|
+
- **underPressure**: Under Pressure plugin options for monitoring and throttling under load (optional).
|
|
73
|
+
|
|
74
|
+
## Authentication
|
|
75
|
+
|
|
76
|
+
The plugin provides both Admin and User authentication with Stack Auth integration. It handles token verification, protected routes, and authentication endpoints.
|
|
77
|
+
|
|
78
|
+
### User Authentication
|
|
79
|
+
|
|
80
|
+
- `/portal/auth/login` - User login endpoint
|
|
81
|
+
- `/portal/auth/me` - Get current user information
|
|
82
|
+
- `/portal/auth/verify` - Verify a user token
|
|
83
|
+
|
|
84
|
+
### Admin Authentication
|
|
85
|
+
|
|
86
|
+
- `/admin/auth/login` - Admin login endpoint
|
|
87
|
+
- `/admin/auth/me` - Get current admin information
|
|
88
|
+
- `/admin/auth/verify` - Verify an admin token
|
|
89
|
+
|
|
90
|
+
### Stack Auth Integration
|
|
91
|
+
|
|
92
|
+
The plugin integrates with [Stack Auth](https://stack-auth.com) for secure authentication. You'll need to provide:
|
|
93
|
+
|
|
94
|
+
- `stackProjectId` - Your Stack Auth project ID
|
|
95
|
+
- `publishableKey` - Your Stack Auth publishable client key
|
|
96
|
+
- `secretKey` - Your Stack Auth secret server key
|
|
97
|
+
|
|
98
|
+
These can be provided via environment variables or in the plugin options.
|
|
99
|
+
|
|
100
|
+
## Health Check
|
|
26
101
|
|
|
27
|
-
|
|
102
|
+
The `/health` route provides a health check with details about uptime, memory usage, CPU load, database and Redis status, and environment variable validation.
|
|
28
103
|
|
|
29
|
-
|
|
104
|
+
## Services
|
|
105
|
+
|
|
106
|
+
- **Prisma**: Initializes Prisma Client and decorates the Fastify instance for database queries.
|
|
107
|
+
- **SendGrid**: Provides an email sending service through SendGrid, integrated with the Fastify instance.
|
|
108
|
+
- **Twilio**: Provides SMS sending and phone number validation services.
|
|
109
|
+
- **Cloudinary**: Handles file uploads to Cloudinary and deletion of media files.
|
|
110
|
+
- **Authentication**: Stack Auth integration for secure user and admin authentication.
|
|
111
|
+
|
|
112
|
+
## Hooks
|
|
113
|
+
|
|
114
|
+
- **onRequest**: Validates tokens for protected routes.
|
|
115
|
+
- **onClose**: Gracefully disconnects Prisma and other services on server shutdown.
|
|
116
|
+
|
|
117
|
+
## Error Handling
|
|
118
|
+
|
|
119
|
+
Enhanced error handling is enabled by default, showing detailed error messages during development. Bugsnag integration is optional for real-time error reporting.
|
|
120
|
+
|
|
121
|
+
## Route Listing
|
|
122
|
+
|
|
123
|
+
The plugin prints all routes after registration, color-coded by HTTP method, unless disabled.
|
|
124
|
+
|
|
125
|
+
## Dependencies
|
|
126
|
+
|
|
127
|
+
The following dependencies are used for various services and integrations:
|
|
128
|
+
|
|
129
|
+
- Prisma Client, SendGrid, Twilio, Cloudinary, Fastify CORS, Fastify Rate Limit, Fastify Multipart, Fastify Under Pressure, and Fastify Bugsnag.
|
|
130
|
+
|
|
131
|
+
## Environment Variables
|
|
132
|
+
|
|
133
|
+
### Required for Authentication
|
|
134
|
+
|
|
135
|
+
- `ADMIN_STACK_PROJECT_ID` - Stack Auth project ID for admin authentication
|
|
136
|
+
- `ADMIN_STACK_PUBLISHABLE_CLIENT_KEY` - Stack Auth publishable key for admin authentication
|
|
137
|
+
- `ADMIN_STACK_SECRET_SERVER_KEY` - Stack Auth secret key for admin authentication
|
|
138
|
+
- `USER_STACK_PROJECT_ID` - Stack Auth project ID for user authentication
|
|
139
|
+
- `USER_STACK_PUBLISHABLE_CLIENT_KEY` - Stack Auth publishable key for user authentication
|
|
140
|
+
- `USER_STACK_SECRET_SERVER_KEY` - Stack Auth secret key for user authentication
|
|
141
|
+
|
|
142
|
+
### Other Environment Variables
|
|
143
|
+
|
|
144
|
+
- `DATABASE_URL` - Required for Prisma Client
|
|
145
|
+
- `SENDGRID_API_KEY` - Required for SendGrid integration
|
|
146
|
+
- `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`, `TWILIO_PHONE_NUMBER` - Required for Twilio integration
|
|
147
|
+
- `CLOUDINARY_CLOUD_NAME`, `CLOUDINARY_API_KEY`, `CLOUDINARY_API_SECRET` - Required for Cloudinary integration
|
|
148
|
+
|
|
149
|
+
## Example Configuration
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
fastify.register(xConfig, {
|
|
153
|
+
prisma: { active: true },
|
|
154
|
+
sendGrid: { apiKey: "SG.your_api_key" },
|
|
155
|
+
twilio: {
|
|
156
|
+
accountSid: "ACxxxxxxxxxxxxxxxx",
|
|
157
|
+
authToken: "your_auth_token",
|
|
158
|
+
phoneNumber: "+1234567890",
|
|
159
|
+
},
|
|
160
|
+
cloudinary: {
|
|
161
|
+
cloudName: "your-cloud-name",
|
|
162
|
+
apiKey: "your-api-key",
|
|
163
|
+
apiSecret: "your-api-secret",
|
|
164
|
+
},
|
|
165
|
+
auth: {
|
|
166
|
+
excludedPaths: ["/public", "/portal/auth/login", "/portal/auth/register"],
|
|
167
|
+
admin: {
|
|
168
|
+
active: true,
|
|
169
|
+
stackProjectId: "your-admin-stack-project-id",
|
|
170
|
+
publishableKey: "your-admin-stack-publishable-key",
|
|
171
|
+
secretKey: "your-admin-stack-secret-key",
|
|
172
|
+
},
|
|
173
|
+
user: {
|
|
174
|
+
active: true,
|
|
175
|
+
stackProjectId: "your-user-stack-project-id",
|
|
176
|
+
publishableKey: "your-user-stack-publishable-key",
|
|
177
|
+
secretKey: "your-user-stack-secret-key",
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
cors: { origin: ["https://your-frontend.com"], credentials: true },
|
|
181
|
+
});
|
|
182
|
+
```
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const generateUUID:
|
|
1
|
+
export declare const generateUUID: any;
|
package/dist/xConfig.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
declare function xConfig(fastify: FastifyInstance, options: FastifyPluginOptions): Promise<void>;
|
|
1
|
+
declare function xConfig(fastify: any, options: any): Promise<void>;
|
|
3
2
|
declare const _default: typeof xConfig;
|
|
4
3
|
export default _default;
|
package/dist/xConfig.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
+
//src/xConfig.js
|
|
1
2
|
import fp from "fastify-plugin";
|
|
2
|
-
import { captureRoutes, printRoutes } from "./utils/colorize";
|
|
3
3
|
async function xConfig(fastify, options) {
|
|
4
|
-
|
|
5
|
-
const routes = captureRoutes(fastify);
|
|
6
|
-
// After all plugins are loaded, print the routes
|
|
7
|
-
fastify.ready(() => {
|
|
8
|
-
printRoutes(routes);
|
|
9
|
-
});
|
|
4
|
+
fastify.decorate("xEcho", () => "Hello from X Enterprises!");
|
|
10
5
|
}
|
|
11
6
|
export default fp(xConfig, {
|
|
12
7
|
name: "xConfig",
|
package/dist/xConfig.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xConfig.js","sourceRoot":"","sources":["../src/xConfig.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"xConfig.js","sourceRoot":"","sources":["../src/xConfig.ts"],"names":[],"mappings":"AAAA,gBAAgB;AAChB,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEhC,KAAK,UAAU,OAAO,CAAC,OAAO,EAAE,OAAO;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,2BAA2B,CAAC,CAAC;AAC/D,CAAC;AAED,eAAe,EAAE,CAAC,OAAO,EAAE;IACzB,IAAI,EAAE,SAAS;CAChB,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,67 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xenterprises/fastify-xconfig",
|
|
3
|
-
"version": "1.0.2",
|
|
4
|
-
"description": "Fastify configuration plugin for setting up middleware, services, and route handling.",
|
|
5
|
-
"main": "dist/xConfig.js",
|
|
6
|
-
"directories": {
|
|
7
|
-
"test": "test"
|
|
8
|
-
},
|
|
9
3
|
"type": "module",
|
|
4
|
+
"version": "1.1.0",
|
|
5
|
+
"description": "Fastify configuration plugin for setting up middleware, services, and route handling.",
|
|
6
|
+
"main": "src/xConfig.js",
|
|
10
7
|
"scripts": {
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"test": "node
|
|
14
|
-
"lint": "standard && npm run lint:typescript",
|
|
15
|
-
"lint:typescript": "ts-standard",
|
|
16
|
-
"test:typescript": "tsd",
|
|
17
|
-
"unit": "node --test"
|
|
8
|
+
"start": "fastify start -l info server/app.js",
|
|
9
|
+
"dev": "fastify start -w -l info -P server/app.js",
|
|
10
|
+
"test": "node --test test/**/*.test.js"
|
|
18
11
|
},
|
|
19
|
-
"keywords": [],
|
|
20
12
|
"author": "Tim Mushen",
|
|
21
|
-
"license": "
|
|
22
|
-
"
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/node": "^22.7.4",
|
|
16
|
+
"c8": "^9.0.0",
|
|
17
|
+
"fastify": "^4.28.1",
|
|
18
|
+
"fastify-plugin": "^4.0.0",
|
|
19
|
+
"fastify-tsconfig": "^2.0.0",
|
|
20
|
+
"typescript": "^5.6.3"
|
|
21
|
+
},
|
|
23
22
|
"dependencies": {
|
|
24
|
-
"@fastify/autoload": "^6.0.
|
|
23
|
+
"@fastify/autoload": "^6.0.2",
|
|
25
24
|
"@fastify/cookie": "^9.4.0",
|
|
26
25
|
"@fastify/cors": "^9.0.1",
|
|
27
|
-
"@fastify/jwt": "^8.0.1",
|
|
28
26
|
"@fastify/multipart": "^8.3.0",
|
|
29
27
|
"@fastify/rate-limit": "^9.1.0",
|
|
30
28
|
"@fastify/sensible": "^5.6.0",
|
|
31
29
|
"@fastify/under-pressure": "^9.0.1",
|
|
32
|
-
"@prisma/client": "^
|
|
30
|
+
"@prisma/client": "^6.3.1",
|
|
33
31
|
"@sendgrid/client": "^8.1.3",
|
|
34
32
|
"@sendgrid/mail": "^8.1.3",
|
|
35
33
|
"bcrypt": "^5.1.1",
|
|
34
|
+
"check-disk-space": "^3.4.0",
|
|
35
|
+
"fastify": "^4.28.1",
|
|
36
36
|
"fastify-bugsnag": "^4.1.4",
|
|
37
37
|
"fastify-cli": "^6.2.1",
|
|
38
38
|
"fastify-cloudinary": "^2.0.0",
|
|
39
39
|
"fastify-list-routes": "^1.0.0",
|
|
40
40
|
"fastify-multipart": "^5.4.0",
|
|
41
|
+
"fastify-plugin": "^4.0.0",
|
|
41
42
|
"fastify-rate-limit": "^5.9.0",
|
|
43
|
+
"jose": "^6.0.11",
|
|
42
44
|
"stripe": "^16.12.0",
|
|
43
45
|
"twilio": "^5.3.2",
|
|
44
46
|
"uncrypto": "^0.1.3"
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
"@types/node": "^20.17.0",
|
|
48
|
-
"fastify": "^4.28.1",
|
|
49
|
-
"fastify-plugin": "^4.0.0",
|
|
50
|
-
"fastify-tsconfig": "^2.0.0",
|
|
51
|
-
"standard": "^17.0.0",
|
|
52
|
-
"ts-standard": "^12.0.1",
|
|
53
|
-
"tsd": "^0.31.0",
|
|
54
|
-
"typescript": "^5.6.3"
|
|
55
|
-
},
|
|
56
|
-
"tsd": {
|
|
57
|
-
"directory": "test"
|
|
58
|
-
},
|
|
59
|
-
"repository": {
|
|
60
|
-
"type": "git",
|
|
61
|
-
"url": "git+https://github.com/xenterprises/module-fastify-xconfig.git"
|
|
62
|
-
},
|
|
63
|
-
"bugs": {
|
|
64
|
-
"url": "https://github.com/xenterprises/module-fastify-xconfig/issues"
|
|
65
|
-
},
|
|
66
|
-
"homepage": "https://github.com/xenterprises/module-fastify-xconfig#readme"
|
|
67
|
-
}
|
|
47
|
+
}
|
|
48
|
+
}
|
package/server/app.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// server/app.js
|
|
2
|
+
import Fastify from 'fastify';
|
|
3
|
+
import xConfig from '../src/xConfig.js'; // Import your plugin correctly
|
|
4
|
+
|
|
5
|
+
const fastify = Fastify();
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export default async function (fastify, opts) {
|
|
9
|
+
fastify.register(xConfig, {
|
|
10
|
+
professional: false,
|
|
11
|
+
fancyErrors: true,
|
|
12
|
+
prisma: {
|
|
13
|
+
active: false,
|
|
14
|
+
},
|
|
15
|
+
bugsnag: {
|
|
16
|
+
apiKey: process.env.BUGSNAG_API_KEY
|
|
17
|
+
},
|
|
18
|
+
rateLimit: {
|
|
19
|
+
max: process.env.RATE_LIMIT_MAX || 100,
|
|
20
|
+
timeWindow: process.env.RATE_LIMIT_TIME_WINDOW || '1 minute'
|
|
21
|
+
},
|
|
22
|
+
stripe: {
|
|
23
|
+
active: false,
|
|
24
|
+
apiKey: process.env.STRIPE_API_KEY
|
|
25
|
+
},
|
|
26
|
+
sendGrid: {
|
|
27
|
+
active: true,
|
|
28
|
+
apiKey: process.env.SENDGRID_API_KEY,
|
|
29
|
+
apiKeyEmailValidation: process.env.SENDGRID_API_EMAIL_VALIDATION_KEY,
|
|
30
|
+
fromEmail: process.env.SENDGRID_FROM_EMAIL || 'ops@getx.io',
|
|
31
|
+
},
|
|
32
|
+
twilio: {
|
|
33
|
+
active: true,
|
|
34
|
+
accountSid: process.env.TWILIO_ACCOUNT_SID,
|
|
35
|
+
authToken: process.env.TWILIO_AUTH_TOKEN,
|
|
36
|
+
phoneNumber: process.env.TWILIO_PHONE_NUMBER
|
|
37
|
+
},
|
|
38
|
+
cloudinary: {
|
|
39
|
+
active: false,
|
|
40
|
+
cloudName: process.env.CLOUDINARY_CLOUD_NAME,
|
|
41
|
+
apiKey: process.env.CLOUDINARY_API_KEY,
|
|
42
|
+
apiSecret: process.env.CLOUDINARY_API_SECRET,
|
|
43
|
+
basePath: process.env.CLOUDINARY_BASE_PATH || 'basepath'
|
|
44
|
+
},
|
|
45
|
+
cors: {
|
|
46
|
+
active: true,
|
|
47
|
+
origin: process.env.CORS_ORIGIN || ['http://localhost:3000', 'https://app.bandmate.io'],
|
|
48
|
+
credentials: true
|
|
49
|
+
},
|
|
50
|
+
auth: {
|
|
51
|
+
excludedPaths: ['/public', '/portal/auth/register'],
|
|
52
|
+
admin: {
|
|
53
|
+
active: true,
|
|
54
|
+
secret: process.env.ADMIN_STACK_PROJECT_ID,
|
|
55
|
+
},
|
|
56
|
+
user: {
|
|
57
|
+
active: true,
|
|
58
|
+
portalStackProjectId: process.env.STACK_PROJECT_ID,
|
|
59
|
+
me: {
|
|
60
|
+
isOnboarded: true
|
|
61
|
+
},
|
|
62
|
+
registerEmail: {
|
|
63
|
+
subject: 'Welcome to Bandmate!',
|
|
64
|
+
templateId: ''
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
geocode: {
|
|
69
|
+
active: true,
|
|
70
|
+
apiKey: process.env.GEOCODIO_API_KEY
|
|
71
|
+
}
|
|
72
|
+
}); // Register the default export, which should be a function
|
|
73
|
+
fastify.get('/', async (request, reply) => {
|
|
74
|
+
console.log(fastify.xEcho())
|
|
75
|
+
return { status: fastify.xEcho() }
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import * as jose from "jose";
|
|
2
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
3
|
+
|
|
4
|
+
export async function setupAdminAuth(fastify, options) {
|
|
5
|
+
if (options.admin?.active !== false) {
|
|
6
|
+
// Get configuration
|
|
7
|
+
const projectId = options.admin.stackProjectId || process.env.ADMIN_STACK_PROJECT_ID;
|
|
8
|
+
const publishableKey = options.admin.publishableKey || process.env.ADMIN_STACK_PUBLISHABLE_CLIENT_KEY;
|
|
9
|
+
const secretKey = options.admin.secretKey || process.env.ADMIN_STACK_SECRET_SERVER_KEY;
|
|
10
|
+
|
|
11
|
+
if (!projectId) {
|
|
12
|
+
throw new Error("Stack Auth admin project ID is required");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!publishableKey || !secretKey) {
|
|
16
|
+
throw new Error("Stack Auth admin publishable and secret keys are required");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const adminExcludedPaths = options.admin.excludedPaths || [
|
|
20
|
+
"/admin/auth/login",
|
|
21
|
+
"/admin/auth/logout",
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
// Base URL for Stack Auth API
|
|
25
|
+
const stackAuthBaseUrl = `https://api.stack-auth.com/api/v1`;
|
|
26
|
+
const jwksUrl = `${stackAuthBaseUrl}/projects/${projectId}/.well-known/jwks.json`;
|
|
27
|
+
|
|
28
|
+
// Create JWKS for token verification
|
|
29
|
+
const jwks = jose.createRemoteJWKSet(new URL(jwksUrl));
|
|
30
|
+
|
|
31
|
+
// Helper for Stack Auth API headers
|
|
32
|
+
fastify.decorate('getAdminStackAuthHeaders', function (accessType = "server") {
|
|
33
|
+
const headers = {
|
|
34
|
+
'Content-Type': 'application/json',
|
|
35
|
+
'Accept': 'application/json',
|
|
36
|
+
'X-Stack-Access-Type': accessType,
|
|
37
|
+
'X-Stack-Project-Id': projectId,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (accessType === "client") {
|
|
41
|
+
headers['X-Stack-Publishable-Client-Key'] = publishableKey;
|
|
42
|
+
} else if (accessType === "server") {
|
|
43
|
+
headers['X-Stack-Secret-Server-Key'] = secretKey;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return headers;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// JWT verification helper
|
|
50
|
+
fastify.decorate('verifyAdminStackJWT', async function (accessToken) {
|
|
51
|
+
try {
|
|
52
|
+
// Add validation to ensure accessToken is a valid string
|
|
53
|
+
if (!accessToken || typeof accessToken !== 'string') {
|
|
54
|
+
fastify.log.error('Invalid admin token format: token must be a non-empty string');
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const { payload } = await jose.jwtVerify(accessToken, jwks);
|
|
59
|
+
|
|
60
|
+
// Verify the payload has expected properties
|
|
61
|
+
if (!payload || !payload.sub) {
|
|
62
|
+
fastify.log.error('Invalid admin token payload: missing required claims');
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return payload;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
fastify.log.error(error);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Admin authentication hook
|
|
74
|
+
fastify.addHook("onRequest", async (request, reply) => {
|
|
75
|
+
const url = request.url;
|
|
76
|
+
|
|
77
|
+
// Skip authentication for excluded paths
|
|
78
|
+
if (adminExcludedPaths.some((path) => url.startsWith(path))) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (url.startsWith("/admin")) {
|
|
83
|
+
try {
|
|
84
|
+
// Get token from header
|
|
85
|
+
const authHeader = request.headers.authorization;
|
|
86
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
87
|
+
return reply.code(401).send({ error: "Admin access token required" });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Verify token
|
|
91
|
+
const token = authHeader.slice(7);
|
|
92
|
+
const payload = await fastify.verifyAdminStackJWT(token);
|
|
93
|
+
|
|
94
|
+
if (!payload) {
|
|
95
|
+
return reply.code(401).send({ error: "Invalid admin token" });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Set admin info on request
|
|
99
|
+
request.admin = payload;
|
|
100
|
+
request.adminAuth = { id: payload.sub };
|
|
101
|
+
} catch (err) {
|
|
102
|
+
return reply.code(401).send({ error: "Admin authentication failed" });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Admin login endpoint
|
|
108
|
+
fastify.post("/admin/auth/login", async (req, reply) => {
|
|
109
|
+
try {
|
|
110
|
+
const { email, password } = req.body;
|
|
111
|
+
|
|
112
|
+
if (!email || !password) {
|
|
113
|
+
return reply.code(400).send({ error: "Email and password required" });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Call Stack Auth API for password sign-in
|
|
117
|
+
const response = await fetch(
|
|
118
|
+
`${stackAuthBaseUrl}/auth/password/sign-in`,
|
|
119
|
+
{
|
|
120
|
+
method: 'POST',
|
|
121
|
+
headers: fastify.getAdminStackAuthHeaders("server"),
|
|
122
|
+
body: JSON.stringify({ email, password })
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const data = await response.json().catch(() => ({}));
|
|
127
|
+
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
return reply.code(response.status || 400).send({
|
|
130
|
+
error: data.message || "Authentication failed"
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Check if we have the expected response properties
|
|
135
|
+
if (!data.access_token || !data.refresh_token || !data.user_id) {
|
|
136
|
+
return reply.code(500).send({ error: "Invalid response from authentication service" });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Verify token
|
|
140
|
+
const payload = await fastify.verifyAdminStackJWT(data.access_token);
|
|
141
|
+
if (!payload) {
|
|
142
|
+
return reply.code(401).send({ error: "Invalid token received" });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
reply.send({
|
|
146
|
+
accessToken: data.access_token,
|
|
147
|
+
refreshToken: data.refresh_token,
|
|
148
|
+
admin: payload,
|
|
149
|
+
adminId: data.user_id
|
|
150
|
+
});
|
|
151
|
+
} catch (err) {
|
|
152
|
+
fastify.log.error(err);
|
|
153
|
+
reply.code(500).send({ error: "Internal server error" });
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Admin info endpoint
|
|
158
|
+
fastify.get("/admin/auth/me", async (req, reply) => {
|
|
159
|
+
reply.send(req.admin || { authenticated: false });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Token verification endpoint
|
|
163
|
+
fastify.post("/admin/auth/verify", async (req, reply) => {
|
|
164
|
+
const { token } = req.body;
|
|
165
|
+
|
|
166
|
+
if (!token) {
|
|
167
|
+
return reply.code(400).send({ error: "Token required" });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const payload = await fastify.verifyAdminStackJWT(token);
|
|
171
|
+
|
|
172
|
+
if (!payload) {
|
|
173
|
+
return reply.code(401).send({ valid: false });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
reply.send({ valid: true, admin: payload });
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
console.info(" ✅ Auth Admin Enabled");
|
|
180
|
+
}
|
|
181
|
+
}
|