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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sentinelsup
3
- Version: 0.2.4
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 SENTINEL_KEY environment variable; I will configure the
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["SENTINEL_KEY"]) # or omit — reads the env var itself
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 SENTINEL_KEY (or SENTINEL_API_KEY) from env
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 SENTINEL_KEY (or SENTINEL_API_KEY) from env
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` | `$SENTINEL_KEY` (falls back to `$SENTINEL_API_KEY`) | Your key starting with `sk_live_` |
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 SENTINEL_KEY environment variable; I will configure the
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["SENTINEL_KEY"]) # or omit — reads the env var itself
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 SENTINEL_KEY (or SENTINEL_API_KEY) from env
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 SENTINEL_KEY (or SENTINEL_API_KEY) from env
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` | `$SENTINEL_KEY` (falls back to `$SENTINEL_API_KEY`) | Your key starting with `sk_live_` |
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 SENTINEL_KEY, with legacy fallback
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 SENTINEL_KEY=sk_live_...
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, with SENTINEL_API_KEY fallback
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.4"
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 SENTINEL_KEY or SENTINEL_API_KEY from the env
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.4"
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
- # SENTINEL_KEY is the name every doc surface uses; SENTINEL_API_KEY
82
- # is kept for existing installs that already adopted it.
83
- api_key = api_key or os.environ.get("SENTINEL_KEY") or os.environ.get("SENTINEL_API_KEY")
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 SENTINEL_KEY. "
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
- with patch.dict(os.environ, {"SENTINEL_KEY": "primary", "SENTINEL_API_KEY": "legacy"}):
112
- self.assertEqual(Sentinel().api_key, "primary")
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, {"SENTINEL_KEY": "sk_test_fixture"}):
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