Initial ...
This commit is contained in:
@@ -0,0 +1,700 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using xCommons.Extensions;
|
||||
using xExceptions.Constants;
|
||||
using xIdentityModels;
|
||||
using xIdentityModels.Constants;
|
||||
using xIdentityModels.Models;
|
||||
using xIdentityModels.Navigations;
|
||||
|
||||
namespace xIds.Providers
|
||||
{
|
||||
public partial class XIdentityManager
|
||||
{
|
||||
//
|
||||
#region Registration Actions ...
|
||||
/// <summary>
|
||||
/// Invite a User to Register on Dashboard
|
||||
/// </summary>
|
||||
/// <param name="lang">specify destination language</param>
|
||||
/// <param name="device">an instance of <see>XDevice</see></param>
|
||||
/// <param name="email">which email address is going to invite</param>
|
||||
/// <param name="returnUrl">return url for invitation user to redirect</param>
|
||||
/// <returns>an instance of <see>XActionResponse</see></returns>
|
||||
public async Task<XActionResponse> InviteUser(
|
||||
string lang,
|
||||
XDevice device,
|
||||
string email,
|
||||
string returnUrl
|
||||
)
|
||||
{
|
||||
//
|
||||
// Validate Args ...
|
||||
await ValidationProvider
|
||||
.GroupValidationBuilder()
|
||||
.AddNotEmpty(
|
||||
lang,
|
||||
email,
|
||||
returnUrl
|
||||
)
|
||||
.AddNotNull(device)
|
||||
.AddEmailAddress(email)
|
||||
.ValidateGroupAsync();
|
||||
|
||||
//
|
||||
// Check Email is Unique ...
|
||||
await ValidateUserNotExists(email);
|
||||
|
||||
//
|
||||
// Get Request Result ...
|
||||
var result = await ActionRequest(
|
||||
lang,
|
||||
device,
|
||||
email,
|
||||
XAction.Invite,
|
||||
forceCheckContext: false,
|
||||
forceCheckUserExists: false);
|
||||
|
||||
//
|
||||
// Prepare Propper Message ...
|
||||
var xMessage = GetUserInviteMessage(lang, email, result.Token, returnUrl);
|
||||
|
||||
//
|
||||
// Send Message ...
|
||||
try
|
||||
{
|
||||
await MessageProvider.SendMailAsync(xMessage);
|
||||
}
|
||||
catch { }
|
||||
|
||||
//
|
||||
// Send Result ...
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recieve some Basic Informations and Start Registration Proccess
|
||||
/// if they Valid
|
||||
///
|
||||
/// Registration Proccess Starts with Invoking this Action
|
||||
/// </summary>
|
||||
/// <param name="lang">specify destination language</param>
|
||||
/// <param name="device">an instance of <see>XDevice</see></param>
|
||||
/// <param name="invitationHash">optional, if user invited, this is the invitation token</param>
|
||||
/// <param name="firstName">user's FirstName</param>
|
||||
/// <param name="lastName">user's LastName</param>
|
||||
/// <param name="dateOfBirth">user's dob date</param>
|
||||
/// <param name="mobileNumber">user's Mobile Number</param>
|
||||
/// <param name="emailAddress">user's Email address</param>
|
||||
/// <returns>an instance of <see>XActionResponse</see></returns>
|
||||
public async Task<XActionResponse> RequestRegistration(
|
||||
string lang,
|
||||
XDevice device,
|
||||
string invitationHash,
|
||||
string firstName,
|
||||
string lastName,
|
||||
DateTime dateOfBirth,
|
||||
string mobileNumber = null,
|
||||
string emailAddress = null
|
||||
)
|
||||
{
|
||||
//
|
||||
// Validate Args ...
|
||||
await ValidationProvider
|
||||
.GroupValidationBuilder()
|
||||
.AddNotEmpty(
|
||||
lang,
|
||||
firstName,
|
||||
lastName
|
||||
)
|
||||
.AddNotNull(
|
||||
device,
|
||||
dateOfBirth
|
||||
)
|
||||
.ValidateGroupAsync();
|
||||
|
||||
//
|
||||
// Validate Mobile Number if Exists ...
|
||||
if (!mobileNumber.IsNullOrEmpty())
|
||||
{
|
||||
//
|
||||
ValidationProvider.MobileNumber(mobileNumber);
|
||||
|
||||
//
|
||||
// Check Mobile is Uniqsue ...
|
||||
await ValidateUserNotExists(mobileNumber);
|
||||
}
|
||||
|
||||
//
|
||||
// Validate Email Address if Exists ...
|
||||
if (!emailAddress.IsNullOrEmpty())
|
||||
{
|
||||
//
|
||||
ValidationProvider.EmailAddress(emailAddress);
|
||||
|
||||
//
|
||||
// Check Email is Unique ...
|
||||
await ValidateUserNotExists(emailAddress);
|
||||
}
|
||||
|
||||
//
|
||||
// Get Related Token and Validate it and
|
||||
// Check Related Configurations ...
|
||||
await ValidateInvitationHash(invitationHash);
|
||||
|
||||
//
|
||||
// Check Device Validation ...
|
||||
await ValidateDeviceForActions(device);
|
||||
|
||||
//
|
||||
// Validate DateOfBirth ...
|
||||
ValidateDateOfBirth(dateOfBirth);
|
||||
|
||||
//
|
||||
// Define Empty Objects for Using ...
|
||||
var userSelectByParam = "";
|
||||
var context = new XActionRequestContext();
|
||||
//
|
||||
context.Lang = lang;
|
||||
context.Device = device;
|
||||
context.LastName = lastName;
|
||||
context.FirstName = firstName;
|
||||
context.DateOfBirth = dateOfBirth;
|
||||
context.MobileNumber = mobileNumber ?? "";
|
||||
context.Email = !emailAddress.IsNullOrEmpty() ? emailAddress.ToNormalString() : "";
|
||||
|
||||
//
|
||||
userSelectByParam = GetUserSelectByParam(context);
|
||||
|
||||
//
|
||||
// Token Data Parsing ...
|
||||
if (!invitationHash.IsNullOrEmpty())
|
||||
{
|
||||
//
|
||||
// Get Related Token, Validate it and Parse Contains
|
||||
// XAction Request and Validate the Request itself and
|
||||
// Return it ...
|
||||
var inviteRequest = await ValidateAndParseActionHash(invitationHash);
|
||||
|
||||
//
|
||||
// Extract User Select By Param and Type ...
|
||||
userSelectByParam = GetUserSelectByParam(inviteRequest);
|
||||
var userSelectBy = GetUserSelectByType(userSelectByParam);
|
||||
|
||||
//
|
||||
// Check Invitation Only done with Email Address ...
|
||||
if (userSelectBy != XUserSelectBy.Email)
|
||||
{
|
||||
XException.InvalidData.Throw();
|
||||
}
|
||||
|
||||
//
|
||||
// Check Invited Email and Given Email to be Same ...
|
||||
if (emailAddress.IsNullOrEmpty() ||
|
||||
(!emailAddress.IsNullOrEmpty() &&
|
||||
!emailAddress.IsValidEmail()))
|
||||
{
|
||||
XException.InvalidEmailAddress.Throw();
|
||||
}
|
||||
var isSameEmail = inviteRequest.Context.Email.ToNormalString() == emailAddress.ToNormalString();
|
||||
if (!isSameEmail)
|
||||
{
|
||||
XException.EmailsSame.Throw();
|
||||
}
|
||||
|
||||
//
|
||||
// Update Context Email for Request Action ...
|
||||
context.Email = userSelectByParam.ToNormalString();
|
||||
context.EmailVerified = true;
|
||||
}
|
||||
|
||||
//
|
||||
var result = await ActionRequest(
|
||||
lang,
|
||||
device,
|
||||
userSelectByParam,
|
||||
XAction.Registration,
|
||||
context,
|
||||
forceCheckUserExists: false
|
||||
);
|
||||
|
||||
//
|
||||
// Remove Invitation Token ...
|
||||
if (!invitationHash.IsNullOrEmpty())
|
||||
{
|
||||
await RemoveTokenByHash(invitationHash);
|
||||
}
|
||||
|
||||
//
|
||||
// Return Result ...
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add User Account Info
|
||||
/// </summary>
|
||||
/// <param name="lang">specify destination language</param>
|
||||
/// <param name="device">an instance of <see>XDevice</see></param>
|
||||
/// <param name="actionToken">a token which approved user action</param>
|
||||
/// <param name="userName">user name</param>
|
||||
/// <param name="password">assigne password</param>
|
||||
/// <returns>an instance of <see>XActionResponse</see></returns>
|
||||
public async Task<XActionResponse> AddAcountInfo(
|
||||
string lang,
|
||||
XDevice device,
|
||||
string actionToken,
|
||||
string userName,
|
||||
string password
|
||||
)
|
||||
{
|
||||
//
|
||||
// Validate Args ...
|
||||
await ValidationProvider
|
||||
.GroupValidationBuilder()
|
||||
.AddNotEmpty(
|
||||
lang,
|
||||
actionToken,
|
||||
userName,
|
||||
password)
|
||||
.AddNotNull(device)
|
||||
.ValidateGroupAsync();
|
||||
|
||||
//
|
||||
// Check Device Validation ...
|
||||
await ValidateDeviceForActions(device);
|
||||
|
||||
//
|
||||
// Validate a UserSelector is Available or not ...
|
||||
await ValidateCanRegister(userName);
|
||||
|
||||
//
|
||||
// Check UserName is Unique ...
|
||||
await ValidateUserNotExists(userName);
|
||||
|
||||
//
|
||||
// Get Related Token to Registration Hash and Validate it,
|
||||
// then Parse XActionRequest Instance from Token ...
|
||||
var request = await ValidateAndParseActionHash(actionToken);
|
||||
|
||||
//
|
||||
// Validate Requeired Data for Register User must be in Action Context ...
|
||||
await ValidationProvider
|
||||
.GroupValidationBuilder()
|
||||
.AddNotNull(
|
||||
request.Context,
|
||||
request.Context.Device,
|
||||
request.Context.DateOfBirth)
|
||||
.AddNotEmpty(
|
||||
request.Context.Email,
|
||||
request.Context.MobileNumber,
|
||||
request.Context.FirstName,
|
||||
request.Context.LastName)
|
||||
.AddEmailAddress(request.Context.Email)
|
||||
.AddMobileNumber(request.Context.MobileNumber)
|
||||
.ValidateGroupAsync();
|
||||
|
||||
//
|
||||
// Validate two Device Must Same ...
|
||||
ValidateRequestAndGivenDevices(device, request.Context.Device);
|
||||
|
||||
//
|
||||
// Extract UserSelectByParam from XActionRequest ...
|
||||
var userSelectByParam = GetUserSelectByParam(request);
|
||||
|
||||
//
|
||||
// Validate Request Pointer User doesn't Exists ...
|
||||
await ValidateUserNotExists(userSelectByParam);
|
||||
|
||||
//
|
||||
// Create an Empty Instance of XUser ...
|
||||
var xUser = new XUser();
|
||||
|
||||
//
|
||||
// Start Filling User Fields ...
|
||||
xUser.UserName = userName;
|
||||
xUser.Email = request.Context.Email;
|
||||
xUser.PhoneNumber = request.Context.MobileNumber;
|
||||
|
||||
//
|
||||
xUser.FirstName = request.Context.FirstName;
|
||||
xUser.LastName = request.Context.LastName;
|
||||
xUser.DateOfBirth = request.Context.DateOfBirth;
|
||||
|
||||
//
|
||||
xUser.EmailConfirmed = request.Context.EmailVerified;
|
||||
xUser.PhoneNumberConfirmed = request.Context.MobileVerified;
|
||||
|
||||
//
|
||||
xUser.CreationDate = DateTime.UtcNow;
|
||||
xUser.LastLogin = null;
|
||||
|
||||
//
|
||||
#region Handling New User IsEnable State ...
|
||||
//
|
||||
// By Default New Users will Enables ...
|
||||
xUser.IsEnable = true;
|
||||
xUser.IsBanned = false;
|
||||
|
||||
//
|
||||
// Check Auto Enable Users ...
|
||||
if (Configuration.RequireRegistrationConfirm)
|
||||
{
|
||||
xUser.IsEnable = false;
|
||||
}
|
||||
|
||||
//
|
||||
// Check Auto Confirm Email Users ...
|
||||
if (Configuration.AutoConfirmNewUsersEmail)
|
||||
{
|
||||
xUser.EmailConfirmed = true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
//
|
||||
// Check Auto Confirm Phone Number Users ...
|
||||
if (Configuration.AutoConfirmNewUsersPhoneNumber)
|
||||
{
|
||||
xUser.PhoneNumberConfirmed = true;
|
||||
}
|
||||
|
||||
//
|
||||
// Validating given Password ...
|
||||
await ValidatePasswordPolicies(xUser, password);
|
||||
|
||||
//
|
||||
// Now add new User to Database ...
|
||||
var createUserResult = await CreateUserAsync(xUser, password);
|
||||
if (!createUserResult.Succeeded)
|
||||
{
|
||||
XException.ActionFailed.Throw();
|
||||
}
|
||||
|
||||
//
|
||||
// Update Request UserId Value ...
|
||||
request.Context.UserId = xUser.Id;
|
||||
request.Context.UserName = xUser.UserName;
|
||||
|
||||
//
|
||||
#region Handling New User Role ...
|
||||
//
|
||||
// Now Try to Assign Default User role ...
|
||||
var isContainsDefaultUserRole = !Configuration.NewUsersRole.IsNullOrEmpty();
|
||||
if (isContainsDefaultUserRole)
|
||||
{
|
||||
//
|
||||
var newUserRoleName = Configuration.NewUsersRole;
|
||||
var isNewUserRoleExists = await IsRoleExistsAsync(newUserRoleName);
|
||||
//
|
||||
// Make Sure New User role Exists ...
|
||||
if (!isNewUserRoleExists)
|
||||
{
|
||||
//
|
||||
// Create Default Roles ...
|
||||
await CreateIdentityRoles();
|
||||
}
|
||||
|
||||
//
|
||||
// Assign User to Role ...
|
||||
var assignNewUserToRoleResult = await AddUserToRoleAsync(xUser, newUserRoleName);
|
||||
if (!assignNewUserToRoleResult.Succeeded)
|
||||
{
|
||||
//
|
||||
// Delete User if Role Assignment Faild ...
|
||||
await UserManager.DeleteAsync(xUser);
|
||||
|
||||
//
|
||||
// Thrown ActionFailed Error ...
|
||||
XException.ActionFailed.Throw();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
//
|
||||
#region Handle Profile Image ...
|
||||
// Save and Attach Profile Image to User if it's Added Before ...
|
||||
if (!request.Context.UserId.IsNullOrEmpty() &&
|
||||
!request.Context.Thubmnail.IsNull())
|
||||
{
|
||||
//
|
||||
xUser = await ValidateUserExistsAndRetrieve(
|
||||
userSelectByParam,
|
||||
containDetails: true,
|
||||
checkCanLoginPolicies: false,
|
||||
checkIsBanned: false,
|
||||
ignoreDisabledUser: true,
|
||||
exception: XException.NotFound.ToException()
|
||||
);
|
||||
|
||||
//
|
||||
// Handle Saving File and Attach it to User ...
|
||||
var saveResult = await StorageProvider
|
||||
.HandleProfileImageSave(request.Context.Thubmnail);
|
||||
|
||||
//
|
||||
var xProfileImage = new XProfileImage
|
||||
{
|
||||
UserId = request.Context.UserId,
|
||||
Name = saveResult.FileName,
|
||||
Path = saveResult.FilePath,
|
||||
Thumb = saveResult.Thmbnail,
|
||||
ThumbPath = saveResult.ThmbnailPath
|
||||
};
|
||||
|
||||
//
|
||||
xUser.Avatars.Add(xProfileImage);
|
||||
var updateResult = await UpdateUserAsync(
|
||||
xUser,
|
||||
checkCanLoginPolicies: false,
|
||||
checkIsBanned: false
|
||||
);
|
||||
|
||||
//
|
||||
if (updateResult.Succeeded)
|
||||
{
|
||||
//
|
||||
xUser.Avatar = xProfileImage.ThumbPath;
|
||||
await UpdateUserAsync(
|
||||
xUser,
|
||||
checkCanLoginPolicies: false,
|
||||
checkIsBanned: false
|
||||
);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
//
|
||||
// Handle Attaching User device ...
|
||||
var xUserDevice = await AddUserRelatedDevice(
|
||||
userSelectByParam,
|
||||
device,
|
||||
checkCanLoginPolicies: false);
|
||||
|
||||
//
|
||||
// Prepare New Token Result Resource,
|
||||
// by Requesting an Action ...
|
||||
var result = await ActionRequest(
|
||||
lang,
|
||||
device,
|
||||
userSelectByParam,
|
||||
XAction.Finish,
|
||||
request.Context,
|
||||
forceCheckUserExists: false
|
||||
);
|
||||
|
||||
//
|
||||
// Remove Previous Token ...
|
||||
await RemoveTokenByHash(actionToken);
|
||||
|
||||
//
|
||||
// Return Result ...
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attach Profile Image
|
||||
/// </summary>
|
||||
/// <param name="actionToken">a token which approved user action</param>
|
||||
/// <param name="file">an instance of <see>IFormFile</see> for user's Avatar</param>
|
||||
/// <returns>an instance of <see>XActionResponse</see></returns>
|
||||
public async Task<XActionResponse> AttachProfileImage(
|
||||
string actionToken,
|
||||
IFormFile file
|
||||
)
|
||||
{
|
||||
//
|
||||
// Validate Args ...
|
||||
await ValidationProvider
|
||||
.GroupValidationBuilder()
|
||||
.AddNotEmpty(actionToken)
|
||||
.AddNotNull(file)
|
||||
.ValidateGroupAsync();
|
||||
|
||||
//
|
||||
// Get Related Token to Registration Hash and Validate it,
|
||||
// then Parse XActionRequest Instance from Token ...
|
||||
var request = await ValidateAndParseActionHash(actionToken);
|
||||
|
||||
//
|
||||
// Extract UserSelectByParam from XActionRequest ...
|
||||
var userSelectByParam = GetUserSelectByParam(request);
|
||||
if (userSelectByParam.IsNullOrEmpty())
|
||||
{
|
||||
XException.InvalidData.Throw();
|
||||
}
|
||||
|
||||
//
|
||||
// Validate Given FormFile as Profile Image ...
|
||||
StorageProvider.ValidateProfileImage(file);
|
||||
|
||||
//
|
||||
request.Context.Thubmnail = file;
|
||||
|
||||
//
|
||||
// Save and Attach Profile Image to User if it's Added Before ...
|
||||
if (!request.Context.UserId.IsNullOrEmpty())
|
||||
{
|
||||
//
|
||||
var xUser = await ValidateUserExistsAndRetrieve(
|
||||
userSelectByParam,
|
||||
containDetails: true,
|
||||
checkCanLoginPolicies: false,
|
||||
checkIsBanned: false,
|
||||
ignoreDisabledUser: true,
|
||||
exception: XException.NotFound.ToException()
|
||||
);
|
||||
|
||||
//
|
||||
// Handle Saving File and Attach it to User ...
|
||||
var saveResult = await StorageProvider.HandleProfileImageSave(file);
|
||||
|
||||
//
|
||||
var xProfileImage = new XProfileImage
|
||||
{
|
||||
UserId = request.Context.UserId,
|
||||
Name = saveResult.FileName,
|
||||
Path = saveResult.FilePath,
|
||||
Thumb = saveResult.Thmbnail,
|
||||
ThumbPath = saveResult.ThmbnailPath
|
||||
};
|
||||
|
||||
//
|
||||
xUser.Avatars.Add(xProfileImage);
|
||||
var updateResult = await UpdateUserAsync(
|
||||
xUser,
|
||||
checkCanLoginPolicies: false,
|
||||
checkIsBanned: false
|
||||
);
|
||||
|
||||
//
|
||||
if (updateResult.Succeeded)
|
||||
{
|
||||
//
|
||||
xUser.Avatar = xProfileImage.ThumbPath;
|
||||
await UpdateUserAsync(
|
||||
xUser,
|
||||
checkCanLoginPolicies: false,
|
||||
checkIsBanned: false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Remove Invitation Token ...
|
||||
if (!actionToken.IsNullOrEmpty())
|
||||
{
|
||||
await RemoveTokenByHash(actionToken);
|
||||
}
|
||||
|
||||
//
|
||||
var result = await ActionRequest(
|
||||
request.Context.Lang,
|
||||
request.Context.Device,
|
||||
userSelectByParam,
|
||||
XAction.ProfileAction,
|
||||
request.Context,
|
||||
forceCheckUserExists: false
|
||||
);
|
||||
|
||||
//
|
||||
// Return Result ...
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finishing Registration
|
||||
/// </summary>
|
||||
/// <param name="lang">specify destination language</param>
|
||||
/// <param name="device">an instance of <see>XDevice</see></param>
|
||||
/// <param name="userSelectByParam">user identifier</param>
|
||||
/// <param name="password">assigne password</param>
|
||||
/// <param name="returnUrl">return url for invitation user to redirect</param>
|
||||
/// <returns></returns>
|
||||
public async Task FinishRegistration(
|
||||
string lang,
|
||||
XDevice device,
|
||||
string userSelectByParam,
|
||||
string password,
|
||||
string returnUrl
|
||||
)
|
||||
{
|
||||
//
|
||||
// Validate Args ...
|
||||
await ValidationProvider
|
||||
.GroupValidationBuilder()
|
||||
.AddNotEmpty(lang, userSelectByParam, password, returnUrl)
|
||||
.AddNotNull(device)
|
||||
.AddUrl(returnUrl)
|
||||
.ValidateGroupAsync();
|
||||
|
||||
//
|
||||
// Validate User Exists And Retrieve it ...
|
||||
var xUser = await ValidateUserExistsAndRetrieve(
|
||||
userSelectByParam,
|
||||
containDetails: false,
|
||||
checkCanLoginPolicies: false,
|
||||
checkIsBanned: false,
|
||||
ignoreDisabledUser: true,
|
||||
forceAdmin: false
|
||||
);
|
||||
|
||||
//
|
||||
#region Handling New User Email Notification Senario ...
|
||||
//
|
||||
// Here we Check if User is Enable and Approved Email
|
||||
// send RegisteredMsg and if User is not Enable and
|
||||
// Verify Registration value in Configuration is True
|
||||
// send Verify Registration mail to user and handle it ...
|
||||
if (Configuration.RequireRegistrationConfirm)
|
||||
{
|
||||
//
|
||||
if (xUser.IsEnable)
|
||||
{
|
||||
//
|
||||
// Send Registration Finished Message ...
|
||||
// since in this step we need to prevent errors from
|
||||
// going forward. add this try catch here ...
|
||||
try
|
||||
{
|
||||
//
|
||||
var xMessage = GetRegistrationFinishedMessage(lang, xUser.Email, returnUrl);
|
||||
|
||||
//
|
||||
await MessageProvider.SendMailAsync(xMessage);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// Send Registration Confirm Message ...
|
||||
// since in this step we need to prevent errors from
|
||||
// going forward. add this try catch here ...
|
||||
try
|
||||
{
|
||||
//
|
||||
var request = await ActionRequest(
|
||||
lang,
|
||||
device,
|
||||
userSelectByParam,
|
||||
XAction.Finish,
|
||||
forceCheckContext: false,
|
||||
forceCheckUserExists: true);
|
||||
|
||||
//
|
||||
var xMessage = GetRegistrationConfirmMessage(lang, xUser.Email, request.Token, returnUrl);
|
||||
|
||||
//
|
||||
await MessageProvider.SendMailAsync(xMessage);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
// //
|
||||
// await RemoveTokenByDevice (device);
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user