Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
Browse files
Merge pull request #67 from brockallen/2fa
add two factor authentication support
  • Loading branch information
brockallen committed Aug 3, 2013
2 parents d2ea2a3 + d9fa1e8 commit 992b5265350b8b723836bb3a4d8d4a22b03e780e
Showing 33 changed files with 1,194 additions and 90 deletions.
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Net.Http;
using System.Web;

namespace BrockAllen.MembershipReboot.Mvc.App_Start
@@ -12,8 +13,8 @@ public static MembershipRebootConfiguration Create()
{
var settings = SecuritySettings.Instance;
var config = new MembershipRebootConfiguration(settings, new DelegateFactory(()=>new DefaultUserAccountRepository(settings.ConnectionStringName)));
var formatter = new EmailMessageFormatter(new Lazy<ApplicationInformation>(() =>

var appinfo = new Lazy<ApplicationInformation>(() =>
{
// build URL
var baseUrl = HttpContext.Current.GetApplicationUrl();
@@ -29,14 +30,70 @@ public static MembershipRebootConfiguration Create()
ConfirmPasswordResetUrl = baseUrl + "PasswordReset/Confirm/",
ConfirmChangeEmailUrl = baseUrl + "ChangeEmail/Confirm/"
};
}));
});
var emailFormatter = new EmailMessageFormatter(appinfo);
if (settings.RequireAccountVerification)
{
config.AddEventHandler(new EmailAccountCreatedEventHandler(formatter));
config.AddEventHandler(new EmailAccountCreatedEventHandler(emailFormatter));
}
config.AddEventHandler(new EmailAccountEventsHandler(formatter));
config.AddEventHandler(new EmailAccountEventsHandler(emailFormatter));
config.AddEventHandler(new TwilloSmsEventHandler(appinfo));

return config;
}
}

public class TwilloSmsEventHandler : SmsEventHandler
{
const string sid = "";
const string token = "";
const string fromPhone = "";

public TwilloSmsEventHandler(Lazy<ApplicationInformation> appInfo)
: base(new SmsMessageFormatter(appInfo))
{
}

string Url
{
get
{
return String.Format("https://api.twilio.com/2010-04-01/Accounts/{0}/SMS/Messages", sid);
}
}

string BasicAuthToken
{
get
{
var val = sid + ":" + token;
var bytes = System.Text.Encoding.UTF8.GetBytes(val);
val = Convert.ToBase64String(bytes);
return val;
}
}

HttpContent GetBody(Message msg)
{
var values = new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("From", fromPhone),
new KeyValuePair<string, string>("To", msg.To),
new KeyValuePair<string, string>("Body", msg.Body),
};

return new FormUrlEncodedContent(values);
}

protected override void SendSms(Message message)
{
if (!String.IsNullOrWhiteSpace(sid))
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", BasicAuthToken);
var result = client.PostAsync(Url, GetBody(message)).Result;
result.EnsureSuccessStatusCode();
}
}
}
}
@@ -0,0 +1,101 @@
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using BrockAllen.MembershipReboot.Mvc.Areas.UserAccount.Models;

