ribaunt 0.0.5

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.
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Ribaunt CAPTCHA Widget - Web Component
3
+ *
4
+ * Usage:
5
+ * ```html
6
+ * <ribaunt-widget
7
+ * challenge-endpoint="/api/challenge"
8
+ * verify-endpoint="/api/verify"
9
+ * show-warning="true"
10
+ * warning-message="Custom warning message"
11
+ * ></ribaunt-widget>
12
+ * ```
13
+ *
14
+ * Or in React/Vue/Svelte:
15
+ * ```jsx
16
+ * <ribaunt-widget
17
+ * challengeEndpoint="/api/challenge"
18
+ * verifyEndpoint="/api/verify"
19
+ * />
20
+ * ```
21
+ *
22
+ * Events:
23
+ * - verify: Fired when verification is complete
24
+ * - error: Fired when an error occurs
25
+ * - state-change: Fired when widget state changes
26
+ */
27
+ type WidgetState = 'initial' | 'verifying' | 'done' | 'error';
28
+ export declare class RibauntWidget extends HTMLElement {
29
+ private shadow;
30
+ private state;
31
+ private progress;
32
+ private captchaElement;
33
+ private checkboxElement;
34
+ private messageElement;
35
+ private warningElement;
36
+ static get observedAttributes(): string[];
37
+ constructor();
38
+ connectedCallback(): void;
39
+ disconnectedCallback(): void;
40
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
41
+ private render;
42
+ private attachEventListeners;
43
+ private removeEventListeners;
44
+ private handleClick;
45
+ private handleKeyPress;
46
+ private getMessage;
47
+ private setState;
48
+ private setProgress;
49
+ private verify;
50
+ /**
51
+ * Public API: Reset the widget to initial state
52
+ */
53
+ reset(): void;
54
+ /**
55
+ * Public API: Get current state
56
+ */
57
+ getState(): WidgetState;
58
+ /**
59
+ * Public API: Programmatically trigger verification
60
+ */
61
+ startVerification(): void;
62
+ }
63
+ export default RibauntWidget;
64
+ //# sourceMappingURL=widget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.d.ts","sourceRoot":"","sources":["../../src/widget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AA+KH,KAAK,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,MAAM,GAAG,OAAO,CAAC;AAE9D,qBAAa,aAAc,SAAQ,WAAW;IAC5C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,cAAc,CAAqC;IAC3D,OAAO,CAAC,cAAc,CAA+B;IAErD,MAAM,KAAK,kBAAkB,aAQ5B;;IASD,iBAAiB;IAKjB,oBAAoB;IAIpB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAMvF,OAAO,CAAC,MAAM;IA+Bd,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,QAAQ;IAoBhB,OAAO,CAAC,WAAW;YAYL,MAAM;IAgEpB;;OAEG;IACH,KAAK;IAKL;;OAEG;IACH,QAAQ,IAAI,WAAW;IAIvB;;OAEG;IACH,iBAAiB;CAKlB;AAQD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,402 @@
1
+ "use strict";
2
+ /**
3
+ * Ribaunt CAPTCHA Widget - Web Component
4
+ *
5
+ * Usage:
6
+ * ```html
7
+ * <ribaunt-widget
8
+ * challenge-endpoint="/api/challenge"
9
+ * verify-endpoint="/api/verify"
10
+ * show-warning="true"
11
+ * warning-message="Custom warning message"
12
+ * ></ribaunt-widget>
13
+ * ```
14
+ *
15
+ * Or in React/Vue/Svelte:
16
+ * ```jsx
17
+ * <ribaunt-widget
18
+ * challengeEndpoint="/api/challenge"
19
+ * verifyEndpoint="/api/verify"
20
+ * />
21
+ * ```
22
+ *
23
+ * Events:
24
+ * - verify: Fired when verification is complete
25
+ * - error: Fired when an error occurs
26
+ * - state-change: Fired when widget state changes
27
+ */
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.RibauntWidget = void 0;
30
+ const solver_js_1 = require("./solver.js");
31
+ const WIDGET_STYLES = `
32
+ /* Widget Container Styles */
33
+ :host,
34
+ :host * {
35
+ box-sizing: border-box;
36
+ }
37
+
38
+ .captcha {
39
+ background-color: var(--ribaunt-background, #fdfdfd);
40
+ border: 1px solid var(--ribaunt-border-color, #dddddd8f);
41
+ border-radius: var(--ribaunt-border-radius, 14px);
42
+ user-select: none;
43
+ height: var(--ribaunt-widget-height, 58px);
44
+ width: var(--ribaunt-widget-width, 230px);
45
+ display: flex;
46
+ align-items: center;
47
+ padding: var(--ribaunt-widget-padding, 14px);
48
+ gap: var(--ribaunt-gap, 15px);
49
+ cursor: pointer;
50
+ transition: filter .2s, transform .2s;
51
+ position: relative;
52
+ -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
53
+ overflow: hidden;
54
+ color: var(--ribaunt-color, #212121);
55
+ }
56
+
57
+ .captcha:hover {
58
+ filter: brightness(98%);
59
+ }
60
+
61
+ /* Checkbox Styles */
62
+ .checkbox {
63
+ width: var(--ribaunt-checkbox-size, 25px);
64
+ height: var(--ribaunt-checkbox-size, 25px);
65
+ border: var(--ribaunt-checkbox-border, 1px solid #aaaaaad1);
66
+ border-radius: var(--ribaunt-checkbox-border-radius, 6px);
67
+ background-color: var(--ribaunt-checkbox-background, #fafafa91);
68
+ transition: opacity .2s;
69
+ margin-top: var(--ribaunt-checkbox-margin, 2px);
70
+ margin-bottom: var(--ribaunt-checkbox-margin, 2px);
71
+ flex-shrink: 0;
72
+ }
73
+
74
+ /* Font Family */
75
+ .captcha * {
76
+ font-family: var(--ribaunt-font, system, -apple-system, "BlinkMacSystemFont", ".SFNSText-Regular", "San Francisco", "Roboto", "Segoe UI", "Helvetica Neue", "Lucida Grande", "Ubuntu", "arial", sans-serif);
77
+ }
78
+
79
+ /* Label Text */
80
+ .captcha p {
81
+ margin: 0;
82
+ font-weight: 500;
83
+ font-size: 15px;
84
+ user-select: none;
85
+ transition: opacity .2s;
86
+ }
87
+
88
+ /* Verifying State */
89
+ .captcha[data-state=verifying] .checkbox {
90
+ background: none;
91
+ display: flex;
92
+ align-items: center;
93
+ justify-content: center;
94
+ transform: scale(1.1);
95
+ border: none;
96
+ border-radius: 50%;
97
+ background: conic-gradient(
98
+ var(--ribaunt-spinner-color, #000) 0%,
99
+ var(--ribaunt-spinner-color, #000) var(--progress, 0%),
100
+ var(--ribaunt-spinner-background-color, #eee) var(--progress, 0%),
101
+ var(--ribaunt-spinner-background-color, #eee) 100%
102
+ );
103
+ position: relative;
104
+ transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
105
+ }
106
+
107
+ .captcha[data-state=verifying] .checkbox::after {
108
+ content: "";
109
+ background-color: var(--ribaunt-background, #fdfdfd);
110
+ width: calc(100% - var(--ribaunt-spinner-thickness, 5px));
111
+ height: calc(100% - var(--ribaunt-spinner-thickness, 5px));
112
+ border-radius: 50%;
113
+ margin: calc(var(--ribaunt-spinner-thickness, 5px) / 2);
114
+ transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
115
+ }
116
+
117
+ /* Done/Success State */
118
+ .captcha[data-state=done] .checkbox {
119
+ border: 1px solid transparent;
120
+ background-image: var(--ribaunt-checkmark, url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cstyle%3E%40keyframes%20anim%7B0%25%7Bstroke-dashoffset%3A23.21320343017578px%7Dto%7Bstroke-dashoffset%3A0%7D%7D%3C%2Fstyle%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%2300a67d%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m5%2012%205%205L20%207%22%20style%3D%22stroke-dashoffset%3A0%3Bstroke-dasharray%3A23.21320343017578px%3Banimation%3Aanim%20.5s%20ease%22%2F%3E%3C%2Fsvg%3E"));
121
+ background-size: cover;
122
+ }
123
+
124
+ /* Error State */
125
+ .captcha[data-state=error] .checkbox {
126
+ border: 1px solid transparent;
127
+ background-image: var(--ribaunt-error-cross, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='96' viewBox='0 0 24 24'%3E%3Cpath fill='%23f55b50' d='M11 15h2v2h-2zm0-8h2v6h-2zm1-5C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m0 18a8 8 0 0 1-8-8a8 8 0 0 1 8-8a8 8 0 0 1 8 8a8 8 0 0 1-8 8'/%3E%3C/svg%3E"));
128
+ background-size: cover;
129
+ }
130
+
131
+ /* Disabled State */
132
+ .captcha[disabled] {
133
+ cursor: not-allowed;
134
+ }
135
+
136
+ .captcha[disabled][data-state=verifying] {
137
+ cursor: progress;
138
+ }
139
+
140
+ .captcha[disabled][data-state=done] {
141
+ cursor: default;
142
+ }
143
+
144
+ /* Logo */
145
+ .logo {
146
+ position: absolute;
147
+ bottom: 8px;
148
+ right: 8px;
149
+ width: 20px;
150
+ height: auto;
151
+ opacity: 0.6;
152
+ transition: opacity 0.2s, color 0.2s;
153
+ color: var(--ribaunt-logo-color, #666);
154
+ pointer-events: auto;
155
+ }
156
+
157
+ .logo svg {
158
+ width: 100%;
159
+ height: auto;
160
+ display: block;
161
+ }
162
+
163
+ /* Logo color adapts to theme */
164
+ @media (prefers-color-scheme: dark) {
165
+ .logo {
166
+ color: var(--ribaunt-logo-color, #999);
167
+ }
168
+ }
169
+
170
+ /* Warning Message */
171
+ .warning {
172
+ width: var(--ribaunt-widget-width, 230px);
173
+ background: rgb(237, 56, 46);
174
+ color: white;
175
+ padding: 4px 6px;
176
+ padding-bottom: calc(var(--ribaunt-border-radius, 14px) + 5px);
177
+ font-size: 10px;
178
+ box-sizing: border-box;
179
+ font-family: system-ui;
180
+ border-top-left-radius: 8px;
181
+ border-top-right-radius: 8px;
182
+ text-align: center;
183
+ user-select: none;
184
+ margin-bottom: -35.5px;
185
+ opacity: 0;
186
+ transition: margin-bottom .3s, opacity .3s;
187
+ }
188
+
189
+ .warning.visible {
190
+ margin-bottom: calc(-1 * var(--ribaunt-border-radius, 14px));
191
+ opacity: 1;
192
+ }
193
+ `;
194
+ const RIBAUNT_LOGO = `
195
+ <svg width="500" height="384" viewBox="0 0 500 384" fill="none" xmlns="http://www.w3.org/2000/svg">
196
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M368.357 122.188H236.618L187.59 0H450.972L500 122.188L434.14 286.295H302.478L368.357 122.188Z" fill="currentColor"/>
197
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M236.619 122.188L341.575 383.702H209.913L170.796 286.199L131.663 383.702H0L104.957 122.188H236.619Z" fill="currentColor"/>
198
+ </svg>
199
+ `;
200
+ class RibauntWidget extends HTMLElement {
201
+ static get observedAttributes() {
202
+ return [
203
+ 'challenge-endpoint',
204
+ 'verify-endpoint',
205
+ 'show-warning',
206
+ 'warning-message',
207
+ 'disabled',
208
+ ];
209
+ }
210
+ constructor() {
211
+ super();
212
+ this.state = 'initial';
213
+ this.progress = 0;
214
+ this.captchaElement = null;
215
+ this.checkboxElement = null;
216
+ this.messageElement = null;
217
+ this.warningElement = null;
218
+ this.shadow = this.attachShadow({ mode: 'open' });
219
+ this.handleClick = this.handleClick.bind(this);
220
+ this.handleKeyPress = this.handleKeyPress.bind(this);
221
+ }
222
+ connectedCallback() {
223
+ this.render();
224
+ this.attachEventListeners();
225
+ }
226
+ disconnectedCallback() {
227
+ this.removeEventListeners();
228
+ }
229
+ attributeChangedCallback(name, oldValue, newValue) {
230
+ if (oldValue !== newValue) {
231
+ this.render();
232
+ }
233
+ }
234
+ render() {
235
+ const showWarning = this.hasAttribute('show-warning') && this.getAttribute('show-warning') !== 'false';
236
+ const warningMessage = this.getAttribute('warning-message') || 'Enable WASM for significantly faster solving';
237
+ this.shadow.innerHTML = `
238
+ <style>${WIDGET_STYLES}</style>
239
+ <div>
240
+ ${showWarning ? `<div class="warning ${showWarning ? 'visible' : ''}">${warningMessage}</div>` : ''}
241
+ <div class="captcha" data-state="${this.state}" role="button" tabindex="0" aria-label="${this.getMessage()}">
242
+ <div class="checkbox"></div>
243
+ <p>${this.getMessage()}</p>
244
+ <a class="logo" href="https://ribaunt.com" target="_blank" rel="noopener noreferrer" aria-label="Powered by Ribaunt">
245
+ ${RIBAUNT_LOGO}
246
+ </a>
247
+ </div>
248
+ </div>
249
+ `;
250
+ this.captchaElement = this.shadow.querySelector('.captcha');
251
+ this.checkboxElement = this.shadow.querySelector('.checkbox');
252
+ this.messageElement = this.shadow.querySelector('p');
253
+ this.warningElement = this.shadow.querySelector('.warning');
254
+ // Update progress CSS variable if verifying
255
+ if (this.state === 'verifying' && this.captchaElement) {
256
+ this.captchaElement.style.setProperty('--progress', `${this.progress}%`);
257
+ }
258
+ this.attachEventListeners();
259
+ }
260
+ attachEventListeners() {
261
+ if (this.captchaElement) {
262
+ this.captchaElement.addEventListener('click', this.handleClick);
263
+ this.captchaElement.addEventListener('keypress', this.handleKeyPress);
264
+ }
265
+ }
266
+ removeEventListeners() {
267
+ if (this.captchaElement) {
268
+ this.captchaElement.removeEventListener('click', this.handleClick);
269
+ this.captchaElement.removeEventListener('keypress', this.handleKeyPress);
270
+ }
271
+ }
272
+ handleClick() {
273
+ if (this.state !== 'initial' && this.state !== 'error')
274
+ return;
275
+ this.verify();
276
+ }
277
+ handleKeyPress(e) {
278
+ if (e.key === 'Enter' || e.key === ' ') {
279
+ e.preventDefault();
280
+ this.handleClick();
281
+ }
282
+ }
283
+ getMessage() {
284
+ switch (this.state) {
285
+ case 'initial':
286
+ return "I'm a human";
287
+ case 'verifying':
288
+ return `Verifying... ${this.progress}%`;
289
+ case 'done':
290
+ return "You're a human";
291
+ case 'error':
292
+ return 'Error. Try again.';
293
+ }
294
+ }
295
+ setState(newState) {
296
+ this.state = newState;
297
+ if (this.captchaElement) {
298
+ this.captchaElement.setAttribute('data-state', this.state);
299
+ this.captchaElement.setAttribute('aria-label', this.getMessage());
300
+ }
301
+ if (this.messageElement) {
302
+ this.messageElement.textContent = this.getMessage();
303
+ }
304
+ // Dispatch state change event
305
+ this.dispatchEvent(new CustomEvent('state-change', {
306
+ detail: { state: this.state },
307
+ bubbles: true,
308
+ composed: true,
309
+ }));
310
+ }
311
+ setProgress(value) {
312
+ // Smooth the progress value for better animation
313
+ const smoothedProgress = Math.round(value * 10) / 10;
314
+ this.progress = smoothedProgress;
315
+ if (this.messageElement) {
316
+ this.messageElement.textContent = this.getMessage();
317
+ }
318
+ if (this.captchaElement) {
319
+ this.captchaElement.style.setProperty('--progress', `${smoothedProgress}%`);
320
+ }
321
+ }
322
+ async verify() {
323
+ this.setState('verifying');
324
+ this.setProgress(0);
325
+ try {
326
+ const challengeEndpoint = this.getAttribute('challenge-endpoint');
327
+ const verifyEndpoint = this.getAttribute('verify-endpoint');
328
+ // Get challenge tokens
329
+ let tokens = [];
330
+ if (challengeEndpoint) {
331
+ const response = await fetch(challengeEndpoint);
332
+ if (!response.ok)
333
+ throw new Error('Failed to fetch challenge');
334
+ const data = await response.json();
335
+ tokens = data.challenges || data.tokens || data;
336
+ }
337
+ if (!tokens || !Array.isArray(tokens) || tokens.length === 0) {
338
+ throw new Error('No challenge tokens available');
339
+ }
340
+ // Solve challenges using the browser-compatible solver
341
+ const solutions = await (0, solver_js_1.solveChallenge)(tokens, (progress) => {
342
+ this.setProgress(progress);
343
+ });
344
+ // Verify solution
345
+ if (verifyEndpoint) {
346
+ const response = await fetch(verifyEndpoint, {
347
+ method: 'POST',
348
+ headers: { 'Content-Type': 'application/json' },
349
+ body: JSON.stringify({ tokens, solutions }),
350
+ });
351
+ if (!response.ok) {
352
+ throw new Error('Verification failed');
353
+ }
354
+ }
355
+ this.setState('done');
356
+ // Dispatch verify event
357
+ this.dispatchEvent(new CustomEvent('verify', {
358
+ detail: { solutions },
359
+ bubbles: true,
360
+ composed: true,
361
+ }));
362
+ }
363
+ catch (error) {
364
+ this.setState('error');
365
+ // Dispatch error event
366
+ this.dispatchEvent(new CustomEvent('error', {
367
+ detail: { error: error instanceof Error ? error.message : String(error) },
368
+ bubbles: true,
369
+ composed: true,
370
+ }));
371
+ }
372
+ }
373
+ /**
374
+ * Public API: Reset the widget to initial state
375
+ */
376
+ reset() {
377
+ this.setState('initial');
378
+ this.setProgress(0);
379
+ }
380
+ /**
381
+ * Public API: Get current state
382
+ */
383
+ getState() {
384
+ return this.state;
385
+ }
386
+ /**
387
+ * Public API: Programmatically trigger verification
388
+ */
389
+ startVerification() {
390
+ if (this.state === 'initial' || this.state === 'error') {
391
+ this.verify();
392
+ }
393
+ }
394
+ }
395
+ exports.RibauntWidget = RibauntWidget;
396
+ // Register the custom element
397
+ if (typeof window !== 'undefined' && !customElements.get('ribaunt-widget')) {
398
+ customElements.define('ribaunt-widget', RibauntWidget);
399
+ }
400
+ // Export for use in TypeScript
401
+ exports.default = RibauntWidget;
402
+ //# sourceMappingURL=widget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.js","sourceRoot":"","sources":["../../src/widget.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ export type ChallengeToken = string;
2
+ export interface ChallengeSolution {
3
+ nonce: string;
4
+ hash: string;
5
+ }
6
+ /**
7
+ * Creates one or more PoW challenges and returns them as signed JWT tokens.
8
+ *
9
+ * @param difficulty - Number of leading zeros required in the hash (default 6)
10
+ * @param amount - Number of challenges to create (default 4)
11
+ * @param ttlSeconds - Time to live for each challenge in seconds (default 30)
12
+ * @returns An array of JWT challenge tokens
13
+ */
14
+ export declare function createChallenge(difficulty?: number, amount?: number, ttlSeconds?: number): ChallengeToken[];
15
+ /**
16
+ * Solves one or more PoW challenges encoded in JWT tokens.
17
+ *
18
+ * @param token - The JWT challenge token or an array of tokens
19
+ * @returns The nonce/hash pair for single input or an array of them for multiple tokens
20
+ */
21
+ export declare function solveChallenge(token: ChallengeToken): ChallengeSolution | undefined;
22
+ export declare function solveChallenge(token: ChallengeToken[]): ChallengeSolution[] | undefined;
23
+ /**
24
+ * Verifies a PoW solution returned by the client.
25
+ *
26
+ * @param token - The original JWT issued as the challenge (single token or array of tokens)
27
+ * @param nonce - The nonce/answer submitted by the client (single nonce, array of nonces, or array of solution objects)
28
+ * @returns true only if every provided solution is valid; otherwise false
29
+ */
30
+ export declare function verifySolution(token: ChallengeToken | ChallengeToken[], nonce: number | string | Array<number | string> | ChallengeSolution | ChallengeSolution[]): boolean;
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAqCD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,UAAU,GAAE,MAAU,EACtB,MAAM,GAAE,MAAU,EAClB,UAAU,GAAE,MAAW,GACtB,cAAc,EAAE,CAKlB;AAoDD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,iBAAiB,GAAG,SAAS,CAAC;AACrF,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,iBAAiB,EAAE,GAAG,SAAS,CAAC;AAuBzF;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,cAAc,GAAG,cAAc,EAAE,EACxC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,iBAAiB,GAAG,iBAAiB,EAAE,GACxF,OAAO,CAqDT"}
package/dist/index.js ADDED
@@ -0,0 +1,167 @@
1
+ import crypto from 'crypto';
2
+ import jwt from 'jsonwebtoken';
3
+ const secret = process.env.RIBAUNT_SECRET;
4
+ if (!secret) {
5
+ throw new Error('RIBAUNT_SECRET environment variable is not set!');
6
+ }
7
+ function generateChallenge(length = 8) {
8
+ const buffer = crypto.randomBytes(length);
9
+ return buffer.toString('base64').slice(0, length);
10
+ }
11
+ function createChallengePayload(difficulty, ttlSeconds) {
12
+ return {
13
+ challenge: generateChallenge(),
14
+ difficulty,
15
+ expires: Math.floor(Date.now() / 1000) + ttlSeconds,
16
+ };
17
+ }
18
+ function signChallenge(payload) {
19
+ return jwt.sign(payload, secret);
20
+ }
21
+ function assertValidAmount(amount) {
22
+ if (!Number.isFinite(amount)) {
23
+ throw new Error('Challenge amount must be a finite number');
24
+ }
25
+ const normalized = Math.floor(amount);
26
+ if (normalized < 1) {
27
+ throw new Error('Challenge amount must be at least 1');
28
+ }
29
+ return normalized;
30
+ }
31
+ function createSingleChallenge(difficulty, ttlSeconds) {
32
+ const payload = createChallengePayload(difficulty, ttlSeconds);
33
+ return signChallenge(payload);
34
+ }
35
+ /**
36
+ * Creates one or more PoW challenges and returns them as signed JWT tokens.
37
+ *
38
+ * @param difficulty - Number of leading zeros required in the hash (default 6)
39
+ * @param amount - Number of challenges to create (default 4)
40
+ * @param ttlSeconds - Time to live for each challenge in seconds (default 30)
41
+ * @returns An array of JWT challenge tokens
42
+ */
43
+ export function createChallenge(difficulty = 5, amount = 4, ttlSeconds = 30) {
44
+ const normalizedAmount = assertValidAmount(amount);
45
+ const challenges = Array.from({ length: normalizedAmount }, () => createSingleChallenge(difficulty, ttlSeconds));
46
+ return challenges;
47
+ }
48
+ function solveSingleChallenge(token) {
49
+ try {
50
+ const payload = jwt.decode(token);
51
+ if (!payload)
52
+ return undefined;
53
+ const { challenge, difficulty } = payload;
54
+ const prefix = '0'.repeat(difficulty);
55
+ let nonce = 0;
56
+ while (true) {
57
+ const hash = crypto
58
+ .createHash('sha256')
59
+ .update(`${challenge}${nonce}`)
60
+ .digest('hex');
61
+ if (hash.startsWith(prefix)) {
62
+ return { nonce: String(nonce), hash };
63
+ }
64
+ nonce++;
65
+ }
66
+ }
67
+ catch (err) {
68
+ return undefined;
69
+ }
70
+ }
71
+ function verifySingleSolution(token, nonce) {
72
+ if (nonce === undefined || nonce === null) {
73
+ return false;
74
+ }
75
+ try {
76
+ const payload = jwt.verify(token, secret);
77
+ const now = Math.floor(Date.now() / 1000);
78
+ if (payload.expires < now)
79
+ return false;
80
+ const nonceValue = typeof nonce === 'number' ? String(nonce) : nonce;
81
+ const hash = crypto
82
+ .createHash('sha256')
83
+ .update(`${payload.challenge}${nonceValue}`)
84
+ .digest('hex');
85
+ const prefix = '0'.repeat(payload.difficulty);
86
+ return hash.startsWith(prefix);
87
+ }
88
+ catch (err) {
89
+ return false;
90
+ }
91
+ }
92
+ export function solveChallenge(token) {
93
+ if (Array.isArray(token)) {
94
+ const solutions = [];
95
+ for (const singleToken of token) {
96
+ const solution = solveSingleChallenge(singleToken);
97
+ if (!solution) {
98
+ return undefined;
99
+ }
100
+ solutions.push(solution);
101
+ }
102
+ return solutions;
103
+ }
104
+ const solution = solveSingleChallenge(token);
105
+ return solution;
106
+ }
107
+ /**
108
+ * Verifies a PoW solution returned by the client.
109
+ *
110
+ * @param token - The original JWT issued as the challenge (single token or array of tokens)
111
+ * @param nonce - The nonce/answer submitted by the client (single nonce, array of nonces, or array of solution objects)
112
+ * @returns true only if every provided solution is valid; otherwise false
113
+ */
114
+ export function verifySolution(token, nonce) {
115
+ if (Array.isArray(token)) {
116
+ let nonces;
117
+ if (Array.isArray(nonce)) {
118
+ if (nonce.length !== token.length) {
119
+ return false;
120
+ }
121
+ if (nonce.length > 0 && typeof nonce[0] === 'object' && 'nonce' in nonce[0]) {
122
+ nonces = nonce.map(s => s.nonce);
123
+ }
124
+ else {
125
+ nonces = nonce;
126
+ }
127
+ }
128
+ else {
129
+ return false;
130
+ }
131
+ for (let index = 0; index < token.length; index++) {
132
+ const challengeToken = token[index];
133
+ const nonceEntry = nonces[index];
134
+ if (challengeToken === undefined || nonceEntry === undefined) {
135
+ return false;
136
+ }
137
+ if (!verifySingleSolution(challengeToken, nonceEntry)) {
138
+ return false;
139
+ }
140
+ }
141
+ return true;
142
+ }
143
+ let effectiveNonce;
144
+ if (Array.isArray(nonce)) {
145
+ if (nonce.length === 0) {
146
+ return false;
147
+ }
148
+ if (typeof nonce[0] === 'object' && 'nonce' in nonce[0]) {
149
+ effectiveNonce = nonce[0].nonce;
150
+ }
151
+ else {
152
+ effectiveNonce = nonce[0];
153
+ }
154
+ }
155
+ else if (typeof nonce === 'object' && 'nonce' in nonce) {
156
+ effectiveNonce = nonce.nonce;
157
+ }
158
+ else {
159
+ effectiveNonce = nonce;
160
+ }
161
+ if (effectiveNonce === undefined) {
162
+ return false;
163
+ }
164
+ const result = verifySingleSolution(token, effectiveNonce);
165
+ return result;
166
+ }
167
+ //# sourceMappingURL=index.js.map