@sneekin/sdk 1.1.0 → 1.1.2
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/core/derive-keys.d.ts +1 -0
- package/dist/core/generated/api.d.ts +283 -245
- package/dist/index.cjs +31 -24
- package/dist/index.js +29 -23
- package/dist/messages/index.cjs +31 -24
- package/dist/messages/index.js +29 -23
- package/dist/pay/index.cjs +31 -24
- package/dist/pay/index.js +29 -23
- package/dist/verify/auth-schema.d.ts +2 -2
- package/dist/verify/index.cjs +44 -29
- package/dist/verify/index.js +43 -29
- package/package.json +2 -2
package/dist/verify/index.js
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
// src/core/derive-keys.ts
|
|
2
|
+
import { hkdfSync } from "crypto";
|
|
3
|
+
var KEY_LABELS = {
|
|
4
|
+
qrSigningKey: "sneek-qr-v1",
|
|
5
|
+
webhookSigningKey: "sneek-webhook-v1",
|
|
6
|
+
proofKey: "sneek-proof-v1"
|
|
7
|
+
};
|
|
8
|
+
var API_KEY_PATTERN = /^sneek_[A-Za-z0-9_-]{43}$/;
|
|
9
|
+
function isValidApiKey(apiKey) {
|
|
10
|
+
return API_KEY_PATTERN.test(apiKey);
|
|
11
|
+
}
|
|
12
|
+
function deriveKeys(apiKey) {
|
|
13
|
+
if (typeof apiKey !== "string" || !isValidApiKey(apiKey)) {
|
|
14
|
+
throw new TypeError("apiKey must use the sneek_ format with a 43-character secret");
|
|
15
|
+
}
|
|
16
|
+
const inputKeyMaterial = Buffer.from(apiKey, "utf8");
|
|
17
|
+
const derive = (label) => Buffer.from(
|
|
18
|
+
hkdfSync("sha256", inputKeyMaterial, Buffer.alloc(0), label, 32)
|
|
19
|
+
);
|
|
20
|
+
return {
|
|
21
|
+
qrSigningKey: derive(KEY_LABELS.qrSigningKey),
|
|
22
|
+
webhookSigningKey: derive(KEY_LABELS.webhookSigningKey),
|
|
23
|
+
proofKey: derive(KEY_LABELS.proofKey)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
1
27
|
// src/core/client.ts
|
|
2
28
|
var SneekApiError = class extends Error {
|
|
3
29
|
constructor(status, type, title, detail) {
|
|
@@ -22,6 +48,7 @@ var SneekClient = class {
|
|
|
22
48
|
constructor(options) {
|
|
23
49
|
if (!options.apiUrl) throw new Error("apiUrl is required");
|
|
24
50
|
if (!options.apiKey) throw new Error("apiKey is required");
|
|
51
|
+
if (!isValidApiKey(options.apiKey)) throw new Error("apiKey must use the sneek_ format with a 43-character secret");
|
|
25
52
|
this.apiUrl = options.apiUrl.replace(/\/+$/, "");
|
|
26
53
|
this.apiKey = options.apiKey;
|
|
27
54
|
this.fetchImpl = options.fetch ?? fetch;
|
|
@@ -93,31 +120,9 @@ function asProblem(value) {
|
|
|
93
120
|
};
|
|
94
121
|
}
|
|
95
122
|
|
|
96
|
-
// src/core/derive-keys.ts
|
|
97
|
-
import { hkdfSync } from "crypto";
|
|
98
|
-
var KEY_LABELS = {
|
|
99
|
-
qrSigningKey: "sneek-qr-v1",
|
|
100
|
-
webhookSigningKey: "sneek-webhook-v1",
|
|
101
|
-
proofKey: "sneek-proof-v1"
|
|
102
|
-
};
|
|
103
|
-
function deriveKeys(apiKey) {
|
|
104
|
-
if (typeof apiKey !== "string" || apiKey.length === 0) {
|
|
105
|
-
throw new TypeError("apiKey must be a nonempty string");
|
|
106
|
-
}
|
|
107
|
-
const inputKeyMaterial = Buffer.from(apiKey, "utf8");
|
|
108
|
-
const derive = (label) => Buffer.from(
|
|
109
|
-
hkdfSync("sha256", inputKeyMaterial, Buffer.alloc(0), label, 32)
|
|
110
|
-
);
|
|
111
|
-
return {
|
|
112
|
-
qrSigningKey: derive(KEY_LABELS.qrSigningKey),
|
|
113
|
-
webhookSigningKey: derive(KEY_LABELS.webhookSigningKey),
|
|
114
|
-
proofKey: derive(KEY_LABELS.proofKey)
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
123
|
// src/verify/handler.ts
|
|
119
124
|
import { createHash, createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
120
|
-
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
125
|
+
import { createLocalJWKSet, createRemoteJWKSet, jwtVerify } from "jose";
|
|
121
126
|
|
|
122
127
|
// src/verify/auth-schema.ts
|
|
123
128
|
import { z } from "zod";
|
|
@@ -305,8 +310,16 @@ var SneekVerifyHandler = class {
|
|
|
305
310
|
const partner = await this.client.request("GET", "/api/verify/qr/partner-config");
|
|
306
311
|
let payload;
|
|
307
312
|
try {
|
|
308
|
-
|
|
309
|
-
|
|
313
|
+
let jwks = this.jwksByIssuer.get(partner.issuer);
|
|
314
|
+
if (!jwks) {
|
|
315
|
+
try {
|
|
316
|
+
const localJwksData = await this.client.request("GET", "/oauth/jwks");
|
|
317
|
+
jwks = createLocalJWKSet(localJwksData);
|
|
318
|
+
} catch {
|
|
319
|
+
jwks = createRemoteJWKSet(new URL("/oauth/jwks", partner.issuer));
|
|
320
|
+
}
|
|
321
|
+
this.jwksByIssuer.set(partner.issuer, jwks);
|
|
322
|
+
}
|
|
310
323
|
({ payload } = await jwtVerify(result.idToken, jwks, {
|
|
311
324
|
issuer: partner.issuer,
|
|
312
325
|
audience: partner.clientId
|
|
@@ -390,11 +403,11 @@ var SneekVerifyHandler = class {
|
|
|
390
403
|
const partner = await this.partnerConfig();
|
|
391
404
|
if (!partner.origins.includes(origin)) return jsonResponse({ error: "Origin is not registered for QR verification" }, 403);
|
|
392
405
|
const nonce = randomBytes(32).toString("hex");
|
|
393
|
-
const ref = createHash("sha256").update(nonce).digest("hex");
|
|
406
|
+
const ref = createHash("sha256").update(Buffer.from(nonce, "hex")).digest("hex");
|
|
394
407
|
const issuedAt = Math.floor(Date.now() / 1e3);
|
|
395
408
|
const expiresAt = (issuedAt + partner.qrTtlSeconds) * 1e3;
|
|
396
409
|
const bindingProof = randomBytes(32).toString("base64url");
|
|
397
|
-
const signature = createHmac("sha256", this.keys.qrSigningKey).update(["sneek-qr-v1", partner.applicationId, nonce, origin, String(issuedAt)].join("\n")).digest().subarray(0, 16).toString("base64url");
|
|
410
|
+
const signature = createHmac("sha256", this.keys.qrSigningKey).update(["sneek-qr-v1", partner.applicationId, partner.kid, nonce, origin, String(issuedAt)].join("\n")).digest().subarray(0, 16).toString("base64url");
|
|
398
411
|
await this.approvalStore.createQrSession({
|
|
399
412
|
ref,
|
|
400
413
|
applicationId: partner.applicationId,
|
|
@@ -402,7 +415,7 @@ var SneekVerifyHandler = class {
|
|
|
402
415
|
bindingProofHash: this.hash(bindingProof),
|
|
403
416
|
expiresAt
|
|
404
417
|
});
|
|
405
|
-
return jsonResponse({ ref, nonce, issuedAt, expiresAt, signature, bindingProof }, 200);
|
|
418
|
+
return jsonResponse({ ref, nonce, issuedAt, expiresAt, kid: partner.kid, signature, bindingProof }, 200);
|
|
406
419
|
}
|
|
407
420
|
async qrStatus(ref, bindingProof, origin) {
|
|
408
421
|
const session = await this.qrSession(ref, bindingProof, origin);
|
|
@@ -437,5 +450,6 @@ export {
|
|
|
437
450
|
SneekApiError,
|
|
438
451
|
SneekClient,
|
|
439
452
|
SneekVerifyHandler,
|
|
440
|
-
deriveKeys
|
|
453
|
+
deriveKeys,
|
|
454
|
+
isValidApiKey
|
|
441
455
|
};
|