@whatalo/plugin-sdk 1.0.0 → 1.1.0
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/dist/bridge/index.cjs +190 -4
- package/dist/bridge/index.cjs.map +1 -1
- package/dist/bridge/index.d.cts +109 -2
- package/dist/bridge/index.d.ts +109 -2
- package/dist/bridge/index.mjs +185 -3
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/client/index.cjs +5 -0
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.mjs +5 -0
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.cjs +193 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +189 -2
- package/dist/index.mjs.map +1 -1
- package/dist/server/verify-session-token.cjs +103 -0
- package/dist/server/verify-session-token.cjs.map +1 -0
- package/dist/server/verify-session-token.d.cts +1 -0
- package/dist/server/verify-session-token.d.ts +1 -0
- package/dist/server/verify-session-token.mjs +76 -0
- package/dist/server/verify-session-token.mjs.map +1 -0
- package/dist/session-token.cjs +134 -0
- package/dist/session-token.cjs.map +1 -0
- package/dist/session-token.d.cts +77 -0
- package/dist/session-token.d.ts +77 -0
- package/dist/session-token.mjs +108 -0
- package/dist/session-token.mjs.map +1 -0
- package/package.json +26 -30
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/session-token.ts
|
|
21
|
+
var session_token_exports = {};
|
|
22
|
+
__export(session_token_exports, {
|
|
23
|
+
signSessionToken: () => signSessionToken,
|
|
24
|
+
verifySessionToken: () => verifySessionToken
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(session_token_exports);
|
|
27
|
+
var import_node_crypto = require("crypto");
|
|
28
|
+
var TOKEN_TTL_SECONDS = 300;
|
|
29
|
+
var HEADER_B64 = Buffer.from(
|
|
30
|
+
JSON.stringify({ alg: "HS256", typ: "JWT" })
|
|
31
|
+
).toString("base64url");
|
|
32
|
+
function encodeB64(obj) {
|
|
33
|
+
return Buffer.from(JSON.stringify(obj)).toString("base64url");
|
|
34
|
+
}
|
|
35
|
+
function decodeB64(encoded) {
|
|
36
|
+
const json = Buffer.from(encoded, "base64url").toString("utf8");
|
|
37
|
+
return JSON.parse(json);
|
|
38
|
+
}
|
|
39
|
+
function computeHmac(data, secret) {
|
|
40
|
+
return (0, import_node_crypto.createHmac)("sha256", secret).update(data, "utf8").digest("hex");
|
|
41
|
+
}
|
|
42
|
+
function signSessionToken(payload, clientSecret) {
|
|
43
|
+
if (!clientSecret || clientSecret.length === 0) {
|
|
44
|
+
throw new Error("client_secret must be a non-empty string");
|
|
45
|
+
}
|
|
46
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
47
|
+
const exp = now + TOKEN_TTL_SECONDS;
|
|
48
|
+
const claims = {
|
|
49
|
+
iss: "whatalo",
|
|
50
|
+
aud: payload.aud,
|
|
51
|
+
sub: payload.sub,
|
|
52
|
+
iat: now,
|
|
53
|
+
exp,
|
|
54
|
+
// 16 random bytes → 32-char hex string used as the JWT ID (jti)
|
|
55
|
+
jti: (0, import_node_crypto.randomBytes)(16).toString("hex"),
|
|
56
|
+
storeId: payload.storeId,
|
|
57
|
+
appId: payload.appId,
|
|
58
|
+
scopes: payload.scopes,
|
|
59
|
+
installationId: payload.installationId
|
|
60
|
+
};
|
|
61
|
+
const payloadB64 = encodeB64(claims);
|
|
62
|
+
const signingInput = `${HEADER_B64}.${payloadB64}`;
|
|
63
|
+
const signature = computeHmac(signingInput, clientSecret);
|
|
64
|
+
return {
|
|
65
|
+
token: `${signingInput}.${signature}`,
|
|
66
|
+
expiresAt: exp
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function verifySessionToken(token, clientSecret) {
|
|
70
|
+
if (!token || !clientSecret) {
|
|
71
|
+
throw new Error("Token and client_secret are required");
|
|
72
|
+
}
|
|
73
|
+
const parts = token.split(".");
|
|
74
|
+
if (parts.length !== 3) {
|
|
75
|
+
throw new Error("Invalid token structure: expected header.payload.signature");
|
|
76
|
+
}
|
|
77
|
+
const [headerB64, payloadB64, receivedSig] = parts;
|
|
78
|
+
let header;
|
|
79
|
+
try {
|
|
80
|
+
header = decodeB64(headerB64);
|
|
81
|
+
} catch {
|
|
82
|
+
throw new Error("Invalid token: malformed header");
|
|
83
|
+
}
|
|
84
|
+
if (header.alg !== "HS256" || header.typ !== "JWT") {
|
|
85
|
+
throw new Error("Invalid token: unsupported algorithm or type");
|
|
86
|
+
}
|
|
87
|
+
const signingInput = `${headerB64}.${payloadB64}`;
|
|
88
|
+
const expectedSig = computeHmac(signingInput, clientSecret);
|
|
89
|
+
const normalizeKey = expectedSig;
|
|
90
|
+
const receivedNorm = (0, import_node_crypto.createHmac)("sha256", normalizeKey).update(receivedSig).digest();
|
|
91
|
+
const expectedNorm = (0, import_node_crypto.createHmac)("sha256", normalizeKey).update(expectedSig).digest();
|
|
92
|
+
if (!(0, import_node_crypto.timingSafeEqual)(receivedNorm, expectedNorm)) {
|
|
93
|
+
throw new Error("Invalid token: signature mismatch");
|
|
94
|
+
}
|
|
95
|
+
let claims;
|
|
96
|
+
try {
|
|
97
|
+
claims = decodeB64(payloadB64);
|
|
98
|
+
} catch {
|
|
99
|
+
throw new Error("Invalid token: malformed payload");
|
|
100
|
+
}
|
|
101
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
102
|
+
const CLOCK_SKEW_SECONDS = 30;
|
|
103
|
+
if (typeof claims.exp !== "number" || claims.exp + CLOCK_SKEW_SECONDS <= now) {
|
|
104
|
+
throw new Error("Token has expired");
|
|
105
|
+
}
|
|
106
|
+
if (claims.iss !== "whatalo") {
|
|
107
|
+
throw new Error(`Invalid token: unexpected issuer "${claims.iss}"`);
|
|
108
|
+
}
|
|
109
|
+
const required = [
|
|
110
|
+
"aud",
|
|
111
|
+
"sub",
|
|
112
|
+
"iat",
|
|
113
|
+
"jti",
|
|
114
|
+
"storeId",
|
|
115
|
+
"appId",
|
|
116
|
+
"scopes",
|
|
117
|
+
"installationId"
|
|
118
|
+
];
|
|
119
|
+
for (const key of required) {
|
|
120
|
+
if (claims[key] === void 0 || claims[key] === null) {
|
|
121
|
+
throw new Error(`Invalid token: missing required claim "${key}"`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (!Array.isArray(claims.scopes)) {
|
|
125
|
+
throw new Error("Invalid token: scopes must be an array");
|
|
126
|
+
}
|
|
127
|
+
return claims;
|
|
128
|
+
}
|
|
129
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
130
|
+
0 && (module.exports = {
|
|
131
|
+
signSessionToken,
|
|
132
|
+
verifySessionToken
|
|
133
|
+
});
|
|
134
|
+
//# sourceMappingURL=session-token.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/session-token.ts"],"sourcesContent":["/**\n * Session Token — server-side only.\n *\n * Signs and verifies short-lived HMAC-SHA256 JWTs used by plugin iframes to\n * authenticate against their own backend. The token is issued by the Whatalo\n * admin host and forwarded by the plugin frontend to its server, which\n * validates the signature using the app's client_secret.\n *\n * Architecture:\n * Admin host ──issues──► iframe (via App Bridge)\n * iframe frontend ──sends──► plugin backend\n * plugin backend ──validates──► calls Whatalo public API with its own key\n *\n * This module uses ONLY Node.js built-in `node:crypto`. No external dependencies.\n * It must NEVER be imported in browser code (import from server entry points only).\n */\n\nimport { createHmac, timingSafeEqual, randomBytes } from \"node:crypto\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * JWT claims embedded in every session token.\n * All string fields are required so the verifier can validate them fully.\n */\nexport interface SessionTokenClaims {\n /** Issuer — always \"whatalo\" */\n iss: \"whatalo\";\n /** Audience — the app's client_id (marketplace_apps.slug) */\n aud: string;\n /** Subject — the store's public_id */\n sub: string;\n /** Expiration time (Unix seconds) */\n exp: number;\n /** Issued-at time (Unix seconds) */\n iat: number;\n /** JWT ID — unique nonce to prevent replay attacks */\n jti: string;\n /** Store public identifier (same as sub, kept explicit for SDK consumers) */\n storeId: string;\n /** App/plugin public identifier (slug) */\n appId: string;\n /** Permission scopes granted at install time */\n scopes: string[];\n /** app_installations.id for the specific install record */\n installationId: string;\n}\n\n/** Output of signSessionToken — includes the raw token and its expiry. */\nexport interface SignedSessionToken {\n /** Compact JWT string (header.payload.signature) */\n token: string;\n /** Expiration as Unix seconds (same as claims.exp) */\n expiresAt: number;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Token lifetime in seconds. Short by design — the bridge auto-refreshes. */\nconst TOKEN_TTL_SECONDS = 300; // 5 minutes\n\n/** Fixed header for all session tokens (alg=HS256, typ=JWT). */\nconst HEADER_B64 = Buffer.from(\n JSON.stringify({ alg: \"HS256\", typ: \"JWT\" }),\n).toString(\"base64url\");\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Encodes an object as a base64url JSON string (no padding). */\nfunction encodeB64(obj: unknown): string {\n return Buffer.from(JSON.stringify(obj)).toString(\"base64url\");\n}\n\n/** Decodes a base64url string to a plain object. Throws on malformed input. */\nfunction decodeB64<T>(encoded: string): T {\n const json = Buffer.from(encoded, \"base64url\").toString(\"utf8\");\n return JSON.parse(json) as T;\n}\n\n/**\n * Computes HMAC-SHA256 over `data` using `secret` and returns the hex digest.\n * Uses the app's client_secret as the key — never a platform-wide secret.\n */\nfunction computeHmac(data: string, secret: string): string {\n return createHmac(\"sha256\", secret).update(data, \"utf8\").digest(\"hex\");\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Signs a session token for the given claims using the app's client_secret.\n *\n * @param payload - Token claims (storeId, appId, scopes, installationId, aud).\n * exp/iat/jti/iss are injected automatically.\n * @param clientSecret - The app's client_secret from marketplace_apps table.\n * @returns Signed JWT and its expiry timestamp.\n */\nexport function signSessionToken(\n payload: Omit<SessionTokenClaims, \"iss\" | \"iat\" | \"exp\" | \"jti\">,\n clientSecret: string,\n): SignedSessionToken {\n if (!clientSecret || clientSecret.length === 0) {\n throw new Error(\"client_secret must be a non-empty string\");\n }\n\n const now = Math.floor(Date.now() / 1000);\n const exp = now + TOKEN_TTL_SECONDS;\n\n const claims: SessionTokenClaims = {\n iss: \"whatalo\",\n aud: payload.aud,\n sub: payload.sub,\n iat: now,\n exp,\n // 16 random bytes → 32-char hex string used as the JWT ID (jti)\n jti: randomBytes(16).toString(\"hex\"),\n storeId: payload.storeId,\n appId: payload.appId,\n scopes: payload.scopes,\n installationId: payload.installationId,\n };\n\n const payloadB64 = encodeB64(claims);\n const signingInput = `${HEADER_B64}.${payloadB64}`;\n const signature = computeHmac(signingInput, clientSecret);\n\n return {\n token: `${signingInput}.${signature}`,\n expiresAt: exp,\n };\n}\n\n/**\n * Verifies a session token previously issued by signSessionToken.\n *\n * Checks (in order):\n * 1. Token structure (3 parts separated by \".\")\n * 2. Header declares HS256 + JWT\n * 3. HMAC-SHA256 signature matches (constant-time comparison)\n * 4. Token has not expired\n * 5. Issuer is \"whatalo\"\n * 6. Required claims are present\n *\n * @param token - Compact JWT string from the plugin frontend.\n * @param clientSecret - The app's client_secret used to sign the original token.\n * @returns Decoded and validated SessionTokenClaims.\n * @throws Error with a descriptive message on any validation failure.\n */\nexport function verifySessionToken(\n token: string,\n clientSecret: string,\n): SessionTokenClaims {\n if (!token || !clientSecret) {\n throw new Error(\"Token and client_secret are required\");\n }\n\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\"Invalid token structure: expected header.payload.signature\");\n }\n\n const [headerB64, payloadB64, receivedSig] = parts as [string, string, string];\n\n // 1. Validate header\n let header: { alg?: string; typ?: string };\n try {\n header = decodeB64<{ alg?: string; typ?: string }>(headerB64);\n } catch {\n throw new Error(\"Invalid token: malformed header\");\n }\n if (header.alg !== \"HS256\" || header.typ !== \"JWT\") {\n throw new Error(\"Invalid token: unsupported algorithm or type\");\n }\n\n // 2. Verify HMAC signature (constant-time to prevent timing attacks)\n const signingInput = `${headerB64}.${payloadB64}`;\n const expectedSig = computeHmac(signingInput, clientSecret);\n\n // Re-HMAC both sides to normalize to a fixed-length buffer regardless of\n // the received signature's actual length. This eliminates the timing\n // side-channel that would otherwise leak whether lengths match.\n const normalizeKey = expectedSig; // use expected sig as HMAC key for normalization\n const receivedNorm = createHmac(\"sha256\", normalizeKey).update(receivedSig).digest();\n const expectedNorm = createHmac(\"sha256\", normalizeKey).update(expectedSig).digest();\n\n if (!timingSafeEqual(receivedNorm, expectedNorm)) {\n throw new Error(\"Invalid token: signature mismatch\");\n }\n\n // 3. Decode claims\n let claims: SessionTokenClaims;\n try {\n claims = decodeB64<SessionTokenClaims>(payloadB64);\n } catch {\n throw new Error(\"Invalid token: malformed payload\");\n }\n\n // 4. Check expiration (with 30-second clock skew tolerance for distributed systems)\n const now = Math.floor(Date.now() / 1000);\n const CLOCK_SKEW_SECONDS = 30;\n if (typeof claims.exp !== \"number\" || claims.exp + CLOCK_SKEW_SECONDS <= now) {\n throw new Error(\"Token has expired\");\n }\n\n // 5. Check issuer\n if (claims.iss !== \"whatalo\") {\n throw new Error(`Invalid token: unexpected issuer \"${claims.iss}\"`);\n }\n\n // 6. Validate required claims\n const required: Array<keyof SessionTokenClaims> = [\n \"aud\", \"sub\", \"iat\", \"jti\", \"storeId\", \"appId\", \"scopes\", \"installationId\",\n ];\n for (const key of required) {\n if (claims[key] === undefined || claims[key] === null) {\n throw new Error(`Invalid token: missing required claim \"${key}\"`);\n }\n }\n\n if (!Array.isArray(claims.scopes)) {\n throw new Error(\"Invalid token: scopes must be an array\");\n }\n\n return claims;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Token — server-side only.
|
|
3
|
+
*
|
|
4
|
+
* Signs and verifies short-lived HMAC-SHA256 JWTs used by plugin iframes to
|
|
5
|
+
* authenticate against their own backend. The token is issued by the Whatalo
|
|
6
|
+
* admin host and forwarded by the plugin frontend to its server, which
|
|
7
|
+
* validates the signature using the app's client_secret.
|
|
8
|
+
*
|
|
9
|
+
* Architecture:
|
|
10
|
+
* Admin host ──issues──► iframe (via App Bridge)
|
|
11
|
+
* iframe frontend ──sends──► plugin backend
|
|
12
|
+
* plugin backend ──validates──► calls Whatalo public API with its own key
|
|
13
|
+
*
|
|
14
|
+
* This module uses ONLY Node.js built-in `node:crypto`. No external dependencies.
|
|
15
|
+
* It must NEVER be imported in browser code (import from server entry points only).
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* JWT claims embedded in every session token.
|
|
19
|
+
* All string fields are required so the verifier can validate them fully.
|
|
20
|
+
*/
|
|
21
|
+
interface SessionTokenClaims {
|
|
22
|
+
/** Issuer — always "whatalo" */
|
|
23
|
+
iss: "whatalo";
|
|
24
|
+
/** Audience — the app's client_id (marketplace_apps.slug) */
|
|
25
|
+
aud: string;
|
|
26
|
+
/** Subject — the store's public_id */
|
|
27
|
+
sub: string;
|
|
28
|
+
/** Expiration time (Unix seconds) */
|
|
29
|
+
exp: number;
|
|
30
|
+
/** Issued-at time (Unix seconds) */
|
|
31
|
+
iat: number;
|
|
32
|
+
/** JWT ID — unique nonce to prevent replay attacks */
|
|
33
|
+
jti: string;
|
|
34
|
+
/** Store public identifier (same as sub, kept explicit for SDK consumers) */
|
|
35
|
+
storeId: string;
|
|
36
|
+
/** App/plugin public identifier (slug) */
|
|
37
|
+
appId: string;
|
|
38
|
+
/** Permission scopes granted at install time */
|
|
39
|
+
scopes: string[];
|
|
40
|
+
/** app_installations.id for the specific install record */
|
|
41
|
+
installationId: string;
|
|
42
|
+
}
|
|
43
|
+
/** Output of signSessionToken — includes the raw token and its expiry. */
|
|
44
|
+
interface SignedSessionToken {
|
|
45
|
+
/** Compact JWT string (header.payload.signature) */
|
|
46
|
+
token: string;
|
|
47
|
+
/** Expiration as Unix seconds (same as claims.exp) */
|
|
48
|
+
expiresAt: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Signs a session token for the given claims using the app's client_secret.
|
|
52
|
+
*
|
|
53
|
+
* @param payload - Token claims (storeId, appId, scopes, installationId, aud).
|
|
54
|
+
* exp/iat/jti/iss are injected automatically.
|
|
55
|
+
* @param clientSecret - The app's client_secret from marketplace_apps table.
|
|
56
|
+
* @returns Signed JWT and its expiry timestamp.
|
|
57
|
+
*/
|
|
58
|
+
declare function signSessionToken(payload: Omit<SessionTokenClaims, "iss" | "iat" | "exp" | "jti">, clientSecret: string): SignedSessionToken;
|
|
59
|
+
/**
|
|
60
|
+
* Verifies a session token previously issued by signSessionToken.
|
|
61
|
+
*
|
|
62
|
+
* Checks (in order):
|
|
63
|
+
* 1. Token structure (3 parts separated by ".")
|
|
64
|
+
* 2. Header declares HS256 + JWT
|
|
65
|
+
* 3. HMAC-SHA256 signature matches (constant-time comparison)
|
|
66
|
+
* 4. Token has not expired
|
|
67
|
+
* 5. Issuer is "whatalo"
|
|
68
|
+
* 6. Required claims are present
|
|
69
|
+
*
|
|
70
|
+
* @param token - Compact JWT string from the plugin frontend.
|
|
71
|
+
* @param clientSecret - The app's client_secret used to sign the original token.
|
|
72
|
+
* @returns Decoded and validated SessionTokenClaims.
|
|
73
|
+
* @throws Error with a descriptive message on any validation failure.
|
|
74
|
+
*/
|
|
75
|
+
declare function verifySessionToken(token: string, clientSecret: string): SessionTokenClaims;
|
|
76
|
+
|
|
77
|
+
export { type SessionTokenClaims, type SignedSessionToken, signSessionToken, verifySessionToken };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Token — server-side only.
|
|
3
|
+
*
|
|
4
|
+
* Signs and verifies short-lived HMAC-SHA256 JWTs used by plugin iframes to
|
|
5
|
+
* authenticate against their own backend. The token is issued by the Whatalo
|
|
6
|
+
* admin host and forwarded by the plugin frontend to its server, which
|
|
7
|
+
* validates the signature using the app's client_secret.
|
|
8
|
+
*
|
|
9
|
+
* Architecture:
|
|
10
|
+
* Admin host ──issues──► iframe (via App Bridge)
|
|
11
|
+
* iframe frontend ──sends──► plugin backend
|
|
12
|
+
* plugin backend ──validates──► calls Whatalo public API with its own key
|
|
13
|
+
*
|
|
14
|
+
* This module uses ONLY Node.js built-in `node:crypto`. No external dependencies.
|
|
15
|
+
* It must NEVER be imported in browser code (import from server entry points only).
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* JWT claims embedded in every session token.
|
|
19
|
+
* All string fields are required so the verifier can validate them fully.
|
|
20
|
+
*/
|
|
21
|
+
interface SessionTokenClaims {
|
|
22
|
+
/** Issuer — always "whatalo" */
|
|
23
|
+
iss: "whatalo";
|
|
24
|
+
/** Audience — the app's client_id (marketplace_apps.slug) */
|
|
25
|
+
aud: string;
|
|
26
|
+
/** Subject — the store's public_id */
|
|
27
|
+
sub: string;
|
|
28
|
+
/** Expiration time (Unix seconds) */
|
|
29
|
+
exp: number;
|
|
30
|
+
/** Issued-at time (Unix seconds) */
|
|
31
|
+
iat: number;
|
|
32
|
+
/** JWT ID — unique nonce to prevent replay attacks */
|
|
33
|
+
jti: string;
|
|
34
|
+
/** Store public identifier (same as sub, kept explicit for SDK consumers) */
|
|
35
|
+
storeId: string;
|
|
36
|
+
/** App/plugin public identifier (slug) */
|
|
37
|
+
appId: string;
|
|
38
|
+
/** Permission scopes granted at install time */
|
|
39
|
+
scopes: string[];
|
|
40
|
+
/** app_installations.id for the specific install record */
|
|
41
|
+
installationId: string;
|
|
42
|
+
}
|
|
43
|
+
/** Output of signSessionToken — includes the raw token and its expiry. */
|
|
44
|
+
interface SignedSessionToken {
|
|
45
|
+
/** Compact JWT string (header.payload.signature) */
|
|
46
|
+
token: string;
|
|
47
|
+
/** Expiration as Unix seconds (same as claims.exp) */
|
|
48
|
+
expiresAt: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Signs a session token for the given claims using the app's client_secret.
|
|
52
|
+
*
|
|
53
|
+
* @param payload - Token claims (storeId, appId, scopes, installationId, aud).
|
|
54
|
+
* exp/iat/jti/iss are injected automatically.
|
|
55
|
+
* @param clientSecret - The app's client_secret from marketplace_apps table.
|
|
56
|
+
* @returns Signed JWT and its expiry timestamp.
|
|
57
|
+
*/
|
|
58
|
+
declare function signSessionToken(payload: Omit<SessionTokenClaims, "iss" | "iat" | "exp" | "jti">, clientSecret: string): SignedSessionToken;
|
|
59
|
+
/**
|
|
60
|
+
* Verifies a session token previously issued by signSessionToken.
|
|
61
|
+
*
|
|
62
|
+
* Checks (in order):
|
|
63
|
+
* 1. Token structure (3 parts separated by ".")
|
|
64
|
+
* 2. Header declares HS256 + JWT
|
|
65
|
+
* 3. HMAC-SHA256 signature matches (constant-time comparison)
|
|
66
|
+
* 4. Token has not expired
|
|
67
|
+
* 5. Issuer is "whatalo"
|
|
68
|
+
* 6. Required claims are present
|
|
69
|
+
*
|
|
70
|
+
* @param token - Compact JWT string from the plugin frontend.
|
|
71
|
+
* @param clientSecret - The app's client_secret used to sign the original token.
|
|
72
|
+
* @returns Decoded and validated SessionTokenClaims.
|
|
73
|
+
* @throws Error with a descriptive message on any validation failure.
|
|
74
|
+
*/
|
|
75
|
+
declare function verifySessionToken(token: string, clientSecret: string): SessionTokenClaims;
|
|
76
|
+
|
|
77
|
+
export { type SessionTokenClaims, type SignedSessionToken, signSessionToken, verifySessionToken };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// src/session-token.ts
|
|
2
|
+
import { createHmac, timingSafeEqual, randomBytes } from "crypto";
|
|
3
|
+
var TOKEN_TTL_SECONDS = 300;
|
|
4
|
+
var HEADER_B64 = Buffer.from(
|
|
5
|
+
JSON.stringify({ alg: "HS256", typ: "JWT" })
|
|
6
|
+
).toString("base64url");
|
|
7
|
+
function encodeB64(obj) {
|
|
8
|
+
return Buffer.from(JSON.stringify(obj)).toString("base64url");
|
|
9
|
+
}
|
|
10
|
+
function decodeB64(encoded) {
|
|
11
|
+
const json = Buffer.from(encoded, "base64url").toString("utf8");
|
|
12
|
+
return JSON.parse(json);
|
|
13
|
+
}
|
|
14
|
+
function computeHmac(data, secret) {
|
|
15
|
+
return createHmac("sha256", secret).update(data, "utf8").digest("hex");
|
|
16
|
+
}
|
|
17
|
+
function signSessionToken(payload, clientSecret) {
|
|
18
|
+
if (!clientSecret || clientSecret.length === 0) {
|
|
19
|
+
throw new Error("client_secret must be a non-empty string");
|
|
20
|
+
}
|
|
21
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
22
|
+
const exp = now + TOKEN_TTL_SECONDS;
|
|
23
|
+
const claims = {
|
|
24
|
+
iss: "whatalo",
|
|
25
|
+
aud: payload.aud,
|
|
26
|
+
sub: payload.sub,
|
|
27
|
+
iat: now,
|
|
28
|
+
exp,
|
|
29
|
+
// 16 random bytes → 32-char hex string used as the JWT ID (jti)
|
|
30
|
+
jti: randomBytes(16).toString("hex"),
|
|
31
|
+
storeId: payload.storeId,
|
|
32
|
+
appId: payload.appId,
|
|
33
|
+
scopes: payload.scopes,
|
|
34
|
+
installationId: payload.installationId
|
|
35
|
+
};
|
|
36
|
+
const payloadB64 = encodeB64(claims);
|
|
37
|
+
const signingInput = `${HEADER_B64}.${payloadB64}`;
|
|
38
|
+
const signature = computeHmac(signingInput, clientSecret);
|
|
39
|
+
return {
|
|
40
|
+
token: `${signingInput}.${signature}`,
|
|
41
|
+
expiresAt: exp
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function verifySessionToken(token, clientSecret) {
|
|
45
|
+
if (!token || !clientSecret) {
|
|
46
|
+
throw new Error("Token and client_secret are required");
|
|
47
|
+
}
|
|
48
|
+
const parts = token.split(".");
|
|
49
|
+
if (parts.length !== 3) {
|
|
50
|
+
throw new Error("Invalid token structure: expected header.payload.signature");
|
|
51
|
+
}
|
|
52
|
+
const [headerB64, payloadB64, receivedSig] = parts;
|
|
53
|
+
let header;
|
|
54
|
+
try {
|
|
55
|
+
header = decodeB64(headerB64);
|
|
56
|
+
} catch {
|
|
57
|
+
throw new Error("Invalid token: malformed header");
|
|
58
|
+
}
|
|
59
|
+
if (header.alg !== "HS256" || header.typ !== "JWT") {
|
|
60
|
+
throw new Error("Invalid token: unsupported algorithm or type");
|
|
61
|
+
}
|
|
62
|
+
const signingInput = `${headerB64}.${payloadB64}`;
|
|
63
|
+
const expectedSig = computeHmac(signingInput, clientSecret);
|
|
64
|
+
const normalizeKey = expectedSig;
|
|
65
|
+
const receivedNorm = createHmac("sha256", normalizeKey).update(receivedSig).digest();
|
|
66
|
+
const expectedNorm = createHmac("sha256", normalizeKey).update(expectedSig).digest();
|
|
67
|
+
if (!timingSafeEqual(receivedNorm, expectedNorm)) {
|
|
68
|
+
throw new Error("Invalid token: signature mismatch");
|
|
69
|
+
}
|
|
70
|
+
let claims;
|
|
71
|
+
try {
|
|
72
|
+
claims = decodeB64(payloadB64);
|
|
73
|
+
} catch {
|
|
74
|
+
throw new Error("Invalid token: malformed payload");
|
|
75
|
+
}
|
|
76
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
77
|
+
const CLOCK_SKEW_SECONDS = 30;
|
|
78
|
+
if (typeof claims.exp !== "number" || claims.exp + CLOCK_SKEW_SECONDS <= now) {
|
|
79
|
+
throw new Error("Token has expired");
|
|
80
|
+
}
|
|
81
|
+
if (claims.iss !== "whatalo") {
|
|
82
|
+
throw new Error(`Invalid token: unexpected issuer "${claims.iss}"`);
|
|
83
|
+
}
|
|
84
|
+
const required = [
|
|
85
|
+
"aud",
|
|
86
|
+
"sub",
|
|
87
|
+
"iat",
|
|
88
|
+
"jti",
|
|
89
|
+
"storeId",
|
|
90
|
+
"appId",
|
|
91
|
+
"scopes",
|
|
92
|
+
"installationId"
|
|
93
|
+
];
|
|
94
|
+
for (const key of required) {
|
|
95
|
+
if (claims[key] === void 0 || claims[key] === null) {
|
|
96
|
+
throw new Error(`Invalid token: missing required claim "${key}"`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (!Array.isArray(claims.scopes)) {
|
|
100
|
+
throw new Error("Invalid token: scopes must be an array");
|
|
101
|
+
}
|
|
102
|
+
return claims;
|
|
103
|
+
}
|
|
104
|
+
export {
|
|
105
|
+
signSessionToken,
|
|
106
|
+
verifySessionToken
|
|
107
|
+
};
|
|
108
|
+
//# sourceMappingURL=session-token.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/session-token.ts"],"sourcesContent":["/**\n * Session Token — server-side only.\n *\n * Signs and verifies short-lived HMAC-SHA256 JWTs used by plugin iframes to\n * authenticate against their own backend. The token is issued by the Whatalo\n * admin host and forwarded by the plugin frontend to its server, which\n * validates the signature using the app's client_secret.\n *\n * Architecture:\n * Admin host ──issues──► iframe (via App Bridge)\n * iframe frontend ──sends──► plugin backend\n * plugin backend ──validates──► calls Whatalo public API with its own key\n *\n * This module uses ONLY Node.js built-in `node:crypto`. No external dependencies.\n * It must NEVER be imported in browser code (import from server entry points only).\n */\n\nimport { createHmac, timingSafeEqual, randomBytes } from \"node:crypto\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * JWT claims embedded in every session token.\n * All string fields are required so the verifier can validate them fully.\n */\nexport interface SessionTokenClaims {\n /** Issuer — always \"whatalo\" */\n iss: \"whatalo\";\n /** Audience — the app's client_id (marketplace_apps.slug) */\n aud: string;\n /** Subject — the store's public_id */\n sub: string;\n /** Expiration time (Unix seconds) */\n exp: number;\n /** Issued-at time (Unix seconds) */\n iat: number;\n /** JWT ID — unique nonce to prevent replay attacks */\n jti: string;\n /** Store public identifier (same as sub, kept explicit for SDK consumers) */\n storeId: string;\n /** App/plugin public identifier (slug) */\n appId: string;\n /** Permission scopes granted at install time */\n scopes: string[];\n /** app_installations.id for the specific install record */\n installationId: string;\n}\n\n/** Output of signSessionToken — includes the raw token and its expiry. */\nexport interface SignedSessionToken {\n /** Compact JWT string (header.payload.signature) */\n token: string;\n /** Expiration as Unix seconds (same as claims.exp) */\n expiresAt: number;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Token lifetime in seconds. Short by design — the bridge auto-refreshes. */\nconst TOKEN_TTL_SECONDS = 300; // 5 minutes\n\n/** Fixed header for all session tokens (alg=HS256, typ=JWT). */\nconst HEADER_B64 = Buffer.from(\n JSON.stringify({ alg: \"HS256\", typ: \"JWT\" }),\n).toString(\"base64url\");\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Encodes an object as a base64url JSON string (no padding). */\nfunction encodeB64(obj: unknown): string {\n return Buffer.from(JSON.stringify(obj)).toString(\"base64url\");\n}\n\n/** Decodes a base64url string to a plain object. Throws on malformed input. */\nfunction decodeB64<T>(encoded: string): T {\n const json = Buffer.from(encoded, \"base64url\").toString(\"utf8\");\n return JSON.parse(json) as T;\n}\n\n/**\n * Computes HMAC-SHA256 over `data` using `secret` and returns the hex digest.\n * Uses the app's client_secret as the key — never a platform-wide secret.\n */\nfunction computeHmac(data: string, secret: string): string {\n return createHmac(\"sha256\", secret).update(data, \"utf8\").digest(\"hex\");\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Signs a session token for the given claims using the app's client_secret.\n *\n * @param payload - Token claims (storeId, appId, scopes, installationId, aud).\n * exp/iat/jti/iss are injected automatically.\n * @param clientSecret - The app's client_secret from marketplace_apps table.\n * @returns Signed JWT and its expiry timestamp.\n */\nexport function signSessionToken(\n payload: Omit<SessionTokenClaims, \"iss\" | \"iat\" | \"exp\" | \"jti\">,\n clientSecret: string,\n): SignedSessionToken {\n if (!clientSecret || clientSecret.length === 0) {\n throw new Error(\"client_secret must be a non-empty string\");\n }\n\n const now = Math.floor(Date.now() / 1000);\n const exp = now + TOKEN_TTL_SECONDS;\n\n const claims: SessionTokenClaims = {\n iss: \"whatalo\",\n aud: payload.aud,\n sub: payload.sub,\n iat: now,\n exp,\n // 16 random bytes → 32-char hex string used as the JWT ID (jti)\n jti: randomBytes(16).toString(\"hex\"),\n storeId: payload.storeId,\n appId: payload.appId,\n scopes: payload.scopes,\n installationId: payload.installationId,\n };\n\n const payloadB64 = encodeB64(claims);\n const signingInput = `${HEADER_B64}.${payloadB64}`;\n const signature = computeHmac(signingInput, clientSecret);\n\n return {\n token: `${signingInput}.${signature}`,\n expiresAt: exp,\n };\n}\n\n/**\n * Verifies a session token previously issued by signSessionToken.\n *\n * Checks (in order):\n * 1. Token structure (3 parts separated by \".\")\n * 2. Header declares HS256 + JWT\n * 3. HMAC-SHA256 signature matches (constant-time comparison)\n * 4. Token has not expired\n * 5. Issuer is \"whatalo\"\n * 6. Required claims are present\n *\n * @param token - Compact JWT string from the plugin frontend.\n * @param clientSecret - The app's client_secret used to sign the original token.\n * @returns Decoded and validated SessionTokenClaims.\n * @throws Error with a descriptive message on any validation failure.\n */\nexport function verifySessionToken(\n token: string,\n clientSecret: string,\n): SessionTokenClaims {\n if (!token || !clientSecret) {\n throw new Error(\"Token and client_secret are required\");\n }\n\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\"Invalid token structure: expected header.payload.signature\");\n }\n\n const [headerB64, payloadB64, receivedSig] = parts as [string, string, string];\n\n // 1. Validate header\n let header: { alg?: string; typ?: string };\n try {\n header = decodeB64<{ alg?: string; typ?: string }>(headerB64);\n } catch {\n throw new Error(\"Invalid token: malformed header\");\n }\n if (header.alg !== \"HS256\" || header.typ !== \"JWT\") {\n throw new Error(\"Invalid token: unsupported algorithm or type\");\n }\n\n // 2. Verify HMAC signature (constant-time to prevent timing attacks)\n const signingInput = `${headerB64}.${payloadB64}`;\n const expectedSig = computeHmac(signingInput, clientSecret);\n\n // Re-HMAC both sides to normalize to a fixed-length buffer regardless of\n // the received signature's actual length. This eliminates the timing\n // side-channel that would otherwise leak whether lengths match.\n const normalizeKey = expectedSig; // use expected sig as HMAC key for normalization\n const receivedNorm = createHmac(\"sha256\", normalizeKey).update(receivedSig).digest();\n const expectedNorm = createHmac(\"sha256\", normalizeKey).update(expectedSig).digest();\n\n if (!timingSafeEqual(receivedNorm, expectedNorm)) {\n throw new Error(\"Invalid token: signature mismatch\");\n }\n\n // 3. Decode claims\n let claims: SessionTokenClaims;\n try {\n claims = decodeB64<SessionTokenClaims>(payloadB64);\n } catch {\n throw new Error(\"Invalid token: malformed payload\");\n }\n\n // 4. Check expiration (with 30-second clock skew tolerance for distributed systems)\n const now = Math.floor(Date.now() / 1000);\n const CLOCK_SKEW_SECONDS = 30;\n if (typeof claims.exp !== \"number\" || claims.exp + CLOCK_SKEW_SECONDS <= now) {\n throw new Error(\"Token has expired\");\n }\n\n // 5. Check issuer\n if (claims.iss !== \"whatalo\") {\n throw new Error(`Invalid token: unexpected issuer \"${claims.iss}\"`);\n }\n\n // 6. Validate required claims\n const required: Array<keyof SessionTokenClaims> = [\n \"aud\", \"sub\", \"iat\", \"jti\", \"storeId\", \"appId\", \"scopes\", \"installationId\",\n ];\n for (const key of required) {\n if (claims[key] === undefined || claims[key] === null) {\n throw new Error(`Invalid token: missing required claim \"${key}\"`);\n }\n }\n\n if (!Array.isArray(claims.scopes)) {\n throw new Error(\"Invalid token: scopes must be an array\");\n }\n\n return claims;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whatalo/plugin-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Official TypeScript SDK for building Whatalo plugins",
|
|
@@ -46,49 +46,45 @@
|
|
|
46
46
|
"types": "./dist/bridge/index.d.ts",
|
|
47
47
|
"import": "./dist/bridge/index.mjs",
|
|
48
48
|
"require": "./dist/bridge/index.cjs"
|
|
49
|
+
},
|
|
50
|
+
"./session-token": {
|
|
51
|
+
"types": "./dist/session-token.d.ts",
|
|
52
|
+
"import": "./dist/session-token.mjs",
|
|
53
|
+
"require": "./dist/session-token.cjs"
|
|
54
|
+
},
|
|
55
|
+
"./server": {
|
|
56
|
+
"types": "./dist/server/verify-session-token.d.ts",
|
|
57
|
+
"import": "./dist/server/verify-session-token.mjs",
|
|
58
|
+
"require": "./dist/server/verify-session-token.cjs"
|
|
49
59
|
}
|
|
50
60
|
},
|
|
51
|
-
"files": [
|
|
52
|
-
"dist",
|
|
53
|
-
"README.md",
|
|
54
|
-
"LICENSE"
|
|
55
|
-
],
|
|
61
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
56
62
|
"publishConfig": {
|
|
57
63
|
"access": "public"
|
|
58
64
|
},
|
|
59
|
-
"keywords": [
|
|
60
|
-
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
|
|
65
|
+
"keywords": ["whatalo", "plugin-sdk", "api", "ecommerce", "latam", "cod"],
|
|
66
|
+
"scripts": {
|
|
67
|
+
"build": "tsup",
|
|
68
|
+
"dev": "tsup --watch",
|
|
69
|
+
"vitest": "vitest",
|
|
70
|
+
"test": "vitest run",
|
|
71
|
+
"test:watch": "vitest",
|
|
72
|
+
"type-check": "tsc --noEmit",
|
|
73
|
+
"clean": "rm -rf dist",
|
|
74
|
+
"prepublishOnly": "pnpm build"
|
|
75
|
+
},
|
|
67
76
|
"peerDependencies": {
|
|
68
77
|
"react": ">=18",
|
|
69
78
|
"react-dom": ">=18"
|
|
70
79
|
},
|
|
71
80
|
"peerDependenciesMeta": {
|
|
72
|
-
"react": {
|
|
73
|
-
|
|
74
|
-
},
|
|
75
|
-
"react-dom": {
|
|
76
|
-
"optional": false
|
|
77
|
-
}
|
|
81
|
+
"react": { "optional": false },
|
|
82
|
+
"react-dom": { "optional": false }
|
|
78
83
|
},
|
|
79
84
|
"devDependencies": {
|
|
80
85
|
"@types/react": "^19.0.0",
|
|
81
86
|
"tsup": "^8.0.0",
|
|
82
87
|
"typescript": "^5.7.0",
|
|
83
88
|
"vitest": "^3.2.4"
|
|
84
|
-
},
|
|
85
|
-
"scripts": {
|
|
86
|
-
"build": "tsup",
|
|
87
|
-
"dev": "tsup --watch",
|
|
88
|
-
"vitest": "vitest",
|
|
89
|
-
"test": "vitest run",
|
|
90
|
-
"test:watch": "vitest",
|
|
91
|
-
"type-check": "tsc --noEmit",
|
|
92
|
-
"clean": "rm -rf dist"
|
|
93
89
|
}
|
|
94
|
-
}
|
|
90
|
+
}
|