@skuio/storefront 0.5.0 → 0.6.1
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/db/migrations/0007_payments_scope.sql +8 -0
- package/dist/api.d.ts +17 -6
- package/dist/api.js +25 -8
- package/dist/api.js.map +1 -1
- package/dist/{chunk-QWQA4NBG.js → chunk-X7VXO4QU.js} +13 -7
- package/dist/chunk-X7VXO4QU.js.map +1 -0
- package/dist/{config-HNzNP1O_.d.ts → config-C4YCi7m4.d.ts} +8 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/mail.d.ts +1 -1
- package/dist/payments/bitcoin.d.ts +1 -1
- package/dist/payments/btcpay.d.ts +1 -1
- package/dist/payments/manual.d.ts +1 -1
- package/dist/payments/paypal.d.ts +1 -1
- package/dist/payments/stripe.d.ts +1 -1
- package/dist/payments.d.ts +1 -1
- package/dist/store.d.ts +22 -2
- package/dist/store.js +35 -2
- package/dist/store.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-QWQA4NBG.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skuio/storefront",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "The store platform behind SKU.io storefronts: catalogue, checkout, orders, buyer portal and the Store API over one SQL driver \u2014 Cloudflare D1 or SQLite on any Node host. Framework-agnostic; pairs with the Astro reference template.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/store/errors.ts","../src/store/crypto.ts","../src/store/ratelimit.ts","../src/store/context.ts","../src/store/connections.ts"],"sourcesContent":["export type StoreErrorCode = 'not_found' | 'validation_failed' | 'conflict' | 'unavailable' | 'rate_limited' | 'invalid_grant'\n\n/** A domain error the API maps straight to a status code. */\nexport class StoreError extends Error {\n readonly code: StoreErrorCode\n readonly details: Record<string, string> | null\n\n constructor(code: StoreErrorCode, message: string, details: Record<string, string> | null = null) {\n super(message)\n this.name = 'StoreError'\n this.code = code\n this.details = details\n }\n\n get status(): number {\n switch (this.code) {\n case 'not_found':\n return 404\n case 'validation_failed':\n return 422\n case 'conflict':\n return 409\n case 'unavailable':\n return 503\n case 'rate_limited':\n return 429\n case 'invalid_grant':\n return 400\n }\n }\n}\n\nexport const notFound = (what: string) => new StoreError('not_found', `${what} not found`)\nexport const invalid = (message: string, details: Record<string, string> | null = null) =>\n new StoreError('validation_failed', message, details)\nexport const conflict = (message: string) => new StoreError('conflict', message)\n","/**\n * Token and hashing helpers on WebCrypto, which both Workers and Node 20+ expose\n * as the global `crypto`.\n */\nconst encoder = new TextEncoder()\n\nconst ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'\n\n/** Random id with a type prefix: ord_…, cus_…, ful_… */\nexport function newId(prefix: string, length = 20): string {\n const bytes = new Uint8Array(length)\n crypto.getRandomValues(bytes)\n let out = ''\n for (const b of bytes) out += ALPHABET[b % ALPHABET.length]\n return `${prefix}_${out}`\n}\n\n/** Random secret token, hex. */\nexport function randomToken(bytes = 32): string {\n const buf = new Uint8Array(bytes)\n crypto.getRandomValues(buf)\n return toHex(buf)\n}\n\n/** Random secret as URL-safe base64 (no padding); 32 bytes → 43 characters. */\nexport function randomBase64Url(bytes = 32): string {\n const buf = new Uint8Array(bytes)\n crypto.getRandomValues(buf)\n let bin = ''\n for (const b of buf) bin += String.fromCharCode(b)\n return btoa(bin).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nexport function toHex(buf: ArrayBuffer | Uint8Array): string {\n const view = buf instanceof Uint8Array ? buf : new Uint8Array(buf)\n let out = ''\n for (const b of view) out += b.toString(16).padStart(2, '0')\n return out\n}\n\nexport async function sha256Hex(input: string): Promise<string> {\n return toHex(await crypto.subtle.digest('SHA-256', encoder.encode(input)))\n}\n\n/**\n * Hash a bearer token for storage. With a secret the hash is an HMAC, so a\n * copy of the database alone cannot be used to forge a session.\n */\nexport async function hashToken(token: string, secret?: string): Promise<string> {\n if (!secret) return sha256Hex(token)\n const key = await crypto.subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])\n return toHex(await crypto.subtle.sign('HMAC', key, encoder.encode(token)))\n}\n\n/** Compare two secrets without leaking where they differ. Hashes first so lengths never leak either. */\nexport async function secretsEqual(a: string, b: string): Promise<boolean> {\n const [ha, hb] = await Promise.all([sha256Hex(a), sha256Hex(b)])\n let diff = 0\n for (let i = 0; i < ha.length; i++) diff |= ha.charCodeAt(i) ^ hb.charCodeAt(i)\n return diff === 0\n}\n","import type { DbStatement } from '../db/driver'\nimport type { StoreContext } from './context'\nimport { StoreError } from './errors'\n\nexport interface RateLimitPolicy {\n /** Failures allowed inside one window before the key locks. */\n maxFailures: number\n windowMinutes: number\n lockMinutes: number\n}\n\n/**\n * A failure counter in the database, keyed by whatever the caller wants to\n * throttle (sign-ins per email + address, code exchanges per address). Lives\n * in D1 rather than isolate memory so every Worker instance sees the same\n * count. Successes clear the key; nothing is counted for them.\n */\nexport class RateLimiter {\n constructor(private readonly ctx: StoreContext, private readonly policy: RateLimitPolicy) {}\n\n /** Throws `rate_limited` (429) while the key is locked. */\n async assertAllowed(key: string, message: string): Promise<void> {\n const now = this.ctx.now()\n const row = await this.ctx.db.prepare(`SELECT locked_until FROM auth_attempts WHERE key = ?`).bind(key).first<{ locked_until: string | null }>()\n if (row?.locked_until && row.locked_until > now) throw new StoreError('rate_limited', message)\n }\n\n async recordFailure(key: string): Promise<void> {\n const now = this.ctx.now()\n const nowMs = new Date(now).getTime()\n const row = await this.ctx.db\n .prepare(`SELECT failures, window_started_at, locked_until FROM auth_attempts WHERE key = ?`)\n .bind(key)\n .first<{ failures: number; window_started_at: string; locked_until: string | null }>()\n const inWindow = row && nowMs - new Date(row.window_started_at).getTime() < this.policy.windowMinutes * 60_000\n const failures = inWindow ? row!.failures + 1 : 1\n const windowStart = inWindow ? row!.window_started_at : now\n const lockedUntil = failures >= this.policy.maxFailures ? new Date(nowMs + this.policy.lockMinutes * 60_000).toISOString() : null\n await this.ctx.db\n .prepare(\n `INSERT INTO auth_attempts (key, failures, window_started_at, locked_until) VALUES (?, ?, ?, ?)\n ON CONFLICT (key) DO UPDATE SET failures = excluded.failures, window_started_at = excluded.window_started_at, locked_until = excluded.locked_until`\n )\n .bind(key, failures, windowStart, lockedUntil)\n .run()\n }\n\n clearStatement(key: string): DbStatement {\n return this.ctx.db.prepare(`DELETE FROM auth_attempts WHERE key = ?`).bind(key)\n }\n\n /** Rows whose window and lock have both passed carry no information; drop them. */\n pruneStatement(): DbStatement {\n const cutoff = new Date(new Date(this.ctx.now()).getTime() - Math.max(this.policy.windowMinutes, this.policy.lockMinutes) * 60_000).toISOString()\n return this.ctx.db.prepare(`DELETE FROM auth_attempts WHERE window_started_at <= ? AND (locked_until IS NULL OR locked_until <= ?)`).bind(cutoff, this.ctx.now())\n }\n}\n","import type { StorefrontConfig } from '../config'\nimport type { Database } from '../db/driver'\nimport type { MailAdapter } from '../mail/types'\n\n/** Environment the store reads. Every key is optional; missing ones degrade to safe defaults. */\nexport interface StoreEnv {\n /** Absolute public origin used in emails and return URLs. Defaults to config.url. */\n PUBLIC_SITE_URL?: string\n /** HMAC key for session and magic-link hashes. */\n SESSION_SECRET?: string\n /** Merchant address that hears about every paid order. */\n ORDER_NOTIFICATION_EMAIL?: string\n [key: string]: string | undefined\n}\n\nexport interface StoreContext {\n db: Database\n config: StorefrontConfig\n env: StoreEnv\n mail: MailAdapter\n /** ISO timestamp, injectable for tests. */\n now(): string\n siteUrl: string\n log(message: string, data?: unknown): void\n}\n\n/** Split an IN (...) list so no statement exceeds D1's bound-parameter limit. */\nexport function chunk<T>(items: T[], size = 80): T[][] {\n const out: T[][] = []\n for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size))\n return out\n}\n\nexport const placeholders = (n: number) => Array.from({ length: n }, () => '?').join(',')\n\nexport function parseJson<T>(raw: unknown, fallback: T): T {\n if (typeof raw !== 'string' || !raw) return fallback\n try {\n return JSON.parse(raw) as T\n } catch {\n return fallback\n }\n}\n","import { parseJson, type StoreContext } from './context'\nimport { newId, randomBase64Url, randomToken, sha256Hex } from './crypto'\nimport { StoreError } from './errors'\nimport { RateLimiter } from './ratelimit'\n\nexport const CONNECT_CODE_MINUTES = 10\nexport const API_KEY_PREFIX = 'sk_store_'\nexport const CONNECT_CLIENT_IDS = ['skuio'] as const\nexport const CONNECT_SCOPES = ['orders', 'fulfillments', 'inventory', 'catalog'] as const\nexport type ConnectScope = (typeof CONNECT_SCOPES)[number]\n\nexport const SCOPE_DESCRIPTIONS: Record<ConnectScope, string> = {\n orders: 'Read orders, record payments and cancellations',\n fulfillments: 'Push shipments and tracking to orders',\n inventory: 'Update available stock',\n catalog: 'Read and update products and categories',\n}\n\nexport interface Connection {\n id: string\n name: string\n tenant: string | null\n redirectHost: string | null\n scopes: ConnectScope[]\n keyPrefix: string | null\n createdByOwnerId: string | null\n createdAt: string\n issuedAt: string | null\n lastUsedAt: string | null\n revokedAt: string | null\n}\n\nexport interface AuthorizeParams {\n client_id?: string | null\n redirect_uri?: string | null\n state?: string | null\n tenant?: string | null\n scope?: string | null\n}\n\n/** A validated connect request, ready to show the owner. */\nexport interface AuthorizeRequest {\n clientId: string\n redirectUri: string\n redirectHost: string\n state: string\n tenant: string\n scopes: ConnectScope[]\n}\n\nexport interface IssuedKey {\n apiKey: string\n connection: Connection\n}\n\ninterface ConnectionRow {\n id: string\n name: string\n tenant: string | null\n redirect_host: string | null\n scopes_json: string\n key_hash: string | null\n key_prefix: string | null\n created_by_owner_id: string | null\n created_at: string\n issued_at: string | null\n last_used_at: string | null\n revoked_at: string | null\n}\n\nconst toConnection = (r: ConnectionRow): Connection => ({\n id: r.id,\n name: r.name,\n tenant: r.tenant,\n redirectHost: r.redirect_host,\n scopes: parseJson<string[]>(r.scopes_json, []).filter((s): s is ConnectScope => (CONNECT_SCOPES as readonly string[]).includes(s)),\n keyPrefix: r.key_prefix,\n createdByOwnerId: r.created_by_owner_id,\n createdAt: r.created_at,\n issuedAt: r.issued_at,\n lastUsedAt: r.last_used_at,\n revokedAt: r.revoked_at,\n})\n\n/** http is allowed only where TLS cannot exist: a developer's machine. */\nexport function isLocalHost(hostname: string): boolean {\n const h = hostname.toLowerCase()\n return h === 'localhost' || h === '127.0.0.1' || h === '[::1]' || h.endsWith('.localhost') || h.endsWith('.test')\n}\n\n/**\n * Consumers the owner approved. The store is the authorization server: an\n * owner approves a request in the browser, the consumer trades the resulting\n * one-time code for a per-connection API key, and only the key's SHA-256 is\n * ever stored. Revoking a connection turns its key into a 401.\n */\nexport class ConnectionsService {\n private readonly limiter: RateLimiter\n\n constructor(private readonly ctx: StoreContext) {\n this.limiter = new RateLimiter(ctx, { maxFailures: 20, windowMinutes: 15, lockMinutes: 15 })\n }\n\n /** Validate the query of a connect request. Throws `validation_failed` with a field per problem. */\n validateAuthorize(params: AuthorizeParams): AuthorizeRequest {\n const details: Record<string, string> = {}\n const clientId = (params.client_id ?? '').trim()\n if (!(CONNECT_CLIENT_IDS as readonly string[]).includes(clientId)) details.client_id = 'is not a known consumer'\n const state = (params.state ?? '').trim()\n if (!state) details.state = 'is required'\n else if (state.length > 1024) details.state = 'must be at most 1024 characters'\n const tenant = (params.tenant ?? '').trim()\n if (!tenant) details.tenant = 'is required'\n else if (tenant.length > 120) details.tenant = 'must be at most 120 characters'\n\n let redirect: URL | null = null\n const raw = (params.redirect_uri ?? '').trim()\n if (!raw) details.redirect_uri = 'is required'\n else {\n try {\n redirect = new URL(raw)\n } catch {\n details.redirect_uri = 'must be an absolute URL'\n }\n }\n if (redirect) {\n if (redirect.hash) details.redirect_uri = 'must not carry a fragment'\n else if (redirect.protocol !== 'https:' && !(redirect.protocol === 'http:' && isLocalHost(redirect.hostname))) details.redirect_uri = 'must use https'\n else if (redirect.username || redirect.password) details.redirect_uri = 'must not carry credentials'\n }\n\n const scopeRaw = (params.scope ?? '').split(/[\\s,]+/).map((s) => s.trim()).filter(Boolean)\n const scopes = [...new Set(scopeRaw)] as string[]\n const unknown = scopes.filter((s) => !(CONNECT_SCOPES as readonly string[]).includes(s))\n if (!scopes.length) details.scope = 'is required'\n else if (unknown.length) details.scope = `unknown scope: ${unknown.join(', ')}`\n\n if (Object.keys(details).length) throw new StoreError('validation_failed', 'Invalid connect request', details)\n return { clientId, redirectUri: redirect!.href, redirectHost: redirect!.host, state, tenant, scopes: scopes as ConnectScope[] }\n }\n\n /** The owner said yes: create the connection and a one-time code. Returns the URL to send the browser to. */\n async approve(request: AuthorizeRequest, ownerId: string | null): Promise<{ connection: Connection; code: string; redirectTo: string }> {\n const now = this.ctx.now()\n const id = newId('con')\n const code = randomToken(32)\n const expiresAt = new Date(new Date(now).getTime() + CONNECT_CODE_MINUTES * 60_000).toISOString()\n const name = `${request.tenant} on SKU.io`\n await this.ctx.db.batch([\n this.ctx.db\n .prepare(`INSERT INTO connections (id, name, tenant, redirect_host, scopes_json, created_by_owner_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`)\n .bind(id, name, request.tenant, request.redirectHost, JSON.stringify(request.scopes), ownerId, now),\n this.ctx.db\n .prepare(`INSERT INTO connect_codes (code_hash, connection_id, redirect_uri, expires_at, created_at) VALUES (?, ?, ?, ?, ?)`)\n .bind(await sha256Hex(code), id, request.redirectUri, expiresAt, now),\n ])\n const connection = (await this.byId(id))!\n return { connection, code, redirectTo: withQuery(request.redirectUri, { code, state: request.state }) }\n }\n\n /** The owner said no. */\n denyUrl(request: AuthorizeRequest): string {\n return withQuery(request.redirectUri, { error: 'access_denied', state: request.state })\n }\n\n /**\n * Trade a code for the connection's key. Exactly once: the code is marked used\n * in the same batch that stores the key hash, so a replay finds it used.\n * `address` keys the failure counter.\n */\n async exchange(input: { code?: unknown; redirect_uri?: unknown }, address: string): Promise<IssuedKey> {\n const limitKey = `token:${address}`\n await this.limiter.assertAllowed(limitKey, 'Too many failed exchanges. Try again in a few minutes.')\n const fail = async (message: string) => {\n await this.limiter.recordFailure(limitKey)\n return new StoreError('invalid_grant', message)\n }\n const code = typeof input.code === 'string' ? input.code.trim() : ''\n const redirectUri = typeof input.redirect_uri === 'string' ? input.redirect_uri.trim() : ''\n if (!code || !redirectUri) throw await fail('code and redirect_uri are required')\n const now = this.ctx.now()\n const codeHash = await sha256Hex(code)\n const row = await this.ctx.db\n .prepare(`SELECT connection_id, redirect_uri, expires_at, used_at FROM connect_codes WHERE code_hash = ?`)\n .bind(codeHash)\n .first<{ connection_id: string; redirect_uri: string; expires_at: string; used_at: string | null }>()\n if (!row || row.used_at || row.expires_at <= now) throw await fail('The code is unknown, used or expired')\n if (row.redirect_uri !== redirectUri) throw await fail('redirect_uri does not match the one the code was issued for')\n const existing = await this.byId(row.connection_id)\n if (!existing || existing.revokedAt || existing.keyPrefix) throw await fail('The code is unknown, used or expired')\n\n const apiKey = `${API_KEY_PREFIX}${randomBase64Url(32)}`\n await this.ctx.db.batch([\n this.ctx.db.prepare(`UPDATE connect_codes SET used_at = ? WHERE code_hash = ? AND used_at IS NULL`).bind(now, codeHash),\n this.ctx.db\n .prepare(`UPDATE connections SET key_hash = ?, key_prefix = ?, issued_at = ? WHERE id = ? AND key_hash IS NULL`)\n .bind(await sha256Hex(apiKey), apiKey.slice(0, API_KEY_PREFIX.length + 8), now, row.connection_id),\n this.limiter.clearStatement(limitKey),\n ])\n const connection = (await this.byId(row.connection_id))!\n if (!connection.keyPrefix) throw await fail('The code is unknown, used or expired')\n return { apiKey, connection }\n }\n\n /** The live connection behind a bearer key, or null. Bumps last_used_at at most once a minute. */\n async resolveKey(bearer: string): Promise<Connection | null> {\n if (!bearer.startsWith(API_KEY_PREFIX)) return null\n const row = await this.ctx.db.prepare(`SELECT * FROM connections WHERE key_hash = ?`).bind(await sha256Hex(bearer)).first<ConnectionRow>()\n if (!row || row.revoked_at) return null\n const now = this.ctx.now()\n if (!row.last_used_at || new Date(now).getTime() - new Date(row.last_used_at).getTime() > 60_000) {\n await this.ctx.db.prepare(`UPDATE connections SET last_used_at = ? WHERE id = ?`).bind(now, row.id).run()\n row.last_used_at = now\n }\n return toConnection(row)\n }\n\n async byId(id: string): Promise<Connection | null> {\n const row = await this.ctx.db.prepare(`SELECT * FROM connections WHERE id = ?`).bind(id).first<ConnectionRow>()\n return row ? toConnection(row) : null\n }\n\n /** Every connection that was issued a key, newest first. Pending (never exchanged) ones are not listed. */\n async list(): Promise<Connection[]> {\n const { results } = await this.ctx.db.prepare(`SELECT * FROM connections WHERE key_hash IS NOT NULL ORDER BY created_at DESC`).all<ConnectionRow>()\n return results.map(toConnection)\n }\n\n async revoke(id: string): Promise<boolean> {\n const result = await this.ctx.db.prepare(`UPDATE connections SET revoked_at = ? WHERE id = ? AND revoked_at IS NULL`).bind(this.ctx.now(), id).run()\n return (result.meta?.changes ?? 0) > 0\n }\n\n /** Housekeeping: expired codes, connections whose code was never exchanged, stale limiter rows. */\n async prune(): Promise<void> {\n const now = this.ctx.now()\n await this.ctx.db.batch([\n this.ctx.db.prepare(`DELETE FROM connect_codes WHERE expires_at <= ? OR used_at IS NOT NULL`).bind(now),\n this.ctx.db.prepare(`DELETE FROM connections WHERE key_hash IS NULL AND created_at <= ?`).bind(new Date(new Date(now).getTime() - CONNECT_CODE_MINUTES * 60_000).toISOString()),\n this.limiter.pruneStatement(),\n ])\n }\n}\n\nfunction withQuery(base: string, params: Record<string, string>): string {\n const url = new URL(base)\n for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v)\n return url.href\n}\n"],"mappings":"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","names":[]}
|