Skip to content

Commit

Permalink
Create Auth(Login, Register) system
Browse files Browse the repository at this point in the history
  • Loading branch information
manio143 committed Nov 20, 2017
1 parent cd42e11 commit 15f0be3
Show file tree
Hide file tree
Showing 8 changed files with 1,175 additions and 1 deletion.
30 changes: 30 additions & 0 deletions Auth/Authentication.cs
@@ -0,0 +1,30 @@
namespace PLQRefereeApp
{
public class Authentication
{
public static string Scheme => "Cookies"; //same as default for CookieAuthentication

private UserRepository UserRepository { get; }
public Authentication(UserRepository userRepository)
{
UserRepository = userRepository;
}

public bool TryAuthenticateUser(string email, string password, out User outUser)
{
outUser = null;

if (!UserRepository.UserExists(email))
return false;

var user = UserRepository.GetUser(email);

if (!BCrypt.Net.BCrypt.Verify(password, user.Passphrase))
return false;

outUser = user;
return true;
}

}
}
817 changes: 817 additions & 0 deletions Auth/BCrypt.cs

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions Auth/SessionExtension.cs
@@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace PLQRefereeApp
{
public static class SessionExtension
{
public static User GetUser(this ISession session, UserRepository userRepository) {
return userRepository.GetUser(session.GetInt32("UserId").Value);
}
}
}
138 changes: 138 additions & 0 deletions Controllers/AuthController.cs
@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Security.Claims;

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;

namespace PLQRefereeApp
{
public class LoginPostData
{
public string Email { get; set; }
public string Password { get; set; }
}

public class RegisterPostData
{
public string Email { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Team { get; set; }
}

public enum RegisterError
{
EmailAlreadyInUse,
InvalidValue
}

public class AuthControler : Controller
{
private UserRepository UserRepository { get; }
private Authentication Authentication { get; }
public AuthControler(UserRepository userRepository)
{
UserRepository = userRepository;
Authentication = new Authentication(userRepository);
}

[Route("/login")]
[HttpGet]
[AllowAnonymous]
public IActionResult LoginView(string returnUrl = null)
{
if(HttpContext.User.Identity.IsAuthenticated)
return LocalRedirect(returnUrl ?? "/");
return View("Login");
}

[Route("/login")]
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> LoginPost(LoginPostData loginData, string returnUrl = null)
{
if (Authentication.TryAuthenticateUser(loginData.Email, loginData.Password, out var user))
{
await SetAuthSession(user);
return LocalRedirect(returnUrl ?? "/");
}
else
{
ViewBag.Error = true;
return View("Login");
}
}

[Route("/logout")]
[HttpGet]
[Authorize]
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync(Authentication.Scheme);
return LocalRedirect("/");
}

private ClaimsPrincipal ConstructClaimsPrincipal(User user)
{
return new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
{
new Claim(ClaimTypes.Email, user.Email),
new Claim(ClaimTypes.Role, user.Administrator ? "Admin" : "User")
}, Authentication.Scheme));
}

private async Task SetAuthSession(User user)
{
await HttpContext.SignInAsync(Authentication.Scheme, ConstructClaimsPrincipal(user));
HttpContext.Session.SetInt32("UserId", user.Id);
}

[Route("/register")]
[HttpGet]
[AllowAnonymous]
public IActionResult Register()
{
if(HttpContext.User.Identity.IsAuthenticated)
return LocalRedirect("/");
return View("Register");
}

[Route("/register")]
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RegisterPost(RegisterPostData registerData)
{
if (UserRepository.UserExists(registerData.Email))
{
ViewBag.Error = RegisterError.EmailAlreadyInUse;
return View("Register");
}

if(String.IsNullOrWhiteSpace(registerData.Name)
|| String.IsNullOrWhiteSpace(registerData.Surname)
|| String.IsNullOrWhiteSpace(registerData.Email)
|| String.IsNullOrWhiteSpace(registerData.Password)) {
ViewBag.Error = RegisterError.InvalidValue;
return View("Register");
}

var passphrase = BCrypt.Net.BCrypt.HashPassword(registerData.Password, 13); //2^13 iterations

var user = new User { Email = registerData.Email, Passphrase = passphrase };
var userData = new UserData { Name = registerData.Name, Surname = registerData.Surname, Team = registerData.Surname };

UserRepository.AddUser(user, userData);

await SetAuthSession(user);
return LocalRedirect("/");
}
}
}
49 changes: 49 additions & 0 deletions Startup.cs
Expand Up @@ -7,6 +7,8 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Authentication.Cookies;

namespace PLQRefereeApp
{
Expand All @@ -23,7 +25,50 @@ public Startup(IConfiguration configuration)
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromDays(1);
options.Cookie.Name = "Session";
options.Cookie.HttpOnly = true;
if (Configuration.GetValue("SessionCookieAlwaysSecure", true))
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
else
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
});
services.AddAuthentication(Authentication.Scheme)
.AddCookie(options =>
{
options.AccessDeniedPath = "/forbidden";
options.LoginPath = "/login";
options.LogoutPath = "/logout";
options.ReturnUrlParameter = "returnUrl";
options.ExpireTimeSpan = TimeSpan.FromDays(1);
options.Cookie.Name = "AuthSession";
options.Cookie.SameSite = SameSiteMode.Strict;
if (Configuration.GetValue("SessionCookieAlwaysSecure", true))
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
else
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Validate();
});
services.AddDbContext<MainContext>(options => options.UseMySql(Configuration.GetConnectionString("Database")));
services.AddAntiforgery(options =>
{
options.Cookie.Name = "CsrfToken";
options.Cookie.HttpOnly = false;
options.FormFieldName = "csrftoken";
options.HeaderName = "X-CSRF-TOKEN";
options.SuppressXFrameOptionsHeader = false;
if (Configuration.GetValue("SessionCookieAlwaysSecure", true))
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
else
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
});

