altcha-lib 0.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/LICENSE.txt +21 -0
- package/README.md +54 -0
- package/dist/helpers.d.ts +6 -0
- package/dist/helpers.js +31 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +28 -0
- package/dist/types.d.ts +20 -0
- package/dist/types.js +1 -0
- package/package.json +53 -0
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Daniel Regeci
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# ALTCHA JS Library
|
|
2
|
+
|
|
3
|
+
ALTCHA JS Library is a lightweight, zero-dependency library designed for creating and verifying [ALTCHA](https://altcha.org) challenges specifically tailored for Node.js, Bun, and Deno environments.
|
|
4
|
+
|
|
5
|
+
## Compatibility
|
|
6
|
+
|
|
7
|
+
This library utilizes [Web Crypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) and is intended for server-side use.
|
|
8
|
+
|
|
9
|
+
- Node.js 16+
|
|
10
|
+
- Bun 1+
|
|
11
|
+
- Deno 1+
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createChallenge, verifySolution } from 'altcha-lib';
|
|
17
|
+
|
|
18
|
+
const hmacKey = 'secret hmac key';
|
|
19
|
+
|
|
20
|
+
// Create a new challenge and send it to the client:
|
|
21
|
+
const challenge = await createChallenge({
|
|
22
|
+
hmacKey,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// When submitted, verify the payload:
|
|
26
|
+
const ok = await verifySolution(payload, hmacKey);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## API
|
|
30
|
+
|
|
31
|
+
### `createChallenge(options)`
|
|
32
|
+
|
|
33
|
+
Creates a new challenge for ALTCHA.
|
|
34
|
+
|
|
35
|
+
Parameters:
|
|
36
|
+
|
|
37
|
+
- `options: ChallengeOptions`:
|
|
38
|
+
- `algorithm?: string`: Algorithm to use (`SHA-1`, `SHA-256`, `SHA-512`, default: `SHA-256`).
|
|
39
|
+
- `hmacKey: string` (required): Signature HMAC key.
|
|
40
|
+
- `number?: number`: Optional number to use. If not provided, a random number will be generated.
|
|
41
|
+
- `salt?: string`: Optional salt string. If not provided, a random salt will be generated.
|
|
42
|
+
|
|
43
|
+
### `verifySolution(payload, hmacKey)`
|
|
44
|
+
|
|
45
|
+
Verifies an ALTCHA solution. The payload can be a Base64-encoded JSON payload (as submitted by the widget) or an object.
|
|
46
|
+
|
|
47
|
+
Parameters:
|
|
48
|
+
|
|
49
|
+
- `payload: string | Payload`
|
|
50
|
+
- `hmacKey: string`
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
MIT
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Algorithm } from './types.js';
|
|
2
|
+
export declare function ab2hex(ab: ArrayBuffer | Uint8Array): string;
|
|
3
|
+
export declare function hash(algorithm: Algorithm, str: string): Promise<string>;
|
|
4
|
+
export declare function hmac(algorithm: Algorithm, str: string, secret: string): Promise<string>;
|
|
5
|
+
export declare function randomBytes(length: number): Uint8Array;
|
|
6
|
+
export declare function randomInt(max: number): number;
|
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const encoder = new TextEncoder();
|
|
2
|
+
if (!('crypto' in globalThis)) {
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
globalThis.crypto = (await import('node:crypto')).webcrypto;
|
|
5
|
+
}
|
|
6
|
+
export function ab2hex(ab) {
|
|
7
|
+
return [...new Uint8Array(ab)]
|
|
8
|
+
.map((x) => x.toString(16).padStart(2, '0'))
|
|
9
|
+
.join('');
|
|
10
|
+
}
|
|
11
|
+
export async function hash(algorithm, str) {
|
|
12
|
+
return ab2hex(await crypto.subtle.digest(algorithm.toUpperCase(), encoder.encode(str)));
|
|
13
|
+
}
|
|
14
|
+
export async function hmac(algorithm, str, secret) {
|
|
15
|
+
const key = await crypto.subtle.importKey('raw', encoder.encode(secret), {
|
|
16
|
+
name: 'HMAC',
|
|
17
|
+
hash: algorithm,
|
|
18
|
+
}, false, ['sign', 'verify']);
|
|
19
|
+
return ab2hex(await crypto.subtle.sign('HMAC', key, encoder.encode(str)));
|
|
20
|
+
}
|
|
21
|
+
export function randomBytes(length) {
|
|
22
|
+
const ab = new Uint8Array(length);
|
|
23
|
+
crypto.getRandomValues(ab);
|
|
24
|
+
return ab;
|
|
25
|
+
}
|
|
26
|
+
export function randomInt(max) {
|
|
27
|
+
const ab = new Uint32Array(1);
|
|
28
|
+
crypto.getRandomValues(ab);
|
|
29
|
+
const randomNumber = ab[0] / (0xffffffff + 1);
|
|
30
|
+
return Math.floor(randomNumber * max + 1);
|
|
31
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ab2hex, hash, hmac, randomBytes, randomInt } from './helpers.js';
|
|
2
|
+
const DEFAULT_MAX_NUMBER = 1e7;
|
|
3
|
+
const DEFAULT_ALG = 'SHA-256';
|
|
4
|
+
export async function createChallenge(options) {
|
|
5
|
+
const algorithm = options.algorithm || DEFAULT_ALG;
|
|
6
|
+
const salt = options.salt || ab2hex(randomBytes(12));
|
|
7
|
+
const number = options.number === void 0 ? randomInt(DEFAULT_MAX_NUMBER) : options.number;
|
|
8
|
+
const challenge = await hash(algorithm, salt + number);
|
|
9
|
+
return {
|
|
10
|
+
algorithm,
|
|
11
|
+
challenge,
|
|
12
|
+
salt,
|
|
13
|
+
signature: await hmac(algorithm, challenge, options.hmacKey),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export async function verifySolution(payload, hmacKey) {
|
|
17
|
+
if (typeof payload === 'string') {
|
|
18
|
+
payload = JSON.parse(atob(payload));
|
|
19
|
+
}
|
|
20
|
+
const check = await createChallenge({
|
|
21
|
+
algorithm: payload.algorithm,
|
|
22
|
+
hmacKey,
|
|
23
|
+
number: payload.number,
|
|
24
|
+
salt: payload.salt,
|
|
25
|
+
});
|
|
26
|
+
return (check.challenge === payload.challenge &&
|
|
27
|
+
check.signature === payload.signature);
|
|
28
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type Algorithm = 'SHA-1' | 'SHA-256' | 'SHA-512';
|
|
2
|
+
export interface Challenge {
|
|
3
|
+
algorithm: Algorithm;
|
|
4
|
+
challenge: string;
|
|
5
|
+
salt: string;
|
|
6
|
+
signature: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ChallengeOptions {
|
|
9
|
+
algorithm?: Algorithm;
|
|
10
|
+
hmacKey: string;
|
|
11
|
+
number?: number;
|
|
12
|
+
salt?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface Payload {
|
|
15
|
+
algorithm: Algorithm;
|
|
16
|
+
challenge: string;
|
|
17
|
+
number: number;
|
|
18
|
+
salt: string;
|
|
19
|
+
signature: string;
|
|
20
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "altcha-lib",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A library for creating and verifying ALTCHA challenges for Node.js, Bun and Deno.",
|
|
5
|
+
"author": "Daniel Regeci",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"altcha",
|
|
9
|
+
"captcha",
|
|
10
|
+
"antispam",
|
|
11
|
+
"captcha alternative"
|
|
12
|
+
],
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"module": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "rimraf dist && tsc -p tsconfig.build.json",
|
|
19
|
+
"denoify": "rimraf deno_dist && denoify && find deno_dist/. -type f -exec sed -i '' -e 's/node:node:/node:/g' {} +",
|
|
20
|
+
"eslint": "eslint ./lib/**/*",
|
|
21
|
+
"format": "prettier --write './(lib|tests)/**/*'",
|
|
22
|
+
"test": "vitest",
|
|
23
|
+
"test:deno": "deno test tests/deno.ts"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"typesVersions": {
|
|
35
|
+
"*": {
|
|
36
|
+
"types": [
|
|
37
|
+
"./dist/types"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^20.9.0",
|
|
43
|
+
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
44
|
+
"denoify": "^1.6.9",
|
|
45
|
+
"eslint": "^8.56.0",
|
|
46
|
+
"prettier": "^3.2.5",
|
|
47
|
+
"rimraf": "^5.0.5",
|
|
48
|
+
"ts-node": "^10.9.1",
|
|
49
|
+
"tsx": "^4.0.0",
|
|
50
|
+
"typescript": "^5.2.2",
|
|
51
|
+
"vitest": "^1.0.1"
|
|
52
|
+
}
|
|
53
|
+
}
|