requestshield 0.1.4 → 0.1.5

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,210 +1,210 @@
1
- # Browser SDK — Manual mode
2
-
3
- Manual mode gives the application control over **when** a token is obtained and **how**
4
- it is carried to the backend. Choose it when the request escapes Seamless interception,
5
- or when the application must carry the token in an application-controlled header or
6
- request-body field.
7
-
8
- Prerequisites: a public App Key, a backend integration that extracts the chosen carrier,
9
- and a protected operation that is **not** also configured for Seamless mode. One
10
- operation uses one mode.
11
-
12
- ## 1. Load the hosted SDK
13
-
14
- Omit `data-protect` when the page uses Manual mode only — its presence is what turns on
15
- Seamless interception:
16
-
17
- ```html
18
- <script
19
- src="SCRIPT_URL_FROM_CONTRACT"
20
- data-app-key="YOUR_APP_KEY"
21
- defer
22
- ></script>
23
- ```
24
-
25
- | Attribute | Required | Notes |
26
- | --- | --- | --- |
27
- | `src` | Yes | Take it from `requestshield contract` → `browser.script_url`. Currently `https://static.intellifend.ai/intellifend.js`; read the contract rather than trusting this line. |
28
- | `data-app-key` | Yes | Public App Key. Must match the backend's configured key. |
29
- | `defer` | Recommended | Keeps HTML parsing unblocked. |
30
-
31
- The SDK reads configuration from `document.currentScript`, so use **exactly one tag**.
32
- A second tag re-initializes rather than merging, and the last one to run wins.
33
-
34
- In templated or bundled apps, put the tag in the HTML entry point (`index.html`,
35
- `_document.tsx`, the base Django/Rails/Thymeleaf layout) rather than injecting it from
36
- component code, so it is present before any protected request can fire.
37
-
38
- Applications with a custom script loader may configure the same setup in JavaScript
39
- instead:
40
-
41
- ```javascript
42
- IntelliFend.init({appKey: 'YOUR_APP_KEY'});
43
- ```
44
-
45
- Use script attributes *or* explicit initialization for initial setup, not both.
46
-
47
- ## 2. Obtain a token immediately before the request
48
-
49
- Call `getToken()` right before the protected operation, and attach the header **only
50
- when the returned string is non-empty**:
51
-
52
- ```javascript
53
- async function createAccount(payload) {
54
- const token = await IntelliFend.getToken();
55
- const headers = {'Content-Type': 'application/json'};
56
-
57
- if (token) {
58
- headers['X-IntelliFend-Token'] = token;
59
- }
60
-
61
- return fetch('/api/register', {
62
- method: 'POST',
63
- headers,
64
- body: JSON.stringify(payload),
65
- });
66
- }
67
- ```
68
-
69
- `getToken()` resolves to a string. A non-empty value is that request's token; an empty
70
- value means the header is omitted and **the backend applies its configured policy**.
71
-
72
- That last part is why the empty case needs no client-side handling. The decision about
73
- what an absent token means already lives at the backend, so wrapping this in a retry
74
- loop, blocking the submit, showing an error, or substituting a placeholder all move
75
- policy into the browser — the one place it must not be. Attach it when present, omit it
76
- when not, and let the backend decide.
77
-
78
- ### Optional action
79
-
80
- ```javascript
81
- const token = await IntelliFend.getToken({action: 'create-account'});
82
- ```
83
-
84
- Case-sensitive. Use an action **only** when IntelliFend has supplied a corresponding
85
- backend integration rule — a browser-supplied value is not backend configuration, and
86
- inventing one has no effect on the decision.
87
-
88
- ## 3. Backend extracts the carrier
89
-
90
- The Spring Boot starter reads `X-IntelliFend-Token` from the request itself, so no
91
- extraction code is needed. With the Java core SDK, application code extracts the header
92
- and passes the value unchanged to `verify()`.
93
-
94
- A dedicated request-body field is available when the application contract requires it;
95
- application code must then extract that field before verification. Prefer the header
96
- unless there is a real constraint — it keeps the browser and backend halves symmetric
97
- and works with the starter as-is.
98
-
99
- See `backend-spring-boot.md` or `backend-java-core.md` for the extraction side.
100
-
101
- ## Framework call-site patterns
102
-
103
- The recurring failure is attaching the token in one place while some requests reach the
104
- endpoint through another. Enumerate every path to the endpoint before editing — and if
105
- there are several, that is a strong signal Seamless mode is the better fit.
106
-
107
- **Axios interceptor** — covers calls made through one Axios instance. Resolve both the
108
- allow-list and each request to an origin-plus-pathname key. Pathname-only matching can
109
- attach a token to the wrong origin, and resolving `config.url` against `location.origin`
110
- ignores the instance's `baseURL`:
111
-
112
- ```javascript
113
- const protectedEndpointKeys = new Set(
114
- PROTECTED_ENDPOINTS.map((endpoint) => {
115
- const url = new URL(endpoint, location.origin);
116
- return `${url.origin}${url.pathname}`;
117
- }),
118
- );
119
-
120
- api.interceptors.request.use(async (config) => {
121
- let requestUrl;
122
-
123
- try {
124
- requestUrl = new URL(api.getUri(config), document.baseURI);
125
- } catch {
126
- // An unresolved destination must never receive a RequestShield token.
127
- return config;
128
- }
129
-
130
- const requestKey = `${requestUrl.origin}${requestUrl.pathname}`;
131
- if (protectedEndpointKeys.has(requestKey)) {
132
- // Remove a token retained by a reused Axios request config.
133
- for (const headerName of Object.keys(config.headers)) {
134
- if (headerName.toLowerCase() === 'x-intellifend-token') {
135
- delete config.headers[headerName];
136
- }
137
- }
138
- const token = await IntelliFend.getToken();
139
- if (token) {
140
- config.headers['X-IntelliFend-Token'] = token;
141
- }
142
- }
143
- return config;
144
- });
145
- ```
146
-
147
- `api.getUri(config)` applies that Axios instance's request configuration, including
148
- `baseURL`; resolving its result against `location.origin` also handles a root-relative
149
- base URL. Query strings and fragments are deliberately absent from the comparison, but
150
- the origin is mandatory.
151
-
152
- Before shipping the interceptor, verify both directions against the actual instance:
153
-
154
- - With `axios.create({baseURL: '/api'})`, a call to `post('/register')` resolves to the
155
- current origin plus `/api/register` and matches that protected endpoint.
156
- - A call to `https://other.example/api/register` does not match the current origin's
157
- `/api/register`, does not call `getToken()`, and receives no RequestShield header.
158
-
159
- **React / TanStack Query** — obtain the token inside the mutation function, never in a
160
- `useEffect` or component state. A token held in state goes stale, gets reused across
161
- retries, and comes back `token_replayed`.
162
-
163
- **Retries** — any retry wrapper must call `getToken()` again on each attempt. Reusing
164
- the first token is the most common cause of a retry being blocked while the original
165
- attempt succeeded.
166
-
167
- **Server-rendered forms** — a native form POST cannot carry a header. Either move the
168
- submission to `fetch`, or agree a request-body carrier with IntelliFend. Never put the
169
- token in a hidden field improvised on your own, and never in a query parameter.
170
-
171
- ## Cross-origin application APIs
172
-
173
- When the protected endpoint is on another origin, its CORS response must allow the page
174
- origin, the intended HTTP methods, and the `X-IntelliFend-Token` header. Without the
175
- header in `Access-Control-Allow-Headers`, the browser strips it and the backend sees
176
- every request as missing a token — which reads as a browser-integration bug but is a
177
- CORS configuration bug.
178
-
179
- ## Content Security Policy
180
-
181
- If the app sends a CSP, merge these sources into the existing directives — never replace
182
- the policy, and keep every source the app already declares:
183
-
184
- ```http
185
- Content-Security-Policy:
186
- script-src 'self' https://static.intellifend.ai;
187
- connect-src 'self' https://challenge.intellifend.ai;
188
- worker-src 'self' blob:;
189
- ```
190
-
191
- Confirm the hosts **and the directive list** against `requestshield contract` and the
192
- customer deployment guide before editing a live policy — this skill is not the authority
193
- on either. If `getToken()` returns an empty string on a page that sends a CSP, a blocked
194
- directive is the first thing to check: the browser console names what it refused, which
195
- beats guessing at the policy.
196
-
197
- Applications that do not send a CSP need no header changes.
198
-
199
- ## Token handling rules
200
-
201
- - One token for one protected request.
202
- - A new token before retrying a protected operation.
203
- - Forward a non-empty token unchanged; omit the header when it is empty.
204
- - Never cache, persist, transform, or log a token.
205
- - Never place a token in a URL path, query string, or fragment.
206
- - Never verify the same request with both `@RequestShieldProtected` and a manual
207
- `RequestShieldClient.verify()`.
208
- - Exclude `X-IntelliFend-Token` from logs, analytics, and error reporting — check the
209
- error-reporting SDK's header allowlist, which often captures request headers by
210
- default.
1
+ # Browser SDK — Manual mode
2
+
3
+ Manual mode gives the application control over **when** a token is obtained and **how**
4
+ it is carried to the backend. Choose it when the request escapes Seamless interception,
5
+ or when the application must carry the token in an application-controlled header or
6
+ request-body field.
7
+
8
+ Prerequisites: a public App Key, a backend integration that extracts the chosen carrier,
9
+ and a protected operation that is **not** also configured for Seamless mode. One
10
+ operation uses one mode.
11
+
12
+ ## 1. Load the hosted SDK
13
+
14
+ Omit `data-protect` when the page uses Manual mode only — its presence is what turns on
15
+ Seamless interception:
16
+
17
+ ```html
18
+ <script
19
+ src="SCRIPT_URL_FROM_CONTRACT"
20
+ data-app-key="YOUR_APP_KEY"
21
+ defer
22
+ ></script>
23
+ ```
24
+
25
+ | Attribute | Required | Notes |
26
+ | --- | --- | --- |
27
+ | `src` | Yes | Take it from `requestshield contract` → `browser.script_url`. Currently `https://static.intellifend.ai/intellifend.js`; read the contract rather than trusting this line. |
28
+ | `data-app-key` | Yes | Public App Key. Must match the backend's configured key. |
29
+ | `defer` | Recommended | Keeps HTML parsing unblocked. |
30
+
31
+ The SDK reads configuration from `document.currentScript`, so use **exactly one tag**.
32
+ A second tag re-initializes rather than merging, and the last one to run wins.
33
+
34
+ In templated or bundled apps, put the tag in the HTML entry point (`index.html`,
35
+ `_document.tsx`, the base Django/Rails/Thymeleaf layout) rather than injecting it from
36
+ component code, so it is present before any protected request can fire.
37
+
38
+ Applications with a custom script loader may configure the same setup in JavaScript
39
+ instead:
40
+
41
+ ```javascript
42
+ IntelliFend.init({appKey: 'YOUR_APP_KEY'});
43
+ ```
44
+
45
+ Use script attributes *or* explicit initialization for initial setup, not both.
46
+
47
+ ## 2. Obtain a token immediately before the request
48
+
49
+ Call `getToken()` right before the protected operation, and attach the header **only
50
+ when the returned string is non-empty**:
51
+
52
+ ```javascript
53
+ async function createAccount(payload) {
54
+ const token = await IntelliFend.getToken();
55
+ const headers = {'Content-Type': 'application/json'};
56
+
57
+ if (token) {
58
+ headers['X-IntelliFend-Token'] = token;
59
+ }
60
+
61
+ return fetch('/api/register', {
62
+ method: 'POST',
63
+ headers,
64
+ body: JSON.stringify(payload),
65
+ });
66
+ }
67
+ ```
68
+
69
+ `getToken()` resolves to a string. A non-empty value is that request's token; an empty
70
+ value means the header is omitted and **the backend applies its configured policy**.
71
+
72
+ That last part is why the empty case needs no client-side handling. The decision about
73
+ what an absent token means already lives at the backend, so wrapping this in a retry
74
+ loop, blocking the submit, showing an error, or substituting a placeholder all move
75
+ policy into the browser — the one place it must not be. Attach it when present, omit it
76
+ when not, and let the backend decide.
77
+
78
+ ### Optional action
79
+
80
+ ```javascript
81
+ const token = await IntelliFend.getToken({action: 'create-account'});
82
+ ```
83
+
84
+ Case-sensitive. Use an action **only** when IntelliFend has supplied a corresponding
85
+ backend integration rule — a browser-supplied value is not backend configuration, and
86
+ inventing one has no effect on the decision.
87
+
88
+ ## 3. Backend extracts the carrier
89
+
90
+ The Spring Boot starter reads `X-IntelliFend-Token` from the request itself, so no
91
+ extraction code is needed. With the Java core SDK, application code extracts the header
92
+ and passes the value unchanged to `verify()`.
93
+
94
+ A dedicated request-body field is available when the application contract requires it;
95
+ application code must then extract that field before verification. Prefer the header
96
+ unless there is a real constraint — it keeps the browser and backend halves symmetric
97
+ and works with the starter as-is.
98
+
99
+ See `backend-spring-boot.md` or `backend-java-core.md` for the extraction side.
100
+
101
+ ## Framework call-site patterns
102
+
103
+ The recurring failure is attaching the token in one place while some requests reach the
104
+ endpoint through another. Enumerate every path to the endpoint before editing — and if
105
+ there are several, that is a strong signal Seamless mode is the better fit.
106
+
107
+ **Axios interceptor** — covers calls made through one Axios instance. Resolve both the
108
+ allow-list and each request to an origin-plus-pathname key. Pathname-only matching can
109
+ attach a token to the wrong origin, and resolving `config.url` against `location.origin`
110
+ ignores the instance's `baseURL`:
111
+
112
+ ```javascript
113
+ const protectedEndpointKeys = new Set(
114
+ PROTECTED_ENDPOINTS.map((endpoint) => {
115
+ const url = new URL(endpoint, location.origin);
116
+ return `${url.origin}${url.pathname}`;
117
+ }),
118
+ );
119
+
120
+ api.interceptors.request.use(async (config) => {
121
+ let requestUrl;
122
+
123
+ try {
124
+ requestUrl = new URL(api.getUri(config), document.baseURI);
125
+ } catch {
126
+ // An unresolved destination must never receive a RequestShield token.
127
+ return config;
128
+ }
129
+
130
+ const requestKey = `${requestUrl.origin}${requestUrl.pathname}`;
131
+ if (protectedEndpointKeys.has(requestKey)) {
132
+ // Remove a token retained by a reused Axios request config.
133
+ for (const headerName of Object.keys(config.headers)) {
134
+ if (headerName.toLowerCase() === 'x-intellifend-token') {
135
+ delete config.headers[headerName];
136
+ }
137
+ }
138
+ const token = await IntelliFend.getToken();
139
+ if (token) {
140
+ config.headers['X-IntelliFend-Token'] = token;
141
+ }
142
+ }
143
+ return config;
144
+ });
145
+ ```
146
+
147
+ `api.getUri(config)` applies that Axios instance's request configuration, including
148
+ `baseURL`; resolving its result against `location.origin` also handles a root-relative
149
+ base URL. Query strings and fragments are deliberately absent from the comparison, but
150
+ the origin is mandatory.
151
+
152
+ Before shipping the interceptor, verify both directions against the actual instance:
153
+
154
+ - With `axios.create({baseURL: '/api'})`, a call to `post('/register')` resolves to the
155
+ current origin plus `/api/register` and matches that protected endpoint.
156
+ - A call to `https://other.example/api/register` does not match the current origin's
157
+ `/api/register`, does not call `getToken()`, and receives no RequestShield header.
158
+
159
+ **React / TanStack Query** — obtain the token inside the mutation function, never in a
160
+ `useEffect` or component state. A token held in state goes stale, gets reused across
161
+ retries, and comes back `token_replayed`.
162
+
163
+ **Retries** — any retry wrapper must call `getToken()` again on each attempt. Reusing
164
+ the first token is the most common cause of a retry being blocked while the original
165
+ attempt succeeded.
166
+
167
+ **Server-rendered forms** — a native form POST cannot carry a header. Either move the
168
+ submission to `fetch`, or agree a request-body carrier with IntelliFend. Never put the
169
+ token in a hidden field improvised on your own, and never in a query parameter.
170
+
171
+ ## Cross-origin application APIs
172
+
173
+ When the protected endpoint is on another origin, its CORS response must allow the page
174
+ origin, the intended HTTP methods, and the `X-IntelliFend-Token` header. Without the
175
+ header in `Access-Control-Allow-Headers`, the browser strips it and the backend sees
176
+ every request as missing a token — which reads as a browser-integration bug but is a
177
+ CORS configuration bug.
178
+
179
+ ## Content Security Policy
180
+
181
+ If the app sends a CSP, merge these sources into the existing directives — never replace
182
+ the policy, and keep every source the app already declares:
183
+
184
+ ```http
185
+ Content-Security-Policy:
186
+ script-src 'self' https://static.intellifend.ai;
187
+ connect-src 'self' https://challenge.intellifend.ai;
188
+ worker-src 'self' blob:;
189
+ ```
190
+
191
+ Confirm the hosts **and the directive list** against `requestshield contract` and the
192
+ customer deployment guide before editing a live policy — this skill is not the authority
193
+ on either. If `getToken()` returns an empty string on a page that sends a CSP, a blocked
194
+ directive is the first thing to check: the browser console names what it refused, which
195
+ beats guessing at the policy.
196
+
197
+ Applications that do not send a CSP need no header changes.
198
+
199
+ ## Token handling rules
200
+
201
+ - One token for one protected request.
202
+ - A new token before retrying a protected operation.
203
+ - Forward a non-empty token unchanged; omit the header when it is empty.
204
+ - Never cache, persist, transform, or log a token.
205
+ - Never place a token in a URL path, query string, or fragment.
206
+ - Never verify the same request with both `@RequestShieldProtected` and a manual
207
+ `RequestShieldClient.verify()`.
208
+ - Exclude `X-IntelliFend-Token` from logs, analytics, and error reporting — check the
209
+ error-reporting SDK's header allowlist, which often captures request headers by
210
+ default.