@donotdev/functions 0.0.3 → 0.0.5
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 +30 -45
- package/lib/shared/index.js +5 -8
- package/lib/vercel/api/index.js +7 -11
- package/package.json +5 -5
- package/src/firebase/auth/getCustomClaims.ts +13 -4
- package/src/firebase/auth/getUserAuthStatus.ts +21 -4
- package/src/firebase/auth/removeCustomClaims.ts +18 -4
- package/src/firebase/auth/setCustomClaims.ts +18 -4
- package/src/firebase/billing/createCheckoutSession.ts +15 -2
- package/src/firebase/billing/createCustomerPortal.ts +34 -11
- package/src/firebase/billing/webhookHandler.ts +17 -5
- package/src/firebase/config/constants.ts +7 -1
- package/src/firebase/config/secrets.ts +32 -0
- package/src/firebase/crud/aggregate.ts +361 -0
- package/src/firebase/crud/create.ts +22 -4
- package/src/firebase/crud/delete.ts +8 -3
- package/src/firebase/crud/get.ts +16 -4
- package/src/firebase/crud/index.ts +1 -0
- package/src/firebase/crud/list.ts +14 -6
- package/src/firebase/crud/update.ts +21 -4
- package/src/firebase/oauth/githubAccess.ts +27 -6
- package/src/shared/schema.ts +45 -21
- package/src/shared/utils.ts +26 -13
- package/src/vercel/api/crud/create.ts +11 -1
- package/src/vercel/api/crud/get.ts +8 -1
- package/src/vercel/api/crud/list.ts +4 -0
- package/src/vercel/api/crud/update.ts +23 -2
package/lib/firebase/index.js
CHANGED
|
@@ -31217,14 +31217,11 @@ async function WR(e, t) {
|
|
|
31217
31217
|
let c = { success: !0, customClaims: s };
|
|
31218
31218
|
return (
|
|
31219
31219
|
n &&
|
|
31220
|
-
(await oe()
|
|
31221
|
-
|
|
31222
|
-
.
|
|
31223
|
-
.
|
|
31224
|
-
|
|
31225
|
-
processedAt: new Date().toISOString(),
|
|
31226
|
-
processedBy: t.uid,
|
|
31227
|
-
})),
|
|
31220
|
+
(await oe().collection('idempotency').doc(`claims_${n}`).set({
|
|
31221
|
+
result: c,
|
|
31222
|
+
processedAt: new Date().toISOString(),
|
|
31223
|
+
processedBy: t.uid,
|
|
31224
|
+
})),
|
|
31228
31225
|
c
|
|
31229
31226
|
);
|
|
31230
31227
|
}
|
|
@@ -32504,14 +32501,11 @@ function yI(e, t) {
|
|
|
32504
32501
|
w = Dt({ id: m.id, ...m.data() });
|
|
32505
32502
|
return (
|
|
32506
32503
|
s &&
|
|
32507
|
-
(await u
|
|
32508
|
-
|
|
32509
|
-
.
|
|
32510
|
-
|
|
32511
|
-
|
|
32512
|
-
processedAt: new Date().toISOString(),
|
|
32513
|
-
processedBy: c,
|
|
32514
|
-
})),
|
|
32504
|
+
(await u.collection('idempotency').doc(`create_${s}`).set({
|
|
32505
|
+
result: w,
|
|
32506
|
+
processedAt: new Date().toISOString(),
|
|
32507
|
+
processedBy: c,
|
|
32508
|
+
})),
|
|
32515
32509
|
w
|
|
32516
32510
|
);
|
|
32517
32511
|
}, 'createEntityLogic');
|
|
@@ -32632,14 +32626,11 @@ function wI(e, t) {
|
|
|
32632
32626
|
k = Dt({ id: C.id, ...C.data() });
|
|
32633
32627
|
return (
|
|
32634
32628
|
c &&
|
|
32635
|
-
(await u
|
|
32636
|
-
|
|
32637
|
-
.
|
|
32638
|
-
|
|
32639
|
-
|
|
32640
|
-
processedAt: new Date().toISOString(),
|
|
32641
|
-
processedBy: h,
|
|
32642
|
-
})),
|
|
32629
|
+
(await u.collection('idempotency').doc(`update_${c}`).set({
|
|
32630
|
+
result: k,
|
|
32631
|
+
processedAt: new Date().toISOString(),
|
|
32632
|
+
processedBy: h,
|
|
32633
|
+
})),
|
|
32643
32634
|
k
|
|
32644
32635
|
);
|
|
32645
32636
|
}, 'updateEntityLogic');
|
|
@@ -32682,19 +32673,16 @@ async function Cue(e, t, r, n = 'pull', i) {
|
|
|
32682
32673
|
});
|
|
32683
32674
|
}
|
|
32684
32675
|
(process.env.SAVE_GITHUB_ACCESS_TO_FIRESTORE === 'true' &&
|
|
32685
|
-
(await oe()
|
|
32686
|
-
|
|
32687
|
-
|
|
32688
|
-
|
|
32689
|
-
|
|
32690
|
-
|
|
32691
|
-
|
|
32692
|
-
|
|
32693
|
-
|
|
32694
|
-
|
|
32695
|
-
},
|
|
32696
|
-
{ merge: !0 }
|
|
32697
|
-
)),
|
|
32676
|
+
(await oe().collection('githubAccess').doc(e).set(
|
|
32677
|
+
{
|
|
32678
|
+
githubUsername: t,
|
|
32679
|
+
repoConfig: r,
|
|
32680
|
+
permission: n,
|
|
32681
|
+
accessGranted: !0,
|
|
32682
|
+
grantedAt: Date.now(),
|
|
32683
|
+
},
|
|
32684
|
+
{ merge: !0 }
|
|
32685
|
+
)),
|
|
32698
32686
|
kc.info('GitHub repository access granted', {
|
|
32699
32687
|
userId: e,
|
|
32700
32688
|
githubUsername: t,
|
|
@@ -32964,14 +32952,11 @@ var Jue = TI(async (e) => {
|
|
|
32964
32952
|
let C = { success: !0, credentials: S, profile: z };
|
|
32965
32953
|
return (
|
|
32966
32954
|
c &&
|
|
32967
|
-
(await oe()
|
|
32968
|
-
|
|
32969
|
-
.
|
|
32970
|
-
|
|
32971
|
-
|
|
32972
|
-
processedAt: new Date().toISOString(),
|
|
32973
|
-
processedBy: t,
|
|
32974
|
-
})),
|
|
32955
|
+
(await oe().collection('idempotency').doc(`oauth_${c}`).set({
|
|
32956
|
+
result: C,
|
|
32957
|
+
processedAt: new Date().toISOString(),
|
|
32958
|
+
processedBy: t,
|
|
32959
|
+
})),
|
|
32975
32960
|
C
|
|
32976
32961
|
);
|
|
32977
32962
|
} catch (h) {
|
package/lib/shared/index.js
CHANGED
|
@@ -31308,14 +31308,11 @@ async function c5e(e, t) {
|
|
|
31308
31308
|
o(c5e, 'checkIdempotency');
|
|
31309
31309
|
async function h5e(e, t, r, n) {
|
|
31310
31310
|
try {
|
|
31311
|
-
(await Ue()
|
|
31312
|
-
|
|
31313
|
-
.
|
|
31314
|
-
|
|
31315
|
-
|
|
31316
|
-
processedAt: new Date().toISOString(),
|
|
31317
|
-
processedBy: n,
|
|
31318
|
-
}),
|
|
31311
|
+
(await Ue().collection('idempotency').doc(`${t}_${e}`).set({
|
|
31312
|
+
result: r,
|
|
31313
|
+
processedAt: new Date().toISOString(),
|
|
31314
|
+
processedBy: n,
|
|
31315
|
+
}),
|
|
31319
31316
|
B1.info('Idempotency result stored', {
|
|
31320
31317
|
key: e,
|
|
31321
31318
|
operation: t,
|
package/lib/vercel/api/index.js
CHANGED
|
@@ -30612,23 +30612,19 @@ function Nr(e, t, r, o) {
|
|
|
30612
30612
|
return a.status(405).json({ error: 'Method not allowed' });
|
|
30613
30613
|
let l = Ft(t, i.body);
|
|
30614
30614
|
if (!l.success)
|
|
30615
|
-
return a
|
|
30616
|
-
.
|
|
30617
|
-
|
|
30618
|
-
error: `Validation failed: ${l.issues.map((S) => S.message).join(', ')}`,
|
|
30619
|
-
});
|
|
30615
|
+
return a.status(400).json({
|
|
30616
|
+
error: `Validation failed: ${l.issues.map((S) => S.message).join(', ')}`,
|
|
30617
|
+
});
|
|
30620
30618
|
let h = l.output,
|
|
30621
30619
|
d = P8(i.headers.authorization),
|
|
30622
30620
|
m = `${r}_${d}`,
|
|
30623
30621
|
p = O0[r] || O0.api,
|
|
30624
30622
|
f = await A8(m, p);
|
|
30625
30623
|
if (!f.allowed)
|
|
30626
|
-
return a
|
|
30627
|
-
.
|
|
30628
|
-
.
|
|
30629
|
-
|
|
30630
|
-
retryAfter: f.blockRemainingSeconds,
|
|
30631
|
-
});
|
|
30624
|
+
return a.status(429).json({
|
|
30625
|
+
error: `Rate limit exceeded. Try again in ${f.blockRemainingSeconds} seconds.`,
|
|
30626
|
+
retryAfter: f.blockRemainingSeconds,
|
|
30627
|
+
});
|
|
30632
30628
|
let z = await o(i, a, h, { uid: d });
|
|
30633
30629
|
return (
|
|
30634
30630
|
await D0({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@donotdev/functions",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
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,14 +42,14 @@
|
|
|
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.12",
|
|
46
|
+
"@donotdev/firebase": "^0.0.5"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@sentry/node": "^10.
|
|
49
|
+
"@sentry/node": "^10.33.0",
|
|
50
50
|
"firebase-admin": "^13.6.0",
|
|
51
51
|
"firebase-functions": "^7.0.1",
|
|
52
|
-
"next": "^16.
|
|
52
|
+
"next": "^16.1.4",
|
|
53
53
|
"stripe": "^20.1.0",
|
|
54
54
|
"valibot": "^1.2.0"
|
|
55
55
|
},
|
|
@@ -15,10 +15,16 @@ import * as v from 'valibot';
|
|
|
15
15
|
import { createBaseFunction } from '../baseFunction.js';
|
|
16
16
|
import { AUTH_CONFIG } from '../config/constants.js';
|
|
17
17
|
|
|
18
|
-
import type {
|
|
18
|
+
import type {
|
|
19
|
+
CallableFunction,
|
|
20
|
+
CallableRequest,
|
|
21
|
+
} from 'firebase-functions/v2/https';
|
|
19
22
|
|
|
20
23
|
const getCustomClaimsSchema = v.object({});
|
|
21
24
|
|
|
25
|
+
type GetCustomClaimsRequest = v.InferOutput<typeof getCustomClaimsSchema>;
|
|
26
|
+
type GetCustomClaimsResponse = { customClaims: Record<string, any> };
|
|
27
|
+
|
|
22
28
|
/**
|
|
23
29
|
* Business logic for getting custom claims
|
|
24
30
|
* Base function handles: validation, auth, rate limiting, monitoring
|
|
@@ -28,10 +34,10 @@ const getCustomClaimsSchema = v.object({});
|
|
|
28
34
|
* @author AMBROISE PARK Consulting
|
|
29
35
|
*/
|
|
30
36
|
async function getCustomClaimsLogic(
|
|
31
|
-
data:
|
|
37
|
+
data: GetCustomClaimsRequest,
|
|
32
38
|
context: {
|
|
33
39
|
uid: string;
|
|
34
|
-
request: CallableRequest<
|
|
40
|
+
request: CallableRequest<GetCustomClaimsRequest>;
|
|
35
41
|
}
|
|
36
42
|
) {
|
|
37
43
|
const user = await getFirebaseAdminAuth().getUser(context.uid);
|
|
@@ -48,7 +54,10 @@ async function getCustomClaimsLogic(
|
|
|
48
54
|
*/
|
|
49
55
|
export const getCustomClaims = (
|
|
50
56
|
customSchema?: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>
|
|
51
|
-
)
|
|
57
|
+
): CallableFunction<
|
|
58
|
+
GetCustomClaimsRequest,
|
|
59
|
+
Promise<GetCustomClaimsResponse>
|
|
60
|
+
> => {
|
|
52
61
|
const schema = customSchema || getCustomClaimsSchema;
|
|
53
62
|
return createBaseFunction(
|
|
54
63
|
AUTH_CONFIG,
|
|
@@ -15,10 +15,24 @@ import * as v from 'valibot';
|
|
|
15
15
|
import { createBaseFunction } from '../baseFunction.js';
|
|
16
16
|
import { AUTH_CONFIG } from '../config/constants.js';
|
|
17
17
|
|
|
18
|
-
import type {
|
|
18
|
+
import type {
|
|
19
|
+
CallableFunction,
|
|
20
|
+
CallableRequest,
|
|
21
|
+
} from 'firebase-functions/v2/https';
|
|
19
22
|
|
|
20
23
|
const getUserAuthStatusSchema = v.object({});
|
|
21
24
|
|
|
25
|
+
export type GetUserAuthStatusRequest = v.InferOutput<
|
|
26
|
+
typeof getUserAuthStatusSchema
|
|
27
|
+
>;
|
|
28
|
+
export type GetUserAuthStatusResponse = {
|
|
29
|
+
uid: string;
|
|
30
|
+
email?: string;
|
|
31
|
+
emailVerified: boolean;
|
|
32
|
+
customClaims: Record<string, any>;
|
|
33
|
+
disabled: boolean;
|
|
34
|
+
};
|
|
35
|
+
|
|
22
36
|
/**
|
|
23
37
|
* Business logic for getting user auth status
|
|
24
38
|
* Base function handles: validation, auth, rate limiting, monitoring
|
|
@@ -28,10 +42,10 @@ const getUserAuthStatusSchema = v.object({});
|
|
|
28
42
|
* @author AMBROISE PARK Consulting
|
|
29
43
|
*/
|
|
30
44
|
async function getUserAuthStatusLogic(
|
|
31
|
-
data:
|
|
45
|
+
data: GetUserAuthStatusRequest,
|
|
32
46
|
context: {
|
|
33
47
|
uid: string;
|
|
34
|
-
request: CallableRequest<
|
|
48
|
+
request: CallableRequest<GetUserAuthStatusRequest>;
|
|
35
49
|
}
|
|
36
50
|
) {
|
|
37
51
|
const user = await getFirebaseAdminAuth().getUser(context.uid);
|
|
@@ -55,7 +69,10 @@ async function getUserAuthStatusLogic(
|
|
|
55
69
|
*/
|
|
56
70
|
export const getUserAuthStatus = (
|
|
57
71
|
customSchema?: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>
|
|
58
|
-
)
|
|
72
|
+
): CallableFunction<
|
|
73
|
+
GetUserAuthStatusRequest,
|
|
74
|
+
Promise<GetUserAuthStatusResponse>
|
|
75
|
+
> => {
|
|
59
76
|
const schema = customSchema || getUserAuthStatusSchema;
|
|
60
77
|
return createBaseFunction(
|
|
61
78
|
AUTH_CONFIG,
|
|
@@ -15,7 +15,10 @@ import * as v from 'valibot';
|
|
|
15
15
|
import { createBaseFunction } from '../baseFunction.js';
|
|
16
16
|
import { AUTH_CONFIG } from '../config/constants.js';
|
|
17
17
|
|
|
18
|
-
import type {
|
|
18
|
+
import type {
|
|
19
|
+
CallableFunction,
|
|
20
|
+
CallableRequest,
|
|
21
|
+
} from 'firebase-functions/v2/https';
|
|
19
22
|
|
|
20
23
|
const removeCustomClaimsSchema = v.object({
|
|
21
24
|
claimsToRemove: v.pipe(
|
|
@@ -24,6 +27,14 @@ const removeCustomClaimsSchema = v.object({
|
|
|
24
27
|
),
|
|
25
28
|
});
|
|
26
29
|
|
|
30
|
+
export type RemoveCustomClaimsRequest = v.InferOutput<
|
|
31
|
+
typeof removeCustomClaimsSchema
|
|
32
|
+
>;
|
|
33
|
+
export type RemoveCustomClaimsResponse = {
|
|
34
|
+
success: boolean;
|
|
35
|
+
customClaims: Record<string, any>;
|
|
36
|
+
};
|
|
37
|
+
|
|
27
38
|
/**
|
|
28
39
|
* Business logic for removing custom claims
|
|
29
40
|
* Base function handles: validation, auth, rate limiting, monitoring
|
|
@@ -33,10 +44,10 @@ const removeCustomClaimsSchema = v.object({
|
|
|
33
44
|
* @author AMBROISE PARK Consulting
|
|
34
45
|
*/
|
|
35
46
|
async function removeCustomClaimsLogic(
|
|
36
|
-
data:
|
|
47
|
+
data: RemoveCustomClaimsRequest,
|
|
37
48
|
context: {
|
|
38
49
|
uid: string;
|
|
39
|
-
request: CallableRequest<
|
|
50
|
+
request: CallableRequest<RemoveCustomClaimsRequest>;
|
|
40
51
|
}
|
|
41
52
|
) {
|
|
42
53
|
const { claimsToRemove } = data;
|
|
@@ -65,7 +76,10 @@ async function removeCustomClaimsLogic(
|
|
|
65
76
|
*/
|
|
66
77
|
export const removeCustomClaims = (
|
|
67
78
|
customSchema?: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>
|
|
68
|
-
)
|
|
79
|
+
): CallableFunction<
|
|
80
|
+
RemoveCustomClaimsRequest,
|
|
81
|
+
Promise<RemoveCustomClaimsResponse>
|
|
82
|
+
> => {
|
|
69
83
|
const schema = customSchema || removeCustomClaimsSchema;
|
|
70
84
|
return createBaseFunction(
|
|
71
85
|
AUTH_CONFIG,
|
|
@@ -17,13 +17,24 @@ import * as v from 'valibot';
|
|
|
17
17
|
import { createBaseFunction } from '../baseFunction.js';
|
|
18
18
|
import { AUTH_CONFIG } from '../config/constants.js';
|
|
19
19
|
|
|
20
|
-
import type {
|
|
20
|
+
import type {
|
|
21
|
+
CallableFunction,
|
|
22
|
+
CallableRequest,
|
|
23
|
+
} from 'firebase-functions/v2/https';
|
|
21
24
|
|
|
22
25
|
const setCustomClaimsSchema = v.object({
|
|
23
26
|
customClaims: v.record(v.string(), v.any()),
|
|
24
27
|
idempotencyKey: v.optional(v.string()),
|
|
25
28
|
});
|
|
26
29
|
|
|
30
|
+
export type SetCustomClaimsRequest = v.InferOutput<
|
|
31
|
+
typeof setCustomClaimsSchema
|
|
32
|
+
>;
|
|
33
|
+
export type SetCustomClaimsResponse = {
|
|
34
|
+
success: boolean;
|
|
35
|
+
customClaims: Record<string, any>;
|
|
36
|
+
};
|
|
37
|
+
|
|
27
38
|
/**
|
|
28
39
|
* Business logic for setting custom claims
|
|
29
40
|
* Base function handles: validation, auth, rate limiting, monitoring
|
|
@@ -33,10 +44,10 @@ const setCustomClaimsSchema = v.object({
|
|
|
33
44
|
* @author AMBROISE PARK Consulting
|
|
34
45
|
*/
|
|
35
46
|
async function setCustomClaimsLogic(
|
|
36
|
-
data:
|
|
47
|
+
data: SetCustomClaimsRequest,
|
|
37
48
|
context: {
|
|
38
49
|
uid: string;
|
|
39
|
-
request: CallableRequest<
|
|
50
|
+
request: CallableRequest<SetCustomClaimsRequest>;
|
|
40
51
|
}
|
|
41
52
|
) {
|
|
42
53
|
const { customClaims, idempotencyKey } = data;
|
|
@@ -94,7 +105,10 @@ async function setCustomClaimsLogic(
|
|
|
94
105
|
*/
|
|
95
106
|
export const setCustomClaims = (
|
|
96
107
|
customSchema?: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>
|
|
97
|
-
)
|
|
108
|
+
): CallableFunction<
|
|
109
|
+
SetCustomClaimsRequest,
|
|
110
|
+
Promise<SetCustomClaimsResponse>
|
|
111
|
+
> => {
|
|
98
112
|
const schema = customSchema || setCustomClaimsSchema;
|
|
99
113
|
return createBaseFunction(
|
|
100
114
|
AUTH_CONFIG,
|
|
@@ -27,8 +27,13 @@ import {
|
|
|
27
27
|
import { stripe, validateStripeEnvironment } from '../../shared/utils.js';
|
|
28
28
|
import { createBaseFunction } from '../baseFunction.js';
|
|
29
29
|
import { STRIPE_CONFIG } from '../config/constants.js';
|
|
30
|
+
import { stripeSecretKey } from '../config/secrets.js'; // ✅ IMPORT SECRET
|
|
31
|
+
import { initStripe } from '../../shared/utils.js'; // ✅ IMPORT INIT
|
|
30
32
|
|
|
31
|
-
import type {
|
|
33
|
+
import type {
|
|
34
|
+
CallableFunction,
|
|
35
|
+
CallableRequest,
|
|
36
|
+
} from 'firebase-functions/v2/https';
|
|
32
37
|
|
|
33
38
|
/**
|
|
34
39
|
* Business logic for creating Stripe checkout session
|
|
@@ -41,6 +46,9 @@ async function createCheckoutSessionLogic(
|
|
|
41
46
|
},
|
|
42
47
|
billingConfig: StripeBackConfig
|
|
43
48
|
) {
|
|
49
|
+
// ✅ LATEST BEST PRACTICE: Initialize with secret value explicitly
|
|
50
|
+
initStripe(stripeSecretKey.value());
|
|
51
|
+
|
|
44
52
|
try {
|
|
45
53
|
validateStripeEnvironment();
|
|
46
54
|
} catch (error) {
|
|
@@ -217,7 +225,12 @@ async function createCheckoutSessionLogic(
|
|
|
217
225
|
* });
|
|
218
226
|
* window.location.href = result.data.sessionUrl;
|
|
219
227
|
*/
|
|
220
|
-
export function createCheckoutSession(
|
|
228
|
+
export function createCheckoutSession(
|
|
229
|
+
billingConfig: StripeBackConfig
|
|
230
|
+
): CallableFunction<
|
|
231
|
+
CreateCheckoutSessionRequest,
|
|
232
|
+
Promise<{ sessionId: string; sessionUrl: string }>
|
|
233
|
+
> {
|
|
221
234
|
return createBaseFunction<
|
|
222
235
|
CreateCheckoutSessionRequest,
|
|
223
236
|
{ sessionId: string; sessionUrl: string }
|
|
@@ -13,38 +13,58 @@ import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
|
|
|
13
13
|
import * as v from 'valibot';
|
|
14
14
|
|
|
15
15
|
import { handleError } from '../../shared/errorHandling.js';
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
stripe,
|
|
18
|
+
validateStripeEnvironment,
|
|
19
|
+
initStripe,
|
|
20
|
+
} from '../../shared/utils.js'; // ✅ IMPORT INIT
|
|
17
21
|
import { createBaseFunction } from '../baseFunction.js';
|
|
18
22
|
import { STRIPE_CONFIG } from '../config/constants.js';
|
|
23
|
+
import { stripeSecretKey } from '../config/secrets.js'; // ✅ IMPORT SECRET
|
|
19
24
|
|
|
20
|
-
import type {
|
|
25
|
+
import type {
|
|
26
|
+
CallableFunction,
|
|
27
|
+
CallableRequest,
|
|
28
|
+
} from 'firebase-functions/v2/https';
|
|
21
29
|
|
|
22
30
|
const customerPortalSchema = v.object({
|
|
23
31
|
userId: v.string(),
|
|
24
32
|
returnUrl: v.optional(v.pipe(v.string(), v.url())),
|
|
25
33
|
});
|
|
26
34
|
|
|
35
|
+
type CustomerPortalRequest = v.InferOutput<typeof customerPortalSchema>;
|
|
36
|
+
|
|
27
37
|
async function createCustomerPortalLogic(
|
|
28
|
-
data:
|
|
29
|
-
context: { uid: string; request: CallableRequest }
|
|
38
|
+
data: CustomerPortalRequest,
|
|
39
|
+
context: { uid: string; request: CallableRequest<CustomerPortalRequest> }
|
|
30
40
|
) {
|
|
31
|
-
|
|
41
|
+
// ✅ LATEST BEST PRACTICE: Init with secret value
|
|
42
|
+
initStripe(stripeSecretKey.value());
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
validateStripeEnvironment();
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw handleError(error);
|
|
48
|
+
}
|
|
32
49
|
|
|
33
50
|
const { userId, returnUrl } = data;
|
|
34
51
|
|
|
35
52
|
// Get customer ID from user claims
|
|
36
53
|
const user = await getFirebaseAdminAuth().getUser(userId);
|
|
37
|
-
|
|
38
|
-
const
|
|
54
|
+
// ... (rest of logic)
|
|
55
|
+
const currentClaims = (user.customClaims || {}) as any;
|
|
56
|
+
const customerId =
|
|
57
|
+
currentClaims.subscription?.customerId ||
|
|
58
|
+
user.customClaims?.stripeCustomerId;
|
|
39
59
|
|
|
40
60
|
if (!customerId) {
|
|
41
|
-
throw handleError(new Error('No customer found'));
|
|
61
|
+
throw handleError(new Error('No Stripe customer ID found for user'));
|
|
42
62
|
}
|
|
43
63
|
|
|
44
64
|
// Create portal session
|
|
45
65
|
const session = await stripe.billingPortal.sessions.create({
|
|
46
66
|
customer: customerId,
|
|
47
|
-
return_url: returnUrl ||
|
|
67
|
+
return_url: returnUrl || 'https://donotdev.com/dashboard', // Fallback
|
|
48
68
|
});
|
|
49
69
|
|
|
50
70
|
return {
|
|
@@ -52,8 +72,11 @@ async function createCustomerPortalLogic(
|
|
|
52
72
|
};
|
|
53
73
|
}
|
|
54
74
|
|
|
55
|
-
export function createCustomerPortal()
|
|
56
|
-
|
|
75
|
+
export function createCustomerPortal(): CallableFunction<
|
|
76
|
+
CustomerPortalRequest,
|
|
77
|
+
Promise<{ url: string }>
|
|
78
|
+
> {
|
|
79
|
+
return createBaseFunction<CustomerPortalRequest, { url: string }>(
|
|
57
80
|
STRIPE_CONFIG,
|
|
58
81
|
customerPortalSchema,
|
|
59
82
|
'create_customer_portal',
|
|
@@ -10,15 +10,20 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { logger } from 'firebase-functions/v2';
|
|
13
|
-
import { onRequest } from 'firebase-functions/v2/https';
|
|
13
|
+
import { onRequest, type HttpsFunction } from 'firebase-functions/v2/https';
|
|
14
14
|
|
|
15
15
|
import type { StripeBackConfig } from '@donotdev/core/server';
|
|
16
16
|
|
|
17
17
|
import { updateUserSubscription } from '../../shared/billing/helpers/updateUserSubscription.js';
|
|
18
18
|
import { processWebhook } from '../../shared/billing/webhookHandler.js';
|
|
19
19
|
import { handleError } from '../../shared/errorHandling.js';
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
stripe,
|
|
22
|
+
validateStripeEnvironment,
|
|
23
|
+
initStripe,
|
|
24
|
+
} from '../../shared/utils.js'; // ✅ IMPORT INIT
|
|
21
25
|
import { STRIPE_CONFIG } from '../config/constants.js';
|
|
26
|
+
import { stripeSecretKey, stripeWebhookSecret } from '../config/secrets.js'; // ✅ IMPORT SECRETS
|
|
22
27
|
|
|
23
28
|
/**
|
|
24
29
|
* Read raw body from request stream
|
|
@@ -42,10 +47,15 @@ async function getRawBody(req: any): Promise<Buffer> {
|
|
|
42
47
|
/**
|
|
43
48
|
* Create Firebase webhook handler from billing config
|
|
44
49
|
*/
|
|
45
|
-
export function createStripeWebhook(
|
|
50
|
+
export function createStripeWebhook(
|
|
51
|
+
billingConfig: StripeBackConfig
|
|
52
|
+
): HttpsFunction {
|
|
46
53
|
return onRequest(STRIPE_CONFIG, async (req, res) => {
|
|
54
|
+
// ✅ LATEST BEST PRACTICE: Init with secret value
|
|
55
|
+
initStripe(stripeSecretKey.value());
|
|
56
|
+
|
|
47
57
|
try {
|
|
48
|
-
validateStripeEnvironment();
|
|
58
|
+
// validateStripeEnvironment(); // initStripe handles this now
|
|
49
59
|
|
|
50
60
|
const sig = req.headers['stripe-signature'];
|
|
51
61
|
if (!sig || Array.isArray(sig)) {
|
|
@@ -58,7 +68,9 @@ export function createStripeWebhook(billingConfig: StripeBackConfig) {
|
|
|
58
68
|
throw handleError(error);
|
|
59
69
|
}
|
|
60
70
|
|
|
61
|
-
|
|
71
|
+
// ✅ LATEST BEST PRACTICE: Use defineSecret value
|
|
72
|
+
const webhookSecret = stripeWebhookSecret.value();
|
|
73
|
+
|
|
62
74
|
if (!webhookSecret) {
|
|
63
75
|
const error = new Error('STRIPE_WEBHOOK_SECRET not configured');
|
|
64
76
|
logger.error('[Webhook] Missing webhook secret', {
|
|
@@ -27,14 +27,19 @@ export const FUNCTION_CONFIG = {
|
|
|
27
27
|
...BASE_CONFIG,
|
|
28
28
|
memory: '1GiB' as const,
|
|
29
29
|
timeoutSeconds: 60,
|
|
30
|
+
cors: true, // Enable CORS by default for all functions (required for web apps)
|
|
30
31
|
} as const;
|
|
31
32
|
|
|
33
|
+
import { stripeSecretKey, stripeWebhookSecret } from './secrets.js';
|
|
34
|
+
|
|
32
35
|
/** Stripe/billing functions */
|
|
33
36
|
export const STRIPE_CONFIG = {
|
|
34
37
|
...BASE_CONFIG,
|
|
35
38
|
memory: '512MiB' as const,
|
|
36
39
|
timeoutSeconds: 30,
|
|
37
|
-
|
|
40
|
+
cors: true,
|
|
41
|
+
secrets: [stripeSecretKey, stripeWebhookSecret],
|
|
42
|
+
};
|
|
38
43
|
|
|
39
44
|
/** Auth functions */
|
|
40
45
|
export const AUTH_CONFIG = {
|
|
@@ -48,6 +53,7 @@ export const CRUD_CONFIG = {
|
|
|
48
53
|
...BASE_CONFIG,
|
|
49
54
|
memory: '256MiB' as const,
|
|
50
55
|
timeoutSeconds: 15,
|
|
56
|
+
cors: true, // Enable CORS for cross-origin requests (required for web apps)
|
|
51
57
|
} as const;
|
|
52
58
|
|
|
53
59
|
/**
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// packages/functions/src/firebase/config/secrets.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Firebase Functions V2 Secrets
|
|
5
|
+
* @description Definition of secrets using the modern defineSecret API
|
|
6
|
+
*
|
|
7
|
+
* @version 0.0.1
|
|
8
|
+
* @since 0.0.1
|
|
9
|
+
* @author AMBROISE PARK Consulting
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { defineSecret } from 'firebase-functions/params';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Stripe Secret Key
|
|
16
|
+
* Used for server-side Stripe API operations
|
|
17
|
+
*/
|
|
18
|
+
export const stripeSecretKey = defineSecret('STRIPE_SECRET_KEY');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Stripe Webhook Secret
|
|
22
|
+
* Used to verify signatures of incoming Stripe webhooks
|
|
23
|
+
*/
|
|
24
|
+
export const stripeWebhookSecret = defineSecret('STRIPE_WEBHOOK_SECRET');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* GitHub Personal Access Token
|
|
28
|
+
* Used for GitHub API operations (repo access, etc.)
|
|
29
|
+
*/
|
|
30
|
+
export const githubPersonalAccessToken = defineSecret(
|
|
31
|
+
'GITHUB_PERSONAL_ACCESS_TOKEN'
|
|
32
|
+
);
|