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.
- package/README.md +28 -0
- package/dist/cjs/v2/frameworks/express.js +6 -3
- package/dist/cjs/v2/frameworks/fastify.js +6 -3
- package/dist/cjs/v2/frameworks/h3.d.ts +1 -1
- package/dist/cjs/v2/frameworks/h3.js +9 -3
- package/dist/cjs/v2/frameworks/hono.d.ts +18 -0
- package/dist/cjs/v2/frameworks/hono.js +8 -3
- package/dist/cjs/v2/frameworks/nestjs.d.ts +5 -4
- package/dist/cjs/v2/frameworks/nestjs.js +8 -3
- package/dist/cjs/v2/frameworks/nextjs.js +6 -3
- package/dist/cjs/v2/frameworks/shared.d.ts +4 -4
- package/dist/cjs/v2/frameworks/shared.js +22 -3
- package/dist/cjs/v2/frameworks/sveltekit.js +7 -4
- package/dist/cjs/v2/frameworks/types.d.ts +17 -5
- package/dist/cjs/v2/index.d.ts +3 -1
- package/dist/cjs/v2/index.js +4 -1
- package/dist/cjs/v2/types.d.ts +54 -0
- package/dist/cjs/v2/verify-server.d.ts +3 -0
- package/dist/cjs/v2/verify-server.js +57 -0
- package/dist/esm/v2/frameworks/express.js +6 -3
- package/dist/esm/v2/frameworks/fastify.js +6 -3
- package/dist/esm/v2/frameworks/h3.d.ts +1 -1
- package/dist/esm/v2/frameworks/h3.js +9 -3
- package/dist/esm/v2/frameworks/hono.d.ts +18 -0
- package/dist/esm/v2/frameworks/hono.js +8 -3
- package/dist/esm/v2/frameworks/nestjs.d.ts +5 -4
- package/dist/esm/v2/frameworks/nestjs.js +9 -3
- package/dist/esm/v2/frameworks/nextjs.js +6 -3
- package/dist/esm/v2/frameworks/shared.d.ts +4 -4
- package/dist/esm/v2/frameworks/shared.js +22 -3
- package/dist/esm/v2/frameworks/sveltekit.js +7 -4
- package/dist/esm/v2/frameworks/types.d.ts +17 -5
- package/dist/esm/v2/index.d.ts +3 -1
- package/dist/esm/v2/index.js +3 -1
- package/dist/esm/v2/types.d.ts +54 -0
- package/dist/esm/v2/verify-server.d.ts +3 -0
- package/dist/esm/v2/verify-server.js +54 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.verifyServer = verifyServer;
|
|
4
|
+
const helpers_js_1 = require("./helpers.js");
|
|
5
|
+
/** Verifies a payload remotely via the ALTCHA Sentinel `/v1/verify/signature` API. */
|
|
6
|
+
async function verifyServer(options) {
|
|
7
|
+
const { controller, fetch: fetchImpl = fetch, headers, payload, retries = 0, retryBackoff = 'exponential', retryDelay = 300, secret, timeout = 10_000, url, } = options;
|
|
8
|
+
const body = JSON.stringify({ payload, secret });
|
|
9
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
10
|
+
if (controller?.signal.aborted) {
|
|
11
|
+
return { verified: false, reason: 'ABORTED' };
|
|
12
|
+
}
|
|
13
|
+
const attemptController = new AbortController();
|
|
14
|
+
const onAbort = () => attemptController.abort();
|
|
15
|
+
controller?.signal.addEventListener('abort', onAbort);
|
|
16
|
+
const timer = setTimeout(() => attemptController.abort(), timeout);
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetchImpl(url, {
|
|
19
|
+
body,
|
|
20
|
+
headers: {
|
|
21
|
+
'Content-Type': 'application/json',
|
|
22
|
+
...headers,
|
|
23
|
+
},
|
|
24
|
+
method: 'POST',
|
|
25
|
+
signal: attemptController.signal,
|
|
26
|
+
});
|
|
27
|
+
if (response.status === 400) {
|
|
28
|
+
const data = await response
|
|
29
|
+
.json()
|
|
30
|
+
.catch(() => null);
|
|
31
|
+
return {
|
|
32
|
+
verified: false,
|
|
33
|
+
reason: data?.error ?? `HTTP_${response.status}`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
throw new Error(`HTTP_${response.status}`);
|
|
38
|
+
}
|
|
39
|
+
return (await response.json());
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
if (attempt >= retries) {
|
|
43
|
+
return {
|
|
44
|
+
verified: false,
|
|
45
|
+
reason: err instanceof Error ? err.message : 'NETWORK_ERROR',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const backoffDelay = retryBackoff === 'fixed' ? retryDelay : retryDelay * 2 ** attempt;
|
|
49
|
+
await (0, helpers_js_1.delay)(backoffDelay);
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
controller?.signal.removeEventListener('abort', onAbort);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { verified: false, reason: 'NETWORK_ERROR' };
|
|
57
|
+
}
|
|
@@ -7,8 +7,11 @@ const asyncHandler = (fn) => (req, res, next) => {
|
|
|
7
7
|
fn(req, res, next).catch(next);
|
|
8
8
|
};
|
|
9
9
|
export function create(options) {
|
|
10
|
-
const { createChallengeParameters, deriveKey, fieldName = 'altcha', hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, } = options;
|
|
10
|
+
const { createChallengeParameters, deriveKey, fieldName = 'altcha', hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, verifyServer: verifyServerOptions, } = options;
|
|
11
11
|
const challengeHandler = asyncHandler(async (req, res) => {
|
|
12
|
+
if (!deriveKey || !createChallengeParameters) {
|
|
13
|
+
throw new Error('deriveKey and createChallengeParameters are required to generate challenges. Omit challengeHandler when relying on Sentinel to issue challenges.');
|
|
14
|
+
}
|
|
12
15
|
const challenge = await createChallenge({
|
|
13
16
|
deriveKey,
|
|
14
17
|
hmacSignatureSecret,
|
|
@@ -26,7 +29,7 @@ export function create(options) {
|
|
|
26
29
|
});
|
|
27
30
|
const verifyHandler = asyncHandler(async (req, res) => {
|
|
28
31
|
const payload = await getPayloadFromRequest(req);
|
|
29
|
-
const result = await verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
|
|
32
|
+
const result = await verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
|
|
30
33
|
res.json(result);
|
|
31
34
|
});
|
|
32
35
|
const getPayloadFromRequest = async (req, cookieName) => {
|
|
@@ -39,7 +42,7 @@ export function create(options) {
|
|
|
39
42
|
const { throwOnFailure = true } = options;
|
|
40
43
|
return asyncHandler(async (req, res, next) => {
|
|
41
44
|
const payload = await getPayloadFromRequest(req, setCookie?.name);
|
|
42
|
-
const { error, payload: resultPayload, verification, } = await verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
|
|
45
|
+
const { error, payload: resultPayload, verification, } = await verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
|
|
43
46
|
res.locals.altcha = {
|
|
44
47
|
error,
|
|
45
48
|
payload: resultPayload,
|
|
@@ -4,7 +4,7 @@ import { deriveHmacKeySecret, verify } from './shared.js';
|
|
|
4
4
|
import { CappedMap } from '../capped-map.js';
|
|
5
5
|
export { CappedMap, deriveHmacKeySecret, randomInt };
|
|
6
6
|
export function create(options) {
|
|
7
|
-
const { createChallengeParameters, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, } = options;
|
|
7
|
+
const { createChallengeParameters, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, verifyServer: verifyServerOptions, } = options;
|
|
8
8
|
const deleteCookie = (reply, name, path = '/') => {
|
|
9
9
|
reply.header('Set-Cookie', `${name}=; Path=${path ?? '/'}; Max-Age=0`);
|
|
10
10
|
};
|
|
@@ -28,6 +28,9 @@ export function create(options) {
|
|
|
28
28
|
return request.body?.altcha;
|
|
29
29
|
};
|
|
30
30
|
const challengeHandler = async (request, reply) => {
|
|
31
|
+
if (!deriveKey || !createChallengeParameters) {
|
|
32
|
+
throw new Error('deriveKey and createChallengeParameters are required to generate challenges. Omit challengeHandler when relying on Sentinel to issue challenges.');
|
|
33
|
+
}
|
|
31
34
|
const challenge = await createChallenge({
|
|
32
35
|
deriveKey,
|
|
33
36
|
hmacSignatureSecret,
|
|
@@ -45,14 +48,14 @@ export function create(options) {
|
|
|
45
48
|
};
|
|
46
49
|
const verifyHandler = async (request, reply) => {
|
|
47
50
|
const payload = await getPayloadFromRequest(request);
|
|
48
|
-
const result = await verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
|
|
51
|
+
const result = await verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
|
|
49
52
|
return reply.send(result);
|
|
50
53
|
};
|
|
51
54
|
const middleware = (options = {}) => {
|
|
52
55
|
const { throwOnFailure = true } = options;
|
|
53
56
|
return async (request, reply) => {
|
|
54
57
|
const payload = await getPayloadFromRequest(request, setCookie?.name);
|
|
55
|
-
const { error, payload: resultPayload, verification, } = await verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
|
|
58
|
+
const { error, payload: resultPayload, verification, } = await verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
|
|
56
59
|
request.altcha = {
|
|
57
60
|
error,
|
|
58
61
|
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>>;
|
|
@@ -5,8 +5,14 @@ import { CappedMap } from '../capped-map.js';
|
|
|
5
5
|
import { deriveHmacKeySecret, verify } from './shared.js';
|
|
6
6
|
export { CappedMap, deriveHmacKeySecret, randomInt };
|
|
7
7
|
export function create(options) {
|
|
8
|
-
const { createChallengeParameters, deriveKey, fieldName = 'altcha', hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, } = options;
|
|
8
|
+
const { createChallengeParameters, deriveKey, fieldName = 'altcha', hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, verifyServer: verifyServerOptions, } = options;
|
|
9
9
|
const challengeHandler = defineEventHandler(async (event) => {
|
|
10
|
+
if (!deriveKey || !createChallengeParameters) {
|
|
11
|
+
throw new HTTPError({
|
|
12
|
+
message: 'deriveKey and createChallengeParameters are required to generate challenges. Omit challengeHandler when relying on Sentinel to issue challenges.',
|
|
13
|
+
status: 500,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
10
16
|
setResponseHeader(event, 'Cache-Control', 'no-store');
|
|
11
17
|
return {
|
|
12
18
|
configuration: setCookie
|
|
@@ -23,7 +29,7 @@ export function create(options) {
|
|
|
23
29
|
};
|
|
24
30
|
});
|
|
25
31
|
const verifyHandler = defineEventHandler(async (event) => {
|
|
26
|
-
return await verify(await getPayloadFromEvent(event), deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
|
|
32
|
+
return await verify(await getPayloadFromEvent(event), deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
|
|
27
33
|
});
|
|
28
34
|
const getPayloadFromEvent = async (event, cookieName) => {
|
|
29
35
|
let payload = undefined;
|
|
@@ -54,7 +60,7 @@ export function create(options) {
|
|
|
54
60
|
const middleware = (options = {}) => {
|
|
55
61
|
const { throwOnFailure = true } = options;
|
|
56
62
|
return defineEventHandler(async (event) => {
|
|
57
|
-
const { error, payload, verification } = await verify(await getPayloadFromEvent(event, setCookie?.name), deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
|
|
63
|
+
const { error, payload, verification } = await verify(await getPayloadFromEvent(event, setCookie?.name), deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
|
|
58
64
|
event.context.altcha = {
|
|
59
65
|
error,
|
|
60
66
|
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").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").ContentfulStatusCode, "json">>;
|
|
89
107
|
getPayloadFromContext: (c: Context, cookieName?: string) => Promise<string | undefined>;
|
|
@@ -6,8 +6,13 @@ import { CappedMap } from '../capped-map.js';
|
|
|
6
6
|
import { deriveHmacKeySecret, verify } from './shared.js';
|
|
7
7
|
export { CappedMap, deriveHmacKeySecret, randomInt };
|
|
8
8
|
export function create(options) {
|
|
9
|
-
const { createChallengeParameters, deriveKey, fieldName = 'altcha', hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, } = options;
|
|
9
|
+
const { createChallengeParameters, deriveKey, fieldName = 'altcha', hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, verifyServer: verifyServerOptions, } = options;
|
|
10
10
|
const challengeHandler = async (c) => {
|
|
11
|
+
if (!deriveKey || !createChallengeParameters) {
|
|
12
|
+
throw new HTTPException(500, {
|
|
13
|
+
message: 'deriveKey and createChallengeParameters are required to generate challenges. Omit challengeHandler when relying on Sentinel to issue challenges.',
|
|
14
|
+
});
|
|
15
|
+
}
|
|
11
16
|
c.header('Cache-Control', 'no-store');
|
|
12
17
|
return c.json({
|
|
13
18
|
configuration: setCookie
|
|
@@ -24,7 +29,7 @@ export function create(options) {
|
|
|
24
29
|
});
|
|
25
30
|
};
|
|
26
31
|
const verifyHandler = async (c) => {
|
|
27
|
-
return c.json(await verify(await getPayloadFromContext(c), deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store));
|
|
32
|
+
return c.json(await verify(await getPayloadFromContext(c), deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions));
|
|
28
33
|
};
|
|
29
34
|
const getPayloadFromContext = async (c, cookieName) => {
|
|
30
35
|
let payload = undefined;
|
|
@@ -48,7 +53,7 @@ export function create(options) {
|
|
|
48
53
|
const middleware = (options = {}) => {
|
|
49
54
|
const { throwOnFailure = true } = options;
|
|
50
55
|
return async (c, next) => {
|
|
51
|
-
const { error, payload, verification } = await verify(await getPayloadFromContext(c, setCookie?.name), deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
|
|
56
|
+
const { error, payload, verification } = await verify(await getPayloadFromContext(c, setCookie?.name), deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
|
|
52
57
|
c.set('altcha', {
|
|
53
58
|
error,
|
|
54
59
|
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 {
|
|
@@ -61,6 +61,7 @@ let AltchaService = class AltchaService {
|
|
|
61
61
|
fieldName;
|
|
62
62
|
setCookieOptions;
|
|
63
63
|
store;
|
|
64
|
+
verifyServerOptions;
|
|
64
65
|
constructor(options) {
|
|
65
66
|
this.hmacSignatureSecret = options.hmacSignatureSecret;
|
|
66
67
|
this.hmacKeySignatureSecret = options.hmacKeySignatureSecret;
|
|
@@ -69,16 +70,21 @@ let AltchaService = class AltchaService {
|
|
|
69
70
|
this.fieldName = options.fieldName || 'altcha';
|
|
70
71
|
this.setCookieOptions = options.setCookie;
|
|
71
72
|
this.store = options.store;
|
|
73
|
+
this.verifyServerOptions = options.verifyServer;
|
|
72
74
|
}
|
|
73
75
|
get setCookie() {
|
|
74
76
|
return this.setCookieOptions;
|
|
75
77
|
}
|
|
76
78
|
async getChallenge() {
|
|
79
|
+
const { createChallengeParameters, deriveKey } = this;
|
|
80
|
+
if (!deriveKey || !createChallengeParameters) {
|
|
81
|
+
throw new HttpException('deriveKey and createChallengeParameters are required to generate challenges. Omit the /challenge route when relying on Sentinel to issue challenges.', HttpStatus.INTERNAL_SERVER_ERROR);
|
|
82
|
+
}
|
|
77
83
|
const challenge = await createChallenge({
|
|
78
|
-
deriveKey
|
|
84
|
+
deriveKey,
|
|
79
85
|
hmacSignatureSecret: this.hmacSignatureSecret,
|
|
80
86
|
hmacKeySignatureSecret: this.hmacKeySignatureSecret,
|
|
81
|
-
...
|
|
87
|
+
...createChallengeParameters(),
|
|
82
88
|
});
|
|
83
89
|
return {
|
|
84
90
|
configuration: this.setCookieOptions
|
|
@@ -94,7 +100,7 @@ let AltchaService = class AltchaService {
|
|
|
94
100
|
return req.body?.[this.fieldName];
|
|
95
101
|
}
|
|
96
102
|
async verify(payload) {
|
|
97
|
-
return verify(payload, this.deriveKey, this.hmacSignatureSecret, this.hmacKeySignatureSecret, this.store);
|
|
103
|
+
return verify(payload, this.deriveKey, this.hmacSignatureSecret, this.hmacKeySignatureSecret, this.store, this.verifyServerOptions);
|
|
98
104
|
}
|
|
99
105
|
};
|
|
100
106
|
AltchaService = __decorate([
|
|
@@ -15,9 +15,12 @@ function deleteCookie(res, name, path) {
|
|
|
15
15
|
res.headers.append('Set-Cookie', `${name}=; Path=${path ?? '/'}; Max-Age=0`);
|
|
16
16
|
}
|
|
17
17
|
export function create(options) {
|
|
18
|
-
const { createChallengeParameters, deriveKey, fieldName = 'altcha', hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, } = options;
|
|
18
|
+
const { createChallengeParameters, deriveKey, fieldName = 'altcha', hmacSignatureSecret, hmacKeySignatureSecret, setCookie, store, verifyServer: verifyServerOptions, } = options;
|
|
19
19
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
20
20
|
async function challengeHandler(_req) {
|
|
21
|
+
if (!deriveKey || !createChallengeParameters) {
|
|
22
|
+
throw new Error('deriveKey and createChallengeParameters are required to generate challenges. Omit challengeHandler when relying on Sentinel to issue challenges.');
|
|
23
|
+
}
|
|
21
24
|
const challenge = await createChallenge({
|
|
22
25
|
deriveKey,
|
|
23
26
|
hmacSignatureSecret,
|
|
@@ -39,7 +42,7 @@ export function create(options) {
|
|
|
39
42
|
}
|
|
40
43
|
async function verifyHandler(req) {
|
|
41
44
|
const payload = await getPayloadFromRequest(req);
|
|
42
|
-
const result = await verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
|
|
45
|
+
const result = await verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
|
|
43
46
|
return Response.json(result);
|
|
44
47
|
}
|
|
45
48
|
async function getPayloadFromRequest(req, cookieName) {
|
|
@@ -63,7 +66,7 @@ export function create(options) {
|
|
|
63
66
|
}
|
|
64
67
|
async function middleware(req, throwOnFailure = true) {
|
|
65
68
|
const payload = await getPayloadFromRequest(req, setCookie?.name);
|
|
66
|
-
const { error, payload: verifiedPayload, verification, } = await verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
|
|
69
|
+
const { error, payload: verifiedPayload, verification, } = await verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
|
|
67
70
|
const result = {
|
|
68
71
|
error,
|
|
69
72
|
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
|
}>;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { verifySolution } from '../pow.js';
|
|
2
2
|
import { verifyServerSignature } from '../server-signature.js';
|
|
3
|
+
import { verifyServer } from '../verify-server.js';
|
|
3
4
|
import { bufferToHex, hmac } from '../helpers.js';
|
|
4
5
|
import { HmacAlgorithm, } from '../types.js';
|
|
5
6
|
export async function deriveHmacKeySecret(masterSecret) {
|
|
6
7
|
return bufferToHex(await hmac(HmacAlgorithm.SHA_256, masterSecret, 'derived-secret'));
|
|
7
8
|
}
|
|
8
|
-
export async function verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store) {
|
|
9
|
+
export async function verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions) {
|
|
9
10
|
if (!payload) {
|
|
10
11
|
return {
|
|
11
12
|
error: 'ALTCHA payload is missing.',
|
|
@@ -29,6 +30,12 @@ export async function verify(payload, deriveKey, hmacSignatureSecret, hmacKeySig
|
|
|
29
30
|
try {
|
|
30
31
|
switch (type) {
|
|
31
32
|
case 'client':
|
|
33
|
+
if (!deriveKey) {
|
|
34
|
+
throw new Error('deriveKey is required to verify self-hosted ALTCHA challenges.');
|
|
35
|
+
}
|
|
36
|
+
if (!hmacSignatureSecret) {
|
|
37
|
+
throw new Error('hmacSignatureSecret is required to verify self-hosted ALTCHA challenges.');
|
|
38
|
+
}
|
|
32
39
|
challengeId = getChallengeId(payload);
|
|
33
40
|
if (store && challengeId) {
|
|
34
41
|
await checkChallengeId(store, challengeId);
|
|
@@ -40,7 +47,18 @@ export async function verify(payload, deriveKey, hmacSignatureSecret, hmacKeySig
|
|
|
40
47
|
if (store && challengeId) {
|
|
41
48
|
await checkChallengeId(store, challengeId);
|
|
42
49
|
}
|
|
43
|
-
|
|
50
|
+
if (verifyServerOptions) {
|
|
51
|
+
verification = await verifyServer({
|
|
52
|
+
...verifyServerOptions,
|
|
53
|
+
payload: payload,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
if (!hmacSignatureSecret) {
|
|
58
|
+
throw new Error('hmacSignatureSecret or verifyServer must be configured to verify this payload.');
|
|
59
|
+
}
|
|
60
|
+
verification = await verifyServerSignaturePayload(payload, hmacSignatureSecret);
|
|
61
|
+
}
|
|
44
62
|
break;
|
|
45
63
|
default:
|
|
46
64
|
throw new Error('ALTCHA payload is invalid.');
|
|
@@ -55,7 +73,8 @@ export async function verify(payload, deriveKey, hmacSignatureSecret, hmacKeySig
|
|
|
55
73
|
}
|
|
56
74
|
if (!verification?.verified) {
|
|
57
75
|
return {
|
|
58
|
-
error: '
|
|
76
|
+
error: (verification && 'reason' in verification && verification.reason) ||
|
|
77
|
+
'ALTCHA verification failed.',
|
|
59
78
|
payload: payload,
|
|
60
79
|
verification,
|
|
61
80
|
};
|
|
@@ -21,8 +21,11 @@ async function getPayloadFromEvent(event, fieldName, cookieName) {
|
|
|
21
21
|
return body?.[fieldName];
|
|
22
22
|
}
|
|
23
23
|
export 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
|
async function challengeHandler() {
|
|
26
|
+
if (!deriveKey || !createChallengeParameters) {
|
|
27
|
+
error(500, 'deriveKey and createChallengeParameters are required to generate challenges. Omit challengeHandler when relying on Sentinel to issue challenges.');
|
|
28
|
+
}
|
|
26
29
|
const challenge = await createChallenge({
|
|
27
30
|
deriveKey,
|
|
28
31
|
hmacSignatureSecret,
|
|
@@ -40,14 +43,14 @@ export function create(options) {
|
|
|
40
43
|
}
|
|
41
44
|
async function verifyHandler(event) {
|
|
42
45
|
const payload = await getPayloadFromEvent(event, fieldName);
|
|
43
|
-
const result = await verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
|
|
46
|
+
const result = await verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
|
|
44
47
|
return json(result);
|
|
45
48
|
}
|
|
46
49
|
function createHandle(middlewareOptions = {}) {
|
|
47
50
|
const { throwOnFailure = true } = middlewareOptions;
|
|
48
51
|
return async ({ event, resolve }) => {
|
|
49
52
|
const payload = await getPayloadFromEvent(event, fieldName, setCookie?.name);
|
|
50
|
-
const { error: verifyError, payload: resultPayload, verification, } = await verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
|
|
53
|
+
const { error: verifyError, payload: resultPayload, verification, } = await verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
|
|
51
54
|
event.locals.altcha = {
|
|
52
55
|
error: verifyError,
|
|
53
56
|
payload: resultPayload,
|
|
@@ -66,7 +69,7 @@ export function create(options) {
|
|
|
66
69
|
}
|
|
67
70
|
async function verifyEvent(event) {
|
|
68
71
|
const payload = await getPayloadFromEvent(event, fieldName, setCookie?.name);
|
|
69
|
-
const { error: verifyError, payload: resultPayload, verification, } = await verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store);
|
|
72
|
+
const { error: verifyError, payload: resultPayload, verification, } = await verify(payload, deriveKey, hmacSignatureSecret, hmacKeySignatureSecret, store, verifyServerOptions);
|
|
70
73
|
if (setCookie) {
|
|
71
74
|
event.cookies.delete(setCookie.name, {
|
|
72
75
|
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
|
|
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
|
|
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
|
|
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 {
|
package/dist/esm/v2/index.d.ts
CHANGED
|
@@ -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
|
};
|
package/dist/esm/v2/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
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
|
-
export { CappedMap, HmacAlgorithm, createChallenge, randomInt, solveChallenge, solveChallengeWorkers, verifyFieldsHash, verifyServerSignature, verifySolution, };
|
|
7
|
+
export { CappedMap, HmacAlgorithm, createChallenge, randomInt, solveChallenge, solveChallengeWorkers, verifyFieldsHash, verifyServer, verifyServerSignature, verifySolution, };
|
|
7
8
|
export default {
|
|
8
9
|
CappedMap,
|
|
9
10
|
HmacAlgorithm,
|
|
@@ -12,6 +13,7 @@ export default {
|
|
|
12
13
|
solveChallenge,
|
|
13
14
|
solveChallengeWorkers,
|
|
14
15
|
verifyFieldsHash,
|
|
16
|
+
verifyServer,
|
|
15
17
|
verifyServerSignature,
|
|
16
18
|
verifySolution,
|
|
17
19
|
};
|
package/dist/esm/v2/types.d.ts
CHANGED
|
@@ -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
|