Skip to content

Commit ece0a0d

Browse files
mkaliobyLex van Buiten
andauthored
Lvanbuiten csp compliances (#97)
* HTML compliant - Stylesheets (CSS) and reformatting * HTML compliant - JavaScript + fix reference to `sb-admin.min.css` in `logout.html` * HTML compliant - JavaScript Fix typo * HTML compliant - JavaScript Fix HTML inline `onclick` events and `onsubmit` for forms * Merged CSP --------- Co-authored-by: Lex van Buiten <l.vanbuiten@20face.com>
1 parent 53bfdde commit ece0a0d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1537
-1316
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## 3.1
44

55
* Upgraded to fido==1.2.0
6+
* Added: CSP Compliance (optional), thanks to @lvanbuiten, to under CSP refer to [van Buiten](https://github.com/mkalioby/django-mfa2/pull/93#issuecomment-2847112870)
67
* Fix: issue when finding the user based on credential id.
78
* Fix: Move key delete to be POST call. Thanks to @AndreasDickow
89

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ function some_func() {
224224
* [ezrajrice](https://github.com/ezrajrice)
225225
* [Spitfireap](https://github.com/Spitfireap)
226226
* [peterthomassen](https://github.com/peterthomassen)
227+
* [lvanbuiten](https://github.com/lvanbuiten)
227228

228229

229230
# Security contact information
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
django-sslserver
2+
django-csp~=3.8

example/example/settings.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"django.contrib.messages",
4242
"django.contrib.staticfiles",
4343
"mfa",
44+
'csp',
4445
"sslserver",
4546
]
4647

@@ -49,6 +50,7 @@
4950
"django.contrib.sessions.middleware.SessionMiddleware",
5051
"django.middleware.common.CommonMiddleware",
5152
"django.middleware.csrf.CsrfViewMiddleware",
53+
"csp.middleware.CSPMiddleware",
5254
"django.contrib.auth.middleware.AuthenticationMiddleware",
5355
"django.contrib.messages.middleware.MessageMiddleware",
5456
"django.middleware.clickjacking.XFrameOptionsMiddleware",
@@ -168,3 +170,13 @@
168170
"localhost" # Server rp id for FIDO2, it the full domain of your project
169171
)
170172
FIDO_SERVER_NAME = "TestApp"
173+
174+
175+
CSP = ("'self'", f'*.localhost')
176+
CSP_DEFAULT_SRC = CSP
177+
CSP_IMG_SRC = CSP + ('data:', 'blob:')
178+
CSP_FONT_SRC = CSP + ('data:',)
179+
CSP_SCRIPT_SRC = CSP
180+
CSP_STYLE_SRC = CSP
181+
CSP_CONNECT_SRC = CSP
182+
CSP_FORM_ACTION = CSP

example/example/templates/logout.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<link href="{% static 'vendor/fontawesome-free/css/all.min.css'%}" rel="stylesheet" type="text/css">
1717

1818
<!-- Custom styles for this template-->
19-
<link href="{% static 'css/sb-admin.css'%}" rel="stylesheet">
19+
<link href="{% static 'css/sb-admin.min.css'%}" rel="stylesheet">
2020

2121
</head>
2222

example/example/urls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
urlpatterns = [
2222
path("admin/", admin.site.urls),
23-
path("mfa/", include("mfa.urls")),
23+
path("mfa2/", include("mfa.urls")),
2424
path("auth/login", auth.loginView, name="login"),
2525
path("auth/logout", auth.logoutView, name="logout"),
2626
path("devices/add/", TrustedDevice.add, name="add_trusted_device"),

mfa/static/mfa/css/mfa.css

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
.text-center {
2+
text-align: center;
3+
}
4+
5+
.text-right {
6+
text-align: right;
7+
}
8+
9+
.padding-10 {
10+
padding: 10px;
11+
}
12+
13+
.padding-left-0 {
14+
padding-left: 0;
15+
}
16+
17+
.padding-left-15 {
18+
padding-left: 15px;
19+
}
20+
21+
.padding-left-25 {
22+
padding-left: 25px;
23+
}
24+
25+
.padding-top-10 {
26+
padding-top: 10px;
27+
}
28+
29+
.padding-right-30 {
30+
padding-right: 30px;
31+
}
32+
33+
#popUpModal {
34+
top: 40px;
35+
36+
& .modal-dialog {
37+
height: 80%;
38+
width: 80%;
39+
}
40+
}
41+
42+
.img-circle {
43+
padding: 3px;
44+
height: 50px;
45+
}
46+
47+
.success-message {
48+
color: green;
49+
}
50+
.error-message {
51+
color: red;
52+
}
53+
54+
.font-10 {
55+
font-size: 10px;
56+
}
57+
58+
.color-gray {
59+
color: #333333;
60+
}
61+
62+
.bg-white {
63+
background-color: #f0f0f0;
64+
}
65+
66+
.institution-code {
67+
font-size: 10pt;
68+
font-family: Calibri;
69+
height: 34px;width: 230px
70+
}
71+
72+
.td-digits {
73+
font-size: 16px;
74+
font-weight: bold;
75+
margin-left: 50px;
76+
}
77+
78+
#two-factor-steps {
79+
border: 1px solid #ccc;
80+
border-radius: 3px;
81+
padding: 15px;
82+
83+
& .row {
84+
margin: 0;
85+
align-items: center;
86+
}
87+
88+
& .toolbtn {
89+
border-radius: 7px;
90+
cursor: pointer;
91+
92+
& :hover {
93+
background-color: gray;
94+
transition: 0.2s;
95+
}
96+
97+
& :active {
98+
background-color: green;
99+
transition: 0.2s;
100+
}
101+
}
102+
103+
& #answer {
104+
display: inline;
105+
width: 95%
106+
}
107+
108+
& #second_step {
109+
display: none;
110+
text-align: center;
111+
align-content: center
112+
}
113+
114+
& .tokenrow {
115+
margin-top: 10px;
116+
margin-left: 5px;
117+
}
118+
}
119+
120+
.row.margin-3 {
121+
margin: 3px;
122+
}
123+
124+
.row.margin-left-15, .auth-card .row {
125+
margin-left: 15px;
126+
}

mfa/static/mfa/js/Email/recheck.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
$(document).ready(function () {
2+
const mode = JSON.parse(document.getElementById('mode').textContent);
3+
if (mode == "recheck") {
4+
$("#send_totp").click(function () {send_totp()});
5+
}
6+
})

mfa/static/mfa/js/FIDO2/add.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
var MakeCredReq = (makeCredReq) => {
2+
makeCredReq.publicKey.challenge = base64url.decode(makeCredReq.publicKey.challenge);
3+
makeCredReq.publicKey.user.id = base64url.decode(makeCredReq.publicKey.user.id);
4+
5+
for (let excludeCred of makeCredReq.publicKey.excludeCredentials) {
6+
excludeCred.id = base64url.decode(excludeCred.id);
7+
}
8+
9+
return makeCredReq
10+
}
11+
12+
function begin_reg() {
13+
const fido2_begin_reg = JSON.parse(document.getElementById('fido2_begin_reg').textContent);
14+
const fido2_complete_reg = JSON.parse(document.getElementById('fido2_complete_reg').textContent);
15+
const redirect_html = JSON.parse(document.getElementById('redirect_html').textContent);
16+
const reg_success_msg = JSON.parse(document.getElementById('reg_success_msg').textContent);
17+
const manage_recovery_codes = JSON.parse(document.getElementById('manage_recovery_codes').textContent);
18+
const RECOVERY_METHOD = JSON.parse(document.getElementById('RECOVERY_METHOD').textContent);
19+
const mfa_home = JSON.parse(document.getElementById('mfa_home').textContent);
20+
fetch(fido2_begin_reg, {}).then(function (response) {
21+
if (response.ok) {
22+
return response.json().then(function (req) {
23+
return MakeCredReq(req)
24+
});
25+
}
26+
throw new Error('Error getting registration data!');
27+
}).then(function (options) {
28+
//options.publicKey.attestation="direct"
29+
console.log(options)
30+
return navigator.credentials.create(options);
31+
}).then(function (attestation) {
32+
return fetch(fido2_complete_reg, {
33+
method: 'POST',
34+
body: JSON.stringify(publicKeyCredentialToJSON(attestation))
35+
});
36+
}).then(function (response) {
37+
var stat = response.ok ? 'successful' : 'unsuccessful';
38+
return response.json()
39+
}).then(function (res) {
40+
if (res["status"] == 'OK')
41+
$("#res").html("<div class='alert alert-success'>Registered Successfully, <a href='"+redirect_html+"'>"+reg_success_msg+"</a></div>")
42+
else if (res['status'] = "RECOVERY") {
43+
setTimeout(function () {
44+
location.href = manage_recovery_codes
45+
}, 2500)
46+
$("#res").html("<div class='alert alert-success'>Registered Successfully, but <a href='"+manage_recovery_codes+"'>redirecting to "+RECOVERY_METHOD+" method</a></div>")
47+
} else
48+
$("#res").html("<div class='alert alert-danger'>Registration Failed as " + res["message"] + ", <a href='#' id='failed_begin_reg'> try again or <a href='"+mfa_home+"'> Go to Security Home</a></div>")
49+
$("#failed_begin_reg").click(function() {begin_reg()});
50+
}, function (reason) {
51+
$("#res").html("<div class='alert alert-danger'>Registration Failed as " + reason + ", <a href='#' id='failed_begin_reg'> try again </a> or <a href='"+mfa_home+"'> Go to Security Home</a></div>")
52+
$("#failed_begin_reg").click(function() {begin_reg()});
53+
})
54+
}
55+
56+
$(document).ready(function () {
57+
ua = new UAParser().getResult()
58+
if (ua.browser.name == "Safari" || ua.browser.name == "Mobile Safari") {
59+
$("#res").html("<button class='btn btn-success' id='ua_begin_reg'>Start...</button>")
60+
$("#ua_begin_reg").click(function() { begin_reg(); });
61+
} else {
62+
setTimeout(begin_reg, 500)
63+
}
64+
})

mfa/static/mfa/js/FIDO2/auth_js.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
window.conditionalUI = false;
2+
window.conditionUIAbortController = new AbortController();
3+
window.conditionUIAbortSignal = conditionUIAbortController.signal;
4+
5+
function checkConditionalUI(form) {
6+
if (window.PublicKeyCredential && PublicKeyCredential.isConditionalMediationAvailable) {
7+
// Check if conditional mediation is available.
8+
PublicKeyCredential.isConditionalMediationAvailable().then((result) => {
9+
window.conditionalUI = result;
10+
if (window.conditionalUI) {
11+
authen(true)
12+
}
13+
});
14+
}
15+
}
16+
17+
var GetAssertReq = (getAssert) => {
18+
getAssert.publicKey.challenge = base64url.decode(getAssert.publicKey.challenge);
19+
for (let allowCred of getAssert.publicKey.allowCredentials) {
20+
allowCred.id = base64url.decode(allowCred.id);
21+
}
22+
return getAssert
23+
}
24+
25+
function authen(conditionalUI = false) {
26+
const fido2_begin_auth = JSON.parse(document.getElementById('fido2_begin_auth').textContent);
27+
const fido2_complete_auth = JSON.parse(document.getElementById('fido2_complete_auth').textContent);
28+
const mode = JSON.parse(document.getElementById('mode').textContent);
29+
fetch(fido2_begin_auth, {
30+
method: 'GET',
31+
}).then(function (response) {
32+
if (response.ok) {
33+
return response.json().then(function (req) {
34+
return GetAssertReq(req)
35+
});
36+
}
37+
throw new Error('No credential available to authenticate!');
38+
}).then(function (options) {
39+
if (conditionalUI) {
40+
options.mediation = 'conditional';
41+
options.signal = window.conditionUIAbortSignal;
42+
} else {
43+
window.conditionUIAbortController.abort()
44+
}
45+
console.log(options)
46+
return navigator.credentials.get(options);
47+
}).then(function (assertion) {
48+
return fetch(fido2_complete_auth, {
49+
method: 'POST',
50+
headers: {'Content-Type': 'application/json'},
51+
body: JSON.stringify(publicKeyCredentialToJSON(assertion)),
52+
}
53+
).then(function (response) {
54+
if (response.ok) return res = response.json()
55+
}).then(function (res) {
56+
if (res.status == "OK") {
57+
$("#msgdiv").addClass("alert alert-success").removeClass("alert-danger")
58+
$("#msgdiv").html("Verified....please wait")
59+
if (mode === "auth" || !mode) {
60+
window.location.href = res.redirect;
61+
}
62+
else if (mode === "recheck") {
63+
mfa_success_function();
64+
}
65+
} else {
66+
$("#msgdiv").addClass("alert alert-danger").removeClass("alert-success")
67+
$("#msgdiv").html("Verification Failed as " + res.message + ", <a href='#' id='failed_authen'> try again</a> or <a href='#' id='failed_history_back'> Go Back</a>")
68+
$("#failed_history_back").click(function() { history.back() });
69+
$("#failed_authen").click(function() { authen(); });
70+
71+
if (mode === "auth") {
72+
// do nothing
73+
}
74+
else if (mode === "recheck") {
75+
mfa_failed_function();
76+
}
77+
}
78+
})
79+
})
80+
}
81+
82+
$(document).ready(function () {
83+
if (location.protocol != 'https:') {
84+
$("#main_paragraph").addClass("alert alert-danger")
85+
$("#main_paragraph").html("FIDO2 must work under secure context")
86+
} else {
87+
ua = new UAParser().getResult()
88+
if (ua.browser.name == "Safari" || ua.browser.name == "Mobile Safari" || ua.os.name == "iOS" || ua.os.name == "iPadOS") {
89+
$("#res").html("<button class='btn btn-success' id='ua_authen'>Authenticate...</button>");
90+
$("#ua_authen").click(function () { authen(); });
91+
}
92+
else {
93+
authen()
94+
}
95+
}
96+
});

0 commit comments

Comments
 (0)