commit 844b4c47ada5047b44afc12fb56901e75e245328 Author: Hadi Khazaee Asl Date: Thu Jan 25 04:42:47 2024 +0330 Initial Commit ... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1bd9d0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# +# DotNet ... +bin +obj + +# +# Natural Docs ... +Documentation/* diff --git a/Configurations/XIdentityConfiguration.cs b/Configurations/XIdentityConfiguration.cs new file mode 100644 index 0000000..d8c83f1 --- /dev/null +++ b/Configurations/XIdentityConfiguration.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using xIdentityModels.Dtos; + +namespace xIdentityModels.Configurations { + public class XIdentityConfiguration { + // + // Define All Policies which can Apply to Identity Framework + // and also create a Default instance if it's not Exists + // in AppSetting JSON file ... + public XIdentityPolicy Policy { get; set; } + + /// + /// the Security Key which used to Signing JWT Tokens + /// in Authentication Provider + /// + /// + public string IdentitySecretKey { get; set; } + + /// + /// the Issuer and Creator of Token + /// + /// + public string IdentityIssuer { get; set; } + + /// + /// the Audience and Consumer of Token + /// + /// + public string IdentityAudience { get; set; } + + /// + /// the List of Users Roles which must Provides in Identity + /// + /// + public HashSet IdentityRoles { get; set; } + + /// + /// determines All new users must Assign to which role in Registration + /// + /// + public string NewUsersRole { get; set; } + + /// + /// determines New Registered Users Confirm Email or not + /// + /// + public bool AutoConfirmNewUsersEmail { get; set; } + + /// + /// determines New Registered Users Confirm PhoneNumber or not + /// + /// + public bool AutoConfirmNewUsersPhoneNumber { get; set; } + + /// + /// determines the Registration only Can be done with Invitation or not + /// + /// + public bool RegistrationJustWithInvite { get; set; } + + /// + /// determines the Registration required confirmation with email or not + /// + /// + public bool RequireRegistrationConfirm { get; set; } + + // + public int MaxNumberOfVerificationCodeSend { get; set; } + + // + public int DeleyBetweenTwoVerificationCode { get; set; } + + public int BannedDeviceTimeout { get; set; } + + /// + /// an string Which Provide how long a action token can be valid + /// + /// + public string ActionTokenExpirationDateProvider { get; set; } + + public HashSet IdentityMessages { get; set; } + } +} \ No newline at end of file diff --git a/Configurations/XIdentityPolicy.cs b/Configurations/XIdentityPolicy.cs new file mode 100644 index 0000000..31425f2 --- /dev/null +++ b/Configurations/XIdentityPolicy.cs @@ -0,0 +1,37 @@ +namespace xIdentityModels.Configurations { + /// + /// Provide the Available Policies with their Default Values + /// + public class XIdentityPolicy { + /// + /// User Account Lock Policies + /// + /// + public XIdentityPolicyLockout Lockout { get; set; } + + /// + /// User Password Policies + /// + /// + public XIdentityPolicyPassword Password { get; set; } + + /// + /// the Policies for User for Allowing to SignIn + /// + /// + public XIdentityPolicySignIn SignIn { get; set; } + + /// + /// the Policies for User + /// + /// + public XIdentityPolicyUser User { get; set; } + + /// + /// the policies which determines how to + /// populate user profile + /// + /// + public XIdentityProfile Profile { get; set; } + } +} \ No newline at end of file diff --git a/Configurations/XIdentityPolicyLockout.cs b/Configurations/XIdentityPolicyLockout.cs new file mode 100644 index 0000000..5482409 --- /dev/null +++ b/Configurations/XIdentityPolicyLockout.cs @@ -0,0 +1,27 @@ +namespace xIdentityModels.Configurations { + /// + /// User Account Lock Policies + /// + public class XIdentityPolicyLockout { + /// + /// The String which Provide Time Span Delais + /// between Lockout and UnLock a User Account + /// this Provided String the Parsed before Use + /// + /// + public string LockoutTimeSpanProvider { get; set; } + + /// + /// the Number of InvalidLoginAttemps which can + /// Happens to Lockout User Account + /// + /// + public byte MaxFailedAccessAttempts { get; set; } + + /// + /// determines Lockout mechanism allowed for new Users or not + /// + /// + public bool AllowedForNewUsers { get; set; } + } +} \ No newline at end of file diff --git a/Configurations/XIdentityPolicyPassword.cs b/Configurations/XIdentityPolicyPassword.cs new file mode 100644 index 0000000..b5ed15f --- /dev/null +++ b/Configurations/XIdentityPolicyPassword.cs @@ -0,0 +1,42 @@ +namespace xIdentityModels.Configurations { + /// + /// User Password Policies + /// + public class XIdentityPolicyPassword { + /// + /// determines Password must contains digits or not + /// + /// + public bool RequireDigit { get; set; } + + /// + /// determines the minimum allowed length for a password + /// + /// + public byte RequiredLength { get; set; } + + /// + /// determines the number of Unique Chars must be included in a Password + /// + /// + public byte RequiredUniqueChars { get; set; } + + /// + /// determines Password must contains LowerCase chars or not + /// + /// + public bool RequireLowercase { get; set; } + + /// + /// determines Password must contains Non-Alphabetical chars or not + /// + /// + public bool RequireNonAlphanumeric { get; set; } + + /// + /// determines Password must contains UpperCase chars or not + /// + /// + public bool RequireUppercase { get; set; } + } +} \ No newline at end of file diff --git a/Configurations/XIdentityPolicySignIn.cs b/Configurations/XIdentityPolicySignIn.cs new file mode 100644 index 0000000..e9bc73c --- /dev/null +++ b/Configurations/XIdentityPolicySignIn.cs @@ -0,0 +1,26 @@ +namespace xIdentityModels.Configurations { + /// + /// the Policies for User for Allowing to SignIn + /// + public class XIdentityPolicySignIn { + /// + /// determines only enabled users can SignIn + /// + /// + public bool RequiredEnabled { get; set; } + + /// + /// determines User must Confirm the given Email Address + /// before Login or not + /// + /// + public bool RequireConfirmedEmail { get; set; } + + /// + /// determines User must Confirm the given Phone Number + /// before Login or not + /// + /// + public bool RequireConfirmedPhoneNumber { get; set; } + } +} \ No newline at end of file diff --git a/Configurations/XIdentityPolicyUser.cs b/Configurations/XIdentityPolicyUser.cs new file mode 100644 index 0000000..93e98fc --- /dev/null +++ b/Configurations/XIdentityPolicyUser.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; + +namespace xIdentityModels.Configurations { + /// + /// the Policies for User + /// + public class XIdentityPolicyUser { + /// + /// determines Allowed characters which can used in UserName + /// + /// + public string AllowedUserNameCharacters { get; set; } + + /// + /// determines the Email Address of each User must be Unique or not + /// + /// + public bool RequireUniqueEmail { get; set; } + + /// + /// minimum length of User Name + /// + /// + public byte MinLength { get; set; } + + /// + /// maximum length of user name + /// + /// + public byte MaxLength { get; set; } + + /// + /// Minimum Age for Registration + /// + /// + public int MinAgeForRegistration { get; set; } + + /// + /// Maximum age for Registration + /// + /// + public int MaxAgeForRegistration { get; set; } + + /// + /// a collection of invalid usernames + /// + /// + public ICollection InvalidUserNames { get; set; } = new HashSet (); + } +} \ No newline at end of file diff --git a/Configurations/XIdentityProfile.cs b/Configurations/XIdentityProfile.cs new file mode 100644 index 0000000..347ff64 --- /dev/null +++ b/Configurations/XIdentityProfile.cs @@ -0,0 +1,30 @@ +namespace xIdentityModels.Configurations { + /// + /// contains user profile configuration + /// + public class XIdentityProfile { + /// + /// determines user profile contain email or not + /// + /// + public bool ContainsEmail { get; set; } + + /// + /// determines user profile contains phone number or not + /// + /// + public bool ContainsPhoneNumber { get; set; } + + /// + /// determines user profile contains Roles collection or not + /// + /// + public bool ContainsRoles { get; set; } + + public bool ContainsDateOfBirth { get; set; } + + public bool ContainsLastLogin { get; set; } + + public bool ContainsCreationDate { get; set; } + } +} \ No newline at end of file diff --git a/Constants/XAction.cs b/Constants/XAction.cs new file mode 100644 index 0000000..d5bc302 --- /dev/null +++ b/Constants/XAction.cs @@ -0,0 +1,16 @@ +namespace xIdentityModels.Constants { + public enum XAction { + Authentication, + Invite, + Registration, + RequestMobileVerificationCode, + ConfirmMobileNumber, + RequestEmailVerificationCode, + ConfirmEmailAddress, + AddAccountInfo, + AttachProfileImage, + RequestResetPassword, + ProfileAction, + Finish, + } +} \ No newline at end of file diff --git a/Constants/XFriendshipState.cs b/Constants/XFriendshipState.cs new file mode 100644 index 0000000..0a315fd --- /dev/null +++ b/Constants/XFriendshipState.cs @@ -0,0 +1,12 @@ +namespace xIdentityModels.Constants { + /// + /// Represent State of Friendship between to Users + /// + public enum XFriendshipState { + None, + Pending, + Accepted, + Rejected, + Blocked, + } +} \ No newline at end of file diff --git a/Constants/XGender.cs b/Constants/XGender.cs new file mode 100644 index 0000000..cb704aa --- /dev/null +++ b/Constants/XGender.cs @@ -0,0 +1,9 @@ +namespace xIdentityModels.Constants { + /// + /// Determines Available Genders + /// + public enum XGender { + Male, + Female + } +} \ No newline at end of file diff --git a/Constants/XUserSelectBy.cs b/Constants/XUserSelectBy.cs new file mode 100644 index 0000000..bf6c2ef --- /dev/null +++ b/Constants/XUserSelectBy.cs @@ -0,0 +1,9 @@ +namespace xIdentityModels.Constants { + public enum XUserSelectBy { + NotSpecified, + ID, + Username, + Email, + MobileNumber + } +} \ No newline at end of file diff --git a/Constants/xIdentityModelsConstants.cs b/Constants/xIdentityModelsConstants.cs new file mode 100644 index 0000000..2be39ff --- /dev/null +++ b/Constants/xIdentityModelsConstants.cs @@ -0,0 +1,3 @@ +namespace xIdentityModels.Constants { + public class xIdentityModelsConstants { } +} \ No newline at end of file diff --git a/DI/XDIHelperExtension.cs b/DI/XDIHelperExtension.cs new file mode 100644 index 0000000..dca1af3 --- /dev/null +++ b/DI/XDIHelperExtension.cs @@ -0,0 +1,3 @@ +namespace xIdentityModels.DI { + public static class XDIHelperExtension { } +} \ No newline at end of file diff --git a/Descriptors/XIdentityUserDescriptor.cs b/Descriptors/XIdentityUserDescriptor.cs new file mode 100644 index 0000000..2fd4b57 --- /dev/null +++ b/Descriptors/XIdentityUserDescriptor.cs @@ -0,0 +1,20 @@ +namespace xIdentityModels.Descriptors { + /// + /// contains default users information + /// + public class XIdentityUserDescriptor { + public string Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string UserName { get; set; } + public string Email { get; set; } + public bool EmailConfirmed { get; set; } + public string PhoneNumber { get; set; } + public bool PhoneNumberConfirmed { get; set; } + public string Password { get; set; } + public string Role { get; set; } + public bool IsEnable { get; set; } + public bool IsBanned { get; set; } + public string DateOfBirth { get; set; } + } +} \ No newline at end of file diff --git a/Dtos/XDiscoveryDocumentDto.cs b/Dtos/XDiscoveryDocumentDto.cs new file mode 100644 index 0000000..f51e542 --- /dev/null +++ b/Dtos/XDiscoveryDocumentDto.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace xIdentityModels.Dtos +{ + public class XDiscoveryDocumentDto + { + [JsonProperty ("issuer")] + public string Issuer { get; set; } + + [JsonProperty ("jwks_uri")] + public string JwksUri { get; set; } + + [JsonProperty ("authorization_endpoint")] + public string AuthorizationEndpoint { get; set; } + + [JsonProperty ("token_endpoint")] + public string TokenEndpoint { get; set; } + + [JsonProperty ("userinfo_endpoint")] + public string UserinfoEndpoint { get; set; } + + [JsonProperty ("end_session_endpoint")] + public string EndSessionEndpoint { get; set; } + + [JsonProperty ("check_session_iframe")] + public string CheckSessionIframe { get; set; } + + [JsonProperty ("revocation_endpoint")] + public string RevocationEndpoint { get; set; } + + [JsonProperty ("introspection_endpoint")] + public string IntrospectionEndpoint { get; set; } + + [JsonProperty ("device_authorization_endpoint")] + public string DeviceAuthorizationEndpoint { get; set; } + + [JsonProperty ("frontchannel_logout_supported")] + public bool FrontchannelLogoutSupported { get; set; } + + [JsonProperty ("frontchannel_logout_session_supported")] + public bool FrontchannelLogoutSessionSupported { get; set; } + + [JsonProperty ("backchannel_logout_supported")] + public bool BackchannelLogoutSupported { get; set; } + + [JsonProperty ("backchannel_logout_session_supported")] + public bool BackchannelLogoutSessionSupported { get; set; } + + [JsonProperty ("scopes_supported")] + public List ScopesSupported { get; set; } + + [JsonProperty ("claims_supported")] + public List ClaimsSupported { get; set; } + + [JsonProperty ("grant_types_supported")] + public List GrantTypesSupported { get; set; } + + [JsonProperty ("response_types_supported")] + public List ResponseTypesSupported { get; set; } + + [JsonProperty ("response_modes_supported")] + public List ResponseModesSupported { get; set; } + + [JsonProperty ("token_endpoint_auth_methods_supported")] + public List TokenEndpointAuthMethodsSupported { get; set; } + + [JsonProperty ("id_token_signing_alg_values_supported")] + public List IdTokenSigningAlgValuesSupported { get; set; } + + [JsonProperty ("subject_types_supported")] + public List SubjectTypesSupported { get; set; } + + [JsonProperty ("code_challenge_methods_supported")] + public List CodeChallengeMethodsSupported { get; set; } + + [JsonProperty ("request_parameter_supported")] + public bool RequestParameterSupported { get; set; } + + [JsonProperty ("account_api")] + public string AccountApi { get; set; } + } +} \ No newline at end of file diff --git a/Dtos/XFriendshipInfoDto.cs b/Dtos/XFriendshipInfoDto.cs new file mode 100644 index 0000000..ff164c2 --- /dev/null +++ b/Dtos/XFriendshipInfoDto.cs @@ -0,0 +1,14 @@ +using xModels.Base; +using xIdentityModels.Constants; + +namespace xIdentityModels.Dtos { + public class XFriendshipInfoDto : XBaseDto { + public string UserId { get; set; } + public bool IsFollower { get; set; } + public bool IsFollowing { get; set; } + public XFriendshipState FollowerState { get; set; } + public XFriendshipState FollowingState { get; set; } + public long Followers { get; set; } + public long Followings { get; set; } + } +} \ No newline at end of file diff --git a/Dtos/XIdentityMessageDto.cs b/Dtos/XIdentityMessageDto.cs new file mode 100644 index 0000000..4af6dbd --- /dev/null +++ b/Dtos/XIdentityMessageDto.cs @@ -0,0 +1,9 @@ +using xModels.Base; + +namespace xIdentityModels.Dtos { + public class XIdentityMessageDto : XBaseDto { + public string Language { get; set; } + public string ResourceTitle { get; set; } + public string TranslatedValue { get; set; } + } +} \ No newline at end of file diff --git a/Dtos/XProfileImageDto.cs b/Dtos/XProfileImageDto.cs new file mode 100644 index 0000000..0c04a2d --- /dev/null +++ b/Dtos/XProfileImageDto.cs @@ -0,0 +1,39 @@ +using System; +using Newtonsoft.Json; +using xModels.Base; + +namespace xIdentityModels.Dtos { + public class XProfileImageDto : XBaseDto { + public int Id { get; set; } + + [JsonProperty ( + "name", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string Name { get; set; } + + [JsonProperty ( + "thumb", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string Thumb { get; set; } + + [JsonProperty ( + "path", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string Path { get; set; } + + [JsonProperty ( + "thumbPath", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string ThumbPath { get; set; } + + [JsonProperty ( + "creationDate", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public DateTime CreationDate { get; set; } + } +} \ No newline at end of file diff --git a/Dtos/XUserNameIdRequest.cs b/Dtos/XUserNameIdRequest.cs new file mode 100644 index 0000000..961bc00 --- /dev/null +++ b/Dtos/XUserNameIdRequest.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; +using xModels.Base; + +namespace xIdentityModels.Dtos { + public class XUserNameIdRequest : XBaseDto { + public IEnumerable Ids { get; set; } + } +} \ No newline at end of file diff --git a/Dtos/XUserNameIdResponse.cs b/Dtos/XUserNameIdResponse.cs new file mode 100644 index 0000000..a61513a --- /dev/null +++ b/Dtos/XUserNameIdResponse.cs @@ -0,0 +1,8 @@ +using xModels.Base; + +namespace xIdentityModels.Dtos { + public class XUserNameIdResponse : XBaseDto { + public string Id { get; set; } + public string UserName { get; set; } + } +} \ No newline at end of file diff --git a/Dtos/XUserProfileDto.cs b/Dtos/XUserProfileDto.cs new file mode 100644 index 0000000..cf304ac --- /dev/null +++ b/Dtos/XUserProfileDto.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using xIdentityModels.Constants; +using xModels.Base; + +namespace xIdentityModels.Dtos { + public class XUserProfileDto : XBaseDto { + [JsonProperty ( + "userId", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string UserId { get; set; } + + [JsonProperty ( + "userName", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string UserName { get; set; } + + [JsonProperty ( + "email", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string Email { get; set; } + + [JsonProperty ( + "emailConfirmed", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public bool EmailConfirmed { get; set; } + + [JsonProperty ( + "phoneNumber", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string PhoneNumber { get; set; } + + [JsonProperty ( + "phoneNumberConfirmed", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public bool PhoneNumberConfirmed { get; set; } + + [JsonProperty ( + "firstName", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string FirstName { get; set; } + + [JsonProperty ( + "lastName", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string LastName { get; set; } + + [JsonProperty ( + "dateOfBirth", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public DateTime? DateOfBirth { get; set; } + + [JsonProperty ( + "creationDate", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public DateTime? CreationDate { get; set; } + + [JsonProperty ( + "lastLogin", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public DateTime? LastLogin { get; set; } + + [JsonProperty ( + "avatar", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string Avatar { get; set; } + + [JsonProperty ( + "gender", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public XGender Gender { get; set; } + + [JsonProperty ( + "friendshipInfo", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public XFriendshipInfoDto FriendshipInfo { get; set; } + + [JsonProperty ( + "roles", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public ICollection Roles { get; set; } = new HashSet (); + + [JsonProperty ( + "avatars", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public ICollection Avatars { get; set; } = new HashSet (); + + [JsonProperty ( + "isEnable", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public bool IsEnable { get; set; } + + [JsonProperty ( + "isBanned", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public bool IsBanned { get; set; } + } +} \ No newline at end of file diff --git a/Extensions/DIExtensions.cs b/Extensions/DIExtensions.cs new file mode 100644 index 0000000..b7a185e --- /dev/null +++ b/Extensions/DIExtensions.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.DependencyInjection; +using xCommons.Extensions; +using xIdentityModels.Interfaces; +using xIdentityModels.Providers; + +namespace xIdentityModels.Extensions { + public static class DIExtensions { + /// + /// since registering IXUserRoleHelper need to add default instance if + /// user not provided we create this helper ... + /// + /// + public static void AddXUserRoleHandler ( + this IServiceCollection services, + IXUserRoleHelper helper = null + ) { + // + // Check if Provider is null ... + if (helper.IsNull ()) { + helper = services.GetRegisteredService (); + } + + // + // check if Provider registered or not ... + if (helper.IsNull ()) { + // + // Create Default provider ... + services.AddSingleton (new XDefaultUserRoleHelper ()); + } else { + // + // Register Given Helper ... + services.AddSingleton (helper); + } + } + } +} \ No newline at end of file diff --git a/Extensions/XIdentityExtensions.cs b/Extensions/XIdentityExtensions.cs new file mode 100644 index 0000000..abcaf9c --- /dev/null +++ b/Extensions/XIdentityExtensions.cs @@ -0,0 +1,105 @@ +using System.Collections.Generic; +using xCommons.Extensions; +using xExceptions.Constants; +using xIdentityModels.Constants; +using xIdentityModels.Models; + +namespace xIdentityModels.Extensions { + public static class XIdentityExtensions { + public static XUserSelectBy GetUserSelectByType ( + this string source, + bool forceUserSelectByParamNotEmpty = true, + bool forceUserSelectByTypeMustSpecified = false + ) { + // + // Validate Args ... + if (source.IsNullOrEmpty () && + forceUserSelectByParamNotEmpty) { + XException.InvalidArgs.Throw (); + } + + // + var result = XUserSelectBy.NotSpecified; + var userSelectByParam = source.ToNormalString (); + if (userSelectByParam.IsValidEmail ()) { + // + // Find User by Email ... + result = XUserSelectBy.Email; + } else if (userSelectByParam.IsValidMobileNumber ()) { + // + // Find User by Mobile Number ... + result = XUserSelectBy.MobileNumber; + } else if (userSelectByParam.IsGuid ()) { + // + // Find User By it's ID ... + result = XUserSelectBy.ID; + } else { + // + // UserName ... + result = XUserSelectBy.Username; + } + + // + // Validation Result ... + if (forceUserSelectByTypeMustSpecified && + result == XUserSelectBy.NotSpecified) { + XException.InvalidData.Throw (); + } + + // + return result; + } + + public static string GetUserSelectByParam ( + this XActionRequest source, + bool forceNotNull = true, + ICollection excludes = null + ) { + // + // Validate Args ... + if (source.IsNull ()) { + XException.InvalidArgs.Throw (); + } + + // + var id = source.UserId; + var userName = source.UserName; + var email = source.Email; + var phoneNumber = source.MobileNumber; + + // + if (excludes != null) { + // + if (excludes.Contains (XUserSelectBy.ID)) { + id = null; + } + if (excludes.Contains (XUserSelectBy.Email)) { + email = null; + } + if (excludes.Contains (XUserSelectBy.MobileNumber)) { + phoneNumber = null; + } + if (excludes.Contains (XUserSelectBy.Username)) { + userName = null; + } + } + + // + var selectByParam = + id.IsNullOrEmpty () ? + userName.IsNullOrEmpty () ? + email.IsNullOrEmpty () ? + phoneNumber.IsNullOrEmpty () ? null : phoneNumber : email : userName : id; + + // + // Check result ... + if (forceNotNull && + selectByParam.IsNullOrEmpty ()) { + XException.InvalidData.Throw (); + } + + // + return selectByParam; + } + } +} \ No newline at end of file diff --git a/Extensions/XModelExtensions.cs b/Extensions/XModelExtensions.cs new file mode 100644 index 0000000..6ae1704 --- /dev/null +++ b/Extensions/XModelExtensions.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using xCommons.Extensions; +using xIdentityModels.Descriptors; +using xIdentityModels.Models; +using xIdentityModels.Navigations; + +namespace xIdentityModels.Extensions { + public static class XModelExtensions { + /// + /// /// Convert XIdentityUserDescriptor instance to XUser + /// + /// + /// + public static XUser ToXUser (this XIdentityUserDescriptor source) { + return new XUser { + Id = source.Id, + FirstName = source.FirstName, + LastName = source.LastName, + UserName = source.UserName, + Email = source.Email, + EmailConfirmed = source.EmailConfirmed, + PhoneNumber = source.PhoneNumber, + PhoneNumberConfirmed = source.PhoneNumberConfirmed, + IsEnable = true, + IsBanned = false, + CreationDate = DateTime.UtcNow, + DateOfBirth = DateTime.Parse (source.DateOfBirth) + }; + } + + /// + /// Check a User is Same as Descriptor User + /// + /// + /// + /// + public static bool IsSameAs (this XUser source, XIdentityUserDescriptor dest) { + // + if (source.IsNull () || + dest.IsNull ()) { + return false; + } + + // + var isSame = source.IsSameContent (dest, propertyWhiteList : new [] { + nameof (XIdentityUserDescriptor.FirstName), + nameof (XIdentityUserDescriptor.LastName), + nameof (XIdentityUserDescriptor.UserName), + nameof (XIdentityUserDescriptor.Email), + nameof (XIdentityUserDescriptor.EmailConfirmed), + nameof (XIdentityUserDescriptor.PhoneNumber), + nameof (XIdentityUserDescriptor.PhoneNumberConfirmed), + nameof (XIdentityUserDescriptor.IsEnable), + nameof (XIdentityUserDescriptor.IsBanned), + nameof (XIdentityUserDescriptor.DateOfBirth) + }, + propertyValueCheckers : new [] { + new KeyValuePair> ( + nameof (XIdentityUserDescriptor.DateOfBirth), + (user, descriptorUser) => { + // + var descriptorUserDateOfBirth = DateTime.Parse (descriptorUser.DateOfBirth); + return DateTime.Equals (user.DateOfBirth, descriptorUserDateOfBirth); + }) + } + ); + + // + return isSame; + } + + /// + /// Check to XDevice instance to be same or not + /// + /// + /// + /// + public static bool IsSameAs (this XDevice source, XDevice dest) { + // + if (source.IsNull () || + dest.IsNull ()) { + return false; + } + + // + var isSame = source.Os.ToNormalString () == dest.Os.ToNormalString () && + source.OsVersion.ToNormalString () == dest.OsVersion.ToNormalString () && + source.Browser.ToNormalString () == dest.Browser.ToNormalString () && + source.UserAgent.ToNormalString () == dest.UserAgent.ToNormalString () && + source.DeviceType == dest.DeviceType; + + // + return isSame; + } + + /// + /// convert UserClaimsInfo to TokenResponse ... + /// + /// + /// + public static XTokenResponse ToXTokenResponse (this XUserClaimsInfoDto source) { + // + if (source.IsNull () || + (source.AccessToken.IsNullOrEmpty () && source.RefreshToken.IsNullOrEmpty ())) { + return null; + } + + // + var result = new XTokenResponse { + AccessToken = source.AccessToken, + RefreshToken = source.RefreshToken, + ExpiresAt = source.ExpiresAt + }; + + // + return result; + } + } +} \ No newline at end of file diff --git a/Extensions/XTokenExtensions.cs b/Extensions/XTokenExtensions.cs new file mode 100644 index 0000000..c510ac7 --- /dev/null +++ b/Extensions/XTokenExtensions.cs @@ -0,0 +1,748 @@ +using System; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Threading.Tasks; +using IdentityModel.Client; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.IdentityModel.Tokens; +using xCommons.Constants; +using xCommons.Extensions; +using xExceptions.Constants; +using xIdentityModels.Constants; +using xIdentityModels.Models; + +namespace xIdentityModels.Extensions { + public static class XTokenExtensions { + // + #region Identity Token Extensions ... + /// + /// Convert a SecurityToken to it's Token String + /// + /// + /// + /// + public static string ToTokenString ( + this SecurityToken source, + JwtSecurityTokenHandler tokenHandler + ) { + return tokenHandler.WriteToken (source); + } + + /// + /// Validate a Token + /// + /// + /// + /// + /// + public static bool ValidateToken ( + this string token, + JwtSecurityTokenHandler tokenHandler, + TokenValidationParameters tokenValidationParams) { + // + if (token.IsNullOrEmpty ()) { + return false; + } + + // + SecurityToken tokenObj = null; + try { + tokenHandler.ValidateToken (token, tokenValidationParams, out tokenObj); + } catch { + return false; + } + + // + return tokenObj != null; + } + + /// + /// Convert a token string to Security Token + /// + /// + /// + /// + /// + public static SecurityToken ToSecurityToken ( + this string token, + JwtSecurityTokenHandler tokenHandler, + TokenValidationParameters tokenValidationParams) { + // + if (token.IsNullOrEmpty () || + !token.ValidateToken (tokenHandler, tokenValidationParams)) { + // + return null; + } + + // + // convert token from String to JWTSecurity Token ... + try { + return tokenHandler.ReadToken (token); + } catch { + return null; + } + } + + /// + /// Parse Action Token and return Corresponding RegistrationRequestDto + /// + /// + /// + /// + /// + public static XActionRequestToken ParseActionRequestToken ( + this string token, + JwtSecurityTokenHandler tokenHandler, + TokenValidationParameters tokenValidationParams) { + // + if (token.IsNullOrEmpty () || + !token.ValidateToken (tokenHandler, tokenValidationParams)) { + // + return null; + } + + // + // convert token from String to JWTSecurity Token ... + JwtSecurityToken tokenObj = null; + try { + tokenObj = tokenHandler.ReadToken (token) as JwtSecurityToken; + } catch { + return null; + } + + // + if (tokenObj == null) { + return null; + } + + // + // Extract Registered Payload Claims ... + var mCurrentStepClaim = tokenObj.Claims + .FirstOrDefault (c => c.Type == nameof (XActionRequestToken.Action)); + var mContextClaim = tokenObj.Claims + .FirstOrDefault (c => c.Type == nameof (XActionRequestToken.Context)); + var mTokenClaim = tokenObj.Claims + .FirstOrDefault (c => c.Type == nameof (XActionRequestToken.Token)); + + // + // Check All Claims to be NotNull ... + if (mCurrentStepClaim == null || + mContextClaim == null || + mTokenClaim == null) { + return null; + } + + // + // Reading all Claims Values ... + var mCurrentStepStr = mCurrentStepClaim.Value; + var mCurrentStep = 0; + try { + int.TryParse (mCurrentStepStr, out mCurrentStep); + } catch { } + + // + var mContext = mContextClaim.Value.IsNullOrEmpty () ? + null : mContextClaim.Value.FromJSON (); + var mToken = mTokenClaim.Value; + + // + // Create Instance of UserInvite ... + var result = new XActionRequestToken { + Action = (XAction) mCurrentStep, + Context = mContext, + Token = mToken + }; + + // + return result; + } + + /// + /// Check to ActionRequestContext object is same or not + /// + /// + /// + /// + public static bool IsSameAs ( + this XActionRequestContext source, + XActionRequestContext dest + ) { + // + if (source == null || + dest == null) { + return false; + } + + // + var result = source.ToJSON () == dest.ToJSON (); + + // + return result; + } + + /// + /// Convert a RegistrationRequestDto object to it's jwt token Claims + /// + /// + /// + public static Claim[] ToJwtClaims (this XActionRequestToken source) { + // + // Generate Required Fields ... + var action = source.Action; + var context = source.Context; + var token = source.Token; + + // + // Generate Claims Array ... + var claims = new Claim[] { + new Claim (nameof (XActionRequestToken.Action), ((int) action).ToString (), ClaimValueTypes.Integer), + new Claim (nameof (XActionRequestToken.Context), context == null ? "" : context.ToJSON (), ClaimValueTypes.String), + new Claim (nameof (XActionRequestToken.Token), token.IsNullOrEmpty () ? "" : token, ClaimValueTypes.String) + }; + + // + // Return Result ... + return claims; + } + + /// + /// Generate a SecurityToken based on RegistrationRequestDto object + /// + /// + /// + /// + /// + public static SecurityToken ToJwtToken ( + this XActionRequestToken source, + JwtSecurityTokenHandler tokenHandler, + SecurityTokenDescriptor tokenDescriptor) { + // + // Getting Claims ... + var claims = source.ToJwtClaims (); + tokenDescriptor.Subject = new ClaimsIdentity (claims); + + // + // Generate Token Object ... + var tokenObject = tokenHandler.CreateToken (tokenDescriptor); + + // + // Return Result ... + return tokenObject; + } + #endregion + + // + #region IdentityServer Token Extensions ... + /// + /// Retrieve Tokens From Headers + /// + /// + /// + public static XTokenResponse RetrieveTokens (this HttpRequest source) { + // + var accessToken = source.Headers[XAuthorization.Header].ToString (); + var refreshToken = source.Headers[XAuthorization.RefreshToken].ToString (); + + // + var expiresAtStr = source.Headers[XAuthorization.ExpiresAt].ToString (); + long expiresAt = expiresAtStr.ConvertTo (); + + // + if (accessToken + .ToNormalString () + .Contains (XAuthorization.TokenIdentifier.ToNormalString ())) { + accessToken = accessToken.Remove (0, XAuthorization.TokenIdentifier.Length); + } + + // + return new XTokenResponse { + AccessToken = accessToken, + RefreshToken = refreshToken, + ExpiresAt = expiresAt + }; + } + + /// + /// Retrieve Tokens From HttpContext + /// + /// + /// + public static async Task RetrieveTokens (this HttpContext source) { + // + var accessToken = await source.GetTokenAsync (XAuthorization.AccessToken); + if (accessToken + .ToNormalString () + .Contains (XAuthorization.TokenIdentifier.ToNormalString ())) { + accessToken = accessToken.Remove (0, XAuthorization.TokenIdentifier.Length); + } + + var refreshToken = await source.GetTokenAsync (XAuthorization.RefreshToken); + var expiresAtStr = await source.GetTokenAsync (XAuthorization.ExpiresAt); + long expiresAt = expiresAtStr.ConvertTo (); + + // + var headerRequestTokens = source.Request.RetrieveTokens (); + + // + var result = new XTokenResponse { + AccessToken = accessToken ?? headerRequestTokens.AccessToken, + RefreshToken = refreshToken ?? headerRequestTokens.RefreshToken, + ExpiresAt = expiresAt != 0 ? expiresAt : headerRequestTokens.ExpiresAt + }; + + // + return result; + } + + /// + /// Retrieve Tokens From ActionExecutingContext + /// + /// + /// + public static async Task RetrieveTokens (this ActionExecutingContext source) { + // + var result = await source.HttpContext.RetrieveTokens (); + return result; + } + + /// + /// Retrieve Tokens From XUserInfo instance + /// + /// + /// + public static XTokenResponse RetrieveTokens (this XUserClaimsInfoDto source) { + // + var result = new XTokenResponse { + AccessToken = source.AccessToken, + RefreshToken = source.RefreshToken, + ExpiresAt = source.ExpiresAt + }; + + // + return result; + } + + /// + /// Retrieve Tokens From IHeader Dictionary + /// + /// + /// + public static XTokenResponse RetrieveTokens (this IHeaderDictionary source) { + // + var accessToken = source[XAuthorization.Header].ToString () ?? ""; + if (accessToken + .ToNormalString () + .Contains (XAuthorization.TokenIdentifier.ToNormalString ())) { + accessToken = accessToken.Remove (0, XAuthorization.TokenIdentifier.Length); + } + + // + var refreshToken = source[XAuthorization.RefreshToken].ToString () ?? ""; + + // + var expiresAtStr = source[XAuthorization.ExpiresAt].ToString () ?? ""; + var expiresAt = expiresAtStr.ConvertTo (); + + // + var result = new XTokenResponse { + AccessToken = accessToken, + RefreshToken = refreshToken, + ExpiresAt = expiresAt + }; + + // + return result; + } + + /// + /// Generate XTokenResponse instance based on TokenResponse + /// + /// + /// + public static XLoginResponse CreateXLoginResponse (this TokenResponse source) { + // + if (source.IsNull () || + source.IsError) { + XException.InvalidToken.Throw (); + } + + // + var expiresAt = new DateTimeOffset (DateTime.UtcNow) + .ToUnixTimeSeconds () + + source.ExpiresIn; + + // + var result = new XLoginResponse { + AccessToken = source.AccessToken, + RefreshToken = source.RefreshToken, + ExpiresAt = expiresAt + }; + + // + return result; + } + + /// + /// Generate XTokenResponse instance based on TokenResponse + /// + /// + /// + public static XTokenResponse CreateXTokenResponse (this TokenResponse source) { + // + if (source.IsNull () || + source.IsError) { + XException.InvalidToken.Throw (); + } + + // + var expiresAt = new DateTimeOffset (DateTime.UtcNow) + .ToUnixTimeSeconds () + + source.ExpiresIn; + + // + var result = new XTokenResponse { + AccessToken = source.AccessToken, + RefreshToken = source.RefreshToken, + ExpiresAt = expiresAt + }; + + // + return result; + } + + /// + /// Convert XLoginResponse to XTokenResponse + /// + /// + /// + public static XTokenResponse ToXTokenResponse (this XLoginResponse source) { + // + if (source.IsNull ()) { + return null; + } + + // + return new XTokenResponse { + AccessToken = source.AccessToken, + RefreshToken = source.RefreshToken, + ExpiresAt = source.ExpiresAt + }; + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific IHeaderDictionary instance + /// + /// + /// + public static void SetXAuthenticationTokens ( + this IHeaderDictionary source, + XLoginResponse model, + string secret = null + ) { + // + source.Remove (XAuthorization.Header); + source.Remove (XAuthorization.RefreshToken); + source.Remove (XAuthorization.ExpiresAt); + source.Remove (XAuthorization.RevisionChecksum); + + // + source.Add (XAuthorization.Header, $"{XAuthorization.TokenIdentifier}{model.AccessToken}"); + source.Add (XAuthorization.RefreshToken, $"{model.RefreshToken}"); + source.Add (XAuthorization.ExpiresAt, $"{model.ExpiresAt}"); + + // + if (!secret.IsNullOrEmpty ()) { + var revisionChecksum = model.GetRevisionChecksum (secret); + source.Add (XAuthorization.RevisionChecksum, revisionChecksum); + } + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific IHeaderDictionary instance + /// + /// + /// + public static void SetXAuthenticationTokens ( + this IHeaderDictionary source, + XTokenResponse model, + string secret = null + ) { + // + source.Remove (XAuthorization.Header); + source.Remove (XAuthorization.RefreshToken); + source.Remove (XAuthorization.ExpiresAt); + source.Remove (XAuthorization.RevisionChecksum); + + // + source.Add (XAuthorization.Header, $"{XAuthorization.TokenIdentifier}{model.AccessToken}"); + source.Add (XAuthorization.RefreshToken, $"{model.RefreshToken}"); + source.Add (XAuthorization.ExpiresAt, $"{model.ExpiresAt}"); + + // + if (!secret.IsNullOrEmpty ()) { + var revisionChecksum = model.GetRevisionChecksum (secret); + source.Add (XAuthorization.RevisionChecksum, revisionChecksum); + } + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific IHeaderDictionary instance + /// + /// + /// + public static void SetXAuthenticationTokens ( + this HttpRequestHeaders source, + XLoginResponse model + ) { + // + source.Remove (XAuthorization.Header); + source.Remove (XAuthorization.RefreshToken); + source.Remove (XAuthorization.ExpiresAt); + source.Remove (XAuthorization.RevisionChecksum); + + // + source.Add (XAuthorization.Header, $"{XAuthorization.TokenIdentifier}{model.AccessToken}"); + source.Add (XAuthorization.RefreshToken, $"{model.RefreshToken}"); + source.Add (XAuthorization.ExpiresAt, $"{model.ExpiresAt}"); + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific IHeaderDictionary instance + /// + /// + /// + public static void SetXAuthenticationTokens ( + this HttpRequestHeaders source, + XTokenResponse model + ) { + // + source.Remove (XAuthorization.Header); + source.Remove (XAuthorization.RefreshToken); + source.Remove (XAuthorization.ExpiresAt); + source.Remove (XAuthorization.RevisionChecksum); + + // + source.Add (XAuthorization.Header, $"{XAuthorization.TokenIdentifier}{model.AccessToken}"); + source.Add (XAuthorization.RefreshToken, $"{model.RefreshToken}"); + source.Add (XAuthorization.ExpiresAt, $"{model.ExpiresAt}"); + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific HttpRequest instance + /// + /// + /// + public static void SetXAuthenticationTokens ( + this HttpRequest source, + XLoginResponse model + ) { + source.Headers.SetXAuthenticationTokens (model); + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific HttpRequest instance + /// + /// + /// + public static void SetXAuthenticationTokens ( + this HttpRequest source, + XTokenResponse model + ) { + source.Headers.SetXAuthenticationTokens (model); + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific HttpRequest instance + /// + /// + /// + public static void SetXAuthenticationTokens ( + this HttpRequestMessage source, + XLoginResponse model + ) { + source.Headers.SetXAuthenticationTokens (model); + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific HttpRequest instance + /// + /// + /// + public static void SetXAuthenticationTokens ( + this HttpRequestMessage source, + XTokenResponse model + ) { + source.Headers.SetXAuthenticationTokens (model); + } + + /// + /// Add ReNewed Authorization Tokens in Headers of + /// Specific Response. + /// Later in HttpInterceptors we can Look for AccessToken-ReNewed + /// header Value and Renew User Tokens based on it + /// + /// + /// + public static void SetXAuthenticationTokens ( + this HttpResponse source, + XLoginResponse model, + string secret + ) { + source.Headers.SetXAuthenticationTokens (model, secret); + } + + /// + /// Add ReNewed Authorization Tokens in Headers of + /// Specific Response. + /// Later in HttpInterceptors we can Look for AccessToken-ReNewed + /// header Value and Renew User Tokens based on it + /// + /// + /// + public static void SetXAuthenticationTokens ( + this HttpResponse source, + XTokenResponse model, + string secret + ) { + source.Headers.SetXAuthenticationTokens (model, secret); + } + + /// + /// Determines a Token Can Refrsh or not + /// + /// + /// + public static bool IsRefreshable (this XLoginResponse source) { + // + var containsTokenData = !source.IsNull () && + !source.AccessToken.IsNullOrEmpty () && + !source.RefreshToken.IsNullOrEmpty (); + if (!containsTokenData) { + return false; + } + + // + var current = new DateTimeOffset (DateTime.UtcNow) + .ToUnixTimeSeconds (); + + // + // var result = current > source.ExpiresAt || current > source.ExpiresAt - XAuthorization.ThresholdBeforeTokenExpiration; + var result = current > source.ExpiresAt - XAuthorization.ThresholdBeforeTokenExpiration; + + // + return result; + } + + /// + /// Determines a Token Can Refrsh or not + /// + /// + /// + public static bool IsRefreshable (this XTokenResponse source) { + // + var containsTokenData = !source.IsNull () && + !source.AccessToken.IsNullOrEmpty () && + !source.RefreshToken.IsNullOrEmpty (); + if (!containsTokenData) { + return false; + } + + // + var current = new DateTimeOffset (DateTime.UtcNow) + .ToUnixTimeSeconds (); + + // + // var result = current > source.ExpiresAt || current > source.ExpiresAt - XAuthorization.ThresholdBeforeTokenExpiration; + var result = current > source.ExpiresAt - XAuthorization.ThresholdBeforeTokenExpiration; + + // + return result; + } + + /// + /// Generate a Revision Checksum for new Tokens + /// + /// + /// + /// + public static string GetRevisionChecksum ( + this XLoginResponse source, + string secret + ) { + return (source.AccessToken + + secret + + source.RefreshToken + + secret + + source.ExpiresAt.ToString () + ) + .ToMd5String (); + } + + /// + /// Generate a Revision Checksum for new Tokens + /// + /// + /// + /// + public static string GetRevisionChecksum ( + this XTokenResponse source, + string secret + ) { + return (source.AccessToken + + secret + + source.RefreshToken + + secret + + source.ExpiresAt.ToString () + ) + .ToMd5String (); + } + + /// + /// Check an XLoginResponse instance is Valid with it's revision + /// + /// + /// + /// + /// + public static bool ValidateRevisionChecksum ( + this XLoginResponse source, + string checksum, + string secret + ) { + // + var xChecksum = source.GetRevisionChecksum (secret); + return checksum == xChecksum; + } + + /// + /// Check an XTokenResponse instance is Valid with it's revision + /// + /// + /// + /// + /// + public static bool ValidateRevisionChecksum ( + this XTokenResponse source, + string checksum, + string secret + ) { + // + var xChecksum = source.GetRevisionChecksum (secret); + return checksum == xChecksum; + } + #endregion + } +} \ No newline at end of file diff --git a/Extensions/xIdentityModelsExtensions.cs b/Extensions/xIdentityModelsExtensions.cs new file mode 100644 index 0000000..12fbbf9 --- /dev/null +++ b/Extensions/xIdentityModelsExtensions.cs @@ -0,0 +1,3 @@ +namespace xIdentityModels.Extensions { + public static class xIdentityModelsExtensions { } +} \ No newline at end of file diff --git a/Interfaces/IXUserRoleHelper.cs b/Interfaces/IXUserRoleHelper.cs new file mode 100644 index 0000000..9aca22a --- /dev/null +++ b/Interfaces/IXUserRoleHelper.cs @@ -0,0 +1,9 @@ +using xIdentityModels.Models; + +namespace xIdentityModels.Interfaces { + public interface IXUserRoleHelper { + bool canReadCriticalData (XUserClaimsInfoDto userInfo); + bool canDoDangerousAction (XUserClaimsInfoDto userInfo); + bool isInRole (XUserClaimsInfoDto userInfo, string role); + } +} \ No newline at end of file diff --git a/Models/XActionRequest.cs b/Models/XActionRequest.cs new file mode 100644 index 0000000..1ade96d --- /dev/null +++ b/Models/XActionRequest.cs @@ -0,0 +1,93 @@ +using System; +using Microsoft.AspNetCore.Http; +using xIdentityModels.Navigations; + +namespace xIdentityModels.Models { + /// + /// represent a system wide actions + /// + public class XActionRequest { + /// + /// Client Device Locale + /// + /// user's client device local such as en-US + public string Lang { get; set; } = null; + /// + /// Requested Action Token + /// + /// a hashed content + public string ActionToken { get; set; } = null; + /// + /// user's device + /// + /// an instance of XDevice which represent user's device + public XDevice Device { get; set; } = null; + /// + /// FirstName + /// + /// users FirstName + public string FirstName { get; set; } = null; + /// + /// LastName + /// + /// user's LastName + public string LastName { get; set; } = null; + /// + /// an Optional Value which represetns User's DateOfBirth + /// + /// user's DateOfBirth + public DateTime? DateOfBirth { get; set; } = DateTime.MinValue; + /// + /// User's Id + /// + /// a GUID string which represent user's Id + public string UserId { get; set; } = null; + /// + /// UserName + /// + /// user's UserName + public string UserName { get; set; } = null; + /// + /// Email + /// + /// user's Email address + public string Email { get; set; } = null; + /// + /// MobileNumber / PhoneNumber + /// + /// user's PhoneNumber + public string MobileNumber { get; set; } = null; + /// + /// Password + /// + /// user's Password + public string Password { get; set; } = null; + /// + /// NewPassword + /// + /// a passwor string which used to Change/Reset password + public string NewPassword { get; set; } = null; + /// + /// EmailVerificationCode + /// + /// a Verification Code which assign to user's for confirm it's Email Address + public string EmailVerificationCode { get; set; } = null; + /// + /// MobileVerificationCode + /// + /// a Verification Code which assign to user's for confirm it's Phone Number + public string MobileVerificationCode { get; set; } = null; + /// + /// Thumbnail + /// + /// an instance of IFormFile which is used as user's avatar + public IFormFile Thumbnail { get; set; } + /// + /// ReturnUrl + /// + /// + /// a url string which used in many used cases for providing a way to lead users to right places. + /// + public string ReturnUrl { get; set; } = null; + } +} \ No newline at end of file diff --git a/Models/XActionRequestContext.cs b/Models/XActionRequestContext.cs new file mode 100644 index 0000000..ce9ff03 --- /dev/null +++ b/Models/XActionRequestContext.cs @@ -0,0 +1,33 @@ +using System; +using Microsoft.AspNetCore.Http; +using xIdentityModels.Navigations; + +namespace xIdentityModels.Models { + public class XActionRequestContext { + // + public string Lang { get; set; } + + // + public string UserId { get; set; } + public string UserName { get; set; } + + // + public string MobileNumber { get; set; } + public string MobileVerificationCode { get; set; } + public bool MobileVerified { get; set; } + + // + public string Email { get; set; } + public string EmailVerificationCode { get; set; } + public bool EmailVerified { get; set; } + + // + public string FirstName { get; set; } + public string LastName { get; set; } + public DateTime DateOfBirth { get; set; } + + // + public XDevice Device { get; set; } + public IFormFile Thubmnail { get; set; } + } +} \ No newline at end of file diff --git a/Models/XActionRequestToken.cs b/Models/XActionRequestToken.cs new file mode 100644 index 0000000..53a221e --- /dev/null +++ b/Models/XActionRequestToken.cs @@ -0,0 +1,22 @@ +using xCommons.Extensions; +using xIdentityModels.Constants; + +namespace xIdentityModels.Models { + public class XActionRequestToken { + public XAction Action { get; set; } + public XActionRequestContext Context { get; set; } + public string Token { get; set; } + + public void Prepare (string secret) { + Token = (Action.ToString () + + secret + + Context.ToJSON ()).ToMd5String (); + } + + public bool Validate (string secret) { + return (Action.ToString () + + secret + + Context.ToJSON ()).ToMd5String () == Token; + } + } +} \ No newline at end of file diff --git a/Models/XActionResponse.cs b/Models/XActionResponse.cs new file mode 100644 index 0000000..447016d --- /dev/null +++ b/Models/XActionResponse.cs @@ -0,0 +1,29 @@ +using System; +using Newtonsoft.Json; + +namespace xIdentityModels.Models { + /// + /// represent a system wide action result + /// + public class XActionResponse { + /// + /// ActionToken + /// + /// a hashed string which carry required info for doing system actions. + [JsonProperty ( + "token", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string Token { get; set; } + + /// + /// ActionToken Lifetime + /// + /// an instance of DateTime which represent token expiration time + [JsonProperty ( + "expiration", + Required = Required.DisallowNull, + NullValueHandling = NullValueHandling.Ignore)] + public DateTime Expiration { get; set; } + } +} \ No newline at end of file diff --git a/Models/XBannedDevice.cs b/Models/XBannedDevice.cs new file mode 100644 index 0000000..5606b1d --- /dev/null +++ b/Models/XBannedDevice.cs @@ -0,0 +1,23 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; +using xCommons.Extensions; +using xIdentityModels.Navigations; +using xModels.Base; + +namespace xIdentityModels.Models { + public class XBannedDevice : XBaseIntIDEntity { + public DateTime BannedOn { get; set; } + + public string DeviceStr { get; set; } + + [NotMapped] + public XDevice Device { + get { + return DeviceStr.FromJSON (); + } + set { + DeviceStr = value.ToJSON (); + } + } + } +} \ No newline at end of file diff --git a/Models/XLoginRequest.cs b/Models/XLoginRequest.cs new file mode 100644 index 0000000..49ad562 --- /dev/null +++ b/Models/XLoginRequest.cs @@ -0,0 +1,35 @@ +using System.ComponentModel.DataAnnotations; +using xIdentityModels.Navigations; + +namespace xIdentityModels.Models { + /// + /// Represent Requirements for Authenticationg a User + /// + public class XLoginRequest { + /// + /// how select an specific User + /// + /// an string value which represent how system select user such as: UserName, Email Address or MobileNumber + [Required] + public string UserSelectBy { get; set; } + + /// + /// user's password + /// + /// an string which represent user's pasword + [Required] + public string Password { get; set; } + + /// + /// user's device + /// + /// an instance of XDevice which represent user's device + public XDevice Device { get; set; } + + /// + /// user's language + /// + /// an string which represent user's language and locale, such as: en-US + public string Language { get; set; } + } +} \ No newline at end of file diff --git a/Models/XLoginResponse.cs b/Models/XLoginResponse.cs new file mode 100644 index 0000000..184a63a --- /dev/null +++ b/Models/XLoginResponse.cs @@ -0,0 +1,7 @@ +using xIdentityModels.Dtos; + +namespace xIdentityModels.Models { + public class XLoginResponse : XTokenResponse { + public XUserProfileDto Profile { get; set; } + } +} \ No newline at end of file diff --git a/Models/XProfileUpdateRequest.cs b/Models/XProfileUpdateRequest.cs new file mode 100644 index 0000000..12edc71 --- /dev/null +++ b/Models/XProfileUpdateRequest.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using xIdentityModels.Constants; + +namespace xIdentityModels.Models { + public class XProfileUpdateRequest { + public string Email { get; set; } + public bool? EmailConfirmed { get; set; } + public string PhoneNumber { get; set; } + public bool? PhoneNumberConfirmed { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Avatar { get; set; } + public DateTime? DateOfBirth { get; set; } + public DateTime? CreationDate { get; set; } + public DateTime? LastLogin { get; set; } + public XGender? Gender { get; set; } + public ICollection Roles { get; set; } = new HashSet (); + public bool? IsEnable { get; set; } + public bool? IsBanned { get; set; } + } +} \ No newline at end of file diff --git a/Models/XToken.cs b/Models/XToken.cs new file mode 100644 index 0000000..d5ab94f --- /dev/null +++ b/Models/XToken.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations.Schema; +using xCommons.Extensions; +using xIdentityModels.Constants; +using xIdentityModels.Navigations; +using xModels.Base; + +namespace xIdentityModels.Models { + public class XToken : XBaseIntIDEntity { + public string Token { get; set; } + public string Hash { get; set; } + public string UserSelectByParam { get; set; } + public string DeviceStr { get; set; } + public XAction Type { get; set; } + + [NotMapped] + public XDevice Device { + get { + return DeviceStr.FromJSON (); + } + + set { + DeviceStr = value.ToJSON (); + } + } + } +} \ No newline at end of file diff --git a/Models/XTokenResponse.cs b/Models/XTokenResponse.cs new file mode 100644 index 0000000..b45b16b --- /dev/null +++ b/Models/XTokenResponse.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace xIdentityModels.Models { + public class XTokenResponse { + [Required] + public string AccessToken { get; set; } + public string RefreshToken { get; set; } + public long ExpiresAt { get; set; } + } +} \ No newline at end of file diff --git a/Models/XUserClaimsInfoDto.cs b/Models/XUserClaimsInfoDto.cs new file mode 100644 index 0000000..447f195 --- /dev/null +++ b/Models/XUserClaimsInfoDto.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using xCommons.Constants; +using xCommons.Extensions; +using xCommons.Helpers; +using xExceptions.Constants; +using xIdentityModels.Constants; +using xModels.Base; + +namespace xIdentityModels.Models { + public class XUserClaimsInfoDto : XBaseDto { + // + #region Properties ... + public string AccessToken { get; } + public string RefreshToken { get; } + public long ExpiresAt { get; } + public string UserId { get; } + public string UserName { get; } + public string FirstName { get; } + public string LastName { get; } + public string Picture { get; } + public string Email { get; } + public string PhoneNumber { get; } + public XGender Gender { get; } + public bool IsBanned { get; } + public bool IsEnabled { get; } + public bool EmailConfirmed { get; } + public bool PhoneNumberConfirmed { get; } + public long? ExpiredOn { get; } + public long? AuthenticatedOn { get; } + public DateTime? BrithDate { get; } + public IEnumerable Issuers { get; } = new HashSet (); + public IEnumerable Audiences { get; } = new HashSet (); + public IEnumerable ClientIds { get; } = new HashSet (); + public IEnumerable Scopes { get; } = new HashSet (); + public IEnumerable Roles { get; } = new HashSet (); + #endregion + + // + public XUserClaimsInfoDto () { } + + public XUserClaimsInfoDto ( + string accessToken, + string refreshToken, + long expiresAt, + IEnumerable claims + ) { + // + // refreshToken.IsNullOrEmpty () + if (claims.IsNull () || + accessToken.IsNullOrEmpty ()) { + XException.InvalidArgs.Throw (); + } + + // + // Attach XLogin Properties ... + AccessToken = accessToken; + RefreshToken = refreshToken; + ExpiresAt = expiresAt; + + // + if (!claims.IsNull ()) { + UserId = claims.FirstOrDefault (c => c.Type == XCustomClaims.UserId)?.Value ?? ""; + UserName = claims.FirstOrDefault (c => c.Type == XCustomClaims.UserName)?.Value ?? ""; + FirstName = claims.FirstOrDefault (c => c.Type == XCustomClaims.FirstName)?.Value ?? ""; + LastName = claims.FirstOrDefault (c => c.Type == XCustomClaims.LastName)?.Value ?? ""; + Picture = claims.FirstOrDefault (c => c.Type == XCustomClaims.Picture)?.Value ?? ""; + Email = claims.FirstOrDefault (c => c.Type == XCustomClaims.Email)?.Value ?? null; + PhoneNumber = claims.FirstOrDefault (c => c.Type == XCustomClaims.PhoneNumber)?.Value ?? null; + + // + // Handle Gender ... + var genders = ObjectHelper.ToEnumerableKeys (); + var genderStr = claims.FirstOrDefault (c => c.Type == XCustomClaims.Gender).Value ?? null; + if (genderStr.IsNullOrEmpty ()) { + Gender = XGender.Male; + } else { + Gender = genderStr.GetValue (); + } + + // + // Handle IsBanned ... + var isBannedStr = claims.FirstOrDefault (c => c.Type == XCustomClaims.IsBanned).Value ?? null; + if (isBannedStr.IsNullOrEmpty ()) { + IsBanned = false; + } else { + // + var isBanned = false; + Boolean.TryParse (isBannedStr, out isBanned); + + // + IsBanned = isBanned; + } + + // + // Handle IsEnabled ... + var isEnabledStr = claims.FirstOrDefault (c => c.Type == XCustomClaims.IsEnabled).Value ?? null; + if (isEnabledStr.IsNullOrEmpty ()) { + IsEnabled = false; + } else { + // + var isEnabled = false; + Boolean.TryParse (isEnabledStr, out isEnabled); + + // + IsEnabled = isEnabled; + } + + // + // Handle Email Confirmed ... + var emailConfirmedStr = claims.FirstOrDefault (c => c.Type == XCustomClaims.EmailVerified).Value ?? null; + if (emailConfirmedStr.IsNullOrEmpty ()) { + EmailConfirmed = false; + } else { + // + var emailConfirmed = false; + Boolean.TryParse (emailConfirmedStr, out emailConfirmed); + + // + EmailConfirmed = emailConfirmed; + } + + // + // Handle PhoneNumber Confirmed ... + var phoneNumberConfirmedStr = claims.FirstOrDefault (c => c.Type == XCustomClaims.PhoneNumberVerified).Value ?? null; + if (phoneNumberConfirmedStr.IsNullOrEmpty ()) { + PhoneNumberConfirmed = false; + } else { + // + var phoneNumberConfirmed = false; + Boolean.TryParse (phoneNumberConfirmedStr, out phoneNumberConfirmed); + + // + PhoneNumberConfirmed = phoneNumberConfirmed; + } + + // + // Handle ExpiredOn ... + var expiredOnStr = claims.FirstOrDefault (c => c.Type == XCustomClaims.ExpiredOn).Value ?? null; + if (expiredOnStr.IsNullOrEmpty ()) { + ExpiredOn = null; + } else { + // + long expiredOn = 0; + long.TryParse (expiredOnStr, out expiredOn); + + // + ExpiredOn = expiredOn; + } + + // + // Handle AuthenticatedOn ... + var authenticatedOnStr = claims.FirstOrDefault (c => c.Type == XCustomClaims.AuthenticatedOn).Value ?? null; + if (authenticatedOnStr.IsNullOrEmpty ()) { + AuthenticatedOn = null; + } else { + // + long authenticatedOn = 0; + long.TryParse (authenticatedOnStr, out authenticatedOn); + + // + AuthenticatedOn = authenticatedOn; + } + + // + // Handle BrithDate ... + var brithDateStr = claims.FirstOrDefault (c => c.Type == XCustomClaims.BrithDate)?.Value ?? ""; + if (brithDateStr.IsNullOrEmpty ()) { + // BrithDate = null; + } else { + // + DateTime bDate; + DateTime.TryParse (brithDateStr, out bDate); + + // + BrithDate = bDate; + } + + // + Issuers = claims.Where (c => c.Type == XCustomClaims.Issuer) ? + .Select (c => c.Value) ?? new HashSet (); + Audiences = claims.Where (c => c.Type == XCustomClaims.Audience) ? + .Select (c => c.Value) ?? new HashSet (); + ClientIds = claims.Where (c => c.Type == XCustomClaims.ClientId) ? + .Select (c => c.Value) ?? new HashSet (); + Scopes = claims.Where (c => c.Type == XCustomClaims.Scope) ? + .Select (c => c.Value) ?? new HashSet (); + Roles = claims.Where (c => c.Type == XCustomClaims.Role) ? + .Select (c => c.Value) ?? new HashSet (); + } + } + } +} \ No newline at end of file diff --git a/Models/XVerificationRequest.cs b/Models/XVerificationRequest.cs new file mode 100644 index 0000000..9eeac99 --- /dev/null +++ b/Models/XVerificationRequest.cs @@ -0,0 +1,41 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using xCommons.Extensions; +using xIdentityModels.Navigations; +using xModels.Base; + +namespace xIdentityModels.Models { + public class XVerificationRequest : XBaseIntIDEntity { + + [Required] + public string VerificationCode { get; set; } + + public int NumberOfTries { get; set; } + public DateTime LastTryOn { get; set; } + + [NotMapped] + public XActionRequestToken Request { + get { + return RequestStr.FromJSON (); + } + set { + RequestStr = value.ToJSON (); + } + } + + public string RequestStr { get; set; } + + [NotMapped] + public XDevice Device { + get { + return DeviceStr.FromJSON (); + } + set { + DeviceStr = value.ToJSON (); + } + } + + public string DeviceStr { get; set; } + } +} \ No newline at end of file diff --git a/Navigations/XDevice.cs b/Navigations/XDevice.cs new file mode 100644 index 0000000..ad7ad30 --- /dev/null +++ b/Navigations/XDevice.cs @@ -0,0 +1,51 @@ +using System.ComponentModel.DataAnnotations.Schema; +using xCommons.Constants; +using xModels.Base; + +namespace xIdentityModels.Navigations { + /// + /// Represent a Client Device which used by User + /// + public class XDevice : XBaseIntIDEntity { + /// + /// Operating System + /// + /// device operating system identifier + public string Os { get; set; } + /// + /// Operating System Version + /// + /// which version of operatin system is in use + public string OsVersion { get; set; } + /// + /// Device Browser + /// + /// the Browser identifier + public string Browser { get; set; } + /// + /// Browser Engine Agent + /// + /// browser engine Agent + public string UserAgent { get; set; } + /// + /// Type of client Device + /// + /// represent device type XDeviceType + public XDeviceType DeviceType { get; set; } + + // + #region User ... + /// + /// UserId + /// + /// Represent this Devie belong to which User Identifier + public string UserId { get; set; } + /// + /// User + /// + /// Represent Device User XUser + [ForeignKey (nameof (UserId))] + public virtual XUser User { get; set; } + #endregion + } +} \ No newline at end of file diff --git a/Navigations/XFriendshipFollower.cs b/Navigations/XFriendshipFollower.cs new file mode 100644 index 0000000..53b19c4 --- /dev/null +++ b/Navigations/XFriendshipFollower.cs @@ -0,0 +1,46 @@ +using System.ComponentModel.DataAnnotations.Schema; +using xIdentityModels.Constants; +using xModels.Base; + +namespace xIdentityModels.Navigations { + /// + /// represent a Follower of specific User + /// + public class XFriendshipFollower : XBaseIntIDEntity { + /// + /// XFriendshipState + /// + /// state of Friendship at current moment XFriendshipState + public XFriendshipState State { get; set; } + + // + #region User ... + /// + /// UserId + /// + /// source User's Id + public string UserId { get; set; } + /// + /// User + /// + /// source User XUser + [ForeignKey (nameof (UserId))] + public virtual XUser User { get; set; } + #endregion + + // + #region Dest User ... + /// + /// DestId + /// + /// destination User's Id + public string DestId { get; set; } + /// + /// Dest + /// + /// dest User XUser + [ForeignKey (nameof (DestId))] + public virtual XUser Dest { get; set; } + #endregion + } +} \ No newline at end of file diff --git a/Navigations/XFriendshipFollowing.cs b/Navigations/XFriendshipFollowing.cs new file mode 100644 index 0000000..bf70518 --- /dev/null +++ b/Navigations/XFriendshipFollowing.cs @@ -0,0 +1,46 @@ +using System.ComponentModel.DataAnnotations.Schema; +using xIdentityModels.Constants; +using xModels.Base; + +namespace xIdentityModels.Navigations { + /// + /// represent a Following of specific User + /// + public class XFriendshipFollowing : XBaseIntIDEntity { + /// + /// XFriendshipState + /// + /// state of Friendship at current moment XFriendshipState + public XFriendshipState State { get; set; } + + // + #region User ... + /// + /// UserId + /// + /// source User's Id + public string UserId { get; set; } + /// + /// User + /// + /// source User XUser + [ForeignKey (nameof (UserId))] + public virtual XUser User { get; set; } + #endregion + + // + #region Dest User ... + /// + /// DestId + /// + /// destination User's Id + public string DestId { get; set; } + /// + /// Dest + /// + /// dest User XUser + [ForeignKey (nameof (DestId))] + public virtual XUser Dest { get; set; } + #endregion + } +} \ No newline at end of file diff --git a/Navigations/XProfileImage.cs b/Navigations/XProfileImage.cs new file mode 100644 index 0000000..2e586ee --- /dev/null +++ b/Navigations/XProfileImage.cs @@ -0,0 +1,56 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using xModels.Base; + +namespace xIdentityModels.Navigations { + /// + /// Represent a User's Avatar Entity + /// + public class XProfileImage : XBaseIntIDEntity { + /// + /// UserId + /// + /// represent current Avatar belongs to which User + public string UserId { get; set; } + /// + /// User + /// + /// User instance XUser + [ForeignKey (nameof (UserId))] + public XUser User { get; set; } + /// + /// Name + /// + /// Avatar's file name + [Required] + [StringLength (255)] + public string Name { get; set; } + /// + /// Thumb + /// + /// Avatar's thumbnail file's name + [Required] + [StringLength (255)] + public string Thumb { get; set; } + /// + /// Path + /// + /// Avatar's Image path on Profile Server + [Required] + [StringLength (255)] + public string Path { get; set; } + /// + /// ThumbPath + /// + /// Avatar's Thumbnail Image path on Profile Service + [Required] + [StringLength (255)] + public string ThumbPath { get; set; } + /// + /// CreationDate + /// + /// Upload Time + public DateTime CreationDate { get; set; } + } +} \ No newline at end of file diff --git a/Providers/XDefaultUserRoleHelper.cs b/Providers/XDefaultUserRoleHelper.cs new file mode 100644 index 0000000..cc02fa6 --- /dev/null +++ b/Providers/XDefaultUserRoleHelper.cs @@ -0,0 +1,23 @@ +using System.Linq; +using xCommons.Extensions; +using xIdentityModels.Models; +using xIdentityModels.Interfaces; + +namespace xIdentityModels.Providers { + public class XDefaultUserRoleHelper : IXUserRoleHelper { + public bool canReadCriticalData (XUserClaimsInfoDto userInfo) { + return isInRole (userInfo, "admin"); + } + + public bool canDoDangerousAction (XUserClaimsInfoDto userInfo) { + return isInRole (userInfo, "admin"); + } + + public bool isInRole (XUserClaimsInfoDto userInfo, string role) { + return userInfo.Roles + .Any (r => + r.ToNormalString () == role.ToNormalString () + ); + } + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c74d5f2 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# xIdentityModels + +it is a Part of xDashboard on SaherElm IT Center which provides all required identity models + +## Maintainer + +Hadi Khazaee asl + +[https://www.saherelm.ir](https://www.saherelm.ir) + +[hadi_khazaee_asl@yahoo.com](mailto:hadi_khazaee_asl@yahoo.com) diff --git a/XApiToken.cs b/XApiToken.cs new file mode 100644 index 0000000..78a24a9 --- /dev/null +++ b/XApiToken.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations.Schema; +using xCommons.Extensions; +using xModels.Base; +using xIdentityModels.Models; + +namespace xIdentityModels { + public class XApiToken : XBaseIntIDEntity { + public string Content { get; set; } + + [NotMapped] + public XTokenResponse Tokens { + get { return Content.IsNullOrEmpty () ? null : Content.FromJSON (); } + set { Content = value.ToJSON (); } + } + } +} \ No newline at end of file diff --git a/XUser.cs b/XUser.cs new file mode 100644 index 0000000..d333f97 --- /dev/null +++ b/XUser.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.AspNetCore.Identity; +using xIdentityModels.Constants; +using xIdentityModels.Navigations; + +namespace xIdentityModels { + /// + /// represnt a User of System + /// + public partial class XUser : IdentityUser { + // + #region Properties ... + /// + /// FirstName + /// + /// user's FirstName + [Required] + [StringLength (50)] + public string FirstName { get; set; } + /// + /// LastName + /// + /// user's LastName + [Required] + [StringLength (100)] + public string LastName { get; set; } + /// + /// User is Enabled or not + /// + /// + /// system was restrict Disabled Users access to Many Resources + /// + /// boolean value represent User is Enable or not + public bool IsEnable { get; set; } + /// + /// User is Banned or not + /// + /// + /// system was reject Banned Users access to Almost all Resources + /// + /// boolean value represent User is Banned or not + public bool IsBanned { get; set; } + /// + /// DateOfBirth + /// + /// user's DateOfBirth + [Required] + public DateTime DateOfBirth { get; set; } + /// + /// CreationDate + /// + /// user's Creation/Registration Date + [Required] + public DateTime CreationDate { get; set; } + /// + /// LastLogin + /// + /// user's Last Successfull Login Date + public DateTime? LastLogin { get; set; } + /// + /// ProfileImage/Avatar + /// + /// user's current Avatar + public string Avatar { get; set; } + /// + /// Gender + /// + /// user's Gender + [Required] + public XGender Gender { get; set; } + #endregion + + // + #region Navigation Properties ... + /// + /// User's Devices + /// + /// XDevice + /// a Collection of all assigned Devices to User + [InverseProperty ("User")] + public virtual ICollection Devices { get; set; } = new HashSet (); + [InverseProperty ("User")] + /// + /// User's Avatars + /// + /// XProfileImage + /// a Collection of all assigned Avatars to User + public virtual ICollection Avatars { get; set; } = new HashSet (); + /// + /// User's Followers + /// + /// XFriendshipFollower + /// a Collection of all User's Followers + [InverseProperty ("User")] + public virtual ICollection Followers { get; set; } = new HashSet (); + /// + /// User's Followings + /// + /// XFriendshipFollowing + /// a Collection of all User's Followings + [InverseProperty ("User")] + public virtual ICollection Followings { get; set; } = new HashSet (); + /// + /// User's Roles + /// + /// a Collection of all User's Roles in System + public virtual ICollection> Roles { get; set; } = new HashSet> (); + #endregion + } +} \ No newline at end of file diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..632defe --- /dev/null +++ b/nuget.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/xIdentityModels.csproj b/xIdentityModels.csproj new file mode 100644 index 0000000..6fb68df --- /dev/null +++ b/xIdentityModels.csproj @@ -0,0 +1,36 @@ + + + + + netstandard2.0 + xDashboard.xIdentityModels + 1.0.0 + Hadi Khazaee Asl + SaherElm IT Center + + it is a Part of xDashboard on SaherElm IT Center which provides all required identity models + + + + icon.png + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file