@donotdev/functions 0.0.6 → 0.0.7
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/lib/firebase/index.js +8026 -32339
- package/lib/firebase/index.js.map +4 -4
- package/lib/shared/index.js +5973 -31507
- package/lib/shared/index.js.map +4 -4
- package/lib/vercel/api/index.js +4173 -30252
- package/lib/vercel/api/index.js.map +4 -4
- package/package.json +3 -3
- package/src/firebase/auth/getCustomClaims.ts +2 -1
- package/src/firebase/auth/getUserAuthStatus.ts +2 -1
- package/src/firebase/auth/removeCustomClaims.ts +2 -1
- package/src/firebase/auth/setCustomClaims.ts +3 -1
- package/src/firebase/baseFunction.ts +167 -65
- package/src/firebase/billing/cancelSubscription.ts +2 -1
- package/src/firebase/billing/changePlan.ts +1 -1
- package/src/firebase/billing/createCheckoutSession.ts +2 -2
- package/src/firebase/billing/createCustomerPortal.ts +2 -1
- package/src/firebase/billing/refreshSubscriptionStatus.ts +1 -1
- package/src/firebase/billing/webhookHandler.ts +3 -1
- package/src/firebase/config/constants.ts +12 -2
- package/src/firebase/crud/aggregate.ts +2 -2
- package/src/firebase/crud/create.ts +10 -12
- package/src/firebase/crud/delete.ts +9 -11
- package/src/firebase/crud/get.ts +21 -11
- package/src/firebase/crud/list.ts +80 -25
- package/src/firebase/crud/update.ts +9 -11
- package/src/firebase/helpers/githubAccessHelper.ts +2 -1
- package/src/firebase/helpers/githubTeamAccessHelper.ts +2 -1
- package/src/firebase/index.ts +7 -0
- package/src/firebase/oauth/disconnect.ts +1 -1
- package/src/firebase/oauth/exchangeToken.ts +4 -4
- package/src/firebase/oauth/getConnections.ts +1 -1
- package/src/firebase/oauth/githubAccess.ts +3 -2
- package/src/firebase/oauth/refreshToken.ts +4 -4
- package/src/firebase/registerCrudFunctions.ts +127 -0
- package/src/firebase/scheduled/checkExpiredSubscriptions.ts +3 -2
- package/src/shared/billing/webhookHandler.ts +1 -1
- package/src/shared/errorHandling.ts +1 -1
- package/src/shared/utils/external/subscription.ts +4 -5
- package/src/shared/utils/firebaseHelpers.ts +3 -1
- package/src/shared/utils/internal/idempotency.ts +2 -1
- package/src/shared/utils/internal/monitoring.ts +2 -1
- package/src/shared/utils/internal/rateLimiter.ts +2 -1
- package/src/shared/utils.ts +56 -7
- package/src/vercel/api/billing/cancel.ts +2 -1
- package/src/vercel/api/billing/change-plan.ts +1 -1
- package/src/vercel/api/billing/create-checkout-session.ts +2 -2
- package/src/vercel/api/billing/customer-portal.ts +2 -1
- package/src/vercel/api/billing/refresh-subscription-status.ts +1 -1
- package/src/vercel/api/crud/create.ts +2 -3
- package/src/vercel/api/crud/delete.ts +0 -1
- package/src/vercel/api/crud/get.ts +2 -3
- package/src/vercel/api/crud/list.ts +2 -3
- package/src/vercel/api/crud/update.ts +2 -3
- package/src/vercel/api/oauth/check-github-access.ts +1 -1
- package/src/vercel/api/oauth/grant-github-access.ts +1 -1
- package/src/vercel/api/oauth/revoke-github-access.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@donotdev/functions",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Backend functions for DoNotDev Framework - Firebase, Vercel, and platform-agnostic implementations for auth, billing, CRUD, and OAuth",
|
|
6
6
|
"main": "./lib/firebase/index.js",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"serve": "firebase emulators:start --only functions"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@donotdev/core": "^0.0.
|
|
46
|
-
"@donotdev/firebase": "^0.0.
|
|
45
|
+
"@donotdev/core": "^0.0.14",
|
|
46
|
+
"@donotdev/firebase": "^0.0.6"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"@sentry/node": "^10.33.0",
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
* @author AMBROISE PARK Consulting
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
|
|
13
12
|
import * as v from 'valibot';
|
|
14
13
|
|
|
14
|
+
import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
|
|
15
|
+
|
|
15
16
|
import { createBaseFunction } from '../baseFunction.js';
|
|
16
17
|
import { AUTH_CONFIG } from '../config/constants.js';
|
|
17
18
|
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
* @author AMBROISE PARK Consulting
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
|
|
13
12
|
import * as v from 'valibot';
|
|
14
13
|
|
|
14
|
+
import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
|
|
15
|
+
|
|
15
16
|
import { createBaseFunction } from '../baseFunction.js';
|
|
16
17
|
import { AUTH_CONFIG } from '../config/constants.js';
|
|
17
18
|
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
* @author AMBROISE PARK Consulting
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
|
|
13
12
|
import * as v from 'valibot';
|
|
14
13
|
|
|
14
|
+
import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
|
|
15
|
+
|
|
15
16
|
import { createBaseFunction } from '../baseFunction.js';
|
|
16
17
|
import { AUTH_CONFIG } from '../config/constants.js';
|
|
17
18
|
|
|
@@ -9,11 +9,13 @@
|
|
|
9
9
|
* @author AMBROISE PARK Consulting
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import * as v from 'valibot';
|
|
13
|
+
|
|
12
14
|
import {
|
|
13
15
|
getFirebaseAdminAuth,
|
|
14
16
|
getFirebaseAdminFirestore,
|
|
15
17
|
} from '@donotdev/firebase/server';
|
|
16
|
-
|
|
18
|
+
|
|
17
19
|
import { createBaseFunction } from '../baseFunction.js';
|
|
18
20
|
import { AUTH_CONFIG } from '../config/constants.js';
|
|
19
21
|
|
|
@@ -4,28 +4,90 @@
|
|
|
4
4
|
* @fileoverview Base Firebase function that handles all common concerns
|
|
5
5
|
* @description Rate limiting, monitoring, authentication, validation - all in one place
|
|
6
6
|
*
|
|
7
|
-
* @version 0.0.
|
|
7
|
+
* @version 0.0.2
|
|
8
8
|
* @since 0.0.1
|
|
9
9
|
* @author AMBROISE PARK Consulting
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { logger } from 'firebase-functions/v2';
|
|
13
|
-
import { onCall
|
|
13
|
+
import { onCall } from 'firebase-functions/v2/https';
|
|
14
14
|
import * as v from 'valibot';
|
|
15
15
|
|
|
16
|
+
import { hasRoleAccess } from '@donotdev/core/server';
|
|
17
|
+
import type { UserRole } from '@donotdev/core/server';
|
|
18
|
+
|
|
16
19
|
import { handleError } from '../shared/errorHandling.js';
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
import { assertAuthenticated, getUserRole } from '../shared/utils.js';
|
|
21
|
+
|
|
22
|
+
import type { CallableRequest } from 'firebase-functions/v2/https';
|
|
23
|
+
|
|
24
|
+
// Optional monitoring imports - only used when enabled
|
|
25
|
+
// Lazy loaded to avoid unnecessary Firestore operations
|
|
26
|
+
let checkRateLimitWithFirestore:
|
|
27
|
+
| typeof import('../shared/utils/internal/rateLimiter.js').checkRateLimitWithFirestore
|
|
28
|
+
| null = null;
|
|
29
|
+
let DEFAULT_RATE_LIMITS:
|
|
30
|
+
| typeof import('../shared/utils/internal/rateLimiter.js').DEFAULT_RATE_LIMITS
|
|
31
|
+
| null = null;
|
|
32
|
+
let recordPaymentMetrics:
|
|
33
|
+
| typeof import('../shared/utils/internal/monitoring.js').recordPaymentMetrics
|
|
34
|
+
| null = null;
|
|
35
|
+
|
|
36
|
+
async function loadRateLimiter() {
|
|
37
|
+
if (!checkRateLimitWithFirestore) {
|
|
38
|
+
const mod = await import('../shared/utils/internal/rateLimiter.js');
|
|
39
|
+
checkRateLimitWithFirestore = mod.checkRateLimitWithFirestore;
|
|
40
|
+
DEFAULT_RATE_LIMITS = mod.DEFAULT_RATE_LIMITS;
|
|
41
|
+
}
|
|
42
|
+
return { checkRateLimitWithFirestore, DEFAULT_RATE_LIMITS };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function loadMonitoring() {
|
|
46
|
+
if (!recordPaymentMetrics) {
|
|
47
|
+
const mod = await import('../shared/utils/internal/monitoring.js');
|
|
48
|
+
recordPaymentMetrics = mod.recordPaymentMetrics;
|
|
49
|
+
}
|
|
50
|
+
return recordPaymentMetrics;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Extract client IP from Firebase callable request
|
|
55
|
+
* Handles proxied requests (X-Forwarded-For header)
|
|
56
|
+
*/
|
|
57
|
+
function getClientIp(request: CallableRequest<unknown>): string {
|
|
58
|
+
// Try X-Forwarded-For first (common for proxied requests)
|
|
59
|
+
const forwardedFor = request.rawRequest.headers['x-forwarded-for'];
|
|
60
|
+
if (forwardedFor) {
|
|
61
|
+
// Take the first IP (client IP) from the comma-separated list
|
|
62
|
+
let ips: string | string[] | undefined;
|
|
63
|
+
if (Array.isArray(forwardedFor)) {
|
|
64
|
+
ips = forwardedFor.length > 0 ? forwardedFor[0] : undefined;
|
|
65
|
+
} else {
|
|
66
|
+
ips = forwardedFor;
|
|
67
|
+
}
|
|
68
|
+
if (ips && typeof ips === 'string') {
|
|
69
|
+
const firstIp = ips.split(',')[0];
|
|
70
|
+
return firstIp ? firstIp.trim() : 'unknown';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Fallback to raw IP
|
|
75
|
+
const rawIp =
|
|
76
|
+
request.rawRequest.ip || request.rawRequest.socket?.remoteAddress;
|
|
77
|
+
return rawIp || 'unknown';
|
|
78
|
+
}
|
|
23
79
|
|
|
24
80
|
/**
|
|
25
81
|
* Base Firebase function that handles all common concerns
|
|
26
82
|
* Users just provide their business logic
|
|
27
83
|
*
|
|
28
|
-
* @
|
|
84
|
+
* @param config - Firebase function config (region, memory, etc.)
|
|
85
|
+
* @param schema - Valibot schema for request validation
|
|
86
|
+
* @param operation - Operation name for logging/metrics
|
|
87
|
+
* @param businessLogic - The actual business logic to execute
|
|
88
|
+
* @param requiredRole - Minimum role required (default: 'user' for backwards compatibility)
|
|
89
|
+
*
|
|
90
|
+
* @version 0.0.2
|
|
29
91
|
* @since 0.0.1
|
|
30
92
|
* @author AMBROISE PARK Consulting
|
|
31
93
|
*/
|
|
@@ -37,9 +99,11 @@ export function createBaseFunction<TRequest, TResponse>(
|
|
|
37
99
|
data: TRequest,
|
|
38
100
|
context: {
|
|
39
101
|
uid: string;
|
|
102
|
+
userRole: UserRole;
|
|
40
103
|
request: CallableRequest<TRequest>;
|
|
41
104
|
}
|
|
42
|
-
) => Promise<TResponse
|
|
105
|
+
) => Promise<TResponse>,
|
|
106
|
+
requiredRole: UserRole = 'user'
|
|
43
107
|
) {
|
|
44
108
|
// Validate schema at function creation time (framework-level robustness)
|
|
45
109
|
if (!schema) {
|
|
@@ -110,21 +174,14 @@ export function createBaseFunction<TRequest, TResponse>(
|
|
|
110
174
|
`Schema is undefined for ${operation} - this indicates a bundling/import issue`
|
|
111
175
|
);
|
|
112
176
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
hasAuth: !!request.auth,
|
|
118
|
-
userId: request.auth?.uid,
|
|
119
|
-
});
|
|
120
|
-
throw new Error(
|
|
121
|
-
`Request data is required for ${operation} but was ${request.data === null ? 'null' : 'undefined'}`
|
|
122
|
-
);
|
|
123
|
-
}
|
|
177
|
+
|
|
178
|
+
// Normalize undefined/null to empty object for validation
|
|
179
|
+
// This allows callable functions with no parameters to work correctly
|
|
180
|
+
const requestData = request.data ?? {};
|
|
124
181
|
|
|
125
182
|
let validatedData: TRequest;
|
|
126
183
|
try {
|
|
127
|
-
validatedData = v.parse(schema,
|
|
184
|
+
validatedData = v.parse(schema, requestData);
|
|
128
185
|
} catch (parseError) {
|
|
129
186
|
logger.error(`Schema validation failed for ${operation}`, {
|
|
130
187
|
error:
|
|
@@ -136,57 +193,102 @@ export function createBaseFunction<TRequest, TResponse>(
|
|
|
136
193
|
throw parseError;
|
|
137
194
|
}
|
|
138
195
|
|
|
139
|
-
//
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
196
|
+
// Get user role from auth context
|
|
197
|
+
const userRole = getUserRole(request.auth);
|
|
198
|
+
let uid: string;
|
|
199
|
+
|
|
200
|
+
// Role-based access control
|
|
201
|
+
if (requiredRole === 'guest') {
|
|
202
|
+
// Guest access: no authentication required
|
|
203
|
+
// Use 'guest' as UID for unauthenticated users
|
|
204
|
+
uid = request.auth?.uid || 'guest';
|
|
205
|
+
} else {
|
|
206
|
+
// Non-guest access: require authentication
|
|
207
|
+
uid = assertAuthenticated(request.auth);
|
|
208
|
+
|
|
209
|
+
// Verify user has required role level
|
|
210
|
+
if (!hasRoleAccess(userRole, requiredRole)) {
|
|
211
|
+
logger.warn(`Insufficient role for ${operation}`, {
|
|
212
|
+
userId: uid,
|
|
213
|
+
userRole,
|
|
214
|
+
requiredRole,
|
|
215
|
+
});
|
|
216
|
+
throw new Error(
|
|
217
|
+
`Access denied. Required role: ${requiredRole}, your role: ${userRole}`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Rate limiting (only if enabled via ENABLE_RATE_LIMITING env var)
|
|
223
|
+
if (process.env.ENABLE_RATE_LIMITING === 'true') {
|
|
224
|
+
const {
|
|
225
|
+
checkRateLimitWithFirestore: checkLimit,
|
|
226
|
+
DEFAULT_RATE_LIMITS: limits,
|
|
227
|
+
} = await loadRateLimiter();
|
|
228
|
+
|
|
229
|
+
// Use IP-based key for guest operations, UID-based for authenticated
|
|
230
|
+
const rateLimitIdentifier =
|
|
231
|
+
requiredRole === 'guest' && uid === 'guest'
|
|
232
|
+
? `ip_${getClientIp(request)}`
|
|
233
|
+
: `uid_${uid}`;
|
|
234
|
+
const rateLimitKey = `${operation}_${rateLimitIdentifier}`;
|
|
235
|
+
const rateLimitConfig = (limits as any)[operation] || limits!.api;
|
|
236
|
+
const rateLimitResult = await checkLimit!(
|
|
237
|
+
rateLimitKey,
|
|
238
|
+
rateLimitConfig
|
|
160
239
|
);
|
|
240
|
+
|
|
241
|
+
if (!rateLimitResult.allowed) {
|
|
242
|
+
logger.warn(`Rate limit exceeded for ${operation}`, {
|
|
243
|
+
identifier: rateLimitIdentifier,
|
|
244
|
+
remaining: rateLimitResult.remaining,
|
|
245
|
+
resetAt: rateLimitResult.resetAt,
|
|
246
|
+
});
|
|
247
|
+
throw new Error(
|
|
248
|
+
`Rate limit exceeded. Try again in ${rateLimitResult.blockRemainingSeconds} seconds.`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
161
251
|
}
|
|
162
252
|
|
|
163
253
|
// Call user's business logic
|
|
164
|
-
const result = await businessLogic(validatedData, {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
operation,
|
|
169
|
-
userId: uid,
|
|
170
|
-
status: 'success',
|
|
171
|
-
timestamp: new Date().toISOString(),
|
|
172
|
-
metadata: {
|
|
173
|
-
requestId: request.rawRequest.headers['x-request-id'] || 'unknown',
|
|
174
|
-
},
|
|
254
|
+
const result = await businessLogic(validatedData, {
|
|
255
|
+
uid,
|
|
256
|
+
userRole,
|
|
257
|
+
request,
|
|
175
258
|
});
|
|
176
259
|
|
|
260
|
+
// Record metrics (only if enabled via ENABLE_METRICS env var)
|
|
261
|
+
if (process.env.ENABLE_METRICS === 'true') {
|
|
262
|
+
const recordMetrics = await loadMonitoring();
|
|
263
|
+
await recordMetrics!({
|
|
264
|
+
operation,
|
|
265
|
+
userId: uid,
|
|
266
|
+
status: 'success',
|
|
267
|
+
timestamp: new Date().toISOString(),
|
|
268
|
+
metadata: {
|
|
269
|
+
requestId:
|
|
270
|
+
request.rawRequest.headers['x-request-id'] || 'unknown',
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
177
275
|
return result;
|
|
178
276
|
} catch (error) {
|
|
179
|
-
// Record error metrics
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
277
|
+
// Record error metrics (only if enabled)
|
|
278
|
+
if (process.env.ENABLE_METRICS === 'true') {
|
|
279
|
+
const recordMetrics = await loadMonitoring();
|
|
280
|
+
await recordMetrics!({
|
|
281
|
+
operation,
|
|
282
|
+
userId: request.auth?.uid || 'anonymous',
|
|
283
|
+
status: 'failed' as const,
|
|
284
|
+
timestamp: new Date().toISOString(),
|
|
285
|
+
metadata: {
|
|
286
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
287
|
+
requestId:
|
|
288
|
+
request.rawRequest.headers['x-request-id'] || 'unknown',
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
}
|
|
190
292
|
|
|
191
293
|
throw handleError(error);
|
|
192
294
|
}
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
* @author AMBROISE PARK Consulting
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
|
|
13
12
|
import * as v from 'valibot';
|
|
14
13
|
|
|
14
|
+
import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
|
|
15
|
+
|
|
15
16
|
import { cancelUserSubscription } from '../../shared/billing/helpers/subscriptionManagement.js';
|
|
16
17
|
import { createBaseFunction } from '../baseFunction.js';
|
|
17
18
|
import { STRIPE_CONFIG } from '../config/constants.js';
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
* @author AMBROISE PARK Consulting
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
|
|
13
12
|
import Stripe from 'stripe';
|
|
14
13
|
import * as v from 'valibot';
|
|
15
14
|
|
|
16
15
|
import type { StripeBackConfig } from '@donotdev/core/server';
|
|
16
|
+
import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
|
|
17
17
|
|
|
18
18
|
import { updateUserSubscription } from '../../shared/billing/helpers/updateUserSubscription.js';
|
|
19
19
|
import { handleError } from '../../shared/errorHandling.js';
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
* @author AMBROISE PARK Consulting
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
|
|
13
12
|
import { logger } from 'firebase-functions/v2';
|
|
14
13
|
|
|
15
14
|
import type {
|
|
@@ -18,6 +17,7 @@ import type {
|
|
|
18
17
|
} from '@donotdev/core/server';
|
|
19
18
|
import { STRIPE_MODES } from '@donotdev/core/server';
|
|
20
19
|
import { CreateCheckoutSessionRequestSchema } from '@donotdev/core/server';
|
|
20
|
+
import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
|
|
21
21
|
|
|
22
22
|
import { handleError } from '../../shared/errorHandling.js';
|
|
23
23
|
import {
|
|
@@ -25,10 +25,10 @@ import {
|
|
|
25
25
|
validateMetadata,
|
|
26
26
|
} from '../../shared/utils/validation.js';
|
|
27
27
|
import { stripe, validateStripeEnvironment } from '../../shared/utils.js';
|
|
28
|
+
import { initStripe } from '../../shared/utils.js'; // ✅ IMPORT INIT
|
|
28
29
|
import { createBaseFunction } from '../baseFunction.js';
|
|
29
30
|
import { STRIPE_CONFIG } from '../config/constants.js';
|
|
30
31
|
import { stripeSecretKey } from '../config/secrets.js'; // ✅ IMPORT SECRET
|
|
31
|
-
import { initStripe } from '../../shared/utils.js'; // ✅ IMPORT INIT
|
|
32
32
|
|
|
33
33
|
import type {
|
|
34
34
|
CallableFunction,
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
* @author AMBROISE PARK Consulting
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
|
|
13
12
|
import * as v from 'valibot';
|
|
14
13
|
|
|
14
|
+
import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
|
|
15
|
+
|
|
15
16
|
import { handleError } from '../../shared/errorHandling.js';
|
|
16
17
|
import {
|
|
17
18
|
stripe,
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
* @author AMBROISE PARK Consulting
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
|
|
13
12
|
import Stripe from 'stripe';
|
|
14
13
|
import * as v from 'valibot';
|
|
15
14
|
|
|
16
15
|
import type { RefreshSubscriptionRequest } from '@donotdev/core/server';
|
|
16
|
+
import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
|
|
17
17
|
|
|
18
18
|
import { stripe, validateStripeEnvironment } from '../../shared/utils.js';
|
|
19
19
|
import { createBaseFunction } from '../baseFunction.js';
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { logger } from 'firebase-functions/v2';
|
|
13
|
-
import { onRequest
|
|
13
|
+
import { onRequest } from 'firebase-functions/v2/https';
|
|
14
14
|
|
|
15
15
|
import type { StripeBackConfig } from '@donotdev/core/server';
|
|
16
16
|
|
|
@@ -25,6 +25,8 @@ import {
|
|
|
25
25
|
import { STRIPE_CONFIG } from '../config/constants.js';
|
|
26
26
|
import { stripeSecretKey, stripeWebhookSecret } from '../config/secrets.js'; // ✅ IMPORT SECRETS
|
|
27
27
|
|
|
28
|
+
import type { HttpsFunction } from 'firebase-functions/v2/https';
|
|
29
|
+
|
|
28
30
|
/**
|
|
29
31
|
* Read raw body from request stream
|
|
30
32
|
*/
|
|
@@ -18,13 +18,13 @@ const ENFORCE_APP_CHECK = process.env.ENFORCE_APP_CHECK === 'true';
|
|
|
18
18
|
/** Base config inherited by all function configs */
|
|
19
19
|
const BASE_CONFIG = {
|
|
20
20
|
region: FIREBASE_REGION,
|
|
21
|
-
invoker: 'public', // Cloud Run allows HTTP, onCall validates Firebase Auth
|
|
22
21
|
...(ENFORCE_APP_CHECK && { enforceAppCheck: true }),
|
|
23
22
|
} as const;
|
|
24
23
|
|
|
25
24
|
/** Default function config */
|
|
26
25
|
export const FUNCTION_CONFIG = {
|
|
27
26
|
...BASE_CONFIG,
|
|
27
|
+
invoker: 'public', // Cloud Run allows HTTP, onCall validates Firebase Auth
|
|
28
28
|
memory: '1GiB' as const,
|
|
29
29
|
timeoutSeconds: 60,
|
|
30
30
|
cors: true, // Enable CORS by default for all functions (required for web apps)
|
|
@@ -48,9 +48,19 @@ export const AUTH_CONFIG = {
|
|
|
48
48
|
timeoutSeconds: 20,
|
|
49
49
|
} as const;
|
|
50
50
|
|
|
51
|
-
/** CRUD functions */
|
|
51
|
+
/** CRUD functions - all use public invoker (security enforced via role-based access in code) */
|
|
52
52
|
export const CRUD_CONFIG = {
|
|
53
53
|
...BASE_CONFIG,
|
|
54
|
+
invoker: 'public', // Cloud Run allows HTTP, onCall validates Firebase Auth + role-based access
|
|
55
|
+
memory: '256MiB' as const,
|
|
56
|
+
timeoutSeconds: 15,
|
|
57
|
+
cors: true, // Enable CORS for cross-origin requests (required for web apps)
|
|
58
|
+
} as const;
|
|
59
|
+
|
|
60
|
+
/** CRUD read functions (get, list) - can be public for guest access */
|
|
61
|
+
export const CRUD_READ_CONFIG = {
|
|
62
|
+
...BASE_CONFIG,
|
|
63
|
+
invoker: 'public', // Cloud Run allows HTTP, onCall validates Firebase Auth
|
|
54
64
|
memory: '256MiB' as const,
|
|
55
65
|
timeoutSeconds: 15,
|
|
56
66
|
cors: true, // Enable CORS for cross-origin requests (required for web apps)
|
|
@@ -10,8 +10,6 @@
|
|
|
10
10
|
* @author AMBROISE PARK Consulting
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type { CallableRequest } from 'firebase-functions/v2/https';
|
|
14
|
-
|
|
15
13
|
import * as v from 'valibot';
|
|
16
14
|
|
|
17
15
|
import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
|
|
@@ -21,6 +19,8 @@ import { DoNotDevError } from '../../shared/utils.js';
|
|
|
21
19
|
import { createBaseFunction } from '../baseFunction.js';
|
|
22
20
|
import { CRUD_CONFIG } from '../config/constants.js';
|
|
23
21
|
|
|
22
|
+
import type { CallableRequest } from 'firebase-functions/v2/https';
|
|
23
|
+
|
|
24
24
|
/** Supported aggregation operations */
|
|
25
25
|
export type AggregateOperation = 'count' | 'sum' | 'avg' | 'min' | 'max';
|
|
26
26
|
|
|
@@ -4,22 +4,23 @@
|
|
|
4
4
|
* @fileoverview Generic function to create an entity.
|
|
5
5
|
* @description Provides a reusable implementation for creating documents in Firestore with validation.
|
|
6
6
|
*
|
|
7
|
-
* @version 0.0.
|
|
7
|
+
* @version 0.0.2
|
|
8
8
|
* @since 0.0.1
|
|
9
9
|
* @author AMBROISE PARK Consulting
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import * as v from 'valibot';
|
|
13
13
|
|
|
14
|
-
import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
|
|
15
14
|
import { DEFAULT_STATUS_VALUE } from '@donotdev/core/server';
|
|
15
|
+
import type { UserRole } from '@donotdev/core/server';
|
|
16
|
+
import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
|
|
16
17
|
|
|
17
18
|
import {
|
|
18
19
|
prepareForFirestore,
|
|
19
20
|
transformFirestoreData,
|
|
20
21
|
} from '../../shared/index.js';
|
|
21
22
|
import { createMetadata } from '../../shared/index.js';
|
|
22
|
-
import {
|
|
23
|
+
import { validateDocument } from '../../shared/utils.js';
|
|
23
24
|
import { createBaseFunction } from '../baseFunction.js';
|
|
24
25
|
import { CRUD_CONFIG } from '../config/constants.js';
|
|
25
26
|
|
|
@@ -47,12 +48,10 @@ function createEntityLogicFactory(
|
|
|
47
48
|
) {
|
|
48
49
|
return async function createEntityLogic(
|
|
49
50
|
data: CreateEntityRequest,
|
|
50
|
-
context: { uid: string; request: CallableRequest<any> }
|
|
51
|
+
context: { uid: string; userRole: UserRole; request: CallableRequest<any> }
|
|
51
52
|
) {
|
|
52
53
|
const { payload, idempotencyKey } = data;
|
|
53
|
-
|
|
54
|
-
// Ensure the user is an admin
|
|
55
|
-
const uid = await assertAdmin(context.uid);
|
|
54
|
+
const { uid } = context;
|
|
56
55
|
|
|
57
56
|
// Idempotency check if key provided
|
|
58
57
|
if (idempotencyKey) {
|
|
@@ -117,16 +116,14 @@ function createEntityLogicFactory(
|
|
|
117
116
|
* Generic function to create entities in any Firestore collection
|
|
118
117
|
* @param collection - The Firestore collection name
|
|
119
118
|
* @param documentSchema - The Valibot schema for document validation
|
|
119
|
+
* @param requiredRole - Minimum role required for this operation
|
|
120
120
|
* @param customSchema - Optional custom request schema
|
|
121
121
|
* @returns Firebase callable function
|
|
122
|
-
*
|
|
123
|
-
* @version 0.0.1
|
|
124
|
-
* @since 0.0.1
|
|
125
|
-
* @author AMBROISE PARK Consulting
|
|
126
122
|
*/
|
|
127
123
|
export const createEntity = (
|
|
128
124
|
collection: string,
|
|
129
125
|
documentSchema: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>,
|
|
126
|
+
requiredRole: UserRole,
|
|
130
127
|
customSchema?: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>
|
|
131
128
|
): CallableFunction<CreateEntityRequest, Promise<any>> => {
|
|
132
129
|
const requestSchema =
|
|
@@ -140,6 +137,7 @@ export const createEntity = (
|
|
|
140
137
|
CRUD_CONFIG,
|
|
141
138
|
requestSchema,
|
|
142
139
|
'create_entity',
|
|
143
|
-
createEntityLogicFactory(collection, documentSchema)
|
|
140
|
+
createEntityLogicFactory(collection, documentSchema),
|
|
141
|
+
requiredRole
|
|
144
142
|
);
|
|
145
143
|
};
|
|
@@ -9,14 +9,12 @@
|
|
|
9
9
|
* @author AMBROISE PARK Consulting
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
|
|
13
12
|
import * as v from 'valibot';
|
|
14
13
|
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} from '../../shared/utils.js';
|
|
14
|
+
import type { UserRole } from '@donotdev/core/server';
|
|
15
|
+
import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
|
|
16
|
+
|
|
17
|
+
import { DoNotDevError, findReferences } from '../../shared/utils.js';
|
|
20
18
|
import { createBaseFunction } from '../baseFunction.js';
|
|
21
19
|
import { CRUD_CONFIG } from '../config/constants.js';
|
|
22
20
|
|
|
@@ -38,13 +36,10 @@ export type DeleteEntityRequest = { id: string };
|
|
|
38
36
|
function deleteEntityLogicFactory(collection: string) {
|
|
39
37
|
return async function deleteEntityLogic(
|
|
40
38
|
data: DeleteEntityRequest,
|
|
41
|
-
context: { uid: string; request: CallableRequest<any> }
|
|
39
|
+
context: { uid: string; userRole: UserRole; request: CallableRequest<any> }
|
|
42
40
|
) {
|
|
43
41
|
const { id } = data;
|
|
44
42
|
|
|
45
|
-
// Ensure the user is an admin
|
|
46
|
-
await assertAdmin(context.uid);
|
|
47
|
-
|
|
48
43
|
// Check for references to this document
|
|
49
44
|
const references = await findReferences(collection, id);
|
|
50
45
|
|
|
@@ -68,6 +63,7 @@ function deleteEntityLogicFactory(collection: string) {
|
|
|
68
63
|
/**
|
|
69
64
|
* Generic function to delete entities from any Firestore collection
|
|
70
65
|
* @param collection - The Firestore collection name
|
|
66
|
+
* @param requiredRole - Minimum role required for this operation
|
|
71
67
|
* @param customSchema - Optional custom request schema
|
|
72
68
|
* @returns Firebase callable function
|
|
73
69
|
*
|
|
@@ -77,6 +73,7 @@ function deleteEntityLogicFactory(collection: string) {
|
|
|
77
73
|
*/
|
|
78
74
|
export const deleteEntity = (
|
|
79
75
|
collection: string,
|
|
76
|
+
requiredRole: UserRole,
|
|
80
77
|
customSchema?: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>
|
|
81
78
|
): CallableFunction<DeleteEntityRequest, Promise<{ success: boolean }>> => {
|
|
82
79
|
const requestSchema =
|
|
@@ -89,6 +86,7 @@ export const deleteEntity = (
|
|
|
89
86
|
CRUD_CONFIG,
|
|
90
87
|
requestSchema,
|
|
91
88
|
'delete_entity',
|
|
92
|
-
deleteEntityLogicFactory(collection)
|
|
89
|
+
deleteEntityLogicFactory(collection),
|
|
90
|
+
requiredRole
|
|
93
91
|
);
|
|
94
92
|
};
|