@swell/apps-sdk 2.0.0-alpha.1 → 2.0.0-alpha.3
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/README.md +22 -20
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/request-context.d.cts +3 -3
- package/dist/request-context.d.ts +3 -3
- package/dist/request-context.js +5 -9
- package/dist/{staff.d.cts → store-user.d.cts} +2 -2
- package/dist/{staff.d.ts → store-user.d.ts} +2 -2
- package/dist/store-user.js +7 -0
- package/dist/version.d.cts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/staff.js +0 -7
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The Swell Apps SDK is a TypeScript library for building server-side Swell apps. It
|
|
4
4
|
provides access to the Backend and Storefront APIs, along with helpers for app
|
|
5
|
-
configuration, customer sessions and
|
|
5
|
+
configuration, customer sessions and store user identity.
|
|
6
6
|
|
|
7
7
|
Use it in Swell-hosted apps, Cloudflare Workers or Node.js servers. For browser
|
|
8
8
|
applications, use [`swell-js`](https://github.com/swellstores/swell-js).
|
|
@@ -23,9 +23,9 @@ Version 2 replaces the 1.x theme API. Existing theme applications should remain
|
|
|
23
23
|
|
|
24
24
|
### Request context
|
|
25
25
|
|
|
26
|
-
Swell supplies the current store's configuration, credentials and
|
|
27
|
-
each request. Verify this context once, then reuse it to create clients and check
|
|
28
|
-
access. Use the same pattern for Swell-hosted and self-hosted frontends.
|
|
26
|
+
Swell supplies the current store's configuration, credentials and store user identity
|
|
27
|
+
with each request. Verify this context once, then reuse it to create clients and check
|
|
28
|
+
store user access. Use the same pattern for Swell-hosted and self-hosted frontends.
|
|
29
29
|
|
|
30
30
|
```ts
|
|
31
31
|
import { verifySwellContext } from '@swell/apps-sdk';
|
|
@@ -96,16 +96,17 @@ swell.init(config.storeId, config.publicKey, config);
|
|
|
96
96
|
|
|
97
97
|
Send only this public config to the browser; the request context contains server credentials.
|
|
98
98
|
|
|
99
|
-
###
|
|
99
|
+
### Store users
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
A store user is someone signed in to the store's Swell dashboard. Use
|
|
102
|
+
`requireStoreUser` to check that a request belongs to one before applying your
|
|
103
|
+
application's permission checks:
|
|
103
104
|
|
|
104
105
|
```ts
|
|
105
|
-
import {
|
|
106
|
+
import { requireStoreUser } from '@swell/apps-sdk';
|
|
106
107
|
|
|
107
|
-
const
|
|
108
|
-
const
|
|
108
|
+
const storeUser = requireStoreUser(context); // { userId, storeId }, or a 401 SwellError.
|
|
109
|
+
const optional = context.storeUser; // null for a visitor; no exception needed.
|
|
109
110
|
```
|
|
110
111
|
|
|
111
112
|
## API reference
|
|
@@ -186,27 +187,28 @@ Node compatibility should pass their bindings as `env`.
|
|
|
186
187
|
| Variable | Default | Purpose |
|
|
187
188
|
| --- | --- | --- |
|
|
188
189
|
| `SWELL_VERIFY_HEADERS` | enabled | Set exactly `"false"` to skip signature verification during local development. |
|
|
189
|
-
| `SWELL_HEADERS_JWKS_URL` | `https://swell.store
|
|
190
|
+
| `SWELL_HEADERS_JWKS_URL` | `https://keys.swell.store/jwks.json` | Override the verification-key endpoint. |
|
|
190
191
|
|
|
191
192
|
For local development against a local Swell instance, put `SWELL_VERIFY_HEADERS=false`
|
|
192
193
|
in `.dev.vars`. Remove it or set it to `"true"` to restore verification. Token structure
|
|
193
194
|
and claim validation still apply, but the context is no longer authenticated: anyone
|
|
194
|
-
who can reach the frontend directly can supply forged context, including
|
|
195
|
+
who can reach the frontend directly can supply forged context, including a store user.
|
|
195
196
|
Use this bypass only for local development.
|
|
196
197
|
|
|
197
198
|
For server integrations, construct `SwellBackendAPI` with explicit credentials and
|
|
198
199
|
`createStorefrontClient` with explicit public configuration. These clients do not
|
|
199
|
-
require an HTTP request or
|
|
200
|
+
require an HTTP request or a store user.
|
|
200
201
|
|
|
201
|
-
###
|
|
202
|
+
### Store users
|
|
202
203
|
|
|
203
|
-
`
|
|
204
|
-
status 401 and code `
|
|
205
|
-
which is null for visitors.
|
|
204
|
+
`requireStoreUser(context)` returns `{ userId, storeId }` or throws `SwellError` with
|
|
205
|
+
status 401 and code `store_user_required`. For optional access, read
|
|
206
|
+
`context.storeUser`, which is null for visitors.
|
|
206
207
|
|
|
207
|
-
|
|
208
|
-
support. Swell's proxy handles
|
|
209
|
-
application decides what each
|
|
208
|
+
A store user is anyone signed in to the store's dashboard, including partners and Swell
|
|
209
|
+
support, who may not appear in the store's own user list. Swell's proxy handles their
|
|
210
|
+
authentication and write-origin checks; your application decides what each store user
|
|
211
|
+
may do.
|
|
210
212
|
|
|
211
213
|
### Errors
|
|
212
214
|
|
package/dist/index.d.cts
CHANGED
|
@@ -7,5 +7,5 @@ export { SwellBackendAPI } from './backend.cjs';
|
|
|
7
7
|
export type { BackendOptions, SwellCollection, SwellData, TransactionOperation, TransactionOptions } from './backend.cjs';
|
|
8
8
|
export { SwellError } from './error.cjs';
|
|
9
9
|
export type { SwellErrorOptions } from './error.cjs';
|
|
10
|
-
export {
|
|
11
|
-
export type {
|
|
10
|
+
export { requireStoreUser } from './store-user.cjs';
|
|
11
|
+
export type { StoreUser } from './store-user.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -7,5 +7,5 @@ export { SwellBackendAPI } from './backend.js';
|
|
|
7
7
|
export type { BackendOptions, SwellCollection, SwellData, TransactionOperation, TransactionOptions } from './backend.js';
|
|
8
8
|
export { SwellError } from './error.js';
|
|
9
9
|
export type { SwellErrorOptions } from './error.js';
|
|
10
|
-
export {
|
|
11
|
-
export type {
|
|
10
|
+
export { requireStoreUser } from './store-user.js';
|
|
11
|
+
export type { StoreUser } from './store-user.js';
|
package/dist/index.js
CHANGED
|
@@ -3,4 +3,4 @@ export { getStorefrontConfig } from './context.js';
|
|
|
3
3
|
export { verifySwellContext } from './request-context.js';
|
|
4
4
|
export { SwellBackendAPI } from './backend.js';
|
|
5
5
|
export { SwellError } from './error.js';
|
|
6
|
-
export {
|
|
6
|
+
export { requireStoreUser } from './store-user.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HeaderReader } from './context.cjs';
|
|
2
|
-
import type {
|
|
2
|
+
import type { StoreUser } from './store-user.cjs';
|
|
3
3
|
export interface SwellHeadersEnv {
|
|
4
4
|
SWELL_VERIFY_HEADERS?: string;
|
|
5
5
|
SWELL_HEADERS_JWKS_URL?: string;
|
|
@@ -26,12 +26,12 @@ export interface SwellRequestContext {
|
|
|
26
26
|
readonly publicKey?: string;
|
|
27
27
|
readonly requestId?: string;
|
|
28
28
|
readonly vaultUrl?: string;
|
|
29
|
-
readonly
|
|
29
|
+
readonly storeUser: Readonly<StoreUser> | null;
|
|
30
30
|
/** False only when trusted runtime configuration explicitly disables verification. */
|
|
31
31
|
readonly signatureVerified: boolean;
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* Resolves Swell-Context once per request. Uses ES256 and a pinned JWKS endpoint;
|
|
35
|
-
* never discovers keys from the request. No fallback to plain headers or
|
|
35
|
+
* never discovers keys from the request. No fallback to plain headers or dashboard cookies.
|
|
36
36
|
*/
|
|
37
37
|
export declare function verifySwellContext(headers: HeaderReader, options?: VerifySwellContextOptions): Promise<SwellRequestContext>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HeaderReader } from './context.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { StoreUser } from './store-user.js';
|
|
3
3
|
export interface SwellHeadersEnv {
|
|
4
4
|
SWELL_VERIFY_HEADERS?: string;
|
|
5
5
|
SWELL_HEADERS_JWKS_URL?: string;
|
|
@@ -26,12 +26,12 @@ export interface SwellRequestContext {
|
|
|
26
26
|
readonly publicKey?: string;
|
|
27
27
|
readonly requestId?: string;
|
|
28
28
|
readonly vaultUrl?: string;
|
|
29
|
-
readonly
|
|
29
|
+
readonly storeUser: Readonly<StoreUser> | null;
|
|
30
30
|
/** False only when trusted runtime configuration explicitly disables verification. */
|
|
31
31
|
readonly signatureVerified: boolean;
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* Resolves Swell-Context once per request. Uses ES256 and a pinned JWKS endpoint;
|
|
35
|
-
* never discovers keys from the request. No fallback to plain headers or
|
|
35
|
+
* never discovers keys from the request. No fallback to plain headers or dashboard cookies.
|
|
36
36
|
*/
|
|
37
37
|
export declare function verifySwellContext(headers: HeaderReader, options?: VerifySwellContextOptions): Promise<SwellRequestContext>;
|
package/dist/request-context.js
CHANGED
|
@@ -15,14 +15,13 @@ function object(value) {
|
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
17
|
* Resolves Swell-Context once per request. Uses ES256 and a pinned JWKS endpoint;
|
|
18
|
-
* never discovers keys from the request. No fallback to plain headers or
|
|
18
|
+
* never discovers keys from the request. No fallback to plain headers or dashboard cookies.
|
|
19
19
|
*/
|
|
20
20
|
export async function verifySwellContext(headers, options = {}) {
|
|
21
21
|
const runtime = globalThis;
|
|
22
22
|
const env = options.env ?? runtime.process?.env ?? {};
|
|
23
23
|
const verify = env.SWELL_VERIFY_HEADERS !== 'false';
|
|
24
|
-
const jwksUrl = validateUrl(env.SWELL_HEADERS_JWKS_URL ?? 'https://swell.store
|
|
25
|
-
const issuer = new URL(jwksUrl).origin;
|
|
24
|
+
const jwksUrl = validateUrl(env.SWELL_HEADERS_JWKS_URL ?? 'https://keys.swell.store/jwks.json', 'SWELL_HEADERS_JWKS_URL');
|
|
26
25
|
for (const field of ['appId', 'storeId']) {
|
|
27
26
|
if (options[field] !== undefined)
|
|
28
27
|
requireString(options[field], field);
|
|
@@ -63,9 +62,6 @@ export async function verifySwellContext(headers, options = {}) {
|
|
|
63
62
|
throw invalidContext();
|
|
64
63
|
if (payload.nbf !== undefined && (typeof payload.nbf !== 'number' || !Number.isFinite(payload.nbf) || payload.nbf > now + 5))
|
|
65
64
|
throw invalidContext();
|
|
66
|
-
requireString(payload.iss, 'iss');
|
|
67
|
-
if (verify && payload.iss !== issuer)
|
|
68
|
-
throw invalidContext();
|
|
69
65
|
for (const field of ['store_id', 'app_id', 'installation_id'])
|
|
70
66
|
requireString(payload[field], field);
|
|
71
67
|
if (payload.aud !== payload.app_id || (options.appId !== undefined && payload.aud !== options.appId) ||
|
|
@@ -75,11 +71,11 @@ export async function verifySwellContext(headers, options = {}) {
|
|
|
75
71
|
if (payload[field] != null)
|
|
76
72
|
requireString(payload[field], field);
|
|
77
73
|
}
|
|
78
|
-
let
|
|
74
|
+
let storeUser = null;
|
|
79
75
|
if (payload.admin !== null) {
|
|
80
76
|
object(payload.admin);
|
|
81
77
|
requireString(payload.admin.user_id, 'admin.user_id');
|
|
82
|
-
|
|
78
|
+
storeUser = Object.freeze({ userId: payload.admin.user_id, storeId: payload.store_id });
|
|
83
79
|
}
|
|
84
80
|
return Object.freeze({
|
|
85
81
|
storeId: payload.store_id, appId: payload.app_id,
|
|
@@ -90,7 +86,7 @@ export async function verifySwellContext(headers, options = {}) {
|
|
|
90
86
|
accessToken: headers.get('Swell-Access-Token') ?? undefined,
|
|
91
87
|
publicKey: headers.get('Swell-Public-Key') ?? undefined,
|
|
92
88
|
requestId: headers.get('Swell-Request-ID') ?? undefined,
|
|
93
|
-
vaultUrl,
|
|
89
|
+
vaultUrl, storeUser, signatureVerified: verify,
|
|
94
90
|
});
|
|
95
91
|
}
|
|
96
92
|
catch {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SwellRequestContext } from './request-context.cjs';
|
|
2
|
-
export interface
|
|
2
|
+
export interface StoreUser {
|
|
3
3
|
readonly userId: string;
|
|
4
4
|
readonly storeId: string;
|
|
5
5
|
}
|
|
6
6
|
/** Identity only; any dashboard role counts. The proxy owns write-origin checks; the app owns permissions. */
|
|
7
|
-
export declare function
|
|
7
|
+
export declare function requireStoreUser(context: SwellRequestContext): StoreUser;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SwellRequestContext } from './request-context.js';
|
|
2
|
-
export interface
|
|
2
|
+
export interface StoreUser {
|
|
3
3
|
readonly userId: string;
|
|
4
4
|
readonly storeId: string;
|
|
5
5
|
}
|
|
6
6
|
/** Identity only; any dashboard role counts. The proxy owns write-origin checks; the app owns permissions. */
|
|
7
|
-
export declare function
|
|
7
|
+
export declare function requireStoreUser(context: SwellRequestContext): StoreUser;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { SwellError } from './error.js';
|
|
2
|
+
/** Identity only; any dashboard role counts. The proxy owns write-origin checks; the app owns permissions. */
|
|
3
|
+
export function requireStoreUser(context) {
|
|
4
|
+
if (!context.storeUser)
|
|
5
|
+
throw new SwellError('Store user required', { status: 401, code: 'store_user_required' });
|
|
6
|
+
return context.storeUser;
|
|
7
|
+
}
|
package/dist/version.d.cts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const USER_AGENT = "swell-apps-sdk/2.0.0-alpha.
|
|
1
|
+
export declare const USER_AGENT = "swell-apps-sdk/2.0.0-alpha.3";
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const USER_AGENT = "swell-apps-sdk/2.0.0-alpha.
|
|
1
|
+
export declare const USER_AGENT = "swell-apps-sdk/2.0.0-alpha.3";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Replaced with the package version by the build.
|
|
2
|
-
export const USER_AGENT = 'swell-apps-sdk/2.0.0-alpha.
|
|
2
|
+
export const USER_AGENT = 'swell-apps-sdk/2.0.0-alpha.3';
|
package/package.json
CHANGED
package/dist/staff.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { SwellError } from './error.js';
|
|
2
|
-
/** Identity only; any dashboard role counts. The proxy owns write-origin checks; the app owns permissions. */
|
|
3
|
-
export function requireStaff(context) {
|
|
4
|
-
if (!context.staff)
|
|
5
|
-
throw new SwellError('Staff identity required', { status: 401, code: 'staff_required' });
|
|
6
|
-
return context.staff;
|
|
7
|
-
}
|