Browse Source

Added two factor auth checking for users that have it enabled.

tags/2.0.3
Teknikode 3 years ago
parent
commit
c95849f468

+ 1
- 0
.gitignore View File

@@ -194,3 +194,4 @@ ModelManifest.xml
/Teknik/TransformWebConfig/assist/Web.config
/Teknik/Properties/PublishProfiles/IIS.pubxml
/Teknik/App_Data/ConnectionStrings.config
/Teknik/App_Data/Config.json.old

+ 83
- 15
Teknik/Areas/User/Controllers/UserController.cs View File

@@ -28,6 +28,7 @@ namespace Teknik.Areas.Users.Controllers
{
public class UserController : DefaultController
{
private static readonly UsedCodesManager usedCodesManager = new UsedCodesManager();
private TeknikEntities db = new TeknikEntities();

// GET: Profile/Profile
@@ -102,6 +103,8 @@ namespace Teknik.Areas.Users.Controllers

if (user != null)
{
Session["AuthenticatedUser"] = user;

ViewBag.Title = "Settings - " + Config.Title;
ViewBag.Description = "Your " + Config.Title + " Settings";

@@ -164,16 +167,31 @@ namespace Teknik.Areas.Users.Controllers
bool userValid = UserHelper.UserPasswordCorrect(db, Config, user, model.Password);
if (userValid)
{
UserHelper.TransferUser(db, Config, user, model.Password);
user.LastSeen = DateTime.Now;
db.Entry(user).State = EntityState.Modified;
db.SaveChanges();
HttpCookie authcookie = UserHelper.CreateAuthCookie(model.Username, model.RememberMe, Request.Url.Host.GetDomain(), Request.IsLocal);
Response.Cookies.Add(authcookie);
string returnUrl = model.ReturnUrl;
if (user.SecuritySettings.TwoFactorEnabled)
{
// We need to check their device, and two factor them
Session["AuthenticatedUser"] = user;
if (string.IsNullOrEmpty(model.ReturnUrl))
returnUrl = Request.UrlReferrer.AbsoluteUri.ToString();
returnUrl = Url.SubRouteUrl("user", "User.CheckAuthenticatorCode", new { returnUrl = returnUrl, rememberMe = model.RememberMe });
model.ReturnUrl = string.Empty;
}
else
{
returnUrl = Request.UrlReferrer.AbsoluteUri.ToString();
// They don't need two factor auth.
UserHelper.TransferUser(db, Config, user, model.Password);
user.LastSeen = DateTime.Now;
db.Entry(user).State = EntityState.Modified;
db.SaveChanges();
HttpCookie authcookie = UserHelper.CreateAuthCookie(model.Username, model.RememberMe, Request.Url.Host.GetDomain(), Request.IsLocal);
Response.Cookies.Add(authcookie);
}

if (string.IsNullOrEmpty(model.ReturnUrl))
{
return Json(new { result = "true" });
return Json(new { result = returnUrl });
}
else
{
@@ -188,7 +206,7 @@ namespace Teknik.Areas.Users.Controllers
public ActionResult Logout()
{
// Get cookie
HttpCookie authCookie = Utility.UserHelper.CreateAuthCookie(User.Identity.Name, false, Request.Url.Host.GetDomain(), Request.IsLocal);
HttpCookie authCookie = UserHelper.CreateAuthCookie(User.Identity.Name, false, Request.Url.Host.GetDomain(), Request.IsLocal);

// Signout
FormsAuthentication.SignOut();
@@ -529,20 +547,44 @@ namespace Teknik.Areas.Users.Controllers
return Json(new { error = "Unable to reset user password" });
}

[HttpGet]
[AllowAnonymous]
public ActionResult ConfirmTwoFactorAuth(string returnUrl, bool rememberMe)
{
ViewBag.Title = "Unknown Device - " + Config.Title;
ViewBag.Description = "We do not recognize this device.";
LoginViewModel model = new LoginViewModel();
model.ReturnUrl = returnUrl;
model.RememberMe = rememberMe;

return View("/Areas/User/Views/User/TwoFactorCheck.cshtml", model);
}

[HttpPost]
public ActionResult ConfirmAuthenticatorCode(string code)
[AllowAnonymous]
public ActionResult ConfirmAuthenticatorCode(string code, string returnUrl, bool rememberMe)
{
User user = UserHelper.GetUser(db, User.Identity.Name);
User user = (User)Session["AuthenticatedUser"];
if (user != null)
{
if (user.SecuritySettings.TwoFactorEnabled)
{
string key = user.SecuritySettings.TwoFactorKey;

TimeAuthenticator ta = new TimeAuthenticator();
bool isValid = false;
TimeAuthenticator ta = new TimeAuthenticator(usedCodeManager: usedCodesManager);
bool isValid = ta.CheckCode(key, code, user);

return Json(new { result = true });
if (isValid)
{
// the code was valid, let's log them in!
HttpCookie authcookie = UserHelper.CreateAuthCookie(user.Username, rememberMe, Request.Url.Host.GetDomain(), Request.IsLocal);
Response.Cookies.Add(authcookie);

if (string.IsNullOrEmpty(returnUrl))
returnUrl = Request.UrlReferrer.AbsoluteUri.ToString();
return Json(new { result = returnUrl });
}
return Json(new { error = "Invalid Authentication Code" });
}
return Json(new { error = "User does not have Two Factor Authentication enabled" });
}
@@ -550,10 +592,36 @@ namespace Teknik.Areas.Users.Controllers
}

[HttpPost]
public ActionResult GenerateQrCode(string content)
public ActionResult VerifyAuthenticatorCode(string code)
{
User user = UserHelper.GetUser(db, User.Identity.Name);
if (user != null)
{
if (user.SecuritySettings.TwoFactorEnabled)
{
string key = user.SecuritySettings.TwoFactorKey;

TimeAuthenticator ta = new TimeAuthenticator(usedCodeManager: usedCodesManager);
bool isValid = ta.CheckCode(key, code, user);

if (isValid)
{
return Json(new { result = true });
}
return Json(new { error = "Invalid Authentication Code" });
}
return Json(new { error = "User does not have Two Factor Authentication enabled" });
}
return Json(new { error = "User does not exist" });
}

[HttpGet]
public ActionResult GenerateAuthQrCode(string key)
{
var ProvisionUrl = string.Format("otpauth://totp/{0}:{1}?secret={2}", Config.Title, User.Identity.Name, key);

QRCodeGenerator qrGenerator = new QRCodeGenerator();
QRCodeData qrCodeData = qrGenerator.CreateQrCode(content, QRCodeGenerator.ECCLevel.Q);
QRCodeData qrCodeData = qrGenerator.CreateQrCode(ProvisionUrl, QRCodeGenerator.ECCLevel.Q);
SvgQRCode qrCode = new SvgQRCode(qrCodeData);
string qrCodeImage = qrCode.GetGraphic(20);
return File(Encoding.UTF8.GetBytes(qrCodeImage), "image/svg+xml");

+ 34
- 0
Teknik/Areas/User/Scripts/CheckAuthCode.js View File

@@ -0,0 +1,34 @@
$(document).ready(function () {
$("#authCheckStatus").css('display', 'none', 'important');

$("#verifyCodeSubmit").click(function () {
setCode = $("#code").val();
returnUrl = $("#returnUrl").val();
rememberMe = $("#rememberMe").val();
if (rememberMe == '') {
rememberMe = false;
}
$.ajax({
type: "POST",
url: confirmAuthCodeURL,
data: {
code: setCode,
returnUrl: returnUrl,
rememberMe: rememberMe
},
xhrFields: {
withCredentials: true
},
success: function (html) {
if (html.result) {
window.location = html.result;
}
else {
$("#authCheckStatus").css('display', 'inline', 'important');
$("#authCheckStatus").html('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>' + html.error + '</div>');
}
}
});
return false;
});
});

+ 38
- 1
Teknik/Areas/User/Scripts/User.js View File

@@ -1,6 +1,7 @@
$(document).ready(function () {
$("[name='update_upload_saveKey']").bootstrapSwitch();
$("[name='update_upload_serverSideEncrypt']").bootstrapSwitch();
$("[name='update_security_two_factor']").bootstrapSwitch();

$('#ResendVerification').click(function () {
$.ajax({
@@ -26,6 +27,41 @@
});
});


$('#authenticatorSetup').on('hide.bs.modal', function (e) {
$("#authSetupStatus").css('display', 'none', 'important');
$("#authSetupStatus").html('');
$('#auth_setup_code').val('');
});

$('#auth_setup_confirm').click(function () {
setCode = $("#auth_setup_code").val();
$.ajax({
type: "POST",
url: confirmAuthSetupURL,
data: {
code: setCode
},
success: function (html) {
if (html.result) {
$("#authSetupStatus").css('display', 'inline', 'important');
$("#authSetupStatus").html('<div class="alert alert-success alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>Success!</div>');
}
else {
errorMsg = html;
if (html.error) {
errorMsg = html.error;
if (html.error.message) {
errorMsg = html.error.message;
}
}
$("#authSetupStatus").css('display', 'inline', 'important');
$("#authSetupStatus").html('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>' + errorMsg + '</div>');
}
}
});
});

$('#delete_account').click(function () {
bootbox.confirm("Are you sure you want to delete your account?", function (result) {
if (result) {
@@ -62,7 +98,7 @@
password = $("#update_password").val();
password_confirm = $("#update_password_confirm").val();
update_pgp_public_key = $("#update_pgp_public_key").val();
update_security_two_factor = $("#update_security_two_factor").val();
update_security_two_factor = $("#update_security_two_factor").is(":checked");
recovery = $("#update_recovery_email").val();
website = $("#update_website").val();
quote = $("#update_quote").val();
@@ -79,6 +115,7 @@
newPass: password,
newPassConfirm: password_confirm,
pgpPublicKey: update_pgp_public_key,
twoFactorEnabled: update_security_two_factor,
recoveryEmail: recovery,
website: website,
quote: quote,

+ 12
- 0
Teknik/Areas/User/UserAreaRegistration.cs View File

@@ -74,6 +74,14 @@ namespace Teknik.Areas.Users
new { controller = "User", action = "VerifyRecoveryEmail" }, // Parameter defaults
new[] { typeof(Controllers.UserController).Namespace }
);
context.MapSubdomainRoute(
"User.CheckAuthenticatorCode", // Route name
new List<string>() { "user" }, // Subdomains
new List<string>() { config.Host }, // domains
"CheckAuthCode", // URL with parameters
new { controller = "User", action = "ConfirmTwoFactorAuth" }, // Parameter defaults
new[] { typeof(Controllers.UserController).Namespace }
);
context.MapSubdomainRoute(
"User.Index", // Route name
new List<string>() { "user" }, // Subdomains
@@ -106,6 +114,10 @@ namespace Teknik.Areas.Users
"~/Scripts/bootstrap-switch.js",
"~/Areas/User/Scripts/User.js"));

// Register Script Bundle
BundleTable.Bundles.Add(new ScriptBundle("~/bundles/checkAuthCode").Include(
"~/Areas/User/Scripts/CheckAuthCode.js"));

// Register Style Bundles
BundleTable.Bundles.Add(new StyleBundle("~/Content/user").Include(
"~/Content/bootstrap-switch/bootstrap3/bootstrap-switch.css"));

+ 0
- 1
Teknik/Areas/User/Views/User/AuthenticatorSetup.cshtml View File

@@ -1 +0,0 @@


+ 37
- 37
Teknik/Areas/User/Views/User/Settings.cshtml View File

@@ -7,6 +7,7 @@
var editUserURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "Edit" })';
var deleteUserURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "Delete" })';
var resendVerifyURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "ResendVerifyRecoveryEmail"})';
var confirmAuthSetupURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "VerifyAuthenticatorCode" })';
</script>

@Styles.Render("~/Content/user")
@@ -23,45 +24,44 @@
<h4 class="modal-title" id="authSetupTitleLabel">Set Up a Third Party App to Generate Codes</h4>
</div>
<div class="modal-body">
<div class="row">
<div class="col-sm-12 text-center">
<div id="authSetupStatus"></div>
</div>
</div>
@if (Model.SecuritySettings.TwoFactorEnabled)
{
<form class="form" action="@Url.SubRouteUrl("user", "User.Action", new { action = "ConfirmAuthenticatorCode" })" method="post" id="confirmAuthSetup">
<form class="form" action="##" method="post" id="confirmAuthSetup">
<p>To get a third party app working, either scan the QR code below or type the secret key into the app.</p>
<div class="row">
<div class="col-sm-2">
QR Code:
<div class="col-sm-4">
<p class="text-muted">QR Code:</p>
</div>
<div class="col-sm-10 text-right">
<!-- QR Code -->
<img src="@Url.SubRouteUrl("user", "User.Action", new { action = "GenerateQRCode", content = Model.SecuritySettings.TwoFactorKey })" alt="qr code" />
<div class="col-sm-8">
<img src="@Url.SubRouteUrl("user", "User.Action", new { action = "GenerateAuthQrCode", key = Model.SecuritySettings.TwoFactorKey })" width="200" height="200" alt="qr code" />
</div>
</div>
<div class="row">
<div class="col-sm-2">
Secret Key:
<div class="col-sm-4">
<p class="text-muted">Secret Key:</p>
</div>
<div class="col-sm-10">
<span class="text-success" id="authSetupSecretKey"></span>
<div class="col-sm-8">
<span class="text-success" id="authSetupSecretKey">@Model.SecuritySettings.TwoFactorKey</span>
</div>
</div>
<hr />
<p>To confirm the third party app is set up correctly, enter the security code that appears on your phone.</p>
<div class="row">
<div class="col-sm-2">
Security Code:
<div class="col-sm-4">
<p class="text-muted">Security Code:</p>
</div>
<div class="col-sm-10">
<div class="col-sm-6">
<input class="form-control" id="auth_setup_code" name="auth_setup_code" title="Authenticator Security Code" type="text" />
</div>
</div>
<hr />
<br />
<div class="row">
<div class="col-sm-12">
<div class="form-group text-right">
<button class="btn btn-primary" id="auth_setup_confirm" type="submit" name="auth_setup_confirm">Confirm</button>
</div>
</div>
<div class="form-group text-right">
<button class="btn btn-primary" id="auth_setup_confirm" type="button" name="auth_setup_confirm">Confirm</button>
</div>
</form>
}
@@ -128,6 +128,22 @@
<textarea class="form-control" id="update_pgp_public_key" name="update_pgp_public_key" placeholder="Public Key Here" title="enter your pgp public key" rows="15">@Model.SecuritySettings.PGPSignature</textarea>
</div>
</div>
<div class="row">
<div class="col-sm-6 text-left">
<label for="update_security_two_factor"><h4>Enable Two Factor Authentication</h4></label>
<div class="checkbox">
<label>
<input id="update_security_two_factor" name="update_security_two_factor" title="whether the key should be saved on the server or not" type="checkbox" value="true" @(Model.SecuritySettings.TwoFactorEnabled ? "checked" : string.Empty) />
</label>
</div>
@if (Model.SecuritySettings.TwoFactorEnabled)
{
<p class="form-control-static">
<small><a href="#" class="text-primary" id="SetupAuthenticator" data-toggle="modal" data-target="#authenticatorSetup"><i class="fa fa-lock"></i> Set Up Authenticator</a></small>
</p>
}
</div>
</div>
<div class="row">
<div class="col-sm-4">
<div class="row">
@@ -151,23 +167,7 @@
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="checkbox">
<label>
<label for="update_security_two_factor"><h4>Enable Two Factor Authentication</h4></label>
<input id="update_security_two_factor" name="update_security_two_factor" title="whether the key should be saved on the server or not" type="checkbox" value="true" @(Model.SecuritySettings.TwoFactorEnabled ? "checked" : string.Empty) />
@if (Model.SecuritySettings.TwoFactorEnabled)
{
<p class="form-control-static">
<small><a href="#" class="text-primary" id="SetupAuthenticator"><i class="fa fa-lock"></i> Set Up Authenticator</a></small>
</p>
}
</label>
</div>
</div>
</div>
</div>
</div>
<!-- Blog Settings -->
<div class="tab-pane" id="blog">
<div class="row">

+ 42
- 0
Teknik/Areas/User/Views/User/TwoFactorCheck.cshtml View File

@@ -0,0 +1,42 @@
@model Teknik.Areas.Users.ViewModels.LoginViewModel

@using Teknik.Helpers

<script>
var confirmAuthCodeURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "ConfirmAuthenticatorCode" })';
</script>

@Scripts.Render("~/bundles/checkAuthCode")

<div class="container">
<div class="row">
<div class="col-md-12">
<div class="text-center">
<h1>Two-factor authentication</h1>
<div class="col-md-4 col-md-offset-4">
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-sm-12 text-center">
<div id="authCheckStatus"></div>
</div>
</div>
<form role="form" id="twoFactorCheckForm" action="##" method="post" accept-charset="UTF-8">
<input name="returnUrl" id="returnUrl" type="hidden" value="@Model.ReturnUrl" />
<input name="rememberMe" id="rememberMe" type="hidden" value="@Model.RememberMe" />
<div class="form-group text-left">
<label for="update_website">Authentication code</label>
<input type="text" class="form-control" id="code" name="code" data-val-required="The Authentication Code is required." data-val="true" />
</div>
<div class="form-group">
<button class="btn btn-primary btn-block" id="verifyCodeSubmit" type="button" name="verifyCodeSubmit">Verify</button>
</div>
</form>
<p class="text-left">Open the two-factor authentication app on your device to view your authentication code and verify your identity.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

BIN
Teknik/Images/logo-black.png View File


BIN
Teknik/Images/logo-blue.png View File


+ 1
- 1
Teknik/Scripts/Common.js View File

@@ -20,7 +20,7 @@
},
success: function (html) {
if (html.result) {
window.location.reload();
window.location = html.result;
}
else {
$("#loginStatus").css('display', 'inline', 'important');

BIN
Teknik/Scripts/_references.js View File


+ 2
- 1
Teknik/Teknik.csproj View File

@@ -342,6 +342,7 @@
<Content Include="Areas\Podcast\Content\Podcast.css" />
<Content Include="Areas\Podcast\Scripts\Podcast.js" />
<Content Include="Areas\Transparency\Scripts\Transparency.js" />
<Content Include="Areas\User\Scripts\CheckAuthCode.js" />
<Content Include="Areas\User\Scripts\User.js" />
<Content Include="Areas\Shortener\Scripts\Shortener.js" />
<Content Include="Areas\Upload\Scripts\Download.js" />
@@ -556,7 +557,7 @@
<Content Include="Areas\User\Views\User\ViewRecoveryEmailVerification.cshtml" />
<Content Include="Areas\User\Views\User\ResetPasswordVerification.cshtml" />
<Content Include="Areas\User\Views\User\ResetPassword.cshtml" />
<Content Include="Areas\User\Views\User\AuthenticatorSetup.cshtml" />
<Content Include="Areas\User\Views\User\TwoFactorCheck.cshtml" />
<None Include="Properties\PublishProfiles\Teknik Dev.pubxml" />
<None Include="Properties\PublishProfiles\Teknik Production.pubxml" />
<None Include="Scripts\jquery-2.1.4.intellisense.js" />

Loading…
Cancel
Save