git-paid 2.1.6 → 2.1.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/bin/git-paid.js +16 -6
- package/lib/auth.js +241 -24
- package/lib/directories.js +179 -123
- package/lib/engine.js +92 -48
- package/lib/fixer.js +128 -52
- package/lib/guard.js +203 -1
- package/lib/inbox.js +49 -4
- package/lib/mcp.js +15 -1
- package/lib/pseo.js +202 -8
- package/lib/safe-fetch.js +14 -5
- package/package.json +1 -1
package/bin/git-paid.js
CHANGED
|
@@ -46,10 +46,10 @@ async function runCli() {
|
|
|
46
46
|
console.log(" Usage:");
|
|
47
47
|
console.log(" npx git-paid <url> Run free 8-point SEO/paywall audit & 5-directory preview");
|
|
48
48
|
console.log(" git paid login <pb_live_key> Authenticate CLI & MCP with your Launch Pass license");
|
|
49
|
-
console.log(" git paid fix [url] [PRO] Write
|
|
49
|
+
console.log(" git paid fix [url] [PRO] Write starter pSEO pages (.postbuild/pseo), robots.txt, sitemap.xml & llms.txt");
|
|
50
50
|
console.log(" git paid submit [url] [PRO] Headless 85-directory form submitter + OTP auto-verifier");
|
|
51
|
-
console.log(" git paid inbox [bot-email] [PRO] Check
|
|
52
|
-
console.log(" git paid mcp Start Postbuild MCP Server (postbuild-mcp v2.1.
|
|
51
|
+
console.log(" git paid inbox [bot-email] [PRO] Check <domain-tld>-bot@postbuild.dev inbox (pass --verify to auto-click links)");
|
|
52
|
+
console.log(" git paid mcp Start Postbuild MCP Server (postbuild-mcp v2.1.7)");
|
|
53
53
|
console.log(" git paid init-agent Scaffold .agent/skills/postbuild/SKILL.md & MCP config");
|
|
54
54
|
console.log(" git paid vault [path] [PRO] Sync live telemetry & Bento dashboard to Obsidian VaultOS\n");
|
|
55
55
|
process.exit(0);
|
|
@@ -153,7 +153,7 @@ async function runCli() {
|
|
|
153
153
|
if (subcmd === "init-agent" || subcmd === "agent" || subcmd === "skill") {
|
|
154
154
|
const res = installAgentSkillFiles({ cwd: process.cwd() });
|
|
155
155
|
console.log(` [+] Scaffolded Agent Skill: ${res.skillMdPath}`);
|
|
156
|
-
console.log(` [+] Powered by: postbuild-mcp@2.1.
|
|
156
|
+
console.log(` [+] Powered by: postbuild-mcp@2.1.7 (Stateful Delta Compaction)\n`);
|
|
157
157
|
console.log(" Add this to your Claude Code / Cursor / Antigravity MCP config:\n");
|
|
158
158
|
console.log(JSON.stringify(res.mcpConfigSnippet, null, 2));
|
|
159
159
|
console.log("");
|
|
@@ -213,7 +213,15 @@ async function runCli() {
|
|
|
213
213
|
if (args.includes("--test")) {
|
|
214
214
|
await sendTestVerificationEmail(inboxAddr, "postbuild.dev");
|
|
215
215
|
}
|
|
216
|
-
const res = await autoVerifyInboxLinks(inboxAddr, {
|
|
216
|
+
const res = await autoVerifyInboxLinks(inboxAddr, {
|
|
217
|
+
autoVerify: args.includes("--verify"),
|
|
218
|
+
bearerToken: license.token
|
|
219
|
+
});
|
|
220
|
+
if (res.requiresDomainVerification) {
|
|
221
|
+
console.log(` [!] Domain ownership verification required for ${license.domain || inboxAddr}:`);
|
|
222
|
+
console.log(` ${res.error || "Verify ownership on https://www.postbuild.dev/?tab=account to unlock your inbox."}\n`);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
217
225
|
console.log(
|
|
218
226
|
` [*] Verification Inbox: ${res.inboxAddress} (${res.totalMessages} messages, ${res.verifiedCount} auto-verified)\n`
|
|
219
227
|
);
|
|
@@ -277,7 +285,9 @@ async function runCli() {
|
|
|
277
285
|
|
|
278
286
|
const fixResult = applyLocalSeoFixes(report, {
|
|
279
287
|
cwd: process.cwd(),
|
|
280
|
-
overwritePublic:
|
|
288
|
+
overwritePublic: args.includes("--force"),
|
|
289
|
+
publishPseoToPublic: args.includes("--publish-pages"),
|
|
290
|
+
verificationToken: license.verificationToken || ""
|
|
281
291
|
});
|
|
282
292
|
|
|
283
293
|
console.log(`\n --- Local Repository Auto-Fix (${fixResult.framework} — Licensed to ${license.email}) ---`);
|
package/lib/auth.js
CHANGED
|
@@ -10,19 +10,178 @@ const otpChallengeState = new Map(); // cid -> { attempts: number, used: boolean
|
|
|
10
10
|
const memoryEntitlements = new Map(); // key (jti or email) -> { jti, email, customerId, plan, domain, inbox, status, expiresAt }
|
|
11
11
|
const processedWebhookEvents = new Set(); // stripe_event_id idempotency set
|
|
12
12
|
|
|
13
|
+
export const FREE_EMAIL_PROVIDERS = new Set([
|
|
14
|
+
"gmail.com",
|
|
15
|
+
"googlemail.com",
|
|
16
|
+
"outlook.com",
|
|
17
|
+
"outlook.de",
|
|
18
|
+
"outlook.fr",
|
|
19
|
+
"outlook.es",
|
|
20
|
+
"outlook.co.uk",
|
|
21
|
+
"hotmail.com",
|
|
22
|
+
"hotmail.co.uk",
|
|
23
|
+
"hotmail.fr",
|
|
24
|
+
"hotmail.de",
|
|
25
|
+
"hotmail.es",
|
|
26
|
+
"hotmail.it",
|
|
27
|
+
"live.com",
|
|
28
|
+
"live.co.uk",
|
|
29
|
+
"live.fr",
|
|
30
|
+
"live.de",
|
|
31
|
+
"msn.com",
|
|
32
|
+
"yahoo.com",
|
|
33
|
+
"yahoo.co.uk",
|
|
34
|
+
"yahoo.ca",
|
|
35
|
+
"yahoo.co.jp",
|
|
36
|
+
"yahoo.fr",
|
|
37
|
+
"yahoo.de",
|
|
38
|
+
"yahoo.es",
|
|
39
|
+
"yahoo.it",
|
|
40
|
+
"yahoo.com.au",
|
|
41
|
+
"yahoo.com.br",
|
|
42
|
+
"yahoo.co.in",
|
|
43
|
+
"ymail.com",
|
|
44
|
+
"icloud.com",
|
|
45
|
+
"me.com",
|
|
46
|
+
"mac.com",
|
|
47
|
+
"aol.com",
|
|
48
|
+
"proton.me",
|
|
49
|
+
"protonmail.com",
|
|
50
|
+
"protonmail.ch",
|
|
51
|
+
"pm.me",
|
|
52
|
+
"zoho.com",
|
|
53
|
+
"gmx.com",
|
|
54
|
+
"gmx.de",
|
|
55
|
+
"gmx.net",
|
|
56
|
+
"gmx.at",
|
|
57
|
+
"gmx.ch",
|
|
58
|
+
"web.de",
|
|
59
|
+
"t-online.de",
|
|
60
|
+
"freenet.de",
|
|
61
|
+
"mail.com",
|
|
62
|
+
"fastmail.com",
|
|
63
|
+
"hey.com",
|
|
64
|
+
"yandex.com",
|
|
65
|
+
"yandex.ru",
|
|
66
|
+
"duck.com",
|
|
67
|
+
"tuta.com",
|
|
68
|
+
"tutanota.com",
|
|
69
|
+
"qq.com",
|
|
70
|
+
"163.com",
|
|
71
|
+
"126.com",
|
|
72
|
+
"sina.com",
|
|
73
|
+
"sohu.com",
|
|
74
|
+
"foxmail.com",
|
|
75
|
+
"naver.com",
|
|
76
|
+
"daum.net",
|
|
77
|
+
"hanmail.net",
|
|
78
|
+
"mail.ru",
|
|
79
|
+
"bk.ru",
|
|
80
|
+
"inbox.ru",
|
|
81
|
+
"list.ru",
|
|
82
|
+
"rambler.ru",
|
|
83
|
+
"seznam.cz",
|
|
84
|
+
"wp.pl",
|
|
85
|
+
"onet.pl",
|
|
86
|
+
"interia.pl",
|
|
87
|
+
"o2.pl",
|
|
88
|
+
"libero.it",
|
|
89
|
+
"virgilio.it",
|
|
90
|
+
"alice.it",
|
|
91
|
+
"orange.fr",
|
|
92
|
+
"wanadoo.fr",
|
|
93
|
+
"free.fr",
|
|
94
|
+
"sfr.fr",
|
|
95
|
+
"laposte.net"
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
export function isFreeEmailDomain(emailDomain = "") {
|
|
99
|
+
const clean = String(emailDomain || "").toLowerCase().trim();
|
|
100
|
+
if (!clean) return true;
|
|
101
|
+
if (FREE_EMAIL_PROVIDERS.has(clean)) return true;
|
|
102
|
+
if (/^(gmail|googlemail|yahoo|ymail|hotmail|outlook|live|msn|icloud|aol|proton|protonmail|zoho|gmx|yandex|tuta|tutanota|fastmail)\.[a-z.]{2,6}$/i.test(clean)) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
13
108
|
export function requireAuthSecret() {
|
|
14
109
|
const explicit = (process.env.POSTBUILD_AUTH_SECRET || process.env.POSTBUILD_SECRET || "").trim();
|
|
15
110
|
if (explicit.length >= 32) {
|
|
16
111
|
return explicit;
|
|
17
112
|
}
|
|
18
|
-
// Safe domain-separated derivation on Vercel if POSTBUILD_AUTH_SECRET has not been added to project env yet
|
|
19
|
-
const resendSeed = (process.env.RESEND_API_KEY || "").trim();
|
|
20
|
-
if (process.env.VERCEL && resendSeed.length >= 24 && !process.env.POSTBUILD_STRICT_SECRET_TEST) {
|
|
21
|
-
return crypto.createHmac("sha256", resendSeed).update("postbuild:auth:v2:hmac-signing-key").digest("hex");
|
|
22
|
-
}
|
|
23
113
|
throw new Error("POSTBUILD_SECRET or POSTBUILD_AUTH_SECRET is missing or too short (minimum 32 characters required)");
|
|
24
114
|
}
|
|
25
115
|
|
|
116
|
+
export function getDomainVerificationToken(domain = "", email = "") {
|
|
117
|
+
const cleanDomain = String(domain || "")
|
|
118
|
+
.toLowerCase()
|
|
119
|
+
.trim()
|
|
120
|
+
.replace(/^https?:\/\//, "")
|
|
121
|
+
.replace(/^www\./, "")
|
|
122
|
+
.split("/")[0]
|
|
123
|
+
.split(":")[0];
|
|
124
|
+
const cleanEmail = String(email || "").toLowerCase().trim();
|
|
125
|
+
const verifyKey = (process.env.POSTBUILD_VERIFY_SECRET || "").trim();
|
|
126
|
+
const secret = verifyKey.length >= 16 ? verifyKey : requireAuthSecret();
|
|
127
|
+
const digest = crypto
|
|
128
|
+
.createHmac("sha256", secret)
|
|
129
|
+
.update(`domain-verify:${cleanDomain}:${cleanEmail}`)
|
|
130
|
+
.digest("hex")
|
|
131
|
+
.slice(0, 32);
|
|
132
|
+
return `postbuild-verify=${digest}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function getCandidateVerificationTokens(domain = "", email = "") {
|
|
136
|
+
const cleanDomain = String(domain || "")
|
|
137
|
+
.toLowerCase()
|
|
138
|
+
.trim()
|
|
139
|
+
.replace(/^https?:\/\//, "")
|
|
140
|
+
.replace(/^www\./, "")
|
|
141
|
+
.split("/")[0]
|
|
142
|
+
.split(":")[0];
|
|
143
|
+
const cleanEmail = String(email || "").toLowerCase().trim();
|
|
144
|
+
const secrets = [
|
|
145
|
+
(process.env.POSTBUILD_VERIFY_SECRET || "").trim(),
|
|
146
|
+
(() => {
|
|
147
|
+
try {
|
|
148
|
+
return requireAuthSecret();
|
|
149
|
+
} catch {
|
|
150
|
+
return "";
|
|
151
|
+
}
|
|
152
|
+
})()
|
|
153
|
+
].filter((s) => s.length >= 16);
|
|
154
|
+
const tokens = new Set([getDomainVerificationToken(cleanDomain, cleanEmail)]);
|
|
155
|
+
for (const s of secrets) {
|
|
156
|
+
const digest = crypto
|
|
157
|
+
.createHmac("sha256", s)
|
|
158
|
+
.update(`domain-verify:${cleanDomain}:${cleanEmail}`)
|
|
159
|
+
.digest("hex")
|
|
160
|
+
.slice(0, 32);
|
|
161
|
+
tokens.add(`postbuild-verify=${digest}`);
|
|
162
|
+
}
|
|
163
|
+
return Array.from(tokens);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function isSameDomainVerifiedEmail(email = "", domain = "", emailVerified = false) {
|
|
167
|
+
const cleanEmail = String(email || "").toLowerCase().trim();
|
|
168
|
+
const cleanDomain = String(domain || "")
|
|
169
|
+
.toLowerCase()
|
|
170
|
+
.trim()
|
|
171
|
+
.replace(/^https?:\/\//, "")
|
|
172
|
+
.replace(/^www\./, "")
|
|
173
|
+
.split("/")[0]
|
|
174
|
+
.split(":")[0];
|
|
175
|
+
if (!cleanEmail || !cleanDomain || !cleanDomain.includes(".")) return false;
|
|
176
|
+
if (getFounderEmailsSet().has(cleanEmail) && cleanDomain === "postbuild.dev") {
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
if (!emailVerified) return false;
|
|
180
|
+
const emailDomain = cleanEmail.includes("@") ? cleanEmail.split("@")[1] : "";
|
|
181
|
+
if (!emailDomain || isFreeEmailDomain(emailDomain)) return false;
|
|
182
|
+
return cleanDomain === emailDomain || cleanDomain.endsWith(`.${emailDomain}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
26
185
|
function timingSafeStringEqual(a, b) {
|
|
27
186
|
if (typeof a !== "string" || typeof b !== "string") return false;
|
|
28
187
|
const bufA = Buffer.from(a, "utf8");
|
|
@@ -244,6 +403,7 @@ export async function verifyCliLicense({ domain = "postbuild.dev", token = "" }
|
|
|
244
403
|
plan: localCheck.plan,
|
|
245
404
|
domain: localCheck.domain,
|
|
246
405
|
inbox: localCheck.inbox,
|
|
406
|
+
verificationToken: getDomainVerificationToken(localCheck.domain, localCheck.email),
|
|
247
407
|
jti: localCheck.jti,
|
|
248
408
|
token: candidateToken
|
|
249
409
|
};
|
|
@@ -294,6 +454,7 @@ export async function verifyCliLicense({ domain = "postbuild.dev", token = "" }
|
|
|
294
454
|
plan: data.plan,
|
|
295
455
|
domain: data.domain,
|
|
296
456
|
inbox: data.inbox,
|
|
457
|
+
verificationToken: data.verificationToken || "",
|
|
297
458
|
token: candidateToken
|
|
298
459
|
};
|
|
299
460
|
}
|
|
@@ -320,6 +481,7 @@ export function signLicenseToken({
|
|
|
320
481
|
domain = "postbuild.dev",
|
|
321
482
|
jti = "",
|
|
322
483
|
customerId = null,
|
|
484
|
+
emailVerified = true,
|
|
323
485
|
ttlSeconds = 365 * 24 * 3600
|
|
324
486
|
}) {
|
|
325
487
|
const secret = requireAuthSecret();
|
|
@@ -353,6 +515,7 @@ export function signLicenseToken({
|
|
|
353
515
|
plan,
|
|
354
516
|
domain: cleanDomain,
|
|
355
517
|
inbox,
|
|
518
|
+
emailVerified: Boolean(emailVerified),
|
|
356
519
|
iat: now,
|
|
357
520
|
exp
|
|
358
521
|
};
|
|
@@ -403,7 +566,7 @@ export function verifyLicenseToken(token) {
|
|
|
403
566
|
if (!payload.inbox && payload.domain) {
|
|
404
567
|
payload.inbox = getBotInboxForDomain(payload.domain, payload.email);
|
|
405
568
|
}
|
|
406
|
-
return { valid: true, ...payload };
|
|
569
|
+
return { valid: true, ...payload, emailVerified: Boolean(payload.emailVerified) };
|
|
407
570
|
} catch {
|
|
408
571
|
return { valid: false };
|
|
409
572
|
}
|
|
@@ -566,13 +729,17 @@ async function sendLoginOtpViaResend(email, otpCode) {
|
|
|
566
729
|
},
|
|
567
730
|
body: JSON.stringify({
|
|
568
731
|
from: "postbuild <login@postbuild.dev>",
|
|
732
|
+
reply_to: "support@postbuild.dev",
|
|
569
733
|
to: recipients,
|
|
570
734
|
subject: `${otpCode} is your postbuild verification code`,
|
|
571
|
-
html: `<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, sans-serif; max-width: 520px; margin: 0 auto; padding:
|
|
572
|
-
<
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
<
|
|
735
|
+
html: `<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, Roboto, sans-serif; max-width: 520px; margin: 0 auto; padding: 24px; border: 1px solid #e2e8f0; border-radius: 14px; background: #ffffff; color: #0f172a;">
|
|
736
|
+
<div style="margin-bottom: 16px;">
|
|
737
|
+
<span style="display: inline-block; background: #ecfdf5; color: #047857; border: 1px solid #a7f3d0; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; padding: 3px 8px; border-radius: 999px;">Security Code</span>
|
|
738
|
+
</div>
|
|
739
|
+
<h2 style="margin: 0 0 8px; font-size: 20px; font-weight: 700; color: #0f172a;">Verify your postbuild sign-in</h2>
|
|
740
|
+
<p style="color: #475569; font-size: 14px; line-height: 1.6; margin: 0 0 18px;">Enter this 6-digit verification code on <strong>postbuild.dev</strong> within 10 minutes to access your license key, verification inbox, and receipts:</p>
|
|
741
|
+
<div style="background: #ecfdf5; border: 1px solid #6ee7b7; color: #047857; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 28px; font-weight: 800; letter-spacing: 6px; text-align: center; padding: 14px; border-radius: 10px; margin: 0 0 18px;">${otpCode}</div>
|
|
742
|
+
<p style="color: #64748b; font-size: 12px; line-height: 1.5; margin: 0;">If you did not request this code, you can safely ignore this email.</p>
|
|
576
743
|
</div>`
|
|
577
744
|
})
|
|
578
745
|
});
|
|
@@ -599,9 +766,16 @@ function escapeHtml(str) {
|
|
|
599
766
|
export async function sendLicenseFulfillmentEmail({ email, licenseToken, planId, targetDomain }) {
|
|
600
767
|
const resendKey = (process.env.RESEND_API_KEY || "").trim();
|
|
601
768
|
if (!resendKey || !email) return { sent: false };
|
|
602
|
-
const
|
|
603
|
-
const
|
|
769
|
+
const rawDomain = targetDomain || "postbuild.dev";
|
|
770
|
+
const safeDomain = escapeHtml(rawDomain);
|
|
771
|
+
const inbox = escapeHtml(getBotInboxForDomain(rawDomain, email));
|
|
604
772
|
const safeToken = escapeHtml(licenseToken);
|
|
773
|
+
const planName = planId === "studio_monthly" ? "postbuild Studio" : "postbuild Launch Pass";
|
|
774
|
+
const badgeLabel = planId === "studio_monthly" ? "Studio Active" : "Launch Pass Active";
|
|
775
|
+
const autoVerifiedByEmail = isSameDomainVerifiedEmail(email, rawDomain, true);
|
|
776
|
+
const inboxStatusNote = autoVerifiedByEmail
|
|
777
|
+
? "Confirm your email on Account tab (or use key below) to open"
|
|
778
|
+
: "Opens after domain check on Account tab";
|
|
605
779
|
try {
|
|
606
780
|
const resp = await fetch("https://api.resend.com/emails", {
|
|
607
781
|
method: "POST",
|
|
@@ -611,15 +785,49 @@ export async function sendLicenseFulfillmentEmail({ email, licenseToken, planId,
|
|
|
611
785
|
},
|
|
612
786
|
body: JSON.stringify({
|
|
613
787
|
from: "postbuild <login@postbuild.dev>",
|
|
788
|
+
reply_to: "support@postbuild.dev",
|
|
614
789
|
to: [email],
|
|
615
|
-
subject: `Your
|
|
616
|
-
html: `<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, sans-serif; max-width: 540px; margin: 0 auto; padding:
|
|
617
|
-
<
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
<
|
|
621
|
-
<
|
|
622
|
-
|
|
790
|
+
subject: `Your ${planName} for ${rawDomain} is active`,
|
|
791
|
+
html: `<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, Roboto, sans-serif; max-width: 540px; margin: 0 auto; padding: 24px; border: 1px solid #e2e8f0; border-radius: 14px; background: #ffffff; color: #0f172a;">
|
|
792
|
+
<div style="margin-bottom: 14px;">
|
|
793
|
+
<span style="display: inline-block; background: #ecfdf5; color: #047857; border: 1px solid #a7f3d0; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; padding: 3px 9px; border-radius: 999px;">${escapeHtml(badgeLabel)}</span>
|
|
794
|
+
</div>
|
|
795
|
+
<h2 style="margin: 0 0 8px; font-size: 20px; font-weight: 700; color: #0f172a;">Your ${escapeHtml(planName)} for ${safeDomain} is ready</h2>
|
|
796
|
+
<p style="color: #475569; font-size: 14px; line-height: 1.6; margin: 0 0 18px;">All 85 startup & AI directories, your SEO Launch Bundle (<code>.zip</code>), and your verification inbox (<code>${inbox}</code>) are included.${autoVerifiedByEmail ? ` Confirm your <strong>@${safeDomain}</strong> email once on your Account tab (or sign in with the license key below) to open <code>${inbox}</code>.` : ` Because your checkout email is not on <strong>@${safeDomain}</strong>, complete the domain ownership check on your Account tab (and keep the verification file or meta tag in place) before submitting to directories so you can read incoming confirmation codes.`}</p>
|
|
797
|
+
|
|
798
|
+
<div style="margin: 0 0 20px;">
|
|
799
|
+
<a href="https://www.postbuild.dev/?tab=account" style="display: inline-block; background: #047857; color: #ffffff; text-decoration: none; font-weight: 600; font-size: 14px; padding: 11px 18px; border-radius: 8px;">Open Account & Download .zip Bundle →</a>
|
|
800
|
+
</div>
|
|
801
|
+
|
|
802
|
+
<table role="presentation" style="width: 100%; border-collapse: collapse; margin: 0 0 18px; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 10px;">
|
|
803
|
+
<tr>
|
|
804
|
+
<td style="padding: 10px 14px; border-bottom: 1px solid #e2e8f0; font-size: 12px; color: #64748b; font-weight: 600;">Licensed Domain</td>
|
|
805
|
+
<td style="padding: 10px 14px; border-bottom: 1px solid #e2e8f0; font-size: 13px; color: #0f172a; font-weight: 700; text-align: right;">${safeDomain}</td>
|
|
806
|
+
</tr>
|
|
807
|
+
<tr>
|
|
808
|
+
<td style="padding: 10px 14px; font-size: 12px; color: #64748b; font-weight: 600;">Verification Inbox</td>
|
|
809
|
+
<td style="padding: 10px 14px; font-size: 12px; color: #047857; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 600; text-align: right; word-break: break-all;">${inbox}<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, sans-serif; font-size: 11px; color: #64748b; font-weight: 500; margin-top: 2px; word-break: normal; overflow-wrap: break-word;">${escapeHtml(inboxStatusNote)}</div></td>
|
|
810
|
+
</tr>
|
|
811
|
+
</table>
|
|
812
|
+
|
|
813
|
+
<div style="border-top: 1px solid #e2e8f0; padding-top: 16px; margin-bottom: 18px;">
|
|
814
|
+
<div style="font-size: 13px; font-weight: 700; color: #0f172a; margin-bottom: 8px;">3-Step Quickstart</div>
|
|
815
|
+
<ol style="margin: 0; padding-left: 18px; color: #475569; font-size: 13px; line-height: 1.6;">
|
|
816
|
+
<li style="margin-bottom: 6px;"><strong>Deploy Your .zip Launch Bundle:</strong> Click <em>Download .zip</em> on your Account tab (includes <code>START_HERE.md</code>, <code>public/.well-known/postbuild-verify.txt</code>, starter HTML SEO pages, and <code>directories-85.csv</code>) and deploy to <code>${safeDomain}</code>.</li>
|
|
817
|
+
<li style="margin-bottom: 6px;"><strong>Verify Domain Ownership:</strong> ${autoVerifiedByEmail ? `Confirm your <code>@${safeDomain}</code> email once on your Account tab (or paste the license key below) to open <code>${inbox}</code>.` : `Click <em>Verify Domain Now</em> on your Account tab after deploying your bundle (or adding the meta tag / DNS TXT record) and keep it in place to unlock <code>${inbox}</code>.`}</li>
|
|
818
|
+
<li><strong>Submit to 85 Directories:</strong> Launch prefilled directory links from the Dispatch tab or <code>directories-85.csv</code> using <code>${inbox}</code> — 6-digit OTPs and confirmation links arrive in your Account Inbox.</li>
|
|
819
|
+
</ol>
|
|
820
|
+
</div>
|
|
821
|
+
|
|
822
|
+
<div style="margin: 0 0 18px;">
|
|
823
|
+
<div style="font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: #64748b; margin-bottom: 4px;">CLI & MCP License Key</div>
|
|
824
|
+
<div style="font-size: 12px; color: #64748b; margin-bottom: 6px;">Most buyers never need this — you can sign in with your email at <strong>postbuild.dev</strong>, or copy this key for <code>npx git-paid login</code>:</div>
|
|
825
|
+
<div style="background: #f8fafc; border: 1px solid #cbd5e1; padding: 12px; border-radius: 8px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; line-height: 1.5; color: #0f172a; word-break: break-all;">${safeToken}</div>
|
|
826
|
+
</div>
|
|
827
|
+
|
|
828
|
+
<div style="border-top: 1px solid #e2e8f0; padding-top: 12px; font-size: 12px; color: #64748b; line-height: 1.5;">
|
|
829
|
+
Covered by our 14-day refund guarantee. Questions? Reply to this email or contact <a href="mailto:support@postbuild.dev" style="color: #047857; text-decoration: none; font-weight: 600;">support@postbuild.dev</a>.
|
|
830
|
+
</div>
|
|
623
831
|
</div>`
|
|
624
832
|
})
|
|
625
833
|
});
|
|
@@ -705,10 +913,13 @@ export async function authenticateUser({ email, token, sessionId, otpCode, chall
|
|
|
705
913
|
ok: true,
|
|
706
914
|
authenticated: true,
|
|
707
915
|
email: verified.email,
|
|
916
|
+
emailVerified: Boolean(verified.emailVerified),
|
|
917
|
+
domainVerified: isSameDomainVerifiedEmail(verified.email, verified.domain, verified.emailVerified),
|
|
708
918
|
customerId: verified.customerId || null,
|
|
709
919
|
plan: verified.plan,
|
|
710
920
|
domain: verified.domain,
|
|
711
921
|
inbox: verified.inbox,
|
|
922
|
+
verificationToken: getDomainVerificationToken(verified.domain, verified.email),
|
|
712
923
|
isFounder: founderEmails.has(String(verified.email || "").toLowerCase().trim()),
|
|
713
924
|
token
|
|
714
925
|
};
|
|
@@ -729,15 +940,18 @@ export async function authenticateUser({ email, token, sessionId, otpCode, chall
|
|
|
729
940
|
const plan = existingEnt?.plan || check.plan || "launch_pass";
|
|
730
941
|
const domain = existingEnt?.domain || check.domain || "postbuild.dev";
|
|
731
942
|
const customerId = existingEnt?.customerId || check.customerId || null;
|
|
732
|
-
const issued = signLicenseToken({ email: check.email, plan, domain, customerId });
|
|
943
|
+
const issued = signLicenseToken({ email: check.email, plan, domain, customerId, emailVerified: true });
|
|
733
944
|
return {
|
|
734
945
|
ok: true,
|
|
735
946
|
authenticated: true,
|
|
736
947
|
email: check.email,
|
|
948
|
+
emailVerified: true,
|
|
949
|
+
domainVerified: isSameDomainVerifiedEmail(check.email, domain, true),
|
|
737
950
|
customerId,
|
|
738
951
|
plan,
|
|
739
952
|
domain,
|
|
740
953
|
inbox: getBotInboxForDomain(domain, check.email),
|
|
954
|
+
verificationToken: getDomainVerificationToken(domain, check.email),
|
|
741
955
|
isFounder: founderEmails.has(String(check.email || "").toLowerCase().trim()),
|
|
742
956
|
token: issued
|
|
743
957
|
};
|
|
@@ -749,7 +963,7 @@ export async function authenticateUser({ email, token, sessionId, otpCode, chall
|
|
|
749
963
|
stripe = stripeMod.getStripeClient();
|
|
750
964
|
} catch {}
|
|
751
965
|
|
|
752
|
-
// 3. Stripe Checkout return session_id verification
|
|
966
|
+
// 3. Stripe Checkout return session_id verification (checkout email is NOT verified by Stripe)
|
|
753
967
|
if (sessionId && stripe) {
|
|
754
968
|
const session = await stripe.checkout.sessions.retrieve(sessionId, { expand: ["customer"] });
|
|
755
969
|
if (session && session.payment_status !== "unpaid") {
|
|
@@ -777,15 +991,18 @@ export async function authenticateUser({ email, token, sessionId, otpCode, chall
|
|
|
777
991
|
if (custStatus !== "active" || isEntitlementRevoked({ email: custEmail, customerId, plan })) {
|
|
778
992
|
return { ok: false, authenticated: false, error: "This license entitlement has been revoked or refunded." };
|
|
779
993
|
}
|
|
780
|
-
const newToken = signLicenseToken({ email: custEmail, plan, domain, customerId });
|
|
994
|
+
const newToken = signLicenseToken({ email: custEmail, plan, domain, customerId, emailVerified: false });
|
|
781
995
|
return {
|
|
782
996
|
ok: true,
|
|
783
997
|
authenticated: true,
|
|
784
998
|
email: custEmail,
|
|
999
|
+
emailVerified: false,
|
|
1000
|
+
domainVerified: false,
|
|
785
1001
|
customerId,
|
|
786
1002
|
plan,
|
|
787
1003
|
domain,
|
|
788
1004
|
inbox: getBotInboxForDomain(domain, custEmail),
|
|
1005
|
+
verificationToken: getDomainVerificationToken(domain, custEmail),
|
|
789
1006
|
isFounder: false,
|
|
790
1007
|
token: newToken
|
|
791
1008
|
};
|