Skip to content

Commit a126bad

Browse files
committed
verifyServer helper
1 parent d131f00 commit a126bad

63 files changed

Lines changed: 1194 additions & 119 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ The library includes plugins for several popular frameworks to simplify integrat
5555

5656
If your framework is not listed, see the [Advanced Usage](/docs/advanced-usage.md) guide for custom integrations.
5757

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+
5860
## Documentation
5961

6062
- Advanced Usage: [`/docs/advanced-usage.md`](/docs/advanced-usage.md)
@@ -217,6 +219,32 @@ Returns `VerifyServerSignatureResult` (extends `VerifySolutionResult`):
217219
| `verified` | `boolean` | Whether the signature is valid. |
218220
| `verificationData` | `ServerSignatureVerificationData \| null` | Parsed verification data. |
219221

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+
220248
#### `obfuscate(str: string, options?): Promise<string>`
221249

222250
> Import from `altcha-lib/obfuscation`

dist/cjs/v2/frameworks/express.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ const asyncHandler = (fn) => (req, res, next) => {
1313
fn(req, res, next).catch(next);
1414
};
1515
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;
1717
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+
}
1821
const challenge = await (0, pow_js_1.createChallenge)({
1922
deriveKey,
2023
hmacSignatureSecret,
@@ -32,7 +35,7 @@ function create(options) {
3235
});
3336
const verifyHandler = asyncHandler(async (req, res) => {
3437
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);
3639
res.json(result);
3740
});
3841
const getPayloadFromRequest = async (req, cookieName) => {
@@ -45,7 +48,7 @@ function create(options) {
4548
const { throwOnFailure = true } = options;
4649
return asyncHandler(async (req, res, next) => {
4750
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);
4952
res.locals.altcha = {
5053
error,
5154
payload: resultPayload,

dist/cjs/v2/frameworks/fastify.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "deriveHmacKeySecret", { enumerable: true, get: f
1010
const capped_map_js_1 = require("../capped-map.js");
1111
Object.defineProperty(exports, "CappedMap", { enumerable: true, get: function () { return capped_map_js_1.CappedMap; } });
1212
function create(options) {
13-
const { createChallengeParameters, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, } = options;
13+
const { createChallengeParameters, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, verifyServer: verifyServerOptions, } = options;
1414
const deleteCookie = (reply, name, path = '/') => {
1515
reply.header('Set-Cookie', `${name}=; Path=${path ?? '/'}; Max-Age=0`);
1616
};
@@ -34,6 +34,9 @@ function create(options) {
3434
return request.body?.altcha;
3535
};
3636
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+
}
3740
const challenge = await (0, pow_js_1.createChallenge)({
3841
deriveKey,
3942
hmacSignatureSecret,
@@ -51,14 +54,14 @@ function create(options) {
5154
};
5255
const verifyHandler = async (request, reply) => {
5356
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);
5558
return reply.send(result);
5659
};
5760
const middleware = (options = {}) => {
5861
const { throwOnFailure = true } = options;
5962
return async (request, reply) => {
6063
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);
6265
request.altcha = {
6366
error,
6467
payload: resultPayload,

dist/cjs/v2/frameworks/h3.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export declare function create(options: AltchaOptions): {
2222
verifyHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
2323
error: string | null;
2424
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;
2626
}>>;
2727
getPayloadFromEvent: (event: H3Event, cookieName?: string) => Promise<string | undefined>;
2828
middleware: (options?: AltchaMiddlewareOptions) => import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<void>>;

dist/cjs/v2/frameworks/h3.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,14 @@ Object.defineProperty(exports, "CappedMap", { enumerable: true, get: function ()
1111
const shared_js_1 = require("./shared.js");
1212
Object.defineProperty(exports, "deriveHmacKeySecret", { enumerable: true, get: function () { return shared_js_1.deriveHmacKeySecret; } });
1313
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;
1515
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+
}
1622
(0, h3_1.setResponseHeader)(event, 'Cache-Control', 'no-store');
1723
return {
1824
configuration: setCookie
@@ -29,7 +35,7 @@ function create(options) {
2935
};
3036
});
3137
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);
3339
});
3440
const getPayloadFromEvent = async (event, cookieName) => {
3541
let payload = undefined;
@@ -60,7 +66,7 @@ function create(options) {
6066
const middleware = (options = {}) => {
6167
const { throwOnFailure = true } = options;
6268
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);
6470
event.context.altcha = {
6571
error,
6672
payload,

dist/cjs/v2/frameworks/hono.d.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,24 @@ export declare function create(options: AltchaOptions): {
8484
invalidSolution: boolean | null;
8585
time: number;
8686
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;
87105
} | null;
88106
}, import("hono/utils/http-status.js").ContentfulStatusCode, "json">>;
89107
getPayloadFromContext: (c: Context, cookieName?: string) => Promise<string | undefined>;

dist/cjs/v2/frameworks/hono.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,13 @@ Object.defineProperty(exports, "CappedMap", { enumerable: true, get: function ()
1212
const shared_js_1 = require("./shared.js");
1313
Object.defineProperty(exports, "deriveHmacKeySecret", { enumerable: true, get: function () { return shared_js_1.deriveHmacKeySecret; } });
1414
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;
1616
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+
}
1722
c.header('Cache-Control', 'no-store');
1823
return c.json({
1924
configuration: setCookie
@@ -30,7 +35,7 @@ function create(options) {
3035
});
3136
};
3237
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));
3439
};
3540
const getPayloadFromContext = async (c, cookieName) => {
3641
let payload = undefined;
@@ -54,7 +59,7 @@ function create(options) {
5459
const middleware = (options = {}) => {
5560
const { throwOnFailure = true } = options;
5661
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);
5863
c.set('altcha', {
5964
error,
6065
payload,

dist/cjs/v2/frameworks/nestjs.d.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@ export declare function createAltchaMiddleware(options?: AltchaMiddlewareOptions
2222
};
2323
};
2424
export declare class AltchaService {
25-
private readonly hmacSignatureSecret;
25+
private readonly hmacSignatureSecret?;
2626
private readonly hmacKeySignatureSecret?;
2727
private readonly createChallengeParameters;
28-
private readonly deriveKey;
28+
private readonly deriveKey?;
2929
private readonly fieldName;
3030
private readonly setCookieOptions?;
3131
private readonly store?;
32+
private readonly verifyServerOptions?;
3233
constructor(options: AltchaOptions);
3334
get setCookie(): RequireField<SetCookieOptions, 'name'> | undefined;
3435
getChallenge(): Promise<{
@@ -43,7 +44,7 @@ export declare class AltchaService {
4344
verify(payload: string | undefined): Promise<{
4445
error: string | null;
4546
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;
4748
}>;
4849
}
4950
export declare class AltchaController {
@@ -60,7 +61,7 @@ export declare class AltchaController {
6061
verifySolution(req: Request): Promise<{
6162
error: string | null;
6263
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;
6465
}>;
6566
}
6667
export declare class AltchaMiddleware implements NestMiddleware {

dist/cjs/v2/frameworks/nestjs.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,21 @@ let AltchaService = class AltchaService {
6767
this.fieldName = options.fieldName || 'altcha';
6868
this.setCookieOptions = options.setCookie;
6969
this.store = options.store;
70+
this.verifyServerOptions = options.verifyServer;
7071
}
7172
get setCookie() {
7273
return this.setCookieOptions;
7374
}
7475
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+
}
7580
const challenge = await (0, pow_js_1.createChallenge)({
76-
deriveKey: this.deriveKey,
81+
deriveKey,
7782
hmacSignatureSecret: this.hmacSignatureSecret,
7883
hmacKeySignatureSecret: this.hmacKeySignatureSecret,
79-
...this.createChallengeParameters(),
84+
...createChallengeParameters(),
8085
});
8186
return {
8287
configuration: this.setCookieOptions
@@ -92,7 +97,7 @@ let AltchaService = class AltchaService {
9297
return req.body?.[this.fieldName];
9398
}
9499
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);
96101
}
97102
};
98103
exports.AltchaService = AltchaService;

dist/cjs/v2/frameworks/nextjs.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ function deleteCookie(res, name, path) {
2121
res.headers.append('Set-Cookie', `${name}=; Path=${path ?? '/'}; Max-Age=0`);
2222
}
2323
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;
2525
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2626
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+
}
2730
const challenge = await (0, pow_js_1.createChallenge)({
2831
deriveKey,
2932
hmacSignatureSecret,
@@ -45,7 +48,7 @@ function create(options) {
4548
}
4649
async function verifyHandler(req) {
4750
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);
4952
return Response.json(result);
5053
}
5154
async function getPayloadFromRequest(req, cookieName) {
@@ -69,7 +72,7 @@ function create(options) {
6972
}
7073
async function middleware(req, throwOnFailure = true) {
7174
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);
7376
const result = {
7477
error,
7578
payload: verifiedPayload,

0 commit comments

Comments
 (0)