瀏覽代碼

implemented necessary endpoints for user invitation

pull/47/head
meris.ahmatovic 3 年之前
父節點
當前提交
1ab38cd4f6

+ 18
- 0
Diligent.WebAPI.Business/Helper/HTMLHelper.cs 查看文件

"</div>" + "</div>" +
"</div>"; "</div>";
} }
public static string RenderRegisterPage(string url)
{
return "<div style=\"font-family: sans-serif\">" +
"<div style=\"font-family: sans-serif;text-align: center;\">" +
"<h2 style=\"color: #017397;\">Welcome to HR Center</h2>" +
"<p style=\"font-size: 20px\">" +
"To register, please click on the button below." +
"</p>" +
"<a style = \"color: white;text-decoration:none;background-color: #017397;cursor: pointer;font-size: 20px;width: 220px;text-align: center;border-radius: 5px;padding: 5px 15px;height: 25px;\" " +
$"href=\"{url}\">" +
" Click here to register" +
"</a>" +
"<p style = \"font-size: 12px; margin-top: 25px;\" >" +
"Please do not reply to this email.This message was sent from a notification-only address that is not monitored." +
"</p>" +
"</div>" +
"</div>";
}
} }
} }

+ 54
- 0
Diligent.WebAPI.Business/Helper/StringGenerator.cs 查看文件

namespace Diligent.WebAPI.Business.Helper
{
public static class StringGenerator
{
public static string GenerateRandomPassword(PasswordOptions opts = null)
{
if (opts == null) opts = new PasswordOptions()
{
RequiredLength = 8,
RequiredUniqueChars = 4,
RequireDigit = true,
RequireLowercase = true,
RequireNonAlphanumeric = true,
RequireUppercase = true
};

string[] randomChars = new[] {
"ABCDEFGHJKLMNOPQRSTUVWXYZ", // uppercase
"abcdefghijkmnopqrstuvwxyz", // lowercase
"0123456789", // digits
"!@$?_-" // non-alphanumeric
};

Random rand = new(Environment.TickCount);
List<char> chars = new List<char>();

if (opts.RequireUppercase)
chars.Insert(rand.Next(0, chars.Count),
randomChars[0][rand.Next(0, randomChars[0].Length)]);

if (opts.RequireLowercase)
chars.Insert(rand.Next(0, chars.Count),
randomChars[1][rand.Next(0, randomChars[1].Length)]);

if (opts.RequireDigit)
chars.Insert(rand.Next(0, chars.Count),
randomChars[2][rand.Next(0, randomChars[2].Length)]);

if (opts.RequireNonAlphanumeric)
chars.Insert(rand.Next(0, chars.Count),
randomChars[3][rand.Next(0, randomChars[3].Length)]);

for (int i = chars.Count; i < opts.RequiredLength
|| chars.Distinct().Count() < opts.RequiredUniqueChars; i++)
{
string rcs = randomChars[rand.Next(0, randomChars.Length)];
chars.Insert(rand.Next(0, chars.Count),
rcs[rand.Next(0, rcs.Length)]);
}

return new string(chars.ToArray());
}
}
}

+ 4
- 0
Diligent.WebAPI.Business/MappingProfiles/UserMappingProfile.cs 查看文件



#region Model to DTO #region Model to DTO
CreateMap<User, UserResponseDTO>(); CreateMap<User, UserResponseDTO>();
CreateMap<User, UserDetailsResponseDTO>()
.ForMember(dest => dest.PhoneNumber, opt => opt.NullSubstitute("User has no phone number saved."))
.ForMember(dest => dest.Position, opt => opt.NullSubstitute("Position has not been declared yet."))
.ForMember(dest => dest.SocialMedias, opt => opt.NullSubstitute("User takes no part in any social media."));
#endregion #endregion
} }
} }

+ 6
- 4
Diligent.WebAPI.Business/Services/Interfaces/IUserService.cs 查看文件

namespace Diligent.WebAPI.Business.Services.Interfaces
using Diligent.WebAPI.Contracts.DTOs.User;

namespace Diligent.WebAPI.Business.Services.Interfaces
{ {
public interface IUserService public interface IUserService
{ {
Task<IEnumerable<User?>> GetAll(); Task<IEnumerable<User?>> GetAll();

Task<User?> GetById(int id); Task<User?> GetById(int id);
Task<User?> GetByEmail(string email);
Task CreateUser(CreateUserRequestDto model); Task CreateUser(CreateUserRequestDto model);

Task ToggleEnable(User user); Task ToggleEnable(User user);
Task RemoveUser(User user); Task RemoveUser(User user);
Task<bool> VerifyToken(User user, string token);
Task<ServiceResponseDTO<object>> SendRegistrationLink(InviteDTO invite);
} }
} }

