@vlayer/sdk 0.1.0-nightly-20241028-591419e → 0.1.0-nightly-202410292-42360cf
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/api/email/dnsResolver.d.ts +1 -0
- package/dist/api/email/dnsResolver.js +6 -0
- package/dist/api/email/dnsResolver.test.d.ts +1 -0
- package/dist/api/email/dnsResolver.test.js +12 -0
- package/dist/api/email/parseEmail.d.ts +10 -0
- package/dist/api/email/parseEmail.js +38 -0
- package/dist/api/email/parseEmail.test.d.ts +1 -0
- package/dist/api/email/parseEmail.test.js +91 -0
- package/dist/api/email/preverify.d.ts +4 -0
- package/dist/api/email/preverify.js +18 -0
- package/dist/api/email/preverify.test.d.ts +1 -0
- package/dist/api/email/preverify.test.js +25 -0
- package/dist/api/helpers.d.ts +38 -0
- package/dist/api/helpers.js +72 -0
- package/dist/api/lib/client.d.ts +6 -0
- package/dist/api/lib/client.js +45 -0
- package/dist/api/lib/types/ethereum.d.ts +14 -0
- package/dist/api/lib/types/ethereum.js +12 -0
- package/dist/api/lib/types/index.js +3 -0
- package/dist/api/lib/types/viem.d.ts +8 -0
- package/dist/api/lib/types/viem.js +1 -0
- package/dist/api/lib/types/vlayer.d.ts +47 -0
- package/dist/api/lib/types/vlayer.js +1 -0
- package/dist/api/lib/types/webProofProvider.d.ts +29 -0
- package/dist/api/lib/types/webProofProvider.js +1 -0
- package/dist/api/prover.d.ts +2 -0
- package/dist/api/prover.js +15 -0
- package/dist/api/v_call.d.ts +2 -0
- package/dist/api/v_call.js +30 -0
- package/dist/api/webProof/createWebProof.d.ts +2 -0
- package/dist/api/webProof/createWebProof.js +7 -0
- package/dist/api/webProof/index.js +3 -0
- package/dist/api/webProof/providers/extension.d.ts +2 -0
- package/dist/api/webProof/providers/extension.js +32 -0
- package/dist/api/webProof/providers/index.js +1 -0
- package/dist/api/webProof/steps/expectUrl.d.ts +2 -0
- package/dist/api/webProof/steps/expectUrl.js +8 -0
- package/dist/api/webProof/steps/index.d.ts +9 -0
- package/dist/api/webProof/steps/index.js +9 -0
- package/dist/api/webProof/steps/notarize.d.ts +2 -0
- package/dist/api/webProof/steps/notarize.js +9 -0
- package/dist/api/webProof/steps/startPage.d.ts +2 -0
- package/dist/api/webProof/steps/startPage.js +8 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/testHelpers/readFile.d.ts +1 -0
- package/dist/testHelpers/readFile.js +2 -0
- package/dist/web-proof-commons/index.d.ts +3 -0
- package/dist/web-proof-commons/index.js +3 -0
- package/dist/web-proof-commons/types/message.d.ts +56 -0
- package/dist/web-proof-commons/types/message.js +5 -0
- package/dist/web-proof-commons/types/webProof.d.ts +86 -0
- package/dist/web-proof-commons/types/webProof.js +10 -0
- package/dist/web-proof-commons/utils.d.ts +7 -0
- package/dist/web-proof-commons/utils.js +5 -0
- package/package.json +16 -7
- package/.changeset/config.json +0 -11
- package/CHANGELOG.md +0 -7
- package/eslint.config.ts +0 -22
- package/src/api/email/dnsResolver.test.ts +0 -18
- package/src/api/email/dnsResolver.ts +0 -7
- package/src/api/email/parseEmail.test.ts +0 -133
- package/src/api/email/parseEmail.ts +0 -49
- package/src/api/email/preverify.test.ts +0 -37
- package/src/api/email/preverify.ts +0 -19
- package/src/api/email/testdata/test_email.txt +0 -21
- package/src/api/email/testdata/test_email_multiple_dkims.txt +0 -28
- package/src/api/email/testdata/test_email_unknown_domain.txt +0 -21
- package/src/api/helpers.ts +0 -173
- package/src/api/lib/client.ts +0 -76
- package/src/api/lib/types/ethereum.ts +0 -43
- package/src/api/lib/types/viem.ts +0 -28
- package/src/api/lib/types/vlayer.ts +0 -60
- package/src/api/lib/types/webProofProvider.ts +0 -44
- package/src/api/prover.ts +0 -34
- package/src/api/v_call.ts +0 -45
- package/src/api/webProof/createWebProof.ts +0 -9
- package/src/api/webProof/providers/extension.ts +0 -72
- package/src/api/webProof/steps/expectUrl.ts +0 -12
- package/src/api/webProof/steps/index.ts +0 -11
- package/src/api/webProof/steps/notarize.ts +0 -17
- package/src/api/webProof/steps/startPage.ts +0 -12
- package/src/index.ts +0 -9
- package/src/testHelpers/readFile.ts +0 -3
- package/src/web-proof-commons/index.ts +0 -3
- package/src/web-proof-commons/types/message.ts +0 -73
- package/src/web-proof-commons/types/webProof.ts +0 -111
- package/src/web-proof-commons/utils.ts +0 -12
- package/tsconfig.json +0 -24
- package/vite.config.ts +0 -7
- /package/{src/api/lib/types/index.ts → dist/api/lib/types/index.d.ts} +0 -0
- /package/{src/api/webProof/index.ts → dist/api/webProof/index.d.ts} +0 -0
- /package/{src/api/webProof/providers/index.ts → dist/api/webProof/providers/index.d.ts} +0 -0
@@ -0,0 +1,9 @@
|
|
1
|
+
import { expectUrl } from "./expectUrl";
|
2
|
+
import { startPage } from "./startPage";
|
3
|
+
import { notarize } from "./notarize";
|
4
|
+
declare const steps: {
|
5
|
+
expectUrl: (url: string, label: string) => import("../../../web-proof-commons").WebProofStepExpectUrl;
|
6
|
+
startPage: (url: string, label: string) => import("../../../web-proof-commons").WebProofStepStartPage;
|
7
|
+
notarize: (url: string, method: string | undefined, label: string) => import("../../../web-proof-commons").WebProofStepNotarize;
|
8
|
+
};
|
9
|
+
export { expectUrl, startPage, notarize, steps };
|
package/dist/index.d.ts
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
export * as testHelpers from "./api/helpers";
|
2
|
+
export { preverifyEmail } from "./api/email/preverify";
|
3
|
+
export { createVlayerClient } from "./api/lib/client";
|
4
|
+
export * from "./api/webProof";
|
5
|
+
export * from "./api/lib/types";
|
6
|
+
export * from "./web-proof-commons/types/webProof";
|
7
|
+
export * from "./web-proof-commons/utils";
|
package/dist/index.js
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
export * as testHelpers from "./api/helpers";
|
2
|
+
export { preverifyEmail } from "./api/email/preverify";
|
3
|
+
export { createVlayerClient } from "./api/lib/client";
|
4
|
+
export * from "./api/webProof";
|
5
|
+
export * from "./api/lib/types";
|
6
|
+
export * from "./web-proof-commons/types/webProof";
|
7
|
+
export * from "./web-proof-commons/utils";
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare const readFile: (path: string) => string;
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import type { Branded } from "../utils";
|
2
|
+
import type { WebProof } from "./webProof";
|
3
|
+
export declare const EXTENSION_STEP: {
|
4
|
+
readonly expectUrl: "expectUrl";
|
5
|
+
readonly startPage: "startPage";
|
6
|
+
readonly notarize: "notarize";
|
7
|
+
};
|
8
|
+
export type ExtensionStep = (typeof EXTENSION_STEP)[keyof typeof EXTENSION_STEP];
|
9
|
+
export declare const enum ExtensionAction {
|
10
|
+
RequestWebProof = 0
|
11
|
+
}
|
12
|
+
export type MessageToExtension = {
|
13
|
+
action: ExtensionAction;
|
14
|
+
payload: WebProverSessionConfig;
|
15
|
+
};
|
16
|
+
export declare const enum ExtensionMessageType {
|
17
|
+
ProofDone = "ProofDone",
|
18
|
+
ProofError = "ProofError",
|
19
|
+
RedirectBack = "RedirectBack",
|
20
|
+
TabOpened = "TabOpened"
|
21
|
+
}
|
22
|
+
export type ExtensionMessage = {
|
23
|
+
type: ExtensionMessageType.ProofDone;
|
24
|
+
proof: WebProof;
|
25
|
+
} | {
|
26
|
+
type: ExtensionMessageType.ProofError;
|
27
|
+
error: string;
|
28
|
+
} | {
|
29
|
+
type: ExtensionMessageType.RedirectBack;
|
30
|
+
} | {
|
31
|
+
type: ExtensionMessageType.TabOpened;
|
32
|
+
tabId: number;
|
33
|
+
};
|
34
|
+
export type WebProverSessionConfig = {
|
35
|
+
notaryUrl: string;
|
36
|
+
wsProxyUrl: string;
|
37
|
+
logoUrl: string;
|
38
|
+
steps: WebProofStep[];
|
39
|
+
};
|
40
|
+
export type WebProofStep = WebProofStepNotarize | WebProofStepExpectUrl | WebProofStepStartPage;
|
41
|
+
export type WebProofStepNotarize = Branded<{
|
42
|
+
url: string;
|
43
|
+
method: string;
|
44
|
+
label: string;
|
45
|
+
step: typeof EXTENSION_STEP.notarize;
|
46
|
+
}, "notarize">;
|
47
|
+
export type WebProofStepExpectUrl = Branded<{
|
48
|
+
url: string;
|
49
|
+
label: string;
|
50
|
+
step: typeof EXTENSION_STEP.expectUrl;
|
51
|
+
}, "expectUrl">;
|
52
|
+
export type WebProofStepStartPage = Branded<{
|
53
|
+
url: string;
|
54
|
+
label: string;
|
55
|
+
step: typeof EXTENSION_STEP.startPage;
|
56
|
+
}, "startPage">;
|
@@ -0,0 +1,86 @@
|
|
1
|
+
export interface WebProof {
|
2
|
+
session: Session;
|
3
|
+
substrings: Substrings;
|
4
|
+
notaryUrl: string;
|
5
|
+
}
|
6
|
+
export interface Session {
|
7
|
+
header: Header;
|
8
|
+
signature: Signature;
|
9
|
+
session_info: SessionInfo;
|
10
|
+
}
|
11
|
+
export interface SessionInfo {
|
12
|
+
server_name: ServerName;
|
13
|
+
handshake_decommitment: HandshakeDecommitment;
|
14
|
+
}
|
15
|
+
export interface HandshakeDecommitment {
|
16
|
+
nonce: number[];
|
17
|
+
data: Data;
|
18
|
+
}
|
19
|
+
export interface Data {
|
20
|
+
server_cert_details: ServerCERTDetails;
|
21
|
+
server_kx_details: ServerKxDetails;
|
22
|
+
client_random: number[];
|
23
|
+
server_random: number[];
|
24
|
+
}
|
25
|
+
export interface ServerCERTDetails {
|
26
|
+
cert_chain: Array<number[]>;
|
27
|
+
ocsp_response: number[];
|
28
|
+
scts: null;
|
29
|
+
}
|
30
|
+
export interface ServerKxDetails {
|
31
|
+
kx_params: number[];
|
32
|
+
kx_sig: KxSig;
|
33
|
+
}
|
34
|
+
export interface KxSig {
|
35
|
+
scheme: string;
|
36
|
+
sig: number[];
|
37
|
+
}
|
38
|
+
export interface Header {
|
39
|
+
encoder_seed: number[];
|
40
|
+
merkle_root: number[];
|
41
|
+
sent_len: number;
|
42
|
+
recv_len: number;
|
43
|
+
handshake_summary: HandshakeSummary;
|
44
|
+
}
|
45
|
+
export interface HandshakeSummary {
|
46
|
+
time: number;
|
47
|
+
server_public_key: ServerPublicKey;
|
48
|
+
handshake_commitment: number[];
|
49
|
+
}
|
50
|
+
export interface ServerPublicKey {
|
51
|
+
group: string;
|
52
|
+
key: number[];
|
53
|
+
}
|
54
|
+
export interface ServerName {
|
55
|
+
Dns: string;
|
56
|
+
}
|
57
|
+
export interface Signature {
|
58
|
+
P256: string;
|
59
|
+
}
|
60
|
+
export interface Substrings {
|
61
|
+
openings: {
|
62
|
+
[key: string]: Opening[];
|
63
|
+
};
|
64
|
+
inclusion_proof: InclusionProof;
|
65
|
+
}
|
66
|
+
export interface InclusionProof {
|
67
|
+
proof: unknown[];
|
68
|
+
total_leaves: number;
|
69
|
+
}
|
70
|
+
export interface Opening {
|
71
|
+
kind?: string;
|
72
|
+
ranges?: Range[];
|
73
|
+
direction?: string;
|
74
|
+
Blake3?: Blake3;
|
75
|
+
}
|
76
|
+
export interface Blake3 {
|
77
|
+
data: number[];
|
78
|
+
nonce: number[];
|
79
|
+
}
|
80
|
+
export interface Range {
|
81
|
+
start: number;
|
82
|
+
end: number;
|
83
|
+
}
|
84
|
+
export declare const assertWebProof: (candidate: {
|
85
|
+
notaryUrl?: string;
|
86
|
+
}) => asserts candidate is WebProof;
|
@@ -0,0 +1,10 @@
|
|
1
|
+
// NOTE : this is copied from tlsn-js 5.4
|
2
|
+
// for some reason newest versions doesn't export this type (clarification is in progress)
|
3
|
+
// probably it should be reexported from tlsn-js
|
4
|
+
export const assertWebProof = function (candidate) {
|
5
|
+
//for now only thing we check is notary url
|
6
|
+
//TODO: implement later once we known the conteact with tlsn-js
|
7
|
+
if (!candidate.notaryUrl) {
|
8
|
+
throw new Error("Missing required parameter");
|
9
|
+
}
|
10
|
+
};
|
package/package.json
CHANGED
@@ -1,13 +1,17 @@
|
|
1
1
|
{
|
2
2
|
"name": "@vlayer/sdk",
|
3
3
|
"type": "module",
|
4
|
-
"exports":
|
5
|
-
|
6
|
-
|
4
|
+
"exports": {
|
5
|
+
".": {
|
6
|
+
"import": "./dist/index.js",
|
7
|
+
"types": "./dist/index.d.ts"
|
8
|
+
}
|
9
|
+
},
|
10
|
+
"version": "0.1.0-nightly-202410292-42360cf",
|
7
11
|
"scripts": {
|
8
|
-
"build": "
|
12
|
+
"build": "bun tsc",
|
9
13
|
"test": "vitest --run",
|
10
|
-
"gen:types": "sh
|
14
|
+
"gen:types": "sh ../../bash/build_ts_types.sh"
|
11
15
|
},
|
12
16
|
"devDependencies": {
|
13
17
|
"@changesets/cli": "^2.27.7",
|
@@ -16,7 +20,7 @@
|
|
16
20
|
"vitest": "^2.1.1"
|
17
21
|
},
|
18
22
|
"peerDependencies": {
|
19
|
-
"typescript": "^5.
|
23
|
+
"typescript": "^5.6.3"
|
20
24
|
},
|
21
25
|
"dependencies": {
|
22
26
|
"dns-over-http-resolver": "^3.0.3",
|
@@ -24,5 +28,10 @@
|
|
24
28
|
"viem": "2.21.0",
|
25
29
|
"vite-tsconfig-paths": "^5.0.1",
|
26
30
|
"@vitejs/plugin-react": "^4.3.2"
|
27
|
-
}
|
31
|
+
},
|
32
|
+
"files": [
|
33
|
+
"dist/",
|
34
|
+
"package.json",
|
35
|
+
"README.md"
|
36
|
+
]
|
28
37
|
}
|
package/.changeset/config.json
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
{
|
2
|
-
"$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json",
|
3
|
-
"changelog": "@changesets/cli/changelog",
|
4
|
-
"commit": false,
|
5
|
-
"fixed": [],
|
6
|
-
"linked": [],
|
7
|
-
"access": "restricted",
|
8
|
-
"baseBranch": "main",
|
9
|
-
"updateInternalDependencies": "patch",
|
10
|
-
"ignore": []
|
11
|
-
}
|
package/CHANGELOG.md
DELETED
package/eslint.config.ts
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
import js from "@eslint/js";
|
2
|
-
import ts from "typescript-eslint";
|
3
|
-
import prettierRecommended from "eslint-plugin-prettier/recommended";
|
4
|
-
import globals from "globals";
|
5
|
-
|
6
|
-
export default [
|
7
|
-
js.configs.recommended,
|
8
|
-
...ts.configs.recommended,
|
9
|
-
prettierRecommended,
|
10
|
-
{
|
11
|
-
rules: {
|
12
|
-
"no-unused-vars": "warn",
|
13
|
-
"no-undef": "warn",
|
14
|
-
},
|
15
|
-
languageOptions: {
|
16
|
-
globals: {
|
17
|
-
...globals.browser,
|
18
|
-
Bun: false,
|
19
|
-
},
|
20
|
-
},
|
21
|
-
},
|
22
|
-
];
|
@@ -1,18 +0,0 @@
|
|
1
|
-
import { describe, expect, test } from "vitest";
|
2
|
-
import { resolveDkimDns } from "./dnsResolver.ts";
|
3
|
-
|
4
|
-
describe("resolveDkimDns Integration", () => {
|
5
|
-
test("resolves VLayer DNS", async () => {
|
6
|
-
const resolved = await resolveDkimDns(
|
7
|
-
"vlayer-xyz.20230601.gappssmtp.com",
|
8
|
-
"20230601",
|
9
|
-
);
|
10
|
-
const expected =
|
11
|
-
"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3gWcOhCm99qzN+h7/2+LeP3CLsJkQQ4EP/2mrceXle5pKq8uZmBl1U4d2Vxn4w+pWFANDLmcHolLboESLFqEL5N6ae7u9b236dW4zn9AFkXAGenTzQEeif9VUFtLAZ0Qh2eV7OQgz/vPj5IaNqJ7h9hpM9gO031fe4v+J0DLCE8Rgo7hXbNgJavctc0983DaCDQaznHZ44LZ6TtZv9TBs+QFvsy4+UCTfsuOtHzoEqOOuXsVXZKLP6B882XbEnBpXEF8QzV4J26HiAJFUbO3mAqZL2UeKC0hhzoIZqZXNG0BfuzOF0VLpDa18GYMUiu+LhEJPJO9D8zhzvQIHNrpGwIDAQAB";
|
12
|
-
expect(resolved).toBe(expected);
|
13
|
-
});
|
14
|
-
|
15
|
-
test("throws error if dns not found", async () => {
|
16
|
-
await expect(resolveDkimDns("not-a-domain.com", "abcd")).rejects.toThrow();
|
17
|
-
});
|
18
|
-
});
|
@@ -1,7 +0,0 @@
|
|
1
|
-
import DnsResolver from "dns-over-http-resolver";
|
2
|
-
|
3
|
-
export async function resolveDkimDns(domain: string, selector: string) {
|
4
|
-
const resolver = new DnsResolver();
|
5
|
-
const address = await resolver.resolveTxt(`${selector}._domainkey.${domain}`);
|
6
|
-
return address.flat()[0];
|
7
|
-
}
|
@@ -1,133 +0,0 @@
|
|
1
|
-
import { describe, expect, test } from "vitest";
|
2
|
-
import { getDkimSigners, parseEmail, parseParams } from "./parseEmail.ts";
|
3
|
-
|
4
|
-
const emailHeaders = `From: "John Doe" <john@d.oe>
|
5
|
-
To: "Jane Doe" <jane@d.oe>
|
6
|
-
Subject: Hello World
|
7
|
-
Date: Thu, 1 Jan 1970 00:00:00 +0000
|
8
|
-
`;
|
9
|
-
|
10
|
-
const dkimHeader =
|
11
|
-
"DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; h=from:to:subject; s=selector1; b=abcdef;";
|
12
|
-
|
13
|
-
const body = "Hello, World!";
|
14
|
-
|
15
|
-
const emailFixture = `${emailHeaders}${dkimHeader}\n\n${body}`;
|
16
|
-
|
17
|
-
describe("parseEmail", () => {
|
18
|
-
test("should get dkim header from email", async () => {});
|
19
|
-
|
20
|
-
test("correctly parses untrimmed email", async () => {
|
21
|
-
const untrimmed = `\n ${emailFixture} \n`;
|
22
|
-
const email = await parseEmail(untrimmed);
|
23
|
-
expect(email.headers.find((h) => h.key === "dkim-signature")).toBeDefined();
|
24
|
-
});
|
25
|
-
|
26
|
-
test("works well with multiple dkim headers", async () => {
|
27
|
-
const dkimHeader2 =
|
28
|
-
"DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=second.signer; h=from:to:subject; s=selector2; b=abcdef;";
|
29
|
-
|
30
|
-
const email = await parseEmail(
|
31
|
-
`${emailHeaders}${dkimHeader}\n${dkimHeader2}\n\n${body}`,
|
32
|
-
);
|
33
|
-
const dkim = email.headers.filter((h) => h.key === "dkim-signature");
|
34
|
-
|
35
|
-
expect(dkim).toHaveLength(2);
|
36
|
-
expect(parseParams(dkim[0].value).s).toBe("selector1");
|
37
|
-
expect(parseParams(dkim[1].value).s).toBe("selector2");
|
38
|
-
});
|
39
|
-
});
|
40
|
-
|
41
|
-
describe("getDkimSigners", () => {
|
42
|
-
test("should get dkim signers from email", async () => {
|
43
|
-
const email = await parseEmail(emailFixture);
|
44
|
-
const dkim = getDkimSigners(email);
|
45
|
-
expect(dkim).toHaveLength(1);
|
46
|
-
expect(dkim[0].domain).toBe("example.com");
|
47
|
-
expect(dkim[0].selector).toBe("selector1");
|
48
|
-
});
|
49
|
-
|
50
|
-
test("should get multiple dkim signers from email", async () => {
|
51
|
-
const dkimHeader2 =
|
52
|
-
"DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=second.signer; h=from:to:subject; s=selector2; b=abcdef;";
|
53
|
-
const email = await parseEmail(
|
54
|
-
`${emailHeaders}${dkimHeader}\n${dkimHeader2}\n\n${body}`,
|
55
|
-
);
|
56
|
-
|
57
|
-
const dkim = getDkimSigners(email);
|
58
|
-
expect(dkim).toHaveLength(2);
|
59
|
-
expect(dkim[0].domain).toBe("example.com");
|
60
|
-
expect(dkim[0].selector).toBe("selector1");
|
61
|
-
expect(dkim[1].domain).toBe("second.signer");
|
62
|
-
expect(dkim[1].selector).toBe("selector2");
|
63
|
-
});
|
64
|
-
|
65
|
-
test("should throw if no dkim header found", async () => {
|
66
|
-
const email = await parseEmail(emailHeaders);
|
67
|
-
expect(() => getDkimSigners(email)).toThrowError("No DKIM header found");
|
68
|
-
});
|
69
|
-
|
70
|
-
test("should throw if dkim header is invalid", async () => {
|
71
|
-
const email = await parseEmail(
|
72
|
-
`${emailHeaders}DKIM-Signature: invalid\n\n${body}`,
|
73
|
-
);
|
74
|
-
expect(() => getDkimSigners(email)).toThrowError(
|
75
|
-
"DKIM header missing domain",
|
76
|
-
);
|
77
|
-
});
|
78
|
-
|
79
|
-
test("should throw if dkim header is missing domain", async () => {
|
80
|
-
const email = await parseEmail(
|
81
|
-
`${emailHeaders}DKIM-Signature: v=1; s=selector\n\n${body}`,
|
82
|
-
);
|
83
|
-
expect(() => getDkimSigners(email)).toThrowError(
|
84
|
-
"DKIM header missing domain",
|
85
|
-
);
|
86
|
-
});
|
87
|
-
|
88
|
-
test("should throw if dkim header is missing selector", async () => {
|
89
|
-
const email = await parseEmail(
|
90
|
-
`${emailHeaders}DKIM-Signature: v=1; d=example.com\n\n${body}`,
|
91
|
-
);
|
92
|
-
expect(() => getDkimSigners(email)).toThrowError(
|
93
|
-
"DKIM header missing selector",
|
94
|
-
);
|
95
|
-
});
|
96
|
-
});
|
97
|
-
|
98
|
-
describe("parseParams", () => {
|
99
|
-
test("should parse single parameter", () => {
|
100
|
-
const params = parseParams("a=b");
|
101
|
-
expect(params).toEqual({ a: "b" });
|
102
|
-
});
|
103
|
-
|
104
|
-
test("should parse multiple parameters", () => {
|
105
|
-
const params = parseParams("a=b; c=d; e=f");
|
106
|
-
expect(params).toEqual({ a: "b", c: "d", e: "f" });
|
107
|
-
});
|
108
|
-
|
109
|
-
test("should trim spaces around parameters", () => {
|
110
|
-
const params = parseParams(" a = b ; c = d ; e = f ");
|
111
|
-
expect(params).toEqual({ a: "b", c: "d", e: "f" });
|
112
|
-
});
|
113
|
-
|
114
|
-
test("should handle empty values", () => {
|
115
|
-
const params = parseParams("a=; b=c");
|
116
|
-
expect(params).toEqual({ a: "", b: "c" });
|
117
|
-
});
|
118
|
-
|
119
|
-
test("should handle missing values", () => {
|
120
|
-
const params = parseParams("a; b=c");
|
121
|
-
expect(params).toEqual({ a: undefined, b: "c" });
|
122
|
-
});
|
123
|
-
|
124
|
-
test("should handle empty string", () => {
|
125
|
-
const params = parseParams("");
|
126
|
-
expect(params).toEqual({});
|
127
|
-
});
|
128
|
-
|
129
|
-
test("should handle parameters with extra semicolons", () => {
|
130
|
-
const params = parseParams("a=b;; c=d;");
|
131
|
-
expect(params).toEqual({ a: "b", c: "d" });
|
132
|
-
});
|
133
|
-
});
|
@@ -1,49 +0,0 @@
|
|
1
|
-
import PostalMime, { Email, Header } from "postal-mime";
|
2
|
-
|
3
|
-
export class DkimParsingError extends Error {
|
4
|
-
constructor(message: string) {
|
5
|
-
super(message);
|
6
|
-
this.name = "DkimParsingError";
|
7
|
-
}
|
8
|
-
}
|
9
|
-
|
10
|
-
export async function parseEmail(mime: string) {
|
11
|
-
return await PostalMime.parse(mime.trim());
|
12
|
-
}
|
13
|
-
|
14
|
-
export function getDkimSigners(mail: Email) {
|
15
|
-
const dkimHeader = mail.headers.filter((h) => h.key === "dkim-signature");
|
16
|
-
if (dkimHeader.length === 0)
|
17
|
-
throw new DkimParsingError("No DKIM header found");
|
18
|
-
return dkimHeader.map(parseHeader);
|
19
|
-
}
|
20
|
-
|
21
|
-
export function parseParams(str: string) {
|
22
|
-
return Object.fromEntries(
|
23
|
-
str.split(";").map((s) =>
|
24
|
-
s
|
25
|
-
.trim()
|
26
|
-
.split("=")
|
27
|
-
.map((v) => v && v.trim()),
|
28
|
-
),
|
29
|
-
) as Record<string, string>;
|
30
|
-
}
|
31
|
-
|
32
|
-
function parseHeader(header: Header) {
|
33
|
-
const params = parseParams(header.value);
|
34
|
-
if (!params) {
|
35
|
-
throw new DkimParsingError(`Invalid DKIM header ${header.value}`);
|
36
|
-
}
|
37
|
-
|
38
|
-
if (!params.d) {
|
39
|
-
throw new DkimParsingError("DKIM header missing domain");
|
40
|
-
}
|
41
|
-
|
42
|
-
if (!params.s) {
|
43
|
-
throw new DkimParsingError("DKIM header missing selector");
|
44
|
-
}
|
45
|
-
return {
|
46
|
-
domain: params.d,
|
47
|
-
selector: params.s,
|
48
|
-
};
|
49
|
-
}
|
@@ -1,37 +0,0 @@
|
|
1
|
-
import { describe, expect, test } from "vitest";
|
2
|
-
import { preverifyEmail } from "./preverify.ts";
|
3
|
-
import { readFile } from "../../testHelpers/readFile";
|
4
|
-
|
5
|
-
describe("Preverify email: integration", () => {
|
6
|
-
test("adds dns record to email mime", async () => {
|
7
|
-
const rawEmail = readFile("./src/api/email/testdata/test_email.txt");
|
8
|
-
const preverifiedEmail = await preverifyEmail(rawEmail);
|
9
|
-
expect(preverifiedEmail).toMatchObject({
|
10
|
-
email: rawEmail,
|
11
|
-
dnsRecords: [expect.stringContaining("v=DKIM1; k=rsa; p=")],
|
12
|
-
});
|
13
|
-
});
|
14
|
-
|
15
|
-
test("throws error if DKIM not found", async () => {
|
16
|
-
const emailWithNoDkimHeader = 'From: "Alice"\n\nBody';
|
17
|
-
await expect(preverifyEmail(emailWithNoDkimHeader)).rejects.toThrow(
|
18
|
-
"No DKIM header found",
|
19
|
-
);
|
20
|
-
});
|
21
|
-
|
22
|
-
test("throws error if DNS could not be resolved", async () => {
|
23
|
-
const emailWithNoDkimHeader = readFile(
|
24
|
-
"./src/api/email/testdata/test_email_unknown_domain.txt",
|
25
|
-
);
|
26
|
-
await expect(preverifyEmail(emailWithNoDkimHeader)).rejects.toThrow();
|
27
|
-
});
|
28
|
-
|
29
|
-
test("throws error if multiple DNS records found", async () => {
|
30
|
-
const emailWithNoDkimHeader = readFile(
|
31
|
-
"./src/api/email/testdata/test_email_multiple_dkims.txt",
|
32
|
-
);
|
33
|
-
await expect(preverifyEmail(emailWithNoDkimHeader)).rejects.toThrow(
|
34
|
-
"Multiple DKIM headers found",
|
35
|
-
);
|
36
|
-
});
|
37
|
-
});
|
@@ -1,19 +0,0 @@
|
|
1
|
-
import { parseEmail, getDkimSigners } from "./parseEmail.ts";
|
2
|
-
import { resolveDkimDns } from "./dnsResolver.ts";
|
3
|
-
|
4
|
-
export async function preverifyEmail(mimeEmail: string) {
|
5
|
-
const parsedEmail = await parseEmail(mimeEmail);
|
6
|
-
const signers = getDkimSigners(parsedEmail);
|
7
|
-
if (signers.length === 0) {
|
8
|
-
throw new Error("No DKIM header found");
|
9
|
-
}
|
10
|
-
if (signers.length > 1) {
|
11
|
-
throw new Error("Multiple DKIM headers found");
|
12
|
-
}
|
13
|
-
const [{ domain, selector }] = signers;
|
14
|
-
const dnsRecord = await resolveDkimDns(domain, selector);
|
15
|
-
return {
|
16
|
-
email: mimeEmail,
|
17
|
-
dnsRecords: [dnsRecord],
|
18
|
-
};
|
19
|
-
}
|
@@ -1,21 +0,0 @@
|
|
1
|
-
DKIM-Signature: a=rsa-sha256; bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
|
2
|
-
c=simple/simple; d=google.com;
|
3
|
-
h=Received:From:To:Subject:Date:Message-ID; i=joe@football.example.com;
|
4
|
-
s=20230601; t=1615825284; v=1;
|
5
|
-
b=Xh4Ujb2wv5x54gXtulCiy4C0e+plRm6pZ4owF+kICpYzs/8WkTVIDBrzhJP0DAYCpnL62T0G
|
6
|
-
k+0OH8pi/yqETVjKtKk+peMnNvKkut0GeWZMTze0bfq3/JUK3Ln3jTzzpXxrgVnvBxeY9EZIL4g
|
7
|
-
s4wwFRRKz/1bksZGSjD8uuSU=
|
8
|
-
Received: from client1.football.example.com [192.0.2.1]
|
9
|
-
by submitserver.example.com with SUBMISSION;
|
10
|
-
Fri, 11 Jul 2003 21:01:54 -0700 (PDT)
|
11
|
-
From: Joe SixPack <joe@football.example.com>
|
12
|
-
To: Suzie Q <suzie@shopping.example.net>
|
13
|
-
Subject: Is dinner ready?
|
14
|
-
Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT)
|
15
|
-
Message-ID: <20030712040037.46341.5F8J@football.example.com>
|
16
|
-
|
17
|
-
Hi.
|
18
|
-
|
19
|
-
We lost the game. Are you hungry yet?
|
20
|
-
|
21
|
-
Joe.
|