altcha-lib 2.2.0 → 2.3.1
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 +34 -0
- package/dist/cjs/v2/frameworks/express.js +5 -2
- package/dist/cjs/v2/frameworks/fastify.js +5 -2
- package/dist/cjs/v2/frameworks/h3.d.ts +1 -1
- package/dist/cjs/v2/frameworks/h3.js +8 -2
- package/dist/cjs/v2/frameworks/hono.d.ts +18 -0
- package/dist/cjs/v2/frameworks/hono.js +7 -2
- package/dist/cjs/v2/frameworks/nestjs.d.ts +8 -5
- package/dist/cjs/v2/frameworks/nestjs.js +11 -6
- package/dist/cjs/v2/frameworks/nextjs.js +5 -2
- 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 +6 -3
- 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 +5 -2
- package/dist/esm/v2/frameworks/fastify.js +5 -2
- package/dist/esm/v2/frameworks/h3.d.ts +1 -1
- package/dist/esm/v2/frameworks/h3.js +8 -2
- package/dist/esm/v2/frameworks/hono.d.ts +18 -0
- package/dist/esm/v2/frameworks/hono.js +7 -2
- package/dist/esm/v2/frameworks/nestjs.d.ts +8 -5
- package/dist/esm/v2/frameworks/nestjs.js +12 -6
- package/dist/esm/v2/frameworks/nextjs.js +5 -2
- 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 +6 -3
- 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
|
@@ -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,
|
|
@@ -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,
|
|
@@ -52,7 +55,7 @@ export function create(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
|
|
@@ -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
|
|
@@ -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<{
|
|
@@ -40,10 +41,12 @@ export declare class AltchaService {
|
|
|
40
41
|
} | undefined;
|
|
41
42
|
}>;
|
|
42
43
|
getPayloadFromRequest(req: Request, cookieName?: string): string | undefined;
|
|
43
|
-
verify(payload: string | undefined
|
|
44
|
+
verify(payload: string | undefined, options?: {
|
|
45
|
+
allowRemote?: boolean;
|
|
46
|
+
}): Promise<{
|
|
44
47
|
error: string | null;
|
|
45
48
|
payload: import("../types.js").Payload | import("../types.js").ServerSignaturePayload | null;
|
|
46
|
-
verification: import("../types.js").VerifySolutionResult | null;
|
|
49
|
+
verification: import("../types.js").VerifySolutionResult | import("../types.js").VerifyServerResult | null;
|
|
47
50
|
}>;
|
|
48
51
|
}
|
|
49
52
|
export declare class AltchaController {
|
|
@@ -60,7 +63,7 @@ export declare class AltchaController {
|
|
|
60
63
|
verifySolution(req: Request): Promise<{
|
|
61
64
|
error: string | null;
|
|
62
65
|
payload: import("../types.js").Payload | import("../types.js").ServerSignaturePayload | null;
|
|
63
|
-
verification: import("../types.js").VerifySolutionResult | null;
|
|
66
|
+
verification: import("../types.js").VerifySolutionResult | import("../types.js").VerifyServerResult | null;
|
|
64
67
|
}>;
|
|
65
68
|
}
|
|
66
69
|
export declare class AltchaMiddleware implements NestMiddleware {
|
|
@@ -31,7 +31,7 @@ export function createAltchaMiddleware(options = {}) {
|
|
|
31
31
|
}
|
|
32
32
|
async use(req, res, next) {
|
|
33
33
|
const payload = this.altchaService.getPayloadFromRequest(req);
|
|
34
|
-
const { error, payload: resultPayload, verification, } = await this.altchaService.verify(payload);
|
|
34
|
+
const { error, payload: resultPayload, verification, } = await this.altchaService.verify(payload, { allowRemote: true });
|
|
35
35
|
req.altcha = {
|
|
36
36
|
error,
|
|
37
37
|
payload: resultPayload,
|
|
@@ -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
|
|
@@ -93,8 +99,8 @@ let AltchaService = class AltchaService {
|
|
|
93
99
|
}
|
|
94
100
|
return req.body?.[this.fieldName];
|
|
95
101
|
}
|
|
96
|
-
async verify(payload) {
|
|
97
|
-
return verify(payload, this.deriveKey, this.hmacSignatureSecret, this.hmacKeySignatureSecret, this.store);
|
|
102
|
+
async verify(payload, options = {}) {
|
|
103
|
+
return verify(payload, this.deriveKey, this.hmacSignatureSecret, this.hmacKeySignatureSecret, this.store, options.allowRemote ? this.verifyServerOptions : undefined);
|
|
98
104
|
}
|
|
99
105
|
};
|
|
100
106
|
AltchaService = __decorate([
|
|
@@ -142,7 +148,7 @@ let AltchaMiddleware = class AltchaMiddleware {
|
|
|
142
148
|
}
|
|
143
149
|
async use(req, res, next) {
|
|
144
150
|
const payload = this.altchaService.getPayloadFromRequest(req, this.altchaService.setCookie?.name);
|
|
145
|
-
const { error, payload: resultPayload, verification, } = await this.altchaService.verify(payload);
|
|
151
|
+
const { error, payload: resultPayload, verification, } = await this.altchaService.verify(payload, { allowRemote: true });
|
|
146
152
|
req.altcha = {
|
|
147
153
|
error,
|
|
148
154
|
payload: resultPayload,
|
|
@@ -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,
|
|
@@ -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,
|
|
@@ -47,7 +50,7 @@ export function create(options) {
|
|
|
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
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { delay } from './helpers.js';
|
|
2
|
+
/** Verifies a payload remotely via the ALTCHA Sentinel `/v1/verify/signature` API. */
|
|
3
|
+
export async function verifyServer(options) {
|
|
4
|
+
const { controller, fetch: fetchImpl = fetch, headers, payload, retries = 0, retryBackoff = 'exponential', retryDelay = 300, secret, timeout = 10_000, url, } = options;
|
|
5
|
+
const body = JSON.stringify({ payload, secret });
|
|
6
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
7
|
+
if (controller?.signal.aborted) {
|
|
8
|
+
return { verified: false, reason: 'ABORTED' };
|
|
9
|
+
}
|
|
10
|
+
const attemptController = new AbortController();
|
|
11
|
+
const onAbort = () => attemptController.abort();
|
|
12
|
+
controller?.signal.addEventListener('abort', onAbort);
|
|
13
|
+
const timer = setTimeout(() => attemptController.abort(), timeout);
|
|
14
|
+
try {
|
|
15
|
+
const response = await fetchImpl(url, {
|
|
16
|
+
body,
|
|
17
|
+
headers: {
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
...headers,
|
|
20
|
+
},
|
|
21
|
+
method: 'POST',
|
|
22
|
+
signal: attemptController.signal,
|
|
23
|
+
});
|
|
24
|
+
if (response.status === 400) {
|
|
25
|
+
const data = await response
|
|
26
|
+
.json()
|
|
27
|
+
.catch(() => null);
|
|
28
|
+
return {
|
|
29
|
+
verified: false,
|
|
30
|
+
reason: data?.error ?? `HTTP_${response.status}`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
throw new Error(`HTTP_${response.status}`);
|
|
35
|
+
}
|
|
36
|
+
return (await response.json());
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
if (attempt >= retries) {
|
|
40
|
+
return {
|
|
41
|
+
verified: false,
|
|
42
|
+
reason: err instanceof Error ? err.message : 'NETWORK_ERROR',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const backoffDelay = retryBackoff === 'fixed' ? retryDelay : retryDelay * 2 ** attempt;
|
|
46
|
+
await delay(backoffDelay);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
controller?.signal.removeEventListener('abort', onAbort);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { verified: false, reason: 'NETWORK_ERROR' };
|
|
54
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/v1/helpers.ts","../src/v1/index.ts","../src/v1/types.ts","../src/v1/worker.ts","../src/v2/capped-map.ts","../src/v2/helpers.ts","../src/v2/index.ts","../src/v2/obfuscation.ts","../src/v2/pow.ts","../src/v2/server-signature.ts","../src/v2/types.ts","../src/v2/algorithms/argon2id.ts","../src/v2/algorithms/pbkdf2.ts","../src/v2/algorithms/scrypt.ts","../src/v2/algorithms/sha.ts","../src/v2/algorithms/web/pbkdf2.ts","../src/v2/algorithms/web/sha.ts","../src/v2/frameworks/express.ts","../src/v2/frameworks/fastify.ts","../src/v2/frameworks/h3.ts","../src/v2/frameworks/hono.ts","../src/v2/frameworks/nestjs.ts","../src/v2/frameworks/nextjs.ts","../src/v2/frameworks/shared.ts","../src/v2/frameworks/sveltekit.ts","../src/v2/frameworks/types.ts","../src/v2/workers/argon2id.ts","../src/v2/workers/pbkdf2.ts","../src/v2/workers/scrypt.ts","../src/v2/workers/sha.ts","../src/v2/workers/shared.ts"],"version":"5.9.3"}
|
|
1
|
+
{"root":["../src/v1/helpers.ts","../src/v1/index.ts","../src/v1/types.ts","../src/v1/worker.ts","../src/v2/capped-map.ts","../src/v2/helpers.ts","../src/v2/index.ts","../src/v2/obfuscation.ts","../src/v2/pow.ts","../src/v2/server-signature.ts","../src/v2/types.ts","../src/v2/verify-server.ts","../src/v2/algorithms/argon2id.ts","../src/v2/algorithms/pbkdf2.ts","../src/v2/algorithms/scrypt.ts","../src/v2/algorithms/sha.ts","../src/v2/algorithms/web/pbkdf2.ts","../src/v2/algorithms/web/sha.ts","../src/v2/frameworks/express.ts","../src/v2/frameworks/fastify.ts","../src/v2/frameworks/h3.ts","../src/v2/frameworks/hono.ts","../src/v2/frameworks/nestjs.ts","../src/v2/frameworks/nextjs.ts","../src/v2/frameworks/shared.ts","../src/v2/frameworks/sveltekit.ts","../src/v2/frameworks/types.ts","../src/v2/workers/argon2id.ts","../src/v2/workers/pbkdf2.ts","../src/v2/workers/scrypt.ts","../src/v2/workers/sha.ts","../src/v2/workers/shared.ts"],"version":"5.9.3"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/v1/helpers.ts","../src/v1/index.ts","../src/v1/types.ts","../src/v1/worker.ts","../src/v2/capped-map.ts","../src/v2/helpers.ts","../src/v2/index.ts","../src/v2/obfuscation.ts","../src/v2/pow.ts","../src/v2/server-signature.ts","../src/v2/types.ts","../src/v2/algorithms/argon2id.ts","../src/v2/algorithms/pbkdf2.ts","../src/v2/algorithms/scrypt.ts","../src/v2/algorithms/sha.ts","../src/v2/algorithms/web/pbkdf2.ts","../src/v2/algorithms/web/sha.ts","../src/v2/frameworks/express.ts","../src/v2/frameworks/fastify.ts","../src/v2/frameworks/h3.ts","../src/v2/frameworks/hono.ts","../src/v2/frameworks/nestjs.ts","../src/v2/frameworks/nextjs.ts","../src/v2/frameworks/shared.ts","../src/v2/frameworks/sveltekit.ts","../src/v2/frameworks/types.ts","../src/v2/workers/argon2id.ts","../src/v2/workers/pbkdf2.ts","../src/v2/workers/scrypt.ts","../src/v2/workers/sha.ts","../src/v2/workers/shared.ts"],"version":"5.9.3"}
|
|
1
|
+
{"root":["../src/v1/helpers.ts","../src/v1/index.ts","../src/v1/types.ts","../src/v1/worker.ts","../src/v2/capped-map.ts","../src/v2/helpers.ts","../src/v2/index.ts","../src/v2/obfuscation.ts","../src/v2/pow.ts","../src/v2/server-signature.ts","../src/v2/types.ts","../src/v2/verify-server.ts","../src/v2/algorithms/argon2id.ts","../src/v2/algorithms/pbkdf2.ts","../src/v2/algorithms/scrypt.ts","../src/v2/algorithms/sha.ts","../src/v2/algorithms/web/pbkdf2.ts","../src/v2/algorithms/web/sha.ts","../src/v2/frameworks/express.ts","../src/v2/frameworks/fastify.ts","../src/v2/frameworks/h3.ts","../src/v2/frameworks/hono.ts","../src/v2/frameworks/nestjs.ts","../src/v2/frameworks/nextjs.ts","../src/v2/frameworks/shared.ts","../src/v2/frameworks/sveltekit.ts","../src/v2/frameworks/types.ts","../src/v2/workers/argon2id.ts","../src/v2/workers/pbkdf2.ts","../src/v2/workers/scrypt.ts","../src/v2/workers/sha.ts","../src/v2/workers/shared.ts"],"version":"5.9.3"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "altcha-lib",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "A lightweight library for creating and verifying ALTCHA challenges on the server.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Daniel Regeci",
|
|
@@ -184,6 +184,8 @@
|
|
|
184
184
|
},
|
|
185
185
|
"overrides": {
|
|
186
186
|
"cookie": "^1.1.1",
|
|
187
|
-
|
|
187
|
+
"esbuild": "^0.28.1",
|
|
188
|
+
"file-type": "^21.3.1",
|
|
189
|
+
"multer": "^2.2.0"
|
|
188
190
|
}
|
|
189
191
|
}
|