forked from Weasyl/weasyl
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #222 from kfkitsune/2fa
Add Two-Factor Authentication (2FA)
- Loading branch information
Showing
26 changed files
with
1,787 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
libweasyl/libweasyl/alembic/versions/abeefecabdad_implement_2fa.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
""" | ||
Implements two-factor authentication. | ||
Revision ID: abeefecabdad | ||
Revises: 40c00abab5f9 | ||
Create Date: 2017-01-19 01:56:20.093477 | ||
""" | ||
|
||
# revision identifiers, used by Alembic. | ||
revision = 'abeefecabdad' | ||
down_revision = '40c00abab5f9' | ||
|
||
from alembic import op | ||
import sqlalchemy as sa | ||
|
||
|
||
def upgrade(): | ||
# Create a table to store 2FA backup codes for use when the authenticator is unavailable. | ||
op.create_table('twofa_recovery_codes', | ||
sa.Column('userid', sa.Integer(), nullable=False), | ||
sa.Column('recovery_code_hash', sa.String(length=100), nullable=False), | ||
sa.ForeignKeyConstraint(['userid'], ['login.userid'], name='twofa_recovery_codes_userid_fkey', onupdate='CASCADE', ondelete='CASCADE'), | ||
) | ||
op.create_index('ind_twofa_recovery_codes_userid', 'twofa_recovery_codes', ['userid']) | ||
|
||
# Modify `login` to hold the 2FA code (if set) for a user account | ||
op.add_column( | ||
'login', | ||
sa.Column('twofa_secret', sa.String(length=420), nullable=True, server_default=None), | ||
) | ||
|
||
|
||
def downgrade(): | ||
# Remove 2FA logic of recovery codes and secret | ||
op.drop_index('ind_twofa_recovery_codes_userid', 'twofa_recovery_codes') | ||
op.drop_table('twofa_recovery_codes') | ||
op.drop_column('login', 'twofa_secret') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
$def with (username, error) | ||
$:{RENDER("common/stage_title.html", ["Disable 2FA", "Two-Factor Authentication", "/control/2fa/status"])} | ||
|
||
<div class="content form clear"> | ||
<form method="POST" class="form skinny clear" action="/control/2fa/disable"> | ||
$if error == "2fa": | ||
<div id="signin_error"><strong>Whoops!</strong> The 2FA response token or recovery code you provided did not validate.</div> | ||
$elif error == "verify": | ||
<div id="signin_error"><strong>Hey!</strong> Did you want to disable 2FA? You didn't check the verification checkbox!</div> | ||
<h3>Disable Two-Factor Authentication (2FA)</h3> | ||
<p><strong>Username: ${username}</strong></p> | ||
<p> | ||
You are about to disable 2FA for the above account, and remove the | ||
security which it provides. If you wish to re-enable 2FA, you will | ||
need to go through through the enabling process once again. | ||
</p> | ||
|
||
<label for="disable-tfa">2FA token or Recovery Code</label> | ||
<input type="text" class="input" maxlength="24" id="disable-tfa" name="tfaresponse" autocomplete="off" required /> | ||
<div style="padding-top: 1em;"> | ||
<label><input class="checkbox" type="checkbox" name="verify" required /> I confirm that I want to disable 2FA</label> | ||
</div> | ||
|
||
$:{CSRF()} | ||
|
||
<div style="padding-top: 1em;"> | ||
<a class="button negative" href="/control/2fa/status">Cancel</a> | ||
<button class="button positive">Disable 2FA</button> | ||
</div> | ||
</form> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
$def with (tfa_recovery_codes, error) | ||
$:{RENDER("common/stage_title.html", ["Generate Recovery Codes", "Two-Factor Authentication", "/control/2fa/status"])} | ||
|
||
<div class="content form clear"> | ||
<div class="constrained"> | ||
$if error == "2fa": | ||
<div id="signin_error"> | ||
<strong>Whoops!</strong> The 2FA response token or recovery code you provided did not validate. | ||
</div> | ||
$elif error == "verify": | ||
<div id="signin_error"> | ||
<strong>Hey!</strong> Did you want to generate a new set of recovery codes? You didn't check the verification checkbox! | ||
<strong>If entered, your recovery code was <em>not</em> consumed.</strong> | ||
</div> | ||
|
||
<h3>Generate New Recovery Codes</h3> | ||
<p> | ||
The following recovery codes are <strong>not yet active</strong>. Once you provide | ||
a valid 2FA token or recovery code, the below list of codes will replace all recovery | ||
codes currently associated with your account. | ||
</p> | ||
<p> | ||
Before proceeding, print or save these codes to a secure place. In the event you lose access | ||
to your authenticator app, you will need these codes to regain access to your Weasyl account. | ||
If you no longer have access to your authenticator app, please <a href="/control/2fa/disable">disable 2FA</a> | ||
instead. You may then set up another authenticator. | ||
</p> | ||
</div> <!-- /div.constrained --> | ||
|
||
<form method="POST" class="form skinny clear" action="/control/2fa/generate_recovery_codes"> | ||
<h3>Your new recovery codes will be:</h3> | ||
<ol> | ||
$for code in tfa_recovery_codes: | ||
<li>${code[0:4] + ' ' + code[4:8] + ' ' + code[8:12] + ' ' + code[12:16] + ' ' + code[16:20]}</li> | ||
</ol> | ||
|
||
<br /> | ||
|
||
<p> | ||
These codes are one-time use, and upon successful use will not be able to be reused or retrieved. | ||
Codes may be used in any order. Cross each code off when used. | ||
</p> | ||
|
||
<label for="tfa-regenerate-codes">Enter 2FA token or Recovery Code</label> | ||
<input type="text" id="tfa-regenerate-codes" maxlength="24" name="tfaresponse" placeholder="012345" autocomplete="off" required /><br /> | ||
<label><input class="checkbox" type="checkbox" name="verify" required /> I confirm that I have saved the above new recovery codes.</label> | ||
|
||
$:{CSRF()} | ||
|
||
<div style="padding-top: 1em;"> | ||
<a class="button negative" href="/control/2fa/status">Cancel</a> | ||
<button class="button positive">Save New Recovery Codes</button> | ||
</div> | ||
</form> | ||
</div> |
29 changes: 29 additions & 0 deletions
29
templates/control/2fa/generate_recovery_codes_verify_password.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
$def with (error) | ||
$:{RENDER("common/stage_title.html", ["Generate Recovery Codes: Verify Password", "Two-Factor Authentication", "/control/2fa/status"])} | ||
|
||
<div class="content form clear"> | ||
<form method="POST" class="form skinny clear" action="/control/2fa/generate_recovery_codes_verify_password"> | ||
$if error == "password": | ||
<div id="signin_error"><strong>Whoops!</strong> You entered an incorrect password.</div> | ||
|
||
<h3>Verify your password</h3> | ||
<p> | ||
Before generating new recovery codes, we need to verify your current Weasyl password. | ||
</p> | ||
<br /> | ||
<p> | ||
<strong>Note</strong>: For the security of your account, entering your password | ||
will clear all other active login sessions for your account. | ||
</p> | ||
|
||
$:{CSRF()} | ||
|
||
<label for="login-pass">Confirm your Password</label> | ||
<input type="password" id="login-pass" class="input" name="password" placeholder="Password" required /> | ||
|
||
<div style="padding-top: 1em;"> | ||
<a class="button negative" href="/control/2fa/status">Cancel</a> | ||
<button class="button positive">Continue</button> | ||
</div> | ||
</form> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
$def with (username, error) | ||
$:{RENDER("common/stage_title.html", ["Enable 2FA: Step 1", "Two-Factor Authentication", "/control/2fa/status"])} | ||
|
||
<div class="content form clear"> | ||
<div class="constrained"> | ||
$if error == "password": | ||
<div id="signin_error"><strong>Whoops!</strong> You entered an incorrect password.</div> | ||
|
||
<h3>Two-Factor Authentication</h3> | ||
<p> | ||
This process will guide you through enabling Two-Factor Authentication (2FA), a method of enhancing the security of | ||
your Weasyl account. For more information about 2FA, please visit the <a href="/help/two_factor_authentication">2FA help page</a>. As part of this | ||
process, you will: | ||
<ol> | ||
<li>Confirm that you have access to this account;</li> | ||
<li>Set-up your authenticator application; and finally</li> | ||
<li>Be presented a set of recovery codes used to regain access if you lose your authenticator.</li> | ||
</ol> | ||
</p> | ||
|
||
<hr /> | ||
|
||
<a href="#begin-setup" class="faq-permalink">Link</a> | ||
<h3 id="begin-setup">Begin setting up 2FA</h3> | ||
|
||
<p><strong>Username: ${username}</strong></p> | ||
<p> | ||
You are about to enable 2FA for the Weasyl account identified above. | ||
In order to proceed through this process, you will need a compatible app such as | ||
<a href="https://support.google.com/accounts/answer/1066447?hl=en">Google Authenticator</a> for Android and iOS devices, | ||
or <a href="http://www.nongnu.org/oath-toolkit/">OATH Toolkit</a> for Linux, | ||
to generate time-based tokens each time you log into your Weasyl account. | ||
</p> | ||
<p> | ||
To begin, we need to confirm that you have control over this account. Please enter your password to continue. | ||
</p> | ||
|
||
<form method="POST" class="form skinny clear" action="/control/2fa/init"> | ||
$:{CSRF()} | ||
|
||
<label for="login-pass">Confirm your Password</label> | ||
<input type="password" id="login-pass" class="input" name="password" placeholder="Password" required /> | ||
|
||
<div style="padding-top: 1em;"> | ||
<a class="button negative" href="/control/2fa/status">Cancel</a> | ||
<button class="button positive">Continue</button> | ||
</div> | ||
</form> | ||
</div> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
$def with (username, tfa_secret, qrcode, error) | ||
$:{RENDER("common/stage_title.html", ["Enable 2FA: Step 2", "Two-Factor Authentication", "/control/2fa/status"])} | ||
|
||
<div class="content form clear"> | ||
<form method="POST" class="form skinny clear" action="/control/2fa/init_qrcode"> | ||
$if error == "2fa": | ||
<div id="signin_error"><strong>Whoops!</strong> The 2FA response token you provided did not validate.</div> | ||
|
||
<h3>Two-Factor Authentication</h3> | ||
<p><strong>Username: ${username}</strong></p> | ||
<p> | ||
In order to proceed from this point onwards, you will need a compatible app such as | ||
<a href="https://support.google.com/accounts/answer/1066447?hl=en">Google Authenticator</a> for Android and iOS devices, | ||
or <a href="http://www.nongnu.org/oath-toolkit/">OATH Toolkit</a> for Linux, | ||
to generate time-based tokens each time you log into your Weasyl account. | ||
</p> | ||
|
||
<h3>Scan with your authenticator</h3> | ||
<div> | ||
<img src="data:image/svg+xml;utf8,${qrcode}"> | ||
</div> | ||
<h3>Or manually enter your key</h3> | ||
<p> | ||
Your TOTP secret key is: ${tfa_secret[0:4] + ' ' + tfa_secret[4:8] + ' ' + tfa_secret[8:12] + ' ' + tfa_secret[12:16]} | ||
</p> | ||
|
||
<label for="tfa-init-verify">Enter 2FA token</label> | ||
<input type="text" id="tfa-init-verify" maxlength="7" name="tfaresponse" placeholder="012345" autocomplete="off" required /> | ||
|
||
$:{CSRF()} | ||
|
||
<div style="padding-top: 1em;"> | ||
<a class="button negative" href="/control/2fa/status">Cancel</a> | ||
<button class="button positive">Continue</button> | ||
</div> | ||
</form> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
$def with (tfa_recovery_codes, error) | ||
$:{RENDER("common/stage_title.html", ["Enable 2FA: Final Step", "Two-Factor Authentication", "/control/2fa/status"])} | ||
|
||
<div class="content form clear"> | ||
<div class="constrained"> | ||
$if error == "2fa": | ||
<div id="signin_error"> | ||
<strong>Whoops!</strong> The 2FA response token you provided did not validate. | ||
</div> | ||
$elif error == "verify": | ||
<div id="signin_error"> | ||
<strong>Hey!</strong> Did you want to enable 2FA? You didn't check the verification checkbox! | ||
</div> | ||
|
||
<h3>Final Step: Verify Receipt of Two-Factor Authentication (2FA) Recovery Codes</h3> | ||
<h4>You've almost enabled 2FA for your account. One final step remains!</h4> | ||
|
||
<br /> | ||
|
||
<p> | ||
<strong>Print these codes, and secure them as you would your password.</strong> | ||
In the event you lose access to your authenticator app, you will need these | ||
codes to regain access to your Weasyl account. Recovery codes can be | ||
refreshed at any time from your settings page. | ||
</p> | ||
<p> | ||
<strong>Warning:</strong> For security reasons, Weasyl staff will be unable to assist you if | ||
you lose access to your recovery codes. | ||
<br /> | ||
As a precaution against being locked out your account if all recovery codes are used, 2FA will | ||
automatically be disabled if--and only if--all recovery codes are consumed during the login process. | ||
Generating a new set of codes will prevent this from occurring. If this occurs, you will need | ||
to set-up 2FA again. | ||
</p> | ||
</div> <!-- /div.constrained --> | ||
|
||
<form method="POST" class="form skinny clear" action="/control/2fa/init_verify"> | ||
<h3>Your recovery codes are:</h3> | ||
<ol> | ||
$for code in tfa_recovery_codes: | ||
<li>${code[0:4] + ' ' + code[4:8] + ' ' + code[8:12] + ' ' + code[12:16] + ' ' + code[16:20]}</li> | ||
</ol> | ||
|
||
<br /> | ||
|
||
<p> | ||
These codes are one-time use, and upon successful use will not be able to be reused or retrieved. | ||
Codes may be used in any order. Cross each code off when used. | ||
</p> | ||
|
||
<label for="tfa-init-verify">Enter 2FA token</label> | ||
<input type="text" id="tfa-init-verify" maxlength="7" name="tfaresponse" placeholder="012345" autocomplete="off" required /><br /> | ||
<label><input class="checkbox" type="checkbox" name="verify" required /> I have printed or saved the above recovery codes and want to enable 2FA.</label> | ||
|
||
$:{CSRF()} | ||
|
||
<div style="padding-top: 1em;"> | ||
<a class="button negative" href="/control/2fa/status">Cancel</a> | ||
<button class="button positive">Enable 2FA</button> | ||
</div> | ||
</form> | ||
</div> |
Oops, something went wrong.