namespace BrockAllen.MembershipReboot.Mvc.Areas.UserAccount.Controllers
{
[Authorize]
public class ChangeMobileController : Controller
{
UserAccountService userAccountService;
ClaimsBasedAuthenticationService authSvc;

public ChangeMobileController(UserAccountService userAccountService, ClaimsBasedAuthenticationService authSvc)
{
this.userAccountService = userAccountService;
this.authSvc = authSvc;
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
this.userAccountService.TryDispose();
this.userAccountService = null;
}
base.Dispose(disposing);
}

public ActionResult Index()
{
return View("Index");
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(string button, ChangeMobileRequestInputModel model)
{
if (button == "change")
{
if (ModelState.IsValid)
{
try
{
if (this.userAccountService.ChangeMobilePhoneRequest(User.Identity.Name, model.NewMobilePhone))
{
return View("ChangeRequestSuccess", (object)model.NewMobilePhone);
}

ModelState.AddModelError("", "Error requesting mobile phone number change.");
}
catch (ValidationException ex)
{
ModelState.AddModelError("", ex.Message);
}
}
}

if (button == "remove")
{
if (this.userAccountService.RemoveMobilePhone(User.GetUserID()))
{
return View("Success");
}
else
{
ModelState.AddModelError("", "Error removing the mobile phone");
}
}

return View("Index", model);
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Confirm(ChangeMobileFromCodeInputModel model)
{
if (ModelState.IsValid)
{
try
{
if (this.userAccountService.ChangeMobileFromCode(this.User.Identity.Name, model.Code))
{
// since the mobile had changed, reissue the
// cookie with the updated claims
authSvc.SignIn(this.User.Identity.Name);

return View("Success");
}

ModelState.AddModelError("", "Error confirming code.");
}
catch (ValidationException ex)
{
ModelState.AddModelError("", ex.Message);
}
}

return View("Confirm", model);
}
}
}
@@ -1,5 +1,6 @@
using System.Web.Mvc;
using BrockAllen.MembershipReboot.Mvc.Areas.UserAccount.Models;
using System.Security.Claims;

namespace BrockAllen.MembershipReboot.Mvc.Areas.UserAccount.Controllers
{
@@ -52,23 +53,22 @@ public ActionResult Index(LoginInputModel model)
{
authSvc.SignIn(account);

//authSvc.SignIn(model.Username);

if (userAccountService.IsPasswordExpired(model.Username))
if (account.UseTwoFactorAuth)
{
return View("TwoFactorAuth");
}

if (userAccountService.IsPasswordExpired(account.Username))
{
return RedirectToAction("Index", "ChangePassword");
}
else

if (Url.IsLocalUrl(model.ReturnUrl))
{
if (Url.IsLocalUrl(model.ReturnUrl))
{
return Redirect(model.ReturnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
return Redirect(model.ReturnUrl);
}

return RedirectToAction("Index", "Home");
}
else
{
@@ -78,5 +78,51 @@ public ActionResult Index(LoginInputModel model)

return View(model);
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult TwoFactorAuthLogin(string button, TwoFactorAuthInputModel model)
{
if (!User.HasUserID())
{
// if the temp cookie is expired, then make the login again
return RedirectToAction("Index");
}

if (button == "signin")
{
if (ModelState.IsValid)
{
BrockAllen.MembershipReboot.UserAccount account;
if (userAccountService.AuthenticateWithCode(this.User.GetUserID(), model.Code, out account))
{
authSvc.SignIn(account);

if (userAccountService.IsPasswordExpired(account.Username))
{
return RedirectToAction("Index", "ChangePassword");
}

if (Url.IsLocalUrl(model.ReturnUrl))
{
return Redirect(model.ReturnUrl);
}

return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "Invalid Code");
}
}
}

if (button == "resend")
{
this.userAccountService.SendTwoFactorAuthenticationCode(this.User.GetUserID());
}

return View("TwoFactorAuth", model);
}
}
}
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace BrockAllen.MembershipReboot.Mvc.Areas.UserAccount.Controllers
{
public class TwoFactorAuthController : Controller
{
UserAccountService userAccountService;

public TwoFactorAuthController(UserAccountService userAccountService)
{
this.userAccountService = userAccountService;
}

public ActionResult Index()
{
var acct = userAccountService.GetByUsername(this.User.Identity.Name);
return View(acct);
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Enable()
{
if (this.userAccountService.EnableTwoFactorAuthentication(this.User.GetUserID()))
{
return View("Success");
}

return View("Fail");
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Disable()
{
this.userAccountService.DisableTwoFactorAuthentication(this.User.GetUserID());
return View("Success");
}
}
}
@@ -3,17 +3,9 @@

namespace BrockAllen.MembershipReboot.Mvc.Areas.UserAccount.Models
{
public class ChangeEmailFromKeyInputModel
public class ChangeMobileFromCodeInputModel
{
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }

[Required]
[EmailAddress]
public string NewEmail { get; set; }

[HiddenInput]
public string Key { get; set; }
public string Code { get; set; }
}
}
@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace BrockAllen.MembershipReboot.Mvc.Areas.UserAccount.Models
{
public class ChangeEmailFromKeyInputModel
{
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }

[Required]
[EmailAddress]
public string NewEmail { get; set; }

[HiddenInput]
public string Key { get; set; }
}
}
@@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations;

namespace BrockAllen.MembershipReboot.Mvc.Areas.UserAccount.Models
{
public class ChangeMobileRequestInputModel
{
//[Required]
public string NewMobilePhone { get; set; }
}
}
@@ -5,6 +5,7 @@ namespace BrockAllen.MembershipReboot.Mvc.Areas.UserAccount.Models
public class LoginInputModel
{
[Required]
[Display(Name="Username or Email")]
public string Username { get; set; }
[Required]
[DataType(DataType.Password)]

0 comments on commit 992b526

Please sign in to comment.