Skip to content

Commit 665a2fd

Browse files
committed
allow paste otp
1 parent e1624a7 commit 665a2fd

File tree

3 files changed

+106
-184
lines changed

3 files changed

+106
-184
lines changed

web-assets/css/otp.css

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,6 @@ body {
1111
padding: 0;
1212
}
1313

14-
/* Chrome, Safari, Edge, Opera */
15-
input::-webkit-outer-spin-button,
16-
input::-webkit-inner-spin-button {
17-
-webkit-appearance: none;
18-
margin: 0;
19-
}
20-
21-
/* Firefox */
22-
input[type="number"] {
23-
-moz-appearance: textfield;
24-
}
25-
2614
.page-wrapper {
2715
margin-top: 60px;
2816
margin-left: auto;

web-assets/js/otp.js

Lines changed: 100 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -11,41 +11,49 @@ var qs = (function (a) {
1111

1212
$(document).ready(function () {
1313
window.history.forward();
14+
let formAction = qs["formAction"] || "#";
15+
const opt1 = 'https://auth.{{DOMAIN}}/continue';
16+
const opt2 = 'https://{{AUTH0DOMAIN}}/continue';
17+
if (!formAction.startsWith(opt1) && !formAction.startsWith(opt2)) {
18+
// looks like XSS attack
19+
formAction = "#"
20+
return false;
21+
}
1422
const resendToken = qs["resendToken"];
1523
const userId = qs["userId"];
1624
if (resendToken && userId) {
17-
const apiServerUrl = "https://api.{{DOMAIN}}/v3/users";
18-
$("#resend").click(function () {
19-
$.ajax({
20-
type: "POST",
21-
url: apiServerUrl + "/resendOtpEmail",
22-
contentType: "application/json",
23-
mimeType: "application/json",
24-
data: JSON.stringify({
25-
"param": {
26-
userId, resendToken
27-
}
28-
}),
29-
dataType: "json",
30-
success: function (result) {
31-
$("#notify").html("Email sent");
32-
$("#notify").closest(".message-wrapper").fadeIn();
33-
$("#resend").hide();
34-
},
35-
error: function (error) {
36-
if (error.responseJSON && error.responseJSON.result) {
37-
$("#error").html(error.responseJSON.result.content);
38-
$("#error").closest(".message-wrapper").fadeIn();
39-
$("#resend").hide();
40-
} else {
41-
$("#error").html("Unknown Error");
42-
$("#error").closest(".message-wrapper").fadeIn();
43-
}
44-
}
45-
});
25+
const apiServerUrl = "https://api.{{DOMAIN}}/v3/users";
26+
$("#resend").click(function () {
27+
$.ajax({
28+
type: "POST",
29+
url: apiServerUrl + "/resendOtpEmail",
30+
contentType: "application/json",
31+
mimeType: "application/json",
32+
data: JSON.stringify({
33+
"param": {
34+
userId, resendToken
35+
}
36+
}),
37+
dataType: "json",
38+
success: function (result) {
39+
$("#notify").html("Email sent");
40+
$("#notify").closest(".message-wrapper").fadeIn();
41+
$("#resend").hide();
42+
},
43+
error: function (error) {
44+
if (error.responseJSON && error.responseJSON.result) {
45+
$("#error").html(error.responseJSON.result.content);
46+
$("#error").closest(".message-wrapper").fadeIn();
47+
$("#resend").hide();
48+
} else {
49+
$("#error").html("Unknown Error");
50+
$("#error").closest(".message-wrapper").fadeIn();
51+
}
52+
}
4653
});
54+
});
4755
} else {
48-
$("#resend").hide();
56+
$("#resend").hide();
4957
}
5058
const errorMessage = qs["message"];
5159
if (errorMessage) {
@@ -54,124 +62,72 @@ $(document).ready(function () {
5462
}
5563

5664
$(".close-error").on("click", function () {
57-
$(this).closest(".message-wrapper").fadeOut();
65+
$(this).closest(".message-wrapper").fadeOut();
5866
});
59-
});
60-
61-
let inputVal = [];
62-
63-
const isKeyInput = (e) => {
64-
// exclude backspace, tab, shift, ctrl, alt, esc and arrow keys
65-
return (
66-
[8, 9, 16, 17, 18, 27, 37, 38, 39, 40, 46].indexOf(e.which) === -1
67-
);
68-
};
69-
70-
const isNumberInput = (e) => {
71-
var charKey = e.key;
72-
return !isNaN(charKey) || charKey.toLowerCase() === "backspace";
73-
};
74-
75-
const autotab = (e, currentPosition, to) => {
76-
const currentElement = e.currentTarget;
77-
if (
78-
isKeyInput(e) &&
79-
currentElement.getAttribute &&
80-
!e.ctrlKey &&
81-
currentElement.value.length >=
82-
currentElement.getAttribute("maxlength")
83-
) {
84-
inputVal[currentPosition] = currentElement.value;
85-
if (to) {
86-
const elem = document.getElementById(to);
87-
if (elem) {
88-
elem.focus();
89-
elem.select();
90-
}
91-
} else {
92-
submit();
93-
}
67+
const otpcodes = $(".otpcode").toArray();
68+
const handleKeyDown = (e) => {
69+
const currentElement = e.currentTarget;
70+
const i = otpcodes.indexOf(currentElement)
71+
if (e.which === 8 && !currentElement.value && i) {
72+
const previousElement = otpcodes[i - 1];
73+
previousElement.focus();
74+
previousElement.select();
75+
previousElement.value = "";
76+
}
9477
}
95-
};
96-
97-
const submit = () => {
98-
let formAction = qs["formAction"] || "#";
99-
const opt1 = 'https://auth.{{DOMAIN}}/continue';
100-
const opt2 = 'https://{{AUTH0DOMAIN}}/continue';
101-
if (!formAction.startsWith(opt1) && !formAction.startsWith(opt2)) {
102-
// looks like XSS attack
103-
$('#verifyOtp').attr('action', '#');
104-
return false;
78+
const handleInput = (e) => {
79+
const currentElement = e.currentTarget;
80+
const i = otpcodes.indexOf(currentElement)
81+
if (currentElement.value && (i + 1) % otpcodes.length) {
82+
otpcodes[i + 1].focus();
83+
otpcodes[i + 1].select();
84+
}
85+
if (checkForSubmit()) {
86+
console.log("will submit")
87+
submit();
88+
}
10589
}
106-
$('#verifyOtp').attr('action', formAction);
107-
$("#code1").attr('disabled', 'disabled');
108-
$("#code2").attr('disabled', 'disabled');
109-
$("#code3").attr('disabled', 'disabled');
110-
$("#code4").attr('disabled', 'disabled');
111-
$("#code5").attr('disabled', 'disabled');
112-
$("#code6").attr('disabled', 'disabled');
113-
var otp = `${$("#code1").val()}${$("#code2").val()}${$("#code3").val()}${$("#code4").val()}${$("#code5").val()}${$("#code6").val()}`;
114-
$("#otp").val(otp);
115-
$("#state").val(qs["state"]);
116-
$("#returnUrl").val(qs["returnUrl"]);
117-
$("#verifyOtp").submit();
118-
}
90+
const handlePaste = (e) => {
91+
const clipboardData = e.clipboardData || window.clipboardData || e.originalEvent.clipboardData;
92+
const pastedData = clipboardData.getData("Text");
93+
const pin = pastedData.replace(/\s/g, "");
94+
if (!pin) return;
95+
const ch = [...pin];
96+
otpcodes.forEach((el, i) => el.value = ch[i] ?? "");
97+
e.preventDefault();
98+
if (pin.length >= otpcodes.length) {
99+
otpcodes[otpcodes.length - 1].focus();
100+
otpcodes[otpcodes.length - 1].select();
101+
submit();
102+
} else {
103+
otpcodes[pin.length].focus();
104+
otpcodes[pin.length].select();
105+
}
119106

120-
const keydownHandler = (e, prefix, currentPosition) => {
121-
const currentElement = e.currentTarget;
122-
if (e.which === 8 && currentElement.value.length === 0) {
123-
// go to previous input when backspace is pressed
124-
const elem = document.getElementById(
125-
`${prefix}${currentPosition - 1}`
126-
);
127-
if (elem) {
128-
elem.focus();
129-
elem.select();
130-
elem.value = "";
131-
e.preventDefault();
132-
return;
133-
}
134107
}
135-
// only allows numbers (prevents e, +, - on input number type)
136-
if (
137-
// currentElement.type === "number" &&
138-
e.which === 69 ||
139-
e.which === 187 ||
140-
e.which === 189 ||
141-
e.which === 190 ||
142-
!isNumberInput(e)
143-
) {
144-
e.preventDefault();
145-
return;
108+
const checkForSubmit = () => {
109+
for (let i = 0; i < otpcodes.length; i++) {
110+
if (!otpcodes[i].value) {
111+
return false;
112+
}
113+
}
114+
return true;
146115
}
147-
const elem = document.getElementById(
148-
`${prefix}${currentPosition - 1}`
149-
);
150-
if (elem && !elem.value) {
151-
e.preventDefault();
152-
return;
116+
const submit = () => {
117+
$('#verifyOtp').attr('action', formAction);
118+
let otp = "";
119+
otpcodes.forEach(element => {
120+
$(element).attr('disabled', 'disabled');
121+
otp = `${otp}${$(element).val()}`;
122+
})
123+
$("#otp").val(otp);
124+
$("#state").val(qs["state"]);
125+
$("#returnUrl").val(qs["returnUrl"]);
126+
$("#verifyOtp").submit();
153127
}
154-
};
155-
156-
const pasteHandler = (e, prefix, currentPosition) => {
157-
const clipboardData = e.clipboardData || window.clipboardData;
158-
const pastedData = clipboardData.getData("Text");
159-
let inputPos = currentPosition;
160-
let strIndex = 0;
161-
let elem;
162-
do {
163-
elem = document.getElementById(`${prefix}${inputPos}`);
164-
if (elem && pastedData[strIndex]) {
165-
elem.value = pastedData[strIndex];
166-
elem.dispatchEvent(new Event("input"));
167-
if (inputPos === 6) {
168-
submit();
169-
}
170-
e.preventDefault();
171-
} else {
172-
break;
173-
}
174-
strIndex++;
175-
inputPos++;
176-
} while (elem && strIndex < pastedData.length);
177-
};
128+
otpcodes.forEach(element => {
129+
$(element).on("paste", handlePaste);
130+
$(element).on("input", handleInput);
131+
$(element).on("keydown", handleKeyDown);
132+
});
133+
});

web-assets/static-pages/otp.html

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -59,34 +59,12 @@
5959
<input id="state" name="state" value="" type="hidden" />
6060
<input id="returnUrl" name="returnUrl" value="" type="hidden" />
6161
<input id="otp" name="otp" value="" type="hidden" />
62-
<input type="number" inputmode="numeric" pattern="\d" title="Numeric"
63-
onKeyPress="if(this.value.length===1) return false;" id="code1" class="text-center w-8 xxs:w-12 mx-1"
64-
maxlength="1" onPaste="pasteHandler(event, 'code', 1)" onKeydown="keydownHandler(event, 'code', 1)"
65-
onKeyup="autotab(event, 1, 'code2')" required />
66-
<input type="number" inputmode="numeric" pattern="\d" title="Numeric"
67-
onKeyPress="if(this.value.length===1) return false;" id="code2" class="text-center w-8 xxs:w-12 mx-1"
68-
maxlength="1" onPaste="pasteHandler(event, 'code', 2)" onKeydown="keydownHandler(event, 'code', 2)"
69-
onKeyup="autotab(event, 2, 'code3')" required />
70-
71-
<input type="number" inputmode="numeric" pattern="\d" title="Numeric"
72-
onKeyPress="if(this.value.length===1) return false;" id="code3" class="text-center w-8 xxs:w-12 mx-1"
73-
maxlength="1" onPaste="pasteHandler(event, 'code', 3)" onKeydown="keydownHandler(event, 'code', 3)"
74-
onKeyup="autotab(event, 3, 'code4')" required />
75-
76-
<input type="number" inputmode="numeric" pattern="\d" title="Numeric"
77-
onKeyPress="if(this.value.length===1) return false;" id="code4" class="text-center w-8 xxs:w-12 mx-1"
78-
maxlength="1" onPaste="pasteHandler(event, 'code', 4)" onKeydown="keydownHandler(event, 'code', 4)"
79-
onKeyup="autotab(event, 4, 'code5')" required />
80-
81-
<input type="number" inputmode="numeric" pattern="\d" title="Numeric"
82-
onKeyPress="if(this.value.length===1) return false;" id="code5" class="text-center w-8 xxs:w-12 mx-1"
83-
maxlength="1" onPaste="pasteHandler(event, 'code', 5)" onKeydown="keydownHandler(event, 'code', 5)"
84-
onKeyup="autotab(event, 5, 'code6')" required />
85-
86-
<input type="number" inputmode="numeric" pattern="\d" title="Numeric"
87-
onKeyPress="if(this.value.length===1) return false;" id="code6" class="text-center w-8 xxs:w-12 mx-1"
88-
maxlength="1" onPaste="pasteHandler(event, 'code', 6)" onKeydown="keydownHandler(event, 'code', 6)"
89-
onKeyup="autotab(event, 6, '')" required />
62+
<input type="text" id="code1" class="otpcode" maxlength="1" />
63+
<input type="text" id="code2" class="otpcode" maxlength="1" />
64+
<input type="text" id="code3" class="otpcode" maxlength="1" />
65+
<input type="text" id="code4" class="otpcode" maxlength="1" />
66+
<input type="text" id="code5" class="otpcode" maxlength="1" />
67+
<input type="text" id="code6" class="otpcode" maxlength="1" />
9068
</form>
9169
<a href="#" id="resend" class="resend-link">Resend the 6 digit code</a>
9270
</div>

0 commit comments

Comments
 (0)