altcha-lib 2.2.0 → 2.3.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.
Files changed (40) hide show
  1. package/README.md +28 -0
  2. package/dist/cjs/v2/frameworks/express.js +6 -3
  3. package/dist/cjs/v2/frameworks/fastify.js +6 -3
  4. package/dist/cjs/v2/frameworks/h3.d.ts +1 -1
  5. package/dist/cjs/v2/frameworks/h3.js +9 -3
  6. package/dist/cjs/v2/frameworks/hono.d.ts +18 -0
  7. package/dist/cjs/v2/frameworks/hono.js +8 -3
  8. package/dist/cjs/v2/frameworks/nestjs.d.ts +5 -4
  9. package/dist/cjs/v2/frameworks/nestjs.js +8 -3
  10. package/dist/cjs/v2/frameworks/nextjs.js +6 -3
  11. package/dist/cjs/v2/frameworks/shared.d.ts +4 -4
  12. package/dist/cjs/v2/frameworks/shared.js +22 -3
  13. package/dist/cjs/v2/frameworks/sveltekit.js +7 -4
  14. package/dist/cjs/v2/frameworks/types.d.ts +17 -5
  15. package/dist/cjs/v2/index.d.ts +3 -1
  16. package/dist/cjs/v2/index.js +4 -1
  17. package/dist/cjs/v2/types.d.ts +54 -0
  18. package/dist/cjs/v2/verify-server.d.ts +3 -0
  19. package/dist/cjs/v2/verify-server.js +57 -0
  20. package/dist/esm/v2/frameworks/express.js +6 -3
  21. package/dist/esm/v2/frameworks/fastify.js +6 -3
  22. package/dist/esm/v2/frameworks/h3.d.ts +1 -1
  23. package/dist/esm/v2/frameworks/h3.js +9 -3
  24. package/dist/esm/v2/frameworks/hono.d.ts +18 -0
  25. package/dist/esm/v2/frameworks/hono.js +8 -3
  26. package/dist/esm/v2/frameworks/nestjs.d.ts +5 -4
  27. package/dist/esm/v2/frameworks/nestjs.js +9 -3
  28. package/dist/esm/v2/frameworks/nextjs.js +6 -3
  29. package/dist/esm/v2/frameworks/shared.d.ts +4 -4
  30. package/dist/esm/v2/frameworks/shared.js +22 -3
  31. package/dist/esm/v2/frameworks/sveltekit.js +7 -4
  32. package/dist/esm/v2/frameworks/types.d.ts +17 -5
  33. package/dist/esm/v2/index.d.ts +3 -1
  34. package/dist/esm/v2/index.js +3 -1
  35. package/dist/esm/v2/types.d.ts +54 -0
  36. package/dist/esm/v2/verify-server.d.ts +3 -0
  37. package/dist/esm/v2/verify-server.js +54 -0
  38. package/dist/tsconfig.build.tsbuildinfo +1 -1
  39. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  40. package/package.json +4 -2
package/README.md CHANGED
@@ -55,6 +55,8 @@ The library includes plugins for several popular frameworks to simplify integrat
55
55
 
56
56
  If your framework is not listed, see the [Advanced Usage](/docs/advanced-usage.md) guide for custom integrations.
57
57
 
