sentinelsup 0.2.4__tar.gz → 0.2.5__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.
- {sentinelsup-0.2.4 → sentinelsup-0.2.5}/PKG-INFO +9 -6
- {sentinelsup-0.2.4 → sentinelsup-0.2.5}/README.md +8 -5
- {sentinelsup-0.2.4 → sentinelsup-0.2.5}/examples/django_middleware.py +1 -1
- {sentinelsup-0.2.4 → sentinelsup-0.2.5}/examples/flask_signup_guard.py +2 -2
- {sentinelsup-0.2.4 → sentinelsup-0.2.5}/pyproject.toml +1 -1
- {sentinelsup-0.2.4 → sentinelsup-0.2.5}/sentinel/__init__.py +13 -6
- {sentinelsup-0.2.4 → sentinelsup-0.2.5}/tests/test_client.py +20 -3
- {sentinelsup-0.2.4 → sentinelsup-0.2.5}/.github/workflows/ci.yml +0 -0
- {sentinelsup-0.2.4 → sentinelsup-0.2.5}/.gitignore +0 -0
- {sentinelsup-0.2.4 → sentinelsup-0.2.5}/LICENSE +0 -0
- {sentinelsup-0.2.4 → sentinelsup-0.2.5}/sentinel/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sentinelsup
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: Official Maskbreak SDK for browser-backed fraud checks and cloud/Tor IP lookups.
|
|
5
5
|
Project-URL: Homepage, https://maskbreak.com
|
|
6
6
|
Project-URL: Documentation, https://maskbreak.com/api
|
|
@@ -49,7 +49,7 @@ env var, and a test:
|
|
|
49
49
|
|
|
50
50
|
> Fetch https://maskbreak.com/integrate.md and follow it to add Maskbreak fraud
|
|
51
51
|
> protection to this app — protect signup, login, and checkout. Read the API key
|
|
52
|
-
> from the server-only
|
|
52
|
+
> from the server-only MASKBREAK_API_KEY environment variable; I will configure the
|
|
53
53
|
> secret separately. Never put it in client-side code. Show me how to test it.
|
|
54
54
|
|
|
55
55
|
[`integrate.md`](https://maskbreak.com/integrate.md) is the canonical
|
|
@@ -69,7 +69,7 @@ Python 3.8+. Get a free API key (no credit card) at [maskbreak.com/signup](https
|
|
|
69
69
|
import os
|
|
70
70
|
from sentinel import Sentinel
|
|
71
71
|
|
|
72
|
-
s = Sentinel(api_key=os.environ["
|
|
72
|
+
s = Sentinel(api_key=os.environ["MASKBREAK_API_KEY"]) # or omit — reads the env var itself
|
|
73
73
|
|
|
74
74
|
result = s.evaluate(
|
|
75
75
|
token=request.json["sentinelToken"],
|
|
@@ -90,6 +90,9 @@ to your verification/review flow; only `allow` is an approval. Keep API keys on
|
|
|
90
90
|
the server. An unavailable device layer or `raw["degraded"]` is not proof of a
|
|
91
91
|
clean visit; `degraded` describes the network layer only.
|
|
92
92
|
|
|
93
|
+
Since v0.2.5, `Sentinel()` without a key reads `MASKBREAK_API_KEY`; the older
|
|
94
|
+
`SENTINEL_KEY` and `SENTINEL_API_KEY` names are still read as fallbacks.
|
|
95
|
+
|
|
93
96
|
Check the signup email against the disposable-domain feed (checked
|
|
94
97
|
transiently, never stored), or look up an arbitrary IP with no browser
|
|
95
98
|
token at all:
|
|
@@ -171,7 +174,7 @@ from flask import Flask, request, abort, jsonify
|
|
|
171
174
|
from sentinel import Sentinel, SentinelError
|
|
172
175
|
|
|
173
176
|
app = Flask(__name__)
|
|
174
|
-
sentinel = Sentinel() # reads
|
|
177
|
+
sentinel = Sentinel() # reads MASKBREAK_API_KEY from env
|
|
175
178
|
|
|
176
179
|
@app.route("/signup", methods=["POST"])
|
|
177
180
|
def signup():
|
|
@@ -202,7 +205,7 @@ def signup():
|
|
|
202
205
|
from django.http import JsonResponse
|
|
203
206
|
from sentinel import Sentinel, SentinelError
|
|
204
207
|
|
|
205
|
-
sentinel = Sentinel() # reads
|
|
208
|
+
sentinel = Sentinel() # reads MASKBREAK_API_KEY from env
|
|
206
209
|
|
|
207
210
|
class FraudCheckMiddleware:
|
|
208
211
|
def __init__(self, get_response):
|
|
@@ -233,7 +236,7 @@ Runnable versions live in [`examples/`](./examples/).
|
|
|
233
236
|
|
|
234
237
|
| Option | Default | Description |
|
|
235
238
|
|--------|---------|-------------|
|
|
236
|
-
| `api_key` | `$
|
|
239
|
+
| `api_key` | `$MASKBREAK_API_KEY` (falls back to `$SENTINEL_KEY`, then `$SENTINEL_API_KEY`) | Your key starting with `sk_live_` |
|
|
237
240
|
| `endpoint` | `https://maskbreak.com` | Override base URL (for testing) |
|
|
238
241
|
| `timeout` | `5.0` | Per-request timeout in seconds |
|
|
239
242
|
|
|
@@ -20,7 +20,7 @@ env var, and a test:
|
|
|
20
20
|
|
|
21
21
|
> Fetch https://maskbreak.com/integrate.md and follow it to add Maskbreak fraud
|
|
22
22
|
> protection to this app — protect signup, login, and checkout. Read the API key
|
|
23
|
-
> from the server-only
|
|
23
|
+
> from the server-only MASKBREAK_API_KEY environment variable; I will configure the
|
|
24
24
|
> secret separately. Never put it in client-side code. Show me how to test it.
|
|
25
25
|
|
|
26
26
|
[`integrate.md`](https://maskbreak.com/integrate.md) is the canonical
|
|
@@ -40,7 +40,7 @@ Python 3.8+. Get a free API key (no credit card) at [maskbreak.com/signup](https
|
|
|
40
40
|
import os
|
|
41
41
|
from sentinel import Sentinel
|
|
42
42
|
|
|
43
|
-
s = Sentinel(api_key=os.environ["
|
|
43
|
+
s = Sentinel(api_key=os.environ["MASKBREAK_API_KEY"]) # or omit — reads the env var itself
|
|
44
44
|
|
|
45
45
|
result = s.evaluate(
|
|
46
46
|
token=request.json["sentinelToken"],
|
|
@@ -61,6 +61,9 @@ to your verification/review flow; only `allow` is an approval. Keep API keys on
|
|
|
61
61
|
the server. An unavailable device layer or `raw["degraded"]` is not proof of a
|
|
62
62
|
clean visit; `degraded` describes the network layer only.
|
|
63
63
|
|
|
64
|
+
Since v0.2.5, `Sentinel()` without a key reads `MASKBREAK_API_KEY`; the older
|
|
65
|
+
`SENTINEL_KEY` and `SENTINEL_API_KEY` names are still read as fallbacks.
|
|
66
|
+
|
|
64
67
|
Check the signup email against the disposable-domain feed (checked
|
|
65
68
|
transiently, never stored), or look up an arbitrary IP with no browser
|
|
66
69
|
token at all:
|
|
@@ -142,7 +145,7 @@ from flask import Flask, request, abort, jsonify
|
|
|
142
145
|
from sentinel import Sentinel, SentinelError
|
|
143
146
|
|
|
144
147
|
app = Flask(__name__)
|
|
145
|
-
sentinel = Sentinel() # reads
|
|
148
|
+
sentinel = Sentinel() # reads MASKBREAK_API_KEY from env
|
|
146
149
|
|
|
147
150
|
@app.route("/signup", methods=["POST"])
|
|
148
151
|
def signup():
|
|
@@ -173,7 +176,7 @@ def signup():
|
|
|
173
176
|
from django.http import JsonResponse
|
|
174
177
|
from sentinel import Sentinel, SentinelError
|
|
175
178
|
|
|
176
|
-
sentinel = Sentinel() # reads
|
|
179
|
+
sentinel = Sentinel() # reads MASKBREAK_API_KEY from env
|
|
177
180
|
|
|
178
181
|
class FraudCheckMiddleware:
|
|
179
182
|
def __init__(self, get_response):
|
|
@@ -204,7 +207,7 @@ Runnable versions live in [`examples/`](./examples/).
|
|
|
204
207
|
|
|
205
208
|
| Option | Default | Description |
|
|
206
209
|
|--------|---------|-------------|
|
|
207
|
-
| `api_key` | `$
|
|
210
|
+
| `api_key` | `$MASKBREAK_API_KEY` (falls back to `$SENTINEL_KEY`, then `$SENTINEL_API_KEY`) | Your key starting with `sk_live_` |
|
|
208
211
|
| `endpoint` | `https://maskbreak.com` | Override base URL (for testing) |
|
|
209
212
|
| `timeout` | `5.0` | Per-request timeout in seconds |
|
|
210
213
|
|
|
@@ -24,7 +24,7 @@ _GUARDED_PATHS = ("/api/checkout", "/api/withdraw", "/api/transfer")
|
|
|
24
24
|
class SentinelMiddleware:
|
|
25
25
|
def __init__(self, get_response):
|
|
26
26
|
self.get_response = get_response
|
|
27
|
-
self.sentinel = Sentinel() # reads
|
|
27
|
+
self.sentinel = Sentinel() # reads MASKBREAK_API_KEY, older names as fallback
|
|
28
28
|
|
|
29
29
|
def __call__(self, request):
|
|
30
30
|
if not request.path.startswith(_GUARDED_PATHS):
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Run:
|
|
4
4
|
pip install flask sentinelsup
|
|
5
|
-
export
|
|
5
|
+
export MASKBREAK_API_KEY=sk_live_...
|
|
6
6
|
python flask_signup_guard.py
|
|
7
7
|
"""
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@ from flask import Flask, abort, jsonify, request
|
|
|
12
12
|
from sentinel import Sentinel, SentinelError
|
|
13
13
|
|
|
14
14
|
app = Flask(__name__)
|
|
15
|
-
sentinel = Sentinel() # reads SENTINEL_KEY
|
|
15
|
+
sentinel = Sentinel() # reads MASKBREAK_API_KEY (older SENTINEL_KEY still works)
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
@app.route("/signup", methods=["POST"])
|
|
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
|
|
|
8
8
|
# would install a stranger's code. "sentinelsup" matches the npm scope
|
|
9
9
|
# (@sentinelsup/sdk).
|
|
10
10
|
name = "sentinelsup"
|
|
11
|
-
version = "0.2.
|
|
11
|
+
version = "0.2.5"
|
|
12
12
|
description = "Official Maskbreak SDK for browser-backed fraud checks and cloud/Tor IP lookups."
|
|
13
13
|
readme = "README.md"
|
|
14
14
|
requires-python = ">=3.8"
|
|
@@ -4,7 +4,7 @@ fraud detection API at https://maskbreak.com/v1/evaluate.
|
|
|
4
4
|
|
|
5
5
|
Usage:
|
|
6
6
|
from sentinel import Sentinel
|
|
7
|
-
s = Sentinel() # reads
|
|
7
|
+
s = Sentinel() # reads MASKBREAK_API_KEY from the env
|
|
8
8
|
result = s.evaluate(token=request.json["sentinelToken"])
|
|
9
9
|
if result.is_blocked:
|
|
10
10
|
abort(403)
|
|
@@ -18,7 +18,7 @@ from urllib import error, parse, request
|
|
|
18
18
|
|
|
19
19
|
DEFAULT_ENDPOINT = "https://maskbreak.com"
|
|
20
20
|
DEFAULT_TIMEOUT = 5.0
|
|
21
|
-
__version__ = "0.2.
|
|
21
|
+
__version__ = "0.2.5"
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class _NoRedirect(request.HTTPRedirectHandler):
|
|
@@ -78,13 +78,20 @@ class Sentinel:
|
|
|
78
78
|
endpoint: str = DEFAULT_ENDPOINT,
|
|
79
79
|
timeout: float = DEFAULT_TIMEOUT,
|
|
80
80
|
) -> None:
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
|
|
81
|
+
# MASKBREAK_API_KEY is the name every doc surface uses. SENTINEL_KEY
|
|
82
|
+
# and SENTINEL_API_KEY are older names, still read so existing installs
|
|
83
|
+
# keep working; the first non-empty one wins.
|
|
84
|
+
api_key = (
|
|
85
|
+
api_key
|
|
86
|
+
or os.environ.get("MASKBREAK_API_KEY")
|
|
87
|
+
or os.environ.get("SENTINEL_KEY")
|
|
88
|
+
or os.environ.get("SENTINEL_API_KEY")
|
|
89
|
+
)
|
|
84
90
|
if not api_key or not isinstance(api_key, str):
|
|
85
91
|
raise SentinelError(
|
|
86
92
|
"Sentinel: api_key is required. "
|
|
87
|
-
"Pass it explicitly or set
|
|
93
|
+
"Pass it explicitly or set MASKBREAK_API_KEY "
|
|
94
|
+
"(the older SENTINEL_KEY still works). "
|
|
88
95
|
"Get one free at https://maskbreak.com/signup"
|
|
89
96
|
)
|
|
90
97
|
self.api_key = api_key
|
|
@@ -108,12 +108,29 @@ class ClientTests(unittest.TestCase):
|
|
|
108
108
|
self.assertEqual(len(self.server.calls), 1, "redirect target must not receive the API key")
|
|
109
109
|
|
|
110
110
|
def test_environment_key_precedence(self):
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
env = {"MASKBREAK_API_KEY": "current", "SENTINEL_KEY": "primary", "SENTINEL_API_KEY": "legacy"}
|
|
112
|
+
with patch.dict(os.environ, env):
|
|
113
|
+
self.assertEqual(Sentinel().api_key, "current")
|
|
113
114
|
self.assertEqual(Sentinel("explicit").api_key, "explicit")
|
|
115
|
+
os.environ.pop("MASKBREAK_API_KEY")
|
|
116
|
+
self.assertEqual(Sentinel().api_key, "primary")
|
|
114
117
|
os.environ.pop("SENTINEL_KEY")
|
|
115
118
|
self.assertEqual(Sentinel().api_key, "legacy")
|
|
116
119
|
|
|
120
|
+
def test_environment_key_new_name_alone(self):
|
|
121
|
+
with patch.dict(os.environ, {"MASKBREAK_API_KEY": "current"}):
|
|
122
|
+
for old in ("SENTINEL_KEY", "SENTINEL_API_KEY"):
|
|
123
|
+
os.environ.pop(old, None)
|
|
124
|
+
self.assertEqual(Sentinel().api_key, "current")
|
|
125
|
+
|
|
126
|
+
def test_missing_key_names_the_current_variable(self):
|
|
127
|
+
with patch.dict(os.environ, {}):
|
|
128
|
+
for name in ("MASKBREAK_API_KEY", "SENTINEL_KEY", "SENTINEL_API_KEY"):
|
|
129
|
+
os.environ.pop(name, None)
|
|
130
|
+
with self.assertRaises(SentinelError) as caught:
|
|
131
|
+
Sentinel()
|
|
132
|
+
self.assertIn("MASKBREAK_API_KEY", str(caught.exception))
|
|
133
|
+
|
|
117
134
|
def test_result_helpers_keep_review_distinct_from_block(self):
|
|
118
135
|
for decision in ("allow", "review", "block"):
|
|
119
136
|
result = EvaluateResult(decision=decision)
|
|
@@ -132,7 +149,7 @@ class DjangoGuardTests(unittest.TestCase):
|
|
|
132
149
|
]:
|
|
133
150
|
with self.subTest(decision=decision, flag=flag):
|
|
134
151
|
view = Mock(return_value=SimpleNamespace(status_code=200))
|
|
135
|
-
with patch.dict(os.environ, {"
|
|
152
|
+
with patch.dict(os.environ, {"MASKBREAK_API_KEY": "sk_test_fixture"}):
|
|
136
153
|
middleware = example["SentinelMiddleware"](view)
|
|
137
154
|
middleware.sentinel = Mock()
|
|
138
155
|
middleware.sentinel.evaluate.return_value = EvaluateResult(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|