@@ -4,6 +4,7 @@ using System.Linq; | |||
using System.Web; | |||
using System.Web.Mvc; | |||
using System.Web.Routing; | |||
using Teknik.Helpers; | |||
namespace Teknik | |||
{ |
@@ -4,6 +4,7 @@ using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using Teknik.Areas.Shortener.Models; | |||
using Teknik.Helpers; | |||
using Teknik.Models; | |||
namespace Teknik.Areas.Shortener |
@@ -5,6 +5,7 @@ using System.Web; | |||
using System.IO; | |||
using Teknik.Configuration; | |||
using Teknik.Models; | |||
using Teknik.Helpers; | |||
namespace Teknik.Areas.Upload | |||
{ |
@@ -23,6 +23,7 @@ using Teknik.Filters; | |||
using QRCoder; | |||
using System.Text; | |||
using TwoStepsAuthenticator; | |||
using System.Drawing; | |||
namespace Teknik.Areas.Users.Controllers | |||
{ | |||
@@ -167,10 +168,30 @@ namespace Teknik.Areas.Users.Controllers | |||
bool userValid = UserHelper.UserPasswordCorrect(db, Config, user, model.Password); | |||
if (userValid) | |||
{ | |||
bool twoFactor = false; | |||
string returnUrl = model.ReturnUrl; | |||
if (user.SecuritySettings.TwoFactorEnabled) | |||
{ | |||
twoFactor = true; | |||
// We need to check their device, and two factor them | |||
if (user.SecuritySettings.AllowTrustedDevices) | |||
{ | |||
// Check for the trusted device cookie | |||
HttpCookie cookie = Request.Cookies[Constants.TRUSTEDDEVICECOOKIE + "_" + username]; | |||
if (cookie != null) | |||
{ | |||
string token = cookie.Value; | |||
if (user.TrustedDevices.Where(d => d.Token == token).FirstOrDefault() != null) | |||
{ | |||
// The device token is attached to the user, let's let it slide | |||
twoFactor = false; | |||
} | |||
} | |||
} | |||
} | |||
if (twoFactor) | |||
{ | |||
Session["AuthenticatedUser"] = user; | |||
if (string.IsNullOrEmpty(model.ReturnUrl)) | |||
returnUrl = Request.UrlReferrer.AbsoluteUri.ToString(); | |||
@@ -295,7 +316,7 @@ namespace Teknik.Areas.Users.Controllers | |||
} | |||
[HttpPost] | |||
public ActionResult Edit(string curPass, string newPass, string newPassConfirm, string pgpPublicKey, string recoveryEmail, bool twoFactorEnabled, string website, string quote, string about, string blogTitle, string blogDesc, bool saveKey, bool serverSideEncrypt) | |||
public ActionResult Edit(string curPass, string newPass, string newPassConfirm, string pgpPublicKey, string recoveryEmail, bool allowTrustedDevices, bool twoFactorEnabled, string website, string quote, string about, string blogTitle, string blogDesc, bool saveKey, bool serverSideEncrypt) | |||
{ | |||
if (ModelState.IsValid) | |||
{ | |||
@@ -329,6 +350,7 @@ namespace Teknik.Areas.Users.Controllers | |||
} | |||
user.SecuritySettings.PGPSignature = pgpPublicKey; | |||
// Recovery Email | |||
bool newRecovery = false; | |||
if (recoveryEmail != user.SecuritySettings.RecoveryEmail) | |||
{ | |||
@@ -337,24 +359,56 @@ namespace Teknik.Areas.Users.Controllers | |||
user.SecuritySettings.RecoveryVerified = false; | |||
} | |||
// Trusted Devices | |||
user.SecuritySettings.AllowTrustedDevices = allowTrustedDevices; | |||
if (!allowTrustedDevices) | |||
{ | |||
// They turned it off, let's clear the trusted devices | |||
user.TrustedDevices.Clear(); | |||
List<TrustedDevice> foundDevices = db.TrustedDevices.Where(d => d.UserId == user.UserId).ToList(); | |||
if (foundDevices != null) | |||
{ | |||
foreach (TrustedDevice device in foundDevices) | |||
{ | |||
db.TrustedDevices.Remove(device); | |||
} | |||
} | |||
} | |||
// Two Factor Authentication | |||
bool oldTwoFactor = user.SecuritySettings.TwoFactorEnabled; | |||
user.SecuritySettings.TwoFactorEnabled = twoFactorEnabled; | |||
string newKey = string.Empty; | |||
if (twoFactorEnabled) | |||
if (!oldTwoFactor && twoFactorEnabled) | |||
{ | |||
// They just enabled it, let's regen the key | |||
newKey = Authenticator.GenerateKey(); | |||
} | |||
else if (!twoFactorEnabled) | |||
{ | |||
// remove the key when it's disabled | |||
newKey = string.Empty; | |||
} | |||
else | |||
{ | |||
// No change, let's use the old value | |||
newKey = user.SecuritySettings.TwoFactorKey; | |||
} | |||
user.SecuritySettings.TwoFactorKey = newKey; | |||
// Profile Info | |||
user.UserSettings.Website = website; | |||
user.UserSettings.Quote = quote; | |||
user.UserSettings.About = about; | |||
// Blogs | |||
user.BlogSettings.Title = blogTitle; | |||
user.BlogSettings.Description = blogDesc; | |||
// Uploads | |||
user.UploadSettings.SaveKey = saveKey; | |||
user.UploadSettings.ServerSideEncrypt = serverSideEncrypt; | |||
UserHelper.EditAccount(db, Config, user, changePass, newPass); | |||
// If they have a recovery email, let's send a verification | |||
@@ -557,18 +611,24 @@ namespace Teknik.Areas.Users.Controllers | |||
[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); | |||
User user = (User)Session["AuthenticatedUser"]; | |||
if (user != null) | |||
{ | |||
ViewBag.Title = "Unknown Device - " + Config.Title; | |||
ViewBag.Description = "We do not recognize this device."; | |||
TwoFactorViewModel model = new TwoFactorViewModel(); | |||
model.ReturnUrl = returnUrl; | |||
model.RememberMe = rememberMe; | |||
model.AllowTrustedDevice = user.SecuritySettings.AllowTrustedDevices; | |||
return View("/Areas/User/Views/User/TwoFactorCheck.cshtml", model); | |||
} | |||
return Redirect(Url.SubRouteUrl("error", "Error.Http403")); | |||
} | |||
[HttpPost] | |||
[AllowAnonymous] | |||
public ActionResult ConfirmAuthenticatorCode(string code, string returnUrl, bool rememberMe) | |||
public ActionResult ConfirmAuthenticatorCode(string code, string returnUrl, bool rememberMe, bool rememberDevice, string deviceName) | |||
{ | |||
User user = (User)Session["AuthenticatedUser"]; | |||
if (user != null) | |||
@@ -586,6 +646,23 @@ namespace Teknik.Areas.Users.Controllers | |||
HttpCookie authcookie = UserHelper.CreateAuthCookie(user.Username, rememberMe, Request.Url.Host.GetDomain(), Request.IsLocal); | |||
Response.Cookies.Add(authcookie); | |||
if (user.SecuritySettings.AllowTrustedDevices && rememberDevice) | |||
{ | |||
// They want to remember the device, and have allow trusted devices on | |||
HttpCookie trustedDeviceCookie = UserHelper.CreateTrustedDeviceCookie(user.Username, Request.Url.Host.GetDomain(), Request.IsLocal); | |||
Response.Cookies.Add(trustedDeviceCookie); | |||
TrustedDevice device = new TrustedDevice(); | |||
device.UserId = user.UserId; | |||
device.Name = (string.IsNullOrEmpty(deviceName)) ? "Unknown" : deviceName; | |||
device.DateSeen = DateTime.Now; | |||
device.Token = trustedDeviceCookie.Value; | |||
// Add the token | |||
db.TrustedDevices.Add(device); | |||
db.SaveChanges(); | |||
} | |||
if (string.IsNullOrEmpty(returnUrl)) | |||
returnUrl = Request.UrlReferrer.AbsoluteUri.ToString(); | |||
return Json(new { result = returnUrl }); | |||
@@ -628,9 +705,9 @@ namespace Teknik.Areas.Users.Controllers | |||
QRCodeGenerator qrGenerator = new QRCodeGenerator(); | |||
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"); | |||
QRCode qrCode = new QRCode(qrCodeData); | |||
Bitmap qrCodeImage = qrCode.GetGraphic(20); | |||
return File(Helpers.Utility.ImageToByte(qrCodeImage), "image/png"); | |||
} | |||
} | |||
} |
@@ -24,6 +24,8 @@ namespace Teknik.Areas.Users.Models | |||
public bool RecoveryVerified { get; set; } | |||
public bool AllowTrustedDevices { get; set; } | |||
public bool TwoFactorEnabled { get; set; } | |||
[CaseSensitive] | |||
@@ -35,6 +37,7 @@ namespace Teknik.Areas.Users.Models | |||
{ | |||
RecoveryEmail = string.Empty; | |||
RecoveryVerified = false; | |||
AllowTrustedDevices = false; | |||
TwoFactorEnabled = false; | |||
TwoFactorKey = string.Empty; | |||
PGPSignature = string.Empty; |
@@ -6,9 +6,9 @@ using Teknik.Attributes; | |||
namespace Teknik.Areas.Users.Models | |||
{ | |||
public class UserDevice | |||
public class TrustedDevice | |||
{ | |||
public int UserDeviceId { get; set; } | |||
public int TrustedDeviceId { get; set; } | |||
public int UserId { get; set; } | |||
@@ -33,7 +33,7 @@ namespace Teknik.Areas.Users.Models | |||
public virtual UploadSettings UploadSettings { get; set; } | |||
public virtual ICollection<UserDevice> Devices { get; set; } | |||
public virtual ICollection<TrustedDevice> TrustedDevices { get; set; } | |||
public virtual ICollection<Upload.Models.Upload> Uploads { get; set; } | |||
@@ -47,7 +47,7 @@ namespace Teknik.Areas.Users.Models | |||
JoinDate = DateTime.Now; | |||
LastSeen = DateTime.Now; | |||
Groups = new List<Group>(); | |||
Devices = new List<UserDevice>(); | |||
TrustedDevices = new List<TrustedDevice>(); | |||
} | |||
} | |||
} |
@@ -2,16 +2,18 @@ | |||
$("#authCheckStatus").css('display', 'none', 'important'); | |||
$("#verifyCodeSubmit").click(function () { | |||
setCode = $("#code").val(); | |||
returnUrl = $("#returnUrl").val(); | |||
rememberMe = ($("#rememberMe").val() == 'True'); | |||
setCode = $("#Code").val(); | |||
returnUrl = $("#ReturnUrl").val(); | |||
rememberMe = ($("#RememberMe").val() == 'True'); | |||
rememberDevice = $("#RememberDevice").is(":checked"); | |||
$.ajax({ | |||
type: "POST", | |||
url: confirmAuthCodeURL, | |||
data: { | |||
code: setCode, | |||
returnUrl: returnUrl, | |||
rememberMe: rememberMe | |||
rememberMe: rememberMe, | |||
rememberDevice: rememberDevice | |||
}, | |||
xhrFields: { | |||
withCredentials: true |
@@ -2,6 +2,7 @@ | |||
$("[name='update_upload_saveKey']").bootstrapSwitch(); | |||
$("[name='update_upload_serverSideEncrypt']").bootstrapSwitch(); | |||
$("[name='update_security_two_factor']").bootstrapSwitch(); | |||
$("[name='update_security_allow_trusted']").bootstrapSwitch(); | |||
$('#ResendVerification').click(function () { | |||
$.ajax({ | |||
@@ -98,6 +99,7 @@ | |||
password = $("#update_password").val(); | |||
password_confirm = $("#update_password_confirm").val(); | |||
update_pgp_public_key = $("#update_pgp_public_key").val(); | |||
update_security_allow_trusted = $("#update_security_allow_trusted").is(":checked"); | |||
update_security_two_factor = $("#update_security_two_factor").is(":checked"); | |||
recovery = $("#update_recovery_email").val(); | |||
website = $("#update_website").val(); | |||
@@ -115,6 +117,7 @@ | |||
newPass: password, | |||
newPassConfirm: password_confirm, | |||
pgpPublicKey: update_pgp_public_key, | |||
allowTrustedDevices: update_security_allow_trusted, | |||
twoFactorEnabled: update_security_two_factor, | |||
recoveryEmail: recovery, | |||
website: website, |
@@ -437,7 +437,7 @@ namespace Teknik.Areas.Users.Utility | |||
} | |||
// Create a new verification code and add it | |||
string verifyCode = Teknik.Utility.RandomString(24); | |||
string verifyCode = Helpers.Utility.RandomString(24); | |||
RecoveryEmailVerification ver = new RecoveryEmailVerification(); | |||
ver.UserId = user.UserId; | |||
ver.Code = verifyCode; | |||
@@ -518,7 +518,7 @@ Thank you and enjoy! | |||
} | |||
// Create a new verification code and add it | |||
string verifyCode = Teknik.Utility.RandomString(24); | |||
string verifyCode = Helpers.Utility.RandomString(24); | |||
ResetPasswordVerification ver = new ResetPasswordVerification(); | |||
ver.UserId = user.UserId; | |||
ver.Code = verifyCode; | |||
@@ -858,7 +858,7 @@ If you recieved this email and you did not reset your password, you can ignore t | |||
{ | |||
Config config = Config.Load(); | |||
HttpCookie authcookie = FormsAuthentication.GetAuthCookie(username, remember); | |||
authcookie.Name = "TeknikAuth"; | |||
authcookie.Name = Constants.AUTHCOOKIE; | |||
authcookie.HttpOnly = true; | |||
authcookie.Secure = true; | |||
@@ -878,5 +878,35 @@ If you recieved this email and you did not reset your password, you can ignore t | |||
return authcookie; | |||
} | |||
public static HttpCookie CreateTrustedDeviceCookie(string username, string domain, bool local) | |||
{ | |||
Config config = Config.Load(); | |||
byte[] time = BitConverter.GetBytes(DateTime.UtcNow.ToBinary()); | |||
byte[] key = Guid.NewGuid().ToByteArray(); | |||
string token = Convert.ToBase64String(time.Concat(key).ToArray()); | |||
HttpCookie trustCookie = new HttpCookie(Constants.TRUSTEDDEVICECOOKIE + "_" + username); | |||
trustCookie.Value = token; | |||
trustCookie.HttpOnly = true; | |||
trustCookie.Secure = true; | |||
trustCookie.Expires = DateTime.Now.AddYears(1); | |||
// Set domain dependent on where it's being ran from | |||
if (local) // localhost | |||
{ | |||
trustCookie.Domain = null; | |||
} | |||
else if (config.DevEnvironment) // dev.example.com | |||
{ | |||
trustCookie.Domain = string.Format("dev.{0}", domain); | |||
} | |||
else // A production instance | |||
{ | |||
trustCookie.Domain = string.Format(".{0}", domain); | |||
} | |||
return trustCookie; | |||
} | |||
} | |||
} |
@@ -0,0 +1,17 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Web; | |||
using Teknik.ViewModels; | |||
namespace Teknik.Areas.Users.ViewModels | |||
{ | |||
public class TwoFactorViewModel : ViewModelBase | |||
{ | |||
public bool RememberMe { get; set; } | |||
public string ReturnUrl { get; set; } | |||
public bool AllowTrustedDevice { get; set; } | |||
} | |||
} |
@@ -125,19 +125,6 @@ | |||
<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> | |||
<p class="form-control-static @(Model.SecuritySettings.TwoFactorEnabled ? string.Empty : "hide")" id="setupAuthenticatorLink"> | |||
<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"> | |||
@@ -161,6 +148,27 @@ | |||
</div> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="col-sm-4 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 two factor authentication should occur for this account" type="checkbox" value="true" @(Model.SecuritySettings.TwoFactorEnabled ? "checked" : string.Empty) /> | |||
</label> | |||
</div> | |||
<p class="form-control-static @(Model.SecuritySettings.TwoFactorEnabled ? string.Empty : "hide")" id="setupAuthenticatorLink"> | |||
<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 class="col-sm-4 text-left"> | |||
<label for="update_security_allow_trusted"><h4>Allow Trusted Device Saving</h4></label> | |||
<div class="checkbox"> | |||
<label> | |||
<input id="update_security_allow_trusted" name="update_security_allow_trusted" title="whether a device could be cached to bypass two factor authentication" type="checkbox" value="true" @(Model.SecuritySettings.AllowTrustedDevices ? "checked" : string.Empty) /> | |||
</label> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- Blog Settings --> | |||
<div class="tab-pane" id="blog"> |
@@ -1,4 +1,4 @@ | |||
@model Teknik.Areas.Users.ViewModels.LoginViewModel | |||
@model Teknik.Areas.Users.ViewModels.TwoFactorViewModel | |||
@using Teknik.Helpers | |||
@@ -22,12 +22,23 @@ | |||
</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" /> | |||
<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" /> | |||
<input type="text" class="form-control" id="Code" name="Code" data-val-required="The Authentication Code is required." data-val="true" /> | |||
</div> | |||
@if (Model.AllowTrustedDevice) | |||
{ | |||
<div class="checkbox"> | |||
<label> | |||
<input id="RememberDevice" type="checkbox" value="true" name="RememberDevice" /><input name="RememberDevice" type="hidden" value="false"> Remember Device | |||
</label> | |||
</div> | |||
<small>Set this device as a trusted device. It is not advised to trust a public computer.</small> | |||
<br /> | |||
<br /> | |||
} | |||
<div class="form-group"> | |||
<button class="btn btn-primary btn-block" id="verifyCodeSubmit" type="button" name="verifyCodeSubmit">Verify</button> | |||
</div> |
@@ -9,6 +9,9 @@ namespace Teknik.Helpers | |||
public static class Constants | |||
{ | |||
public const string SERVERUSER = "Server Admin"; | |||
public const string AUTHCOOKIE = "TeknikAuth"; | |||
public const string TRUSTEDDEVICECOOKIE = "TeknikTrustedDevice"; | |||
// Paste Constants | |||
public static Dictionary<string, string> HIGHLIGHTFORMATS = new Dictionary<string, string>() | |||
{ |
@@ -8,7 +8,7 @@ using System.Linq; | |||
using System.Text; | |||
using System.Web; | |||
namespace Teknik | |||
namespace Teknik.Helpers | |||
{ | |||
public static class Utility | |||
{ |
@@ -22,7 +22,7 @@ namespace Teknik.Models | |||
public DbSet<User> Users { get; set; } | |||
public DbSet<Group> Groups { get; set; } | |||
public DbSet<Role> Roles { get; set; } | |||
public DbSet<UserDevice> UserDevices { get; set; } | |||
public DbSet<TrustedDevice> TrustedDevices { get; set; } | |||
public DbSet<TransferType> TransferTypes { get; set; } | |||
// User Settings | |||
public DbSet<UserSettings> UserSettings { get; set; } | |||
@@ -112,7 +112,7 @@ namespace Teknik.Models | |||
modelBuilder.Entity<User>().ToTable("Users"); | |||
modelBuilder.Entity<Group>().ToTable("Groups"); | |||
modelBuilder.Entity<Role>().ToTable("Roles"); | |||
modelBuilder.Entity<UserDevice>().ToTable("UserDevices"); | |||
modelBuilder.Entity<TrustedDevice>().ToTable("TrustedDevices"); | |||
modelBuilder.Entity<TransferType>().ToTable("TransferTypes"); | |||
modelBuilder.Entity<RecoveryEmailVerification>().ToTable("RecoveryEmailVerifications"); | |||
modelBuilder.Entity<ResetPasswordVerification>().ToTable("ResetPasswordVerifications"); |
@@ -224,7 +224,8 @@ | |||
<Compile Include="Areas\User\Models\ResetPasswordVerification.cs" /> | |||
<Compile Include="Areas\User\Models\RecoveryEmailVerification.cs" /> | |||
<Compile Include="Areas\User\Models\SecuritySettings.cs" /> | |||
<Compile Include="Areas\User\Models\UserDevice.cs" /> | |||
<Compile Include="Areas\User\Models\TrustedDevice.cs" /> | |||
<Compile Include="Areas\User\ViewModels\TwoFactorViewModel.cs" /> | |||
<Compile Include="Models\TransferTypes.cs" /> | |||
<Compile Include="Areas\User\Models\UploadSettings.cs" /> | |||
<Compile Include="Areas\User\Models\UserSettings.cs" /> |