Skip to content

Commit

Permalink
Merge pull request #36 from devdanielsun/developer
Browse files Browse the repository at this point in the history
Developer
  • Loading branch information
devdanielsun authored Feb 3, 2024
2 parents 5b8dca3 + c050426 commit a79fb7e
Show file tree
Hide file tree
Showing 69 changed files with 1,524 additions and 157 deletions.
2 changes: 1 addition & 1 deletion database/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ MS SQL database
```
```
docker run -d --name POLLOR_DATABASE -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=myPassw0rd' -p 1433:1433 mcr.microsoft.com/mssql/server:2022-latest
docker run -d --name POLLOR_DATABASE -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=myPassw0rd" -p 1433:1433 mcr.microsoft.com/mssql/server:2022-latest
```
```
Expand Down
11 changes: 7 additions & 4 deletions database/migration.sql
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ GO
CREATE TABLE [dbo].[users](
[id] [int] IDENTITY(1,1) NOT NULL,
[emailaddress] [nvarchar](256) NOT NULL,
[first_name] [nvarchar](64) NOT NULL,
[last_name] [nvarchar](64) NOT NULL,
[profile_username] [nvarchar](64) NOT NULL,
[username] [nvarchar](64) NOT NULL,
[password] [nvarchar](128) NOT NULL,
[first_name] [nvarchar](64) NULL,
[last_name] [nvarchar](64) NULL,
[role] [nvarchar](32) NULL DEFAULT 'user',
[created_at] [datetime] NOT NULL,
CONSTRAINT PK_users PRIMARY KEY NONCLUSTERED (id)
CONSTRAINT PK_users PRIMARY KEY NONCLUSTERED (id),
CONSTRAINT UC_Users UNIQUE (id,emailaddress,username)
) ON [PRIMARY]
GO

Expand Down
4 changes: 2 additions & 2 deletions database/seed.sql
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
USE [pollor_db]
GO

INSERT INTO users (emailaddress, first_name, last_name, profile_username, created_at)
VALUES ('[email protected]', 'Tester', 'Test', 'Testing', '1970-01-01T00:00:01');
INSERT INTO users (emailaddress, first_name, last_name, username, password, created_at)
VALUES ('[email protected]', 'Tester', 'Test', 'Testing', '', '1970-01-01T00:00:01');
GO

INSERT INTO polls (user_id, question, ending_date, created_at)
Expand Down
4 changes: 3 additions & 1 deletion pollor.Server/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ DB_SERVER=localhost\MSSQLSERVERx
DB_NAME=name
DB_UID=
DB_PASSWORD=
ASPNETCORE_ENVIRONMENT=Development
ASPNETCORE_ENVIRONMENT=Development
SECRET_JWT_KEY=secret-hash-key
JWT_TOKEN_DOMAIN=https://localhost:5001
35 changes: 29 additions & 6 deletions pollor.Server/Controllers/AnswersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
using pollor.Server.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore.ChangeTracking;

namespace answeror.Server.Controllers
{
[ApiController]
[Route("[controller]")]
[Route("api/")]
public class AnswersController : ControllerBase
{
private readonly ILogger<AnswersController> _logger;
Expand All @@ -17,7 +19,7 @@ public AnswersController(ILogger<AnswersController> logger)
_logger = logger;
}

[HttpGet(Name = "GetAnswersController")]
[HttpGet("answers")]
public IActionResult GetAllAnswers()
{
try {
Expand All @@ -26,7 +28,7 @@ public IActionResult GetAllAnswers()
.Include(a => a.Votes)
.ToList();
if (answers.IsNullOrEmpty()) {
return NotFound();
return NotFound(new { message = "No records found" });
}
return Ok(answers);
}
Expand All @@ -37,17 +39,17 @@ public IActionResult GetAllAnswers()
}
}

[HttpGet("{id}")]
[HttpGet("answer/{id}")]
public IActionResult GetAnswerById(int id)
{
try {
using (var context = new PollorDbContext()) {
AnswerModel? answer = context.Answers
.Where(p => p.Id.Equals(id))
.Where(p => p.id.Equals(id))
.Include(a => a.Votes)
.FirstOrDefault();
if (answer == null) {
return NotFound();
return NotFound(new { message = "No records found" });
}
return Ok(answer);
}
Expand All @@ -57,5 +59,26 @@ public IActionResult GetAnswerById(int id)
return StatusCode(500, new { message = ex.Message});
}
}

[HttpPost("answer")]
[Authorize]
public IActionResult AddAnswer(AnswerModel answer)
{
try {
using (var context = new PollorDbContext()) {
EntityEntry<AnswerModel> newAnswer = context.Answers.Add(answer);
context.SaveChanges();

if (newAnswer == null) {
return NotFound(new { message = "No records found" });
}
return Created("answer/" + newAnswer.Entity.id.ToString(), newAnswer.Entity);
}
}
catch (Exception ex) {
_logger.LogError(ex, ex.Message);
return StatusCode(500, new { message = ex.Message});
}
}
}
}
196 changes: 196 additions & 0 deletions pollor.Server/Controllers/AuthController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Identity;
using pollor.Server.Models;
using pollor.Server.Services;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System.Security.Principal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.AspNetCore.Authorization;
using pollor.Server.Controllers;
using System.Data;

