dt360-py-jwt-manager 1.1.3__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.

Potentially problematic release.


This version of dt360-py-jwt-manager might be problematic. Click here for more details.

@@ -0,0 +1,185 @@
1
+ Metadata-Version: 2.5
2
+ Name: dt360-py-jwt-manager
3
+ Version: 1.1.3
4
+ Summary: A Python package for managing JWT tokens in the DT360 platform.
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: cryptography>=42
7
+ Requires-Dist: flask<4,>=3.0
8
+ Requires-Dist: pyjwt<3,>=2.15.1
9
+ Description-Content-Type: text/markdown
10
+
11
+ # DT360 Python JWT Manager
12
+
13
+ JWT codec and Flask integration for the DT360 services. The package wraps
14
+ PyJWT's codec, derives short HMAC secrets to a 64-byte HS512 key, and can
15
+ resolve keys from a Flask app or request execution context.
16
+
17
+ ## Project structure
18
+
19
+ The layers have separate responsibilities and share the same codec:
20
+
21
+ ```text
22
+ src/dt360_py_jwt_manager/
23
+ jwt.py PyJWT-compatible codec and module functions
24
+ core/
25
+ FlaskJWTManager.py Flask config, key selection, and callbacks
26
+ JwtManager.py Docsmanager-specific token helpers
27
+ helper/
28
+ JwtKeyDerivation.py HS512 key normalization and derivation
29
+ URLEncodedTokenUtilities.py Java-compatible encrypted-token decoding
30
+ ```
31
+
32
+ For the full architecture, component responsibilities, key-resolution flows,
33
+ security behavior, packaging details, and end-to-end usage examples, see
34
+ [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
35
+
36
+ `PyJWT` in `jwt.py` is the common signing and verification layer. It owns
37
+ algorithm-aware key preparation, the optional legacy-key fallback, strict
38
+ minimum-key handling, and the single candidate-key verification path. The
39
+ module-level `encode()` and `decode()` functions are convenience entry points
40
+ that use one default codec instance.
41
+
42
+ `JWTManager` (also exported as `FlaskJWTManager`) is the Flask adapter. It
43
+ selects keys from a context-local override, callback/provider, or Flask app
44
+ configuration and delegates signing and verification to `PyJWT`. If key
45
+ selection needs the header and claims, it reads them together in one unverified
46
+ parse, then verifies the signature before returning claims. It does not
47
+ construct application-specific claims. `JwtManager` is the Docsmanager
48
+ adapter: it maps V1, V2, Box, and Spring key types to the Java service's token
49
+ formats, builds the expected claims, and normalizes Bearer or URL-encrypted
50
+ tokens before delegating cryptographic work to `PyJWT`.
51
+
52
+ `JwtKeyDerivation` contains the shared key-format rules. Both adapters pass
53
+ normalized source secrets to the codec; neither reimplements PBKDF2 or stores
54
+ a parallel set of padded legacy keys. `URLEncodedTokenUtilities` handles only
55
+ the Java URL-token envelope and does not participate in JWT signing.
56
+
57
+ ## Replace a direct PyJWT import
58
+
59
+ Replace `import jwt as jwt_lib` with:
60
+
61
+ ```python
62
+ from dt360_py_jwt_manager import jwt as jwt_lib
63
+
64
+ claims = jwt_lib.decode(
65
+ token,
66
+ secret,
67
+ algorithms=["HS512"],
68
+ options={"enforce_minimum_key_length": True},
69
+ )
70
+ ```
71
+
72
+ The package's module-level `encode()` and `decode()` functions use a codec
73
+ configured with `enforce_minimum_key_length=True`. You can also create an
74
+ instance with `PyJWT(options={"enforce_minimum_key_length": True})`.
75
+
76
+ Short HMAC secrets are converted to a 64-byte key using the Java-compatible
77
+ PBKDF2-HMAC-SHA512 parameters: 220,000 iterations, the fixed salt, and the
78
+ `JWT-HS512-V1` context. This allows HS256, HS384, and HS512 operations to meet
79
+ their minimum key lengths. RSA keys are passed to PyJWT unchanged, where the
80
+ minimum RSA key size is enforced.
81
+
82
+ ## Use from Flask
83
+
84
+ ```python
85
+ from flask import current_app
86
+ from dt360_py_jwt_manager import JWTManager
87
+
88
+ jwt = JWTManager(app)
89
+ app.config["JWT_SECRET_KEY"] = Globals.key
90
+ app.config["JWT_SIGNING_KEYS"] = [
91
+ Globals.key,
92
+ Globals.user_key,
93
+ Globals.spring_key,
94
+ ]
95
+ app.config["JWT_DECODE_ALGORITHMS"] = ["HS512", "HS384", "HS256", "RS256"]
96
+
97
+ # Optional provider for request or tenant-specific key selection.
98
+ app.config["JWT_KEY_PROVIDER"] = lambda purpose, header, payload: current_app.config[
99
+ "JWT_SECRET_KEY"
100
+ ]
101
+ ```
102
+
103
+ `JWTManager` registers itself in `app.extensions` and reads configuration when
104
+ encoding or decoding, so keys can be set after `JWTManager(app)`. It supports
105
+ the `decode_key_loader`, `invalid_token_loader`, `expired_token_loader`, and
106
+ `unauthorized_loader` callback registration patterns. A key loader receives
107
+ the unverified header and payload; its returned key is still signature-verified
108
+ by the wrapper.
109
+
110
+ This package replaces the PyJWT codec and supplies Flask key configuration. If
111
+ the application uses Flask-JWT-Extended's `@jwt_required` or
112
+ `get_jwt_identity()`, keep that extension for request authentication and
113
+ register the wrapper's resolver as its decode-key callback. The callback must
114
+ return the derived key that matched the signature. Returning the original
115
+ short secret would make the decorator verify with that short secret again.
116
+
117
+ If Flask-JWT-Extended remains responsible for request decorators, keep its
118
+ manager and replace only the direct PyJWT import:
119
+
120
+ ```python
121
+ from flask_jwt_extended import JWTManager as FlaskJWTManager
122
+ from dt360_py_jwt_manager import jwt as jwt_lib
123
+
124
+ flask_jwt = FlaskJWTManager(app)
125
+
126
+ @flask_jwt.decode_key_loader
127
+ def load_decode_key(jwt_header, jwt_data):
128
+ return jwt_lib.resolve_verification_key(
129
+ extract_token_from_request(),
130
+ app.config["JWT_SIGNING_KEYS"],
131
+ algorithms=app.config["JWT_DECODE_ALGORITHMS"],
132
+ allow_legacy_key=app.config.get("JWT_ALLOW_LEGACY_KEY", False),
133
+ )
134
+
135
+ @flask_jwt.encode_key_loader
136
+ def load_encode_key(identity):
137
+ return jwt_lib.prepare_key(
138
+ app.config["JWT_SECRET_KEY"], app.config.get("JWT_ALGORITHM", "HS512")
139
+ )
140
+ ```
141
+
142
+ Flask-JWT-Extended's decode-key callback receives the unverified header and
143
+ claims and must return the key used by its decorator to verify the token. The
144
+ resolver checks which configured source key matches the token, returns its
145
+ derived key, and leaves expiration and claim checks to Flask-JWT-Extended.
146
+ This is the integration point that makes `@jwt_required()` use the same
147
+ derivation rules as direct calls to `jwt.encode()` and `jwt.decode()`. The
148
+ encode-key callback applies the same derivation to tokens created with
149
+ `create_access_token()`. Set `JWT_ALGORITHM="HS512"` explicitly when the
150
+ service must issue HS512 tokens.
151
+
152
+ For an explicit execution-context override, use:
153
+
154
+ ```python
155
+ with jwt.key_context(tenant_secret):
156
+ claims = jwt.decode(token, algorithms=["HS512"])
157
+ ```
158
+
159
+ Keys resolve in this order: `key_context()`, a registered decode key loader,
160
+ `JWT_KEY_PROVIDER`, `JWT_SIGNING_KEYS` (decode only), and `JWT_SECRET_KEY`.
161
+ The provider receives `(purpose, header, payload)` and may return one key or an
162
+ ordered sequence of keys.
163
+
164
+ ## Existing and legacy tokens
165
+
166
+ When decoding a short HMAC secret, the derived key is tried first. To continue
167
+ accepting tokens signed with the old short raw key, enable
168
+ `allow_legacy_key=True` on `PyJWT`/`JwtManager`, or set
169
+ `app.config["JWT_ALLOW_LEGACY_KEY"] = True` for the Flask manager. The legacy
170
+ key is used for verification only. PBKDF2 does not add entropy; new deployments
171
+ should use random secrets with at least 64 bytes.
172
+
173
+ The high-level `JwtManager` also provides V1, V2, and Box token helpers, along
174
+ with generic claims creation for application-specific JWTs. Session helpers
175
+ store `sessionData` as a JSON string, matching the Java format.
176
+
177
+ ## Build artifacts
178
+
179
+ From the project root, build the source archive and wheel with:
180
+
181
+ ```powershell
182
+ python -m build
183
+ ```
184
+
185
+ The artifacts are written to `dist/`.
@@ -0,0 +1,175 @@
1
+ # DT360 Python JWT Manager
2
+
3
+ JWT codec and Flask integration for the DT360 services. The package wraps
4
+ PyJWT's codec, derives short HMAC secrets to a 64-byte HS512 key, and can
5
+ resolve keys from a Flask app or request execution context.
6
+
7
+ ## Project structure
8
+
9
+ The layers have separate responsibilities and share the same codec:
10
+
11
+ ```text
12
+ src/dt360_py_jwt_manager/
13
+ jwt.py PyJWT-compatible codec and module functions
14
+ core/
15
+ FlaskJWTManager.py Flask config, key selection, and callbacks
16
+ JwtManager.py Docsmanager-specific token helpers
17
+ helper/
18
+ JwtKeyDerivation.py HS512 key normalization and derivation
19
+ URLEncodedTokenUtilities.py Java-compatible encrypted-token decoding
20
+ ```
21
+
22
+ For the full architecture, component responsibilities, key-resolution flows,
23
+ security behavior, packaging details, and end-to-end usage examples, see
24
+ [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
25
+
26
+ `PyJWT` in `jwt.py` is the common signing and verification layer. It owns
27
+ algorithm-aware key preparation, the optional legacy-key fallback, strict
28
+ minimum-key handling, and the single candidate-key verification path. The
29
+ module-level `encode()` and `decode()` functions are convenience entry points
30
+ that use one default codec instance.
31
+
32
+ `JWTManager` (also exported as `FlaskJWTManager`) is the Flask adapter. It
33
+ selects keys from a context-local override, callback/provider, or Flask app
34
+ configuration and delegates signing and verification to `PyJWT`. If key
35
+ selection needs the header and claims, it reads them together in one unverified
36
+ parse, then verifies the signature before returning claims. It does not
37
+ construct application-specific claims. `JwtManager` is the Docsmanager
38
+ adapter: it maps V1, V2, Box, and Spring key types to the Java service's token
39
+ formats, builds the expected claims, and normalizes Bearer or URL-encrypted
40
+ tokens before delegating cryptographic work to `PyJWT`.
41
+
42
+ `JwtKeyDerivation` contains the shared key-format rules. Both adapters pass
43
+ normalized source secrets to the codec; neither reimplements PBKDF2 or stores
44
+ a parallel set of padded legacy keys. `URLEncodedTokenUtilities` handles only
45
+ the Java URL-token envelope and does not participate in JWT signing.
46
+
47
+ ## Replace a direct PyJWT import
48
+
49
+ Replace `import jwt as jwt_lib` with:
50
+
51
+ ```python
52
+ from dt360_py_jwt_manager import jwt as jwt_lib
53
+
54
+ claims = jwt_lib.decode(
55
+ token,
56
+ secret,
57
+ algorithms=["HS512"],
58
+ options={"enforce_minimum_key_length": True},
59
+ )
60
+ ```
61
+
62
+ The package's module-level `encode()` and `decode()` functions use a codec
63
+ configured with `enforce_minimum_key_length=True`. You can also create an
64
+ instance with `PyJWT(options={"enforce_minimum_key_length": True})`.
65
+
66
+ Short HMAC secrets are converted to a 64-byte key using the Java-compatible
67
+ PBKDF2-HMAC-SHA512 parameters: 220,000 iterations, the fixed salt, and the
68
+ `JWT-HS512-V1` context. This allows HS256, HS384, and HS512 operations to meet
69
+ their minimum key lengths. RSA keys are passed to PyJWT unchanged, where the
70
+ minimum RSA key size is enforced.
71
+
72
+ ## Use from Flask
73
+
74
+ ```python
75
+ from flask import current_app
76
+ from dt360_py_jwt_manager import JWTManager
77
+
78
+ jwt = JWTManager(app)
79
+ app.config["JWT_SECRET_KEY"] = Globals.key
80
+ app.config["JWT_SIGNING_KEYS"] = [
81
+ Globals.key,
82
+ Globals.user_key,
83
+ Globals.spring_key,
84
+ ]
85
+ app.config["JWT_DECODE_ALGORITHMS"] = ["HS512", "HS384", "HS256", "RS256"]
86
+
87
+ # Optional provider for request or tenant-specific key selection.
88
+ app.config["JWT_KEY_PROVIDER"] = lambda purpose, header, payload: current_app.config[
89
+ "JWT_SECRET_KEY"
90
+ ]
91
+ ```
92
+
93
+ `JWTManager` registers itself in `app.extensions` and reads configuration when
94
+ encoding or decoding, so keys can be set after `JWTManager(app)`. It supports
95
+ the `decode_key_loader`, `invalid_token_loader`, `expired_token_loader`, and
96
+ `unauthorized_loader` callback registration patterns. A key loader receives
97
+ the unverified header and payload; its returned key is still signature-verified
98
+ by the wrapper.
99
+
100
+ This package replaces the PyJWT codec and supplies Flask key configuration. If
101
+ the application uses Flask-JWT-Extended's `@jwt_required` or
102
+ `get_jwt_identity()`, keep that extension for request authentication and
103
+ register the wrapper's resolver as its decode-key callback. The callback must
104
+ return the derived key that matched the signature. Returning the original
105
+ short secret would make the decorator verify with that short secret again.
106
+
107
+ If Flask-JWT-Extended remains responsible for request decorators, keep its
108
+ manager and replace only the direct PyJWT import:
109
+
110
+ ```python
111
+ from flask_jwt_extended import JWTManager as FlaskJWTManager
112
+ from dt360_py_jwt_manager import jwt as jwt_lib
113
+
114
+ flask_jwt = FlaskJWTManager(app)
115
+
116
+ @flask_jwt.decode_key_loader
117
+ def load_decode_key(jwt_header, jwt_data):
118
+ return jwt_lib.resolve_verification_key(
119
+ extract_token_from_request(),
120
+ app.config["JWT_SIGNING_KEYS"],
121
+ algorithms=app.config["JWT_DECODE_ALGORITHMS"],
122
+ allow_legacy_key=app.config.get("JWT_ALLOW_LEGACY_KEY", False),
123
+ )
124
+
125
+ @flask_jwt.encode_key_loader
126
+ def load_encode_key(identity):
127
+ return jwt_lib.prepare_key(
128
+ app.config["JWT_SECRET_KEY"], app.config.get("JWT_ALGORITHM", "HS512")
129
+ )
130
+ ```
131
+
132
+ Flask-JWT-Extended's decode-key callback receives the unverified header and
133
+ claims and must return the key used by its decorator to verify the token. The
134
+ resolver checks which configured source key matches the token, returns its
135
+ derived key, and leaves expiration and claim checks to Flask-JWT-Extended.
136
+ This is the integration point that makes `@jwt_required()` use the same
137
+ derivation rules as direct calls to `jwt.encode()` and `jwt.decode()`. The
138
+ encode-key callback applies the same derivation to tokens created with
139
+ `create_access_token()`. Set `JWT_ALGORITHM="HS512"` explicitly when the
140
+ service must issue HS512 tokens.
141
+
142
+ For an explicit execution-context override, use:
143
+
144
+ ```python
145
+ with jwt.key_context(tenant_secret):
146
+ claims = jwt.decode(token, algorithms=["HS512"])
147
+ ```
148
+
149
+ Keys resolve in this order: `key_context()`, a registered decode key loader,
150
+ `JWT_KEY_PROVIDER`, `JWT_SIGNING_KEYS` (decode only), and `JWT_SECRET_KEY`.
151
+ The provider receives `(purpose, header, payload)` and may return one key or an
152
+ ordered sequence of keys.
153
+
154
+ ## Existing and legacy tokens
155
+
156
+ When decoding a short HMAC secret, the derived key is tried first. To continue
157
+ accepting tokens signed with the old short raw key, enable
158
+ `allow_legacy_key=True` on `PyJWT`/`JwtManager`, or set
159
+ `app.config["JWT_ALLOW_LEGACY_KEY"] = True` for the Flask manager. The legacy
160
+ key is used for verification only. PBKDF2 does not add entropy; new deployments
161
+ should use random secrets with at least 64 bytes.
162
+
163
+ The high-level `JwtManager` also provides V1, V2, and Box token helpers, along
164
+ with generic claims creation for application-specific JWTs. Session helpers
165
+ store `sessionData` as a JSON string, matching the Java format.
166
+
167
+ ## Build artifacts
168
+
169
+ From the project root, build the source archive and wheel with:
170
+
171
+ ```powershell
172
+ python -m build
173
+ ```
174
+
175
+ The artifacts are written to `dist/`.