Quellcode durchsuchen

Added trusted device support for Two Factor authentication.

tags/2.0.3
Teknikode vor 4 Jahren
Ursprung
Commit
000f977dfe

+ 1
- 0
Teknik/App_Start/SubdomainRoute.cs Datei anzeigen

@@ -4,6 +4,7 @@ using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Teknik.Helpers;

namespace Teknik
{

+ 1
- 0
Teknik/Areas/Shortener/Shortener.cs Datei anzeigen

@@ -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

+ 1
- 0
Teknik/Areas/Upload/Uploader.cs Datei anzeigen

@@ -5,6 +5,7 @@ using System.Web;
using System.IO;
using Teknik.Configuration;
using Teknik.Models;
using Teknik.Helpers;

namespace Teknik.Areas.Upload
{

+ 90
- 13
Teknik/Areas/User/Controllers/UserController.cs Datei anzeigen

@@ -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");
}
}
}

+ 3
- 0
Teknik/Areas/User/Models/SecuritySettings.cs Datei anzeigen

@@ -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;

Teknik/Areas/User/Models/UserDevice.cs → Teknik/Areas/User/Models/TrustedDevice.cs Datei anzeigen

@@ -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; }


+ 2
- 2
Teknik/Areas/User/Models/User.cs Datei anzeigen

@@ -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>();
}
}
}

+ 6
- 4
Teknik/Areas/User/Scripts/CheckAuthCode.js Datei anzeigen

@@ -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

+ 3
- 0
Teknik/Areas/User/Scripts/User.js Datei anzeigen

@@ -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,

+ 33
- 3
Teknik/Areas/User/Utility/UserHelper.cs Datei anzeigen

@@ -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;
}
}
}

+ 17
- 0
Teknik/Areas/User/ViewModels/TwoFactorViewModel.cs Datei anzeigen

@@ -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; }
}
}

+ 21
- 13
Teknik/Areas/User/Views/User/Settings.cshtml Datei anzeigen

@@ -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">

+ 15
- 4
Teknik/Areas/User/Views/User/TwoFactorCheck.cshtml Datei anzeigen

@@ -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>

+ 3
- 0
Teknik/Helpers/Constants.cs Datei anzeigen

@@ -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>()
{

+ 1
- 1
Teknik/Helpers/Utility.cs Datei anzeigen

@@ -8,7 +8,7 @@ using System.Linq;
using System.Text;
using System.Web;

namespace Teknik
namespace Teknik.Helpers
{
public static class Utility
{

+ 2
- 2
Teknik/Models/TeknikEntities.cs Datei anzeigen

@@ -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");

+ 2
- 1
Teknik/Teknik.csproj Datei anzeigen

@@ -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" />

Laden…
Abbrechen
Speichern