@tma.js/init-data-node 1.1.15 → 1.2.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/dist/dts/index.d.ts +4 -2
- package/dist/dts/initDataToSearchParams.d.ts +4 -0
- package/dist/dts/sign.d.ts +10 -0
- package/dist/dts/signData.d.ts +7 -0
- package/dist/dts/types.d.ts +4 -0
- package/dist/dts/validate.d.ts +15 -22
- package/dist/index.cjs +4 -2
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +346 -0
- package/dist/index.js.map +1 -0
- package/package.json +5 -8
- package/dist/dts/parse.d.ts +0 -2
- package/dist/index.mjs +0 -253
- package/src/__tests__/validate.ts +0 -43
- package/src/index.ts +0 -3
- package/src/parse.ts +0 -3
- package/src/validate.ts +0 -107
package/dist/index.mjs
DELETED
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
import { createHmac as N } from "node:crypto";
|
|
2
|
-
import { URLSearchParams as E } from "node:url";
|
|
3
|
-
var O = Object.defineProperty, T = (e, t, r) => t in e ? O(e, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : e[t] = r, U = (e, t, r) => (T(e, typeof t != "symbol" ? t + "" : t, r), r);
|
|
4
|
-
class l extends Error {
|
|
5
|
-
constructor(t, { cause: r, type: o } = {}) {
|
|
6
|
-
super(`Unable to parse value${o ? ` as ${o}` : ""}`, { cause: r }), U(this, "type"), this.value = t, Object.setPrototypeOf(this, l.prototype), this.type = o;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
class m extends Error {
|
|
10
|
-
constructor(t, { cause: r, type: o } = {}) {
|
|
11
|
-
super(`Unable to parse field "${t}"${o ? ` as ${o}` : ""}`, { cause: r }), Object.setPrototypeOf(this, m.prototype);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
function P(e, t) {
|
|
15
|
-
const r = {};
|
|
16
|
-
for (const o in e) {
|
|
17
|
-
const a = e[o];
|
|
18
|
-
if (!a)
|
|
19
|
-
continue;
|
|
20
|
-
let s, p;
|
|
21
|
-
if (typeof a == "function" || "parse" in a)
|
|
22
|
-
s = o, p = typeof a == "function" ? a : a.parse.bind(a);
|
|
23
|
-
else {
|
|
24
|
-
const { type: i } = a;
|
|
25
|
-
s = a.from || o, p = typeof i == "function" ? i : i.parse.bind(i);
|
|
26
|
-
}
|
|
27
|
-
let u;
|
|
28
|
-
const b = t(s);
|
|
29
|
-
try {
|
|
30
|
-
u = p(b);
|
|
31
|
-
} catch (i) {
|
|
32
|
-
throw i instanceof l ? new m(s, {
|
|
33
|
-
type: i.type,
|
|
34
|
-
cause: i
|
|
35
|
-
}) : new m(s, { cause: i });
|
|
36
|
-
}
|
|
37
|
-
u !== void 0 && (r[o] = u);
|
|
38
|
-
}
|
|
39
|
-
return r;
|
|
40
|
-
}
|
|
41
|
-
function f() {
|
|
42
|
-
return new TypeError("Value has unexpected type");
|
|
43
|
-
}
|
|
44
|
-
function x(e) {
|
|
45
|
-
let t = e;
|
|
46
|
-
if (typeof t == "string" && (t = JSON.parse(t)), typeof t != "object" || t === null || Array.isArray(t))
|
|
47
|
-
throw f();
|
|
48
|
-
return t;
|
|
49
|
-
}
|
|
50
|
-
class w {
|
|
51
|
-
constructor(t, r, o) {
|
|
52
|
-
this.parser = t, this.isOptional = r, this.type = o;
|
|
53
|
-
}
|
|
54
|
-
parse(t) {
|
|
55
|
-
if (!(this.isOptional && t === void 0))
|
|
56
|
-
try {
|
|
57
|
-
return this.parser(t);
|
|
58
|
-
} catch (r) {
|
|
59
|
-
throw new l(t, { type: this.type, cause: r });
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
optional() {
|
|
63
|
-
return this.isOptional = !0, this;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
function _(e, t) {
|
|
67
|
-
return new w((r) => {
|
|
68
|
-
const o = x(r);
|
|
69
|
-
return P(e, (a) => o[a]);
|
|
70
|
-
}, !1, t);
|
|
71
|
-
}
|
|
72
|
-
function y(e, t) {
|
|
73
|
-
return () => new w(e, !1, t);
|
|
74
|
-
}
|
|
75
|
-
const n = y((e) => {
|
|
76
|
-
if (typeof e == "string" || typeof e == "number")
|
|
77
|
-
return e.toString();
|
|
78
|
-
throw f();
|
|
79
|
-
}, "string"), h = y((e) => {
|
|
80
|
-
if (typeof e == "boolean")
|
|
81
|
-
return e;
|
|
82
|
-
const t = String(e);
|
|
83
|
-
if (t === "1" || t === "true")
|
|
84
|
-
return !0;
|
|
85
|
-
if (t === "0" || t === "false")
|
|
86
|
-
return !1;
|
|
87
|
-
throw f();
|
|
88
|
-
}, "boolean"), c = y((e) => {
|
|
89
|
-
if (typeof e == "number")
|
|
90
|
-
return e;
|
|
91
|
-
if (typeof e == "string") {
|
|
92
|
-
const t = Number(e);
|
|
93
|
-
if (!Number.isNaN(t))
|
|
94
|
-
return t;
|
|
95
|
-
}
|
|
96
|
-
throw f();
|
|
97
|
-
}, "number");
|
|
98
|
-
function I() {
|
|
99
|
-
return _({
|
|
100
|
-
id: c(),
|
|
101
|
-
type: n(),
|
|
102
|
-
title: n(),
|
|
103
|
-
photoUrl: {
|
|
104
|
-
type: n().optional(),
|
|
105
|
-
from: "photo_url"
|
|
106
|
-
},
|
|
107
|
-
username: n().optional()
|
|
108
|
-
}, "Chat");
|
|
109
|
-
}
|
|
110
|
-
function D() {
|
|
111
|
-
return _({
|
|
112
|
-
addedToAttachmentMenu: {
|
|
113
|
-
type: h().optional(),
|
|
114
|
-
from: "added_to_attachment_menu"
|
|
115
|
-
},
|
|
116
|
-
allowsWriteToPm: {
|
|
117
|
-
type: h().optional(),
|
|
118
|
-
from: "allows_write_to_pm"
|
|
119
|
-
},
|
|
120
|
-
firstName: {
|
|
121
|
-
type: n(),
|
|
122
|
-
from: "first_name"
|
|
123
|
-
},
|
|
124
|
-
id: c(),
|
|
125
|
-
isBot: {
|
|
126
|
-
type: h().optional(),
|
|
127
|
-
from: "is_bot"
|
|
128
|
-
},
|
|
129
|
-
isPremium: {
|
|
130
|
-
type: h().optional(),
|
|
131
|
-
from: "is_premium"
|
|
132
|
-
},
|
|
133
|
-
languageCode: {
|
|
134
|
-
type: n().optional(),
|
|
135
|
-
from: "language_code"
|
|
136
|
-
},
|
|
137
|
-
lastName: {
|
|
138
|
-
type: n().optional(),
|
|
139
|
-
from: "last_name"
|
|
140
|
-
},
|
|
141
|
-
photoUrl: {
|
|
142
|
-
type: n().optional(),
|
|
143
|
-
from: "photo_url"
|
|
144
|
-
},
|
|
145
|
-
username: n().optional()
|
|
146
|
-
}, "User");
|
|
147
|
-
}
|
|
148
|
-
const v = y((e) => e instanceof Date ? e : new Date(c().parse(e) * 1e3), "Date");
|
|
149
|
-
function S(e, t) {
|
|
150
|
-
return new w((r) => {
|
|
151
|
-
if (typeof r != "string" && !(r instanceof URLSearchParams))
|
|
152
|
-
throw f();
|
|
153
|
-
const o = typeof r == "string" ? new URLSearchParams(r) : r;
|
|
154
|
-
return P(e, (a) => {
|
|
155
|
-
const s = o.get(a);
|
|
156
|
-
return s === null ? void 0 : s;
|
|
157
|
-
});
|
|
158
|
-
}, !1, t);
|
|
159
|
-
}
|
|
160
|
-
function $() {
|
|
161
|
-
return S({
|
|
162
|
-
authDate: {
|
|
163
|
-
type: v(),
|
|
164
|
-
from: "auth_date"
|
|
165
|
-
},
|
|
166
|
-
canSendAfter: {
|
|
167
|
-
type: c().optional(),
|
|
168
|
-
from: "can_send_after"
|
|
169
|
-
},
|
|
170
|
-
chat: I().optional(),
|
|
171
|
-
chatInstance: {
|
|
172
|
-
type: n().optional(),
|
|
173
|
-
from: "chat_instance"
|
|
174
|
-
},
|
|
175
|
-
chatType: {
|
|
176
|
-
type: n().optional(),
|
|
177
|
-
from: "chat_type"
|
|
178
|
-
},
|
|
179
|
-
hash: n(),
|
|
180
|
-
queryId: {
|
|
181
|
-
type: n().optional(),
|
|
182
|
-
from: "query_id"
|
|
183
|
-
},
|
|
184
|
-
receiver: D().optional(),
|
|
185
|
-
startParam: {
|
|
186
|
-
type: n().optional(),
|
|
187
|
-
from: "start_param"
|
|
188
|
-
},
|
|
189
|
-
user: D().optional()
|
|
190
|
-
}, "InitData");
|
|
191
|
-
}
|
|
192
|
-
function R(e) {
|
|
193
|
-
return $().parse(e);
|
|
194
|
-
}
|
|
195
|
-
S({
|
|
196
|
-
contact: _({
|
|
197
|
-
userId: {
|
|
198
|
-
type: c(),
|
|
199
|
-
from: "user_id"
|
|
200
|
-
},
|
|
201
|
-
phoneNumber: {
|
|
202
|
-
type: n(),
|
|
203
|
-
from: "phone_number"
|
|
204
|
-
},
|
|
205
|
-
firstName: {
|
|
206
|
-
type: n(),
|
|
207
|
-
from: "first_name"
|
|
208
|
-
},
|
|
209
|
-
lastName: {
|
|
210
|
-
type: n().optional(),
|
|
211
|
-
from: "last_name"
|
|
212
|
-
}
|
|
213
|
-
}),
|
|
214
|
-
authDate: {
|
|
215
|
-
type: v(),
|
|
216
|
-
from: "auth_date"
|
|
217
|
-
},
|
|
218
|
-
hash: n()
|
|
219
|
-
});
|
|
220
|
-
function L(e, t, r = {}) {
|
|
221
|
-
const o = typeof e == "string" ? new E(e) : e;
|
|
222
|
-
let a = /* @__PURE__ */ new Date(0), s = "";
|
|
223
|
-
const p = [];
|
|
224
|
-
if (o.forEach((i, d) => {
|
|
225
|
-
if (d === "hash") {
|
|
226
|
-
s = i;
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
if (d === "auth_date") {
|
|
230
|
-
const g = parseInt(i, 10);
|
|
231
|
-
if (Number.isNaN(g))
|
|
232
|
-
throw new TypeError('"auth_date" should present integer');
|
|
233
|
-
a = new Date(g * 1e3);
|
|
234
|
-
}
|
|
235
|
-
p.push(`${d}=${i}`);
|
|
236
|
-
}), s.length === 0)
|
|
237
|
-
throw new Error('"hash" is empty or not found');
|
|
238
|
-
if (a.getTime() === 0)
|
|
239
|
-
throw new Error('"auth_date" is empty or not found');
|
|
240
|
-
const { expiresIn: u = 86400 } = r;
|
|
241
|
-
if (u > 0 && a.getTime() + u * 1e3 < (/* @__PURE__ */ new Date()).getTime())
|
|
242
|
-
throw new Error("Init data expired");
|
|
243
|
-
if (p.sort(), N(
|
|
244
|
-
"sha256",
|
|
245
|
-
N("sha256", "WebAppData").update(t).digest()
|
|
246
|
-
).update(p.join(`
|
|
247
|
-
`)).digest().toString("hex") !== s)
|
|
248
|
-
throw new Error("Signature is invalid");
|
|
249
|
-
}
|
|
250
|
-
export {
|
|
251
|
-
R as parse,
|
|
252
|
-
L as validate
|
|
253
|
-
};
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { URLSearchParams } from 'node:url';
|
|
2
|
-
|
|
3
|
-
import { expect, it } from 'vitest';
|
|
4
|
-
|
|
5
|
-
import { validate } from '../validate.js';
|
|
6
|
-
|
|
7
|
-
const sp = 'query_id=AAHdF6IQAAAAAN0XohDhrOrc&user=%7B%22id%22%3A279058397%2C%22first_name%22%3A%22Vladislav%22%2C%22last_name%22%3A%22Kibenko%22%2C%22username%22%3A%22vdkfrost%22%2C%22language_code%22%3A%22ru%22%2C%22is_premium%22%3Atrue%7D&auth_date=1662771648&hash=c501b71e775f74ce10e377dea85a7ea24ecd640b223ea86dfe453e0eaed2e2b2';
|
|
8
|
-
const secretToken = '5768337691:AAH5YkoiEuPk8-FZa32hStHTqXiLPtAEhx8';
|
|
9
|
-
|
|
10
|
-
it('should throw missing hash error in case, it is not in search params', () => {
|
|
11
|
-
expect(() => validate('auth_date=1', secretToken))
|
|
12
|
-
.toThrowError('"hash" is empty or not found');
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('should throw an error on case, auth_date is not passed, equal to 0 or does not represent integer', () => {
|
|
16
|
-
expect(() => validate('auth_date=0&hash=HHH', secretToken))
|
|
17
|
-
.toThrowError('"auth_date" is empty or not found');
|
|
18
|
-
expect(() => validate('hash=HHH', secretToken))
|
|
19
|
-
.toThrowError('"auth_date" is empty or not found');
|
|
20
|
-
expect(() => validate('auth_date=AAA&hash=HHH', secretToken))
|
|
21
|
-
.toThrowError('"auth_date" should present integer');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('should throw an error in case, parameters are expired', () => {
|
|
25
|
-
expect(() => validate(sp, secretToken, {
|
|
26
|
-
expiresIn: 1,
|
|
27
|
-
})).toThrowError('Init data expired');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('should throw an error in case, sign is invalid', () => {
|
|
31
|
-
expect(() => validate(sp, `${secretToken}A`, {
|
|
32
|
-
expiresIn: 0,
|
|
33
|
-
})).toThrowError('Signature is invalid');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should correctly validate parameters in case, they are valid', () => {
|
|
37
|
-
expect(() => validate(sp, secretToken, { expiresIn: 0 })).not.toThrow();
|
|
38
|
-
expect(() => validate(new URLSearchParams(sp), secretToken, { expiresIn: 0 })).not.toThrow();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('should throw an error in case, expiration time is not passed, parameters were created more than 1 day ago and already expired', () => {
|
|
42
|
-
expect(() => validate(sp, secretToken)).toThrow('Init data expired');
|
|
43
|
-
});
|
package/src/index.ts
DELETED
package/src/parse.ts
DELETED
package/src/validate.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { createHmac } from 'node:crypto';
|
|
2
|
-
import { URLSearchParams } from 'node:url';
|
|
3
|
-
|
|
4
|
-
export interface ValidateOptions {
|
|
5
|
-
/**
|
|
6
|
-
* Time in seconds which states, how long from creation time is init data
|
|
7
|
-
* considered valid.
|
|
8
|
-
*
|
|
9
|
-
* In other words, in case, when authDate + expiresIn is before current
|
|
10
|
-
* time, init data recognized as expired.
|
|
11
|
-
*
|
|
12
|
-
* In case, this value is equal to 0, function does not check init data
|
|
13
|
-
* expiration.
|
|
14
|
-
* @default 86400 (1 day)
|
|
15
|
-
*/
|
|
16
|
-
expiresIn?: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Validates passed init data presented as search params converted to string,
|
|
21
|
-
* or its object presentation.
|
|
22
|
-
*
|
|
23
|
-
* @param sp - search parameters.
|
|
24
|
-
* @param token - Telegram bot secret token.
|
|
25
|
-
* @param options - validation options.
|
|
26
|
-
* @see toSearchParams
|
|
27
|
-
* @throws {TypeError} "hash" should be string.
|
|
28
|
-
* @throws {Error} "hash" is empty or not found.
|
|
29
|
-
* @throws {TypeError} "auth_date" should be string.
|
|
30
|
-
* @throws {TypeError} "auth_date" does not represent integer.
|
|
31
|
-
* @throws {Error} "auth_date" is empty or not found.
|
|
32
|
-
* @throws {Error} Init data expired.
|
|
33
|
-
* @throws {Error} Sign invalid.
|
|
34
|
-
*/
|
|
35
|
-
export function validate(
|
|
36
|
-
sp: string | URLSearchParams,
|
|
37
|
-
token: string,
|
|
38
|
-
options: ValidateOptions = {},
|
|
39
|
-
): void {
|
|
40
|
-
const searchParams = typeof sp === 'string' ? new URLSearchParams(sp) : sp;
|
|
41
|
-
|
|
42
|
-
// Init data creation time.
|
|
43
|
-
let authDate = new Date(0);
|
|
44
|
-
|
|
45
|
-
// Init data sign.
|
|
46
|
-
let hash = '';
|
|
47
|
-
|
|
48
|
-
// All search params pairs presented as `k=v`.
|
|
49
|
-
const pairs: string[] = [];
|
|
50
|
-
|
|
51
|
-
// Iterate over all key-value pairs of parsed parameters and find required
|
|
52
|
-
// parameters.
|
|
53
|
-
searchParams.forEach((value, key) => {
|
|
54
|
-
if (key === 'hash') {
|
|
55
|
-
hash = value;
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (key === 'auth_date') {
|
|
60
|
-
const authDateNum = parseInt(value, 10);
|
|
61
|
-
|
|
62
|
-
if (Number.isNaN(authDateNum)) {
|
|
63
|
-
throw new TypeError('"auth_date" should present integer');
|
|
64
|
-
}
|
|
65
|
-
authDate = new Date(authDateNum * 1000);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Append new pair.
|
|
69
|
-
pairs.push(`${key}=${value}`);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// Hash and auth date always required.
|
|
73
|
-
if (hash.length === 0) {
|
|
74
|
-
throw new Error('"hash" is empty or not found');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (authDate.getTime() === 0) {
|
|
78
|
-
throw new Error('"auth_date" is empty or not found');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// In case, expiration time passed, we do additional parameters check.
|
|
82
|
-
const { expiresIn = 86400 } = options;
|
|
83
|
-
|
|
84
|
-
if (expiresIn > 0) {
|
|
85
|
-
// Check if init data expired.
|
|
86
|
-
if (authDate.getTime() + expiresIn * 1000 < new Date().getTime()) {
|
|
87
|
-
throw new Error('Init data expired');
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// According to docs, we sort all the pairs in alphabetical order.
|
|
92
|
-
pairs.sort();
|
|
93
|
-
|
|
94
|
-
// Compute sign.
|
|
95
|
-
const computedHash = createHmac(
|
|
96
|
-
'sha256',
|
|
97
|
-
createHmac('sha256', 'WebAppData').update(token).digest(),
|
|
98
|
-
)
|
|
99
|
-
.update(pairs.join('\n'))
|
|
100
|
-
.digest()
|
|
101
|
-
.toString('hex');
|
|
102
|
-
|
|
103
|
-
// In case, our sign is not equal to found one, we should throw an error.
|
|
104
|
-
if (computedHash !== hash) {
|
|
105
|
-
throw new Error('Signature is invalid');
|
|
106
|
-
}
|
|
107
|
-
}
|