create-cartbase 0.1.16 → 0.1.18
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/LICENSE +21 -21
- package/README.md +25 -25
- package/dist/index.js +20 -20
- package/package.json +1 -1
- package/template/app/docs/auth.md +105 -105
- package/template/app/docs/carts.md +376 -376
- package/template/app/docs/categories.md +194 -194
- package/template/app/docs/checkout.md +714 -714
- package/template/app/docs/components.md +55 -11
- package/template/app/docs/consent.md +91 -91
- package/template/app/docs/deploy.md +197 -197
- package/template/app/docs/gift-cards.md +153 -153
- package/template/app/docs/metaobjects.md +126 -126
- package/template/app/docs/orders.md +221 -221
- package/template/app/docs/products.md +51 -2
- package/template/app/docs/regions.md +269 -269
- package/template/app/docs/reviews.md +223 -223
- package/template/app/docs/search.md +227 -227
- package/template/app/docs/store.md +47 -47
- package/template/app/docs/subscriptions.md +148 -148
- package/template/app/docs/variables.md +315 -315
- package/template/app/package.json +1 -1
- package/template/app/postcss.config.cjs +11 -11
- package/template/app/src/app/checkout/checkout-page-client.tsx +73 -73
- package/template/app/src/app/checkout/page.tsx +48 -48
- package/template/app/src/app/globals.css +26 -26
- package/template/app/src/app/page.tsx +28 -28
- package/template/app/src/app/products/[handle]/page.tsx +87 -87
- package/template/app/src/app/providers.tsx +68 -64
- package/template/app/src/app/search/page.tsx +23 -23
- package/template/app/src/lib/browser-client.ts +35 -35
- package/template/app/src/lib/config.ts +41 -41
- package/template/app/src/lib/server-client.ts +25 -25
- package/template/app/src/lib/cart-actions.ts +0 -47
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Cartbase
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cartbase
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
# create-cartbase
|
|
2
|
-
|
|
3
|
-
Scaffold a storefront on the [Cartbase](https://cartbase.net) commerce
|
|
4
|
-
platform. One input: your store's publishable key, handed out on the
|
|
5
|
-
**Storefront** page of your Cartbase admin.
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npx create-cartbase@latest my-store --key pk_…
|
|
9
|
-
# or
|
|
10
|
-
npm create cartbase my-store -- --key pk_…
|
|
11
|
-
bun create cartbase my-store --key pk_…
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
You get a working Next.js storefront wired to your store through
|
|
15
|
-
`@cartbase/storefront`, styled, with the **complete platform docs
|
|
16
|
-
included in `docs/`** and a `CLAUDE.md` brief. Hand the folder to a
|
|
17
|
-
coding agent and it has everything it needs offline. `--url` points the
|
|
18
|
-
app at a local or staging platform; without it the app talks to the
|
|
19
|
-
platform. Deploy with the
|
|
20
|
-
[`cartbase`](https://www.npmjs.com/package/cartbase) CLI: `cartbase login`,
|
|
21
|
-
then `cartbase deploy`.
|
|
22
|
-
|
|
23
|
-
Run it in a folder of its own. Inside an existing project, Next.js takes
|
|
24
|
-
that project's folder as the workspace root; the scaffold notices and
|
|
25
|
-
prints the one line to add to `next.config.ts` if that is what you meant.
|
|
1
|
+
# create-cartbase
|
|
2
|
+
|
|
3
|
+
Scaffold a storefront on the [Cartbase](https://cartbase.net) commerce
|
|
4
|
+
platform. One input: your store's publishable key, handed out on the
|
|
5
|
+
**Storefront** page of your Cartbase admin.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx create-cartbase@latest my-store --key pk_…
|
|
9
|
+
# or
|
|
10
|
+
npm create cartbase my-store -- --key pk_…
|
|
11
|
+
bun create cartbase my-store --key pk_…
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
You get a working Next.js storefront wired to your store through
|
|
15
|
+
`@cartbase/storefront`, styled, with the **complete platform docs
|
|
16
|
+
included in `docs/`** and a `CLAUDE.md` brief. Hand the folder to a
|
|
17
|
+
coding agent and it has everything it needs offline. `--url` points the
|
|
18
|
+
app at a local or staging platform; without it the app talks to the
|
|
19
|
+
platform. Deploy with the
|
|
20
|
+
[`cartbase`](https://www.npmjs.com/package/cartbase) CLI: `cartbase login`,
|
|
21
|
+
then `cartbase deploy`.
|
|
22
|
+
|
|
23
|
+
Run it in a folder of its own. Inside an existing project, Next.js takes
|
|
24
|
+
that project's folder as the workspace root; the scaffold notices and
|
|
25
|
+
prints the one line to add to `next.config.ts` if that is what you meant.
|
package/dist/index.js
CHANGED
|
@@ -53,13 +53,13 @@ function envFile(input) {
|
|
|
53
53
|
function main() {
|
|
54
54
|
const { dir, flags } = parseArgs(process.argv.slice(2));
|
|
55
55
|
if (!dir || flags.get("help") === true) {
|
|
56
|
-
console.log(`create-cartbase — scaffold a Cartbase storefront
|
|
57
|
-
|
|
58
|
-
Usage:
|
|
59
|
-
npx create-cartbase@latest <directory> --key <pk_…>
|
|
60
|
-
|
|
61
|
-
The key is your store's publishable key (Cartbase admin, Storefront page).
|
|
62
|
-
Given as a flag it is written into .env.local; otherwise a placeholder is.
|
|
56
|
+
console.log(`create-cartbase — scaffold a Cartbase storefront
|
|
57
|
+
|
|
58
|
+
Usage:
|
|
59
|
+
npx create-cartbase@latest <directory> --key <pk_…>
|
|
60
|
+
|
|
61
|
+
The key is your store's publishable key (Cartbase admin, Storefront page).
|
|
62
|
+
Given as a flag it is written into .env.local; otherwise a placeholder is.
|
|
63
63
|
--url points at a local or staging platform; the default is the platform.`);
|
|
64
64
|
process.exitCode = dir ? 0 : 1;
|
|
65
65
|
return;
|
|
@@ -86,21 +86,21 @@ Given as a flag it is written into .env.local; otherwise a placeholder is.
|
|
|
86
86
|
// A store scaffolded inside another project inherits that project's
|
|
87
87
|
// workspace root in Next's eyes; said once, here, while it is cheap to move.
|
|
88
88
|
const enclosing = findEnclosingProject(target);
|
|
89
|
-
console.log(`
|
|
90
|
-
Created ${path.basename(target)}/
|
|
91
|
-
|
|
92
|
-
Next steps:
|
|
93
|
-
cd ${dir}
|
|
89
|
+
console.log(`
|
|
90
|
+
Created ${path.basename(target)}/
|
|
91
|
+
|
|
92
|
+
Next steps:
|
|
93
|
+
cd ${dir}
|
|
94
94
|
bun install (or npm install)${key
|
|
95
95
|
? ""
|
|
96
|
-
: `
|
|
97
|
-
open .env.local and paste your store's publishable key
|
|
98
|
-
(Cartbase admin, Storefront page)`}
|
|
99
|
-
bun run dev
|
|
100
|
-
|
|
101
|
-
The complete storefront reference is in docs/ — hand CLAUDE.md (or
|
|
102
|
-
AGENTS.md) to your coding agent and it has everything. Deploy with the
|
|
103
|
-
Cartbase CLI: cartbase login, then cartbase deploy.
|
|
96
|
+
: `
|
|
97
|
+
open .env.local and paste your store's publishable key
|
|
98
|
+
(Cartbase admin, Storefront page)`}
|
|
99
|
+
bun run dev
|
|
100
|
+
|
|
101
|
+
The complete storefront reference is in docs/ — hand CLAUDE.md (or
|
|
102
|
+
AGENTS.md) to your coding agent and it has everything. Deploy with the
|
|
103
|
+
Cartbase CLI: cartbase login, then cartbase deploy.
|
|
104
104
|
${enclosing ? `\n${enclosingProjectNote(target, enclosing)}\n` : ""}`);
|
|
105
105
|
}
|
|
106
106
|
main();
|
package/package.json
CHANGED
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
# Auth
|
|
2
|
-
|
|
3
|
-
Passwordless email-code login (customer-accounts card, Shopify Customer
|
|
4
|
-
Account API direction). Two calls: **request** emails a 6-digit code,
|
|
5
|
-
**verify** exchanges it for a session. Password login
|
|
6
|
-
(`supabase.auth.signInWithPassword` client-side) stays available and yields
|
|
7
|
-
an equivalent JWT — nothing here is needed for it.
|
|
8
|
-
|
|
9
|
-
## The session discipline (one rule)
|
|
10
|
-
|
|
11
|
-
`verify` mints a **real Supabase session**. Its `access_token` is the SAME
|
|
12
|
-
`authorization: Bearer <jwt>` every `/api/store/*` route accepts — wire it
|
|
13
|
-
into `StorefrontClient` once and every customer surface (customers/me,
|
|
14
|
-
orders, documents, cart attach) is authenticated:
|
|
15
|
-
|
|
16
|
-
```jsonc
|
|
17
|
-
// new StorefrontClient({ baseUrl, clientId, getAuthToken: () => storedAccessToken })
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Persist `access_token` + `refresh_token`; refresh client-side with the
|
|
21
|
-
Supabase SDK (`setSession` → `refreshSession`) — the Cartbase API does not
|
|
22
|
-
proxy token refresh. `expires_in` is seconds; `expires_at` epoch-seconds.
|
|
23
|
-
|
|
24
|
-
## Server-enforced security posture (the SDK adds nothing)
|
|
25
|
-
|
|
26
|
-
- Request NEVER reveals whether the email is registered — `{ok: true}`
|
|
27
|
-
always (no enumeration oracle). The code exists ONLY in the sent email.
|
|
28
|
-
- Codes: hashed at rest, TTL **10 min**, single-use, one live code per
|
|
29
|
-
email (a new request supersedes prior codes), **5-wrong-attempt lockout**.
|
|
30
|
-
- Rate limits per 15-min window: **5 requests per email**, **20 per IP** →
|
|
31
|
-
`429 rate_limited` — the only distinguishable request failure.
|
|
32
|
-
- Every verify failure — wrong code, expired, consumed, locked out, unknown
|
|
33
|
-
email — is the SAME `401 invalid_code`.
|
|
34
|
-
|
|
35
|
-
## POST /api/store/auth/code/request — step 1
|
|
36
|
-
|
|
37
|
-
- **Purpose**: email a 6-digit one-time login code. First-time emails
|
|
38
|
-
register lazily at VERIFY, not here.
|
|
39
|
-
- **Auth**: anon (`x-client-id` only).
|
|
40
|
-
- **Request**: `{email: string}`.
|
|
41
|
-
- **Response**: `{ok: true}` — always, by design.
|
|
42
|
-
- **Errors**: `429 rate_limited` · `400 validation_failed` (malformed
|
|
43
|
-
email) · `400 missing_client_id`.
|
|
44
|
-
- **SDK**: `auth.requestLoginCode(client, {email})`
|
|
45
|
-
- **Components**: login form (account pages family).
|
|
46
|
-
- **Settings**: the `auth-code` notification template carries the code.
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
# The request contract: {ok:true}, nothing else — no code echo, no
|
|
50
|
-
# registered-or-not oracle. RUN-stamped email → no rate-limit collisions.
|
|
51
|
-
curl -sf -X POST "$BASE/api/store/auth/code/request" \
|
|
52
|
-
-H "x-client-id: $CLIENT_ID" -H "content-type: application/json" \
|
|
53
|
-
-d '{"email": "doc-auth-'$RUN'@doc.test"}' \
|
|
54
|
-
| grep -q '^{"ok":true}$'
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## POST /api/store/auth/code/verify — step 2
|
|
58
|
-
|
|
59
|
-
- **Purpose**: exchange (email, code) for a session. This is ALSO
|
|
60
|
-
passwordless **registration** — a first-time email lazy-creates the
|
|
61
|
-
customer (`account_status` per store policy) and triggers the welcome
|
|
62
|
-
email.
|
|
63
|
-
- **Auth**: anon (`x-client-id` only).
|
|
64
|
-
- **Request**: `{email: string, code: string}` — code is exactly 6 digits
|
|
65
|
-
(leading zeros count; any other shape → `400 validation_failed`).
|
|
66
|
-
- **Response**:
|
|
67
|
-
|
|
68
|
-
```jsonc
|
|
69
|
-
{
|
|
70
|
-
"access_token": "eyJ…", // the Bearer JWT for every store route
|
|
71
|
-
"refresh_token": "…",
|
|
72
|
-
"expires_in": 3600, // seconds
|
|
73
|
-
"expires_at": 1789300000, // epoch seconds (may be absent)
|
|
74
|
-
"token_type": "bearer",
|
|
75
|
-
"customer": { /* full customer + addresses — see customers.md */ }
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
- **Errors**: `401 invalid_code` (every auth failure, generic) ·
|
|
80
|
-
`400 validation_failed` · `400 missing_client_id`.
|
|
81
|
-
- **SDK**: `auth.verifyLoginCode(client, {email, code})`
|
|
82
|
-
- **Components**: code-entry form (account pages family).
|
|
83
|
-
- **Settings**: store approval policy (new-customer `account_status`);
|
|
84
|
-
`account-welcome` template.
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
# The 401 invalid_code contract — a well-formed but wrong code against the
|
|
88
|
-
# RUN-stamped email (its real code lives only in the email we never read).
|
|
89
|
-
STATUS=$(curl -s -o /dev/null -w '%{http_code}' \
|
|
90
|
-
-X POST "$BASE/api/store/auth/code/verify" \
|
|
91
|
-
-H "x-client-id: $CLIENT_ID" -H "content-type: application/json" \
|
|
92
|
-
-d '{"email": "doc-auth-'$RUN'@doc.test", "code": "000000"}')
|
|
93
|
-
test "$STATUS" = 401
|
|
94
|
-
curl -s -X POST "$BASE/api/store/auth/code/verify" \
|
|
95
|
-
-H "x-client-id: $CLIENT_ID" -H "content-type: application/json" \
|
|
96
|
-
-d '{"email": "doc-auth-'$RUN'@doc.test", "code": "000000"}' \
|
|
97
|
-
| grep -q '"code":"invalid_code"'
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
> Cleanup note: the code rows this doc creates are single-per-run
|
|
101
|
-
> (RUN-stamped email), superseded on any later request and dead after the
|
|
102
|
-
> 10-minute TTL — there is no store-surface delete for them by design (they
|
|
103
|
-
> are the auth trail). The happy-path session mint is exercised with a real
|
|
104
|
-
> inbox-free code by `tests/store/customer-accounts-auth.test.ts` and the
|
|
105
|
-
> SDK contract test (server-side code issue via the lib).
|
|
1
|
+
# Auth
|
|
2
|
+
|
|
3
|
+
Passwordless email-code login (customer-accounts card, Shopify Customer
|
|
4
|
+
Account API direction). Two calls: **request** emails a 6-digit code,
|
|
5
|
+
**verify** exchanges it for a session. Password login
|
|
6
|
+
(`supabase.auth.signInWithPassword` client-side) stays available and yields
|
|
7
|
+
an equivalent JWT — nothing here is needed for it.
|
|
8
|
+
|
|
9
|
+
## The session discipline (one rule)
|
|
10
|
+
|
|
11
|
+
`verify` mints a **real Supabase session**. Its `access_token` is the SAME
|
|
12
|
+
`authorization: Bearer <jwt>` every `/api/store/*` route accepts — wire it
|
|
13
|
+
into `StorefrontClient` once and every customer surface (customers/me,
|
|
14
|
+
orders, documents, cart attach) is authenticated:
|
|
15
|
+
|
|
16
|
+
```jsonc
|
|
17
|
+
// new StorefrontClient({ baseUrl, clientId, getAuthToken: () => storedAccessToken })
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Persist `access_token` + `refresh_token`; refresh client-side with the
|
|
21
|
+
Supabase SDK (`setSession` → `refreshSession`) — the Cartbase API does not
|
|
22
|
+
proxy token refresh. `expires_in` is seconds; `expires_at` epoch-seconds.
|
|
23
|
+
|
|
24
|
+
## Server-enforced security posture (the SDK adds nothing)
|
|
25
|
+
|
|
26
|
+
- Request NEVER reveals whether the email is registered — `{ok: true}`
|
|
27
|
+
always (no enumeration oracle). The code exists ONLY in the sent email.
|
|
28
|
+
- Codes: hashed at rest, TTL **10 min**, single-use, one live code per
|
|
29
|
+
email (a new request supersedes prior codes), **5-wrong-attempt lockout**.
|
|
30
|
+
- Rate limits per 15-min window: **5 requests per email**, **20 per IP** →
|
|
31
|
+
`429 rate_limited` — the only distinguishable request failure.
|
|
32
|
+
- Every verify failure — wrong code, expired, consumed, locked out, unknown
|
|
33
|
+
email — is the SAME `401 invalid_code`.
|
|
34
|
+
|
|
35
|
+
## POST /api/store/auth/code/request — step 1
|
|
36
|
+
|
|
37
|
+
- **Purpose**: email a 6-digit one-time login code. First-time emails
|
|
38
|
+
register lazily at VERIFY, not here.
|
|
39
|
+
- **Auth**: anon (`x-client-id` only).
|
|
40
|
+
- **Request**: `{email: string}`.
|
|
41
|
+
- **Response**: `{ok: true}` — always, by design.
|
|
42
|
+
- **Errors**: `429 rate_limited` · `400 validation_failed` (malformed
|
|
43
|
+
email) · `400 missing_client_id`.
|
|
44
|
+
- **SDK**: `auth.requestLoginCode(client, {email})`
|
|
45
|
+
- **Components**: login form (account pages family).
|
|
46
|
+
- **Settings**: the `auth-code` notification template carries the code.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# The request contract: {ok:true}, nothing else — no code echo, no
|
|
50
|
+
# registered-or-not oracle. RUN-stamped email → no rate-limit collisions.
|
|
51
|
+
curl -sf -X POST "$BASE/api/store/auth/code/request" \
|
|
52
|
+
-H "x-client-id: $CLIENT_ID" -H "content-type: application/json" \
|
|
53
|
+
-d '{"email": "doc-auth-'$RUN'@doc.test"}' \
|
|
54
|
+
| grep -q '^{"ok":true}$'
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## POST /api/store/auth/code/verify — step 2
|
|
58
|
+
|
|
59
|
+
- **Purpose**: exchange (email, code) for a session. This is ALSO
|
|
60
|
+
passwordless **registration** — a first-time email lazy-creates the
|
|
61
|
+
customer (`account_status` per store policy) and triggers the welcome
|
|
62
|
+
email.
|
|
63
|
+
- **Auth**: anon (`x-client-id` only).
|
|
64
|
+
- **Request**: `{email: string, code: string}` — code is exactly 6 digits
|
|
65
|
+
(leading zeros count; any other shape → `400 validation_failed`).
|
|
66
|
+
- **Response**:
|
|
67
|
+
|
|
68
|
+
```jsonc
|
|
69
|
+
{
|
|
70
|
+
"access_token": "eyJ…", // the Bearer JWT for every store route
|
|
71
|
+
"refresh_token": "…",
|
|
72
|
+
"expires_in": 3600, // seconds
|
|
73
|
+
"expires_at": 1789300000, // epoch seconds (may be absent)
|
|
74
|
+
"token_type": "bearer",
|
|
75
|
+
"customer": { /* full customer + addresses — see customers.md */ }
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- **Errors**: `401 invalid_code` (every auth failure, generic) ·
|
|
80
|
+
`400 validation_failed` · `400 missing_client_id`.
|
|
81
|
+
- **SDK**: `auth.verifyLoginCode(client, {email, code})`
|
|
82
|
+
- **Components**: code-entry form (account pages family).
|
|
83
|
+
- **Settings**: store approval policy (new-customer `account_status`);
|
|
84
|
+
`account-welcome` template.
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# The 401 invalid_code contract — a well-formed but wrong code against the
|
|
88
|
+
# RUN-stamped email (its real code lives only in the email we never read).
|
|
89
|
+
STATUS=$(curl -s -o /dev/null -w '%{http_code}' \
|
|
90
|
+
-X POST "$BASE/api/store/auth/code/verify" \
|
|
91
|
+
-H "x-client-id: $CLIENT_ID" -H "content-type: application/json" \
|
|
92
|
+
-d '{"email": "doc-auth-'$RUN'@doc.test", "code": "000000"}')
|
|
93
|
+
test "$STATUS" = 401
|
|
94
|
+
curl -s -X POST "$BASE/api/store/auth/code/verify" \
|
|
95
|
+
-H "x-client-id: $CLIENT_ID" -H "content-type: application/json" \
|
|
96
|
+
-d '{"email": "doc-auth-'$RUN'@doc.test", "code": "000000"}' \
|
|
97
|
+
| grep -q '"code":"invalid_code"'
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
> Cleanup note: the code rows this doc creates are single-per-run
|
|
101
|
+
> (RUN-stamped email), superseded on any later request and dead after the
|
|
102
|
+
> 10-minute TTL — there is no store-surface delete for them by design (they
|
|
103
|
+
> are the auth trail). The happy-path session mint is exercised with a real
|
|
104
|
+
> inbox-free code by `tests/store/customer-accounts-auth.test.ts` and the
|
|
105
|
+
> SDK contract test (server-side code issue via the lib).
|