@prosopo/client-bundle-example 2.10.22 → 2.10.24
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.
- package/.turbo/turbo-build$colon$cjs.log +2 -2
- package/.turbo/turbo-build$colon$tsc.log +11 -11
- package/.turbo/turbo-build.log +3 -3
- package/CHANGELOG.md +51 -0
- package/package.json +9 -9
- package/src/frictionless-explicit.html +8 -0
- package/src/frictionless-manual-start.html +224 -0
- package/src/image-explicit.html +8 -0
- package/src/plugins/navigation-injector.ts +2 -0
- package/src/plugins/placement-injector.ts +153 -0
- package/src/pow-implicit-sessionid.html +223 -0
- package/src/puzzle-bind-explicit.html +98 -0
- package/src/puzzle-explicit.html +8 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vite.config.ts +10 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<link href="https://cdn.muicss.com/mui-0.10.3/css/mui.min.css" rel="stylesheet" type="text/css"/>
|
|
5
|
+
<link href="styles/field.css" rel="stylesheet" type="text/css"/>
|
|
6
|
+
|
|
7
|
+
<script src="index.js"></script>
|
|
8
|
+
<title>Procaptcha demo: PoW - Client Session Id</title>
|
|
9
|
+
<link href="styles/captcha.css" rel="stylesheet" type="text/css"/>
|
|
10
|
+
<script type="module">
|
|
11
|
+
// Function to update CAPTCHA status display is now provided by status-log-injector
|
|
12
|
+
|
|
13
|
+
// The site's own per-user session id. Rendered onto the widget via
|
|
14
|
+
// data-sessionid and sent again at verify as clientSessionId; the
|
|
15
|
+
// provider only accepts the token when the two agree.
|
|
16
|
+
//
|
|
17
|
+
// `?mismatch=1` deliberately verifies with a DIFFERENT id, which must
|
|
18
|
+
// be rejected — that is the half of the contract that proves the
|
|
19
|
+
// correlation is actually enforced rather than silently skipped.
|
|
20
|
+
const CLIENT_SESSION_ID = 'cypress-session-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee';
|
|
21
|
+
const MISMATCHED_SESSION_ID = 'cypress-session-99999999-9999-9999-9999-999999999999';
|
|
22
|
+
const verifyingSessionId = () =>
|
|
23
|
+
new URLSearchParams(window.location.search).get('mismatch') === '1'
|
|
24
|
+
? MISMATCHED_SESSION_ID
|
|
25
|
+
: CLIENT_SESSION_ID;
|
|
26
|
+
|
|
27
|
+
window.addEventListener('load', () => {
|
|
28
|
+
|
|
29
|
+
window.setIsError = (isError) => {
|
|
30
|
+
const messageContainer = document.getElementById('messageContainer');
|
|
31
|
+
messageContainer.style.color = isError ? 'red' : 'black';
|
|
32
|
+
messageContainer.style.display = 'block';
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
window.setMessage = (message) => {
|
|
36
|
+
document.getElementById('message').innerText = message;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
window.onLoggedIn = (token) => {
|
|
40
|
+
updateCaptchaStatus('Logged in, fetching private resource', 'info');
|
|
41
|
+
const url = new URL("/private", config.serverUrl).href;
|
|
42
|
+
console.log("getting private resource with token ", token, "at", url);
|
|
43
|
+
fetch(url, {
|
|
44
|
+
method: "GET",
|
|
45
|
+
headers: {
|
|
46
|
+
Origin: "http://localhost:9232", // TODO: change this to env var
|
|
47
|
+
"Content-Type": "application/json",
|
|
48
|
+
Authorization: `Bearer ${token}`,
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
.then(async (res) => {
|
|
52
|
+
try {
|
|
53
|
+
const jsonRes = await res.json();
|
|
54
|
+
if (res.status === 200) {
|
|
55
|
+
updateCaptchaStatus(`Successfully accessed private resource`, 'success');
|
|
56
|
+
setMessage(jsonRes.message);
|
|
57
|
+
} else {
|
|
58
|
+
updateCaptchaStatus(`Failed to access private resource: ${res.status}`, 'error');
|
|
59
|
+
}
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.log(err);
|
|
62
|
+
updateCaptchaStatus(`Error parsing response: ${err}`, 'error');
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
.catch((err) => {
|
|
66
|
+
console.log(err);
|
|
67
|
+
updateCaptchaStatus(`Fetch error: ${err}`, 'error');
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
window.onActionHandler = () => {
|
|
72
|
+
updateCaptchaStatus('Form submission initiated', 'info');
|
|
73
|
+
|
|
74
|
+
const procaptchaElements = document.getElementsByName('procaptcha-response');
|
|
75
|
+
|
|
76
|
+
if (!procaptchaElements.length) {
|
|
77
|
+
updateCaptchaStatus('Error: No CAPTCHA response found', 'error');
|
|
78
|
+
alert("Must complete captcha");
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const procaptchaToken = procaptchaElements[0].value;
|
|
83
|
+
updateCaptchaStatus(`Token received: ${procaptchaToken.substring(0, 15)}...`, 'success');
|
|
84
|
+
|
|
85
|
+
const payload = {
|
|
86
|
+
email: document.getElementById('email').value,
|
|
87
|
+
name: document.getElementById('name').value,
|
|
88
|
+
password: document.getElementById('password').value,
|
|
89
|
+
siteKey: import.meta.env.PROSOPO_SITE_KEY_POW,
|
|
90
|
+
"procaptcha-response": procaptchaToken,
|
|
91
|
+
clientSessionId: verifyingSessionId(),
|
|
92
|
+
};
|
|
93
|
+
const url = new URL('signup', import.meta.env.PROSOPO_SERVER_URL).href;
|
|
94
|
+
console.log("posting to", url, "with payload", payload);
|
|
95
|
+
fetch(url, {
|
|
96
|
+
method: "POST",
|
|
97
|
+
headers: {
|
|
98
|
+
"Content-Type": "application/json",
|
|
99
|
+
},
|
|
100
|
+
body: JSON.stringify(payload),
|
|
101
|
+
contentType: "application/json",
|
|
102
|
+
}).then((response) => {
|
|
103
|
+
return new Promise((resolve) => response.json()
|
|
104
|
+
.then((json) => resolve({
|
|
105
|
+
status: response.status,
|
|
106
|
+
ok: response.ok,
|
|
107
|
+
json,
|
|
108
|
+
})))
|
|
109
|
+
})
|
|
110
|
+
.then(async ({status, json, ok}) => {
|
|
111
|
+
console.log("status", status, "json", json.message, "ok", ok);
|
|
112
|
+
console.log("json", json)
|
|
113
|
+
try {
|
|
114
|
+
if (status !== 200) {
|
|
115
|
+
updateCaptchaStatus(`API Error: ${json.message}`, 'error');
|
|
116
|
+
setIsError(true);
|
|
117
|
+
setMessage(json.message);
|
|
118
|
+
} else {
|
|
119
|
+
updateCaptchaStatus(`API Success: ${json.message}`, 'success');
|
|
120
|
+
setIsError(false);
|
|
121
|
+
setMessage(json.message);
|
|
122
|
+
}
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.log(err);
|
|
125
|
+
updateCaptchaStatus(`Error processing response: ${err}`, 'error');
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
.catch((err) => {
|
|
129
|
+
console.log(err);
|
|
130
|
+
updateCaptchaStatus(`Fetch error: ${err}`, 'error');
|
|
131
|
+
setIsError(true);
|
|
132
|
+
setMessage("Error: " + err);
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
updateCaptchaStatus('Page loaded - Initializing CAPTCHA system', 'info');
|
|
137
|
+
|
|
138
|
+
// Monitor DOM for CAPTCHA initialization
|
|
139
|
+
const observer = new MutationObserver((mutations) => {
|
|
140
|
+
mutations.forEach((mutation) => {
|
|
141
|
+
if (mutation.addedNodes.length) {
|
|
142
|
+
for (let i = 0; i < mutation.addedNodes.length; i++) {
|
|
143
|
+
const node = mutation.addedNodes[i];
|
|
144
|
+
if (node.classList && (node.classList.contains('procaptcha') ||
|
|
145
|
+
node.querySelector && node.querySelector('.procaptcha'))) {
|
|
146
|
+
updateCaptchaStatus('CAPTCHA DOM elements initialized', 'info');
|
|
147
|
+
observer.disconnect();
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
window.onCaptchaFailed = function () {
|
|
160
|
+
console.log('Challenge failed');
|
|
161
|
+
updateCaptchaStatus('Challenge failed - CAPTCHA verification could not be completed', 'error');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
window.onCaptchaVerified = (output) => {
|
|
165
|
+
console.log('Challenge passed');
|
|
166
|
+
updateCaptchaStatus('Challenge passed successfully!', 'success');
|
|
167
|
+
updateCaptchaStatus(`Token generated: ${output.substring(0, 15)}...`, 'success');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Add custom event listener for CAPTCHA verification stages
|
|
171
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
172
|
+
setTimeout(() => {
|
|
173
|
+
updateCaptchaStatus('CAPTCHA script loaded and initialized', 'info');
|
|
174
|
+
updateCaptchaStatus('Waiting for user interaction...', 'info');
|
|
175
|
+
}, 500);
|
|
176
|
+
});
|
|
177
|
+
</script>
|
|
178
|
+
<script id="procaptchaScript" type="module" src="%VITE_BUNDLE_URL%" async defer></script>
|
|
179
|
+
</head>
|
|
180
|
+
<body>
|
|
181
|
+
|
|
182
|
+
<div class="mui-container">
|
|
183
|
+
<h1>Proof of Work CAPTCHA - Client Session Id</h1>
|
|
184
|
+
<p>Renders with <code>data-sessionid</code> and verifies with the same <code>clientSessionId</code>.</p>
|
|
185
|
+
|
|
186
|
+
<!-- CAPTCHA Status Display will be injected by the status-log-injector plugin -->
|
|
187
|
+
|
|
188
|
+
<form action="%PROSOPO_SERVER_URL%/signup" method="POST" class="mui-form">
|
|
189
|
+
<h2>Example Login Form</h2>
|
|
190
|
+
<div class="mui-textfield mui-textfield--float-label">
|
|
191
|
+
<label for="name">Name</label>
|
|
192
|
+
<input id="name" type="text" name="name" required/>
|
|
193
|
+
</div>
|
|
194
|
+
<div class="mui-textfield mui-textfield--float-label">
|
|
195
|
+
<label for="email">Email Address</label>
|
|
196
|
+
<input id="email" type="email" name="email" required/>
|
|
197
|
+
</div>
|
|
198
|
+
<div class="mui-textfield mui-textfield--float-label">
|
|
199
|
+
<label for="password">Password</label>
|
|
200
|
+
<input id="password" type="password" name="password" required/>
|
|
201
|
+
</div>
|
|
202
|
+
<div class="mui-textfield mui-textfield--float-label">
|
|
203
|
+
<!-- Dev sitekey -->
|
|
204
|
+
<div
|
|
205
|
+
class="procaptcha"
|
|
206
|
+
data-theme="light"
|
|
207
|
+
data-sitekey="%PROSOPO_SITE_KEY_POW%"
|
|
208
|
+
data-sessionid="cypress-session-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
|
|
209
|
+
data-failed-callback="onCaptchaFailed"
|
|
210
|
+
data-callback="onCaptchaVerified"
|
|
211
|
+
></div>
|
|
212
|
+
</div>
|
|
213
|
+
<div id="messageContainer" style="display: none; color: black;"><span id="message"></span></div>
|
|
214
|
+
<button type="button" class="mui-btn mui-btn--raised" onclick="onActionHandler()" data-cy="submit-button">Submit</button>
|
|
215
|
+
</form>
|
|
216
|
+
|
|
217
|
+
<!-- Console output display area -->
|
|
218
|
+
<div id="console-output" class="console-output" style="display: none;"></div>
|
|
219
|
+
|
|
220
|
+
<!-- Explanation will be injected by the explanation-injector plugin -->
|
|
221
|
+
</div>
|
|
222
|
+
</body>
|
|
223
|
+
</html>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<link href="https://cdn.muicss.com/mui-0.10.3/css/mui.min.css" rel="stylesheet" type="text/css"/>
|
|
5
|
+
<link href="styles/field.css" rel="stylesheet" type="text/css"/>
|
|
6
|
+
|
|
7
|
+
<script src="index.js"></script>
|
|
8
|
+
<title>Procaptcha Puzzle Mode - Bound Button</title>
|
|
9
|
+
<link href="styles/captcha.css" rel="stylesheet" type="text/css"/>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div class="mui-container">
|
|
13
|
+
<h1>Procaptcha Puzzle Mode - Bound Button</h1>
|
|
14
|
+
<p>
|
|
15
|
+
The widget is rendered once and bound to the form's own submit button with
|
|
16
|
+
<code>bind: "#verify-btn"</code>. Clicking the button triggers that widget's challenge
|
|
17
|
+
without ticking the checkbox. Add <code>?placement=float</code> to anchor the
|
|
18
|
+
challenge to the widget instead of centring it.
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<!-- CAPTCHA Status Display will be injected by the status-log-injector plugin -->
|
|
22
|
+
|
|
23
|
+
<form id="demo-form" class="mui-form">
|
|
24
|
+
<h2>Example Form</h2>
|
|
25
|
+
|
|
26
|
+
<div class="mui-textfield mui-textfield--float-label">
|
|
27
|
+
<label for="name">Name</label>
|
|
28
|
+
<input type="text" id="name" name="name" required />
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div class="mui-textfield mui-textfield--float-label">
|
|
32
|
+
<label for="email">Email</label>
|
|
33
|
+
<input type="email" id="email" name="email" required />
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<!-- The container for the CAPTCHA -->
|
|
37
|
+
<div id="procaptcha-container"></div>
|
|
38
|
+
|
|
39
|
+
<button type="submit" id="verify-btn" class="mui-btn mui-btn--raised mui-btn--primary">Verify & submit</button>
|
|
40
|
+
|
|
41
|
+
<div id="error"></div>
|
|
42
|
+
</form>
|
|
43
|
+
|
|
44
|
+
<div id="result" class="info-box" style="display: none;"></div>
|
|
45
|
+
|
|
46
|
+
<!-- Console output display area -->
|
|
47
|
+
<div id="console-output" class="console-output" style="display: none;"></div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<script type="module">
|
|
51
|
+
import { render } from "%VITE_BUNDLE_URL%"
|
|
52
|
+
|
|
53
|
+
function handleCaptchaResponse(token) {
|
|
54
|
+
window.updateCaptchaStatus('Challenge passed successfully!', 'success');
|
|
55
|
+
window.updateCaptchaStatus(`Token generated: ${token.substring(0, 15)}...`, 'success');
|
|
56
|
+
|
|
57
|
+
const resultElement = document.getElementById('result');
|
|
58
|
+
resultElement.replaceChildren();
|
|
59
|
+
const heading = document.createElement('strong');
|
|
60
|
+
heading.textContent = 'Form submitted with:';
|
|
61
|
+
resultElement.append(heading);
|
|
62
|
+
for (const line of [
|
|
63
|
+
`- Name: ${document.getElementById('name').value}`,
|
|
64
|
+
`- Email: ${document.getElementById('email').value}`,
|
|
65
|
+
'- Procaptcha verified: Yes',
|
|
66
|
+
]) {
|
|
67
|
+
resultElement.append(document.createElement('br'), document.createTextNode(line));
|
|
68
|
+
}
|
|
69
|
+
resultElement.style.display = 'block';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function handleCaptchaFailed() {
|
|
73
|
+
window.updateCaptchaStatus('Challenge failed - CAPTCHA verification could not be completed', 'error');
|
|
74
|
+
document.getElementById('error').textContent = 'CAPTCHA verification failed. Please try again.';
|
|
75
|
+
document.getElementById('error').style.display = 'block';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
document.addEventListener('DOMContentLoaded', async function() {
|
|
79
|
+
// ?placement=float anchors the challenge to the widget; the default is popup.
|
|
80
|
+
const placement =
|
|
81
|
+
new URLSearchParams(window.location.search).get("placement") === "float"
|
|
82
|
+
? "float"
|
|
83
|
+
: "popup";
|
|
84
|
+
window.updateCaptchaStatus('Challenge placement: ' + placement, 'info');
|
|
85
|
+
|
|
86
|
+
const widgetId = await render(document.getElementById('procaptcha-container'), {
|
|
87
|
+
siteKey: import.meta.env.PROSOPO_SITE_KEY_PUZZLE,
|
|
88
|
+
placement,
|
|
89
|
+
bind: "#verify-btn",
|
|
90
|
+
callback: handleCaptchaResponse,
|
|
91
|
+
"failed-callback": handleCaptchaFailed
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
window.updateCaptchaStatus('Widget ' + widgetId + ' bound to #verify-btn', 'info');
|
|
95
|
+
});
|
|
96
|
+
</script>
|
|
97
|
+
</body>
|
|
98
|
+
</html>
|
package/src/puzzle-explicit.html
CHANGED
|
@@ -135,8 +135,16 @@
|
|
|
135
135
|
window.updateCaptchaStatus('Initializing CAPTCHA with explicit render call', 'info');
|
|
136
136
|
|
|
137
137
|
// Render the CAPTCHA
|
|
138
|
+
// ?placement=float anchors the challenge to the widget; the default is popup.
|
|
139
|
+
const placement =
|
|
140
|
+
new URLSearchParams(window.location.search).get("placement") === "float"
|
|
141
|
+
? "float"
|
|
142
|
+
: "popup";
|
|
143
|
+
window.updateCaptchaStatus('Challenge placement: ' + placement, 'info');
|
|
144
|
+
|
|
138
145
|
const widgetId = render(document.getElementById('procaptcha-container'), {
|
|
139
146
|
siteKey: import.meta.env.PROSOPO_SITE_KEY_PUZZLE,
|
|
147
|
+
placement,
|
|
140
148
|
callback: handleCaptchaResponse,
|
|
141
149
|
"failed-callback": handleCaptchaFailed
|
|
142
150
|
});
|