+ 55
- 0
Diligent.WebAPI.Business/Services/UserService.cs 查看文件

using Diligent.WebAPI.Business.Services.Interfaces; using Diligent.WebAPI.Business.Services.Interfaces;
using Diligent.WebAPI.Business.Settings; using Diligent.WebAPI.Business.Settings;
using Diligent.WebAPI.Contracts.DTOs.User;
using Diligent.WebAPI.Data; using Diligent.WebAPI.Data;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Web;


namespace Diligent.WebAPI.Business.Services namespace Diligent.WebAPI.Business.Services
{ {


public async Task<User?> GetById(int id) => public async Task<User?> GetById(int id) =>
await _userManager.FindByIdAsync(id.ToString()); await _userManager.FindByIdAsync(id.ToString());
public async Task<User?> GetByEmail(string email) =>
await _userManager.FindByEmailAsync(email);


public async Task CreateUser(CreateUserRequestDto model) public async Task CreateUser(CreateUserRequestDto model)
{ {


await _databaseContext.SaveChangesAsync(); await _databaseContext.SaveChangesAsync();
} }

public async Task<ServiceResponseDTO<object>> SendRegistrationLink(InviteDTO invite)
{
// check if user exists
var check = await _userManager.FindByEmailAsync(invite.Email);
if (check != null)
return new ServiceResponseDTO<object>()
{
IsError = true,
ErrorMessage = "User already registered."
};

// create template user
// this user is disabled to log in until confirming invitation
var user = new User
{
UserName = invite.Email,
Email = invite.Email,
FirstName = invite.FirstName,
LastName = invite.LastName,
IsEnabled = false
};

await _userManager.CreateAsync(user, StringGenerator.GenerateRandomPassword());

// generate invitation token for user
// encoded for URLs
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
token = HttpUtility.UrlEncode(token);

// send link
await _emailer.SendEmailAndWriteToDbAsync(invite.Email, "Welcome", HTMLHelper.RenderRegisterPage($"{_frontEndSettings.BaseUrl}/register?token={token}&email={invite.Email}"), isHtml: true);

await _databaseContext.SaveChangesAsync();

return new ServiceResponseDTO<object>
{
Data = new { Message = "Link has been sent!" }
};
}

public async Task<bool> VerifyToken(User user, string token)
{
// this method is going to be updated
// curent new password value is static and only used for testing
// method is not complete and is currently only used to check if valid reset token is sent
var result = await _userManager.ResetPasswordAsync(user, token, "Nekasifra123!");
return result.Succeeded;
}

} }
} }

+ 15
- 0
Diligent.WebAPI.Contracts/DTOs/User/InviteDTO.cs 查看文件

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Diligent.WebAPI.Contracts.DTOs.User
{
public class InviteDTO
{
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
}

+ 14
- 0
Diligent.WebAPI.Contracts/DTOs/User/UserDetailsResponseDTO.cs 查看文件

namespace Diligent.WebAPI.Contracts.DTOs.User
{
public class UserDetailsResponseDTO
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public bool IsEnabled { get; set; }
public string PhoneNumber { get; set; }
public string Position { get; set; }
public string SocialMedias { get; set; }
}
}

+ 0
- 1
Diligent.WebAPI.Contracts/DTOs/User/UserResponseDTO.cs 查看文件

public string LastName { get; set; } public string LastName { get; set; }
public string Email { get; set; } public string Email { get; set; }
public bool IsEnabled { get; set; } public bool IsEnabled { get; set; }
//public string CVLink { get; set; }
//public string Position { get; set; } //public string Position { get; set; }
} }
} }

+ 1
- 1
Diligent.WebAPI.Data/Entities/User.cs 查看文件

public string LastName { get; set; } public string LastName { get; set; }
public string? PasswordResetToken { get; set; } public string? PasswordResetToken { get; set; }
public List<Comment> Comments { get; set; } public List<Comment> Comments { get; set; }
public bool IsEnabled { get; set; }
public bool? IsEnabled { get; set; }
} }

+ 1003
- 0
Diligent.WebAPI.Data/Migrations/20221116181306_DefaultDisabledUser.Designer.cs
文件差異過大導致無法顯示
查看文件


