Skip to content

Commit da0bb72

Browse files
Adil-SLadil.slassierdnaxelaweb
authored
WIP feat captchEtat V2 (#224)
* feat: upgrade to CaptchEtat V2 --------- Co-authored-by: adil.slassi <adil.slassi@almaviacx.com> Co-authored-by: Florian ALEXANDRE <f.alexandre@novactive.com>
1 parent b11249e commit da0bb72

File tree

10 files changed

+168
-240
lines changed

10 files changed

+168
-240
lines changed

components/CaptchEtatBundle/src/bundle/Controller/CaptchEtatController.php

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,16 @@
55
namespace AlmaviaCX\Bundle\CaptchEtatBundle\Controller;
66

77
use AlmaviaCX\Bundle\CaptchEtat\Api\Gateway;
8-
use AlmaviaCX\Bundle\CaptchEtat\Challenge\ChallengeGenerator;
98
use Symfony\Component\HttpFoundation\Request;
109
use Symfony\Component\HttpFoundation\Response;
1110
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
1211

1312
class CaptchEtatController
1413
{
15-
protected Gateway $gateway;
16-
protected ChallengeGenerator $challengeGenerator;
17-
1814
public function __construct(
19-
Gateway $gateway,
20-
ChallengeGenerator $challengeGenerator
15+
Gateway $gateway
2116
) {
2217
$this->gateway = $gateway;
23-
$this->challengeGenerator = $challengeGenerator;
2418
}
2519

2620
/**
@@ -31,34 +25,20 @@ public function apiSimpleCaptchaEndpointAction(Request $request): Response
3125
$get = $request->get('get');
3226
$tech = $request->get('t');
3327
$type = $request->get('c');
34-
$content = $this->gateway->getSimpleCaptchaEndpoint($get, null, $tech, $type);
28+
$content = $this->gateway->getSimpleCaptchaEndpoint($get, $tech, $type);
3529
$response = new Response($content);
36-
if ('script-include' === $get) {
37-
$response->headers->set('Content-Type', 'text/javascript');
38-
} elseif ('image' === $get) {
39-
$response->headers->set('Content-Disposition', $response->headers->makeDisposition(
40-
ResponseHeaderBag::DISPOSITION_INLINE,
41-
'captcha.png'
42-
));
43-
$response->headers->set('Content-Type', 'image/png');
44-
} elseif ('sound' === $get) {
30+
31+
if ('sound' === $get) {
4532
$response->headers->set('Content-Disposition', $response->headers->makeDisposition(
4633
ResponseHeaderBag::DISPOSITION_INLINE,
4734
'captcha-sound.wave'
4835
));
49-
$response->headers->set('Content-Type', 'audio/wave');
36+
$response->headers->set('Content-Type', 'audio/x-wav');
37+
} else {
38+
$response->headers->set('Content-Type', 'application/json');
5039
}
5140
$response->setPrivate();
5241

5342
return $response;
5443
}
55-
56-
public function getCaptcha(): Response
57-
{
58-
$challenge = ($this->challengeGenerator)();
59-
$response = new Response($challenge->captchaHtml);
60-
$response->setPrivate();
61-
62-
return $response;
63-
}
6444
}

components/CaptchEtatBundle/src/bundle/Resources/config/routes.yaml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,3 @@ captchetat.endpoint:
22
path: /api/simple-captcha-endpoint
33
defaults:
44
_controller: 'AlmaviaCX\Bundle\CaptchEtatBundle\Controller\CaptchEtatController::apiSimpleCaptchaEndpointAction'
5-
6-
captchetat.html:
7-
path: /api/simple-captcha
8-
defaults:
9-
_controller: 'AlmaviaCX\Bundle\CaptchEtatBundle\Controller\CaptchEtatController::getCaptcha'

components/CaptchEtatBundle/src/bundle/Resources/config/services.yaml

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,6 @@ services:
2020
$url: '%captchetat_api_url%'
2121
$timeout: '%captchetat_timeout%'
2222

23-
AlmaviaCX\Bundle\CaptchEtat\Challenge\ChallengeGenerator:
24-
arguments:
25-
$configResolver: '@Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface'
26-
$localeConverter: '@Ibexa\Core\MVC\Symfony\Locale\LocaleConverterInterface'
27-
$gateway: '@AlmaviaCX\Bundle\CaptchEtat\Api\Gateway'
28-
$translator: '@Symfony\Contracts\Translation\TranslatorInterface'
29-
$logger: '@AlmaviaCX\Bundle\CaptchEtat\Logger\CaptchEtatLogger'
30-
3123
AlmaviaCX\Bundle\CaptchEtat\Challenge\ChallengeValidator:
3224
arguments:
3325
$gateway: '@AlmaviaCX\Bundle\CaptchEtat\Api\Gateway'
@@ -47,10 +39,16 @@ services:
4739
AlmaviaCX\Bundle\CaptchEtatBundle\Controller\CaptchEtatController:
4840
arguments:
4941
$gateway: '@AlmaviaCX\Bundle\CaptchEtat\Api\Gateway'
50-
$challengeGenerator: '@AlmaviaCX\Bundle\CaptchEtat\Challenge\ChallengeGenerator'
5142
tags:
5243
- controller.service_arguments
5344

45+
AlmaviaCX\Bundle\CaptchEtat\Twig\CaptchEtatExtension:
46+
arguments:
47+
$configResolver: '@Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface'
48+
$localeConverter: '@Ibexa\Core\MVC\Symfony\Locale\LocaleConverterInterface'
49+
tags:
50+
- { name: twig.extension }
51+
5452
# AlmaviaCX\Bundle\CaptchEtat\FormBuilder\FieldType\Field\Mapper\ButtonFieldMapperDecorator:
5553
# decorates: Ibexa\FormBuilder\FieldType\Field\Mapper\ButtonFieldMapper
5654
# arguments:
Lines changed: 88 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,6 @@
1-
2-
/**
3-
* @param {HTMLElement} container
4-
*/
5-
export const addScriptsToHead = container => {
6-
const scriptEls = container.querySelectorAll('script');
7-
/** @type {HTMLScriptElement} */
8-
for (const scriptEl of scriptEls) {
9-
if (
10-
!document.head.querySelector('script[src="' + scriptEl.src + '"]')
11-
) {
12-
/** @type {HTMLScriptElement} */
13-
const el = document.createElement('script');
14-
el.type = scriptEl.type;
15-
el.src = scriptEl.src;
16-
document.head.appendChild(el);
17-
}
18-
scriptEl.remove()
19-
}
20-
};
21-
22-
/**
23-
* @param {HTMLElement} container
24-
*/
25-
export const addLinksToHead = container => {
26-
const linkEls = container.querySelectorAll('link');
27-
/** @type {HTMLLinkElement} */
28-
for (const linkEl of linkEls) {
29-
if (!document.head.querySelector('link[href="' + linkEl.href + '"]')) {
30-
/** @type {HTMLLinkElement} */
31-
const el = document.createElement('link');
32-
el.type = linkEl.type;
33-
el.rel = linkEl.rel;
34-
el.href = linkEl.href;
35-
document.head.appendChild(el);
36-
}
37-
linkEl.remove()
38-
}
39-
};
1+
function getApiUrl(get, type) {
2+
return "/api/simple-captcha-endpoint?get=" + get + "&c=" + type;
3+
}
404

415
const captchaEtat = (function () {
426
function _init(container = document) {
@@ -45,39 +9,98 @@ const captchaEtat = (function () {
459
for (const widget of widgets) {
4610
const htmlContainer = widget.querySelector('.captcha-html-container');
4711
const idInput = widget.querySelector('.captcha-input [name*="[captcha_id]"]')
48-
if(htmlContainer.querySelector('.captcha-html')) {
49-
return
50-
}
12+
const captchaType = widget.dataset.type
13+
fetch(
14+
getApiUrl("image", captchaType),
15+
{
16+
method: "GET",
17+
headers: {"Content-Type": "application/json"}
18+
}
19+
).then((response => {
20+
if (!response.ok) {
21+
throw new Error("La réponse n'est pas OK");
22+
}
23+
return response.json()
24+
})).then((response => {
25+
let uuid = response.uuid;
26+
27+
const captchaContainer = document.createElement("div");
28+
captchaContainer.style.display = "flex";
29+
captchaContainer.style.flexDirection = "row";
30+
htmlContainer.prepend(captchaContainer);
31+
32+
const imageElement = document.createElement("img");
33+
imageElement.src = `${response.imageb64}`;
34+
imageElement.alt = "Rewrite the captcha code";
35+
captchaContainer.appendChild(imageElement);
36+
37+
const refreshCaptcha = () => {
38+
fetch(
39+
getApiUrl("image", captchaType),
40+
{
41+
method: "GET",
42+
headers: {"Content-Type": "application/json"}
43+
}
44+
).then((refreshResponse => {
45+
if (!refreshResponse.ok) throw new Error("La réponse n'est pas OK");
46+
return refreshResponse.json()
47+
})).then((refreshResponse => {
48+
imageElement.src = refreshResponse.imageb64;
49+
uuid = refreshResponse.uuid;
50+
idInput.setAttribute("value", uuid)
51+
})).catch((e => {
52+
console.error("Erreur lors de la récupération du captcha")
53+
}))
54+
}
55+
56+
const logosElement = document.createElement("div");
57+
logosElement.style.marginLeft = "10px";
58+
logosElement.style.display = "flex";
59+
logosElement.style.flexDirection = "column";
60+
logosElement.style.justifyContent = "space-evenly";
61+
captchaContainer.appendChild(logosElement);
5162

52-
fetch('/api/simple-captcha').then((response) => {
53-
return response.text();
54-
}).then((html) => {
55-
const tmp = document.createElement('div');
56-
tmp.innerHTML = html;
57-
addLinksToHead(tmp);
58-
addScriptsToHead(tmp);
59-
const originalIdInput = tmp.querySelector('[name^="BDC_VCID"]')
60-
idInput.value = originalIdInput.value;
63+
const playButtonElement = document.createElement("button");
64+
playButtonElement.type = "button";
65+
playButtonElement.title = "Speak the captcha code";
66+
playButtonElement.alt = "Speak the captcha code";
67+
playButtonElement.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="25" height="25" viewBox="0 0 75 75"\nid="iconSound">\n<path d="M39.389,13.769 L22.235,28.606 L6,28.606 L6,47.699 L21.989,47.699 L39.389,62.75 L39.389,13.769z"\n style="stroke:#111;stroke-width:5;stroke-linejoin:round;fill:#111;"/>\n<path d="M48,27.6a19.5,19.5 0 0 1 0,21.4M55.1,20.5a30,30 0 0 1 0,35.6M61.6,14a38.8,38.8 0 0 1 0,48.6"\n style="fill:none;stroke:#111;stroke-width:5;stroke-linecap:round"/>\n</svg>';
68+
logosElement.appendChild(playButtonElement),
69+
playButtonElement.addEventListener("click", () => {
70+
try {
71+
new Audio(getApiUrl("sound", captchaType) + "&t=" + uuid).play()
72+
} catch (e) {
73+
console.error("Erreur lors de la récupération du son");
74+
refreshCaptcha()
75+
}
76+
});
6177

62-
const tmpRoot = tmp.children.item(0);
63-
tmpRoot.removeAttribute('id')
64-
tmpRoot.classList.add('captcha-html');
78+
const refreshButtonElement = document.createElement("button");
79+
refreshButtonElement.type = "button";
80+
refreshButtonElement.title = "Generate a new captcha";
81+
refreshButtonElement.alt = "Generate a new captcha";
82+
refreshButtonElement.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 17 20" fill="none"\nid="iconReload">\n<path d="M9.00003 4.0001C11.1 4.0001 13.1 4.8001 14.6 6.3001C17.7 9.4001 17.7 14.5001 14.6 17.6001C12.8 19.5001 10.3 20.2001 7.90003 19.9001L8.40003 17.9001C10.1 18.1001 11.9 17.5001 13.2 16.2001C15.5 13.9001 15.5 10.1001 13.2 7.7001C12.1 6.6001 10.5 6.0001 9.00003 6.0001V10.6001L4.00003 5.6001L9.00003 0.600098V4.0001ZM3.30003 17.6001C0.700029 15.0001 0.300029 11.0001 2.10003 7.9001L3.60003 9.4001C2.50003 11.6001 2.90003 14.4001 4.80003 16.2001C5.30003 16.7001 5.90003 17.1001 6.60003 17.4001L6.00003 19.4001C5.00003 19.0001 4.10003 18.4001 3.30003 17.6001V17.6001Z"\n fill="black"/>\n</svg>';
83+
logosElement.appendChild(refreshButtonElement);
84+
refreshButtonElement.addEventListener("click", refreshCaptcha);
6585

66-
htmlContainer.prepend(tmpRoot);
86+
idInput.setAttribute("value", uuid)
6787

68-
const soundLink = widget.querySelector('.BDC_SoundLink');
69-
const answerInput = widget.querySelector('.captcha-input input[type="text"]');
70-
soundLink.addEventListener('click', function (e) {
71-
if(answerInput) {
72-
answer.removeAttribute('disabled');
73-
answerInput.focus()
74-
}
75-
})
76-
})
88+
if (captchaType.includes("FR")) {
89+
playButtonElement.setAttribute("title", "Énoncer le code du captcha")
90+
refreshButtonElement.setAttribute("title", "Générer un nouveau captcha")
91+
imageElement.alt = "Recopier le code de sécurité de cette image";
92+
playButtonElement.title = "Enoncer le code du captcha";
93+
playButtonElement.alt = "Enoncer le code du captcha";
94+
refreshButtonElement.title = "Générer un nouveau captcha";
95+
refreshButtonElement.alt = "Générer un nouveau captcha";
96+
}
97+
})).catch((e => {
98+
console.error("Erreur lors de la récupération du captcha")
99+
}))
77100
}
78101
}
79102

80-
return { init: _init };
103+
return {init: _init};
81104
})();
82105

83106
export default captchaEtat;

components/CaptchEtatBundle/src/bundle/Resources/templates/captchetat-fields.html.twig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
{% block captchetat_widget %}
1010
{% apply spaceless %}
1111
{%- set attr = attr|merge({
12-
'class': 'widget-container row align-items-center js-captcha-widget'
12+
'class': 'widget-container row align-items-center js-captcha-widget',
13+
'data-type': captchetat_type()
1314
}) -%}
1415
<div {{ block('widget_container_attributes') }}>
1516
<div class="captcha captcha-html-container col-lg-4 col-md-6 d-flex align-items-center">

components/CaptchEtatBundle/src/lib/Api/Gateway.php

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,45 +34,33 @@ public function __construct(
3434
}
3535

3636
public function getSimpleCaptchaEndpoint(
37-
string $captchaType = 'html',
38-
?string $mode = null,
37+
string $captchaType,
3938
?string $tech = null,
4039
string $type = 'numerique6_7CaptchaFR'
4140
): string {
4241
$token = $this->oauthGateway->getOauth20Token();
43-
$available = [
44-
'html',
45-
'layout-stylesheet',
46-
'script-include',
42+
$availableTypes = [
4743
'image',
48-
'reload-icon',
49-
'sound-icon',
50-
'reload-disabled-icon',
51-
'sound-disabled-icon',
5244
'sound',
53-
'p',
5445
];
5546

56-
if (!in_array($captchaType, $available)) {
47+
if (!in_array($captchaType, $availableTypes)) {
5748
throw new RuntimeException(
5849
sprintf(
5950
'c value "%s" not alloweb. One of %s waiting',
6051
$captchaType,
61-
implode(', ', $available)
52+
implode(', ', $availableTypes)
6253
)
6354
);
6455
}
6556

66-
$service = '/piste/captcha/simple-captcha-endpoint';
57+
$service = '/piste/captchetat/v2/simple-captcha-endpoint';
6758
$method = 'GET';
6859

6960
$queryParams = [
7061
'get' => $captchaType,
7162
'c' => $type,
7263
];
73-
if ($mode) {
74-
$queryParams['mode'] = $mode;
75-
}
7664
if ($tech) {
7765
$queryParams['t'] = $tech;
7866
}
@@ -113,7 +101,7 @@ public function validateChallenge(
113101
string $answer
114102
): bool {
115103
$token = $this->oauthGateway->getOauth20Token();
116-
$service = '/piste/captcha/valider-captcha';
104+
$service = '/piste/captchetat/v2/valider-captcha';
117105
$method = 'POST';
118106

119107
$url = $this->url.$service;
@@ -125,7 +113,7 @@ public function validateChallenge(
125113
],
126114
'timeout' => $this->timeout,
127115
'body' => json_encode([
128-
'id' => $captchaId,
116+
'uuid' => $captchaId,
129117
'code' => $answer,
130118
]),
131119
];

components/CaptchEtatBundle/src/lib/Api/OauthGateway.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public function getOauth20Token(): string
5252
'grant_type' => 'client_credentials',
5353
'client_id' => $this->clientId,
5454
'client_secret' => $this->clientSecret,
55-
'scope' => ['resource.READ', 'piste.captchetat'],
55+
'scope' => ['aife.captchetatv2', 'piste.captchetat'],
5656
];
5757

5858
$url = $this->url.$service;

0 commit comments

Comments
 (0)