@sneekin/sdk 1.0.0 → 1.1.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/dist/core/derive-keys.d.ts +1 -0
- package/dist/core/generated/api.d.ts +199 -245
- package/dist/index.cjs +31 -24
- package/dist/index.js +29 -23
- package/dist/messages/client.d.ts +15 -0
- package/dist/messages/index.cjs +49 -22
- package/dist/messages/index.d.ts +1 -0
- package/dist/messages/index.js +46 -21
- package/dist/pay/index.cjs +31 -24
- package/dist/pay/index.js +29 -23
- package/dist/verify/approval-store.d.ts +22 -0
- package/dist/verify/auth-schema.d.ts +97 -0
- package/dist/verify/handler.d.ts +41 -0
- package/dist/verify/index.cjs +349 -21
- package/dist/verify/index.d.ts +2 -0
- package/dist/verify/index.js +346 -20
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -24,10 +24,37 @@ __export(src_exports, {
|
|
|
24
24
|
SneekApiError: () => SneekApiError,
|
|
25
25
|
SneekClient: () => SneekClient,
|
|
26
26
|
SneekPayClient: () => SneekPayClient,
|
|
27
|
-
deriveKeys: () => deriveKeys
|
|
27
|
+
deriveKeys: () => deriveKeys,
|
|
28
|
+
isValidApiKey: () => isValidApiKey
|
|
28
29
|
});
|
|
29
30
|
module.exports = __toCommonJS(src_exports);
|
|
30
31
|
|
|
32
|
+
// src/core/derive-keys.ts
|
|
33
|
+
var import_node_crypto = require("crypto");
|
|
34
|
+
var KEY_LABELS = {
|
|
35
|
+
qrSigningKey: "sneek-qr-v1",
|
|
36
|
+
webhookSigningKey: "sneek-webhook-v1",
|
|
37
|
+
proofKey: "sneek-proof-v1"
|
|
38
|
+
};
|
|
39
|
+
var API_KEY_PATTERN = /^sneek_[A-Za-z0-9_-]{43}$/;
|
|
40
|
+
function isValidApiKey(apiKey) {
|
|
41
|
+
return API_KEY_PATTERN.test(apiKey);
|
|
42
|
+
}
|
|
43
|
+
function deriveKeys(apiKey) {
|
|
44
|
+
if (typeof apiKey !== "string" || !isValidApiKey(apiKey)) {
|
|
45
|
+
throw new TypeError("apiKey must use the sneek_ format with a 43-character secret");
|
|
46
|
+
}
|
|
47
|
+
const inputKeyMaterial = Buffer.from(apiKey, "utf8");
|
|
48
|
+
const derive = (label) => Buffer.from(
|
|
49
|
+
(0, import_node_crypto.hkdfSync)("sha256", inputKeyMaterial, Buffer.alloc(0), label, 32)
|
|
50
|
+
);
|
|
51
|
+
return {
|
|
52
|
+
qrSigningKey: derive(KEY_LABELS.qrSigningKey),
|
|
53
|
+
webhookSigningKey: derive(KEY_LABELS.webhookSigningKey),
|
|
54
|
+
proofKey: derive(KEY_LABELS.proofKey)
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
31
58
|
// src/core/client.ts
|
|
32
59
|
var SneekApiError = class extends Error {
|
|
33
60
|
constructor(status, type, title, detail) {
|
|
@@ -52,6 +79,7 @@ var SneekClient = class {
|
|
|
52
79
|
constructor(options) {
|
|
53
80
|
if (!options.apiUrl) throw new Error("apiUrl is required");
|
|
54
81
|
if (!options.apiKey) throw new Error("apiKey is required");
|
|
82
|
+
if (!isValidApiKey(options.apiKey)) throw new Error("apiKey must use the sneek_ format with a 43-character secret");
|
|
55
83
|
this.apiUrl = options.apiUrl.replace(/\/+$/, "");
|
|
56
84
|
this.apiKey = options.apiKey;
|
|
57
85
|
this.fetchImpl = options.fetch ?? fetch;
|
|
@@ -123,28 +151,6 @@ function asProblem(value) {
|
|
|
123
151
|
};
|
|
124
152
|
}
|
|
125
153
|
|
|
126
|
-
// src/core/derive-keys.ts
|
|
127
|
-
var import_node_crypto = require("crypto");
|
|
128
|
-
var KEY_LABELS = {
|
|
129
|
-
qrSigningKey: "sneek-qr-v1",
|
|
130
|
-
webhookSigningKey: "sneek-webhook-v1",
|
|
131
|
-
proofKey: "sneek-proof-v1"
|
|
132
|
-
};
|
|
133
|
-
function deriveKeys(apiKey) {
|
|
134
|
-
if (typeof apiKey !== "string" || apiKey.length === 0) {
|
|
135
|
-
throw new TypeError("apiKey must be a nonempty string");
|
|
136
|
-
}
|
|
137
|
-
const inputKeyMaterial = Buffer.from(apiKey, "utf8");
|
|
138
|
-
const derive = (label) => Buffer.from(
|
|
139
|
-
(0, import_node_crypto.hkdfSync)("sha256", inputKeyMaterial, Buffer.alloc(0), label, 32)
|
|
140
|
-
);
|
|
141
|
-
return {
|
|
142
|
-
qrSigningKey: derive(KEY_LABELS.qrSigningKey),
|
|
143
|
-
webhookSigningKey: derive(KEY_LABELS.webhookSigningKey),
|
|
144
|
-
proofKey: derive(KEY_LABELS.proofKey)
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
154
|
// src/pay/payments.ts
|
|
149
155
|
var SneekPayClient = class {
|
|
150
156
|
client;
|
|
@@ -219,5 +225,6 @@ var OrdersClient = class {
|
|
|
219
225
|
SneekApiError,
|
|
220
226
|
SneekClient,
|
|
221
227
|
SneekPayClient,
|
|
222
|
-
deriveKeys
|
|
228
|
+
deriveKeys,
|
|
229
|
+
isValidApiKey
|
|
223
230
|
});
|
package/dist/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,28 +120,6 @@ 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/pay/payments.ts
|
|
119
124
|
var SneekPayClient = class {
|
|
120
125
|
client;
|
|
@@ -188,5 +193,6 @@ export {
|
|
|
188
193
|
SneekApiError,
|
|
189
194
|
SneekClient,
|
|
190
195
|
SneekPayClient,
|
|
191
|
-
deriveKeys
|
|
196
|
+
deriveKeys,
|
|
197
|
+
isValidApiKey
|
|
192
198
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { GetMessageResponse, SendMessage, SendMessageResponse } from '../core/generated/api.js';
|
|
2
|
+
import { SneekApiError, type SneekClientOptions } from '../core/index.js';
|
|
3
|
+
export type SneekMessagesClientOptions = SneekClientOptions;
|
|
4
|
+
export interface SendMessageOptions {
|
|
5
|
+
idempotencyKey?: string;
|
|
6
|
+
}
|
|
7
|
+
/** Typed access to the Sneek message send and delivery-status APIs. */
|
|
8
|
+
export declare class SneekMessagesClient {
|
|
9
|
+
private readonly client;
|
|
10
|
+
constructor(options: SneekMessagesClientOptions);
|
|
11
|
+
send(input: SendMessage, options?: SendMessageOptions): Promise<SendMessageResponse>;
|
|
12
|
+
get(id: string): Promise<GetMessageResponse>;
|
|
13
|
+
}
|
|
14
|
+
export { SneekApiError };
|
|
15
|
+
export default SneekMessagesClient;
|
package/dist/messages/index.cjs
CHANGED
|
@@ -22,10 +22,38 @@ var messages_exports = {};
|
|
|
22
22
|
__export(messages_exports, {
|
|
23
23
|
SneekApiError: () => SneekApiError,
|
|
24
24
|
SneekClient: () => SneekClient,
|
|
25
|
-
|
|
25
|
+
SneekMessagesClient: () => SneekMessagesClient,
|
|
26
|
+
deriveKeys: () => deriveKeys,
|
|
27
|
+
isValidApiKey: () => isValidApiKey
|
|
26
28
|
});
|
|
27
29
|
module.exports = __toCommonJS(messages_exports);
|
|
28
30
|
|
|
31
|
+
// src/core/derive-keys.ts
|
|
32
|
+
var import_node_crypto = require("crypto");
|
|
33
|
+
var KEY_LABELS = {
|
|
34
|
+
qrSigningKey: "sneek-qr-v1",
|
|
35
|
+
webhookSigningKey: "sneek-webhook-v1",
|
|
36
|
+
proofKey: "sneek-proof-v1"
|
|
37
|
+
};
|
|
38
|
+
var API_KEY_PATTERN = /^sneek_[A-Za-z0-9_-]{43}$/;
|
|
39
|
+
function isValidApiKey(apiKey) {
|
|
40
|
+
return API_KEY_PATTERN.test(apiKey);
|
|
41
|
+
}
|
|
42
|
+
function deriveKeys(apiKey) {
|
|
43
|
+
if (typeof apiKey !== "string" || !isValidApiKey(apiKey)) {
|
|
44
|
+
throw new TypeError("apiKey must use the sneek_ format with a 43-character secret");
|
|
45
|
+
}
|
|
46
|
+
const inputKeyMaterial = Buffer.from(apiKey, "utf8");
|
|
47
|
+
const derive = (label) => Buffer.from(
|
|
48
|
+
(0, import_node_crypto.hkdfSync)("sha256", inputKeyMaterial, Buffer.alloc(0), label, 32)
|
|
49
|
+
);
|
|
50
|
+
return {
|
|
51
|
+
qrSigningKey: derive(KEY_LABELS.qrSigningKey),
|
|
52
|
+
webhookSigningKey: derive(KEY_LABELS.webhookSigningKey),
|
|
53
|
+
proofKey: derive(KEY_LABELS.proofKey)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
29
57
|
// src/core/client.ts
|
|
30
58
|
var SneekApiError = class extends Error {
|
|
31
59
|
constructor(status, type, title, detail) {
|
|
@@ -50,6 +78,7 @@ var SneekClient = class {
|
|
|
50
78
|
constructor(options) {
|
|
51
79
|
if (!options.apiUrl) throw new Error("apiUrl is required");
|
|
52
80
|
if (!options.apiKey) throw new Error("apiKey is required");
|
|
81
|
+
if (!isValidApiKey(options.apiKey)) throw new Error("apiKey must use the sneek_ format with a 43-character secret");
|
|
53
82
|
this.apiUrl = options.apiUrl.replace(/\/+$/, "");
|
|
54
83
|
this.apiKey = options.apiKey;
|
|
55
84
|
this.fetchImpl = options.fetch ?? fetch;
|
|
@@ -121,30 +150,28 @@ function asProblem(value) {
|
|
|
121
150
|
};
|
|
122
151
|
}
|
|
123
152
|
|
|
124
|
-
// src/
|
|
125
|
-
var
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
proofKey: "sneek-proof-v1"
|
|
130
|
-
};
|
|
131
|
-
function deriveKeys(apiKey) {
|
|
132
|
-
if (typeof apiKey !== "string" || apiKey.length === 0) {
|
|
133
|
-
throw new TypeError("apiKey must be a nonempty string");
|
|
153
|
+
// src/messages/client.ts
|
|
154
|
+
var SneekMessagesClient = class {
|
|
155
|
+
client;
|
|
156
|
+
constructor(options) {
|
|
157
|
+
this.client = new SneekClient(options);
|
|
134
158
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
159
|
+
send(input, options = {}) {
|
|
160
|
+
const headers = options.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : void 0;
|
|
161
|
+
return this.client.request("POST", "/api/messages/send", input, headers);
|
|
162
|
+
}
|
|
163
|
+
get(id) {
|
|
164
|
+
return this.client.request(
|
|
165
|
+
"GET",
|
|
166
|
+
`/api/messages/${encodeURIComponent(id)}`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
145
170
|
// Annotate the CommonJS export names for ESM import in node:
|
|
146
171
|
0 && (module.exports = {
|
|
147
172
|
SneekApiError,
|
|
148
173
|
SneekClient,
|
|
149
|
-
|
|
174
|
+
SneekMessagesClient,
|
|
175
|
+
deriveKeys,
|
|
176
|
+
isValidApiKey
|
|
150
177
|
});
|
package/dist/messages/index.d.ts
CHANGED
package/dist/messages/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,29 +120,27 @@ function asProblem(value) {
|
|
|
93
120
|
};
|
|
94
121
|
}
|
|
95
122
|
|
|
96
|
-
// src/
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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");
|
|
123
|
+
// src/messages/client.ts
|
|
124
|
+
var SneekMessagesClient = class {
|
|
125
|
+
client;
|
|
126
|
+
constructor(options) {
|
|
127
|
+
this.client = new SneekClient(options);
|
|
106
128
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
129
|
+
send(input, options = {}) {
|
|
130
|
+
const headers = options.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : void 0;
|
|
131
|
+
return this.client.request("POST", "/api/messages/send", input, headers);
|
|
132
|
+
}
|
|
133
|
+
get(id) {
|
|
134
|
+
return this.client.request(
|
|
135
|
+
"GET",
|
|
136
|
+
`/api/messages/${encodeURIComponent(id)}`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
117
140
|
export {
|
|
118
141
|
SneekApiError,
|
|
119
142
|
SneekClient,
|
|
120
|
-
|
|
143
|
+
SneekMessagesClient,
|
|
144
|
+
deriveKeys,
|
|
145
|
+
isValidApiKey
|
|
121
146
|
};
|
package/dist/pay/index.cjs
CHANGED
|
@@ -24,10 +24,37 @@ __export(pay_exports, {
|
|
|
24
24
|
SneekApiError: () => SneekApiError,
|
|
25
25
|
SneekClient: () => SneekClient,
|
|
26
26
|
SneekPayClient: () => SneekPayClient,
|
|
27
|
-
deriveKeys: () => deriveKeys
|
|
27
|
+
deriveKeys: () => deriveKeys,
|
|
28
|
+
isValidApiKey: () => isValidApiKey
|
|
28
29
|
});
|
|
29
30
|
module.exports = __toCommonJS(pay_exports);
|
|
30
31
|
|
|
32
|
+
// src/core/derive-keys.ts
|
|
33
|
+
var import_node_crypto = require("crypto");
|
|
34
|
+
var KEY_LABELS = {
|
|
35
|
+
qrSigningKey: "sneek-qr-v1",
|
|
36
|
+
webhookSigningKey: "sneek-webhook-v1",
|
|
37
|
+
proofKey: "sneek-proof-v1"
|
|
38
|
+
};
|
|
39
|
+
var API_KEY_PATTERN = /^sneek_[A-Za-z0-9_-]{43}$/;
|
|
40
|
+
function isValidApiKey(apiKey) {
|
|
41
|
+
return API_KEY_PATTERN.test(apiKey);
|
|
42
|
+
}
|
|
43
|
+
function deriveKeys(apiKey) {
|
|
44
|
+
if (typeof apiKey !== "string" || !isValidApiKey(apiKey)) {
|
|
45
|
+
throw new TypeError("apiKey must use the sneek_ format with a 43-character secret");
|
|
46
|
+
}
|
|
47
|
+
const inputKeyMaterial = Buffer.from(apiKey, "utf8");
|
|
48
|
+
const derive = (label) => Buffer.from(
|
|
49
|
+
(0, import_node_crypto.hkdfSync)("sha256", inputKeyMaterial, Buffer.alloc(0), label, 32)
|
|
50
|
+
);
|
|
51
|
+
return {
|
|
52
|
+
qrSigningKey: derive(KEY_LABELS.qrSigningKey),
|
|
53
|
+
webhookSigningKey: derive(KEY_LABELS.webhookSigningKey),
|
|
54
|
+
proofKey: derive(KEY_LABELS.proofKey)
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
31
58
|
// src/core/client.ts
|
|
32
59
|
var SneekApiError = class extends Error {
|
|
33
60
|
constructor(status, type, title, detail) {
|
|
@@ -52,6 +79,7 @@ var SneekClient = class {
|
|
|
52
79
|
constructor(options) {
|
|
53
80
|
if (!options.apiUrl) throw new Error("apiUrl is required");
|
|
54
81
|
if (!options.apiKey) throw new Error("apiKey is required");
|
|
82
|
+
if (!isValidApiKey(options.apiKey)) throw new Error("apiKey must use the sneek_ format with a 43-character secret");
|
|
55
83
|
this.apiUrl = options.apiUrl.replace(/\/+$/, "");
|
|
56
84
|
this.apiKey = options.apiKey;
|
|
57
85
|
this.fetchImpl = options.fetch ?? fetch;
|
|
@@ -123,28 +151,6 @@ function asProblem(value) {
|
|
|
123
151
|
};
|
|
124
152
|
}
|
|
125
153
|
|
|
126
|
-
// src/core/derive-keys.ts
|
|
127
|
-
var import_node_crypto = require("crypto");
|
|
128
|
-
var KEY_LABELS = {
|
|
129
|
-
qrSigningKey: "sneek-qr-v1",
|
|
130
|
-
webhookSigningKey: "sneek-webhook-v1",
|
|
131
|
-
proofKey: "sneek-proof-v1"
|
|
132
|
-
};
|
|
133
|
-
function deriveKeys(apiKey) {
|
|
134
|
-
if (typeof apiKey !== "string" || apiKey.length === 0) {
|
|
135
|
-
throw new TypeError("apiKey must be a nonempty string");
|
|
136
|
-
}
|
|
137
|
-
const inputKeyMaterial = Buffer.from(apiKey, "utf8");
|
|
138
|
-
const derive = (label) => Buffer.from(
|
|
139
|
-
(0, import_node_crypto.hkdfSync)("sha256", inputKeyMaterial, Buffer.alloc(0), label, 32)
|
|
140
|
-
);
|
|
141
|
-
return {
|
|
142
|
-
qrSigningKey: derive(KEY_LABELS.qrSigningKey),
|
|
143
|
-
webhookSigningKey: derive(KEY_LABELS.webhookSigningKey),
|
|
144
|
-
proofKey: derive(KEY_LABELS.proofKey)
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
154
|
// src/pay/payments.ts
|
|
149
155
|
var SneekPayClient = class {
|
|
150
156
|
client;
|
|
@@ -219,5 +225,6 @@ var OrdersClient = class {
|
|
|
219
225
|
SneekApiError,
|
|
220
226
|
SneekClient,
|
|
221
227
|
SneekPayClient,
|
|
222
|
-
deriveKeys
|
|
228
|
+
deriveKeys,
|
|
229
|
+
isValidApiKey
|
|
223
230
|
});
|
package/dist/pay/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,28 +120,6 @@ 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/pay/payments.ts
|
|
119
124
|
var SneekPayClient = class {
|
|
120
125
|
client;
|
|
@@ -188,5 +193,6 @@ export {
|
|
|
188
193
|
SneekApiError,
|
|
189
194
|
SneekClient,
|
|
190
195
|
SneekPayClient,
|
|
191
|
-
deriveKeys
|
|
196
|
+
deriveKeys,
|
|
197
|
+
isValidApiKey
|
|
192
198
|
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface SneekQrSession {
|
|
2
|
+
ref: string;
|
|
3
|
+
applicationId: string;
|
|
4
|
+
origin: string;
|
|
5
|
+
bindingProofHash: string;
|
|
6
|
+
expiresAt: number;
|
|
7
|
+
requestId?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SneekQrApprovalEvent {
|
|
10
|
+
type: 'qr.approved';
|
|
11
|
+
applicationId: string;
|
|
12
|
+
ref: string;
|
|
13
|
+
requestId: string;
|
|
14
|
+
}
|
|
15
|
+
export interface SneekApprovalStore {
|
|
16
|
+
createQrSession(session: SneekQrSession): Promise<void>;
|
|
17
|
+
/** Atomically attach only the first matching approval to a live QR session. */
|
|
18
|
+
recordQrApproval(event: SneekQrApprovalEvent, now: number): Promise<'recorded' | 'duplicate' | 'missing'>;
|
|
19
|
+
readQrSession(ref: string): Promise<SneekQrSession | null>;
|
|
20
|
+
/** Atomically claim a token jti until its exp time; false means replay. */
|
|
21
|
+
claimJti(jti: string, expiresAt: number): Promise<boolean>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const QrWebhookEventSchema: z.ZodObject<{
|
|
3
|
+
type: z.ZodLiteral<"qr.approved">;
|
|
4
|
+
applicationId: z.ZodString;
|
|
5
|
+
ref: z.ZodString;
|
|
6
|
+
requestId: z.ZodString;
|
|
7
|
+
}, "strict", z.ZodTypeAny, {
|
|
8
|
+
type: "qr.approved";
|
|
9
|
+
requestId: string;
|
|
10
|
+
ref: string;
|
|
11
|
+
applicationId: string;
|
|
12
|
+
}, {
|
|
13
|
+
type: "qr.approved";
|
|
14
|
+
requestId: string;
|
|
15
|
+
ref: string;
|
|
16
|
+
applicationId: string;
|
|
17
|
+
}>;
|
|
18
|
+
export declare const SneekVerifyActionSchema: z.ZodUnion<[z.ZodObject<{
|
|
19
|
+
action: z.ZodLiteral<"config">;
|
|
20
|
+
}, "strict", z.ZodTypeAny, {
|
|
21
|
+
action: "config";
|
|
22
|
+
}, {
|
|
23
|
+
action: "config";
|
|
24
|
+
}>, z.ZodObject<{
|
|
25
|
+
action: z.ZodLiteral<"request">;
|
|
26
|
+
identifier: z.ZodString;
|
|
27
|
+
}, "strict", z.ZodTypeAny, {
|
|
28
|
+
action: "request";
|
|
29
|
+
identifier: string;
|
|
30
|
+
}, {
|
|
31
|
+
action: "request";
|
|
32
|
+
identifier: string;
|
|
33
|
+
}>, z.ZodObject<{
|
|
34
|
+
action: z.ZodLiteral<"switch">;
|
|
35
|
+
requestId: z.ZodString;
|
|
36
|
+
channel: z.ZodEnum<["sms", "whatsapp", "email", "push"]>;
|
|
37
|
+
proof: z.ZodString;
|
|
38
|
+
}, "strict", z.ZodTypeAny, {
|
|
39
|
+
action: "switch";
|
|
40
|
+
requestId: string;
|
|
41
|
+
channel: "sms" | "whatsapp" | "email" | "push";
|
|
42
|
+
proof: string;
|
|
43
|
+
}, {
|
|
44
|
+
action: "switch";
|
|
45
|
+
requestId: string;
|
|
46
|
+
channel: "sms" | "whatsapp" | "email" | "push";
|
|
47
|
+
proof: string;
|
|
48
|
+
}>, z.ZodObject<{
|
|
49
|
+
action: z.ZodLiteral<"verify">;
|
|
50
|
+
requestId: z.ZodString;
|
|
51
|
+
code: z.ZodOptional<z.ZodString>;
|
|
52
|
+
proof: z.ZodString;
|
|
53
|
+
}, "strict", z.ZodTypeAny, {
|
|
54
|
+
action: "verify";
|
|
55
|
+
requestId: string;
|
|
56
|
+
proof: string;
|
|
57
|
+
code?: string | undefined;
|
|
58
|
+
}, {
|
|
59
|
+
action: "verify";
|
|
60
|
+
requestId: string;
|
|
61
|
+
proof: string;
|
|
62
|
+
code?: string | undefined;
|
|
63
|
+
}>, z.ZodObject<{
|
|
64
|
+
action: z.ZodLiteral<"verify">;
|
|
65
|
+
requestId: z.ZodString;
|
|
66
|
+
ref: z.ZodString;
|
|
67
|
+
bindingProof: z.ZodString;
|
|
68
|
+
}, "strict", z.ZodTypeAny, {
|
|
69
|
+
action: "verify";
|
|
70
|
+
requestId: string;
|
|
71
|
+
ref: string;
|
|
72
|
+
bindingProof: string;
|
|
73
|
+
}, {
|
|
74
|
+
action: "verify";
|
|
75
|
+
requestId: string;
|
|
76
|
+
ref: string;
|
|
77
|
+
bindingProof: string;
|
|
78
|
+
}>, z.ZodObject<{
|
|
79
|
+
action: z.ZodLiteral<"create">;
|
|
80
|
+
}, "strict", z.ZodTypeAny, {
|
|
81
|
+
action: "create";
|
|
82
|
+
}, {
|
|
83
|
+
action: "create";
|
|
84
|
+
}>, z.ZodObject<{
|
|
85
|
+
action: z.ZodLiteral<"status">;
|
|
86
|
+
ref: z.ZodString;
|
|
87
|
+
bindingProof: z.ZodString;
|
|
88
|
+
}, "strict", z.ZodTypeAny, {
|
|
89
|
+
action: "status";
|
|
90
|
+
ref: string;
|
|
91
|
+
bindingProof: string;
|
|
92
|
+
}, {
|
|
93
|
+
action: "status";
|
|
94
|
+
ref: string;
|
|
95
|
+
bindingProof: string;
|
|
96
|
+
}>]>;
|
|
97
|
+
export type SneekVerifyAction = z.infer<typeof SneekVerifyActionSchema>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { SneekApprovalStore } from './approval-store.js';
|
|
2
|
+
export interface SneekVerifyHandlerOptions {
|
|
3
|
+
apiUrl: string;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
allowedOrigins: string[];
|
|
6
|
+
approvalStore: SneekApprovalStore;
|
|
7
|
+
onVerified: (result: {
|
|
8
|
+
identifier: string;
|
|
9
|
+
kind: 'email' | 'mobile';
|
|
10
|
+
method: string;
|
|
11
|
+
verifiedAt: string;
|
|
12
|
+
requestId: string;
|
|
13
|
+
}) => void | Promise<void>;
|
|
14
|
+
fetch?: typeof fetch;
|
|
15
|
+
}
|
|
16
|
+
export declare class SneekVerifyHandler {
|
|
17
|
+
private readonly client;
|
|
18
|
+
private readonly allowedOrigins;
|
|
19
|
+
private readonly keys;
|
|
20
|
+
private readonly approvalStore;
|
|
21
|
+
private readonly onVerified;
|
|
22
|
+
private readonly jwksByIssuer;
|
|
23
|
+
constructor(options: SneekVerifyHandlerOptions);
|
|
24
|
+
handle(request: Request): Promise<Response>;
|
|
25
|
+
handleWebhook(request: Request): Promise<Response>;
|
|
26
|
+
private getAllowedOrigin;
|
|
27
|
+
private config;
|
|
28
|
+
private startVerification;
|
|
29
|
+
private switchVerification;
|
|
30
|
+
private verify;
|
|
31
|
+
private verifiedClaims;
|
|
32
|
+
private startResponse;
|
|
33
|
+
private createProof;
|
|
34
|
+
private isValidProof;
|
|
35
|
+
private createQr;
|
|
36
|
+
private qrStatus;
|
|
37
|
+
private qrSession;
|
|
38
|
+
private partnerConfig;
|
|
39
|
+
private isValidWebhookSignature;
|
|
40
|
+
private hash;
|
|
41
|
+
}
|