[Route("api/auth")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly ILogger<AuthController> _logger;

public AuthController(ILogger<AuthController> logger)
{
_logger = logger;
}


[HttpPost("register")]
public IActionResult Register([FromBody] RegisterModel registerUser)
{
if (registerUser is null)
{
return BadRequest(new { message = "Invalid client request" });
}

if (registerUser.password!.Length < 8) {
return BadRequest(new { message = "Password must be longer than 8 characters." });
}

bool isUsernameAvailable = new PollorDbContext().UserAuthModel.Where(u => u.username!.ToLower().Equals(registerUser.username!.ToLower())).IsNullOrEmpty();
if (isUsernameAvailable == false) {
return BadRequest(new { message = "Username is already taken, please login or use another username." });
}

bool isEmailAvailable = new PollorDbContext().UserAuthModel.Where(u => u.emailaddress!.ToLower().Equals(registerUser.emailaddress!.ToLower())).IsNullOrEmpty();
if (isEmailAvailable == false) {
return BadRequest(new { message = "Emailaddress is already taken, please login or use another emailaddress." });
}

var hasher = new PasswordHasher<RegisterModel>();
var hashedPass = hasher.HashPassword(registerUser, registerUser.password!);
UserAuthModel tempUser = new UserAuthModel() {
username = registerUser.username,
password = hashedPass,
emailaddress = registerUser.emailaddress,
created_at = DateTime.Now,
};

try
{
using (var context = new PollorDbContext())
{
// Create new user
EntityEntry<UserAuthModel> createdUser = context.UserAuthModel.Add(tempUser);
context.SaveChanges();

// Get full user data
UserModel? newUser = context.Users
.Where(u => u.id.Equals(createdUser.Entity.id) &&
u.username!.Equals(createdUser.Entity.username) &&
u.emailaddress!.Equals(createdUser.Entity.emailaddress))
.FirstOrDefault();
if (newUser == null)
{
return NotFound(new { message = "User not found..." });
}
var tokeOptions = GetJwtTokenOptions(1, newUser);
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
return Created("user/" + newUser.id, new AuthenticatedResponse { token = tokenString, user = newUser });

}
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return StatusCode(500, new { message = ex.Message });
}
}


[HttpPost("login")]
public IActionResult Login([FromBody] LoginModel loginUser)
{
if (loginUser is null)
{
return BadRequest(new { message = "Invalid client request" });
}

var authUser = new PollorDbContext().UserAuthModel.Where(u => u.username!.ToLower().Equals(loginUser.username!.ToLower())).FirstOrDefault();
if (authUser == null) {
return Unauthorized(new { message = "Username or password is wrong!" });
}

var hasher = new PasswordHasher<LoginModel>();
PasswordVerificationResult passwordIsOk = hasher.VerifyHashedPassword(loginUser, authUser.password!, loginUser.password!);

if (passwordIsOk == PasswordVerificationResult.Failed) {
return Unauthorized(new { message = "Username or password is wrong!" });
}

if (authUser.username == loginUser.username && (passwordIsOk == PasswordVerificationResult.Success || passwordIsOk == PasswordVerificationResult.SuccessRehashNeeded))
{
if (passwordIsOk == PasswordVerificationResult.SuccessRehashNeeded) {
// rehash password and save to DB
_logger.LogError("Rehash password and save to DB");
}

int tokenLongerValid = (bool)loginUser.tokenLongerValid ? 31 : 1;// true = 31, false = 1
var currentUser = new PollorDbContext().Users.Where(u => u.username!.ToLower().Equals(authUser.username!.ToLower())).FirstOrDefault();
var tokenOptions = GetJwtTokenOptions(tokenLongerValid, currentUser!);
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokenOptions);

return Ok(new AuthenticatedResponse { token = tokenString, user = currentUser});
}

return Unauthorized(new { message = "something went wrong" } );
}

[HttpPost("validate")]
[Authorize]
public IActionResult Validate([FromBody] ValidateTokenModel validateTokenModel)
{
// Retrieve the JWT token from the Authorization header
var token = HttpContext.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();

if (string.IsNullOrEmpty(token))
{
return BadRequest(new { message = "Token is missing" });
}

SecurityToken validatedToken;
IPrincipal principal = new JwtSecurityTokenHandler().ValidateToken(token, AuthService.GetValidationParameters(), out validatedToken);

// The user is authenticated, and you can access user information
var userClaims = HttpContext.User;
// Perform additional validation or return success response

var username = userClaims.Claims.Where(e => e.Type.EndsWith("identity/claims/name")).Select(e => e.Value).SingleOrDefault();
var userId = userClaims.Claims.Where(e => e.Type.EndsWith("identity/claims/nameidentifier")).Select(e => e.Value).SingleOrDefault();
var userRole = userClaims.Claims.Where(e => e.Type.EndsWith("identity/claims/role")).Select(e => e.Value).SingleOrDefault();

try
{
using (var context = new PollorDbContext())
{
UserModel? user = context.Users
.Where(u => u.id.ToString().Equals(userId) &&
u.username!.Equals(username) &&
u.role!.Equals(userRole))
.FirstOrDefault();
if (user == null)
{
return NotFound(new { message = "User not found..." });
}
return Ok(new AuthenticatedResponse { token = validateTokenModel.token, user = user });
}
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return StatusCode(500, new { message = ex.Message });
}
}

private JwtSecurityToken GetJwtTokenOptions (int tokenValidForXDays, UserModel user) {
var jwtClaims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.username!),
new Claim(ClaimTypes.NameIdentifier, user.id.ToString()),
new Claim(ClaimTypes.Role, user.role!)
};

String jwtTokenDomain = Environment.GetEnvironmentVariable("JWT_TOKEN_DOMAIN")!.Split(',').FirstOrDefault()!;

var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("SECRET_JWT_KEY")!));
var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var tokeOptions = new JwtSecurityToken(
issuer: jwtTokenDomain,
audience: jwtTokenDomain,
claims: jwtClaims,
expires: DateTime.Now.AddDays(tokenValidForXDays),
signingCredentials: signinCredentials
);
return tokeOptions;
}
}
Loading

0 comments on commit a79fb7e

Please sign in to comment.