services.AddTransient<UserRepository, UserRepository>();
services.AddTransient<TestRepository, TestRepository>();
services.AddTransient<QuestionRepository, QuestionRepository>();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
Expand All @@ -38,6 +83,10 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
app.UseExceptionHandler("/Error");
}

app.UseSession();

app.UseAuthentication();

app.UseStaticFiles();

app.UseMvc(routes =>
Expand Down
24 changes: 24 additions & 0 deletions Views/Shared/Login.cshtml
@@ -0,0 +1,24 @@
@{
ViewData["Title"] = "Login";
bool error = (bool?)ViewData["Error"] ?? false;
}

<h1>Zaloguj</h1>
@if (error) {
<p class="red">Nieprawidłowy email lub hasło.</p>
}

<form method="POST">
<div>
<label for="email">Email</label>
<input type="text" name="email">
</div>
<div>
<label for="password">Hasło</label>
<input type="password" name="password">
</div>
<div>
@Html.AntiForgeryToken()
<button type="submit">Zaloguj</button>
</div>
</form>
103 changes: 103 additions & 0 deletions Views/Shared/Register.cshtml
@@ -0,0 +1,103 @@
@{
ViewData["Title"] = "Rejestracja";
var error = (RegisterError?)ViewData["Error"];
}

<h1>Rejestracja</h1>
@if (error == RegisterError.InvalidValue) {
<p class="red">Wprowadzone dane są nieprawidłowe.</p>
} else if (error == RegisterError.EmailAlreadyInUse) {
<p class="red">Podane adres email jest już w użyciu.</p>
}

<form method="POST" id="register-form">
<div id="register">
<div>
<label for="email">Email<span class="red">*</span></label>
<input type="email" name="email" required>
</div>
<div>
<label for="password">Hasło<span class="red">*</span></label>
<input type="password" name="password" id="password" required>
</div>
<div>
<label>Powtórz hasło<span class="red">*</span></label>
<input type="password" id="password-r" required>
</div>
<div style="display: block">
<p>Siła twojego hasła <span id="pwd-strength">........</span></p>
</div>
<div>
<label for="name">Imię<span class="red">*</span></label>
<input type="text" name="name" required>
</div>
<div>
<label for="surname">Nazwisko<span class="red">*</span></label>
<input type="text" name="surname" required>
</div>
<div>
<label for="team">Drużyna</label>
<input type="text" name="team">
</div>
<div>
@Html.AntiForgeryToken()
<button type="submit" id="submit">Rejestruj</button>
</div>
</div>
<div>
Hasło powinno:
<ul>
<li>Mieć conajmniej 10 znaków.</li>
<li>Nie powtarzać się na innych twoich kontach.</li>
<li>Nie być "znanym" hasłem (na liście popularnych haseł).</li>
<li>Łatwe do zapamiętania są frazy (patrz <a href="https://www.xkcd.com/936/">komiks XKCD</a>).</li>
</ul>
</div>
</form>

@section Scripts {
<script src="/js/zxcvbn.js" async></script>
<script>
window.onload = function() {
let pwd = document.getElementById("password");
let pwdr = document.getElementById("password-r");
let sbmt = document.getElementById("submit");
let pwdStrength = document.getElementById("pwd-strength");
let passCheck = () => {
if(pwd.value.length < 8)
pwd.setCustomValidity("Hasło musi mieć przynajmniej 8 znaków!");
else pwd.setCustomValidity("");
if(pwd.value != pwdr.value)
pwdr.setCustomValidity("Hasła muszą się zgadzać!");
else pwdr.setCustomValidity("");
};
pwd.onkeydown = () => {
let result = zxcvbn(pwd.value);
if(result.score <= 1) {
pwdStrength.innerText = "Słabe";
pwdStrength.style.color = "red";
} else if (result.score == 2) {
pwdStrength.innerText = "Średnie";
pwdStrength.style.color = "#f7c120";
} else if (result.score == 3) {
pwdStrength.innerText = "Dobre";
pwdStrength.style.color = "green";
}
else {
pwdStrength.innerText = "Wspaniałe";
pwdStrength.style.color = "green";
}
passCheck();
};
pwdr.onkeydown = () => {
passCheck();
};
sbmt.onclick = () => {
passCheck();
};
}
</script>
}
3 changes: 2 additions & 1 deletion appsettings.json
Expand Up @@ -7,5 +7,6 @@
},
"ConnectionStrings": {
"Database": "Host=####;Database=Main;Username=root;Password=root"
}
},
"SessionCookieAlwaysSecure": false
}

0 comments on commit 15f0be3

Please sign in to comment.