tikwebsign 0.1.0__tar.gz
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.
- tikwebsign-0.1.0/PKG-INFO +68 -0
- tikwebsign-0.1.0/README.md +59 -0
- tikwebsign-0.1.0/pyproject.toml +16 -0
- tikwebsign-0.1.0/setup.cfg +4 -0
- tikwebsign-0.1.0/tests/run_tests.py +33 -0
- tikwebsign-0.1.0/tests/test_core.py +37 -0
- tikwebsign-0.1.0/tikwebsign/__init__.py +15 -0
- tikwebsign-0.1.0/tikwebsign/core.py +107 -0
- tikwebsign-0.1.0/tikwebsign.egg-info/PKG-INFO +68 -0
- tikwebsign-0.1.0/tikwebsign.egg-info/SOURCES.txt +10 -0
- tikwebsign-0.1.0/tikwebsign.egg-info/dependency_links.txt +1 -0
- tikwebsign-0.1.0/tikwebsign.egg-info/top_level.txt +3 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tikwebsign
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Safe HMAC request signing helpers for authorized APIs
|
|
5
|
+
Author: tikwebsign contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# tikwebsign
|
|
11
|
+
|
|
12
|
+
مكتبة صغيرة لتوقيع طلبات HTTP الخاصة بواجهات API التي تملكها أو لديك تفويض صريح لاستخدامها. تستخدم **HMAC-SHA256** مع canonical request، ولا تطبق تجاوزات anti-bot أو فحص بيانات اعتماد أو جمع cookies/tokens من خدمات طرف ثالث.
|
|
13
|
+
|
|
14
|
+
## التثبيت
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install -e .
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## الاستخدام
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import requests
|
|
24
|
+
import tikwebsign
|
|
25
|
+
|
|
26
|
+
url = "https://api.example.com/v1/items"
|
|
27
|
+
params = {"page": 1, "limit": 20}
|
|
28
|
+
body = {"filter": "active"}
|
|
29
|
+
signer = tikwebsign.RequestSigner("ضع-المفتاح-السري-في-متغير-بيئة", key_id="client-1")
|
|
30
|
+
|
|
31
|
+
# الدوال ترجع dicts؛ لذلك الاستدعاء الصحيح يتضمن الأقواس.
|
|
32
|
+
signed_params = tikwebsign.getparams(
|
|
33
|
+
signer=signer, method="POST", url=url, params=params, body=body
|
|
34
|
+
)
|
|
35
|
+
headers = tikwebsign.getheaders(
|
|
36
|
+
signer=signer, method="POST", url=url, params=params, body=body,
|
|
37
|
+
headers={"Content-Type": "application/json"}
|
|
38
|
+
)
|
|
39
|
+
params.update(signed_params)
|
|
40
|
+
response = requests.post(url, params=params, json=body, headers=headers, timeout=30)
|
|
41
|
+
response.raise_for_status()
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> لا يمكن جعل `params.update(tikwebsign.getparams)` يولّد توقيعًا صحيحًا دون معرفة method وURL وbody والسر. لذلك صُممت الدوال كـ `getparams(...)` و`getheaders(...)` حتى يوقّع الخادم نفس البيانات بالترتيب canonical نفسه.
|
|
45
|
+
|
|
46
|
+
## صيغة التوقيع للخادم
|
|
47
|
+
|
|
48
|
+
يبني العميل النص التالي مفصولًا بـ newline:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
METHOD
|
|
52
|
+
URL
|
|
53
|
+
sorted-urlencoded-query
|
|
54
|
+
sha256(body)
|
|
55
|
+
timestamp
|
|
56
|
+
nonce
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
ثم يحسب `HMAC-SHA256(secret, canonical_request)` ويضع القيمة Base64URL في `X-Request-Signature`. يجب على الخادم التحقق من `timestamp` ضمن نافذة قصيرة (مثل 300 ثانية)، ومنع إعادة استخدام `nonce`، واستخدام مقارنة ثابتة الزمن.
|
|
60
|
+
|
|
61
|
+
## توليد جهاز محلي
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
device = tikwebsign.generate_device(user_agent="my-client/1.0")
|
|
65
|
+
print(device.to_dict())
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
هذا مولد هوية محلية فقط، ولا يتصل بأي خدمة خارجية ولا ينشئ tokens أو cookies خاصة بخدمة أخرى.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# tikwebsign
|
|
2
|
+
|
|
3
|
+
مكتبة صغيرة لتوقيع طلبات HTTP الخاصة بواجهات API التي تملكها أو لديك تفويض صريح لاستخدامها. تستخدم **HMAC-SHA256** مع canonical request، ولا تطبق تجاوزات anti-bot أو فحص بيانات اعتماد أو جمع cookies/tokens من خدمات طرف ثالث.
|
|
4
|
+
|
|
5
|
+
## التثبيت
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install -e .
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## الاستخدام
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
import requests
|
|
15
|
+
import tikwebsign
|
|
16
|
+
|
|
17
|
+
url = "https://api.example.com/v1/items"
|
|
18
|
+
params = {"page": 1, "limit": 20}
|
|
19
|
+
body = {"filter": "active"}
|
|
20
|
+
signer = tikwebsign.RequestSigner("ضع-المفتاح-السري-في-متغير-بيئة", key_id="client-1")
|
|
21
|
+
|
|
22
|
+
# الدوال ترجع dicts؛ لذلك الاستدعاء الصحيح يتضمن الأقواس.
|
|
23
|
+
signed_params = tikwebsign.getparams(
|
|
24
|
+
signer=signer, method="POST", url=url, params=params, body=body
|
|
25
|
+
)
|
|
26
|
+
headers = tikwebsign.getheaders(
|
|
27
|
+
signer=signer, method="POST", url=url, params=params, body=body,
|
|
28
|
+
headers={"Content-Type": "application/json"}
|
|
29
|
+
)
|
|
30
|
+
params.update(signed_params)
|
|
31
|
+
response = requests.post(url, params=params, json=body, headers=headers, timeout=30)
|
|
32
|
+
response.raise_for_status()
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
> لا يمكن جعل `params.update(tikwebsign.getparams)` يولّد توقيعًا صحيحًا دون معرفة method وURL وbody والسر. لذلك صُممت الدوال كـ `getparams(...)` و`getheaders(...)` حتى يوقّع الخادم نفس البيانات بالترتيب canonical نفسه.
|
|
36
|
+
|
|
37
|
+
## صيغة التوقيع للخادم
|
|
38
|
+
|
|
39
|
+
يبني العميل النص التالي مفصولًا بـ newline:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
METHOD
|
|
43
|
+
URL
|
|
44
|
+
sorted-urlencoded-query
|
|
45
|
+
sha256(body)
|
|
46
|
+
timestamp
|
|
47
|
+
nonce
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
ثم يحسب `HMAC-SHA256(secret, canonical_request)` ويضع القيمة Base64URL في `X-Request-Signature`. يجب على الخادم التحقق من `timestamp` ضمن نافذة قصيرة (مثل 300 ثانية)، ومنع إعادة استخدام `nonce`، واستخدام مقارنة ثابتة الزمن.
|
|
51
|
+
|
|
52
|
+
## توليد جهاز محلي
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
device = tikwebsign.generate_device(user_agent="my-client/1.0")
|
|
56
|
+
print(device.to_dict())
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
هذا مولد هوية محلية فقط، ولا يتصل بأي خدمة خارجية ولا ينشئ tokens أو cookies خاصة بخدمة أخرى.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tikwebsign"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Safe HMAC request signing helpers for authorized APIs"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [{name = "tikwebsign contributors"}]
|
|
13
|
+
dependencies = []
|
|
14
|
+
|
|
15
|
+
[tool.setuptools.packages.find]
|
|
16
|
+
where = ["."]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
import hmac
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
sys.path.insert(0, str(Path(__file__).parents[1]))
|
|
8
|
+
import tikwebsign
|
|
9
|
+
|
|
10
|
+
signer = tikwebsign.RequestSigner("secret", key_id="k1")
|
|
11
|
+
params, headers = signer.sign(
|
|
12
|
+
method="post", url="https://api.example.test/v1", params={"b": 2, "a": 1},
|
|
13
|
+
body='{"x":1}', timestamp=1700000000, nonce="n1"
|
|
14
|
+
)
|
|
15
|
+
canonical = "\n".join((
|
|
16
|
+
"POST", "https://api.example.test/v1", "a=1&b=2",
|
|
17
|
+
hashlib.sha256(b'{"x":1}').hexdigest(), "1700000000", "n1"
|
|
18
|
+
))
|
|
19
|
+
expected = base64.urlsafe_b64encode(
|
|
20
|
+
hmac.new(b"secret", canonical.encode(), hashlib.sha256).digest()
|
|
21
|
+
).decode().rstrip("=")
|
|
22
|
+
assert params == {"ts": "1700000000", "nonce": "n1", "key_id": "k1"}
|
|
23
|
+
assert headers["X-Request-Signature"] == expected
|
|
24
|
+
|
|
25
|
+
base = {"q": "hello"}
|
|
26
|
+
base.update(tikwebsign.getparams(signer=signer, method="GET", url="https://x", params=base))
|
|
27
|
+
assert "ts" in base
|
|
28
|
+
assert "X-Request-Signature" in tikwebsign.getheaders(
|
|
29
|
+
signer=signer, method="GET", url="https://x", params={"q": "hello"}
|
|
30
|
+
)
|
|
31
|
+
device = tikwebsign.generate_device()
|
|
32
|
+
assert len(device.device_id) == 36
|
|
33
|
+
print("ok")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
import hmac
|
|
4
|
+
|
|
5
|
+
import tikwebsign
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_signature_is_deterministic_with_fixed_nonce_and_timestamp():
|
|
9
|
+
signer = tikwebsign.RequestSigner("secret", key_id="k1")
|
|
10
|
+
params, headers = signer.sign(
|
|
11
|
+
method="post", url="https://api.example.test/v1", params={"b": 2, "a": 1},
|
|
12
|
+
body='{"x":1}', timestamp=1700000000, nonce="n1"
|
|
13
|
+
)
|
|
14
|
+
canonical = "\n".join((
|
|
15
|
+
"POST", "https://api.example.test/v1", "a=1&b=2",
|
|
16
|
+
hashlib.sha256(b'{"x":1}').hexdigest(), "1700000000", "n1"
|
|
17
|
+
))
|
|
18
|
+
expected = base64.urlsafe_b64encode(
|
|
19
|
+
hmac.new(b"secret", canonical.encode(), hashlib.sha256).digest()
|
|
20
|
+
).decode().rstrip("=")
|
|
21
|
+
assert params == {"ts": "1700000000", "nonce": "n1", "key_id": "k1"}
|
|
22
|
+
assert headers["X-Request-Signature"] == expected
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_helpers_return_mergeable_dicts():
|
|
26
|
+
signer = tikwebsign.RequestSigner("secret")
|
|
27
|
+
params = {"q": "hello"}
|
|
28
|
+
params.update(tikwebsign.getparams(signer=signer, method="GET", url="https://x", params=params))
|
|
29
|
+
headers = tikwebsign.getheaders(signer=signer, method="GET", url="https://x", params=params)
|
|
30
|
+
assert "ts" in params and "X-Request-Signature" in headers
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_device_is_local_and_serializable():
|
|
34
|
+
device = tikwebsign.generate_device()
|
|
35
|
+
data = device.to_dict()
|
|
36
|
+
assert len(data["device_id"]) == 36
|
|
37
|
+
assert data["platform"] == "python"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""tikwebsign: safe request signing helpers for APIs you own or are authorized to use.
|
|
2
|
+
|
|
3
|
+
This package deliberately does not implement third-party anti-bot bypasses,
|
|
4
|
+
credential checking, login automation, or scraping of device tokens.
|
|
5
|
+
"""
|
|
6
|
+
from .core import Device, RequestSigner, generate_device, getheaders, getparams
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Device",
|
|
10
|
+
"RequestSigner",
|
|
11
|
+
"generate_device",
|
|
12
|
+
"getheaders",
|
|
13
|
+
"getparams",
|
|
14
|
+
]
|
|
15
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import hashlib
|
|
5
|
+
import hmac
|
|
6
|
+
import json
|
|
7
|
+
import secrets
|
|
8
|
+
import time
|
|
9
|
+
import uuid
|
|
10
|
+
from dataclasses import asdict, dataclass
|
|
11
|
+
from typing import Mapping, MutableMapping
|
|
12
|
+
from urllib.parse import urlencode
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class Device:
|
|
17
|
+
"""Stable local client identity; contains no third-party cookies or tokens."""
|
|
18
|
+
|
|
19
|
+
device_id: str
|
|
20
|
+
user_agent: str
|
|
21
|
+
platform: str = "python"
|
|
22
|
+
created_at: int = 0
|
|
23
|
+
|
|
24
|
+
def to_dict(self) -> dict[str, str | int]:
|
|
25
|
+
return asdict(self)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def generate_device(*, user_agent: str = "tikwebsign/0.1", platform: str = "python") -> Device:
|
|
29
|
+
"""Generate a local device identity for an authorized API client."""
|
|
30
|
+
return Device(
|
|
31
|
+
device_id=str(uuid.uuid4()),
|
|
32
|
+
user_agent=user_agent,
|
|
33
|
+
platform=platform,
|
|
34
|
+
created_at=int(time.time()),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _canonical_query(params: Mapping[str, object]) -> str:
|
|
39
|
+
pairs: list[tuple[str, str]] = []
|
|
40
|
+
for key, value in params.items():
|
|
41
|
+
if value is None:
|
|
42
|
+
continue
|
|
43
|
+
if isinstance(value, (list, tuple)):
|
|
44
|
+
pairs.extend((str(key), str(item)) for item in value)
|
|
45
|
+
else:
|
|
46
|
+
pairs.append((str(key), str(value)))
|
|
47
|
+
return urlencode(sorted(pairs), doseq=True)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _body_bytes(body: str | bytes | bytearray | Mapping[str, object] | None) -> bytes:
|
|
51
|
+
if body is None:
|
|
52
|
+
return b""
|
|
53
|
+
if isinstance(body, (bytes, bytearray)):
|
|
54
|
+
return bytes(body)
|
|
55
|
+
if isinstance(body, str):
|
|
56
|
+
return body.encode("utf-8")
|
|
57
|
+
return json.dumps(body, separators=(",", ":"), sort_keys=True, ensure_ascii=False).encode("utf-8")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class RequestSigner:
|
|
61
|
+
"""HMAC-SHA256 signer for a first-party or explicitly authorized API."""
|
|
62
|
+
|
|
63
|
+
def __init__(self, secret: str | bytes, *, key_id: str = "default", ttl: int = 300):
|
|
64
|
+
self.secret = secret.encode() if isinstance(secret, str) else bytes(secret)
|
|
65
|
+
if not self.secret:
|
|
66
|
+
raise ValueError("secret must not be empty")
|
|
67
|
+
if ttl <= 0:
|
|
68
|
+
raise ValueError("ttl must be positive")
|
|
69
|
+
self.key_id, self.ttl = key_id, ttl
|
|
70
|
+
|
|
71
|
+
def sign(self, *, method: str, url: str, params: Mapping[str, object] | None = None,
|
|
72
|
+
body: str | bytes | bytearray | Mapping[str, object] | None = None,
|
|
73
|
+
timestamp: int | None = None, nonce: str | None = None) -> tuple[dict[str, str], dict[str, str]]:
|
|
74
|
+
timestamp = int(time.time()) if timestamp is None else int(timestamp)
|
|
75
|
+
nonce = secrets.token_urlsafe(18) if nonce is None else nonce
|
|
76
|
+
query = _canonical_query(params or {})
|
|
77
|
+
body_hash = hashlib.sha256(_body_bytes(body)).hexdigest()
|
|
78
|
+
canonical = "\n".join((method.upper(), url, query, body_hash, str(timestamp), nonce))
|
|
79
|
+
digest = hmac.new(self.secret, canonical.encode("utf-8"), hashlib.sha256).digest()
|
|
80
|
+
signature = base64.urlsafe_b64encode(digest).decode("ascii").rstrip("=")
|
|
81
|
+
signed_params = {"ts": str(timestamp), "nonce": nonce, "key_id": self.key_id}
|
|
82
|
+
signed_headers = {
|
|
83
|
+
"X-Api-Key-Id": self.key_id,
|
|
84
|
+
"X-Request-Timestamp": str(timestamp),
|
|
85
|
+
"X-Request-Nonce": nonce,
|
|
86
|
+
"X-Request-Signature": signature,
|
|
87
|
+
}
|
|
88
|
+
return signed_params, signed_headers
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def getparams(*, signer: RequestSigner, method: str, url: str,
|
|
92
|
+
params: Mapping[str, object] | None = None,
|
|
93
|
+
body: str | bytes | bytearray | Mapping[str, object] | None = None) -> dict[str, str]:
|
|
94
|
+
"""Return signed query parameters to merge with ``params``."""
|
|
95
|
+
signed, _ = signer.sign(method=method, url=url, params=params, body=body)
|
|
96
|
+
return signed
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def getheaders(*, signer: RequestSigner, method: str, url: str,
|
|
100
|
+
params: Mapping[str, object] | None = None,
|
|
101
|
+
body: str | bytes | bytearray | Mapping[str, object] | None = None,
|
|
102
|
+
headers: Mapping[str, str] | None = None) -> dict[str, str]:
|
|
103
|
+
"""Return signed headers, preserving caller headers without overwriting them."""
|
|
104
|
+
_, signed = signer.sign(method=method, url=url, params=params, body=body)
|
|
105
|
+
result = dict(headers or {})
|
|
106
|
+
result.update(signed)
|
|
107
|
+
return result
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tikwebsign
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Safe HMAC request signing helpers for authorized APIs
|
|
5
|
+
Author: tikwebsign contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# tikwebsign
|
|
11
|
+
|
|
12
|
+
مكتبة صغيرة لتوقيع طلبات HTTP الخاصة بواجهات API التي تملكها أو لديك تفويض صريح لاستخدامها. تستخدم **HMAC-SHA256** مع canonical request، ولا تطبق تجاوزات anti-bot أو فحص بيانات اعتماد أو جمع cookies/tokens من خدمات طرف ثالث.
|
|
13
|
+
|
|
14
|
+
## التثبيت
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install -e .
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## الاستخدام
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import requests
|
|
24
|
+
import tikwebsign
|
|
25
|
+
|
|
26
|
+
url = "https://api.example.com/v1/items"
|
|
27
|
+
params = {"page": 1, "limit": 20}
|
|
28
|
+
body = {"filter": "active"}
|
|
29
|
+
signer = tikwebsign.RequestSigner("ضع-المفتاح-السري-في-متغير-بيئة", key_id="client-1")
|
|
30
|
+
|
|
31
|
+
# الدوال ترجع dicts؛ لذلك الاستدعاء الصحيح يتضمن الأقواس.
|
|
32
|
+
signed_params = tikwebsign.getparams(
|
|
33
|
+
signer=signer, method="POST", url=url, params=params, body=body
|
|
34
|
+
)
|
|
35
|
+
headers = tikwebsign.getheaders(
|
|
36
|
+
signer=signer, method="POST", url=url, params=params, body=body,
|
|
37
|
+
headers={"Content-Type": "application/json"}
|
|
38
|
+
)
|
|
39
|
+
params.update(signed_params)
|
|
40
|
+
response = requests.post(url, params=params, json=body, headers=headers, timeout=30)
|
|
41
|
+
response.raise_for_status()
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> لا يمكن جعل `params.update(tikwebsign.getparams)` يولّد توقيعًا صحيحًا دون معرفة method وURL وbody والسر. لذلك صُممت الدوال كـ `getparams(...)` و`getheaders(...)` حتى يوقّع الخادم نفس البيانات بالترتيب canonical نفسه.
|
|
45
|
+
|
|
46
|
+
## صيغة التوقيع للخادم
|
|
47
|
+
|
|
48
|
+
يبني العميل النص التالي مفصولًا بـ newline:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
METHOD
|
|
52
|
+
URL
|
|
53
|
+
sorted-urlencoded-query
|
|
54
|
+
sha256(body)
|
|
55
|
+
timestamp
|
|
56
|
+
nonce
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
ثم يحسب `HMAC-SHA256(secret, canonical_request)` ويضع القيمة Base64URL في `X-Request-Signature`. يجب على الخادم التحقق من `timestamp` ضمن نافذة قصيرة (مثل 300 ثانية)، ومنع إعادة استخدام `nonce`، واستخدام مقارنة ثابتة الزمن.
|
|
60
|
+
|
|
61
|
+
## توليد جهاز محلي
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
device = tikwebsign.generate_device(user_agent="my-client/1.0")
|
|
65
|
+
print(device.to_dict())
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
هذا مولد هوية محلية فقط، ولا يتصل بأي خدمة خارجية ولا ينشئ tokens أو cookies خاصة بخدمة أخرى.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|