Vizen Shop

Storefront resource access

/docs/resource-accessdraftEN· проверено 2026-09-12

Status: draft · Serves: GET /docs/resource-access · Verified: 2026-09-12

Two things live here. Site publication — closing the whole shop behind a short numeric PIN while it is being built — is finished and usable today. The per-resource matrix below it (a password on one product, category or page) is staged: keep companies.resource_access_enabled disabled until the complete public-surface matrix and cache-isolation tests pass.

Site publication

A shop's site is either published or not published. Published is the default — for new shops and for every shop that already exists, with no migration needed to say so. The state is not a stored flag: "not published" is the presence of an active system access policy carrying the PIN on the shop resource, and "published" is that policy being absent or switched off. One source of truth, so the gate and the setting cannot disagree.

While a site is not published:

  • every visitor sees a PIN prompt instead of the site and gets in by typing the PIN (POST /storefront/access/unlock, see below);
  • search engines are shut out — robots.txt answers Disallow: / for the whole host, sitemap.xml answers 404, and storefront responses carry X-Robots-Tag: noindex, nofollow. The same applies on a custom domain;
  • three wrong PINs from one address lock that address out for 10 minutes. The correct PIN does not open the door during the lockout either — otherwise the lockout would be a delay, not a lock. This lockout, not the length of the PIN, is what actually protects a closed site.

GET /site-publication

{ "published": false, "pin": "1234" }

The PIN comes back in plain text, and that is deliberate. It is a temporary guard for a site under construction, not an account secret: the owner dictates it to a client and has to be able to read it back. Only the shop's owner/admin or a key with access:read ever sees it — the public storefront resolve carries site_published and nothing else. pin is empty when the site has never been closed; it survives publishing the site again, so re-closing does not force a new one.

PUT /site-publication

# close the site with the PIN 1234
curl -X PUT https://api.vizen.shop/site-publication \
  -H 'Authorization: Bearer vz_pat_…' -H 'Content-Type: application/json' \
  -d '{"published": false, "pin": "1234"}'

# publish it again (no PIN needed)
curl -X PUT https://api.vizen.shop/site-publication \
  -H 'Authorization: Bearer vz_pat_…' -H 'Content-Type: application/json' \
  -d '{"published": true}'

published: false requires a PIN of digits only, 3 to 6 of them (3–4 is what the cabinet suggests); anything else is refused with SITE_PASSWORD_FORMAT. Every close sets the PIN anew, so closing the site also revokes every pass handed out under the previous one.

Rights: org role owner or admin, token scopes access:read for the GET and access:write for the PUT. Both methods are prod-sensitive: a dev-contour key may not even read them. A cache purge of the shop's host runs on every successful PUT — the whole host changes at once.

The system policy behind the state is not a user policy: it is hidden from GET /access-policies, its id answers ACCESS_POLICY_NOT_FOUND on edit or delete, its code is refused on create (ACCESS_POLICY_CODE_RESERVED), and PUT /resource-access-bindings leaves its binding alone. Everything about it moves through the two methods above.

Read the state of a live storefront without a token from GET /v1/storefronts/resolve: storefront.site_published says whether the site is closed, site_access_granted says whether this caller (by the vz_store_access cookie) is already past the prompt. Neither carries the PIN.

Password challenge

POST /storefront/access/unlock

{
  "company_id": 42,
  "resource_type": "product",
  "resource_id": 1001,
  "password": "customer supplied password"
}

For the site prompt this is resource_type: "shop" with resource_id equal to company_id. Submitted passwords are accepted from 3 characters — the site PIN is short by design, and refusing it while parsing the body would read to the visitor as "the correct PIN does not work". The site PIN is compared in constant time against the stored plain value; per-resource policy passwords are still verified against their Argon2id hash.

On success the server returns 200 and sets the host-only HttpOnly cookie vz_store_access. The cookie is Secure, SameSite=Lax, contains 256 bits of random data and has no Domain attribute. Only its SHA-256 digest is stored.

Unknown resources, an absent password policy and a wrong password all return the same 401 {"error":"ACCESS_DENIED"}. The response never contains policy IDs, audience/role names or password hints. Cross-site requests are rejected and repeated attempts receive 429 with Retry-After: 60 from the per-minute rate limit, or the remaining seconds of the 10-minute lockout after three failures on the same address and resource.

DELETE /storefront/access/session revokes the current opaque session and clears the cookie. Both routes return Cache-Control: private, no-store.

Runtime contract

Roles are not embedded into the cookie. For every protected request the backend loads the target shop, current customer status/roles and current password grant versions. A role removal, customer block, password replacement or policy disable therefore takes effect without waiting for cookie expiry.

Multiple effective policies are combined with AND. A password submission grants only policies whose Argon2id hash matches; policies with different passwords can be unlocked in successive requests.

Direct product/category/article reads are connected. Product catalog pages, marketplace, wishlist, AddToCart/UpdateCartItem, GetCart/QuoteCart and checkout use the same access predicate. Checkout revalidates the live session and grant inside the order transaction.

Category/article lists and dependent redirect/sitemap/related/group/combo/gift, HTML/form/private-file surfaces are still being implemented, so this document is not an activation approval for the per-resource feature flag. Site publication does not depend on that flag: it gates the site at the page level, in front of everything, and is live today.

Исходник: https://api.vizen.shop/docs/resource-access

Работает в демо-режиме