58
+ All plugins also support remote verification via ALTCHA Sentinel (`verifyServer` option) — see [`/docs/server-signatures.md`](/docs/server-signatures.md#remote-verification-sentinel-api).
59
+
58
60
  ## Documentation
59
61
 
60
62
  - Advanced Usage: [`/docs/advanced-usage.md`](/docs/advanced-usage.md)
@@ -217,6 +219,32 @@ Returns `VerifyServerSignatureResult` (extends `VerifySolutionResult`):
217
219
  | `verified` | `boolean` | Whether the signature is valid. |
218
220
  | `verificationData` | `ServerSignatureVerificationData \| null` | Parsed verification data. |
219
221
 
222
+ #### `verifyServer(options: VerifyServerOptions): Promise<VerifyServerResult>`
223
+
224
+ Verifies a payload remotely by calling ALTCHA Sentinel's `POST /v1/verify/signature` API, instead of verifying the HMAC signature locally.
225
+
226
+ | Option | Type | Description |
227
+ |---|---|---|
228
+ | `payload` | `string \| ServerSignaturePayload \| Record<string, unknown>` | The payload to verify, as received from `POST /v1/verify`. |
229
+ | `url` | `string` | Full URL of the Sentinel `/v1/verify/signature` endpoint. |
230
+ | `secret` | `string?` | API key secret. If provided, Sentinel checks that it matches the API key associated with the payload. |
231
+ | `fetch` | `typeof fetch?` | Custom fetch implementation. Defaults to the global `fetch`. |
232
+ | `headers` | `Record<string, string>?` | Additional headers to send with the request. |
233
+ | `controller` | `AbortController?` | For cancelling the verification request. |
234
+ | `timeout` | `number?` | Per-attempt request timeout in milliseconds. Defaults to `10000`. |
235
+ | `retries` | `number?` | Number of retry attempts after the first try. Defaults to `0`. |
236
+ | `retryDelay` | `number?` | Base delay in milliseconds between retries. Defaults to `300`. |
237
+ | `retryBackoff` | `'fixed' \| 'exponential'?` | Backoff strategy for `retryDelay`. Defaults to `'exponential'`. |
238
+
239
+ Returns `VerifyServerResult`:
240
+
241
+ | Field | Type | Description |
242
+ |---|---|---|
243
+ | `verified` | `boolean` | Whether the payload was successfully verified. |
244
+ | `apiKey` | `string \| null?` | API key associated with the verification. |
245
+ | `reason` | `string?` | Reason or error message if verification failed. |
246
+ | `verificationData` | `ServerSignatureVerificationData \| null?` | Verification data returned by Sentinel. |
247
+
220
248
  #### `obfuscate(str: string, options?): Promise<string>`
221
249
 
222
250
  > Import from `altcha-lib/obfuscation`
@@ -13,8 +13,11 @@ const asyncHandler = (fn) => (req, res, next) => {
13
13
  fn(req, res, next).catch(next);
14
14
  };
15
15
  function create(options) {
16
- const { createChallengeParameters, deriveKey, fieldName = 'altcha', hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, } = options;
16
+ const { createChallengeParameters, deriveKey, fieldName = 'altcha', hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, verifyServer: verifyServerOptions, } = options;
17
17
  const challengeHandler = asyncHandler(async (req, res) => {
18
+ if (!deriveKey || !createChallengeParameters) {
19
+ throw new Error('deriveKey and createChallengeParameters are required to generate challenges. Omit challengeHandler when relying on Sentinel to issue challenges.');
20
+ }
18
21
  const challenge = await (0, pow_js_1.createChallenge)({
19
22
  deriveKey,
20
23
  hmacSignatureSecret,
@@ -32,7 +35,7 @@ function create(options) {
32
35
  });
33
36
  const verifyHandler = asyncHandler(async (req, res) => {
34
37
  const payload = await getPayloadFromRequest(req);
35
- const result = await (0, shared_js_1.verify)(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
38
+ const result = await (0, shared_js_1.verify)(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
36
39
  res.json(result);
37
40
  });
38
41
  const getPayloadFromRequest = async (req, cookieName) => {
@@ -45,7 +48,7 @@ function create(options) {
45
48
  const { throwOnFailure = true } = options;
46
49
  return asyncHandler(async (req, res, next) => {
47
50
  const payload = await getPayloadFromRequest(req, setCookie?.name);
48
- const { error, payload: resultPayload, verification, } = await (0, shared_js_1.verify)(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
51
+ const { error, payload: resultPayload, verification, } = await (0, shared_js_1.verify)(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
49
52
  res.locals.altcha = {
50
53
  error,
51
54
  payload: resultPayload,
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "deriveHmacKeySecret", { enumerable: true, get: f
10
10
  const capped_map_js_1 = require("../capped-map.js");
11
11
  Object.defineProperty(exports, "CappedMap", { enumerable: true, get: function () { return capped_map_js_1.CappedMap; } });
12
12
  function create(options) {
13
- const { createChallengeParameters, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, } = options;
13
+ const { createChallengeParameters, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, verifyServer: verifyServerOptions, } = options;
14
14
  const deleteCookie = (reply, name, path = '/') => {
15
15
  reply.header('Set-Cookie', `${name}=; Path=${path ?? '/'}; Max-Age=0`);
16
16
  };
@@ -34,6 +34,9 @@ function create(options) {
34
34
  return request.body?.altcha;
35
35
  };
36
36
  const challengeHandler = async (request, reply) => {
37
+ if (!deriveKey || !createChallengeParameters) {
38
+ throw new Error('deriveKey and createChallengeParameters are required to generate challenges. Omit challengeHandler when relying on Sentinel to issue challenges.');
39
+ }
37
40
  const challenge = await (0, pow_js_1.createChallenge)({
38
41
  deriveKey,
39
42
  hmacSignatureSecret,
@@ -51,14 +54,14 @@ function create(options) {
51
54
  };
52
55
  const verifyHandler = async (request, reply) => {
53
56
  const payload = await getPayloadFromRequest(request);
54
- const result = await (0, shared_js_1.verify)(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
57
+ const result = await (0, shared_js_1.verify)(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
55
58
  return reply.send(result);
56
59
  };
57
60
  const middleware = (options = {}) => {
58
61
  const { throwOnFailure = true } = options;
59
62
  return async (request, reply) => {
60
63
  const payload = await getPayloadFromRequest(request, setCookie?.name);
61
- const { error, payload: resultPayload, verification, } = await (0, shared_js_1.verify)(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
64
+ const { error, payload: resultPayload, verification, } = await (0, shared_js_1.verify)(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
62
65
  request.altcha = {
63
66
  error,
64
67
  payload: resultPayload,
@@ -22,7 +22,7 @@ export declare function create(options: AltchaOptions): {
22
22
  verifyHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
23
23
  error: string | null;
24
24
  payload: import("../types.js").Payload | import("../types.js").ServerSignaturePayload | null;
25
- verification: import("../types.js").VerifySolutionResult | null;
25
+ verification: import("../types.js").VerifySolutionResult | import("../types.js").VerifyServerResult | null;
26
26
  }>>;
27
27
  getPayloadFromEvent: (event: H3Event, cookieName?: string) => Promise<string | undefined>;
28
28
  middleware: (options?: AltchaMiddlewareOptions) => import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<void>>;
@@ -11,8 +11,14 @@ Object.defineProperty(exports, "CappedMap", { enumerable: true, get: function ()
11
11
  const shared_js_1 = require("./shared.js");
12
12
  Object.defineProperty(exports, "deriveHmacKeySecret", { enumerable: true, get: function () { return shared_js_1.deriveHmacKeySecret; } });
13
13
  function create(options) {
14
- const { createChallengeParameters, deriveKey, fieldName = 'altcha', hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, } = options;
14
+ const { createChallengeParameters, deriveKey, fieldName = 'altcha', hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, verifyServer: verifyServerOptions, } = options;
15
15
  const challengeHandler = (0, h3_1.defineEventHandler)(async (event) => {
16
+ if (!deriveKey || !createChallengeParameters) {
17
+ throw new h3_1.HTTPError({
18
+ message: 'deriveKey and createChallengeParameters are required to generate challenges. Omit challengeHandler when relying on Sentinel to issue challenges.',
19
+ status: 500,
20
+ });
21
+ }
16
22
  (0, h3_1.setResponseHeader)(event, 'Cache-Control', 'no-store');
17
23
  return {
18
24
  configuration: setCookie
@@ -29,7 +35,7 @@ function create(options) {
29
35
  };
30
36
  });
31
37
  const verifyHandler = (0, h3_1.defineEventHandler)(async (event) => {
32
- return await (0, shared_js_1.verify)(await getPayloadFromEvent(event), deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
38
+ return await (0, shared_js_1.verify)(await getPayloadFromEvent(event), deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
33
39
  });
34
40
  const getPayloadFromEvent = async (event, cookieName) => {
35
41
  let payload = undefined;
@@ -60,7 +66,7 @@ function create(options) {
60
66
  const middleware = (options = {}) => {
61
67
  const { throwOnFailure = true } = options;
62
68
  return (0, h3_1.defineEventHandler)(async (event) => {
63
- const { error, payload, verification } = await (0, shared_js_1.verify)(await getPayloadFromEvent(event, setCookie?.name), deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
69
+ const { error, payload, verification } = await (0, shared_js_1.verify)(await getPayloadFromEvent(event, setCookie?.name), deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
64
70
  event.context.altcha = {
65
71
  error,
66
72
  payload,
@@ -84,6 +84,24 @@ export declare function create(options: AltchaOptions): {
84
84
  invalidSolution: boolean | null;
85
85
  time: number;
86
86
  verified: boolean;
87
+ } | {
88
+ apiKey?: string | null | undefined;
89
+ reason?: string | undefined;
90
+ verificationData?: {
91
+ [x: string]: import("hono/utils/types.js").JSONValue;
92
+ classification?: import("../types.js").ServerClassification | undefined;
93
+ email?: string | undefined;
94
+ expire?: number | undefined;
95
+ fields?: string[] | undefined;
96
+ fieldsHash?: string | undefined;
97
+ id?: string | undefined;
98
+ ipAddress?: string | undefined;
99
+ reasons?: string[] | undefined;
100
+ score?: number | undefined;
101
+ time?: number | undefined;
102
+ verified?: boolean | undefined;
103
+ } | null | undefined;
104
+ verified: boolean;
87
105
  } | null;
88
106
  }, import("hono/utils/http-status.js").ContentfulStatusCode, "json">>;
89
107
  getPayloadFromContext: (c: Context, cookieName?: string) => Promise<string | undefined>;
@@ -12,8 +12,13 @@ Object.defineProperty(exports, "CappedMap", { enumerable: true, get: function ()
12
12
  const shared_js_1 = require("./shared.js");
13
13
  Object.defineProperty(exports, "deriveHmacKeySecret", { enumerable: true, get: function () { return shared_js_1.deriveHmacKeySecret; } });
14
14
  function create(options) {
15
- const { createChallengeParameters, deriveKey, fieldName = 'altcha', hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, } = options;
15
+ const { createChallengeParameters, deriveKey, fieldName = 'altcha', hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, verifyServer: verifyServerOptions, } = options;
16
16
  const challengeHandler = async (c) => {
17
+ if (!deriveKey || !createChallengeParameters) {
18
+ throw new http_exception_1.HTTPException(500, {
19
+ message: 'deriveKey and createChallengeParameters are required to generate challenges. Omit challengeHandler when relying on Sentinel to issue challenges.',
20
+ });
21
+ }
17
22
  c.header('Cache-Control', 'no-store');
18
23
  return c.json({
19
24
  configuration: setCookie
@@ -30,7 +35,7 @@ function create(options) {
30
35
  });
31
36
  };
32
37
  const verifyHandler = async (c) => {
33
- return c.json(await (0, shared_js_1.verify)(await getPayloadFromContext(c), deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store));
38
+ return c.json(await (0, shared_js_1.verify)(await getPayloadFromContext(c), deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions));
34
39
  };
35
40
  const getPayloadFromContext = async (c, cookieName) => {
36
41
  let payload = undefined;
@@ -54,7 +59,7 @@ function create(options) {
54
59
  const middleware = (options = {}) => {
55
60
  const { throwOnFailure = true } = options;
56
61
  return async (c, next) => {
57
- const { error, payload, verification } = await (0, shared_js_1.verify)(await getPayloadFromContext(c, setCookie?.name), deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
62
+ const { error, payload, verification } = await (0, shared_js_1.verify)(await getPayloadFromContext(c, setCookie?.name), deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
58
63
  c.set('altcha', {
59
64
  error,
60
65
  payload,
@@ -22,13 +22,14 @@ export declare function createAltchaMiddleware(options?: AltchaMiddlewareOptions
22
22
  };
23
23
  };
24
24
  export declare class AltchaService {
25
- private readonly hmacSignatureSecret;
25
+ private readonly hmacSignatureSecret?;
26
26
  private readonly hmacKeySignatureSecret?;
27
27
  private readonly createChallengeParameters;
28
- private readonly deriveKey;
28
+ private readonly deriveKey?;
29
29
  private readonly fieldName;
30
30
  private readonly setCookieOptions?;
31
31
  private readonly store?;
32
+ private readonly verifyServerOptions?;
32
33
  constructor(options: AltchaOptions);
33
34
  get setCookie(): RequireField<SetCookieOptions, 'name'> | undefined;
34
35
  getChallenge(): Promise<{
@@ -43,7 +44,7 @@ export declare class AltchaService {
43
44
  verify(payload: string | undefined): Promise<{
44
45
  error: string | null;
45
46
  payload: import("../types.js").Payload | import("../types.js").ServerSignaturePayload | null;
46
- verification: import("../types.js").VerifySolutionResult | null;
47
+ verification: import("../types.js").VerifySolutionResult | import("../types.js").VerifyServerResult | null;
47
48
  }>;
48
49
  }
49
50
  export declare class AltchaController {
@@ -60,7 +61,7 @@ export declare class AltchaController {
60
61
  verifySolution(req: Request): Promise<{
61
62
  error: string | null;
62
63
  payload: import("../types.js").Payload | import("../types.js").ServerSignaturePayload | null;
63
- verification: import("../types.js").VerifySolutionResult | null;
64
+ verification: import("../types.js").VerifySolutionResult | import("../types.js").VerifyServerResult | null;
64
65
  }>;
65
66
  }
66
67
  export declare class AltchaMiddleware implements NestMiddleware {
@@ -67,16 +67,21 @@ let AltchaService = class AltchaService {
67
67
  this.fieldName = options.fieldName || 'altcha';
68
68
  this.setCookieOptions = options.setCookie;
69
69
  this.store = options.store;
70
+ this.verifyServerOptions = options.verifyServer;
70
71
  }
71
72
  get setCookie() {
72
73
  return this.setCookieOptions;
73
74
  }
74
75
  async getChallenge() {
76
+ const { createChallengeParameters, deriveKey } = this;
77
+ if (!deriveKey || !createChallengeParameters) {
78
+ throw new common_1.HttpException('deriveKey and createChallengeParameters are required to generate challenges. Omit the /challenge route when relying on Sentinel to issue challenges.', common_1.HttpStatus.INTERNAL_SERVER_ERROR);
79
+ }
75
80
  const challenge = await (0, pow_js_1.createChallenge)({
76
- deriveKey: this.deriveKey,
81
+ deriveKey,
77
82
  hmacSignatureSecret: this.hmacSignatureSecret,
78
83
  hmacKeySignatureSecret: this.hmacKeySignatureSecret,
79
- ...this.createChallengeParameters(),
84
+ ...createChallengeParameters(),
80
85
  });
81
86
  return {
82
87
  configuration: this.setCookieOptions
@@ -92,7 +97,7 @@ let AltchaService = class AltchaService {
92
97
  return req.body?.[this.fieldName];
93
98
  }
94
99
  async verify(payload) {
95
- return (0, shared_js_1.verify)(payload, this.deriveKey, this.hmacSignatureSecret, this.hmacKeySignatureSecret, this.store);
100
+ return (0, shared_js_1.verify)(payload, this.deriveKey, this.hmacSignatureSecret, this.hmacKeySignatureSecret, this.store, this.verifyServerOptions);
96
101
  }
97
102
  };
98
103
  exports.AltchaService = AltchaService;
@@ -21,9 +21,12 @@ function deleteCookie(res, name, path) {
21
21
  res.headers.append('Set-Cookie', `${name}=; Path=${path ?? '/'}; Max-Age=0`);
22
22
  }
23
23
  function create(options) {
24
- const { createChallengeParameters, deriveKey, fieldName = 'altcha', hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, } = options;
24
+ const { createChallengeParameters, deriveKey, fieldName = 'altcha', hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, verifyServer: verifyServerOptions, } = options;
25
25
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
26
26
  async function challengeHandler(_req) {
27
+ if (!deriveKey || !createChallengeParameters) {
28
+ throw new Error('deriveKey and createChallengeParameters are required to generate challenges. Omit challengeHandler when relying on Sentinel to issue challenges.');
29
+ }
27
30
  const challenge = await (0, pow_js_1.createChallenge)({
28
31
  deriveKey,
29
32
  hmacSignatureSecret,
@@ -45,7 +48,7 @@ function create(options) {
45
48
  }
46
49
  async function verifyHandler(req) {
47
50
  const payload = await getPayloadFromRequest(req);
48
- const result = await (0, shared_js_1.verify)(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
51
+ const result = await (0, shared_js_1.verify)(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
49
52
  return Response.json(result);
50
53
  }
51
54
  async function getPayloadFromRequest(req, cookieName) {
@@ -69,7 +72,7 @@ function create(options) {
69
72
  }
70
73
  async function middleware(req, throwOnFailure = true) {
71
74
  const payload = await getPayloadFromRequest(req, setCookie?.name);
72
- const { error, payload: verifiedPayload, verification, } = await (0, shared_js_1.verify)(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
75
+ const { error, payload: verifiedPayload, verification, } = await (0, shared_js_1.verify)(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
73
76
  const result = {
74
77
  error,
75
78
  payload: verifiedPayload,
@@ -1,8 +1,8 @@
1
- import { DeriveKeyFunction, Payload, ServerSignaturePayload, VerifySolutionResult } from '../types.js';
2
- import type { Store } from './types.js';
1
+ import { DeriveKeyFunction, Payload, ServerSignaturePayload, VerifyServerResult, VerifySolutionResult } from '../types.js';
2
+ import type { AltchaVerifyServerOptions, Store } from './types.js';
3
3
  export declare function deriveHmacKeySecret(masterSecret: string): Promise<string>;
4
- export declare function verify(payload: unknown, deriveKey: DeriveKeyFunction, hmacSignatureSecret: string, hmacKeySignatureSecret?: string, store?: Store): Promise<{
4
+ export declare function verify(payload: unknown, deriveKey: DeriveKeyFunction | undefined, hmacSignatureSecret: string | undefined, hmacKeySignatureSecret?: string, store?: Store, verifyServerOptions?: AltchaVerifyServerOptions): Promise<{
5
5
  error: string | null;
6
6
  payload: Payload | ServerSignaturePayload | null;
7
- verification: VerifySolutionResult | null;
7
+ verification: VerifySolutionResult | VerifyServerResult | null;
8
8
  }>;
@@ -4,12 +4,13 @@ exports.deriveHmacKeySecret = deriveHmacKeySecret;
4
4
  exports.verify = verify;
5
5
  const pow_js_1 = require("../pow.js");
6
6
  const server_signature_js_1 = require("../server-signature.js");
7
+ const verify_server_js_1 = require("../verify-server.js");
7
8
  const helpers_js_1 = require("../helpers.js");
8
9
  const types_js_1 = require("../types.js");
9
10
  async function deriveHmacKeySecret(masterSecret) {
10
11
  return (0, helpers_js_1.bufferToHex)(await (0, helpers_js_1.hmac)(types_js_1.HmacAlgorithm.SHA_256, masterSecret, 'derived-secret'));
11
12
  }
12
- async function verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store) {
13
+ async function verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions) {
13
14
  if (!payload) {
14
15
  return {
15
16
  error: 'ALTCHA payload is missing.',
@@ -33,6 +34,12 @@ async function verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureS
33
34
  try {
34
35
  switch (type) {
35
36
  case 'client':
37
+ if (!deriveKey) {
38
+ throw new Error('deriveKey is required to verify self-hosted ALTCHA challenges.');
39
+ }
40
+ if (!hmacSignatureSecret) {
41
+ throw new Error('hmacSignatureSecret is required to verify self-hosted ALTCHA challenges.');
42
+ }
36
43
  challengeId = getChallengeId(payload);
37
44
  if (store && challengeId) {
38
45
  await checkChallengeId(store, challengeId);
@@ -44,7 +51,18 @@ async function verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureS
44
51
  if (store && challengeId) {
45
52
  await checkChallengeId(store, challengeId);
46
53
  }
47
- verification = await verifyServerSignaturePayload(payload, hmacSignatureSecret);
54
+ if (verifyServerOptions) {
55
+ verification = await (0, verify_server_js_1.verifyServer)({
56
+ ...verifyServerOptions,
57
+ payload: payload,
58
+ });
59
+ }
60
+ else {
61
+ if (!hmacSignatureSecret) {
62
+ throw new Error('hmacSignatureSecret or verifyServer must be configured to verify this payload.');
63
+ }
64
+ verification = await verifyServerSignaturePayload(payload, hmacSignatureSecret);
65
+ }
48
66
  break;
49
67
  default:
50
68
  throw new Error('ALTCHA payload is invalid.');
@@ -59,7 +77,8 @@ async function verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureS
59
77
  }
60
78
  if (!verification?.verified) {
61
79
  return {
62
- error: 'ALTCHA verification failed.',
80
+ error: (verification && 'reason' in verification && verification.reason) ||
81
+ 'ALTCHA verification failed.',
63
82
  payload: payload,
64
83
  verification,
65
84
  };
@@ -27,8 +27,11 @@ async function getPayloadFromEvent(event, fieldName, cookieName) {
27
27
  return body?.[fieldName];
28
28
  }
29
29
  function create(options) {
30
- const { createChallengeParameters, deriveKey, fieldName = 'altcha', hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, } = options;
30
+ const { createChallengeParameters, deriveKey, fieldName = 'altcha', hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, verifyServer: verifyServerOptions, } = options;
31
31
  async function challengeHandler() {
32
+ if (!deriveKey || !createChallengeParameters) {
33
+ (0, kit_1.error)(500, 'deriveKey and createChallengeParameters are required to generate challenges. Omit challengeHandler when relying on Sentinel to issue challenges.');
34
+ }
32
35
  const challenge = await (0, pow_js_1.createChallenge)({
33
36
  deriveKey,
34
37
  hmacSignatureSecret,
@@ -46,14 +49,14 @@ function create(options) {
46
49
  }
47
50
  async function verifyHandler(event) {
48
51
  const payload = await getPayloadFromEvent(event, fieldName);
49
- const result = await (0, shared_js_1.verify)(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
52
+ const result = await (0, shared_js_1.verify)(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
50
53
  return (0, kit_1.json)(result);
51
54
  }
52
55
  function createHandle(middlewareOptions = {}) {
53
56
  const { throwOnFailure = true } = middlewareOptions;
54
57
  return async ({ event, resolve }) => {
55
58
  const payload = await getPayloadFromEvent(event, fieldName, setCookie?.name);
56
- const { error: verifyError, payload: resultPayload, verification, } = await (0, shared_js_1.verify)(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
59
+ const { error: verifyError, payload: resultPayload, verification, } = await (0, shared_js_1.verify)(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
57
60
  event.locals.altcha = {
58
61
  error: verifyError,
59
62
  payload: resultPayload,
@@ -72,7 +75,7 @@ function create(options) {
72
75
  }
73
76
  async function verifyEvent(event) {
74
77
  const payload = await getPayloadFromEvent(event, fieldName, setCookie?.name);
75
- const { error: verifyError, payload: resultPayload, verification, } = await (0, shared_js_1.verify)(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
78
+ const { error: verifyError, payload: resultPayload, verification, } = await (0, shared_js_1.verify)(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
76
79
  if (setCookie) {
77
80
  event.cookies.delete(setCookie.name, {
78
81
  path: setCookie.path ?? '/',
@@ -1,14 +1,19 @@
1
- import type { CreateChallengeOptions, DeriveKeyFunction, Payload, ServerSignaturePayload, SetCookieOptions, VerifySolutionResult } from '../types.js';
1
+ import type { CreateChallengeOptions, DeriveKeyFunction, Payload, ServerSignaturePayload, SetCookieOptions, VerifyServerOptions, VerifyServerResult, VerifySolutionResult } from '../types.js';
2
+ export type AltchaVerifyServerOptions = Omit<VerifyServerOptions, 'payload'>;
2
3
  export interface AltchaOptions {
3
4
  /**
4
5
  * Returns configuration options passed to `createChallenge`.
5
6
  * Must include `algorithm` and `cost`; all other `CreateChallengeOptions` fields are optional.
7
+ * Required to use `challengeHandler`; optional if challenges are issued by Sentinel and
8
+ * only `verifyServer` is used.
6
9
  */
7
- createChallengeParameters: () => Pick<CreateChallengeOptions, 'algorithm' | 'cost'> & Partial<CreateChallengeOptions>;
10
+ createChallengeParameters?: () => Pick<CreateChallengeOptions, 'algorithm' | 'cost'> & Partial<CreateChallengeOptions>;
8
11
  /**
9
12
  * Algorithm-specific key derivation function used to generate challenge keys.
13
+ * Required to use `challengeHandler` or to verify self-hosted (client-type) payloads;
14
+ * optional if challenges are issued by Sentinel and only `verifyServer` is used.
10
15
  */
11
- deriveKey: DeriveKeyFunction;
16
+ deriveKey?: DeriveKeyFunction;
12
17
  /**
13
18
  * Name of the form field that carries the ALTCHA payload.
14
19
  * Defaults to `'altcha'` if not specified.
@@ -16,8 +21,10 @@ export interface AltchaOptions {
16
21
  fieldName?: string;
17
22
  /**
18
23
  * HMAC secret used to sign and verify challenge signatures.
24
+ * Optional if `verifyServer` is configured and only Sentinel-issued (server-signed)
25
+ * payloads are verified remotely.
19
26
  */
20
- hmacSignatureSecret: string;
27
+ hmacSignatureSecret?: string;
21
28
  /**
22
29
  * HMAC secret used to sign keys in deterministic mode.
23
30
  */
@@ -31,6 +38,11 @@ export interface AltchaOptions {
31
38
  * Store implementation for tracking used challenges and preventing replay attacks.
32
39
  */
33
40
  store?: Store;
41
+ /**
42
+ * When set, Sentinel-issued (server-signed) payloads are verified remotely via
43
+ * `POST /v1/verify/signature` instead of locally with `hmacSignatureSecret`.
44
+ */
45
+ verifyServer?: AltchaVerifyServerOptions;
34
46
  }
35
47
  export interface AltchaMiddlewareOptions {
36
48
  throwOnFailure?: boolean;
@@ -38,7 +50,7 @@ export interface AltchaMiddlewareOptions {
38
50
  export interface AltchaResult {
39
51
  error: string | null;
40
52
  payload: Payload | ServerSignaturePayload | null;
41
- verification: VerifySolutionResult | null;
53
+ verification: VerifySolutionResult | VerifyServerResult | null;
42
54
  }
43
55
  export type RequireField<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
44
56
  export interface Store {
@@ -1,10 +1,11 @@
1
1
  import { createChallenge, solveChallenge, solveChallengeWorkers, verifySolution } from './pow.js';
2
2
  import { verifyFieldsHash, verifyServerSignature } from './server-signature.js';
3
+ import { verifyServer } from './verify-server.js';
3
4
  import { CappedMap } from './capped-map.js';
4
5
  import { randomInt } from './helpers.js';
5
6
  import { HmacAlgorithm } from './types.js';
6
7
  export type * from './types.js';
7
- export { CappedMap, HmacAlgorithm, createChallenge, randomInt, solveChallenge, solveChallengeWorkers, verifyFieldsHash, verifyServerSignature, verifySolution, };
8
+ export { CappedMap, HmacAlgorithm, createChallenge, randomInt, solveChallenge, solveChallengeWorkers, verifyFieldsHash, verifyServer, verifyServerSignature, verifySolution, };
8
9
  declare const _default: {
9
10
  CappedMap: typeof CappedMap;
10
11
  HmacAlgorithm: typeof HmacAlgorithm;
@@ -13,6 +14,7 @@ declare const _default: {
13
14
  solveChallenge: typeof solveChallenge;
14
15
  solveChallengeWorkers: typeof solveChallengeWorkers;
15
16
  verifyFieldsHash: typeof verifyFieldsHash;
17
+ verifyServer: typeof verifyServer;
16
18
  verifyServerSignature: typeof verifyServerSignature;
17
19
  verifySolution: typeof verifySolution;
18
20
  };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.verifySolution = exports.verifyServerSignature = exports.verifyFieldsHash = exports.solveChallengeWorkers = exports.solveChallenge = exports.randomInt = exports.createChallenge = exports.HmacAlgorithm = exports.CappedMap = void 0;
3
+ exports.verifySolution = exports.verifyServerSignature = exports.verifyServer = exports.verifyFieldsHash = exports.solveChallengeWorkers = exports.solveChallenge = exports.randomInt = exports.createChallenge = exports.HmacAlgorithm = exports.CappedMap = void 0;
4
4
  const pow_js_1 = require("./pow.js");
5
5
  Object.defineProperty(exports, "createChallenge", { enumerable: true, get: function () { return pow_js_1.createChallenge; } });
6
6
  Object.defineProperty(exports, "solveChallenge", { enumerable: true, get: function () { return pow_js_1.solveChallenge; } });
@@ -9,6 +9,8 @@ Object.defineProperty(exports, "verifySolution", { enumerable: true, get: functi
9
9
  const server_signature_js_1 = require("./server-signature.js");
10
10
  Object.defineProperty(exports, "verifyFieldsHash", { enumerable: true, get: function () { return server_signature_js_1.verifyFieldsHash; } });
11
11
  Object.defineProperty(exports, "verifyServerSignature", { enumerable: true, get: function () { return server_signature_js_1.verifyServerSignature; } });
12
+ const verify_server_js_1 = require("./verify-server.js");
13
+ Object.defineProperty(exports, "verifyServer", { enumerable: true, get: function () { return verify_server_js_1.verifyServer; } });
12
14
  const capped_map_js_1 = require("./capped-map.js");
13
15
  Object.defineProperty(exports, "CappedMap", { enumerable: true, get: function () { return capped_map_js_1.CappedMap; } });
14
16
  const helpers_js_1 = require("./helpers.js");
@@ -23,6 +25,7 @@ exports.default = {
23
25
  solveChallenge: pow_js_1.solveChallenge,
24
26
  solveChallengeWorkers: pow_js_1.solveChallengeWorkers,
25
27
  verifyFieldsHash: server_signature_js_1.verifyFieldsHash,
28
+ verifyServer: verify_server_js_1.verifyServer,
26
29
  verifyServerSignature: server_signature_js_1.verifyServerSignature,
27
30
  verifySolution: pow_js_1.verifySolution,
28
31
  };
@@ -233,6 +233,60 @@ export interface VerifyResult {
233
233
  export interface VerifyServerSignatureResult extends VerifySolutionResult {
234
234
  verificationData?: ServerSignatureVerificationData | null;
235
235
  }
236
+ export interface VerifyServerOptions {
237
+ /**
238
+ * The payload to verify, as received from `POST /v1/verify`
239
+ * (either the raw string or the decoded object).
240
+ */
241
+ payload: string | ServerSignaturePayload | Record<string, unknown>;
242
+ /**
243
+ * Full URL of the Sentinel `/v1/verify/signature` endpoint.
244
+ */
245
+ url: string;
246
+ /**
247
+ * API key secret. If provided, Sentinel checks that it matches the
248
+ * API key associated with the payload.
249
+ */
250
+ secret?: string;
251
+ /**
252
+ * Custom fetch implementation. Defaults to the global `fetch`.
253
+ */
254
+ fetch?: typeof fetch;
255
+ /**
256
+ * Additional headers to send with the request.
257
+ */
258
+ headers?: Record<string, string>;
259
+ /**
260
+ * AbortController for cancelling the verification request.
261
+ */
262
+ controller?: AbortController;
263
+ /**
264
+ * Per-attempt request timeout in milliseconds.
265
+ * Defaults to `10_000` (10 seconds).
266
+ */
267
+ timeout?: number;
268
+ /**
269
+ * Number of retry attempts after the first try.
270
+ * Defaults to `0` (no retries).
271
+ */
272
+ retries?: number;
273
+ /**
274
+ * Base delay in milliseconds between retry attempts.
275
+ * Defaults to `300`.
276
+ */
277
+ retryDelay?: number;
278
+ /**
279
+ * Backoff strategy applied to `retryDelay` between retries.
280
+ * Defaults to `'exponential'`.
281
+ */
282
+ retryBackoff?: 'fixed' | 'exponential';
283
+ }
284
+ export interface VerifyServerResult {
285
+ apiKey?: string | null;
286
+ reason?: string;
287
+ verificationData?: ServerSignatureVerificationData | null;
288
+ verified: boolean;
289
+ }
236
290
  export interface VerifySolutionOptions {
237
291
  /**
238
292
  * Challenge object to verify against, either decoded from the client payload
@@ -0,0 +1,3 @@
1
+ import type { VerifyServerOptions, VerifyServerResult } from './types.js';
2
+ /** Verifies a payload remotely via the ALTCHA Sentinel `/v1/verify/signature` API. */
3
+ export declare function verifyServer(options: VerifyServerOptions): Promise<VerifyServerResult>;