@zeroad.network/token 0.13.6 → 0.13.8
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 +79 -67
- package/dist/browser-BiNZ2c6t.d.cts +47 -0
- package/dist/browser-BiNZ2c6t.d.mts +47 -0
- package/dist/browser-CMCq-bkd.cjs +132 -0
- package/dist/browser-Citg0bh9.mjs +116 -0
- package/dist/browser.d.mts +1 -54
- package/dist/browser.mjs +1 -1
- package/dist/index.cjs +80 -81
- package/dist/index.d.cts +29 -18
- package/dist/index.d.mts +29 -18
- package/dist/index.mjs +78 -82
- package/package.json +2 -2
- package/dist/browser-C2zgnAdK.cjs +0 -171
- package/dist/browser-DDeUUPbU.mjs +0 -156
- package/dist/browser.d.cts +0 -54
package/dist/index.cjs
CHANGED
|
@@ -1,33 +1,37 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var browser = require('./browser-
|
|
3
|
+
var browser = require('./browser-CMCq-bkd.cjs');
|
|
4
4
|
var node_buffer = require('node:buffer');
|
|
5
5
|
var node_crypto = require('node:crypto');
|
|
6
6
|
|
|
7
|
+
const keyCache = /* @__PURE__ */ new Map();
|
|
7
8
|
const importPrivateKey = (privateKeyBase64) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
key:
|
|
9
|
+
if (keyCache.has(privateKeyBase64)) return keyCache.get(privateKeyBase64);
|
|
10
|
+
const key = node_crypto.createPrivateKey({
|
|
11
|
+
key: node_buffer.Buffer.from(privateKeyBase64, "base64"),
|
|
11
12
|
format: "der",
|
|
12
13
|
type: "pkcs8"
|
|
13
14
|
});
|
|
15
|
+
keyCache.set(privateKeyBase64, key);
|
|
16
|
+
return key;
|
|
14
17
|
};
|
|
15
18
|
const importPublicKey = (publicKeyBase64) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
key:
|
|
19
|
+
if (keyCache.has(publicKeyBase64)) return keyCache.get(publicKeyBase64);
|
|
20
|
+
const key = node_crypto.createPublicKey({
|
|
21
|
+
key: node_buffer.Buffer.from(publicKeyBase64, "base64"),
|
|
19
22
|
format: "der",
|
|
20
23
|
type: "spki"
|
|
21
24
|
});
|
|
25
|
+
keyCache.set(publicKeyBase64, key);
|
|
26
|
+
return key;
|
|
22
27
|
};
|
|
23
28
|
const sign = (data, privateKey) => {
|
|
24
|
-
const
|
|
25
|
-
return node_crypto.sign(null,
|
|
29
|
+
const key = importPrivateKey(privateKey);
|
|
30
|
+
return node_crypto.sign(null, node_buffer.Buffer.from(data), key);
|
|
26
31
|
};
|
|
27
32
|
const verify = (data, signature, publicKey) => {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
return node_crypto.verify(null, dataBuffer, publicKey, sigBuffer);
|
|
33
|
+
const key = importPublicKey(publicKey);
|
|
34
|
+
return node_crypto.verify(null, node_buffer.Buffer.from(data), key, node_buffer.Buffer.from(signature));
|
|
31
35
|
};
|
|
32
36
|
const nonce = (size) => new Uint8Array(node_crypto.randomBytes(size));
|
|
33
37
|
|
|
@@ -35,70 +39,59 @@ const VERSION_BYTES = 1;
|
|
|
35
39
|
const NONCE_BYTES = 4;
|
|
36
40
|
const SEPARATOR = ".";
|
|
37
41
|
const SITE_FEATURES_NATIVE = browser.getSiteFeaturesNative();
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
result[feature] = browser.hasFeature(data.flags, shift);
|
|
42
|
+
function parseClientToken(headerValue, clientId, publicKey) {
|
|
43
|
+
const headerValueAsString = Array.isArray(headerValue) ? headerValue[0] : headerValue;
|
|
44
|
+
const data = decodeClientHeader(headerValueAsString, publicKey);
|
|
45
|
+
let flags = 0;
|
|
46
|
+
if (data && data.expiresAt.getTime() >= Date.now()) flags = data.flags;
|
|
47
|
+
if (flags && data?.clientId && data.clientId !== clientId) flags = 0;
|
|
48
|
+
const features = SITE_FEATURES_NATIVE.map(([feature, shift]) => browser.hasFlag(flags, shift) && feature).filter((e) => !!e);
|
|
49
|
+
const hasCleanWeb = features.includes("CLEAN_WEB");
|
|
50
|
+
const hasOnePass = features.includes("ONE_PASS");
|
|
51
|
+
return {
|
|
52
|
+
HIDE_ADVERTISEMENTS: hasCleanWeb,
|
|
53
|
+
HIDE_COOKIE_CONSENT_SCREEN: hasCleanWeb,
|
|
54
|
+
HIDE_MARKETING_DIALOGS: hasCleanWeb,
|
|
55
|
+
DISABLE_NON_FUNCTIONAL_TRACKING: hasCleanWeb,
|
|
56
|
+
DISABLE_CONTENT_PAYWALL: hasOnePass,
|
|
57
|
+
ENABLE_SUBSCRIPTION_ACCESS: hasOnePass
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function decodeClientHeader(headerValue, publicKey) {
|
|
61
|
+
if (!headerValue?.length) return;
|
|
62
|
+
try {
|
|
63
|
+
const [data, signature] = headerValue.split(SEPARATOR);
|
|
64
|
+
const dataBytes = browser.fromBase64(data);
|
|
65
|
+
const signatureBytes = browser.fromBase64(signature);
|
|
66
|
+
if (!verify(dataBytes.buffer, signatureBytes.buffer, publicKey)) {
|
|
67
|
+
throw new Error("Forged header value is provided");
|
|
65
68
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const signatureBytes = browser.fromBase64(signature);
|
|
75
|
-
if (!verify(dataBytes.buffer, signatureBytes.buffer, this.cryptoPublicKey)) {
|
|
76
|
-
throw new Error("Forged header value is provided");
|
|
77
|
-
}
|
|
78
|
-
const version = dataBytes[0];
|
|
79
|
-
if (version === browser.PROTOCOL_VERSION.V_1) {
|
|
80
|
-
const expiresAt = as32BitNumber(dataBytes, VERSION_BYTES + NONCE_BYTES);
|
|
81
|
-
const flags = as32BitNumber(dataBytes, dataBytes.length - Uint32Array.BYTES_PER_ELEMENT);
|
|
82
|
-
return { version, expiresAt: new Date(expiresAt * 1e3), flags };
|
|
69
|
+
const version = dataBytes[0];
|
|
70
|
+
let clientId;
|
|
71
|
+
if (version === browser.PROTOCOL_VERSION.V_1) {
|
|
72
|
+
const expiresAt = as32BitNumber(dataBytes, VERSION_BYTES + NONCE_BYTES);
|
|
73
|
+
const flags = as32BitNumber(dataBytes, VERSION_BYTES + NONCE_BYTES + Uint32Array.BYTES_PER_ELEMENT);
|
|
74
|
+
const expectedByteLength = VERSION_BYTES + NONCE_BYTES + Uint32Array.BYTES_PER_ELEMENT * 2;
|
|
75
|
+
if (dataBytes.byteLength > expectedByteLength) {
|
|
76
|
+
clientId = new TextDecoder().decode(dataBytes.subarray(expectedByteLength));
|
|
83
77
|
}
|
|
84
|
-
|
|
85
|
-
browser.log("warn", "Could not decode client header value", { reason: err?.message });
|
|
78
|
+
return { version, expiresAt: new Date(expiresAt * 1e3), flags, ...clientId && { clientId } };
|
|
86
79
|
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (!this.privateKey) throw new Error("Private key is required");
|
|
90
|
-
const data = mergeByteArrays([
|
|
91
|
-
new Uint8Array([version]),
|
|
92
|
-
new Uint8Array(nonce(NONCE_BYTES)),
|
|
93
|
-
new Uint32Array([Math.floor(expiresAt.getTime() / 1e3)]),
|
|
94
|
-
new Uint32Array([browser.setFeatures(0, features)])
|
|
95
|
-
]);
|
|
96
|
-
if (!this.cryptoPrivateKey) this.cryptoPrivateKey = importPrivateKey(this.privateKey);
|
|
97
|
-
const signature = sign(data.buffer, this.cryptoPrivateKey);
|
|
98
|
-
return [browser.toBase64(data), browser.toBase64(new Uint8Array(signature))].join(SEPARATOR);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
browser.log("warn", "Could not decode client header value", { reason: err?.message });
|
|
99
82
|
}
|
|
100
83
|
}
|
|
101
|
-
|
|
84
|
+
function encodeClientHeader(data, privateKey) {
|
|
85
|
+
const payload = mergeByteArrays([
|
|
86
|
+
new Uint8Array([data.version]),
|
|
87
|
+
new Uint8Array(nonce(NONCE_BYTES)),
|
|
88
|
+
new Uint32Array([Math.floor(data.expiresAt.getTime() / 1e3)]),
|
|
89
|
+
new Uint32Array([browser.setFlags(data.features)]),
|
|
90
|
+
...data.clientId?.length ? [new Uint8Array(new TextEncoder().encode(data.clientId))] : []
|
|
91
|
+
]);
|
|
92
|
+
return [browser.toBase64(payload), browser.toBase64(new Uint8Array(sign(payload.buffer, privateKey)))].join(SEPARATOR);
|
|
93
|
+
}
|
|
94
|
+
function mergeByteArrays(arrays) {
|
|
102
95
|
const totalLength = arrays.reduce((sum, a) => sum + a.byteLength, 0);
|
|
103
96
|
const data = new Uint8Array(totalLength);
|
|
104
97
|
let offset = 0;
|
|
@@ -111,17 +104,20 @@ const mergeByteArrays = (arrays) => {
|
|
|
111
104
|
offset += bytes.byteLength;
|
|
112
105
|
}
|
|
113
106
|
return data;
|
|
114
|
-
}
|
|
107
|
+
}
|
|
108
|
+
function as32BitNumber(byteArray, begin) {
|
|
109
|
+
const bytes = byteArray.subarray(begin, begin + Uint32Array.BYTES_PER_ELEMENT);
|
|
110
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
111
|
+
return view.getUint32(0, true);
|
|
112
|
+
}
|
|
115
113
|
|
|
116
114
|
function Site(options) {
|
|
117
|
-
const
|
|
118
|
-
const clientHeader = new ClientHeader(browser.ZEROAD_NETWORK_PUBLIC_KEY);
|
|
115
|
+
const serverHeaderValue = browser.encodeServerHeader(options.clientId, options.features);
|
|
119
116
|
return {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
SERVER_HEADER_VALUE: serverHeader.VALUE
|
|
117
|
+
parseClientToken: (headerValue) => parseClientToken(headerValue, options.clientId, browser.ZEROAD_NETWORK_PUBLIC_KEY),
|
|
118
|
+
CLIENT_HEADER_NAME: browser.CLIENT_HEADERS.HELLO,
|
|
119
|
+
SERVER_HEADER_NAME: browser.SERVER_HEADERS.WELCOME,
|
|
120
|
+
SERVER_HEADER_VALUE: serverHeaderValue
|
|
125
121
|
};
|
|
126
122
|
}
|
|
127
123
|
|
|
@@ -130,8 +126,11 @@ exports.CURRENT_PROTOCOL_VERSION = browser.CURRENT_PROTOCOL_VERSION;
|
|
|
130
126
|
exports.FEATURES = browser.FEATURES;
|
|
131
127
|
exports.PROTOCOL_VERSION = browser.PROTOCOL_VERSION;
|
|
132
128
|
exports.SERVER_HEADERS = browser.SERVER_HEADERS;
|
|
133
|
-
exports.ServerHeader = browser.ServerHeader;
|
|
134
129
|
exports.ZEROAD_NETWORK_PUBLIC_KEY = browser.ZEROAD_NETWORK_PUBLIC_KEY;
|
|
130
|
+
exports.decodeServerHeader = browser.decodeServerHeader;
|
|
131
|
+
exports.encodeServerHeader = browser.encodeServerHeader;
|
|
135
132
|
exports.setLogLevel = browser.setLogLevel;
|
|
136
|
-
exports.ClientHeader = ClientHeader;
|
|
137
133
|
exports.Site = Site;
|
|
134
|
+
exports.decodeClientHeader = decodeClientHeader;
|
|
135
|
+
exports.encodeClientHeader = encodeClientHeader;
|
|
136
|
+
exports.parseClientToken = parseClientToken;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,27 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { CURRENT_PROTOCOL_VERSION,
|
|
3
|
-
|
|
4
|
-
declare class ClientHeader {
|
|
5
|
-
private cryptoPublicKey;
|
|
6
|
-
private cryptoPrivateKey;
|
|
7
|
-
private publicKey;
|
|
8
|
-
private privateKey;
|
|
9
|
-
NAME: CLIENT_HEADERS;
|
|
10
|
-
constructor(publicKey: string, privateKey?: string);
|
|
11
|
-
parseToken(headerValue: string | string[] | undefined): Record<"ADS_OFF" | "COOKIE_CONSENT_OFF" | "MARKETING_DIALOG_OFF" | "CONTENT_PAYWALL_OFF" | "SUBSCRIPTION_ACCESS_ON", boolean>;
|
|
12
|
-
decode(headerValue: string | undefined): ClientParsedHeader | undefined;
|
|
13
|
-
encode(version: PROTOCOL_VERSION, expiresAt: Date, features: FEATURES[]): string;
|
|
14
|
-
}
|
|
1
|
+
import { P as PROTOCOL_VERSION, F as FEATURES, C as CLIENT_HEADERS, S as SERVER_HEADERS } from './browser-BiNZ2c6t.cjs';
|
|
2
|
+
export { a as CURRENT_PROTOCOL_VERSION, W as WelcomeHeader, Z as ZEROAD_NETWORK_PUBLIC_KEY, d as decodeServerHeader, e as encodeServerHeader } from './browser-BiNZ2c6t.cjs';
|
|
15
3
|
|
|
16
4
|
type LogLevel = "error" | "warn" | "info" | "debug";
|
|
17
5
|
declare function setLogLevel(level: LogLevel): void;
|
|
18
6
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
7
|
+
type FEATURE_FLAG = "HIDE_ADVERTISEMENTS" | "HIDE_COOKIE_CONSENT_SCREEN" | "HIDE_MARKETING_DIALOGS" | "DISABLE_NON_FUNCTIONAL_TRACKING" | "DISABLE_CONTENT_PAYWALL" | "ENABLE_SUBSCRIPTION_ACCESS";
|
|
8
|
+
type ClientHeaderValue = string | string[] | undefined;
|
|
9
|
+
type FeatureFlags = Record<FEATURE_FLAG, boolean>;
|
|
10
|
+
declare function parseClientToken(headerValue: ClientHeaderValue, clientId: string, publicKey: string): FeatureFlags;
|
|
11
|
+
type DecodedClientHeader = {
|
|
12
|
+
version: PROTOCOL_VERSION;
|
|
13
|
+
expiresAt: Date;
|
|
14
|
+
flags: number;
|
|
15
|
+
clientId?: string;
|
|
16
|
+
};
|
|
17
|
+
declare function decodeClientHeader(headerValue: string | null | undefined, publicKey: string): DecodedClientHeader | undefined;
|
|
18
|
+
type EncodeData = {
|
|
19
|
+
version: PROTOCOL_VERSION;
|
|
20
|
+
expiresAt: Date;
|
|
21
|
+
features: FEATURES[];
|
|
22
|
+
clientId?: string;
|
|
23
|
+
};
|
|
24
|
+
declare function encodeClientHeader(data: EncodeData, privateKey: string): string;
|
|
25
|
+
|
|
26
|
+
type SiteOptions = {
|
|
27
|
+
clientId: string;
|
|
28
|
+
features: FEATURES[];
|
|
29
|
+
};
|
|
30
|
+
declare function Site(options: SiteOptions): {
|
|
31
|
+
parseClientToken: (headerValue: ClientHeaderValue) => FeatureFlags;
|
|
22
32
|
CLIENT_HEADER_NAME: CLIENT_HEADERS;
|
|
23
33
|
SERVER_HEADER_NAME: SERVER_HEADERS;
|
|
24
34
|
SERVER_HEADER_VALUE: string;
|
|
25
35
|
};
|
|
26
36
|
|
|
27
|
-
export { CLIENT_HEADERS,
|
|
37
|
+
export { CLIENT_HEADERS, FEATURES, PROTOCOL_VERSION, SERVER_HEADERS, Site, decodeClientHeader, encodeClientHeader, parseClientToken, setLogLevel };
|
|
38
|
+
export type { ClientHeaderValue, DecodedClientHeader, FEATURE_FLAG, FeatureFlags };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,27 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { CURRENT_PROTOCOL_VERSION,
|
|
3
|
-
|
|
4
|
-
declare class ClientHeader {
|
|
5
|
-
private cryptoPublicKey;
|
|
6
|
-
private cryptoPrivateKey;
|
|
7
|
-
private publicKey;
|
|
8
|
-
private privateKey;
|
|
9
|
-
NAME: CLIENT_HEADERS;
|
|
10
|
-
constructor(publicKey: string, privateKey?: string);
|
|
11
|
-
parseToken(headerValue: string | string[] | undefined): Record<"ADS_OFF" | "COOKIE_CONSENT_OFF" | "MARKETING_DIALOG_OFF" | "CONTENT_PAYWALL_OFF" | "SUBSCRIPTION_ACCESS_ON", boolean>;
|
|
12
|
-
decode(headerValue: string | undefined): ClientParsedHeader | undefined;
|
|
13
|
-
encode(version: PROTOCOL_VERSION, expiresAt: Date, features: FEATURES[]): string;
|
|
14
|
-
}
|
|
1
|
+
import { P as PROTOCOL_VERSION, F as FEATURES, C as CLIENT_HEADERS, S as SERVER_HEADERS } from './browser-BiNZ2c6t.mjs';
|
|
2
|
+
export { a as CURRENT_PROTOCOL_VERSION, W as WelcomeHeader, Z as ZEROAD_NETWORK_PUBLIC_KEY, d as decodeServerHeader, e as encodeServerHeader } from './browser-BiNZ2c6t.mjs';
|
|
15
3
|
|
|
16
4
|
type LogLevel = "error" | "warn" | "info" | "debug";
|
|
17
5
|
declare function setLogLevel(level: LogLevel): void;
|
|
18
6
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
7
|
+
type FEATURE_FLAG = "HIDE_ADVERTISEMENTS" | "HIDE_COOKIE_CONSENT_SCREEN" | "HIDE_MARKETING_DIALOGS" | "DISABLE_NON_FUNCTIONAL_TRACKING" | "DISABLE_CONTENT_PAYWALL" | "ENABLE_SUBSCRIPTION_ACCESS";
|
|
8
|
+
type ClientHeaderValue = string | string[] | undefined;
|
|
9
|
+
type FeatureFlags = Record<FEATURE_FLAG, boolean>;
|
|
10
|
+
declare function parseClientToken(headerValue: ClientHeaderValue, clientId: string, publicKey: string): FeatureFlags;
|
|
11
|
+
type DecodedClientHeader = {
|
|
12
|
+
version: PROTOCOL_VERSION;
|
|
13
|
+
expiresAt: Date;
|
|
14
|
+
flags: number;
|
|
15
|
+
clientId?: string;
|
|
16
|
+
};
|
|
17
|
+
declare function decodeClientHeader(headerValue: string | null | undefined, publicKey: string): DecodedClientHeader | undefined;
|
|
18
|
+
type EncodeData = {
|
|
19
|
+
version: PROTOCOL_VERSION;
|
|
20
|
+
expiresAt: Date;
|
|
21
|
+
features: FEATURES[];
|
|
22
|
+
clientId?: string;
|
|
23
|
+
};
|
|
24
|
+
declare function encodeClientHeader(data: EncodeData, privateKey: string): string;
|
|
25
|
+
|
|
26
|
+
type SiteOptions = {
|
|
27
|
+
clientId: string;
|
|
28
|
+
features: FEATURES[];
|
|
29
|
+
};
|
|
30
|
+
declare function Site(options: SiteOptions): {
|
|
31
|
+
parseClientToken: (headerValue: ClientHeaderValue) => FeatureFlags;
|
|
22
32
|
CLIENT_HEADER_NAME: CLIENT_HEADERS;
|
|
23
33
|
SERVER_HEADER_NAME: SERVER_HEADERS;
|
|
24
34
|
SERVER_HEADER_VALUE: string;
|
|
25
35
|
};
|
|
26
36
|
|
|
27
|
-
export { CLIENT_HEADERS,
|
|
37
|
+
export { CLIENT_HEADERS, FEATURES, PROTOCOL_VERSION, SERVER_HEADERS, Site, decodeClientHeader, encodeClientHeader, parseClientToken, setLogLevel };
|
|
38
|
+
export type { ClientHeaderValue, DecodedClientHeader, FEATURE_FLAG, FeatureFlags };
|
package/dist/index.mjs
CHANGED
|
@@ -1,32 +1,36 @@
|
|
|
1
|
-
import { g as getSiteFeaturesNative,
|
|
2
|
-
export {
|
|
1
|
+
import { g as getSiteFeaturesNative, h as hasFlag, f as fromBase64, P as PROTOCOL_VERSION, l as log, s as setFlags, t as toBase64, e as encodeServerHeader, S as SERVER_HEADERS, C as CLIENT_HEADERS, Z as ZEROAD_NETWORK_PUBLIC_KEY } from './browser-Citg0bh9.mjs';
|
|
2
|
+
export { b as CURRENT_PROTOCOL_VERSION, F as FEATURES, d as decodeServerHeader, a as setLogLevel } from './browser-Citg0bh9.mjs';
|
|
3
3
|
import { Buffer } from 'node:buffer';
|
|
4
|
-
import {
|
|
4
|
+
import { verify as verify$1, randomBytes, sign as sign$1, createPublicKey, createPrivateKey } from 'node:crypto';
|
|
5
5
|
|
|
6
|
+
const keyCache = /* @__PURE__ */ new Map();
|
|
6
7
|
const importPrivateKey = (privateKeyBase64) => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
key:
|
|
8
|
+
if (keyCache.has(privateKeyBase64)) return keyCache.get(privateKeyBase64);
|
|
9
|
+
const key = createPrivateKey({
|
|
10
|
+
key: Buffer.from(privateKeyBase64, "base64"),
|
|
10
11
|
format: "der",
|
|
11
12
|
type: "pkcs8"
|
|
12
13
|
});
|
|
14
|
+
keyCache.set(privateKeyBase64, key);
|
|
15
|
+
return key;
|
|
13
16
|
};
|
|
14
17
|
const importPublicKey = (publicKeyBase64) => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
key:
|
|
18
|
+
if (keyCache.has(publicKeyBase64)) return keyCache.get(publicKeyBase64);
|
|
19
|
+
const key = createPublicKey({
|
|
20
|
+
key: Buffer.from(publicKeyBase64, "base64"),
|
|
18
21
|
format: "der",
|
|
19
22
|
type: "spki"
|
|
20
23
|
});
|
|
24
|
+
keyCache.set(publicKeyBase64, key);
|
|
25
|
+
return key;
|
|
21
26
|
};
|
|
22
27
|
const sign = (data, privateKey) => {
|
|
23
|
-
const
|
|
24
|
-
return sign$1(null,
|
|
28
|
+
const key = importPrivateKey(privateKey);
|
|
29
|
+
return sign$1(null, Buffer.from(data), key);
|
|
25
30
|
};
|
|
26
31
|
const verify = (data, signature, publicKey) => {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
return verify$1(null, dataBuffer, publicKey, sigBuffer);
|
|
32
|
+
const key = importPublicKey(publicKey);
|
|
33
|
+
return verify$1(null, Buffer.from(data), key, Buffer.from(signature));
|
|
30
34
|
};
|
|
31
35
|
const nonce = (size) => new Uint8Array(randomBytes(size));
|
|
32
36
|
|
|
@@ -34,70 +38,59 @@ const VERSION_BYTES = 1;
|
|
|
34
38
|
const NONCE_BYTES = 4;
|
|
35
39
|
const SEPARATOR = ".";
|
|
36
40
|
const SITE_FEATURES_NATIVE = getSiteFeaturesNative();
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
result[feature] = hasFeature(data.flags, shift);
|
|
41
|
+
function parseClientToken(headerValue, clientId, publicKey) {
|
|
42
|
+
const headerValueAsString = Array.isArray(headerValue) ? headerValue[0] : headerValue;
|
|
43
|
+
const data = decodeClientHeader(headerValueAsString, publicKey);
|
|
44
|
+
let flags = 0;
|
|
45
|
+
if (data && data.expiresAt.getTime() >= Date.now()) flags = data.flags;
|
|
46
|
+
if (flags && data?.clientId && data.clientId !== clientId) flags = 0;
|
|
47
|
+
const features = SITE_FEATURES_NATIVE.map(([feature, shift]) => hasFlag(flags, shift) && feature).filter((e) => !!e);
|
|
48
|
+
const hasCleanWeb = features.includes("CLEAN_WEB");
|
|
49
|
+
const hasOnePass = features.includes("ONE_PASS");
|
|
50
|
+
return {
|
|
51
|
+
HIDE_ADVERTISEMENTS: hasCleanWeb,
|
|
52
|
+
HIDE_COOKIE_CONSENT_SCREEN: hasCleanWeb,
|
|
53
|
+
HIDE_MARKETING_DIALOGS: hasCleanWeb,
|
|
54
|
+
DISABLE_NON_FUNCTIONAL_TRACKING: hasCleanWeb,
|
|
55
|
+
DISABLE_CONTENT_PAYWALL: hasOnePass,
|
|
56
|
+
ENABLE_SUBSCRIPTION_ACCESS: hasOnePass
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function decodeClientHeader(headerValue, publicKey) {
|
|
60
|
+
if (!headerValue?.length) return;
|
|
61
|
+
try {
|
|
62
|
+
const [data, signature] = headerValue.split(SEPARATOR);
|
|
63
|
+
const dataBytes = fromBase64(data);
|
|
64
|
+
const signatureBytes = fromBase64(signature);
|
|
65
|
+
if (!verify(dataBytes.buffer, signatureBytes.buffer, publicKey)) {
|
|
66
|
+
throw new Error("Forged header value is provided");
|
|
64
67
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const signatureBytes = fromBase64(signature);
|
|
74
|
-
if (!verify(dataBytes.buffer, signatureBytes.buffer, this.cryptoPublicKey)) {
|
|
75
|
-
throw new Error("Forged header value is provided");
|
|
76
|
-
}
|
|
77
|
-
const version = dataBytes[0];
|
|
78
|
-
if (version === PROTOCOL_VERSION.V_1) {
|
|
79
|
-
const expiresAt = as32BitNumber(dataBytes, VERSION_BYTES + NONCE_BYTES);
|
|
80
|
-
const flags = as32BitNumber(dataBytes, dataBytes.length - Uint32Array.BYTES_PER_ELEMENT);
|
|
81
|
-
return { version, expiresAt: new Date(expiresAt * 1e3), flags };
|
|
68
|
+
const version = dataBytes[0];
|
|
69
|
+
let clientId;
|
|
70
|
+
if (version === PROTOCOL_VERSION.V_1) {
|
|
71
|
+
const expiresAt = as32BitNumber(dataBytes, VERSION_BYTES + NONCE_BYTES);
|
|
72
|
+
const flags = as32BitNumber(dataBytes, VERSION_BYTES + NONCE_BYTES + Uint32Array.BYTES_PER_ELEMENT);
|
|
73
|
+
const expectedByteLength = VERSION_BYTES + NONCE_BYTES + Uint32Array.BYTES_PER_ELEMENT * 2;
|
|
74
|
+
if (dataBytes.byteLength > expectedByteLength) {
|
|
75
|
+
clientId = new TextDecoder().decode(dataBytes.subarray(expectedByteLength));
|
|
82
76
|
}
|
|
83
|
-
|
|
84
|
-
log("warn", "Could not decode client header value", { reason: err?.message });
|
|
77
|
+
return { version, expiresAt: new Date(expiresAt * 1e3), flags, ...clientId && { clientId } };
|
|
85
78
|
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (!this.privateKey) throw new Error("Private key is required");
|
|
89
|
-
const data = mergeByteArrays([
|
|
90
|
-
new Uint8Array([version]),
|
|
91
|
-
new Uint8Array(nonce(NONCE_BYTES)),
|
|
92
|
-
new Uint32Array([Math.floor(expiresAt.getTime() / 1e3)]),
|
|
93
|
-
new Uint32Array([setFeatures(0, features)])
|
|
94
|
-
]);
|
|
95
|
-
if (!this.cryptoPrivateKey) this.cryptoPrivateKey = importPrivateKey(this.privateKey);
|
|
96
|
-
const signature = sign(data.buffer, this.cryptoPrivateKey);
|
|
97
|
-
return [toBase64(data), toBase64(new Uint8Array(signature))].join(SEPARATOR);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
log("warn", "Could not decode client header value", { reason: err?.message });
|
|
98
81
|
}
|
|
99
82
|
}
|
|
100
|
-
|
|
83
|
+
function encodeClientHeader(data, privateKey) {
|
|
84
|
+
const payload = mergeByteArrays([
|
|
85
|
+
new Uint8Array([data.version]),
|
|
86
|
+
new Uint8Array(nonce(NONCE_BYTES)),
|
|
87
|
+
new Uint32Array([Math.floor(data.expiresAt.getTime() / 1e3)]),
|
|
88
|
+
new Uint32Array([setFlags(data.features)]),
|
|
89
|
+
...data.clientId?.length ? [new Uint8Array(new TextEncoder().encode(data.clientId))] : []
|
|
90
|
+
]);
|
|
91
|
+
return [toBase64(payload), toBase64(new Uint8Array(sign(payload.buffer, privateKey)))].join(SEPARATOR);
|
|
92
|
+
}
|
|
93
|
+
function mergeByteArrays(arrays) {
|
|
101
94
|
const totalLength = arrays.reduce((sum, a) => sum + a.byteLength, 0);
|
|
102
95
|
const data = new Uint8Array(totalLength);
|
|
103
96
|
let offset = 0;
|
|
@@ -110,18 +103,21 @@ const mergeByteArrays = (arrays) => {
|
|
|
110
103
|
offset += bytes.byteLength;
|
|
111
104
|
}
|
|
112
105
|
return data;
|
|
113
|
-
}
|
|
106
|
+
}
|
|
107
|
+
function as32BitNumber(byteArray, begin) {
|
|
108
|
+
const bytes = byteArray.subarray(begin, begin + Uint32Array.BYTES_PER_ELEMENT);
|
|
109
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
110
|
+
return view.getUint32(0, true);
|
|
111
|
+
}
|
|
114
112
|
|
|
115
113
|
function Site(options) {
|
|
116
|
-
const
|
|
117
|
-
const clientHeader = new ClientHeader(ZEROAD_NETWORK_PUBLIC_KEY);
|
|
114
|
+
const serverHeaderValue = encodeServerHeader(options.clientId, options.features);
|
|
118
115
|
return {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
SERVER_HEADER_VALUE: serverHeader.VALUE
|
|
116
|
+
parseClientToken: (headerValue) => parseClientToken(headerValue, options.clientId, ZEROAD_NETWORK_PUBLIC_KEY),
|
|
117
|
+
CLIENT_HEADER_NAME: CLIENT_HEADERS.HELLO,
|
|
118
|
+
SERVER_HEADER_NAME: SERVER_HEADERS.WELCOME,
|
|
119
|
+
SERVER_HEADER_VALUE: serverHeaderValue
|
|
124
120
|
};
|
|
125
121
|
}
|
|
126
122
|
|
|
127
|
-
export { CLIENT_HEADERS,
|
|
123
|
+
export { CLIENT_HEADERS, PROTOCOL_VERSION, SERVER_HEADERS, Site, ZEROAD_NETWORK_PUBLIC_KEY, decodeClientHeader, encodeClientHeader, encodeServerHeader, parseClientToken };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeroad.network/token",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.8",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": "github:laurynas-karvelis/zeroad-token-typescript",
|
|
6
6
|
"homepage": "https://zeroad.network",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"@types/bun": "^1.3.3",
|
|
78
78
|
"@types/node": "^24.10.0",
|
|
79
79
|
"pkgroll": "^2.21.4",
|
|
80
|
-
"prettier": "^3.7.
|
|
80
|
+
"prettier": "^3.7.4",
|
|
81
81
|
"typescript": "^5.9.3"
|
|
82
82
|
}
|
|
83
83
|
}
|