##// END OF EJS Templates
feat(2fa): updated and UI fixes...
super-admin -
r5373:834643be default
parent child Browse files
Show More
@@ -34,7 +34,7 b' from pyramid.renderers import render'
34 from pyramid.response import Response
34 from pyramid.response import Response
35 from pyramid.httpexceptions import HTTPFound
35 from pyramid.httpexceptions import HTTPFound
36
36
37
37 import rhodecode
38 from rhodecode.apps._base import BaseAppView
38 from rhodecode.apps._base import BaseAppView
39 from rhodecode.authentication.base import authenticate, HTTP_TYPE
39 from rhodecode.authentication.base import authenticate, HTTP_TYPE
40 from rhodecode.authentication.plugins import auth_rhodecode
40 from rhodecode.authentication.plugins import auth_rhodecode
@@ -510,9 +510,10 b' class LoginView(BaseAppView):'
510 # only then we should persist it
510 # only then we should persist it
511 secret = user_instance.init_secret_2fa(persist=False)
511 secret = user_instance.init_secret_2fa(persist=False)
512
512
513 totp_name = f'RhodeCode token ({self.request.user.username})'
513 instance_name = rhodecode.ConfigGet().get_str('app.base_url', 'rhodecode')
514 totp_name = f'{instance_name}:{self.request.user.username}'
514
515
515 qr = qrcode.QRCode(version=1, box_size=10, border=5)
516 qr = qrcode.QRCode(version=1, box_size=5, border=4)
516 qr.add_data(pyotp.totp.TOTP(secret).provisioning_uri(name=totp_name))
517 qr.add_data(pyotp.totp.TOTP(secret).provisioning_uri(name=totp_name))
517 qr.make(fit=True)
518 qr.make(fit=True)
518 img = qr.make_image(fill_color='black', back_color='white')
519 img = qr.make_image(fill_color='black', back_color='white')
@@ -919,7 +919,7 b' class User(Base, BaseModel):'
919 return ''
919 return ''
920
920
921 def get_secret_2fa(self) -> str:
921 def get_secret_2fa(self) -> str:
922 secret_2fa = self.user_data['secret_2fa']
922 secret_2fa = self.user_data.get('secret_2fa')
923 if secret_2fa:
923 if secret_2fa:
924 strict_mode = ConfigGet().get_bool('rhodecode.encrypted_values.strict', missing=True)
924 strict_mode = ConfigGet().get_bool('rhodecode.encrypted_values.strict', missing=True)
925 return safe_str(
925 return safe_str(
@@ -6,9 +6,11 b''
6 · ${h.branding(c.rhodecode_name)}
6 · ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9 <style>body{background-color:#eeeeee;}</style>
10 <style>body{background-color:#eeeeee;}</style>
10
11
11 <div class="loginbox">
12 <div class="loginbox" style="width: 600px">
13
12 <div class="header-account">
14 <div class="header-account">
13 <div id="header-inner" class="title">
15 <div id="header-inner" class="title">
14 <div id="logo">
16 <div id="logo">
@@ -22,66 +24,67 b''
22 </div>
24 </div>
23
25
24 <div class="loginwrapper">
26 <div class="loginwrapper">
25 <h1>${_('Setup the authenticator app')}</h1>
26
27 <p>Authenticator apps like <a href='https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2' target="_blank" rel="noopener noreferrer">Google Authenticator</a>, etc. generate one-time passwords that are used as a second factor to verify you identity.</p>
28 <rhodecode-toast id="notifications"></rhodecode-toast>
27 <rhodecode-toast id="notifications"></rhodecode-toast>
29
28
30 <div id="setup_2fa">
29 <div class="sign-in-title">
30 <h1>${_('Set up the authenticator app')} - ${_('scan the QR code')}</h1>
31 </div>
32 <div class="inner form">
31 ${h.secure_form(h.route_path('setup_2fa'), request=request, id='totp_form')}
33 ${h.secure_form(h.route_path('setup_2fa'), request=request, id='totp_form')}
32 <div class="sign-in-title">
34 <strong>${_('Use an authenticator app to scan.')}</strong><br/>
33 <h1>${_('Scan the QR code')}: "${totp_name}"</h1>
35
36 ## QR CODE
37 <code>${_('Account')}: ${totp_name}</code><br/>
38 <div class="qr-code-container">
39 <img alt="qr-code" src="data:image/png;base64, ${qr}"/>
34 </div>
40 </div>
35 <p>${_('Use an authenticator app to scan.')}</p>
41
36 <img alt="qr-code" src="data:image/png;base64, ${qr}"/>
42 <div id="alternativeCode" style="margin: -10px 0 5px 0">${_('Unable to scan?')} <a id="toggleLink">${_('Click here')}</a></div>
43
44 ## Secret alternative code
45 <div id="secretDiv" style="display: none">
37
46
38 <p>${_('Unable to scan?')} <a id="toggleLink">${_('Click here')}</a></p>
47 <div style="padding: 10px 0">
39 <div id="secretDiv" class="hidden">
48 <strong style="padding: 4px 0">${_('Copy and use this code to manually set up an authenticator app')}</strong>
40 <p>${_('Copy and use this code to manually set up an authenticator app')}</p>
49 <code>${key}</code><i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${key}" title="${_('Copy the secret key')}" ></i><br/>
41 <input type="text" class="input-monospace" value="${key}" id="secret_totp" name="secret_totp" style="width: 400px"/>
50 <code>${_('type')}: time-based</code>
42 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${key}" title="${_('Copy the secret key')}"></i>
51 </div>
52
43 </div>
53 </div>
44
54
45 <div id="verify_2fa">
55 <label for="totp">${_('Verify the code from the app')}:</label>
56 ${h.text('totp', class_='form-control', )}
57 <div id="formErrors">
58 % if 'totp' in errors:
59 <span class="error-message">${errors.get('totp')}</span>
60 <br />
61 % endif
62 % if 'secret_totp' in errors:
63 <span class="error-message">SECRET:${errors.get('secret_totp')}</span>
64 <br />
65 % endif
66 </div>
67 ${h.hidden('secret_totp', key)}
68 ${h.submit('verify_2fa',_('Verify'), class_="btn sign-in")}
46
69
47 <div class="form mt-4">
48 <div class="field">
49 <p>
50 <div class="label">
51 <label for="totp" class="form-label text-dark font-weight-bold" style="text-align: left;">${_('Verify the code from the app')}:</label>
52 </div>
53 </p>
54 <p>
55 <div>
56 <div class="input-group">
57 ${h.text('totp', class_='form-control', style='width: 40%;')}
58 <div id="formErrors">
59 % if 'totp' in errors:
60 <span class="error-message">${errors.get('totp')}</span>
61 <br />
62 % endif
63 </div>
64 <div class="input-group-append">
65 ${h.submit('verify_2fa',_('Verify'),class_="btn btn-primary", style='width: 40%;')}
66 </div>
67 </div>
68 </div>
69 </p>
70 </div>
71 </div>
72 </div>
73 ${h.end_form()}
70 ${h.end_form()}
74 </div>
71 </div>
72
75 </div>
73 </div>
74
76 </div>
75 </div>
77
76
78 <script>
77 <script type="text/javascript">
78
79 $(document).ready(function() {
79
80
80 document.getElementById('toggleLink').addEventListener('click', function() {
81 $( "#toggleLink" ).on("click", function() {
81 let hiddenField = document.getElementById('secretDiv');
82 $( "#secretDiv" ).toggle();
82 if (hiddenField.classList.contains('hidden')) {
83 $( "#alternativeCode").hide();
83 hiddenField.classList.remove('hidden');
84 $('#totp').focus();
84 }
85 });
85 });
86
87 $('#totp').focus();
88 })
86
89
87 </script>
90 </script>
@@ -8,7 +8,7 b''
8 </%def>
8 </%def>
9 <style>body{background-color:#eeeeee;}</style>
9 <style>body{background-color:#eeeeee;}</style>
10
10
11 <div class="loginbox">
11 <div class="loginbox" style="width: 600px">
12 <div class="header-account">
12 <div class="header-account">
13 <div id="header-inner" class="title">
13 <div id="header-inner" class="title">
14 <div id="logo">
14 <div id="logo">
General Comments 0
You need to be logged in to leave comments. Login now