Repository navigation
feat(pwa): credit codes + scarce invites on app.moshcode.sh - #591
Merged
Merged
Conversation
Codes (@profullstack/redeem-credits, MC-XXXX-XXXX-XXXX-XXXX) grant moshcode
credits INTO the existing credit_ledger: one row per redemption, keyed on
(user_id, source) with a unique index, so a grant can never land twice. The
module keeps only its code/redemption tables (prefix redeem_, so it does not
collide with credit_ledger). Redeem on /settings ("Have a code?"), /redeem?code=,
or POST /api/credits/redeem with an API key (never a cookie: /api is outside CSRF).
Invites (@profullstack/affiliates): 3 per account at /invites, landing at
/i/<code> (sign-up form carrying the code + a signed cookie). Accepted only when
the email sign-up creates the account; one accepted invite per account. The
bonus (INVITE_BONUS_CREDITS, default 100 each side) is paid only after the
invitee confirms their email through a signed link that works in any browser,
and is refused for throwaway domains, the same mailbox (+tags, Gmail dots), an
inviter under 24h old, or more than 10 bonuses per inviter in 30 days. Every
decision is recorded in invite_bonuses. ?ref= links set the shared
referral_code cookie (first touch) and are attributed at sign-up; no commission.
Operators mint with scripts/credit-codes.mjs (docker exec) or
/api/admin/credit-codes (ADMIN_EMAILS). Nothing touches the Moshpit registry,
name sales, ending sales or the CoinPay webhook.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
|
||
| async function signup(base, email, extra = {}) { | ||
| const b = browser(base); | ||
| const r = await b.post("/auth/register", { email, password: "correct-horse", ...extra }); |
| assert.match(landing.text, /name="invite" value="INV-/); | ||
| assert.ok(guest.jar.has("mc_invite"), "the invite rides in a cookie too"); | ||
|
|
||
| const reg = await guest.post("/auth/register", { email: "invitee@moshtest.dev", password: "correct-horse", invite: inviter.code }); |
| assert.equal(Number(theirs[0].delta), 250); | ||
|
|
||
| // The invite is spent. | ||
| const late = await browser(base).post("/auth/register", { email: "late@moshtest.dev", password: "correct-horse", invite: inviter.code }); |
| test("invite bonuses are refused for the same mailbox, a too-new inviter and a throwaway domain", { skip }, async () => { | ||
| const { base, db } = await app(); | ||
| const a = await inviterWithInvite(base, db, "dave.smith@gmail.com"); | ||
| await browser(base).post("/auth/register", { email: "davesmith+alt@gmail.com", password: "correct-horse", invite: a.code }); |
| assert.deepEqual({ ...r1 }, { status: "refused", reason: "same_mailbox" }); | ||
|
|
||
| const young = await inviterWithInvite(base, db, "young@moshtest.dev", { ageHours: 1 }); | ||
| await browser(base).post("/auth/register", { email: "friend@moshtest.dev", password: "correct-horse", invite: young.code }); |
| assert.equal(r2.reason, "inviter_too_new"); | ||
|
|
||
| const c = await inviterWithInvite(base, db, "c@moshtest.dev"); | ||
| await browser(base).post("/auth/register", { email: "x@mailinator.com", password: "correct-horse", invite: c.code }); |
| const { base, db, codes } = await app(); | ||
| const a = await inviterWithInvite(base, db, "one@moshtest.dev"); | ||
| const b = await inviterWithInvite(base, db, "two@moshtest.dev"); | ||
| await browser(base).post("/auth/register", { email: "target@moshtest.dev", password: "correct-horse", invite: a.code }); |
| await v.get(`/?ref=${ref}`); | ||
| await v.get(`/?ref=SOMEONEELSE`); | ||
| assert.equal(v.jar.get("referral_code"), ref); | ||
| await v.post("/auth/register", { email: "referred@moshtest.dev", password: "correct-horse" }); |
ThreatCrush Security Scan8 finding(s) in the 14 file(s) this pull request changes. LOW: 8
106 pre-existing finding(s) elsewhere in the repository — **HIGH/CRITICAL**: 8 | **MEDIUM**: 87 | **LOW**: 11Not introduced by this pull request. The full set is in the Security tab.
…and 86 more. Full results in the Security tab. Snippets are redacted; ThreatCrush never prints matched credential material. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fleet credits rollout, batch 10 (app.moshcode.sh). PWA only; the CLI is untouched.
What a code grants
MC-XXXX-XXXX-XXXX-XXXXcodes (@profullstack/redeem-credits0.2) grant moshcode credits straight into the existingcredit_ledger, the same balance CoinPay packs top up. One ledger row per redemption,source = code:<id>, unique on(user_id, source)(new column + index, migration 027), so a retried grant can never land twice. The module only keeps its code/redemption tables, prefixedredeem_so they do not collide withcredit_ledger./settingsgets a "Have a code?" card;/redeem?code=prefilled page (sign-in first if needed); dashboard credits card links both.POST /api/credits/redeem {code}withAuthorization: Bearer mck_...(API key only:/api/sits outside the CSRF guard, so a cookie is refused).docker exec appmoshcodesh-app-1 bun apps/pwa/scripts/credit-codes.mjs mint --credits 1000 --label "for X"(alsolist,revoke <id>,quota <email> <n>), orPOST /api/admin/credit-codesforADMIN_EMAILSoperators.Invites
@profullstack/affiliates0.3: 3 invites per account on/invites, single use, 30 days, landing at/i/<code>(email sign-up form carrying the code, plus a signed cookie). Accepted only when the email sign-up creates the account; one accepted invite per account.The bonus (
INVITE_BONUS_CREDITS, default 100 each side, same as the sign-up bonus) is paid only after the invitee confirms their email with a signed link that works in any browser. It is refused for throwaway domains, the same mailbox (+tags, Gmail dots), an inviter younger than 24h, or more than 10 bonuses per inviter in 30 days. Every decision is recorded ininvite_bonuses.?ref=sets the sharedreferral_codecookie (first touch wins), and the referrer is recorded at sign-up (aff_attributions). No commission is paid.Not touched
Moshpit registry, name registration, ending sales, offers,
/webhooks/coinpay(still not rate-limited, by the app or by nginx).Tests
test/credit-codes.test.mjs(8 tests) passes on node, on bun, and against Postgres (pg-test-preload).🤖 Generated with Claude Code