appmesh 1.6.7__py3-none-any.whl → 1.6.8__py3-none-any.whl

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.
appmesh/client_http.py CHANGED
@@ -17,7 +17,6 @@ from urllib import parse
17
17
  import aniso8601
18
18
  import jwt
19
19
  import requests
20
- from requests.auth import _basic_auth_str
21
20
  from .app import App
22
21
  from .app_run import AppRun
23
22
  from .app_output import AppOutput
@@ -445,6 +444,44 @@ class AppMeshClient(metaclass=abc.ABCMeta):
445
444
 
446
445
  self._forward_to = host
447
446
 
447
+ def _normalize_totp_secret(self, secret: str) -> str:
448
+ """
449
+ Normalize a TOTP secret to a valid RFC 4648 Base32 format.
450
+ This includes:
451
+ - Removing all whitespace
452
+ - Uppercasing
453
+ - Replacing visually similar or Crockford-like digits:
454
+ '1' -> 'I', '0' -> 'O', '8' -> 'B', '9' -> 'G'
455
+ - Adding RFC 4648 padding
456
+ """
457
+ if not secret:
458
+ raise ValueError("Empty TOTP secret")
459
+
460
+ # Remove all whitespace and uppercase
461
+ secret = "".join(secret.split()).upper()
462
+
463
+ # Crockford/Base32-friendly replacements
464
+ replacements = {
465
+ "1": "I",
466
+ "0": "O",
467
+ "8": "B",
468
+ "9": "G",
469
+ }
470
+
471
+ for old, new in replacements.items():
472
+ secret = secret.replace(old, new)
473
+
474
+ # Validate that only RFC 4648 Base32 characters remain
475
+ valid_chars = set("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")
476
+ if not set(secret).issubset(valid_chars):
477
+ raise ValueError("Invalid characters in TOTP secret")
478
+
479
+ # Add padding to make length a multiple of 8 (RFC 4648)
480
+ padding = (8 - (len(secret) % 8)) % 8
481
+ secret += "=" * padding
482
+
483
+ return secret
484
+
448
485
  ########################################
449
486
  # Security
450
487
  ########################################
@@ -474,7 +511,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
474
511
  AppMeshClient.Method.POST,
475
512
  path="/appmesh/login",
476
513
  header={
477
- self.HTTP_HEADER_KEY_AUTH: _basic_auth_str(user_name, user_pwd),
514
+ self.HTTP_HEADER_KEY_AUTH: "Basic " + base64.b64encode(f"{user_name}:{user_pwd}".encode()).decode(),
478
515
  "X-Expire-Seconds": str(self._parse_duration(timeout_seconds)),
479
516
  **({"X-Audience": audience} if audience else {}),
480
517
  # **({"X-Totp-Code": totp_code} if totp_code else {}),
@@ -610,15 +647,22 @@ class AppMeshClient(metaclass=abc.ABCMeta):
610
647
  raise Exception(f"Token renewal failed: {str(e)}") from e
611
648
 
612
649
  def get_totp_secret(self) -> str:
613
- """Generate TOTP secret for the current user and return MFA URI.
650
+ """
651
+ Generate TOTP secret for the current user and return a normalized secret.
614
652
 
615
653
  Returns:
616
- str: TOTP secret str
654
+ str: Normalized TOTP secret string
617
655
  """
618
656
  resp = self._request_http(method=AppMeshClient.Method.POST, path="/appmesh/totp/secret")
619
657
  if resp.status_code == HTTPStatus.OK:
620
658
  totp_uri = base64.b64decode(resp.json()["mfa_uri"]).decode()
621
- return self._parse_totp_uri(totp_uri).get("secret")
659
+ secret = self._parse_totp_uri(totp_uri).get("secret")
660
+ if not secret:
661
+ raise ValueError("TOTP secret missing in response")
662
+
663
+ # Normalize to standard Base32
664
+ return self._normalize_totp_secret(secret)
665
+
622
666
  raise Exception(resp.text)
623
667
 
624
668
  def setup_totp(self, totp_code: str) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: appmesh
3
- Version: 1.6.7
3
+ Version: 1.6.8
4
4
  Summary: Client SDK for App Mesh
5
5
  Home-page: https://github.com/laoshanxi/app-mesh
6
6
  Author: laoshanxi
@@ -3,14 +3,14 @@ appmesh/app.py,sha256=crD4DRFZJuHtZMfSsz7C-EwvjPmGZbFXYXvA_wCdvdI,10734
3
3
  appmesh/app_output.py,sha256=vfn322AyixblI8DbXds08h6L_ybObiaRSifsA1-Xcoo,1035
4
4
  appmesh/app_run.py,sha256=aYq852a29OThIi32Xtx5s0sTXZ97T0lHD5WXH8yfPoc,2018
5
5
  appmesh/appmesh_client.py,sha256=ywB2222PtJUffdfdxZcBfdhZs1KYyc7JvzMxwuK2qyI,378
6
- appmesh/client_http.py,sha256=dzNZvMztm0POLHNS8iNHo1iuJprjvQP0kii1UWOH-XA,57529
6
+ appmesh/client_http.py,sha256=l8eUmJxXIDjowPvPKIppCLfwlk2kjztU-FXoaOac_tc,59003
7
7
  appmesh/client_http_oauth.py,sha256=1d51o0JX_xtB8d2bEuM7_XJHcwMnhcjkbIq7GE1Zxm8,6120
8
8
  appmesh/client_tcp.py,sha256=aq6UUzytZA4ibE9WQMMWdo1uW8sHETEhJjsbM6IYSno,11457
9
9
  appmesh/server_http.py,sha256=rBIYO9rbR-r3x1Jcry440Sp--IM-OWKRaOhNpGdkxh8,4299
10
10
  appmesh/server_tcp.py,sha256=-CU5tw97WJmDcUNsNPWqpdZ0wxRzRD6kUP3XyNZUTHc,1444
11
11
  appmesh/tcp_messages.py,sha256=H9S_iCy0IuufY2v50_SUgRvcyQmJsySG65tBe_xb3Ko,1878
12
12
  appmesh/tcp_transport.py,sha256=0hRSp5fpL9wKB05JIyIRIuyBC8w1IdokryhMDHqtN4M,8946
13
- appmesh-1.6.7.dist-info/METADATA,sha256=9c0FJtxDPsRIu2CL3K4A7IkAAKLalRm0cpdbBk_j-6A,11828
14
- appmesh-1.6.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
- appmesh-1.6.7.dist-info/top_level.txt,sha256=-y0MNQOGJxUzLdHZ6E_Rfv5_LNCkV-GTmOBME_b6pg8,8
16
- appmesh-1.6.7.dist-info/RECORD,,
13
+ appmesh-1.6.8.dist-info/METADATA,sha256=bDxN-Vm4004kC7FdM52yMx-YcmNcFzexBDYBr3p6db4,11828
14
+ appmesh-1.6.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ appmesh-1.6.8.dist-info/top_level.txt,sha256=-y0MNQOGJxUzLdHZ6E_Rfv5_LNCkV-GTmOBME_b6pg8,8
16
+ appmesh-1.6.8.dist-info/RECORD,,