+ 36
- 0
Diligent.WebAPI.Data/Migrations/20221116181306_DefaultDisabledUser.cs 查看文件

using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace Diligent.WebAPI.Data.Migrations
{
public partial class DefaultDisabledUser : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<bool>(
name: "IsEnabled",
table: "AspNetUsers",
type: "bit",
nullable: true,
defaultValue: true,
oldClrType: typeof(bool),
oldType: "bit",
oldDefaultValue: true);
}

protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<bool>(
name: "IsEnabled",
table: "AspNetUsers",
type: "bit",
nullable: false,
defaultValue: true,
oldClrType: typeof(bool),
oldType: "bit",
oldNullable: true,
oldDefaultValue: true);
}
}
}

+ 1
- 1
Diligent.WebAPI.Data/Migrations/DatabaseContextModelSnapshot.cs 查看文件

.IsRequired() .IsRequired()
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(max)");


b.Property<bool>("IsEnabled")
b.Property<bool?>("IsEnabled")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bit") .HasColumnType("bit")
.HasDefaultValue(true); .HasDefaultValue(true);

+ 38
- 2
Diligent.WebAPI.Host/Controllers/V1/UsersController.cs 查看文件

return Ok(_mapper.Map<IEnumerable<User?>, IEnumerable<UserResponseDTO>>(await _userService.GetAll())); return Ok(_mapper.Map<IEnumerable<User?>, IEnumerable<UserResponseDTO>>(await _userService.GetAll()));
} }


//[Authorize]
[Authorize] [Authorize]
[HttpPost("toggleEnable/{id}")] [HttpPost("toggleEnable/{id}")]
public async Task<IActionResult> ToggleEnable(int id) public async Task<IActionResult> ToggleEnable(int id)
return Ok(user.Id); return Ok(user.Id);
} }


//[Authorize]
[Authorize]
[HttpGet("{id}")]
public async Task<IActionResult> GetUser(int id)
{
var user = await _userService.GetById(id);

if (user == null)
{
return BadRequest("User not found");
}

return Ok(_mapper.Map<User, UserDetailsResponseDTO>(user));
}

[Authorize]
[HttpPost("invite")]
public async Task<IActionResult> InviteUser([FromBody] InviteDTO invite)
{
var response = await _userService.SendRegistrationLink(invite);

if (response.IsError is true)
return BadRequest(new { message = response.ErrorMessage });

return Ok(response.Data);
}

[HttpPost("verify-invite")]
public async Task<IActionResult> VerifyInvite(string email, string token)
{
// controller endpoint currently used only for testing
// user should be enabled to log in after accepting invite and updating his account
var user = await _userService.GetByEmail(email);

var result = await _userService.VerifyToken(user, token);

return Ok(result);
}

//[Authorize] //[Authorize]
[HttpPost] [HttpPost]
public async Task<IActionResult> CreateUser([FromBody] CreateUserRequestDto model) public async Task<IActionResult> CreateUser([FromBody] CreateUserRequestDto model)

+ 2
- 2
Diligent.WebAPI.Host/appsettings.Development.json 查看文件

"SmtpServer": "smtp.mailtrap.io", "SmtpServer": "smtp.mailtrap.io",
"SmtpPort": 2525, "SmtpPort": 2525,
"SmtpUseSSL": true, "SmtpUseSSL": true,
"SmtpUsername": "460e3c49f02e37",
"SmtpPassword": "66443869eaad55",
"SmtpUsername": "179be7a6fd2f50",
"SmtpPassword": "63cde15de0d5d7",
"SmtpFrom": "noreply@hrcenter.net", "SmtpFrom": "noreply@hrcenter.net",
"SmtpFromName": "HRCenter Team" "SmtpFromName": "HRCenter Team"
}, },

+ 2
- 2
Diligent.WebAPI.Host/appsettings.json 查看文件

"SmtpServer": "smtp.mailtrap.io", "SmtpServer": "smtp.mailtrap.io",
"SmtpPort": 2525, "SmtpPort": 2525,
"SmtpUseSSL": true, "SmtpUseSSL": true,
"SmtpUsername": "460e3c49f02e37",
"SmtpPassword": "66443869eaad55",
"SmtpUsername": "179be7a6fd2f50",
"SmtpPassword": "63cde15de0d5d7",
"SmtpFrom": "noreply@hrcenter.net", "SmtpFrom": "noreply@hrcenter.net",
"SmtpFromName": "HRCenter Team" "SmtpFromName": "HRCenter Team"
}, },

Loading…
取消
儲存