Thanks to visit codestin.com
Credit goes to github.com

Skip to content
This repository was archived by the owner on May 3, 2024. It is now read-only.

Commit c5f6b22

Browse files
authored
Fix authentication compatibility and payload (#107)
* Fix authentication compatibility and payload * v2.0.18
1 parent 86b54f6 commit c5f6b22

File tree

6 files changed

+67
-33
lines changed

6 files changed

+67
-33
lines changed

poetry.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ packages = [
66
{include = "thirdweb"},
77
]
88
readme = "README.md"
9-
version = "2.0.17"
9+
version = "2.0.18"
1010

1111
[tool.poetry.dependencies]
1212
python = ">=3.7.1"
@@ -18,6 +18,7 @@ thirdweb-eth-account = "^0.6.6"
1818
pymerkle = "^2.0.2"
1919
pyee = "^9.0.4"
2020
cbor2 = "^5.4.3"
21+
pytz = "^2022.1"
2122

2223
[tool.poetry.dev-dependencies]
2324
black = "^22.1.0"

tests/test_auth.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def test_verify_with_params(sdk: ThirdwebSDK, primary_account, secondary_account
2323
payload = sdk.auth.login(
2424
domain,
2525
LoginOptions(
26-
expirationTime=datetime.utcnow() + timedelta(hours=5), chainId=137
26+
expiration_time=datetime.utcnow() + timedelta(hours=5), chain_id=137
2727
),
2828
)
2929

@@ -32,7 +32,7 @@ def test_verify_with_params(sdk: ThirdwebSDK, primary_account, secondary_account
3232
domain,
3333
payload,
3434
VerifyOptions(
35-
chainId=137,
35+
chain_id=137,
3636
),
3737
)
3838

@@ -56,7 +56,7 @@ def test_reject_incorrect_domain(sdk: ThirdwebSDK, primary_account, secondary_ac
5656
def test_reject_expired_payload(sdk: ThirdwebSDK, primary_account, secondary_account):
5757
sdk.update_signer(primary_account)
5858
payload = sdk.auth.login(
59-
domain, LoginOptions(expirationTime=datetime.utcnow() - timedelta(hours=5))
59+
domain, LoginOptions(expiration_time=datetime.utcnow() - timedelta(hours=5))
6060
)
6161

6262
sdk.update_signer(secondary_account)
@@ -72,11 +72,11 @@ def test_reject_incorrect_chain_id(
7272
sdk: ThirdwebSDK, primary_account, secondary_account
7373
):
7474
sdk.update_signer(primary_account)
75-
payload = sdk.auth.login(domain, LoginOptions(chainId=1))
75+
payload = sdk.auth.login(domain, LoginOptions(chain_id=1))
7676

7777
sdk.update_signer(secondary_account)
7878
try:
79-
sdk.auth.verify(domain, payload, VerifyOptions(chainId=137))
79+
sdk.auth.verify(domain, payload, VerifyOptions(chain_id=137))
8080
assert False
8181
except Exception as e:
8282
assert "Chain ID '137' does not match payload chain ID '1'" in str(e)
@@ -138,7 +138,7 @@ def test_reject_token_before_invalid(
138138
token = sdk.auth.generate_auth_token(
139139
domain,
140140
payload,
141-
AuthenticationOptions(invalidBefore=datetime.utcnow() + timedelta(hours=5)),
141+
AuthenticationOptions(invalid_before=datetime.utcnow() + timedelta(hours=5)),
142142
)
143143
try:
144144
sdk.auth.authenticate(domain, token)
@@ -156,7 +156,7 @@ def test_reject_expired_token(sdk: ThirdwebSDK, primary_account, secondary_accou
156156
token = sdk.auth.generate_auth_token(
157157
domain,
158158
payload,
159-
AuthenticationOptions(expirationTime=datetime.utcnow() - timedelta(hours=5)),
159+
AuthenticationOptions(expiration_time=datetime.utcnow() - timedelta(hours=5)),
160160
)
161161
try:
162162
sdk.auth.authenticate(domain, token)

thirdweb/core/auth/wallet_authenticator.py

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from thirdweb.types.sdk import SDKOptions
1818
from eth_account.messages import encode_defunct
1919
from eth_account.datastructures import SignedMessage
20+
import pytz
2021

2122

2223
class WalletAuthenticator(ProviderHandler):
@@ -80,12 +81,12 @@ def login(
8081
signer_address = self._require_signer().address
8182
payload_data = LoginPayloadData(
8283
domain=domain,
83-
expirationTime=options.expirationTime
84-
if options.expirationTime is not None
84+
expiration_time=options.expiration_time
85+
if options.expiration_time is not None
8586
else datetime.utcnow() + timedelta(minutes=5),
8687
address=signer_address,
8788
nonce=options.nonce if options.nonce is not None else str(uuid4()),
88-
chainId=options.chainId,
89+
chain_id=options.chain_id,
8990
)
9091

9192
message = self._generate_message(payload_data)
@@ -127,13 +128,18 @@ def verify(
127128

128129
# Check that the payload hasn't expired
129130
current_time = datetime.utcnow()
130-
if current_time > payload.payload.expirationTime:
131+
if current_time.replace(
132+
tzinfo=pytz.utc
133+
) > payload.payload.expiration_time.replace(tzinfo=pytz.utc):
131134
raise Exception(f"Login request has expired")
132135

133136
# If chain ID is specified, check that it matches the chain ID of the signature
134-
if options.chainId is not None and options.chainId != payload.payload.chainId:
137+
if (
138+
options.chain_id is not None
139+
and options.chain_id != payload.payload.chain_id
140+
):
135141
raise Exception(
136-
f"Chain ID '{options.chainId}' does not match payload chain ID '{payload.payload.chainId}'"
142+
f"Chain ID '{options.chain_id}' does not match payload chain ID '{payload.payload.chain_id}'"
137143
)
138144

139145
# Check that the signing address is the claimed wallet address
@@ -176,11 +182,11 @@ def generate_auth_token(
176182
iss=admin_address,
177183
sub=user_address,
178184
aud=domain,
179-
nbf=int(options.invalidBefore.timestamp())
180-
if options.invalidBefore is not None
185+
nbf=int(options.invalid_before.timestamp())
186+
if options.invalid_before is not None
181187
else int(datetime.utcnow().timestamp()),
182-
exp=int(options.expirationTime.timestamp())
183-
if options.expirationTime is not None
188+
exp=int(options.expiration_time.timestamp())
189+
if options.expiration_time is not None
184190
else int((datetime.utcnow() + timedelta(hours=5)).timestamp()),
185191
iat=int(datetime.utcnow().timestamp()),
186192
jti=str(uuid4()),
@@ -243,13 +249,17 @@ def authenticate(
243249

244250
# Check that the token is past the invalid before time
245251
current_time = datetime.utcnow()
246-
if current_time < datetime.fromtimestamp(payload.nbf):
252+
if current_time.replace(tzinfo=pytz.utc) < datetime.fromtimestamp(
253+
payload.nbf
254+
).replace(tzinfo=pytz.utc):
247255
raise Exception(
248256
f"This token is invalid before epoch time '{payload.nbf}', current epoch time is '{int(current_time.timestamp())}'"
249257
)
250258

251259
# Check that the token hasn't expired
252-
if current_time > datetime.fromtimestamp(payload.exp):
260+
if current_time.replace(tzinfo=pytz.utc) > datetime.fromtimestamp(
261+
payload.exp
262+
).replace(tzinfo=pytz.utc):
253263
raise Exception(
254264
f"This token expired at epoch time '{payload.exp}', current epoch time is '{int(current_time.timestamp())}'"
255265
)
@@ -289,11 +299,15 @@ def _generate_message(self, payload: LoginPayloadData) -> str:
289299
message += "Make sure that the requesting domain above matches the URL of the current website.\n\n"
290300

291301
# Add data fields in compliance with the EIP-4361 standard
292-
if payload.chainId is not None:
293-
message += f"Chain ID: {payload.chainId}\n"
302+
if payload.chain_id is not None:
303+
message += f"Chain ID: {payload.chain_id}\n"
294304

295305
message += f"Nonce: {payload.nonce}\n"
296-
message += f"Expiration Time: {payload.expirationTime.isoformat()}\n"
306+
307+
time = payload.expiration_time.strftime("%Y-%m-%dT%H:%M:%S")
308+
microseconds = payload.expiration_time.strftime("%f")[0:3]
309+
formatted_time = f"{time}.{microseconds}Z"
310+
message += f"Expiration Time: {formatted_time}\n"
297311

298312
return message
299313

thirdweb/types/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
from .multiwrap import *
44
from .marketplace import *
55
from .settings import *
6+
from .auth import *

thirdweb/types/auth.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,57 @@
11
from dataclasses import dataclass
22
from datetime import datetime
3-
from typing import Optional
3+
from typing import Any, Dict, Optional
44

55

66
@dataclass
77
class LoginOptions:
88
nonce: Optional[str] = None
9-
expirationTime: Optional[datetime] = None
10-
chainId: Optional[int] = None
9+
expiration_time: Optional[datetime] = None
10+
chain_id: Optional[int] = None
1111

1212

1313
@dataclass
1414
class LoginPayloadData:
1515
domain: str
1616
address: str
1717
nonce: str
18-
expirationTime: datetime
19-
chainId: Optional[int] = None
18+
expiration_time: datetime
19+
chain_id: Optional[int] = None
20+
21+
@staticmethod
22+
def from_json(json: Dict[str, Any]) -> "LoginPayloadData":
23+
return LoginPayloadData(
24+
json["domain"],
25+
json["address"],
26+
json["nonce"],
27+
datetime.strptime(json["expiration_time"], "%Y-%m-%dT%H:%M:%S.%fZ"),
28+
json.get("chain_id"),
29+
)
2030

2131

2232
@dataclass
2333
class LoginPayload:
2434
payload: LoginPayloadData
2535
signature: str
2636

37+
@staticmethod
38+
def from_json(json: Dict[str, Any]) -> "LoginPayload":
39+
data = LoginPayloadData.from_json(json["payload"])
40+
return LoginPayload(
41+
data,
42+
signature=json["signature"],
43+
)
44+
2745

2846
@dataclass
2947
class VerifyOptions:
30-
chainId: Optional[int] = None
48+
chain_id: Optional[int] = None
3149

3250

3351
@dataclass
3452
class AuthenticationOptions:
35-
invalidBefore: Optional[datetime] = None
36-
expirationTime: Optional[datetime] = None
53+
invalid_before: Optional[datetime] = None
54+
expiration_time: Optional[datetime] = None
3755

3856

3957
@dataclass

0 commit comments

Comments
 (0)