From cfc093445b7cc13eb832cf783b3a081d4eeda135 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Thu, 15 Jul 2021 19:47:33 +0300 Subject: [PATCH 01/36] set environment for github oauth --- .../appsettings.development.json | 10 +++- .../Configuration/appsettings.production.json | 6 +++ .../Configuration/appsettings.test.json | 6 +++ Devnot.Mentor.Api/DevnotMentor.Api.csproj | 1 + Devnot.Mentor.Api/Startup.cs | 50 +++++++++++++++++-- 5 files changed, 66 insertions(+), 7 deletions(-) diff --git a/Devnot.Mentor.Api/Configuration/appsettings.development.json b/Devnot.Mentor.Api/Configuration/appsettings.development.json index 892f393..b626ae2 100644 --- a/Devnot.Mentor.Api/Configuration/appsettings.development.json +++ b/Devnot.Mentor.Api/Configuration/appsettings.development.json @@ -1,6 +1,12 @@ { "ConnectionStrings": { - "SQLServerConnectionString": "Server=DESKTOP-AT6H87T;Database=MentorDB;Trusted_Connection=True;MultipleActiveResultSets=true" + "SQLServerConnectionString": "Server=localhost;Database=MentorDB_Test;User Id=sa; Password=yourStrongPassword!;" + }, + "GitHub": { + "Client": { + "ID": "2a0aebc08c87e25bab16", + "Secret": "3110f6222264f39f04bda81740c32ca52c9fb814" + } }, "JWT": { "Secret": "c9J2Ff5Uh2ISscBYxC4NJNw7SMB9FGTQ", @@ -22,4 +28,4 @@ "UpdatePasswordWebPageUrl": "http://subdomain.devnot.com/sifreyi-yenile", "SecurityKeyExpiryFromHours": 24 } -} +} \ No newline at end of file diff --git a/Devnot.Mentor.Api/Configuration/appsettings.production.json b/Devnot.Mentor.Api/Configuration/appsettings.production.json index dcfcbe9..12c21fd 100644 --- a/Devnot.Mentor.Api/Configuration/appsettings.production.json +++ b/Devnot.Mentor.Api/Configuration/appsettings.production.json @@ -2,6 +2,12 @@ "ConnectionStrings": { "SQLServerConnectionString": "Server=DESKTOP-AT6H87T;Database=MentorDB;Trusted_Connection=True;MultipleActiveResultSets=true" }, + "GitHub": { + "Client": { + "ID": "-", + "Secret": "-" + } + }, "JWT": { "Secret": "c9J2Ff5Uh2ISscBYxC4NJNw7SMB9FGTQ", "SecretExpirationInMinutes": 1440, diff --git a/Devnot.Mentor.Api/Configuration/appsettings.test.json b/Devnot.Mentor.Api/Configuration/appsettings.test.json index dcfcbe9..12c21fd 100644 --- a/Devnot.Mentor.Api/Configuration/appsettings.test.json +++ b/Devnot.Mentor.Api/Configuration/appsettings.test.json @@ -2,6 +2,12 @@ "ConnectionStrings": { "SQLServerConnectionString": "Server=DESKTOP-AT6H87T;Database=MentorDB;Trusted_Connection=True;MultipleActiveResultSets=true" }, + "GitHub": { + "Client": { + "ID": "-", + "Secret": "-" + } + }, "JWT": { "Secret": "c9J2Ff5Uh2ISscBYxC4NJNw7SMB9FGTQ", "SecretExpirationInMinutes": 1440, diff --git a/Devnot.Mentor.Api/DevnotMentor.Api.csproj b/Devnot.Mentor.Api/DevnotMentor.Api.csproj index c21a605..c8c23b3 100644 --- a/Devnot.Mentor.Api/DevnotMentor.Api.csproj +++ b/Devnot.Mentor.Api/DevnotMentor.Api.csproj @@ -7,6 +7,7 @@ + diff --git a/Devnot.Mentor.Api/Startup.cs b/Devnot.Mentor.Api/Startup.cs index 2edf0c9..f3d7a50 100644 --- a/Devnot.Mentor.Api/Startup.cs +++ b/Devnot.Mentor.Api/Startup.cs @@ -1,7 +1,5 @@ using System; -using AutoMapper; using DevnotMentor.Api.Entities; -using DevnotMentor.Api.Helpers; using DevnotMentor.Api.Services; using DevnotMentor.Api.Services.Interfaces; using Microsoft.AspNetCore.Builder; @@ -24,6 +22,13 @@ using DevnotMentor.Api.Utilities.File; using DevnotMentor.Api.Utilities.File.Local; using DevnotMentor.Api.Utilities.Security.Token.Jwt; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Authentication.OAuth; +using System.Net.Http; +using System.Net.Http.Headers; +using Microsoft.AspNetCore.Authentication.Cookies; +using System.Text.Json; +using Microsoft.AspNetCore.Authentication; namespace DevnotMentor.Api { @@ -40,11 +45,43 @@ public void ConfigureServices(IServiceCollection services) { var connectionString = EnvironmentService.StaticConfiguration["ConnectionStrings:SQLServerConnectionString"]; services.AddDbContext(options => options.UseSqlServer(connectionString)); + services.AddSingleton(); services.AddControllers(); services.AddAutoMapper(typeof(Startup)); - - services.AddSingleton(); + + services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie() + .AddGitHub(options => + { + options.ClientId = EnvironmentService.StaticConfiguration["GitHub:Client:ID"]; + options.ClientSecret = EnvironmentService.StaticConfiguration["GitHub:Client:Secret"]; + options.CallbackPath = new PathString("/auth-github/"); + options.AuthorizationEndpoint = "https://github.com/login/oauth/authorize"; + options.TokenEndpoint = "https://github.com/login/oauth/access_token"; + options.UserInformationEndpoint = "https://api.github.com/user"; + + options.ClaimActions.MapJsonKey("github:id", "id"); + options.ClaimActions.MapJsonKey("github:name", "name"); + options.ClaimActions.MapJsonKey("github:login", "login"); + options.ClaimActions.MapJsonKey("github:avatar", "avatar_url"); + + options.SaveTokens = true; + + options.Events = new OAuthEvents + { + OnCreatingTicket = async ctx => + { + var request = new HttpRequestMessage(HttpMethod.Get, ctx.Options.UserInformationEndpoint); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", ctx.AccessToken); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var response = await ctx.Backchannel.SendAsync(request, ctx.HttpContext.RequestAborted); + response.EnsureSuccessStatusCode(); + + var jsonDocumentFromResponse = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); + ctx.RunClaimActions(jsonDocumentFromResponse.RootElement); + } + }; + }); services.AddScoped(); services.AddScoped(); @@ -106,7 +143,10 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseDeveloperExceptionPage(); } - + app.UseCookiePolicy(new CookiePolicyOptions() + { + MinimumSameSitePolicy = SameSiteMode.Lax + }); app.UseRouting(); app.UseAuthentication(); app.UseCors("AllowMyOrigin"); From 3303ff132bc401d3be0623b569a806d020837b4a Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Thu, 15 Jul 2021 19:55:52 +0300 Subject: [PATCH 02/36] Remove: user implemantations. Update: user entity --- .../Controllers/UserController.cs | 66 +------------------ .../CompleteRemindPasswordRequest.cs | 13 ---- .../UserRequest/RegisterUserRequest.cs | 28 -------- .../UserRequest/UpdatePasswordRequest.cs | 14 ---- .../Request/UserRequest/UpdateUserRequest.cs | 12 +--- .../Request/UserRequest/UserLoginRequest.cs | 13 ---- Devnot.Mentor.Api/Entities/MentorDBContext.cs | 31 ++++----- Devnot.Mentor.Api/Entities/User.cs | 11 +--- Devnot.Mentor.Api/Helpers/MappingProfile.cs | 4 -- .../Interfaces/IUserRepository.cs | 7 +- .../Repositories/UserRepository.cs | 22 ++----- 11 files changed, 25 insertions(+), 196 deletions(-) delete mode 100644 Devnot.Mentor.Api/CustomEntities/Request/UserRequest/CompleteRemindPasswordRequest.cs delete mode 100644 Devnot.Mentor.Api/CustomEntities/Request/UserRequest/RegisterUserRequest.cs delete mode 100644 Devnot.Mentor.Api/CustomEntities/Request/UserRequest/UpdatePasswordRequest.cs delete mode 100644 Devnot.Mentor.Api/CustomEntities/Request/UserRequest/UserLoginRequest.cs diff --git a/Devnot.Mentor.Api/Controllers/UserController.cs b/Devnot.Mentor.Api/Controllers/UserController.cs index 917e1ab..2b71f29 100644 --- a/Devnot.Mentor.Api/Controllers/UserController.cs +++ b/Devnot.Mentor.Api/Controllers/UserController.cs @@ -1,8 +1,4 @@ -using System.Threading.Tasks; -using DevnotMentor.Api.ActionFilters; -using DevnotMentor.Api.CustomEntities.Request.UserRequest; -using DevnotMentor.Api.Helpers.Extensions; -using DevnotMentor.Api.Services.Interfaces; +using DevnotMentor.Api.Services.Interfaces; using Microsoft.AspNetCore.Mvc; namespace DevnotMentor.Api.Controllers @@ -16,65 +12,5 @@ public UserController(IUserService userService) { this.userService = userService; } - - [HttpPost] - [Route("/users/login")] - public async Task Login(UserLoginRequest request) - { - var result = await userService.Login(request); - - return result.Success ? Success(result) : BadRequest(result); - } - - [HttpPost] - [Route("/users/register")] - public async Task Register([FromForm] RegisterUserRequest request) - { - var result = await userService.Register(request); - - return result.Success ? Success(result) : BadRequest(result); - } - - [HttpPost] - [Route("/users/change-password")] - [ServiceFilter(typeof(TokenAuthentication))] - public async Task ChangePassword([FromBody] UpdatePasswordRequest request) - { - request.UserId = User.Claims.GetUserId(); - - var result = await userService.ChangePassword(request); - - return result.Success ? Success(result) : BadRequest(result); - } - - [HttpPatch] - [Route("/users")] - [ServiceFilter(typeof(TokenAuthentication))] - public async Task UpdateUser([FromForm] UpdateUserRequest request) - { - request.UserId = User.Claims.GetUserId(); - - var result = await userService.Update(request); - - return result.Success ? Success(result) : BadRequest(result); - } - - [Route("/users/{email}/remind-password")] - [HttpGet] - public async Task RemindPassword([FromRoute] string email) - { - var result = await userService.RemindPassword(email); - - return result.Success ? Success(result) : BadRequest(result); - } - - [HttpPost] - [Route("/users/me/remind-password-complete")] - public async Task RemindPasswordCompleteAsync(CompleteRemindPasswordRequest request) - { - var result = await userService.RemindPasswordComplete(request); - - return result.Success ? Success(result) : BadRequest(result); - } } } \ No newline at end of file diff --git a/Devnot.Mentor.Api/CustomEntities/Request/UserRequest/CompleteRemindPasswordRequest.cs b/Devnot.Mentor.Api/CustomEntities/Request/UserRequest/CompleteRemindPasswordRequest.cs deleted file mode 100644 index ea316f7..0000000 --- a/Devnot.Mentor.Api/CustomEntities/Request/UserRequest/CompleteRemindPasswordRequest.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; - -namespace DevnotMentor.Api.CustomEntities.Request.UserRequest -{ - public class CompleteRemindPasswordRequest - { - [Required] - public string Password { get; set; } - [Required] - public Guid SecurityKey { get; set; } - } -} diff --git a/Devnot.Mentor.Api/CustomEntities/Request/UserRequest/RegisterUserRequest.cs b/Devnot.Mentor.Api/CustomEntities/Request/UserRequest/RegisterUserRequest.cs deleted file mode 100644 index ae72460..0000000 --- a/Devnot.Mentor.Api/CustomEntities/Request/UserRequest/RegisterUserRequest.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Microsoft.AspNetCore.Http; - -namespace DevnotMentor.Api.CustomEntities.Request.UserRequest -{ - public class RegisterUserRequest - { - [Required] - public string UserName { get; set; } - - [Required] - public string Email { get; set; } - - [Required] - public string Password { get; set; } - - [Required] - public string Name { get; set; } - - [Required] - public string SurName { get; set; } - - public string ProfileImageUrl { get; set; } - - [Required] - public IFormFile ProfileImage { get; set; } - } -} diff --git a/Devnot.Mentor.Api/CustomEntities/Request/UserRequest/UpdatePasswordRequest.cs b/Devnot.Mentor.Api/CustomEntities/Request/UserRequest/UpdatePasswordRequest.cs deleted file mode 100644 index 13ac684..0000000 --- a/Devnot.Mentor.Api/CustomEntities/Request/UserRequest/UpdatePasswordRequest.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace DevnotMentor.Api.CustomEntities.Request.UserRequest -{ - public class UpdatePasswordRequest - { - public int UserId { get; set; } - - [Required] - public string LastPassword { get; set; } - [Required] - public string NewPassword { get; set; } - } -} diff --git a/Devnot.Mentor.Api/CustomEntities/Request/UserRequest/UpdateUserRequest.cs b/Devnot.Mentor.Api/CustomEntities/Request/UserRequest/UpdateUserRequest.cs index 82c353b..41c01c0 100644 --- a/Devnot.Mentor.Api/CustomEntities/Request/UserRequest/UpdateUserRequest.cs +++ b/Devnot.Mentor.Api/CustomEntities/Request/UserRequest/UpdateUserRequest.cs @@ -1,20 +1,12 @@ using System.ComponentModel.DataAnnotations; -using Microsoft.AspNetCore.Http; namespace DevnotMentor.Api.CustomEntities.Request.UserRequest { public class UpdateUserRequest { - public int UserId { get; set; } - [Required] - public string Name { get; set; } - + public string FullName { get; set; } [Required] - public string SurName { get; set; } - - public string ProfileImageUrl { get; set; } - - public IFormFile ProfileImage { get; set; } + public string Email { get; set; } } } diff --git a/Devnot.Mentor.Api/CustomEntities/Request/UserRequest/UserLoginRequest.cs b/Devnot.Mentor.Api/CustomEntities/Request/UserRequest/UserLoginRequest.cs deleted file mode 100644 index cfd153e..0000000 --- a/Devnot.Mentor.Api/CustomEntities/Request/UserRequest/UserLoginRequest.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace DevnotMentor.Api.CustomEntities.Request.UserRequest -{ - public class UserLoginRequest - { - [Required] - public string UserName { get; set; } - - [Required] - public string Password { get; set; } - } -} diff --git a/Devnot.Mentor.Api/Entities/MentorDBContext.cs b/Devnot.Mentor.Api/Entities/MentorDBContext.cs index d0b190d..888aff8 100644 --- a/Devnot.Mentor.Api/Entities/MentorDBContext.cs +++ b/Devnot.Mentor.Api/Entities/MentorDBContext.cs @@ -1,6 +1,5 @@ using System; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata; namespace DevnotMentor.Api.Entities { @@ -292,23 +291,23 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity(entity => { - entity.Property(e => e.Name) - .HasMaxLength(50) - .IsUnicode(false); + entity.HasKey(p => p.Id); - entity.Property(e => e.Password) - .HasMaxLength(512) - .IsUnicode(false); + entity.HasIndex(p=>p.UserName).HasDatabaseName("unique_username").IsUnique(); - entity.Property(e => e.ProfileImageUrl) - .HasMaxLength(500) - .IsFixedLength(); + entity.HasIndex(e => e.Email).HasDatabaseName("unique_email").IsUnique(); - entity.Property(e => e.ProfileUrl) - .HasMaxLength(200) - .IsUnicode(false); + entity.Property(p=>p.Email) + .HasMaxLength(100) + .IsUnicode(false) + .IsRequired(false); + + entity.Property(e => e.UserName) + .HasMaxLength(39) + .IsUnicode(false) + .IsRequired(true); - entity.Property(e => e.SurName) + entity.Property(e => e.FullName) .HasMaxLength(50) .IsUnicode(false); @@ -317,10 +316,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .IsUnicode(false); entity.Property(e => e.TokenExpireDate).HasColumnType("datetime"); - - entity.Property(e => e.UserName) - .HasMaxLength(100) - .IsUnicode(false); }); OnModelCreatingPartial(modelBuilder); diff --git a/Devnot.Mentor.Api/Entities/User.cs b/Devnot.Mentor.Api/Entities/User.cs index e6ba355..e99d31d 100644 --- a/Devnot.Mentor.Api/Entities/User.cs +++ b/Devnot.Mentor.Api/Entities/User.cs @@ -12,19 +12,14 @@ public User() } public int Id { get; set; } + public string GitHubId { get; set; } public string UserName { get; set; } public string Email { get; set; } - public string Password { get; set; } - public string Name { get; set; } - public string SurName { get; set; } - public string ProfileImageUrl { get; set; } - public bool? UserNameConfirmed { get; set; } + public string FullName { get; set; } public int? UserState { get; set; } + public string ProfilePictureUrl { get; set; } public string Token { get; set; } public DateTime? TokenExpireDate { get; set; } - public string ProfileUrl { get; set; } - public DateTime? SecurityKeyExpiryDate { get; set; } - public Guid? SecurityKey { get; set; } public virtual ICollection Mentee { get; set; } public virtual ICollection Mentor { get; set; } diff --git a/Devnot.Mentor.Api/Helpers/MappingProfile.cs b/Devnot.Mentor.Api/Helpers/MappingProfile.cs index 546f1c9..09a958d 100644 --- a/Devnot.Mentor.Api/Helpers/MappingProfile.cs +++ b/Devnot.Mentor.Api/Helpers/MappingProfile.cs @@ -3,7 +3,6 @@ using DevnotMentor.Api.CustomEntities.Request.MenteeRequest; using DevnotMentor.Api.CustomEntities.Request.MentorRequest; using DevnotMentor.Api.Entities; -using DevnotMentor.Api.CustomEntities.Request.UserRequest; namespace DevnotMentor.Api.Helpers { @@ -26,9 +25,6 @@ public MappingProfile() .ForMember(dest => dest.MenteeTags, opt => opt.Ignore()) .ForMember(dest => dest.MenteeLinks, opt => opt.Ignore()) .ReverseMap(); - - - CreateMap(); } } } diff --git a/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs b/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs index ebdea75..21c4e9a 100644 --- a/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs +++ b/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs @@ -1,7 +1,4 @@ using DevnotMentor.Api.Entities; -using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; namespace DevnotMentor.Api.Repositories.Interfaces @@ -9,11 +6,9 @@ namespace DevnotMentor.Api.Repositories.Interfaces public interface IUserRepository : IRepository { Task GetById(int id); + Task GetByGitHubIdOrEmail(object identifier); Task GetByUserName(string userName); - Task Get(string userName, string hashedPassword); - Task Get(int userId, string hashedPassword); Task GetByEmail(string email); - Task Get(Guid securityKey); Task IsExists(int id); } } diff --git a/Devnot.Mentor.Api/Repositories/UserRepository.cs b/Devnot.Mentor.Api/Repositories/UserRepository.cs index 034876d..9e57d64 100644 --- a/Devnot.Mentor.Api/Repositories/UserRepository.cs +++ b/Devnot.Mentor.Api/Repositories/UserRepository.cs @@ -1,8 +1,6 @@ using DevnotMentor.Api.Entities; using DevnotMentor.Api.Repositories.Interfaces; using Microsoft.EntityFrameworkCore; -using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -19,26 +17,11 @@ public async Task GetById(int id) return await DbContext.User.Where(i => i.Id == id).FirstOrDefaultAsync(); } - public async Task Get(string userName, string hashedPassword) - { - return await DbContext.User.Where(u => u.UserName == userName && u.Password == hashedPassword).FirstOrDefaultAsync(); - } - - public async Task Get(int userId, string hashedPassword) - { - return await DbContext.User.Where(i => i.Id == userId && i.Password == hashedPassword).FirstOrDefaultAsync(); - } - public async Task GetByEmail(string email) { return await DbContext.User.Where(i => i.Email == email).FirstOrDefaultAsync(); } - public async Task Get(Guid securityKey) - { - return await DbContext.User.Where(i => i.SecurityKey == securityKey).FirstOrDefaultAsync(); - } - public async Task IsExists(int id) { return await DbContext.User.AnyAsync(i => i.Id == id); @@ -48,5 +31,10 @@ public async Task GetByUserName(string userName) { return await DbContext.User.Where(u => u.UserName == userName).FirstOrDefaultAsync(); } + + public async Task GetByGitHubIdOrEmail(object identifier) + { + return await DbContext.User.Where(u => u.GitHubId == identifier.ToString() || u.Email == identifier.ToString()).FirstOrDefaultAsync(); + } } } From ac7d5c667f5adcf0272169076207cec08f2f757c Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Thu, 15 Jul 2021 19:57:24 +0300 Subject: [PATCH 03/36] Add github auth --- .../Controllers/AuthController.cs | 38 ++++ .../Services/Interfaces/IUserService.cs | 8 +- Devnot.Mentor.Api/Services/UserService.cs | 178 +++--------------- 3 files changed, 61 insertions(+), 163 deletions(-) create mode 100644 Devnot.Mentor.Api/Controllers/AuthController.cs diff --git a/Devnot.Mentor.Api/Controllers/AuthController.cs b/Devnot.Mentor.Api/Controllers/AuthController.cs new file mode 100644 index 0000000..7621061 --- /dev/null +++ b/Devnot.Mentor.Api/Controllers/AuthController.cs @@ -0,0 +1,38 @@ +using System.Security.Claims; +using System.Threading.Tasks; +using DevnotMentor.Api.Services.Interfaces; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Mvc; + +namespace DevnotMentor.Api.Controllers +{ + public class AuthController : BaseController + { + private readonly IUserService userService; + + public AuthController(IUserService userService) + { + this.userService = userService; + } + + [Route("/auth-github")] + [HttpGet] + public IActionResult GitHubChallenge() + { + return Challenge(new AuthenticationProperties() { RedirectUri = "/auth-github-logics" }, "GitHub"); + } + + [Route("/auth-github-logics")] + [HttpGet] + public async Task GitHubLogics() + { + var claimsIdentity = User.Identity as ClaimsIdentity; + var id = claimsIdentity.FindFirst("github:id").Value; + var name = claimsIdentity.FindFirst("github:name").Value; + var login = claimsIdentity.FindFirst("github:login").Value; + var avatar = claimsIdentity.FindFirst("github:avatar").Value; + var result = await userService.GitHubAuth(id, name, login, avatar); + return result.Success ? Success(result) : BadRequest(result); + } + } +} \ No newline at end of file diff --git a/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs b/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs index 38edf37..e8a50d7 100644 --- a/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs +++ b/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs @@ -1,17 +1,11 @@ using System.Threading.Tasks; using DevnotMentor.Api.Common.Response; -using DevnotMentor.Api.CustomEntities.Request.UserRequest; using DevnotMentor.Api.CustomEntities.Response.UserResponse; namespace DevnotMentor.Api.Services.Interfaces { public interface IUserService { - Task> Login(UserLoginRequest request); - Task Register(RegisterUserRequest request); - Task ChangePassword(UpdatePasswordRequest request); - Task Update(UpdateUserRequest request); - Task RemindPassword(string email); - Task RemindPasswordComplete(CompleteRemindPasswordRequest request); + Task> GitHubAuth(string id, string name, string login, string avatar); } } diff --git a/Devnot.Mentor.Api/Services/UserService.cs b/Devnot.Mentor.Api/Services/UserService.cs index 51695ff..6fc60bf 100644 --- a/Devnot.Mentor.Api/Services/UserService.cs +++ b/Devnot.Mentor.Api/Services/UserService.cs @@ -3,191 +3,57 @@ using DevnotMentor.Api.Entities; using DevnotMentor.Api.Repositories.Interfaces; using DevnotMentor.Api.Services.Interfaces; -using DevnotMentor.Api.Utilities.Email; -using DevnotMentor.Api.Utilities.Security.Hash; using DevnotMentor.Api.Utilities.Security.Token; -using System; -using System.Collections.Generic; using System.Threading.Tasks; using DevnotMentor.Api.Common.Response; using DevnotMentor.Api.Configuration.Context; -using DevnotMentor.Api.CustomEntities.Dto; -using DevnotMentor.Api.CustomEntities.Request.UserRequest; using DevnotMentor.Api.CustomEntities.Response.UserResponse; -using DevnotMentor.Api.Utilities.File; +using DevnotMentor.Api.CustomEntities.Dto; namespace DevnotMentor.Api.Services { - //TODO: Aynı username ile kayıt yapılabiliyor public class UserService : BaseService, IUserService { private readonly IUserRepository userRepository; - private readonly IHashService hashService; private readonly ITokenService tokenService; - private readonly IMailService mailService; - - private readonly IFileService fileService; public UserService( IMapper mapper, - ITokenService tokenService, - IHashService hashService, - IMailService mailService, IUserRepository userRepository, ILoggerRepository loggerRepository, - IFileService fileService, + ITokenService tokenService, IDevnotConfigurationContext devnotConfigurationContext) : base(mapper, loggerRepository, devnotConfigurationContext) { this.tokenService = tokenService; - this.hashService = hashService; - this.mailService = mailService; this.userRepository = userRepository; - this.fileService = fileService; } - public async Task ChangePassword(UpdatePasswordRequest request) + public async Task> GitHubAuth(string githubId, string name, string login, string avatar) { - string hashedLastPassword = hashService.CreateHash(request.LastPassword); - - var currentUser = await userRepository.Get(request.UserId, hashedLastPassword); - - if (currentUser == null) - { - return new ErrorApiResponse(ResultMessage.NotFoundUser); - } - - currentUser.Password = hashService.CreateHash(request.NewPassword); - - userRepository.Update(currentUser); - - return new SuccessApiResponse(ResultMessage.Success); - } - - public async Task> Login(UserLoginRequest request) - { - var hashedPassword = hashService.CreateHash(request.Password); - - var user = await userRepository.Get(request.UserName, hashedPassword); - + var user = await userRepository.GetByGitHubIdOrEmail(githubId); if (user == null) { - return new ErrorApiResponse(data: null, ResultMessage.InvalidUserNameOrPassword); - } - - if (!user.UserNameConfirmed.HasValue || !user.UserNameConfirmed.Value) - { - return new ErrorApiResponse(data: null, ResultMessage.UserNameIsNotValidated); - } - - var tokenData = tokenService.CreateToken(user.Id, user.UserName); - - user.Token = tokenData.Token; - user.TokenExpireDate = tokenData.ExpiredDate; - - var mappedUser = mapper.Map(user); - - var loginResponse = new UserLoginResponse(mappedUser, user.Token, user.TokenExpireDate); - - return new SuccessApiResponse(data: loginResponse, ResultMessage.Success); - } - - public async Task Register(RegisterUserRequest request) - { - var checkFileResult = await fileService.InsertProfileImage(request.ProfileImage); - - if (!checkFileResult.IsSuccess) - { - return new ErrorApiResponse(data: default, checkFileResult.ErrorMessage); - } - - request.ProfileImageUrl = checkFileResult.RelativeFilePath; - request.Password = hashService.CreateHash(request.Password); - - userRepository.Create(mapper.Map(request)); - - return new SuccessApiResponse(ResultMessage.Success); - } - - public async Task RemindPassword(string email) - { - if (String.IsNullOrWhiteSpace(email)) - { - return new ErrorApiResponse(ResultMessage.InvalidModel); - } - - var currentUser = await userRepository.GetByEmail(email); - - if (currentUser == null) - { - return new ErrorApiResponse(ResultMessage.NotFoundUser); - } - - currentUser.SecurityKey = Guid.NewGuid(); - currentUser.SecurityKeyExpiryDate = DateTime.Now.AddHours(devnotConfigurationContext.SecurityKeyExpiryFromHours); - - userRepository.Update(currentUser); - - await SendRemindPasswordMail(currentUser); - - return new SuccessApiResponse(ResultMessage.Success); - } - - // TODO: İlerleyen zamanlarda template olarak veri tabanı ya da dosyadan okunulacak. - private async Task SendRemindPasswordMail(User user) - { - var to = new List { user.Email }; - string subject = "Devnot Mentor Programı | Parola Sıfırlama İsteÄŸi"; - string remindPasswordUrl = $"{devnotConfigurationContext.UpdatePasswordWebPageUrl}?securityKey={user.SecurityKey}"; - string body = $"Merhaba {user.Name} {user.SurName}, buradan parolanızı sıfırlayabilirsiniz."; - - await mailService.SendEmailAsync(to, subject, body); - } - - public async Task Update(UpdateUserRequest request) - { - var currentUser = await userRepository.GetById(request.UserId); - - if (request.ProfileImage != null) - { - var checkUploadedImageFileResult = await fileService.InsertProfileImage(request.ProfileImage); - - if (!checkUploadedImageFileResult.IsSuccess) + user = new User() { - return new ErrorApiResponse(data: default, checkUploadedImageFileResult.ErrorMessage); - } - - currentUser.ProfileImageUrl = checkUploadedImageFileResult.RelativeFilePath; - } - - currentUser.Name = request.Name; - currentUser.SurName = request.SurName; - - userRepository.Update(currentUser); - - return new SuccessApiResponse(ResultMessage.Success); - } - - public async Task RemindPasswordComplete(CompleteRemindPasswordRequest request) - { - var currentUser = await userRepository.Get(request.SecurityKey); - - if (currentUser == null) - { - return new ErrorApiResponse(ResultMessage.InvalidSecurityKey); - } - - if (currentUser.SecurityKeyExpiryDate < DateTime.Now) - { - return new ErrorApiResponse(ResultMessage.SecurityKeyExpiryDateAlreadyExpired); - } - - currentUser.SecurityKey = null; - currentUser.Password = hashService.CreateHash(request.Password); - - userRepository.Update(currentUser); + GitHubId = githubId, + FullName = name, + UserName = login, + ProfilePictureUrl = avatar + }; + + user = userRepository.Create(user); + } + + var tokenInfo = tokenService.CreateToken(user.Id, user.UserName); + user.Token = tokenInfo.Token; + user.TokenExpireDate = tokenInfo.ExpiredDate; + var mappedUser = mapper.Map(user); - return new SuccessApiResponse(ResultMessage.Success); + return new SuccessApiResponse(data: new UserLoginResponse( + mappedUser, + user.Token, + user.TokenExpireDate), ResultMessage.Success); } } } From b23e82959dcd16ae6f24fb91b999e509a6ebaf91 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Thu, 15 Jul 2021 23:34:08 +0300 Subject: [PATCH 04/36] Add: extensions for servicecollection --- .../Extensions/ServiceCollectionExtensions.cs | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs diff --git a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..67bbe86 --- /dev/null +++ b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,60 @@ +using System; +using DevnotMentor.Api.Configuration.Context; +using DevnotMentor.Api.Configuration.Environment; +using DevnotMentor.Api.Repositories; +using DevnotMentor.Api.Repositories.Interfaces; +using DevnotMentor.Api.Services; +using DevnotMentor.Api.Services.Interfaces; +using DevnotMentor.Api.Utilities.Email; +using DevnotMentor.Api.Utilities.Email.SmtpMail; +using DevnotMentor.Api.Utilities.File; +using DevnotMentor.Api.Utilities.File.Local; +using DevnotMentor.Api.Utilities.Security.Token; +using DevnotMentor.Api.Utilities.Security.Token.Jwt; +using Microsoft.Extensions.DependencyInjection; + +namespace DevnotMentor.Api.Helpers.Extensions +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddDefaultServices(this IServiceCollection services) + { + services.AddAutoMapper(typeof(Startup)); + + services.AddSingleton(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + + services.AddSingleton(); + //services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + + services.AddCustomSwagger(); + + return services; + } + public static IServiceCollection AddRepositories(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + return services; + } + } +} \ No newline at end of file From 80275ac385143a06499690deab82174e6c492792 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Fri, 16 Jul 2021 00:21:28 +0300 Subject: [PATCH 05/36] Update: returning token via github auth. Add todos --- .../Controllers/AuthController.cs | 19 +---- .../CustomEntities/Auth/GitHubResponse.cs | 10 +++ Devnot.Mentor.Api/DevnotMentor.Api.csproj | 1 + .../Extensions/ServiceCollectionExtensions.cs | 4 +- Devnot.Mentor.Api/Services/UserService.cs | 2 +- Devnot.Mentor.Api/Startup.cs | 82 ++++++------------- 6 files changed, 42 insertions(+), 76 deletions(-) create mode 100644 Devnot.Mentor.Api/CustomEntities/Auth/GitHubResponse.cs diff --git a/Devnot.Mentor.Api/Controllers/AuthController.cs b/Devnot.Mentor.Api/Controllers/AuthController.cs index 7621061..004331c 100644 --- a/Devnot.Mentor.Api/Controllers/AuthController.cs +++ b/Devnot.Mentor.Api/Controllers/AuthController.cs @@ -1,5 +1,3 @@ -using System.Security.Claims; -using System.Threading.Tasks; using DevnotMentor.Api.Services.Interfaces; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc; @@ -15,24 +13,11 @@ public AuthController(IUserService userService) this.userService = userService; } - [Route("/auth-github")] + [Route("/auth/github")] [HttpGet] public IActionResult GitHubChallenge() { - return Challenge(new AuthenticationProperties() { RedirectUri = "/auth-github-logics" }, "GitHub"); - } - - [Route("/auth-github-logics")] - [HttpGet] - public async Task GitHubLogics() - { - var claimsIdentity = User.Identity as ClaimsIdentity; - var id = claimsIdentity.FindFirst("github:id").Value; - var name = claimsIdentity.FindFirst("github:name").Value; - var login = claimsIdentity.FindFirst("github:login").Value; - var avatar = claimsIdentity.FindFirst("github:avatar").Value; - var result = await userService.GitHubAuth(id, name, login, avatar); - return result.Success ? Success(result) : BadRequest(result); + return Challenge(new AuthenticationProperties() { RedirectUri = "/" }, "GitHub"); } } } \ No newline at end of file diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/GitHubResponse.cs b/Devnot.Mentor.Api/CustomEntities/Auth/GitHubResponse.cs new file mode 100644 index 0000000..909d8f2 --- /dev/null +++ b/Devnot.Mentor.Api/CustomEntities/Auth/GitHubResponse.cs @@ -0,0 +1,10 @@ +namespace DevnotMentor.Api.CustomEntities.Auth +{ + public class GitHubResponse + { + public string login { get; set; } + public string id { get; set; } + public string avatar_url { get; set; } + public string name { get; set; } + } +} \ No newline at end of file diff --git a/Devnot.Mentor.Api/DevnotMentor.Api.csproj b/Devnot.Mentor.Api/DevnotMentor.Api.csproj index c8c23b3..175beb2 100644 --- a/Devnot.Mentor.Api/DevnotMentor.Api.csproj +++ b/Devnot.Mentor.Api/DevnotMentor.Api.csproj @@ -22,6 +22,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs index 67bbe86..b4d6c5b 100644 --- a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs +++ b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using System; +using DevnotMentor.Api.ActionFilters; using DevnotMentor.Api.Configuration.Context; using DevnotMentor.Api.Configuration.Environment; using DevnotMentor.Api.Repositories; @@ -17,9 +18,8 @@ namespace DevnotMentor.Api.Helpers.Extensions { public static class ServiceCollectionExtensions { - public static IServiceCollection AddDefaultServices(this IServiceCollection services) + public static IServiceCollection AddCustomServices(this IServiceCollection services) { - services.AddAutoMapper(typeof(Startup)); services.AddSingleton(); diff --git a/Devnot.Mentor.Api/Services/UserService.cs b/Devnot.Mentor.Api/Services/UserService.cs index 6fc60bf..8a87a1b 100644 --- a/Devnot.Mentor.Api/Services/UserService.cs +++ b/Devnot.Mentor.Api/Services/UserService.cs @@ -30,7 +30,7 @@ public UserService( } public async Task> GitHubAuth(string githubId, string name, string login, string avatar) - { + { //todo: seperate jobs(creating new user - creating token for user) var user = await userRepository.GetByGitHubIdOrEmail(githubId); if (user == null) { diff --git a/Devnot.Mentor.Api/Startup.cs b/Devnot.Mentor.Api/Startup.cs index f3d7a50..c57f612 100644 --- a/Devnot.Mentor.Api/Startup.cs +++ b/Devnot.Mentor.Api/Startup.cs @@ -1,6 +1,5 @@ using System; using DevnotMentor.Api.Entities; -using DevnotMentor.Api.Services; using DevnotMentor.Api.Services.Interfaces; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -9,26 +8,17 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using DevnotMentor.Api.ActionFilters; -using DevnotMentor.Api.Utilities.Security.Token; -using DevnotMentor.Api.Configuration.Context; using DevnotMentor.Api.Configuration.Environment; using DevnotMentor.Api.Middlewares; -using DevnotMentor.Api.Utilities.Security.Hash; -using DevnotMentor.Api.Utilities.Security.Hash.Sha256; -using DevnotMentor.Api.Utilities.Email; -using DevnotMentor.Api.Repositories; -using DevnotMentor.Api.Repositories.Interfaces; -using DevnotMentor.Api.Utilities.Email.SmtpMail; -using DevnotMentor.Api.Utilities.File; -using DevnotMentor.Api.Utilities.File.Local; -using DevnotMentor.Api.Utilities.Security.Token.Jwt; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Authentication.OAuth; using System.Net.Http; using System.Net.Http.Headers; using Microsoft.AspNetCore.Authentication.Cookies; -using System.Text.Json; -using Microsoft.AspNetCore.Authentication; +using System.Threading.Tasks; +using Newtonsoft.Json; +using DevnotMentor.Api.CustomEntities.Auth; +using DevnotMentor.Api.Helpers.Extensions; namespace DevnotMentor.Api { @@ -36,7 +26,7 @@ public class Startup { public Startup(IConfiguration configuration) { - Configuration = configuration; + this.Configuration = configuration; } public IConfiguration Configuration { get; } @@ -49,24 +39,17 @@ public void ConfigureServices(IServiceCollection services) services.AddControllers(); services.AddAutoMapper(typeof(Startup)); - - services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie() + + services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie() //todo: remove cookie and implement jwt bearer .AddGitHub(options => { options.ClientId = EnvironmentService.StaticConfiguration["GitHub:Client:ID"]; options.ClientSecret = EnvironmentService.StaticConfiguration["GitHub:Client:Secret"]; - options.CallbackPath = new PathString("/auth-github/"); + options.CallbackPath = new PathString("/auth/github/"); options.AuthorizationEndpoint = "https://github.com/login/oauth/authorize"; options.TokenEndpoint = "https://github.com/login/oauth/access_token"; options.UserInformationEndpoint = "https://api.github.com/user"; - options.ClaimActions.MapJsonKey("github:id", "id"); - options.ClaimActions.MapJsonKey("github:name", "name"); - options.ClaimActions.MapJsonKey("github:login", "login"); - options.ClaimActions.MapJsonKey("github:avatar", "avatar_url"); - - options.SaveTokens = true; - options.Events = new OAuthEvents { OnCreatingTicket = async ctx => @@ -77,40 +60,14 @@ public void ConfigureServices(IServiceCollection services) var response = await ctx.Backchannel.SendAsync(request, ctx.HttpContext.RequestAborted); response.EnsureSuccessStatusCode(); - var jsonDocumentFromResponse = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); - ctx.RunClaimActions(jsonDocumentFromResponse.RootElement); + var gitHubResponse = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + await GitHubAuthAsync(gitHubResponse, ctx.HttpContext); } }; }); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - - services.AddScoped(); - services.AddScoped(); - - services.AddSingleton(); - services.AddSingleton(); - - services.AddSingleton(); - services.AddSingleton(); - - #region Repositories - - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - - #endregion + services.AddCustomServices(); + services.AddRepositories(); services.AddCors(options => { @@ -133,8 +90,21 @@ public void ConfigureServices(IServiceCollection services) { options.JsonSerializerOptions.IgnoreNullValues = true; }); + } - services.AddCustomSwagger(); + public static async Task GitHubAuthAsync(GitHubResponse gitHub, HttpContext httpContext) + { //todo: find another way to return token + var userService = httpContext.RequestServices.GetService(); + var authResponse = await userService.GitHubAuth(gitHub.id, gitHub.name, gitHub.login, gitHub.avatar_url); + if (authResponse.Success) + { + httpContext.Response.Headers.Add("auth-token", authResponse.Data.Token); + httpContext.Response.Headers.Add("auth-token-expiry-date", authResponse.Data.TokenExpiryDate.ToString()); + } + else + { + httpContext.Response.StatusCode = 500; + } } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) From 23c62c077dd9aee85d75f07fa3f1290520bdbb75 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Sat, 17 Jul 2021 21:13:10 +0300 Subject: [PATCH 06/36] Changes: Drop columns from User entity --- Devnot.Mentor.Api/CustomEntities/Dto/UserDto.cs | 9 +++------ Devnot.Mentor.Api/Entities/MentorDBContext.cs | 6 ------ Devnot.Mentor.Api/Entities/User.cs | 3 --- Devnot.Mentor.Api/Services/UserService.cs | 10 +++------- 4 files changed, 6 insertions(+), 22 deletions(-) diff --git a/Devnot.Mentor.Api/CustomEntities/Dto/UserDto.cs b/Devnot.Mentor.Api/CustomEntities/Dto/UserDto.cs index 730f159..f78fea9 100644 --- a/Devnot.Mentor.Api/CustomEntities/Dto/UserDto.cs +++ b/Devnot.Mentor.Api/CustomEntities/Dto/UserDto.cs @@ -5,13 +5,10 @@ namespace DevnotMentor.Api.CustomEntities.Dto public class UserDto { public int Id { get; set; } + public string GitHubId { get; set; } public string UserName { get; set; } - public string Name { get; set; } - public string SurName { get; set; } - public string ProfileImageUrl { get; set; } - public bool? UserNameConfirmed { get; set; } - public int? UserState { get; set; } - public string ProfileUrl { get; set; } + public string FullName { get; set; } + public string ProfilePictureUrl { get; set; } public ICollection Mentees { get; set; } public ICollection Mentors { get; set; } diff --git a/Devnot.Mentor.Api/Entities/MentorDBContext.cs b/Devnot.Mentor.Api/Entities/MentorDBContext.cs index 888aff8..0cd3ce2 100644 --- a/Devnot.Mentor.Api/Entities/MentorDBContext.cs +++ b/Devnot.Mentor.Api/Entities/MentorDBContext.cs @@ -310,12 +310,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.Property(e => e.FullName) .HasMaxLength(50) .IsUnicode(false); - - entity.Property(e => e.Token) - .HasMaxLength(500) - .IsUnicode(false); - - entity.Property(e => e.TokenExpireDate).HasColumnType("datetime"); }); OnModelCreatingPartial(modelBuilder); diff --git a/Devnot.Mentor.Api/Entities/User.cs b/Devnot.Mentor.Api/Entities/User.cs index e99d31d..77f4a58 100644 --- a/Devnot.Mentor.Api/Entities/User.cs +++ b/Devnot.Mentor.Api/Entities/User.cs @@ -16,10 +16,7 @@ public User() public string UserName { get; set; } public string Email { get; set; } public string FullName { get; set; } - public int? UserState { get; set; } public string ProfilePictureUrl { get; set; } - public string Token { get; set; } - public DateTime? TokenExpireDate { get; set; } public virtual ICollection Mentee { get; set; } public virtual ICollection Mentor { get; set; } diff --git a/Devnot.Mentor.Api/Services/UserService.cs b/Devnot.Mentor.Api/Services/UserService.cs index 8a87a1b..05be1b2 100644 --- a/Devnot.Mentor.Api/Services/UserService.cs +++ b/Devnot.Mentor.Api/Services/UserService.cs @@ -41,19 +41,15 @@ public async Task> GitHubAuth(string githubId, st UserName = login, ProfilePictureUrl = avatar }; - + user = userRepository.Create(user); } var tokenInfo = tokenService.CreateToken(user.Id, user.UserName); - user.Token = tokenInfo.Token; - user.TokenExpireDate = tokenInfo.ExpiredDate; + var mappedUser = mapper.Map(user); - return new SuccessApiResponse(data: new UserLoginResponse( - mappedUser, - user.Token, - user.TokenExpireDate), ResultMessage.Success); + return new SuccessApiResponse(data: new UserLoginResponse(mappedUser, tokenInfo.Token, tokenInfo.ExpiredDate), ResultMessage.Success); } } } From 50cf37aee7c8bb0bddefa5201a096e617227c27d Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Sun, 18 Jul 2021 00:59:52 +0300 Subject: [PATCH 07/36] Preparation for google oauth --- .../CustomEntities/Auth/GoogleResponse.cs | 10 ++++++++++ Devnot.Mentor.Api/Entities/MentorDBContext.cs | 16 +++++++++++++--- Devnot.Mentor.Api/Entities/User.cs | 4 ++-- 3 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 Devnot.Mentor.Api/CustomEntities/Auth/GoogleResponse.cs diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/GoogleResponse.cs b/Devnot.Mentor.Api/CustomEntities/Auth/GoogleResponse.cs new file mode 100644 index 0000000..183cd4e --- /dev/null +++ b/Devnot.Mentor.Api/CustomEntities/Auth/GoogleResponse.cs @@ -0,0 +1,10 @@ +namespace DevnotMentor.Api.CustomEntities.Auth +{ + public class GoogleResponse + { + public string id { get; set; } + public string email { get; set; } + public string name { get; set; } + public string picture { get; set; } + } +} \ No newline at end of file diff --git a/Devnot.Mentor.Api/Entities/MentorDBContext.cs b/Devnot.Mentor.Api/Entities/MentorDBContext.cs index 0cd3ce2..94ffd8d 100644 --- a/Devnot.Mentor.Api/Entities/MentorDBContext.cs +++ b/Devnot.Mentor.Api/Entities/MentorDBContext.cs @@ -293,15 +293,25 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { entity.HasKey(p => p.Id); - entity.HasIndex(p=>p.UserName).HasDatabaseName("unique_username").IsUnique(); + entity.HasIndex(p => p.UserName).HasDatabaseName("unique_username").IsUnique(); entity.HasIndex(e => e.Email).HasDatabaseName("unique_email").IsUnique(); - entity.Property(p=>p.Email) - .HasMaxLength(100) + entity.Property(p => p.GitHubId) + .HasMaxLength(1000) // todo: learn max length from github api + .IsUnicode(false) + .IsRequired(false); + + entity.Property(p => p.GoogleId) + .HasMaxLength(1000) // todo: learn max length from google api .IsUnicode(false) .IsRequired(false); + entity.Property(p => p.Email) + .HasMaxLength(100) + .IsUnicode(false) + .IsRequired(false); + entity.Property(e => e.UserName) .HasMaxLength(39) .IsUnicode(false) diff --git a/Devnot.Mentor.Api/Entities/User.cs b/Devnot.Mentor.Api/Entities/User.cs index 77f4a58..ad8301f 100644 --- a/Devnot.Mentor.Api/Entities/User.cs +++ b/Devnot.Mentor.Api/Entities/User.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace DevnotMentor.Api.Entities { @@ -13,6 +12,7 @@ public User() public int Id { get; set; } public string GitHubId { get; set; } + public string GoogleId { get; set; } public string UserName { get; set; } public string Email { get; set; } public string FullName { get; set; } From 99d29f14daaf875c4f920a70e186428637af3920 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Sun, 18 Jul 2021 01:01:07 +0300 Subject: [PATCH 08/36] Add new two model: OAuthUser and OAuthType. Change a method name --- Devnot.Mentor.Api/CustomEntities/Auth/OAuthType.cs | 8 ++++++++ Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs | 12 ++++++++++++ .../Repositories/Interfaces/IUserRepository.cs | 2 +- Devnot.Mentor.Api/Repositories/UserRepository.cs | 2 +- 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 Devnot.Mentor.Api/CustomEntities/Auth/OAuthType.cs create mode 100644 Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthType.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthType.cs new file mode 100644 index 0000000..6950659 --- /dev/null +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthType.cs @@ -0,0 +1,8 @@ +namespace DevnotMentor.Api.CustomEntities.Auth +{ + public enum OAuthType : int + { + Google, + GitHub + } +} diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs new file mode 100644 index 0000000..1fd32ec --- /dev/null +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs @@ -0,0 +1,12 @@ +namespace DevnotMentor.Api.CustomEntities.Auth +{ + public class OAuthUser + { + public string Id { get; set; } + public string IdentifierProperty { get; set; } // Google: Email, GitHub: UserName + + public string FullName { get; set; } + public string ProfilePictureUrl { get; set; } + public OAuthType Type { get; set; } + } +} \ No newline at end of file diff --git a/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs b/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs index 21c4e9a..d35349a 100644 --- a/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs +++ b/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs @@ -6,7 +6,7 @@ namespace DevnotMentor.Api.Repositories.Interfaces public interface IUserRepository : IRepository { Task GetById(int id); - Task GetByGitHubIdOrEmail(object identifier); + Task GetByUserNameOrEmailAsync(object identifier); Task GetByUserName(string userName); Task GetByEmail(string email); Task IsExists(int id); diff --git a/Devnot.Mentor.Api/Repositories/UserRepository.cs b/Devnot.Mentor.Api/Repositories/UserRepository.cs index 9e57d64..e973f19 100644 --- a/Devnot.Mentor.Api/Repositories/UserRepository.cs +++ b/Devnot.Mentor.Api/Repositories/UserRepository.cs @@ -32,7 +32,7 @@ public async Task GetByUserName(string userName) return await DbContext.User.Where(u => u.UserName == userName).FirstOrDefaultAsync(); } - public async Task GetByGitHubIdOrEmail(object identifier) + public async Task GetByUserNameOrEmailAsync(object identifier) { return await DbContext.User.Where(u => u.GitHubId == identifier.ToString() || u.Email == identifier.ToString()).FirstOrDefaultAsync(); } From 53ba0c958b5a37d140d91219418b24e7d607f2d6 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Sun, 18 Jul 2021 01:13:14 +0300 Subject: [PATCH 09/36] Seperate Auth metot jobs. --- .../Services/Interfaces/IUserService.cs | 3 +- Devnot.Mentor.Api/Services/UserService.cs | 36 +++++++++++++------ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs b/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs index e8a50d7..c7cfa19 100644 --- a/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs +++ b/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs @@ -1,11 +1,12 @@ using System.Threading.Tasks; using DevnotMentor.Api.Common.Response; +using DevnotMentor.Api.CustomEntities.Auth; using DevnotMentor.Api.CustomEntities.Response.UserResponse; namespace DevnotMentor.Api.Services.Interfaces { public interface IUserService { - Task> GitHubAuth(string id, string name, string login, string avatar); + Task> SignInAsync(OAuthUser oAuthUser); } } diff --git a/Devnot.Mentor.Api/Services/UserService.cs b/Devnot.Mentor.Api/Services/UserService.cs index 05be1b2..efcfb4a 100644 --- a/Devnot.Mentor.Api/Services/UserService.cs +++ b/Devnot.Mentor.Api/Services/UserService.cs @@ -9,6 +9,7 @@ using DevnotMentor.Api.Configuration.Context; using DevnotMentor.Api.CustomEntities.Response.UserResponse; using DevnotMentor.Api.CustomEntities.Dto; +using DevnotMentor.Api.CustomEntities.Auth; namespace DevnotMentor.Api.Services { @@ -29,22 +30,37 @@ public UserService( this.userRepository = userRepository; } - public async Task> GitHubAuth(string githubId, string name, string login, string avatar) - { //todo: seperate jobs(creating new user - creating token for user) - var user = await userRepository.GetByGitHubIdOrEmail(githubId); + private async Task GetOrCreateForOAuthUserAsync(OAuthUser oAuthUser) + { + var user = await userRepository.GetByUserNameOrEmailAsync(oAuthUser.IdentifierProperty); + if (user == null) { - user = new User() + switch (oAuthUser.Type) { - GitHubId = githubId, - FullName = name, - UserName = login, - ProfilePictureUrl = avatar - }; + case OAuthType.Google: + user.GoogleId = oAuthUser.Id; + user.Email = oAuthUser.IdentifierProperty; + user.UserName = oAuthUser.IdentifierProperty; + break; + case OAuthType.GitHub: + user.GitHubId = oAuthUser.Id; + user.UserName = oAuthUser.IdentifierProperty; + break; + } - user = userRepository.Create(user); + user.FullName = oAuthUser.FullName; + user.ProfilePictureUrl = oAuthUser.ProfilePictureUrl; + return userRepository.Create(user); } + return user; + } + + public async Task> SignInAsync(OAuthUser oAuthUser) + { + User user = await GetOrCreateForOAuthUserAsync(oAuthUser); + var tokenInfo = tokenService.CreateToken(user.Id, user.UserName); var mappedUser = mapper.Map(user); From 258fba88abcb0f51184db0c1429874d510e783d4 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Sun, 18 Jul 2021 01:14:03 +0300 Subject: [PATCH 10/36] Implement Google Auth. Add CustomAuth metot for services --- .../appsettings.development.json | 6 ++ .../Configuration/appsettings.production.json | 6 ++ .../Configuration/appsettings.test.json | 6 ++ .../Controllers/AuthController.cs | 6 ++ Devnot.Mentor.Api/DevnotMentor.Api.csproj | 1 + .../Extensions/ServiceCollectionExtensions.cs | 102 ++++++++++++++++-- Devnot.Mentor.Api/Startup.cs | 50 +-------- 7 files changed, 119 insertions(+), 58 deletions(-) diff --git a/Devnot.Mentor.Api/Configuration/appsettings.development.json b/Devnot.Mentor.Api/Configuration/appsettings.development.json index b626ae2..aac1981 100644 --- a/Devnot.Mentor.Api/Configuration/appsettings.development.json +++ b/Devnot.Mentor.Api/Configuration/appsettings.development.json @@ -8,6 +8,12 @@ "Secret": "3110f6222264f39f04bda81740c32ca52c9fb814" } }, + "Google": { + "Client": { + "ID": "879674287725-fdm9glef0qbscorrb5okplsg2fp92kpv.apps.googleusercontent.com", + "Secret": "xjuEDtdOTEm1fnYMGAe0IeSH" + } + }, "JWT": { "Secret": "c9J2Ff5Uh2ISscBYxC4NJNw7SMB9FGTQ", "SecretExpirationInMinutes": 1440, diff --git a/Devnot.Mentor.Api/Configuration/appsettings.production.json b/Devnot.Mentor.Api/Configuration/appsettings.production.json index 12c21fd..4da8be3 100644 --- a/Devnot.Mentor.Api/Configuration/appsettings.production.json +++ b/Devnot.Mentor.Api/Configuration/appsettings.production.json @@ -8,6 +8,12 @@ "Secret": "-" } }, + "Google": { + "Client": { + "ID": "-", + "Secret": "-" + } + }, "JWT": { "Secret": "c9J2Ff5Uh2ISscBYxC4NJNw7SMB9FGTQ", "SecretExpirationInMinutes": 1440, diff --git a/Devnot.Mentor.Api/Configuration/appsettings.test.json b/Devnot.Mentor.Api/Configuration/appsettings.test.json index 12c21fd..4da8be3 100644 --- a/Devnot.Mentor.Api/Configuration/appsettings.test.json +++ b/Devnot.Mentor.Api/Configuration/appsettings.test.json @@ -8,6 +8,12 @@ "Secret": "-" } }, + "Google": { + "Client": { + "ID": "-", + "Secret": "-" + } + }, "JWT": { "Secret": "c9J2Ff5Uh2ISscBYxC4NJNw7SMB9FGTQ", "SecretExpirationInMinutes": 1440, diff --git a/Devnot.Mentor.Api/Controllers/AuthController.cs b/Devnot.Mentor.Api/Controllers/AuthController.cs index 004331c..a30a56b 100644 --- a/Devnot.Mentor.Api/Controllers/AuthController.cs +++ b/Devnot.Mentor.Api/Controllers/AuthController.cs @@ -19,5 +19,11 @@ public IActionResult GitHubChallenge() { return Challenge(new AuthenticationProperties() { RedirectUri = "/" }, "GitHub"); } + [Route("/auth/google")] + [HttpGet] + public IActionResult GoogleChallenge() + { + return Challenge(new AuthenticationProperties() { RedirectUri = "/" }, "Google"); + } } } \ No newline at end of file diff --git a/Devnot.Mentor.Api/DevnotMentor.Api.csproj b/Devnot.Mentor.Api/DevnotMentor.Api.csproj index 175beb2..c2699a5 100644 --- a/Devnot.Mentor.Api/DevnotMentor.Api.csproj +++ b/Devnot.Mentor.Api/DevnotMentor.Api.csproj @@ -10,6 +10,7 @@ + diff --git a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs index b4d6c5b..1434cfd 100644 --- a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs +++ b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs @@ -1,18 +1,24 @@ using System; +using DevnotMentor.Api.Services.Interfaces; +using Microsoft.Extensions.DependencyInjection; using DevnotMentor.Api.ActionFilters; -using DevnotMentor.Api.Configuration.Context; using DevnotMentor.Api.Configuration.Environment; -using DevnotMentor.Api.Repositories; -using DevnotMentor.Api.Repositories.Interfaces; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Authentication.OAuth; +using Microsoft.AspNetCore.Authentication.Cookies; +using DevnotMentor.Api.CustomEntities.Auth; using DevnotMentor.Api.Services; -using DevnotMentor.Api.Services.Interfaces; using DevnotMentor.Api.Utilities.Email; -using DevnotMentor.Api.Utilities.Email.SmtpMail; -using DevnotMentor.Api.Utilities.File; -using DevnotMentor.Api.Utilities.File.Local; using DevnotMentor.Api.Utilities.Security.Token; +using DevnotMentor.Api.Configuration.Context; +using DevnotMentor.Api.Repositories.Interfaces; +using DevnotMentor.Api.Repositories; +using DevnotMentor.Api.Utilities.Email.SmtpMail; using DevnotMentor.Api.Utilities.Security.Token.Jwt; -using Microsoft.Extensions.DependencyInjection; +using System.Threading.Tasks; +using System.Net.Http; +using System.Net.Http.Headers; +using Newtonsoft.Json; namespace DevnotMentor.Api.Helpers.Extensions { @@ -20,7 +26,7 @@ public static class ServiceCollectionExtensions { public static IServiceCollection AddCustomServices(this IServiceCollection services) { - + services.AddSingleton(); services.AddScoped(); @@ -28,7 +34,7 @@ public static IServiceCollection AddCustomServices(this IServiceCollection servi services.AddScoped(); services.AddScoped(); - services.AddScoped(); + //services.AddScoped(); services.AddSingleton(); //services.AddSingleton(); @@ -56,5 +62,81 @@ public static IServiceCollection AddRepositories(this IServiceCollection service return services; } + + public static IServiceCollection AddCustomAuthentication(this IServiceCollection services) + { + services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie() + .AddGitHub(options => + { + options.ClientId = EnvironmentService.StaticConfiguration["GitHub:Client:ID"]; + options.ClientSecret = EnvironmentService.StaticConfiguration["GitHub:Client:Secret"]; + options.CallbackPath = new PathString("/auth/github/"); + + options.Events = new OAuthEvents + { + OnCreatingTicket = async ctx => { var oAuthUser = await GetOAuthUser(OAuthType.GitHub, ctx); await SignIn(oAuthUser, ctx.HttpContext); } + }; + }) + .AddGoogle(options => + { + options.ClientId = EnvironmentService.StaticConfiguration["Google:Client:ID"]; + options.ClientSecret = EnvironmentService.StaticConfiguration["Google:Client:Secret"]; + options.CallbackPath = new PathString("/auth/google/"); + + options.Events = new OAuthEvents + { + OnCreatingTicket = async ctx => { var oAuthUser = await GetOAuthUser(OAuthType.Google, ctx); await SignIn(oAuthUser, ctx.HttpContext); } + }; + }); + + return services; + } + + private static async Task GetOAuthUser(OAuthType oAuthType, OAuthCreatingTicketContext ctx) + { + var request = new HttpRequestMessage(HttpMethod.Get, ctx.Options.UserInformationEndpoint); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", ctx.AccessToken); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var response = await ctx.Backchannel.SendAsync(request, ctx.HttpContext.RequestAborted); + response.EnsureSuccessStatusCode(); + + var responseContent = await response.Content.ReadAsStringAsync(); + + if (oAuthType == OAuthType.Google) + { + var googleResponse = JsonConvert.DeserializeObject(responseContent); + return new OAuthUser + { + Id = googleResponse.id, + IdentifierProperty = googleResponse.email, + FullName = googleResponse.name, + ProfilePictureUrl = googleResponse.picture, + }; + } + + var gitHubResponse = JsonConvert.DeserializeObject(responseContent); + return new OAuthUser + { + Id = gitHubResponse.id, + IdentifierProperty = gitHubResponse.login, + FullName = gitHubResponse.name, + ProfilePictureUrl = gitHubResponse.avatar_url, + }; + } + + private static async Task SignIn(OAuthUser oAuthUser, HttpContext httpContext) + { + var userService = httpContext.RequestServices.GetService(); + var signInResponse = await userService.SignInAsync(oAuthUser); + if (signInResponse.Success) + { + httpContext.Response.Headers.Add("auth-token", signInResponse.Data.Token); + httpContext.Response.Headers.Add("auth-token-expiry-date", signInResponse.Data.TokenExpiryDate.ToString()); + } + else + { + httpContext.Response.StatusCode = 500; + } + } } } \ No newline at end of file diff --git a/Devnot.Mentor.Api/Startup.cs b/Devnot.Mentor.Api/Startup.cs index c57f612..873e485 100644 --- a/Devnot.Mentor.Api/Startup.cs +++ b/Devnot.Mentor.Api/Startup.cs @@ -1,6 +1,5 @@ using System; using DevnotMentor.Api.Entities; -using DevnotMentor.Api.Services.Interfaces; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; @@ -11,13 +10,6 @@ using DevnotMentor.Api.Configuration.Environment; using DevnotMentor.Api.Middlewares; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Authentication.OAuth; -using System.Net.Http; -using System.Net.Http.Headers; -using Microsoft.AspNetCore.Authentication.Cookies; -using System.Threading.Tasks; -using Newtonsoft.Json; -using DevnotMentor.Api.CustomEntities.Auth; using DevnotMentor.Api.Helpers.Extensions; namespace DevnotMentor.Api @@ -40,33 +32,8 @@ public void ConfigureServices(IServiceCollection services) services.AddControllers(); services.AddAutoMapper(typeof(Startup)); - services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie() //todo: remove cookie and implement jwt bearer - .AddGitHub(options => - { - options.ClientId = EnvironmentService.StaticConfiguration["GitHub:Client:ID"]; - options.ClientSecret = EnvironmentService.StaticConfiguration["GitHub:Client:Secret"]; - options.CallbackPath = new PathString("/auth/github/"); - options.AuthorizationEndpoint = "https://github.com/login/oauth/authorize"; - options.TokenEndpoint = "https://github.com/login/oauth/access_token"; - options.UserInformationEndpoint = "https://api.github.com/user"; - - options.Events = new OAuthEvents - { - OnCreatingTicket = async ctx => - { - var request = new HttpRequestMessage(HttpMethod.Get, ctx.Options.UserInformationEndpoint); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", ctx.AccessToken); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - var response = await ctx.Backchannel.SendAsync(request, ctx.HttpContext.RequestAborted); - response.EnsureSuccessStatusCode(); - - var gitHubResponse = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - await GitHubAuthAsync(gitHubResponse, ctx.HttpContext); - } - }; - }); - services.AddCustomServices(); + services.AddCustomAuthentication(); services.AddRepositories(); services.AddCors(options => @@ -92,20 +59,7 @@ public void ConfigureServices(IServiceCollection services) }); } - public static async Task GitHubAuthAsync(GitHubResponse gitHub, HttpContext httpContext) - { //todo: find another way to return token - var userService = httpContext.RequestServices.GetService(); - var authResponse = await userService.GitHubAuth(gitHub.id, gitHub.name, gitHub.login, gitHub.avatar_url); - if (authResponse.Success) - { - httpContext.Response.Headers.Add("auth-token", authResponse.Data.Token); - httpContext.Response.Headers.Add("auth-token-expiry-date", authResponse.Data.TokenExpiryDate.ToString()); - } - else - { - httpContext.Response.StatusCode = 500; - } - } + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { From 3e2f614853f194260af73ff48ba82cb9f809dab2 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Sun, 18 Jul 2021 01:25:16 +0300 Subject: [PATCH 11/36] Fix miss passing oauth type. --- .../Helpers/Extensions/ServiceCollectionExtensions.cs | 2 ++ Devnot.Mentor.Api/Services/UserService.cs | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs index 1434cfd..dd0ae9d 100644 --- a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs +++ b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs @@ -111,6 +111,7 @@ private static async Task GetOAuthUser(OAuthType oAuthType, OAuthCrea IdentifierProperty = googleResponse.email, FullName = googleResponse.name, ProfilePictureUrl = googleResponse.picture, + Type = oAuthType, }; } @@ -121,6 +122,7 @@ private static async Task GetOAuthUser(OAuthType oAuthType, OAuthCrea IdentifierProperty = gitHubResponse.login, FullName = gitHubResponse.name, ProfilePictureUrl = gitHubResponse.avatar_url, + Type = oAuthType, }; } diff --git a/Devnot.Mentor.Api/Services/UserService.cs b/Devnot.Mentor.Api/Services/UserService.cs index efcfb4a..bfe3cdf 100644 --- a/Devnot.Mentor.Api/Services/UserService.cs +++ b/Devnot.Mentor.Api/Services/UserService.cs @@ -36,6 +36,7 @@ private async Task GetOrCreateForOAuthUserAsync(OAuthUser oAuthUser) if (user == null) { + user = new User(); switch (oAuthUser.Type) { case OAuthType.Google: @@ -43,10 +44,14 @@ private async Task GetOrCreateForOAuthUserAsync(OAuthUser oAuthUser) user.Email = oAuthUser.IdentifierProperty; user.UserName = oAuthUser.IdentifierProperty; break; + case OAuthType.GitHub: user.GitHubId = oAuthUser.Id; user.UserName = oAuthUser.IdentifierProperty; break; + + default: + break; } user.FullName = oAuthUser.FullName; From cd7d9d06c74919ce723678413b2d0a18a9382ea9 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Sun, 18 Jul 2021 01:47:17 +0300 Subject: [PATCH 12/36] Move oauth methods to different class --- .../Extensions/ServiceCollectionExtensions.cs | 66 ++++--------------- Devnot.Mentor.Api/Utilities/OAuthService.cs | 64 ++++++++++++++++++ 2 files changed, 75 insertions(+), 55 deletions(-) create mode 100644 Devnot.Mentor.Api/Utilities/OAuthService.cs diff --git a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs index dd0ae9d..4111c7a 100644 --- a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs +++ b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs @@ -15,10 +15,7 @@ using DevnotMentor.Api.Repositories; using DevnotMentor.Api.Utilities.Email.SmtpMail; using DevnotMentor.Api.Utilities.Security.Token.Jwt; -using System.Threading.Tasks; -using System.Net.Http; -using System.Net.Http.Headers; -using Newtonsoft.Json; +using DevnotMentor.Api.Utilities; namespace DevnotMentor.Api.Helpers.Extensions { @@ -74,7 +71,11 @@ public static IServiceCollection AddCustomAuthentication(this IServiceCollection options.Events = new OAuthEvents { - OnCreatingTicket = async ctx => { var oAuthUser = await GetOAuthUser(OAuthType.GitHub, ctx); await SignIn(oAuthUser, ctx.HttpContext); } + OnCreatingTicket = async ctx => + { + var oAuthUser = await OAuthService.GetOAuthUserAsync(OAuthType.GitHub, ctx); + await OAuthService.SignInAsync(oAuthUser, ctx.HttpContext); + } }; }) .AddGoogle(options => @@ -85,60 +86,15 @@ public static IServiceCollection AddCustomAuthentication(this IServiceCollection options.Events = new OAuthEvents { - OnCreatingTicket = async ctx => { var oAuthUser = await GetOAuthUser(OAuthType.Google, ctx); await SignIn(oAuthUser, ctx.HttpContext); } + OnCreatingTicket = async ctx => + { + var oAuthUser = await OAuthService.GetOAuthUserAsync(OAuthType.Google, ctx); + await OAuthService.SignInAsync(oAuthUser, ctx.HttpContext); + } }; }); return services; } - - private static async Task GetOAuthUser(OAuthType oAuthType, OAuthCreatingTicketContext ctx) - { - var request = new HttpRequestMessage(HttpMethod.Get, ctx.Options.UserInformationEndpoint); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", ctx.AccessToken); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - var response = await ctx.Backchannel.SendAsync(request, ctx.HttpContext.RequestAborted); - response.EnsureSuccessStatusCode(); - - var responseContent = await response.Content.ReadAsStringAsync(); - - if (oAuthType == OAuthType.Google) - { - var googleResponse = JsonConvert.DeserializeObject(responseContent); - return new OAuthUser - { - Id = googleResponse.id, - IdentifierProperty = googleResponse.email, - FullName = googleResponse.name, - ProfilePictureUrl = googleResponse.picture, - Type = oAuthType, - }; - } - - var gitHubResponse = JsonConvert.DeserializeObject(responseContent); - return new OAuthUser - { - Id = gitHubResponse.id, - IdentifierProperty = gitHubResponse.login, - FullName = gitHubResponse.name, - ProfilePictureUrl = gitHubResponse.avatar_url, - Type = oAuthType, - }; - } - - private static async Task SignIn(OAuthUser oAuthUser, HttpContext httpContext) - { - var userService = httpContext.RequestServices.GetService(); - var signInResponse = await userService.SignInAsync(oAuthUser); - if (signInResponse.Success) - { - httpContext.Response.Headers.Add("auth-token", signInResponse.Data.Token); - httpContext.Response.Headers.Add("auth-token-expiry-date", signInResponse.Data.TokenExpiryDate.ToString()); - } - else - { - httpContext.Response.StatusCode = 500; - } - } } } \ No newline at end of file diff --git a/Devnot.Mentor.Api/Utilities/OAuthService.cs b/Devnot.Mentor.Api/Utilities/OAuthService.cs new file mode 100644 index 0000000..493f6c1 --- /dev/null +++ b/Devnot.Mentor.Api/Utilities/OAuthService.cs @@ -0,0 +1,64 @@ +using System.Threading.Tasks; +using System.Net.Http; +using System.Net.Http.Headers; +using Newtonsoft.Json; +using DevnotMentor.Api.CustomEntities.Auth; +using Microsoft.AspNetCore.Authentication.OAuth; +using Microsoft.AspNetCore.Http; +using DevnotMentor.Api.Services.Interfaces; +using Microsoft.Extensions.DependencyInjection; + +namespace DevnotMentor.Api.Utilities +{ + public static class OAuthService + { + public static async Task GetOAuthUserAsync(OAuthType oAuthType, OAuthCreatingTicketContext ctx) + { + var request = new HttpRequestMessage(HttpMethod.Get, ctx.Options.UserInformationEndpoint); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", ctx.AccessToken); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var response = await ctx.Backchannel.SendAsync(request, ctx.HttpContext.RequestAborted); + response.EnsureSuccessStatusCode(); + + var responseContent = await response.Content.ReadAsStringAsync(); + + if (oAuthType == OAuthType.Google) + { + var googleResponse = JsonConvert.DeserializeObject(responseContent); + return new OAuthUser + { + Id = googleResponse.id, + IdentifierProperty = googleResponse.email, + FullName = googleResponse.name, + ProfilePictureUrl = googleResponse.picture, + Type = oAuthType, + }; + } + + var gitHubResponse = JsonConvert.DeserializeObject(responseContent); + return new OAuthUser + { + Id = gitHubResponse.id, + IdentifierProperty = gitHubResponse.login, + FullName = gitHubResponse.name, + ProfilePictureUrl = gitHubResponse.avatar_url, + Type = oAuthType, + }; + } + + public static async Task SignInAsync(OAuthUser oAuthUser, HttpContext httpContext) + { + var userService = httpContext.RequestServices.GetService(); + var signInResponse = await userService.SignInAsync(oAuthUser); + if (signInResponse.Success) + { + httpContext.Response.Headers.Add("auth-token", signInResponse.Data.Token); + httpContext.Response.Headers.Add("auth-token-expiry-date", signInResponse.Data.TokenExpiryDate.ToString()); + } + else + { + httpContext.Response.StatusCode = 500; + } + } + } +} \ No newline at end of file From 1b6005c7782b000b9d115e970d035f3526e453e1 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Sun, 18 Jul 2021 03:15:54 +0300 Subject: [PATCH 13/36] Remove unused service. Add todos --- Devnot.Mentor.Api/Controllers/AuthController.cs | 8 -------- Devnot.Mentor.Api/Services/UserService.cs | 3 ++- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Devnot.Mentor.Api/Controllers/AuthController.cs b/Devnot.Mentor.Api/Controllers/AuthController.cs index a30a56b..e5d1f1f 100644 --- a/Devnot.Mentor.Api/Controllers/AuthController.cs +++ b/Devnot.Mentor.Api/Controllers/AuthController.cs @@ -1,4 +1,3 @@ -using DevnotMentor.Api.Services.Interfaces; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc; @@ -6,13 +5,6 @@ namespace DevnotMentor.Api.Controllers { public class AuthController : BaseController { - private readonly IUserService userService; - - public AuthController(IUserService userService) - { - this.userService = userService; - } - [Route("/auth/github")] [HttpGet] public IActionResult GitHubChallenge() diff --git a/Devnot.Mentor.Api/Services/UserService.cs b/Devnot.Mentor.Api/Services/UserService.cs index bfe3cdf..21b1902 100644 --- a/Devnot.Mentor.Api/Services/UserService.cs +++ b/Devnot.Mentor.Api/Services/UserService.cs @@ -42,12 +42,13 @@ private async Task GetOrCreateForOAuthUserAsync(OAuthUser oAuthUser) case OAuthType.Google: user.GoogleId = oAuthUser.Id; user.Email = oAuthUser.IdentifierProperty; - user.UserName = oAuthUser.IdentifierProperty; + user.UserName = oAuthUser.IdentifierProperty; // todo: after registration, user must select a UserName break; case OAuthType.GitHub: user.GitHubId = oAuthUser.Id; user.UserName = oAuthUser.IdentifierProperty; + //user.Email = ""; // todo: after registration, user must select a Email break; default: From fd45ffc21606384a8c43f9cf005c3ed063704fc1 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Sun, 18 Jul 2021 15:32:22 +0300 Subject: [PATCH 14/36] Add ctor for OAuthUser. Seperate: Get jobs by provider. Update: service for it. --- .../CustomEntities/Auth/OAuthUser.cs | 9 +++ .../Interfaces/IUserRepository.cs | 3 +- .../Repositories/UserRepository.cs | 9 ++- Devnot.Mentor.Api/Services/UserService.cs | 56 ++++++++++--------- Devnot.Mentor.Api/Utilities/OAuthService.cs | 20 +------ 5 files changed, 50 insertions(+), 47 deletions(-) diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs index 1fd32ec..18c0e28 100644 --- a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs @@ -2,6 +2,15 @@ namespace DevnotMentor.Api.CustomEntities.Auth { public class OAuthUser { + public OAuthUser(string id, string identifierProperty, string fullName, string profilePictureUrl, OAuthType type) + { + Id = id; + IdentifierProperty = identifierProperty; + FullName = fullName; + ProfilePictureUrl = profilePictureUrl; + Type = type; + } + public string Id { get; set; } public string IdentifierProperty { get; set; } // Google: Email, GitHub: UserName diff --git a/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs b/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs index d35349a..0820d47 100644 --- a/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs +++ b/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs @@ -6,7 +6,8 @@ namespace DevnotMentor.Api.Repositories.Interfaces public interface IUserRepository : IRepository { Task GetById(int id); - Task GetByUserNameOrEmailAsync(object identifier); + Task GetByGitHubId(object identifier); + Task GetByGoogleId(object identifier); Task GetByUserName(string userName); Task GetByEmail(string email); Task IsExists(int id); diff --git a/Devnot.Mentor.Api/Repositories/UserRepository.cs b/Devnot.Mentor.Api/Repositories/UserRepository.cs index e973f19..0bc89db 100644 --- a/Devnot.Mentor.Api/Repositories/UserRepository.cs +++ b/Devnot.Mentor.Api/Repositories/UserRepository.cs @@ -32,9 +32,14 @@ public async Task GetByUserName(string userName) return await DbContext.User.Where(u => u.UserName == userName).FirstOrDefaultAsync(); } - public async Task GetByUserNameOrEmailAsync(object identifier) + public async Task GetByGitHubId(object identifier) { - return await DbContext.User.Where(u => u.GitHubId == identifier.ToString() || u.Email == identifier.ToString()).FirstOrDefaultAsync(); + return await DbContext.User.Where(u => u.GitHubId == identifier.ToString()).FirstOrDefaultAsync(); + } + + public async Task GetByGoogleId(object identifier) + { + return await DbContext.User.Where(u => u.GoogleId == identifier.ToString()).FirstOrDefaultAsync(); } } } diff --git a/Devnot.Mentor.Api/Services/UserService.cs b/Devnot.Mentor.Api/Services/UserService.cs index 21b1902..39652e8 100644 --- a/Devnot.Mentor.Api/Services/UserService.cs +++ b/Devnot.Mentor.Api/Services/UserService.cs @@ -30,42 +30,44 @@ public UserService( this.userRepository = userRepository; } - private async Task GetOrCreateForOAuthUserAsync(OAuthUser oAuthUser) + private async Task CreateUserForOAuthUserAsync(OAuthUser oAuthUser) { - var user = await userRepository.GetByUserNameOrEmailAsync(oAuthUser.IdentifierProperty); - - if (user == null) + var user = new User(); + switch (oAuthUser.Type) { - user = new User(); - switch (oAuthUser.Type) - { - case OAuthType.Google: - user.GoogleId = oAuthUser.Id; - user.Email = oAuthUser.IdentifierProperty; - user.UserName = oAuthUser.IdentifierProperty; // todo: after registration, user must select a UserName - break; - - case OAuthType.GitHub: - user.GitHubId = oAuthUser.Id; - user.UserName = oAuthUser.IdentifierProperty; - //user.Email = ""; // todo: after registration, user must select a Email - break; - - default: - break; - } + case OAuthType.Google: + user.GoogleId = oAuthUser.Id; + user.Email = oAuthUser.IdentifierProperty; + user.UserName = oAuthUser.IdentifierProperty; // todo: after registration, user must select a UserName + break; - user.FullName = oAuthUser.FullName; - user.ProfilePictureUrl = oAuthUser.ProfilePictureUrl; - return userRepository.Create(user); + case OAuthType.GitHub: + user.GitHubId = oAuthUser.Id; + user.UserName = oAuthUser.IdentifierProperty; + //user.Email = ""; // todo: after registration, user must select a Email + break; + + default: + break; } - return user; + user.FullName = oAuthUser.FullName; + user.ProfilePictureUrl = oAuthUser.ProfilePictureUrl; + return userRepository.Create(user); } public async Task> SignInAsync(OAuthUser oAuthUser) { - User user = await GetOrCreateForOAuthUserAsync(oAuthUser); + var user = oAuthUser.Type switch + { + OAuthType.GitHub => await userRepository.GetByGitHubId(oAuthUser.Id), + OAuthType.Google => await userRepository.GetByGoogleId(oAuthUser.Id), + }; + + if (user == null) + { + user = await CreateUserForOAuthUserAsync(oAuthUser); + } var tokenInfo = tokenService.CreateToken(user.Id, user.UserName); diff --git a/Devnot.Mentor.Api/Utilities/OAuthService.cs b/Devnot.Mentor.Api/Utilities/OAuthService.cs index 493f6c1..e5865f7 100644 --- a/Devnot.Mentor.Api/Utilities/OAuthService.cs +++ b/Devnot.Mentor.Api/Utilities/OAuthService.cs @@ -19,31 +19,17 @@ public static async Task GetOAuthUserAsync(OAuthType oAuthType, OAuth request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var response = await ctx.Backchannel.SendAsync(request, ctx.HttpContext.RequestAborted); response.EnsureSuccessStatusCode(); - + var responseContent = await response.Content.ReadAsStringAsync(); if (oAuthType == OAuthType.Google) { var googleResponse = JsonConvert.DeserializeObject(responseContent); - return new OAuthUser - { - Id = googleResponse.id, - IdentifierProperty = googleResponse.email, - FullName = googleResponse.name, - ProfilePictureUrl = googleResponse.picture, - Type = oAuthType, - }; + return new OAuthUser(googleResponse.id, googleResponse.email, googleResponse.name, googleResponse.picture, oAuthType); } var gitHubResponse = JsonConvert.DeserializeObject(responseContent); - return new OAuthUser - { - Id = gitHubResponse.id, - IdentifierProperty = gitHubResponse.login, - FullName = gitHubResponse.name, - ProfilePictureUrl = gitHubResponse.avatar_url, - Type = oAuthType, - }; + return new OAuthUser(gitHubResponse.id, gitHubResponse.login, gitHubResponse.name, gitHubResponse.avatar_url, oAuthType); } public static async Task SignInAsync(OAuthUser oAuthUser, HttpContext httpContext) From 82aca0efb51e92b05447990d4272c5f826a471a5 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Sun, 18 Jul 2021 15:44:31 +0300 Subject: [PATCH 15/36] Update property name: IdentifierProperty to UniqueByProvider --- Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs | 6 +++--- Devnot.Mentor.Api/Services/UserService.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs index 18c0e28..9167287 100644 --- a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs @@ -2,17 +2,17 @@ namespace DevnotMentor.Api.CustomEntities.Auth { public class OAuthUser { - public OAuthUser(string id, string identifierProperty, string fullName, string profilePictureUrl, OAuthType type) + public OAuthUser(string id, string uniqueByProvider, string fullName, string profilePictureUrl, OAuthType type) { Id = id; - IdentifierProperty = identifierProperty; + UniqueByProvider = uniqueByProvider; FullName = fullName; ProfilePictureUrl = profilePictureUrl; Type = type; } public string Id { get; set; } - public string IdentifierProperty { get; set; } // Google: Email, GitHub: UserName + public string UniqueByProvider { get; set; } // Google: Email, GitHub: UserName public string FullName { get; set; } public string ProfilePictureUrl { get; set; } diff --git a/Devnot.Mentor.Api/Services/UserService.cs b/Devnot.Mentor.Api/Services/UserService.cs index 39652e8..b19c03d 100644 --- a/Devnot.Mentor.Api/Services/UserService.cs +++ b/Devnot.Mentor.Api/Services/UserService.cs @@ -37,13 +37,13 @@ private async Task CreateUserForOAuthUserAsync(OAuthUser oAuthUser) { case OAuthType.Google: user.GoogleId = oAuthUser.Id; - user.Email = oAuthUser.IdentifierProperty; - user.UserName = oAuthUser.IdentifierProperty; // todo: after registration, user must select a UserName + user.Email = oAuthUser.UniqueByProvider; + user.UserName = oAuthUser.UniqueByProvider; // todo: after registration, user must select a UserName break; case OAuthType.GitHub: user.GitHubId = oAuthUser.Id; - user.UserName = oAuthUser.IdentifierProperty; + user.UserName = oAuthUser.UniqueByProvider; //user.Email = ""; // todo: after registration, user must select a Email break; From de8c49708f00642990342234fc4884cbe1b94598 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Sun, 18 Jul 2021 18:00:36 +0300 Subject: [PATCH 16/36] Refactor: deserializing response and creating OAuthUser. --- .../CustomEntities/Auth/GitHubResponse.cs | 10 ------- .../CustomEntities/Auth/GoogleResponse.cs | 10 ------- .../CustomEntities/Auth/OAuthResponse.cs | 20 +++++++++++++ .../CustomEntities/Auth/OAuthType.cs | 2 +- .../CustomEntities/Auth/OAuthUser.cs | 30 ++++++++++++++----- .../Extensions/ServiceCollectionExtensions.cs | 2 +- .../Services/Interfaces/IUserService.cs | 2 +- Devnot.Mentor.Api/Services/UserService.cs | 13 +++----- Devnot.Mentor.Api/Utilities/OAuthService.cs | 13 ++------ 9 files changed, 52 insertions(+), 50 deletions(-) delete mode 100644 Devnot.Mentor.Api/CustomEntities/Auth/GitHubResponse.cs delete mode 100644 Devnot.Mentor.Api/CustomEntities/Auth/GoogleResponse.cs create mode 100644 Devnot.Mentor.Api/CustomEntities/Auth/OAuthResponse.cs diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/GitHubResponse.cs b/Devnot.Mentor.Api/CustomEntities/Auth/GitHubResponse.cs deleted file mode 100644 index 909d8f2..0000000 --- a/Devnot.Mentor.Api/CustomEntities/Auth/GitHubResponse.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace DevnotMentor.Api.CustomEntities.Auth -{ - public class GitHubResponse - { - public string login { get; set; } - public string id { get; set; } - public string avatar_url { get; set; } - public string name { get; set; } - } -} \ No newline at end of file diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/GoogleResponse.cs b/Devnot.Mentor.Api/CustomEntities/Auth/GoogleResponse.cs deleted file mode 100644 index 183cd4e..0000000 --- a/Devnot.Mentor.Api/CustomEntities/Auth/GoogleResponse.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace DevnotMentor.Api.CustomEntities.Auth -{ - public class GoogleResponse - { - public string id { get; set; } - public string email { get; set; } - public string name { get; set; } - public string picture { get; set; } - } -} \ No newline at end of file diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthResponse.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthResponse.cs new file mode 100644 index 0000000..3ae74ca --- /dev/null +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthResponse.cs @@ -0,0 +1,20 @@ +namespace DevnotMentor.Api.CustomEntities.OAuth +{ + public class OAuthResponse + { + #region github & google + public string id { get; set; } // google, github: id + public string name { get; set; } // google, github: fullname + public string email { get; set; } // google, github : email (it can be null, if it's coming from github) + #endregion + + #region just google + public string picture { get; set; } // google: profile picture + #endregion + + #region just github + public string login { get; set; } // github: username + public string avatar_url { get; set; } // github: profilepicture + #endregion + } +} \ No newline at end of file diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthType.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthType.cs index 6950659..f7c75a1 100644 --- a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthType.cs +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthType.cs @@ -1,4 +1,4 @@ -namespace DevnotMentor.Api.CustomEntities.Auth +namespace DevnotMentor.Api.CustomEntities.OAuth { public enum OAuthType : int { diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs index 9167287..db0ffec 100644 --- a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs @@ -1,18 +1,32 @@ -namespace DevnotMentor.Api.CustomEntities.Auth +namespace DevnotMentor.Api.CustomEntities.OAuth { public class OAuthUser { - public OAuthUser(string id, string uniqueByProvider, string fullName, string profilePictureUrl, OAuthType type) + public OAuthUser(OAuthResponse oAuthResponse, OAuthType oAuthType) { - Id = id; - UniqueByProvider = uniqueByProvider; - FullName = fullName; - ProfilePictureUrl = profilePictureUrl; - Type = type; + this.Id = oAuthResponse.id; + this.FullName = oAuthResponse.name; + this.Email = oAuthResponse.email; + + switch (oAuthType) + { + case OAuthType.GitHub: + this.UserName = oAuthResponse.login; + this.ProfilePictureUrl = oAuthResponse.avatar_url; + break; + case OAuthType.Google: + this.UserName = System.IO.Path.GetRandomFileName(); + this.ProfilePictureUrl = oAuthResponse.picture; + break; + } + + this.Type = oAuthType; } public string Id { get; set; } - public string UniqueByProvider { get; set; } // Google: Email, GitHub: UserName + + public string UserName { get; set; } + public string Email { get; set; } public string FullName { get; set; } public string ProfilePictureUrl { get; set; } diff --git a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs index 4111c7a..1e3aae9 100644 --- a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs +++ b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.AspNetCore.Authentication.Cookies; -using DevnotMentor.Api.CustomEntities.Auth; +using DevnotMentor.Api.CustomEntities.OAuth; using DevnotMentor.Api.Services; using DevnotMentor.Api.Utilities.Email; using DevnotMentor.Api.Utilities.Security.Token; diff --git a/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs b/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs index c7cfa19..a94416d 100644 --- a/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs +++ b/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; using DevnotMentor.Api.Common.Response; -using DevnotMentor.Api.CustomEntities.Auth; +using DevnotMentor.Api.CustomEntities.OAuth; using DevnotMentor.Api.CustomEntities.Response.UserResponse; namespace DevnotMentor.Api.Services.Interfaces diff --git a/Devnot.Mentor.Api/Services/UserService.cs b/Devnot.Mentor.Api/Services/UserService.cs index b19c03d..593a12c 100644 --- a/Devnot.Mentor.Api/Services/UserService.cs +++ b/Devnot.Mentor.Api/Services/UserService.cs @@ -9,7 +9,7 @@ using DevnotMentor.Api.Configuration.Context; using DevnotMentor.Api.CustomEntities.Response.UserResponse; using DevnotMentor.Api.CustomEntities.Dto; -using DevnotMentor.Api.CustomEntities.Auth; +using DevnotMentor.Api.CustomEntities.OAuth; namespace DevnotMentor.Api.Services { @@ -33,24 +33,19 @@ public UserService( private async Task CreateUserForOAuthUserAsync(OAuthUser oAuthUser) { var user = new User(); + switch (oAuthUser.Type) { case OAuthType.Google: user.GoogleId = oAuthUser.Id; - user.Email = oAuthUser.UniqueByProvider; - user.UserName = oAuthUser.UniqueByProvider; // todo: after registration, user must select a UserName break; - case OAuthType.GitHub: user.GitHubId = oAuthUser.Id; - user.UserName = oAuthUser.UniqueByProvider; - //user.Email = ""; // todo: after registration, user must select a Email - break; - - default: break; } + user.Email = oAuthUser.Email; // todo: if it's github oauth, it can be null. after the registration user must pass a email. + user.UserName = oAuthUser.UserName; // todo: if it's google oauth, it takes random value user.FullName = oAuthUser.FullName; user.ProfilePictureUrl = oAuthUser.ProfilePictureUrl; return userRepository.Create(user); diff --git a/Devnot.Mentor.Api/Utilities/OAuthService.cs b/Devnot.Mentor.Api/Utilities/OAuthService.cs index e5865f7..81b9470 100644 --- a/Devnot.Mentor.Api/Utilities/OAuthService.cs +++ b/Devnot.Mentor.Api/Utilities/OAuthService.cs @@ -2,7 +2,7 @@ using System.Net.Http; using System.Net.Http.Headers; using Newtonsoft.Json; -using DevnotMentor.Api.CustomEntities.Auth; +using DevnotMentor.Api.CustomEntities.OAuth; using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.AspNetCore.Http; using DevnotMentor.Api.Services.Interfaces; @@ -20,16 +20,9 @@ public static async Task GetOAuthUserAsync(OAuthType oAuthType, OAuth var response = await ctx.Backchannel.SendAsync(request, ctx.HttpContext.RequestAborted); response.EnsureSuccessStatusCode(); - var responseContent = await response.Content.ReadAsStringAsync(); + var oauthResponse = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - if (oAuthType == OAuthType.Google) - { - var googleResponse = JsonConvert.DeserializeObject(responseContent); - return new OAuthUser(googleResponse.id, googleResponse.email, googleResponse.name, googleResponse.picture, oAuthType); - } - - var gitHubResponse = JsonConvert.DeserializeObject(responseContent); - return new OAuthUser(gitHubResponse.id, gitHubResponse.login, gitHubResponse.name, gitHubResponse.avatar_url, oAuthType); + return new OAuthUser(oauthResponse, oAuthType); } public static async Task SignInAsync(OAuthUser oAuthUser, HttpContext httpContext) From 797d010374ff273576d78c53bbb4881634810650 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Mon, 19 Jul 2021 02:41:23 +0300 Subject: [PATCH 17/36] Fix swagger error --- Devnot.Mentor.Api/Controllers/AuthController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Devnot.Mentor.Api/Controllers/AuthController.cs b/Devnot.Mentor.Api/Controllers/AuthController.cs index f30edd3..cbe7604 100644 --- a/Devnot.Mentor.Api/Controllers/AuthController.cs +++ b/Devnot.Mentor.Api/Controllers/AuthController.cs @@ -6,6 +6,7 @@ namespace DevnotMentor.Api.Controllers public class AuthController : BaseController { [Route("/auth/okay")] + [HttpGet] public IActionResult OK() { return Ok("OAuth: okay"); From ebc6c3ba9e1dea6e14c10631a64584fccd2853c9 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Mon, 19 Jul 2021 17:12:14 +0300 Subject: [PATCH 18/36] refactor: SignInAsync returns. Add OAuth to Mapper. Update TokenInfo property name --- .../UserResponse/UserLoginResponse.cs | 19 --------------- Devnot.Mentor.Api/Helpers/MappingProfile.cs | 5 +++- .../Services/Interfaces/IUserService.cs | 4 ++-- Devnot.Mentor.Api/Services/UserService.cs | 23 ++++++------------- Devnot.Mentor.Api/Utilities/OAuthService.cs | 2 +- .../Security/Token/Jwt/JwtTokenService.cs | 2 +- .../Utilities/Security/Token/TokenInfo.cs | 5 +--- 7 files changed, 16 insertions(+), 44 deletions(-) delete mode 100644 Devnot.Mentor.Api/CustomEntities/Response/UserResponse/UserLoginResponse.cs diff --git a/Devnot.Mentor.Api/CustomEntities/Response/UserResponse/UserLoginResponse.cs b/Devnot.Mentor.Api/CustomEntities/Response/UserResponse/UserLoginResponse.cs deleted file mode 100644 index 56ca9f2..0000000 --- a/Devnot.Mentor.Api/CustomEntities/Response/UserResponse/UserLoginResponse.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using DevnotMentor.Api.CustomEntities.Dto; - -namespace DevnotMentor.Api.CustomEntities.Response.UserResponse -{ - public class UserLoginResponse - { - public UserLoginResponse(UserDto user, string token, DateTime? tokenExpiryDate) - { - User = user; - Token = token; - TokenExpiryDate = tokenExpiryDate; - } - - public UserDto User { get; set; } - public string Token { get; set; } - public DateTime? TokenExpiryDate { get; set; } - } -} diff --git a/Devnot.Mentor.Api/Helpers/MappingProfile.cs b/Devnot.Mentor.Api/Helpers/MappingProfile.cs index 09a958d..74790cb 100644 --- a/Devnot.Mentor.Api/Helpers/MappingProfile.cs +++ b/Devnot.Mentor.Api/Helpers/MappingProfile.cs @@ -1,5 +1,6 @@ using AutoMapper; using DevnotMentor.Api.CustomEntities.Dto; +using DevnotMentor.Api.CustomEntities.OAuth; using DevnotMentor.Api.CustomEntities.Request.MenteeRequest; using DevnotMentor.Api.CustomEntities.Request.MentorRequest; using DevnotMentor.Api.Entities; @@ -12,9 +13,11 @@ public MappingProfile() { CreateMap(); CreateMap(); + CreateMap(); - CreateMap(); + CreateMap().ForMember(x => x.Id, opt => opt.Ignore()); + CreateMap(); CreateMap() .ForMember(dest => dest.MentorTags, opt => opt.Ignore()) diff --git a/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs b/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs index a94416d..9334742 100644 --- a/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs +++ b/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs @@ -1,12 +1,12 @@ using System.Threading.Tasks; using DevnotMentor.Api.Common.Response; using DevnotMentor.Api.CustomEntities.OAuth; -using DevnotMentor.Api.CustomEntities.Response.UserResponse; +using DevnotMentor.Api.Utilities.Security.Token; namespace DevnotMentor.Api.Services.Interfaces { public interface IUserService { - Task> SignInAsync(OAuthUser oAuthUser); + Task> SignInAsync(OAuthUser oAuthUser); } } diff --git a/Devnot.Mentor.Api/Services/UserService.cs b/Devnot.Mentor.Api/Services/UserService.cs index a2ef23d..0d1f4c9 100644 --- a/Devnot.Mentor.Api/Services/UserService.cs +++ b/Devnot.Mentor.Api/Services/UserService.cs @@ -7,13 +7,10 @@ using System.Threading.Tasks; using DevnotMentor.Api.Common.Response; using DevnotMentor.Api.Configuration.Context; -using DevnotMentor.Api.CustomEntities.Response.UserResponse; -using DevnotMentor.Api.CustomEntities.Dto; using DevnotMentor.Api.CustomEntities.OAuth; namespace DevnotMentor.Api.Services { - public class UserService : BaseService, IUserService { private readonly IUserRepository userRepository; @@ -32,10 +29,9 @@ public UserService( private async Task CreateUserForOAuthUserAsync(OAuthUser oAuthUser) { - var user = new User(); - - switch (oAuthUser.Type) + var user = mapper.Map(oAuthUser); + switch (oAuthUser.Type) { case OAuthType.Google: user.GoogleId = oAuthUser.Id; @@ -44,15 +40,14 @@ private async Task CreateUserForOAuthUserAsync(OAuthUser oAuthUser) user.GitHubId = oAuthUser.Id; break; } + /* todo: + if it's github oauth, it can be null. after the registration user must pass a email. + if it's google oauth, it takes random value */ - user.Email = oAuthUser.Email; // todo: if it's github oauth, it can be null. after the registration user must pass a email. - user.UserName = oAuthUser.UserName; // todo: if it's google oauth, it takes random value - user.FullName = oAuthUser.FullName; - user.ProfilePictureUrl = oAuthUser.ProfilePictureUrl; return userRepository.Create(user); } - public async Task> SignInAsync(OAuthUser oAuthUser) + public async Task> SignInAsync(OAuthUser oAuthUser) { var user = oAuthUser.Type switch { @@ -65,11 +60,7 @@ public async Task> SignInAsync(OAuthUser oAuthUse user = await CreateUserForOAuthUserAsync(oAuthUser); } - var tokenInfo = tokenService.CreateToken(user.Id, user.UserName); - - var mappedUser = mapper.Map(user); - - return new SuccessApiResponse(data: new UserLoginResponse(mappedUser, tokenInfo.Token, tokenInfo.ExpiredDate), ResultMessage.Success); + return new SuccessApiResponse(data: tokenService.CreateToken(user.Id, user.UserName), ResultMessage.Success); } } } diff --git a/Devnot.Mentor.Api/Utilities/OAuthService.cs b/Devnot.Mentor.Api/Utilities/OAuthService.cs index 81b9470..57c2b04 100644 --- a/Devnot.Mentor.Api/Utilities/OAuthService.cs +++ b/Devnot.Mentor.Api/Utilities/OAuthService.cs @@ -32,7 +32,7 @@ public static async Task SignInAsync(OAuthUser oAuthUser, HttpContext httpContex if (signInResponse.Success) { httpContext.Response.Headers.Add("auth-token", signInResponse.Data.Token); - httpContext.Response.Headers.Add("auth-token-expiry-date", signInResponse.Data.TokenExpiryDate.ToString()); + httpContext.Response.Headers.Add("auth-token-expiry-date", signInResponse.Data.ExpiryDate.ToString()); } else { diff --git a/Devnot.Mentor.Api/Utilities/Security/Token/Jwt/JwtTokenService.cs b/Devnot.Mentor.Api/Utilities/Security/Token/Jwt/JwtTokenService.cs index 91b3da9..57ab1c9 100644 --- a/Devnot.Mentor.Api/Utilities/Security/Token/Jwt/JwtTokenService.cs +++ b/Devnot.Mentor.Api/Utilities/Security/Token/Jwt/JwtTokenService.cs @@ -40,7 +40,7 @@ public TokenInfo CreateToken(int userId, string userName) return new TokenInfo { Token = tokenHandler.WriteToken(token), - ExpiredDate = DateTime.Now.AddMinutes(devnotConfigurationContext.JwtSecretExpirationInMinutes) + ExpiryDate = DateTime.Now.AddMinutes(devnotConfigurationContext.JwtSecretExpirationInMinutes) }; } diff --git a/Devnot.Mentor.Api/Utilities/Security/Token/TokenInfo.cs b/Devnot.Mentor.Api/Utilities/Security/Token/TokenInfo.cs index 05caf40..4432d8f 100644 --- a/Devnot.Mentor.Api/Utilities/Security/Token/TokenInfo.cs +++ b/Devnot.Mentor.Api/Utilities/Security/Token/TokenInfo.cs @@ -1,13 +1,10 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace DevnotMentor.Api.Utilities.Security.Token { public class TokenInfo { public string Token { get; set; } - public DateTime ExpiredDate { get; set; } + public DateTime ExpiryDate { get; set; } } } From b08366d6fe53f328b5d73eb107c6d2b6731e9ade Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Wed, 21 Jul 2021 17:02:34 +0300 Subject: [PATCH 19/36] Add 'users/me' endpoint to get authenticated user info --- Devnot.Mentor.Api/Controllers/UserController.cs | 16 ++++++++++++++-- Devnot.Mentor.Api/CustomEntities/Dto/UserDto.cs | 1 - .../Repositories/Interfaces/IUserRepository.cs | 1 + Devnot.Mentor.Api/Repositories/UserRepository.cs | 6 ++++++ .../Services/Interfaces/IUserService.cs | 2 ++ Devnot.Mentor.Api/Services/UserService.cs | 11 +++++++++-- 6 files changed, 32 insertions(+), 5 deletions(-) diff --git a/Devnot.Mentor.Api/Controllers/UserController.cs b/Devnot.Mentor.Api/Controllers/UserController.cs index 2b71f29..c51d764 100644 --- a/Devnot.Mentor.Api/Controllers/UserController.cs +++ b/Devnot.Mentor.Api/Controllers/UserController.cs @@ -1,9 +1,12 @@ -using DevnotMentor.Api.Services.Interfaces; +using System.Threading.Tasks; +using DevnotMentor.Api.ActionFilters; +using DevnotMentor.Api.Helpers.Extensions; +using DevnotMentor.Api.Services.Interfaces; using Microsoft.AspNetCore.Mvc; namespace DevnotMentor.Api.Controllers { - [ApiController] + [ServiceFilter(typeof(TokenAuthentication)), ApiController, Route("users"),] public class UserController : BaseController { private readonly IUserService userService; @@ -12,5 +15,14 @@ public UserController(IUserService userService) { this.userService = userService; } + + [HttpGet("me")] + public async Task GetMe() + { + var authenticatedUserId = User.Claims.GetUserId(); + var result = await userService.GetByUserIdAsync(authenticatedUserId); + + return result.Success ? Success(result) : BadRequest(result); + } } } \ No newline at end of file diff --git a/Devnot.Mentor.Api/CustomEntities/Dto/UserDto.cs b/Devnot.Mentor.Api/CustomEntities/Dto/UserDto.cs index f78fea9..a48b214 100644 --- a/Devnot.Mentor.Api/CustomEntities/Dto/UserDto.cs +++ b/Devnot.Mentor.Api/CustomEntities/Dto/UserDto.cs @@ -5,7 +5,6 @@ namespace DevnotMentor.Api.CustomEntities.Dto public class UserDto { public int Id { get; set; } - public string GitHubId { get; set; } public string UserName { get; set; } public string FullName { get; set; } public string ProfilePictureUrl { get; set; } diff --git a/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs b/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs index cc7e943..1af0627 100644 --- a/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs +++ b/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs @@ -8,6 +8,7 @@ public interface IUserRepository : IRepository Task GetByGitHubIdAsync(object identifier); Task GetByGoogleIdAsync(object identifier); Task GetByIdAsync(int id); + Task GetByIdIncludeMenteeMentorAsync(int id); Task GetByUserNameAsync(string userName); Task GetByEmailAsync(string email); Task IsExistsAsync(int id); diff --git a/Devnot.Mentor.Api/Repositories/UserRepository.cs b/Devnot.Mentor.Api/Repositories/UserRepository.cs index f5d9119..1ae5e08 100644 --- a/Devnot.Mentor.Api/Repositories/UserRepository.cs +++ b/Devnot.Mentor.Api/Repositories/UserRepository.cs @@ -16,6 +16,12 @@ public async Task GetByIdAsync(int id) { return await DbContext.User.Where(i => i.Id == id).FirstOrDefaultAsync(); } + public async Task GetByIdIncludeMenteeMentorAsync(int id) + { + return await DbContext.User + .Include(x=>x.Mentor).Include(x=>x.Mentee) + .Where(i => i.Id == id).FirstOrDefaultAsync(); + } public async Task GetByEmailAsync(string email) { diff --git a/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs b/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs index 9334742..6525ce7 100644 --- a/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs +++ b/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using DevnotMentor.Api.Common.Response; +using DevnotMentor.Api.CustomEntities.Dto; using DevnotMentor.Api.CustomEntities.OAuth; using DevnotMentor.Api.Utilities.Security.Token; @@ -8,5 +9,6 @@ namespace DevnotMentor.Api.Services.Interfaces public interface IUserService { Task> SignInAsync(OAuthUser oAuthUser); + Task> GetByUserIdAsync(int userId); } } diff --git a/Devnot.Mentor.Api/Services/UserService.cs b/Devnot.Mentor.Api/Services/UserService.cs index 0d1f4c9..a62c2b1 100644 --- a/Devnot.Mentor.Api/Services/UserService.cs +++ b/Devnot.Mentor.Api/Services/UserService.cs @@ -8,6 +8,7 @@ using DevnotMentor.Api.Common.Response; using DevnotMentor.Api.Configuration.Context; using DevnotMentor.Api.CustomEntities.OAuth; +using DevnotMentor.Api.CustomEntities.Dto; namespace DevnotMentor.Api.Services { @@ -27,7 +28,7 @@ public UserService( this.userRepository = userRepository; } - private async Task CreateUserForOAuthUserAsync(OAuthUser oAuthUser) + private User CreateUserForOAuthUser(OAuthUser oAuthUser) { var user = mapper.Map(oAuthUser); @@ -57,10 +58,16 @@ public async Task> SignInAsync(OAuthUser oAuthUser) if (user == null) { - user = await CreateUserForOAuthUserAsync(oAuthUser); + user = CreateUserForOAuthUser(oAuthUser); } return new SuccessApiResponse(data: tokenService.CreateToken(user.Id, user.UserName), ResultMessage.Success); } + + public async Task> GetByUserIdAsync(int userId) + { + var user = await userRepository.GetByIdIncludeMenteeMentorAsync(userId); + return new SuccessApiResponse(data: mapper.Map(user), ResultMessage.Success); + } } } From 8b634b3f0066db0c1db93b5f41fd1f2ff5ae9dfd Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Wed, 21 Jul 2021 19:04:08 +0300 Subject: [PATCH 20/36] Add: new columns: emailconfirmed, createdat and changes for those. --- .../CustomEntities/Auth/OAuthUser.cs | 34 +++++++++++++------ Devnot.Mentor.Api/Entities/User.cs | 5 ++- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs index db0ffec..d64d4c8 100644 --- a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs @@ -1,26 +1,38 @@ +using System; + namespace DevnotMentor.Api.CustomEntities.OAuth { public class OAuthUser { - public OAuthUser(OAuthResponse oAuthResponse, OAuthType oAuthType) + public OAuthUser(OAuthResponse response, OAuthType type) { - this.Id = oAuthResponse.id; - this.FullName = oAuthResponse.name; - this.Email = oAuthResponse.email; + Id = response.id; + FullName = response.name; + Email = response.email; - switch (oAuthType) + switch (type) { case OAuthType.GitHub: - this.UserName = oAuthResponse.login; - this.ProfilePictureUrl = oAuthResponse.avatar_url; + UserName = response.login; + ProfilePictureUrl = response.avatar_url; + if (Email != null) + { + EmailConfirmed = true; + } + else + { + Email = UserName; + } break; case OAuthType.Google: - this.UserName = System.IO.Path.GetRandomFileName(); - this.ProfilePictureUrl = oAuthResponse.picture; + UserName = System.IO.Path.GetRandomFileName(); + ProfilePictureUrl = response.picture; + EmailConfirmed = true; break; } - this.Type = oAuthType; + Type = type; + CreatedAt = DateTime.Now; } public string Id { get; set; } @@ -30,6 +42,8 @@ public OAuthUser(OAuthResponse oAuthResponse, OAuthType oAuthType) public string FullName { get; set; } public string ProfilePictureUrl { get; set; } + public bool EmailConfirmed { get; set; } + public DateTime CreatedAt { get; set; } public OAuthType Type { get; set; } } } \ No newline at end of file diff --git a/Devnot.Mentor.Api/Entities/User.cs b/Devnot.Mentor.Api/Entities/User.cs index ad8301f..1705839 100644 --- a/Devnot.Mentor.Api/Entities/User.cs +++ b/Devnot.Mentor.Api/Entities/User.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace DevnotMentor.Api.Entities { @@ -17,6 +18,8 @@ public User() public string Email { get; set; } public string FullName { get; set; } public string ProfilePictureUrl { get; set; } + public bool EmailConfirmed { get; set; } + public DateTime CreatedAt { get; set; } public virtual ICollection Mentee { get; set; } public virtual ICollection Mentor { get; set; } From cbc524910beb17538561634c7f1a44f4bca06960 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Wed, 21 Jul 2021 19:04:49 +0300 Subject: [PATCH 21/36] Update user entity props --- Devnot.Mentor.Api/Entities/MentorDBContext.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Devnot.Mentor.Api/Entities/MentorDBContext.cs b/Devnot.Mentor.Api/Entities/MentorDBContext.cs index 94ffd8d..9e5dcf3 100644 --- a/Devnot.Mentor.Api/Entities/MentorDBContext.cs +++ b/Devnot.Mentor.Api/Entities/MentorDBContext.cs @@ -298,19 +298,19 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.HasIndex(e => e.Email).HasDatabaseName("unique_email").IsUnique(); entity.Property(p => p.GitHubId) - .HasMaxLength(1000) // todo: learn max length from github api + .HasMaxLength(64) .IsUnicode(false) .IsRequired(false); entity.Property(p => p.GoogleId) - .HasMaxLength(1000) // todo: learn max length from google api + .HasMaxLength(64) .IsUnicode(false) .IsRequired(false); - + entity.Property(p => p.Email) - .HasMaxLength(100) + .HasMaxLength(254) .IsUnicode(false) - .IsRequired(false); + .IsRequired(true); entity.Property(e => e.UserName) .HasMaxLength(39) From 6fd89814e40edc83d16abcf61f13790d60a3a131 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Wed, 21 Jul 2021 19:06:19 +0300 Subject: [PATCH 22/36] Refactor: add try-catch blocks to catch unique email and username exceptions. --- Devnot.Mentor.Api/Services/UserService.cs | 36 +++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/Devnot.Mentor.Api/Services/UserService.cs b/Devnot.Mentor.Api/Services/UserService.cs index a62c2b1..31b0ae0 100644 --- a/Devnot.Mentor.Api/Services/UserService.cs +++ b/Devnot.Mentor.Api/Services/UserService.cs @@ -31,7 +31,6 @@ public UserService( private User CreateUserForOAuthUser(OAuthUser oAuthUser) { var user = mapper.Map(oAuthUser); - switch (oAuthUser.Type) { case OAuthType.Google: @@ -41,11 +40,38 @@ private User CreateUserForOAuthUser(OAuthUser oAuthUser) user.GitHubId = oAuthUser.Id; break; } - /* todo: - if it's github oauth, it can be null. after the registration user must pass a email. - if it's google oauth, it takes random value */ - return userRepository.Create(user); + create: + try + { + user = userRepository.Create(user); + } + catch (System.Exception exception) + { + var innerExMessage = exception.InnerException.Message; + bool exceptionCatched = false; + + if (innerExMessage.Contains("unique_username")) + { + exceptionCatched = true; + user.UserName = System.IO.Path.GetRandomFileName(); + } + if (innerExMessage.Contains("unique_email")) + { + exceptionCatched = true; + user.Email = System.IO.Path.GetRandomFileName(); + user.EmailConfirmed = false; + } + + if (exceptionCatched) + { + goto create; + } + + throw exception; // middleware will catch + } + + return user; } public async Task> SignInAsync(OAuthUser oAuthUser) From f25cd4a8f6580f910018aae9dd62702703368f62 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Wed, 21 Jul 2021 21:33:41 +0300 Subject: [PATCH 23/36] refactor: selecting provider id --- .../CustomEntities/Auth/OAuthUser.cs | 23 ++++++++++++------- Devnot.Mentor.Api/Services/UserService.cs | 14 ++--------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs index d64d4c8..3f7d3cf 100644 --- a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs @@ -6,16 +6,22 @@ public class OAuthUser { public OAuthUser(OAuthResponse response, OAuthType type) { - Id = response.id; + Type = type; + FullName = response.name; Email = response.email; + CreatedAt = DateTime.Now; - switch (type) + switch (Type) { case OAuthType.GitHub: + GoogleId = null; + + GitHubId = response.id; UserName = response.login; ProfilePictureUrl = response.avatar_url; - if (Email != null) + + if (!string.IsNullOrEmpty(Email)) { EmailConfirmed = true; } @@ -25,19 +31,20 @@ public OAuthUser(OAuthResponse response, OAuthType type) } break; case OAuthType.Google: + GitHubId = null; + + GoogleId = response.id; UserName = System.IO.Path.GetRandomFileName(); ProfilePictureUrl = response.picture; EmailConfirmed = true; break; } - - Type = type; - CreatedAt = DateTime.Now; } - public string Id { get; set; } - public string UserName { get; set; } + public string GitHubId { get; set; } + public string GoogleId { get; set; } + public string Email { get; set; } public string FullName { get; set; } diff --git a/Devnot.Mentor.Api/Services/UserService.cs b/Devnot.Mentor.Api/Services/UserService.cs index 31b0ae0..ed3a4f5 100644 --- a/Devnot.Mentor.Api/Services/UserService.cs +++ b/Devnot.Mentor.Api/Services/UserService.cs @@ -31,16 +31,6 @@ public UserService( private User CreateUserForOAuthUser(OAuthUser oAuthUser) { var user = mapper.Map(oAuthUser); - switch (oAuthUser.Type) - { - case OAuthType.Google: - user.GoogleId = oAuthUser.Id; - break; - case OAuthType.GitHub: - user.GitHubId = oAuthUser.Id; - break; - } - create: try { @@ -78,8 +68,8 @@ public async Task> SignInAsync(OAuthUser oAuthUser) { var user = oAuthUser.Type switch { - OAuthType.GitHub => await userRepository.GetByGitHubIdAsync(oAuthUser.Id), - OAuthType.Google => await userRepository.GetByGoogleIdAsync(oAuthUser.Id), + OAuthType.GitHub => await userRepository.GetByGitHubIdAsync(oAuthUser.GitHubId), + OAuthType.Google => await userRepository.GetByGoogleIdAsync(oAuthUser.GoogleId), }; if (user == null) From 2d2312f27910465ae69d015b1bb3a94ff12424d8 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Fri, 23 Jul 2021 20:03:34 +0300 Subject: [PATCH 24/36] Add UniqueIndexName to provide Unique Index Names from one point --- Devnot.Mentor.Api/Common/UniqueIndexName.cs | 8 ++++++++ Devnot.Mentor.Api/Entities/MentorDBContext.cs | 6 +++--- Devnot.Mentor.Api/Services/UserService.cs | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 Devnot.Mentor.Api/Common/UniqueIndexName.cs diff --git a/Devnot.Mentor.Api/Common/UniqueIndexName.cs b/Devnot.Mentor.Api/Common/UniqueIndexName.cs new file mode 100644 index 0000000..d682b97 --- /dev/null +++ b/Devnot.Mentor.Api/Common/UniqueIndexName.cs @@ -0,0 +1,8 @@ +namespace DevnotMentor.Api.Common +{ + public static class UniqueIndexName + { + public static string UserName => "unique_username"; + public static string Email => "unique_email"; + } +} \ No newline at end of file diff --git a/Devnot.Mentor.Api/Entities/MentorDBContext.cs b/Devnot.Mentor.Api/Entities/MentorDBContext.cs index 9e5dcf3..1e76529 100644 --- a/Devnot.Mentor.Api/Entities/MentorDBContext.cs +++ b/Devnot.Mentor.Api/Entities/MentorDBContext.cs @@ -1,4 +1,5 @@ using System; +using DevnotMentor.Api.Common; using Microsoft.EntityFrameworkCore; namespace DevnotMentor.Api.Entities @@ -293,9 +294,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { entity.HasKey(p => p.Id); - entity.HasIndex(p => p.UserName).HasDatabaseName("unique_username").IsUnique(); - - entity.HasIndex(e => e.Email).HasDatabaseName("unique_email").IsUnique(); + entity.HasIndex(p => p.UserName).HasDatabaseName(UniqueIndexName.UserName).IsUnique(); + entity.HasIndex(e => e.Email).HasDatabaseName(UniqueIndexName.Email).IsUnique(); entity.Property(p => p.GitHubId) .HasMaxLength(64) diff --git a/Devnot.Mentor.Api/Services/UserService.cs b/Devnot.Mentor.Api/Services/UserService.cs index ed3a4f5..ff1ac9e 100644 --- a/Devnot.Mentor.Api/Services/UserService.cs +++ b/Devnot.Mentor.Api/Services/UserService.cs @@ -41,12 +41,12 @@ private User CreateUserForOAuthUser(OAuthUser oAuthUser) var innerExMessage = exception.InnerException.Message; bool exceptionCatched = false; - if (innerExMessage.Contains("unique_username")) + if (innerExMessage.Contains(UniqueIndexName.UserName)) { exceptionCatched = true; user.UserName = System.IO.Path.GetRandomFileName(); } - if (innerExMessage.Contains("unique_email")) + if (innerExMessage.Contains(UniqueIndexName.Email)) { exceptionCatched = true; user.Email = System.IO.Path.GetRandomFileName(); From a084156713bca59f5ab9aed50ecfb23b525afb9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20Y=C4=B1lmaz?= Date: Sat, 24 Jul 2021 03:49:32 +0300 Subject: [PATCH 25/36] refactoring --- .../CustomEntities/Auth/OAuthGitHubUser.cs | 25 +++++++ .../CustomEntities/Auth/OAuthGoogleUser.cs | 25 +++++++ .../CustomEntities/Auth/OAuthResponse.cs | 20 ------ .../CustomEntities/Auth/OAuthUser.cs | 68 +++++++------------ .../Auth/Response/OAuthGitHubResponse.cs | 26 +++++++ .../Auth/Response/OAuthGoogleResponse.cs | 25 +++++++ Devnot.Mentor.Api/Entities/MentorDBContext.cs | 4 +- .../Extensions/ServiceCollectionExtensions.cs | 9 +-- Devnot.Mentor.Api/Helpers/MappingProfile.cs | 13 +++- .../Interfaces/IUserRepository.cs | 2 + .../Repositories/UserRepository.cs | 14 +++- Devnot.Mentor.Api/Services/UserService.cs | 53 +++++---------- Devnot.Mentor.Api/Utilities/OAuthService.cs | 20 ++++-- 13 files changed, 192 insertions(+), 112 deletions(-) create mode 100644 Devnot.Mentor.Api/CustomEntities/Auth/OAuthGitHubUser.cs create mode 100644 Devnot.Mentor.Api/CustomEntities/Auth/OAuthGoogleUser.cs delete mode 100644 Devnot.Mentor.Api/CustomEntities/Auth/OAuthResponse.cs create mode 100644 Devnot.Mentor.Api/CustomEntities/Auth/Response/OAuthGitHubResponse.cs create mode 100644 Devnot.Mentor.Api/CustomEntities/Auth/Response/OAuthGoogleResponse.cs diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGitHubUser.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGitHubUser.cs new file mode 100644 index 0000000..37a55a6 --- /dev/null +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGitHubUser.cs @@ -0,0 +1,25 @@ +using System; +using System.Threading.Tasks; +using DevnotMentor.Api.CustomEntities.OAuth; +using DevnotMentor.Api.Entities; +using DevnotMentor.Api.Repositories.Interfaces; + +namespace DevnotMentor.Api.CustomEntities.Auth +{ + public class OAuthGitHubUser : OAuthUser + { + public OAuthGitHubUser() : base(OAuthType.GitHub) + { + } + + public override async Task GetUserFromDatabase(IUserRepository repository) + { + if (repository is null) + { + throw new NullReferenceException($"{repository.GetType().FullName} was null."); + } + + return await repository.GetByGitHubIdAsync(base.Id); + } + } +} diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGoogleUser.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGoogleUser.cs new file mode 100644 index 0000000..05c3cdd --- /dev/null +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGoogleUser.cs @@ -0,0 +1,25 @@ +using System; +using System.Threading.Tasks; +using DevnotMentor.Api.CustomEntities.OAuth; +using DevnotMentor.Api.Entities; +using DevnotMentor.Api.Repositories.Interfaces; + +namespace DevnotMentor.Api.CustomEntities.Auth +{ + public class OAuthGoogleUser : OAuthUser + { + public OAuthGoogleUser() : base(OAuthType.Google) + { + } + + public override async Task GetUserFromDatabase(IUserRepository repository) + { + if (repository is null) + { + throw new NullReferenceException($"{repository.GetType().FullName} was null."); + } + + return await repository.GetByGoogleIdAsync(base.Id); + } + } +} diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthResponse.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthResponse.cs deleted file mode 100644 index 3ae74ca..0000000 --- a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthResponse.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace DevnotMentor.Api.CustomEntities.OAuth -{ - public class OAuthResponse - { - #region github & google - public string id { get; set; } // google, github: id - public string name { get; set; } // google, github: fullname - public string email { get; set; } // google, github : email (it can be null, if it's coming from github) - #endregion - - #region just google - public string picture { get; set; } // google: profile picture - #endregion - - #region just github - public string login { get; set; } // github: username - public string avatar_url { get; set; } // github: profilepicture - #endregion - } -} \ No newline at end of file diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs index 3f7d3cf..e73140b 100644 --- a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs @@ -1,56 +1,38 @@ using System; +using System.Threading.Tasks; +using DevnotMentor.Api.Entities; +using DevnotMentor.Api.Repositories.Interfaces; namespace DevnotMentor.Api.CustomEntities.OAuth { - public class OAuthUser + public abstract class OAuthUser { - public OAuthUser(OAuthResponse response, OAuthType type) + protected OAuthUser(OAuthType providerType) { - Type = type; - - FullName = response.name; - Email = response.email; - CreatedAt = DateTime.Now; - - switch (Type) - { - case OAuthType.GitHub: - GoogleId = null; - - GitHubId = response.id; - UserName = response.login; - ProfilePictureUrl = response.avatar_url; - - if (!string.IsNullOrEmpty(Email)) - { - EmailConfirmed = true; - } - else - { - Email = UserName; - } - break; - case OAuthType.Google: - GitHubId = null; - - GoogleId = response.id; - UserName = System.IO.Path.GetRandomFileName(); - ProfilePictureUrl = response.picture; - EmailConfirmed = true; - break; - } + OAuthProviderType = providerType; } - public string UserName { get; set; } - public string GitHubId { get; set; } - public string GoogleId { get; set; } - - public string Email { get; set; } + /// + /// O an ki provider'a ait kullanýcý id deðeri. + /// + public string Id { get; set; } public string FullName { get; set; } - public string ProfilePictureUrl { get; set; } + public string UserName { get; set; } + public string Email { get; set; } public bool EmailConfirmed { get; set; } - public DateTime CreatedAt { get; set; } - public OAuthType Type { get; set; } + public string ProfilePictureUrl { get; set; } + public OAuthType OAuthProviderType { get; private set; } + + /// + /// Provider kullanýcýsýnýn bilgilerine göre veri tabanýndan ilgili kullanýcýyý getirir. + /// + /// + public abstract Task GetUserFromDatabase(IUserRepository repository); + + public void SetRandomUsername() + { + this.UserName = Guid.NewGuid().ToString().Split('-')[0]; + } } } \ No newline at end of file diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/Response/OAuthGitHubResponse.cs b/Devnot.Mentor.Api/CustomEntities/Auth/Response/OAuthGitHubResponse.cs new file mode 100644 index 0000000..6c9cfda --- /dev/null +++ b/Devnot.Mentor.Api/CustomEntities/Auth/Response/OAuthGitHubResponse.cs @@ -0,0 +1,26 @@ +using System; + +namespace DevnotMentor.Api.CustomEntities.Auth.Response +{ + public class OAuthGitHubResponse + { + public string id { get; set; } + public string name { get; set; } + public string email { get; set; } + public string login { get; set; } + public string avatar_url { get; set; } + + public OAuthGitHubUser MapToOAuthGitHubUser() + { + return new OAuthGitHubUser() + { + Email = email, + Id = id, + FullName = name, + ProfilePictureUrl = avatar_url, + UserName = login, + EmailConfirmed = (!String.IsNullOrEmpty(email)) + }; + } + } +} diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/Response/OAuthGoogleResponse.cs b/Devnot.Mentor.Api/CustomEntities/Auth/Response/OAuthGoogleResponse.cs new file mode 100644 index 0000000..07c3b30 --- /dev/null +++ b/Devnot.Mentor.Api/CustomEntities/Auth/Response/OAuthGoogleResponse.cs @@ -0,0 +1,25 @@ +using System; + +namespace DevnotMentor.Api.CustomEntities.Auth.Response +{ + public class OAuthGoogleResponse + { + public string id { get; set; } + public string name { get; set; } + public string email { get; set; } + public string picture { get; set; } + + public OAuthGoogleUser MapToOAuthGoogleUser() + { + return new OAuthGoogleUser() + { + Email = email, + Id = id, + FullName = name, + ProfilePictureUrl = picture, + EmailConfirmed = (!String.IsNullOrEmpty(email)), + UserName = Guid.NewGuid().ToString().Split('-')[0] + }; + } + } +} diff --git a/Devnot.Mentor.Api/Entities/MentorDBContext.cs b/Devnot.Mentor.Api/Entities/MentorDBContext.cs index 1e76529..de56548 100644 --- a/Devnot.Mentor.Api/Entities/MentorDBContext.cs +++ b/Devnot.Mentor.Api/Entities/MentorDBContext.cs @@ -295,7 +295,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.HasKey(p => p.Id); entity.HasIndex(p => p.UserName).HasDatabaseName(UniqueIndexName.UserName).IsUnique(); - entity.HasIndex(e => e.Email).HasDatabaseName(UniqueIndexName.Email).IsUnique(); entity.Property(p => p.GitHubId) .HasMaxLength(64) @@ -309,8 +308,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.Property(p => p.Email) .HasMaxLength(254) - .IsUnicode(false) - .IsRequired(true); + .IsUnicode(false); entity.Property(e => e.UserName) .HasMaxLength(39) diff --git a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs index 1e3aae9..529a064 100644 --- a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs +++ b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs @@ -73,8 +73,9 @@ public static IServiceCollection AddCustomAuthentication(this IServiceCollection { OnCreatingTicket = async ctx => { - var oAuthUser = await OAuthService.GetOAuthUserAsync(OAuthType.GitHub, ctx); - await OAuthService.SignInAsync(oAuthUser, ctx.HttpContext); + var oAuthGitHubUser = await OAuthService.GetOAuthGitHubUserAsync(ctx); + + await OAuthService.SignInAsync(oAuthGitHubUser, ctx.HttpContext); } }; }) @@ -88,8 +89,8 @@ public static IServiceCollection AddCustomAuthentication(this IServiceCollection { OnCreatingTicket = async ctx => { - var oAuthUser = await OAuthService.GetOAuthUserAsync(OAuthType.Google, ctx); - await OAuthService.SignInAsync(oAuthUser, ctx.HttpContext); + var oAuthGoogleUser = await OAuthService.GetOAuthGoogleUserAsync(ctx); + await OAuthService.SignInAsync(oAuthGoogleUser, ctx.HttpContext); } }; }); diff --git a/Devnot.Mentor.Api/Helpers/MappingProfile.cs b/Devnot.Mentor.Api/Helpers/MappingProfile.cs index 74790cb..d7c0367 100644 --- a/Devnot.Mentor.Api/Helpers/MappingProfile.cs +++ b/Devnot.Mentor.Api/Helpers/MappingProfile.cs @@ -15,7 +15,18 @@ public MappingProfile() CreateMap(); CreateMap(); - CreateMap().ForMember(x => x.Id, opt => opt.Ignore()); + CreateMap() + .ForMember(user => user.GitHubId, opt => + { + opt.Condition(oAuthUser => oAuthUser.OAuthProviderType == OAuthType.GitHub); + opt.MapFrom(oAuthUser => oAuthUser.Id); + }) + .ForMember(user => user.GoogleId, opt => + { + opt.Condition(oAuthUser => oAuthUser.OAuthProviderType == OAuthType.Google); + opt.MapFrom(oAuthUser => oAuthUser.Id); + }) + .ForMember(user => user.Id, opt => opt.Ignore()); CreateMap(); diff --git a/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs b/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs index 1af0627..8458b7f 100644 --- a/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs +++ b/Devnot.Mentor.Api/Repositories/Interfaces/IUserRepository.cs @@ -10,6 +10,8 @@ public interface IUserRepository : IRepository Task GetByIdAsync(int id); Task GetByIdIncludeMenteeMentorAsync(int id); Task GetByUserNameAsync(string userName); + Task AnyByUserNameAsync(string userName); + Task AnyByEmailAsync(string email); Task GetByEmailAsync(string email); Task IsExistsAsync(int id); } diff --git a/Devnot.Mentor.Api/Repositories/UserRepository.cs b/Devnot.Mentor.Api/Repositories/UserRepository.cs index 1ae5e08..992467b 100644 --- a/Devnot.Mentor.Api/Repositories/UserRepository.cs +++ b/Devnot.Mentor.Api/Repositories/UserRepository.cs @@ -19,10 +19,20 @@ public async Task GetByIdAsync(int id) public async Task GetByIdIncludeMenteeMentorAsync(int id) { return await DbContext.User - .Include(x=>x.Mentor).Include(x=>x.Mentee) + .Include(x => x.Mentor).Include(x => x.Mentee) .Where(i => i.Id == id).FirstOrDefaultAsync(); } + public async Task AnyByUserNameAsync(string userName) + { + return await DbContext.User.Where(i => i.UserName == userName).AnyAsync(); + } + + public async Task AnyByEmailAsync(string email) + { + return await DbContext.User.Where(i => i.Email == email).AnyAsync(); + } + public async Task GetByEmailAsync(string email) { return await DbContext.User.Where(i => i.Email == email).FirstOrDefaultAsync(); @@ -42,7 +52,7 @@ public async Task GetByGitHubIdAsync(object identifier) { return await DbContext.User.Where(u => u.GitHubId == identifier.ToString()).FirstOrDefaultAsync(); } - + public async Task GetByGoogleIdAsync(object identifier) { return await DbContext.User.Where(u => u.GoogleId == identifier.ToString()).FirstOrDefaultAsync(); diff --git a/Devnot.Mentor.Api/Services/UserService.cs b/Devnot.Mentor.Api/Services/UserService.cs index ff1ac9e..fc951b8 100644 --- a/Devnot.Mentor.Api/Services/UserService.cs +++ b/Devnot.Mentor.Api/Services/UserService.cs @@ -1,4 +1,5 @@ -using AutoMapper; +using System; +using AutoMapper; using DevnotMentor.Api.Common; using DevnotMentor.Api.Entities; using DevnotMentor.Api.Repositories.Interfaces; @@ -28,53 +29,35 @@ public UserService( this.userRepository = userRepository; } - private User CreateUserForOAuthUser(OAuthUser oAuthUser) + private async Task CreateUserForOAuthUserAsync(OAuthUser oAuthUser) { - var user = mapper.Map(oAuthUser); - create: - try + var checkIsThereAnySimilarUser = await userRepository.AnyByUserNameAsync(oAuthUser.UserName); + + if (checkIsThereAnySimilarUser) { - user = userRepository.Create(user); + oAuthUser.SetRandomUsername(); } - catch (System.Exception exception) - { - var innerExMessage = exception.InnerException.Message; - bool exceptionCatched = false; - - if (innerExMessage.Contains(UniqueIndexName.UserName)) - { - exceptionCatched = true; - user.UserName = System.IO.Path.GetRandomFileName(); - } - if (innerExMessage.Contains(UniqueIndexName.Email)) - { - exceptionCatched = true; - user.Email = System.IO.Path.GetRandomFileName(); - user.EmailConfirmed = false; - } - if (exceptionCatched) - { - goto create; - } + checkIsThereAnySimilarUser = await userRepository.AnyByEmailAsync(oAuthUser.Email); - throw exception; // middleware will catch + if (checkIsThereAnySimilarUser) + { + oAuthUser.Email = null; + oAuthUser.EmailConfirmed = false; } - return user; + var user = mapper.Map(oAuthUser); + + return userRepository.Create(user); } public async Task> SignInAsync(OAuthUser oAuthUser) { - var user = oAuthUser.Type switch - { - OAuthType.GitHub => await userRepository.GetByGitHubIdAsync(oAuthUser.GitHubId), - OAuthType.Google => await userRepository.GetByGoogleIdAsync(oAuthUser.GoogleId), - }; + var user = await oAuthUser.GetUserFromDatabase(userRepository); - if (user == null) + if (user is null) { - user = CreateUserForOAuthUser(oAuthUser); + user = await CreateUserForOAuthUserAsync(oAuthUser); } return new SuccessApiResponse(data: tokenService.CreateToken(user.Id, user.UserName), ResultMessage.Success); diff --git a/Devnot.Mentor.Api/Utilities/OAuthService.cs b/Devnot.Mentor.Api/Utilities/OAuthService.cs index 57c2b04..6185c45 100644 --- a/Devnot.Mentor.Api/Utilities/OAuthService.cs +++ b/Devnot.Mentor.Api/Utilities/OAuthService.cs @@ -1,6 +1,8 @@ using System.Threading.Tasks; using System.Net.Http; using System.Net.Http.Headers; +using DevnotMentor.Api.CustomEntities.Auth; +using DevnotMentor.Api.CustomEntities.Auth.Response; using Newtonsoft.Json; using DevnotMentor.Api.CustomEntities.OAuth; using Microsoft.AspNetCore.Authentication.OAuth; @@ -12,7 +14,19 @@ namespace DevnotMentor.Api.Utilities { public static class OAuthService { - public static async Task GetOAuthUserAsync(OAuthType oAuthType, OAuthCreatingTicketContext ctx) + public static async Task GetOAuthGitHubUserAsync(OAuthCreatingTicketContext ctx) + { + var authGitHubResponse = await GetOAuthUserAsync(ctx); + return authGitHubResponse.MapToOAuthGitHubUser(); + } + + public static async Task GetOAuthGoogleUserAsync(OAuthCreatingTicketContext ctx) + { + var authGoogleResponse = await GetOAuthUserAsync(ctx); + return authGoogleResponse.MapToOAuthGoogleUser(); + } + + public static async Task GetOAuthUserAsync(OAuthCreatingTicketContext ctx) { var request = new HttpRequestMessage(HttpMethod.Get, ctx.Options.UserInformationEndpoint); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", ctx.AccessToken); @@ -20,9 +34,7 @@ public static async Task GetOAuthUserAsync(OAuthType oAuthType, OAuth var response = await ctx.Backchannel.SendAsync(request, ctx.HttpContext.RequestAborted); response.EnsureSuccessStatusCode(); - var oauthResponse = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - - return new OAuthUser(oauthResponse, oAuthType); + return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); } public static async Task SignInAsync(OAuthUser oAuthUser, HttpContext httpContext) From f19e16818ad0fd6e94ece93b8ee449c4e640b052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20Y=C4=B1lmaz?= Date: Sat, 24 Jul 2021 04:02:12 +0300 Subject: [PATCH 26/36] =?UTF-8?q?refactoring:=20namespace=20d=C3=BCzeltild?= =?UTF-8?q?i.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Devnot.Mentor.Api/CustomEntities/Auth/OAuthGitHubUser.cs | 1 - Devnot.Mentor.Api/CustomEntities/Auth/OAuthGoogleUser.cs | 1 - Devnot.Mentor.Api/CustomEntities/Auth/OAuthType.cs | 2 +- Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs | 2 +- .../Helpers/Extensions/ServiceCollectionExtensions.cs | 1 - Devnot.Mentor.Api/Helpers/MappingProfile.cs | 2 +- Devnot.Mentor.Api/Services/Interfaces/IUserService.cs | 2 +- Devnot.Mentor.Api/Services/UserService.cs | 5 ++--- Devnot.Mentor.Api/Utilities/OAuthService.cs | 1 - 9 files changed, 6 insertions(+), 11 deletions(-) diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGitHubUser.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGitHubUser.cs index 37a55a6..f90a891 100644 --- a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGitHubUser.cs +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGitHubUser.cs @@ -1,6 +1,5 @@ using System; using System.Threading.Tasks; -using DevnotMentor.Api.CustomEntities.OAuth; using DevnotMentor.Api.Entities; using DevnotMentor.Api.Repositories.Interfaces; diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGoogleUser.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGoogleUser.cs index 05c3cdd..a776011 100644 --- a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGoogleUser.cs +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGoogleUser.cs @@ -1,6 +1,5 @@ using System; using System.Threading.Tasks; -using DevnotMentor.Api.CustomEntities.OAuth; using DevnotMentor.Api.Entities; using DevnotMentor.Api.Repositories.Interfaces; diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthType.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthType.cs index f7c75a1..6950659 100644 --- a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthType.cs +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthType.cs @@ -1,4 +1,4 @@ -namespace DevnotMentor.Api.CustomEntities.OAuth +namespace DevnotMentor.Api.CustomEntities.Auth { public enum OAuthType : int { diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs index e73140b..f8ffd13 100644 --- a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs @@ -3,7 +3,7 @@ using DevnotMentor.Api.Entities; using DevnotMentor.Api.Repositories.Interfaces; -namespace DevnotMentor.Api.CustomEntities.OAuth +namespace DevnotMentor.Api.CustomEntities.Auth { public abstract class OAuthUser { diff --git a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs index 529a064..89c2aa1 100644 --- a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs +++ b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs @@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.AspNetCore.Authentication.Cookies; -using DevnotMentor.Api.CustomEntities.OAuth; using DevnotMentor.Api.Services; using DevnotMentor.Api.Utilities.Email; using DevnotMentor.Api.Utilities.Security.Token; diff --git a/Devnot.Mentor.Api/Helpers/MappingProfile.cs b/Devnot.Mentor.Api/Helpers/MappingProfile.cs index d7c0367..ceff160 100644 --- a/Devnot.Mentor.Api/Helpers/MappingProfile.cs +++ b/Devnot.Mentor.Api/Helpers/MappingProfile.cs @@ -1,6 +1,6 @@ using AutoMapper; +using DevnotMentor.Api.CustomEntities.Auth; using DevnotMentor.Api.CustomEntities.Dto; -using DevnotMentor.Api.CustomEntities.OAuth; using DevnotMentor.Api.CustomEntities.Request.MenteeRequest; using DevnotMentor.Api.CustomEntities.Request.MentorRequest; using DevnotMentor.Api.Entities; diff --git a/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs b/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs index 6525ce7..28cf745 100644 --- a/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs +++ b/Devnot.Mentor.Api/Services/Interfaces/IUserService.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using DevnotMentor.Api.Common.Response; +using DevnotMentor.Api.CustomEntities.Auth; using DevnotMentor.Api.CustomEntities.Dto; -using DevnotMentor.Api.CustomEntities.OAuth; using DevnotMentor.Api.Utilities.Security.Token; namespace DevnotMentor.Api.Services.Interfaces diff --git a/Devnot.Mentor.Api/Services/UserService.cs b/Devnot.Mentor.Api/Services/UserService.cs index fc951b8..fa47b3d 100644 --- a/Devnot.Mentor.Api/Services/UserService.cs +++ b/Devnot.Mentor.Api/Services/UserService.cs @@ -1,5 +1,4 @@ -using System; -using AutoMapper; +using AutoMapper; using DevnotMentor.Api.Common; using DevnotMentor.Api.Entities; using DevnotMentor.Api.Repositories.Interfaces; @@ -8,7 +7,7 @@ using System.Threading.Tasks; using DevnotMentor.Api.Common.Response; using DevnotMentor.Api.Configuration.Context; -using DevnotMentor.Api.CustomEntities.OAuth; +using DevnotMentor.Api.CustomEntities.Auth; using DevnotMentor.Api.CustomEntities.Dto; namespace DevnotMentor.Api.Services diff --git a/Devnot.Mentor.Api/Utilities/OAuthService.cs b/Devnot.Mentor.Api/Utilities/OAuthService.cs index 6185c45..289516f 100644 --- a/Devnot.Mentor.Api/Utilities/OAuthService.cs +++ b/Devnot.Mentor.Api/Utilities/OAuthService.cs @@ -4,7 +4,6 @@ using DevnotMentor.Api.CustomEntities.Auth; using DevnotMentor.Api.CustomEntities.Auth.Response; using Newtonsoft.Json; -using DevnotMentor.Api.CustomEntities.OAuth; using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.AspNetCore.Http; using DevnotMentor.Api.Services.Interfaces; From 18273cb25a3efd81d8930caa01c0410f3c91d2db Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Sat, 24 Jul 2021 16:13:14 +0300 Subject: [PATCH 27/36] Change column has index filters, Update comments, Set CreatedAt value while creating User --- .../CustomEntities/Auth/OAuthGitHubUser.cs | 2 +- .../CustomEntities/Auth/OAuthGoogleUser.cs | 2 +- .../CustomEntities/Auth/OAuthUser.cs | 7 +++--- Devnot.Mentor.Api/Entities/MentorDBContext.cs | 14 +++++++----- Devnot.Mentor.Api/Entities/User.cs | 2 +- Devnot.Mentor.Api/Helpers/MappingProfile.cs | 22 +++++++++---------- Devnot.Mentor.Api/Services/UserService.cs | 10 ++++----- 7 files changed, 31 insertions(+), 28 deletions(-) diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGitHubUser.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGitHubUser.cs index f90a891..78b939b 100644 --- a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGitHubUser.cs +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGitHubUser.cs @@ -11,7 +11,7 @@ public OAuthGitHubUser() : base(OAuthType.GitHub) { } - public override async Task GetUserFromDatabase(IUserRepository repository) + public override async Task GetUserFromDatabaseAsync(IUserRepository repository) { if (repository is null) { diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGoogleUser.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGoogleUser.cs index a776011..e4ac140 100644 --- a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGoogleUser.cs +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthGoogleUser.cs @@ -11,7 +11,7 @@ public OAuthGoogleUser() : base(OAuthType.Google) { } - public override async Task GetUserFromDatabase(IUserRepository repository) + public override async Task GetUserFromDatabaseAsync(IUserRepository repository) { if (repository is null) { diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs index f8ffd13..c66c4f5 100644 --- a/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs +++ b/Devnot.Mentor.Api/CustomEntities/Auth/OAuthUser.cs @@ -12,9 +12,8 @@ protected OAuthUser(OAuthType providerType) OAuthProviderType = providerType; } - /// - /// O an ki provider'a ait kullanýcý id deðeri. + /// Provider ID /// public string Id { get; set; } public string FullName { get; set; } @@ -25,10 +24,10 @@ protected OAuthUser(OAuthType providerType) public OAuthType OAuthProviderType { get; private set; } /// - /// Provider kullanýcýsýnýn bilgilerine göre veri tabanýndan ilgili kullanýcýyý getirir. + /// Get User by Provider /// /// - public abstract Task GetUserFromDatabase(IUserRepository repository); + public abstract Task GetUserFromDatabaseAsync(IUserRepository repository); public void SetRandomUsername() { diff --git a/Devnot.Mentor.Api/Entities/MentorDBContext.cs b/Devnot.Mentor.Api/Entities/MentorDBContext.cs index de56548..12929d3 100644 --- a/Devnot.Mentor.Api/Entities/MentorDBContext.cs +++ b/Devnot.Mentor.Api/Entities/MentorDBContext.cs @@ -294,17 +294,21 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { entity.HasKey(p => p.Id); - entity.HasIndex(p => p.UserName).HasDatabaseName(UniqueIndexName.UserName).IsUnique(); + entity.HasIndex(p => p.UserName).IsUnique(); + /* + has filter null, it removes filters and allows null to be unique. + */ + entity.HasIndex(p => p.Email).IsUnique().HasFilter(null); + entity.HasIndex(p => p.GitHubId).IsUnique().HasFilter(null); + entity.HasIndex(p => p.GoogleId).IsUnique().HasFilter(null); entity.Property(p => p.GitHubId) .HasMaxLength(64) - .IsUnicode(false) - .IsRequired(false); + .IsUnicode(false); entity.Property(p => p.GoogleId) .HasMaxLength(64) - .IsUnicode(false) - .IsRequired(false); + .IsUnicode(false); entity.Property(p => p.Email) .HasMaxLength(254) diff --git a/Devnot.Mentor.Api/Entities/User.cs b/Devnot.Mentor.Api/Entities/User.cs index 1705839..71dd682 100644 --- a/Devnot.Mentor.Api/Entities/User.cs +++ b/Devnot.Mentor.Api/Entities/User.cs @@ -19,7 +19,7 @@ public User() public string FullName { get; set; } public string ProfilePictureUrl { get; set; } public bool EmailConfirmed { get; set; } - public DateTime CreatedAt { get; set; } + public DateTime CreatedAt { get; private set; } = DateTime.Now; public virtual ICollection Mentee { get; set; } public virtual ICollection Mentor { get; set; } diff --git a/Devnot.Mentor.Api/Helpers/MappingProfile.cs b/Devnot.Mentor.Api/Helpers/MappingProfile.cs index ceff160..434a3e6 100644 --- a/Devnot.Mentor.Api/Helpers/MappingProfile.cs +++ b/Devnot.Mentor.Api/Helpers/MappingProfile.cs @@ -16,17 +16,17 @@ public MappingProfile() CreateMap(); CreateMap() - .ForMember(user => user.GitHubId, opt => - { - opt.Condition(oAuthUser => oAuthUser.OAuthProviderType == OAuthType.GitHub); - opt.MapFrom(oAuthUser => oAuthUser.Id); - }) - .ForMember(user => user.GoogleId, opt => - { - opt.Condition(oAuthUser => oAuthUser.OAuthProviderType == OAuthType.Google); - opt.MapFrom(oAuthUser => oAuthUser.Id); - }) - .ForMember(user => user.Id, opt => opt.Ignore()); + .ForMember(user => user.GitHubId, opt => // if OAuthUser type is GitHub, map Id to GitHubId + { + opt.Condition(oAuthUser => oAuthUser.OAuthProviderType == OAuthType.GitHub); + opt.MapFrom(oAuthUser => oAuthUser.Id); + }) + .ForMember(user => user.GoogleId, opt => // if OAuthUser type is Google, map Id to GoogleId + { + opt.Condition(oAuthUser => oAuthUser.OAuthProviderType == OAuthType.Google); + opt.MapFrom(oAuthUser => oAuthUser.Id); + }) + .ForMember(user => user.Id, opt => opt.Ignore()); CreateMap(); diff --git a/Devnot.Mentor.Api/Services/UserService.cs b/Devnot.Mentor.Api/Services/UserService.cs index fa47b3d..4575dc9 100644 --- a/Devnot.Mentor.Api/Services/UserService.cs +++ b/Devnot.Mentor.Api/Services/UserService.cs @@ -30,16 +30,16 @@ public UserService( private async Task CreateUserForOAuthUserAsync(OAuthUser oAuthUser) { - var checkIsThereAnySimilarUser = await userRepository.AnyByUserNameAsync(oAuthUser.UserName); + var anySimilarUser = await userRepository.AnyByUserNameAsync(oAuthUser.UserName); - if (checkIsThereAnySimilarUser) + if (anySimilarUser) { oAuthUser.SetRandomUsername(); } - checkIsThereAnySimilarUser = await userRepository.AnyByEmailAsync(oAuthUser.Email); + anySimilarUser = await userRepository.AnyByEmailAsync(oAuthUser.Email); - if (checkIsThereAnySimilarUser) + if (anySimilarUser) { oAuthUser.Email = null; oAuthUser.EmailConfirmed = false; @@ -52,7 +52,7 @@ private async Task CreateUserForOAuthUserAsync(OAuthUser oAuthUser) public async Task> SignInAsync(OAuthUser oAuthUser) { - var user = await oAuthUser.GetUserFromDatabase(userRepository); + var user = await oAuthUser.GetUserFromDatabaseAsync(userRepository); if (user is null) { From 15dabd9a425826d5b5f14c6eb51d2ff618b67625 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Sat, 24 Jul 2021 17:15:20 +0300 Subject: [PATCH 28/36] Fix not passing filter. Remove unused codes --- .../ActionFilters/TokenAuthentication.cs | 2 +- Devnot.Mentor.Api/Common/UniqueIndexName.cs | 8 ------ .../Entities/MentorApplications.cs | 1 - Devnot.Mentor.Api/Entities/MentorDBContext.cs | 26 +++---------------- Devnot.Mentor.Api/Startup.cs | 2 -- 5 files changed, 5 insertions(+), 34 deletions(-) delete mode 100644 Devnot.Mentor.Api/Common/UniqueIndexName.cs diff --git a/Devnot.Mentor.Api/ActionFilters/TokenAuthentication.cs b/Devnot.Mentor.Api/ActionFilters/TokenAuthentication.cs index 489c7e3..f6da6b9 100644 --- a/Devnot.Mentor.Api/ActionFilters/TokenAuthentication.cs +++ b/Devnot.Mentor.Api/ActionFilters/TokenAuthentication.cs @@ -54,7 +54,7 @@ public override void OnActionExecuting(ActionExecutingContext context) context.HttpContext.User = claimsPrincipal; } - catch (Exception ex) + catch (Exception) { context.Result = new UnauthorizedObjectResult(new ErrorApiResponse(ResultMessage.InvalidToken)); } diff --git a/Devnot.Mentor.Api/Common/UniqueIndexName.cs b/Devnot.Mentor.Api/Common/UniqueIndexName.cs deleted file mode 100644 index d682b97..0000000 --- a/Devnot.Mentor.Api/Common/UniqueIndexName.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace DevnotMentor.Api.Common -{ - public static class UniqueIndexName - { - public static string UserName => "unique_username"; - public static string Email => "unique_email"; - } -} \ No newline at end of file diff --git a/Devnot.Mentor.Api/Entities/MentorApplications.cs b/Devnot.Mentor.Api/Entities/MentorApplications.cs index 5bc62a3..7ad2297 100644 --- a/Devnot.Mentor.Api/Entities/MentorApplications.cs +++ b/Devnot.Mentor.Api/Entities/MentorApplications.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; namespace DevnotMentor.Api.Entities { diff --git a/Devnot.Mentor.Api/Entities/MentorDBContext.cs b/Devnot.Mentor.Api/Entities/MentorDBContext.cs index 12929d3..2977289 100644 --- a/Devnot.Mentor.Api/Entities/MentorDBContext.cs +++ b/Devnot.Mentor.Api/Entities/MentorDBContext.cs @@ -1,15 +1,9 @@ -using System; -using DevnotMentor.Api.Common; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; namespace DevnotMentor.Api.Entities { public partial class MentorDBContext : DbContext { - public MentorDBContext() - { - } - public MentorDBContext(DbContextOptions options) : base(options) { @@ -31,15 +25,6 @@ public MentorDBContext(DbContextOptions options) public virtual DbSet Tag { get; set; } public virtual DbSet User { get; set; } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - if (!optionsBuilder.IsConfigured) - { -#warning To protect potentially sensitive information in your connection string, you should move it out of source code. See http://go.microsoft.com/fwlink/?LinkId=723263 for guidance on storing connection strings. - optionsBuilder.UseSqlServer("Server=DESKTOP-8TSS65S;Database=MentorDB;Trusted_Connection=True;MultipleActiveResultSets=true"); - } - } - protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(entity => @@ -295,12 +280,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.HasKey(p => p.Id); entity.HasIndex(p => p.UserName).IsUnique(); - /* - has filter null, it removes filters and allows null to be unique. - */ - entity.HasIndex(p => p.Email).IsUnique().HasFilter(null); - entity.HasIndex(p => p.GitHubId).IsUnique().HasFilter(null); - entity.HasIndex(p => p.GoogleId).IsUnique().HasFilter(null); + entity.HasIndex(p => p.Email).IsUnique().HasFilter("[Email] IS NOT NULL"); + entity.HasIndex(p => p.GitHubId).IsUnique().HasFilter("[GitHubId] IS NOT NULL"); + entity.HasIndex(p => p.GoogleId).IsUnique().HasFilter("[GoogleId] IS NOT NULL"); entity.Property(p => p.GitHubId) .HasMaxLength(64) diff --git a/Devnot.Mentor.Api/Startup.cs b/Devnot.Mentor.Api/Startup.cs index 873e485..1a0b5f3 100644 --- a/Devnot.Mentor.Api/Startup.cs +++ b/Devnot.Mentor.Api/Startup.cs @@ -59,8 +59,6 @@ public void ConfigureServices(IServiceCollection services) }); } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) From 723dc7cd255167c265bdf033d0be3f3ea79fd25b Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Sun, 25 Jul 2021 03:01:19 +0300 Subject: [PATCH 29/36] Add: Access to Emails for GitHub --- .../Auth/Response/OAuthGitHubEmailResponse.cs | 9 +++++++++ .../Auth/Response/OAuthGitHubResponse.cs | 10 ++++++---- .../Extensions/ServiceCollectionExtensions.cs | 2 +- Devnot.Mentor.Api/Utilities/OAuthService.cs | 20 ++++++++++++++++--- 4 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 Devnot.Mentor.Api/CustomEntities/Auth/Response/OAuthGitHubEmailResponse.cs diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/Response/OAuthGitHubEmailResponse.cs b/Devnot.Mentor.Api/CustomEntities/Auth/Response/OAuthGitHubEmailResponse.cs new file mode 100644 index 0000000..07964a5 --- /dev/null +++ b/Devnot.Mentor.Api/CustomEntities/Auth/Response/OAuthGitHubEmailResponse.cs @@ -0,0 +1,9 @@ +namespace DevnotMentor.Api.CustomEntities.Auth.Response +{ + public class OAuthGitHubEmailResponse + { + public string email { get; set; } + public bool primary { get; set; } + public bool verified { get; set; } + } +} \ No newline at end of file diff --git a/Devnot.Mentor.Api/CustomEntities/Auth/Response/OAuthGitHubResponse.cs b/Devnot.Mentor.Api/CustomEntities/Auth/Response/OAuthGitHubResponse.cs index 6c9cfda..2555bdd 100644 --- a/Devnot.Mentor.Api/CustomEntities/Auth/Response/OAuthGitHubResponse.cs +++ b/Devnot.Mentor.Api/CustomEntities/Auth/Response/OAuthGitHubResponse.cs @@ -1,4 +1,4 @@ -using System; +using System.Collections.Generic; namespace DevnotMentor.Api.CustomEntities.Auth.Response { @@ -6,20 +6,22 @@ public class OAuthGitHubResponse { public string id { get; set; } public string name { get; set; } - public string email { get; set; } public string login { get; set; } public string avatar_url { get; set; } + public List Emails { get; set; } public OAuthGitHubUser MapToOAuthGitHubUser() { + var primaryEmail = Emails.Find(x => x.primary == true); + return new OAuthGitHubUser() { - Email = email, + Email = primaryEmail?.email, Id = id, FullName = name, ProfilePictureUrl = avatar_url, UserName = login, - EmailConfirmed = (!String.IsNullOrEmpty(email)) + EmailConfirmed = primaryEmail is null ? false : primaryEmail.verified }; } } diff --git a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs index 89c2aa1..3301fe1 100644 --- a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs +++ b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs @@ -66,6 +66,7 @@ public static IServiceCollection AddCustomAuthentication(this IServiceCollection { options.ClientId = EnvironmentService.StaticConfiguration["GitHub:Client:ID"]; options.ClientSecret = EnvironmentService.StaticConfiguration["GitHub:Client:Secret"]; + options.Scope.Add("user:email"); options.CallbackPath = new PathString("/auth/github/"); options.Events = new OAuthEvents @@ -73,7 +74,6 @@ public static IServiceCollection AddCustomAuthentication(this IServiceCollection OnCreatingTicket = async ctx => { var oAuthGitHubUser = await OAuthService.GetOAuthGitHubUserAsync(ctx); - await OAuthService.SignInAsync(oAuthGitHubUser, ctx.HttpContext); } }; diff --git a/Devnot.Mentor.Api/Utilities/OAuthService.cs b/Devnot.Mentor.Api/Utilities/OAuthService.cs index 289516f..7c5a991 100644 --- a/Devnot.Mentor.Api/Utilities/OAuthService.cs +++ b/Devnot.Mentor.Api/Utilities/OAuthService.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Http; using DevnotMentor.Api.Services.Interfaces; using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; namespace DevnotMentor.Api.Utilities { @@ -15,17 +16,30 @@ public static class OAuthService { public static async Task GetOAuthGitHubUserAsync(OAuthCreatingTicketContext ctx) { - var authGitHubResponse = await GetOAuthUserAsync(ctx); + var authGitHubResponse = await GetOAuthUserPublicInformationAsync(ctx); + authGitHubResponse.Emails = await GetGitHubEmailsAsync(ctx); + return authGitHubResponse.MapToOAuthGitHubUser(); } public static async Task GetOAuthGoogleUserAsync(OAuthCreatingTicketContext ctx) { - var authGoogleResponse = await GetOAuthUserAsync(ctx); + var authGoogleResponse = await GetOAuthUserPublicInformationAsync(ctx); return authGoogleResponse.MapToOAuthGoogleUser(); } - public static async Task GetOAuthUserAsync(OAuthCreatingTicketContext ctx) + public static async Task> GetGitHubEmailsAsync(OAuthCreatingTicketContext ctx) + { + var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/user/emails"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", ctx.AccessToken); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var response = await ctx.Backchannel.SendAsync(request, ctx.HttpContext.RequestAborted); + response.EnsureSuccessStatusCode(); + + return JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); + } + + public static async Task GetOAuthUserPublicInformationAsync(OAuthCreatingTicketContext ctx) { var request = new HttpRequestMessage(HttpMethod.Get, ctx.Options.UserInformationEndpoint); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", ctx.AccessToken); From 9404c1bacdeaf999dfa320072d5b5b730aa33225 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Mon, 26 Jul 2021 03:02:59 +0300 Subject: [PATCH 30/36] Add GetResponseContentAsStringAsync to get responses from one point --- Devnot.Mentor.Api/Utilities/OAuthService.cs | 45 +++++++++++---------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/Devnot.Mentor.Api/Utilities/OAuthService.cs b/Devnot.Mentor.Api/Utilities/OAuthService.cs index 7c5a991..7f1579a 100644 --- a/Devnot.Mentor.Api/Utilities/OAuthService.cs +++ b/Devnot.Mentor.Api/Utilities/OAuthService.cs @@ -14,40 +14,43 @@ namespace DevnotMentor.Api.Utilities { public static class OAuthService { - public static async Task GetOAuthGitHubUserAsync(OAuthCreatingTicketContext ctx) + private static async Task GetResponseContentAsStringAsync(HttpRequestMessage request, OAuthCreatingTicketContext ctx) { - var authGitHubResponse = await GetOAuthUserPublicInformationAsync(ctx); - authGitHubResponse.Emails = await GetGitHubEmailsAsync(ctx); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", ctx.AccessToken); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var response = await ctx.Backchannel.SendAsync(request, ctx.HttpContext.RequestAborted); - return authGitHubResponse.MapToOAuthGitHubUser(); + return await response.Content.ReadAsStringAsync(); } - public static async Task GetOAuthGoogleUserAsync(OAuthCreatingTicketContext ctx) + private static async Task GetUserPublicInformationsAsync(OAuthCreatingTicketContext ctx) { - var authGoogleResponse = await GetOAuthUserPublicInformationAsync(ctx); - return authGoogleResponse.MapToOAuthGoogleUser(); + var publicInfoRequest = new HttpRequestMessage(HttpMethod.Get, ctx.Options.UserInformationEndpoint); + var publicInfoResponse = await GetResponseContentAsStringAsync(publicInfoRequest, ctx); + + return JsonConvert.DeserializeObject(publicInfoResponse); } - public static async Task> GetGitHubEmailsAsync(OAuthCreatingTicketContext ctx) + private static async Task> GetGitHubEmailsAsync(OAuthCreatingTicketContext ctx) { - var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/user/emails"); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", ctx.AccessToken); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - var response = await ctx.Backchannel.SendAsync(request, ctx.HttpContext.RequestAborted); - response.EnsureSuccessStatusCode(); + var emailRequest = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/user/emails"); + var emailResponse = await GetResponseContentAsStringAsync(emailRequest, ctx); - return JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); + return JsonConvert.DeserializeObject>(emailResponse); } - public static async Task GetOAuthUserPublicInformationAsync(OAuthCreatingTicketContext ctx) + public static async Task GetOAuthGitHubUserAsync(OAuthCreatingTicketContext ctx) { - var request = new HttpRequestMessage(HttpMethod.Get, ctx.Options.UserInformationEndpoint); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", ctx.AccessToken); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - var response = await ctx.Backchannel.SendAsync(request, ctx.HttpContext.RequestAborted); - response.EnsureSuccessStatusCode(); + var authGitHubResponse = await GetUserPublicInformationsAsync(ctx); + authGitHubResponse.Emails = await GetGitHubEmailsAsync(ctx); + + return authGitHubResponse.MapToOAuthGitHubUser(); + } - return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + public static async Task GetOAuthGoogleUserAsync(OAuthCreatingTicketContext ctx) + { + var authGoogleResponse = await GetUserPublicInformationsAsync(ctx); + return authGoogleResponse.MapToOAuthGoogleUser(); } public static async Task SignInAsync(OAuthUser oAuthUser, HttpContext httpContext) From 5dd6496f7e8e7244763ebd16237c5f8dc2aed2cb Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Mon, 26 Jul 2021 03:33:17 +0300 Subject: [PATCH 31/36] Move response methods to OAuthServiceResponse, change parameter names --- .../Extensions/ServiceCollectionExtensions.cs | 12 +++--- Devnot.Mentor.Api/Utilities/OAuthService.cs | 39 +++---------------- .../Utilities/OAuthServiceResponse.cs | 38 ++++++++++++++++++ 3 files changed, 49 insertions(+), 40 deletions(-) create mode 100644 Devnot.Mentor.Api/Utilities/OAuthServiceResponse.cs diff --git a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs index 3301fe1..0916f05 100644 --- a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs +++ b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs @@ -71,10 +71,10 @@ public static IServiceCollection AddCustomAuthentication(this IServiceCollection options.Events = new OAuthEvents { - OnCreatingTicket = async ctx => + OnCreatingTicket = async creatinTicketContext => { - var oAuthGitHubUser = await OAuthService.GetOAuthGitHubUserAsync(ctx); - await OAuthService.SignInAsync(oAuthGitHubUser, ctx.HttpContext); + var oAuthGitHubUser = await OAuthService.GetOAuthGitHubUserAsync(creatinTicketContext); + await OAuthService.SignInAsync(oAuthGitHubUser, creatinTicketContext.HttpContext); } }; }) @@ -86,10 +86,10 @@ public static IServiceCollection AddCustomAuthentication(this IServiceCollection options.Events = new OAuthEvents { - OnCreatingTicket = async ctx => + OnCreatingTicket = async creatinTicketContext => { - var oAuthGoogleUser = await OAuthService.GetOAuthGoogleUserAsync(ctx); - await OAuthService.SignInAsync(oAuthGoogleUser, ctx.HttpContext); + var oAuthGoogleUser = await OAuthService.GetOAuthGoogleUserAsync(creatinTicketContext); + await OAuthService.SignInAsync(oAuthGoogleUser, creatinTicketContext.HttpContext); } }; }); diff --git a/Devnot.Mentor.Api/Utilities/OAuthService.cs b/Devnot.Mentor.Api/Utilities/OAuthService.cs index 7f1579a..e585777 100644 --- a/Devnot.Mentor.Api/Utilities/OAuthService.cs +++ b/Devnot.Mentor.Api/Utilities/OAuthService.cs @@ -1,55 +1,26 @@ using System.Threading.Tasks; -using System.Net.Http; -using System.Net.Http.Headers; using DevnotMentor.Api.CustomEntities.Auth; using DevnotMentor.Api.CustomEntities.Auth.Response; -using Newtonsoft.Json; using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.AspNetCore.Http; using DevnotMentor.Api.Services.Interfaces; using Microsoft.Extensions.DependencyInjection; -using System.Collections.Generic; namespace DevnotMentor.Api.Utilities { public static class OAuthService { - private static async Task GetResponseContentAsStringAsync(HttpRequestMessage request, OAuthCreatingTicketContext ctx) + public static async Task GetOAuthGitHubUserAsync(OAuthCreatingTicketContext creatinTicketContext) { - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", ctx.AccessToken); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - var response = await ctx.Backchannel.SendAsync(request, ctx.HttpContext.RequestAborted); - - return await response.Content.ReadAsStringAsync(); - } - - private static async Task GetUserPublicInformationsAsync(OAuthCreatingTicketContext ctx) - { - var publicInfoRequest = new HttpRequestMessage(HttpMethod.Get, ctx.Options.UserInformationEndpoint); - var publicInfoResponse = await GetResponseContentAsStringAsync(publicInfoRequest, ctx); - - return JsonConvert.DeserializeObject(publicInfoResponse); - } - - private static async Task> GetGitHubEmailsAsync(OAuthCreatingTicketContext ctx) - { - var emailRequest = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/user/emails"); - var emailResponse = await GetResponseContentAsStringAsync(emailRequest, ctx); - - return JsonConvert.DeserializeObject>(emailResponse); - } - - public static async Task GetOAuthGitHubUserAsync(OAuthCreatingTicketContext ctx) - { - var authGitHubResponse = await GetUserPublicInformationsAsync(ctx); - authGitHubResponse.Emails = await GetGitHubEmailsAsync(ctx); + var authGitHubResponse = await OAuthServiceResponse.GetUserPublicInformationsAsync(creatinTicketContext); + authGitHubResponse.Emails = await OAuthServiceResponse.GetGitHubEmailsAsync(creatinTicketContext); return authGitHubResponse.MapToOAuthGitHubUser(); } - public static async Task GetOAuthGoogleUserAsync(OAuthCreatingTicketContext ctx) + public static async Task GetOAuthGoogleUserAsync(OAuthCreatingTicketContext creatinTicketContext) { - var authGoogleResponse = await GetUserPublicInformationsAsync(ctx); + var authGoogleResponse = await OAuthServiceResponse.GetUserPublicInformationsAsync(creatinTicketContext); return authGoogleResponse.MapToOAuthGoogleUser(); } diff --git a/Devnot.Mentor.Api/Utilities/OAuthServiceResponse.cs b/Devnot.Mentor.Api/Utilities/OAuthServiceResponse.cs new file mode 100644 index 0000000..ba310a4 --- /dev/null +++ b/Devnot.Mentor.Api/Utilities/OAuthServiceResponse.cs @@ -0,0 +1,38 @@ +using System.Threading.Tasks; +using System.Net.Http; +using System.Net.Http.Headers; +using DevnotMentor.Api.CustomEntities.Auth.Response; +using Newtonsoft.Json; +using Microsoft.AspNetCore.Authentication.OAuth; +using System.Collections.Generic; + +namespace DevnotMentor.Api.Utilities +{ + public static class OAuthServiceResponse + { + public static async Task GetResponseContentAsStringAsync(HttpRequestMessage requestWithMethodAndURI, OAuthCreatingTicketContext creatinTicketContext) + { + requestWithMethodAndURI.Headers.Authorization = new AuthenticationHeaderValue("Bearer", creatinTicketContext.AccessToken); + requestWithMethodAndURI.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var response = await creatinTicketContext.Backchannel.SendAsync(requestWithMethodAndURI, creatinTicketContext.HttpContext.RequestAborted); + + return await response.Content.ReadAsStringAsync(); + } + + public static async Task GetUserPublicInformationsAsync(OAuthCreatingTicketContext creatinTicketContext) + { + var publicInfoRequest = new HttpRequestMessage(HttpMethod.Get, creatinTicketContext.Options.UserInformationEndpoint); + var publicInfoResponse = await GetResponseContentAsStringAsync(publicInfoRequest, creatinTicketContext); + + return JsonConvert.DeserializeObject(publicInfoResponse); + } + + public static async Task> GetGitHubEmailsAsync(OAuthCreatingTicketContext creatinTicketContext) + { + var emailRequest = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/user/emails"); + var emailResponse = await GetResponseContentAsStringAsync(emailRequest, creatinTicketContext); + + return JsonConvert.DeserializeObject>(emailResponse); + } + } +} \ No newline at end of file From 4f4f5ae2da15edb286d3341fc4f418a59ce05623 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Mon, 26 Jul 2021 19:27:58 +0300 Subject: [PATCH 32/36] Fix misspelling, add summaries for response methods --- .../Extensions/ServiceCollectionExtensions.cs | 12 ++-- .../Utilities/OAuthResponseService.cs | 55 +++++++++++++++++++ Devnot.Mentor.Api/Utilities/OAuthService.cs | 10 ++-- .../Utilities/OAuthServiceResponse.cs | 38 ------------- 4 files changed, 66 insertions(+), 49 deletions(-) create mode 100644 Devnot.Mentor.Api/Utilities/OAuthResponseService.cs delete mode 100644 Devnot.Mentor.Api/Utilities/OAuthServiceResponse.cs diff --git a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs index 0916f05..6c3b7bc 100644 --- a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs +++ b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs @@ -71,10 +71,10 @@ public static IServiceCollection AddCustomAuthentication(this IServiceCollection options.Events = new OAuthEvents { - OnCreatingTicket = async creatinTicketContext => + OnCreatingTicket = async creatingTicketContext => { - var oAuthGitHubUser = await OAuthService.GetOAuthGitHubUserAsync(creatinTicketContext); - await OAuthService.SignInAsync(oAuthGitHubUser, creatinTicketContext.HttpContext); + var oAuthGitHubUser = await OAuthService.GetOAuthGitHubUserAsync(creatingTicketContext); + await OAuthService.SignInAsync(oAuthGitHubUser, creatingTicketContext.HttpContext); } }; }) @@ -86,10 +86,10 @@ public static IServiceCollection AddCustomAuthentication(this IServiceCollection options.Events = new OAuthEvents { - OnCreatingTicket = async creatinTicketContext => + OnCreatingTicket = async creatingTicketContext => { - var oAuthGoogleUser = await OAuthService.GetOAuthGoogleUserAsync(creatinTicketContext); - await OAuthService.SignInAsync(oAuthGoogleUser, creatinTicketContext.HttpContext); + var oAuthGoogleUser = await OAuthService.GetOAuthGoogleUserAsync(creatingTicketContext); + await OAuthService.SignInAsync(oAuthGoogleUser, creatingTicketContext.HttpContext); } }; }); diff --git a/Devnot.Mentor.Api/Utilities/OAuthResponseService.cs b/Devnot.Mentor.Api/Utilities/OAuthResponseService.cs new file mode 100644 index 0000000..e5f1ff0 --- /dev/null +++ b/Devnot.Mentor.Api/Utilities/OAuthResponseService.cs @@ -0,0 +1,55 @@ +using System.Threading.Tasks; +using System.Net.Http; +using System.Net.Http.Headers; +using DevnotMentor.Api.CustomEntities.Auth.Response; +using Newtonsoft.Json; +using Microsoft.AspNetCore.Authentication.OAuth; +using System.Collections.Generic; + +namespace DevnotMentor.Api.Utilities +{ + public static class OAuthResponseService + { + /// + /// Common method to get OAuth response as string + /// + /// + /// + /// + private static async Task GetResponseContentAsStringAsync(HttpRequestMessage requestWithMethodAndURI, OAuthCreatingTicketContext creatingTicketContext) + { + requestWithMethodAndURI.Headers.Authorization = new AuthenticationHeaderValue("Bearer", creatingTicketContext.AccessToken); + requestWithMethodAndURI.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var response = await creatingTicketContext.Backchannel.SendAsync(requestWithMethodAndURI, creatingTicketContext.HttpContext.RequestAborted); + + return await response.Content.ReadAsStringAsync(); + } + + /// + /// Common method to get user public informations from OAuth providers + /// + /// + /// Generic response + /// Deserialized generic response. Example: , + public static async Task GetUserPublicInformationsAsync(OAuthCreatingTicketContext creatingTicketContext) + { + var publicInfoRequest = new HttpRequestMessage(HttpMethod.Get, creatingTicketContext.Options.UserInformationEndpoint); + var publicInfoResponse = await GetResponseContentAsStringAsync(publicInfoRequest, creatingTicketContext); + + return JsonConvert.DeserializeObject(publicInfoResponse); + } + + /// + /// Get user emails from GitHub OAuth + /// + /// + /// A list of + public static async Task> GetGitHubEmailsAsync(OAuthCreatingTicketContext creatingTicketContext) + { + var emailRequest = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/user/emails"); + var emailResponse = await GetResponseContentAsStringAsync(emailRequest, creatingTicketContext); + + return JsonConvert.DeserializeObject>(emailResponse); + } + } +} \ No newline at end of file diff --git a/Devnot.Mentor.Api/Utilities/OAuthService.cs b/Devnot.Mentor.Api/Utilities/OAuthService.cs index e585777..9a0210e 100644 --- a/Devnot.Mentor.Api/Utilities/OAuthService.cs +++ b/Devnot.Mentor.Api/Utilities/OAuthService.cs @@ -10,17 +10,17 @@ namespace DevnotMentor.Api.Utilities { public static class OAuthService { - public static async Task GetOAuthGitHubUserAsync(OAuthCreatingTicketContext creatinTicketContext) + public static async Task GetOAuthGitHubUserAsync(OAuthCreatingTicketContext creatingTicketContext) { - var authGitHubResponse = await OAuthServiceResponse.GetUserPublicInformationsAsync(creatinTicketContext); - authGitHubResponse.Emails = await OAuthServiceResponse.GetGitHubEmailsAsync(creatinTicketContext); + var authGitHubResponse = await OAuthResponseService.GetUserPublicInformationsAsync(creatingTicketContext); + authGitHubResponse.Emails = await OAuthResponseService.GetGitHubEmailsAsync(creatingTicketContext); return authGitHubResponse.MapToOAuthGitHubUser(); } - public static async Task GetOAuthGoogleUserAsync(OAuthCreatingTicketContext creatinTicketContext) + public static async Task GetOAuthGoogleUserAsync(OAuthCreatingTicketContext creatingTicketContext) { - var authGoogleResponse = await OAuthServiceResponse.GetUserPublicInformationsAsync(creatinTicketContext); + var authGoogleResponse = await OAuthResponseService.GetUserPublicInformationsAsync(creatingTicketContext); return authGoogleResponse.MapToOAuthGoogleUser(); } diff --git a/Devnot.Mentor.Api/Utilities/OAuthServiceResponse.cs b/Devnot.Mentor.Api/Utilities/OAuthServiceResponse.cs deleted file mode 100644 index ba310a4..0000000 --- a/Devnot.Mentor.Api/Utilities/OAuthServiceResponse.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Threading.Tasks; -using System.Net.Http; -using System.Net.Http.Headers; -using DevnotMentor.Api.CustomEntities.Auth.Response; -using Newtonsoft.Json; -using Microsoft.AspNetCore.Authentication.OAuth; -using System.Collections.Generic; - -namespace DevnotMentor.Api.Utilities -{ - public static class OAuthServiceResponse - { - public static async Task GetResponseContentAsStringAsync(HttpRequestMessage requestWithMethodAndURI, OAuthCreatingTicketContext creatinTicketContext) - { - requestWithMethodAndURI.Headers.Authorization = new AuthenticationHeaderValue("Bearer", creatinTicketContext.AccessToken); - requestWithMethodAndURI.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - var response = await creatinTicketContext.Backchannel.SendAsync(requestWithMethodAndURI, creatinTicketContext.HttpContext.RequestAborted); - - return await response.Content.ReadAsStringAsync(); - } - - public static async Task GetUserPublicInformationsAsync(OAuthCreatingTicketContext creatinTicketContext) - { - var publicInfoRequest = new HttpRequestMessage(HttpMethod.Get, creatinTicketContext.Options.UserInformationEndpoint); - var publicInfoResponse = await GetResponseContentAsStringAsync(publicInfoRequest, creatinTicketContext); - - return JsonConvert.DeserializeObject(publicInfoResponse); - } - - public static async Task> GetGitHubEmailsAsync(OAuthCreatingTicketContext creatinTicketContext) - { - var emailRequest = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/user/emails"); - var emailResponse = await GetResponseContentAsStringAsync(emailRequest, creatinTicketContext); - - return JsonConvert.DeserializeObject>(emailResponse); - } - } -} \ No newline at end of file From 53cbbe17866239f00d3d51d0ba745c39d4d2a48d Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Sat, 7 Aug 2021 01:04:13 +0300 Subject: [PATCH 33/36] Remove server-side social login challenging --- Devnot.Mentor.Api/DevnotMentor.Api.csproj | 3 -- .../Extensions/ServiceCollectionExtensions.cs | 42 ------------------- Devnot.Mentor.Api/Startup.cs | 7 +--- 3 files changed, 1 insertion(+), 51 deletions(-) diff --git a/Devnot.Mentor.Api/DevnotMentor.Api.csproj b/Devnot.Mentor.Api/DevnotMentor.Api.csproj index 41b0e85..6382e41 100644 --- a/Devnot.Mentor.Api/DevnotMentor.Api.csproj +++ b/Devnot.Mentor.Api/DevnotMentor.Api.csproj @@ -8,11 +8,8 @@ - - - diff --git a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs index 6c3b7bc..89812e4 100644 --- a/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs +++ b/Devnot.Mentor.Api/Helpers/Extensions/ServiceCollectionExtensions.cs @@ -3,9 +3,6 @@ using Microsoft.Extensions.DependencyInjection; using DevnotMentor.Api.ActionFilters; using DevnotMentor.Api.Configuration.Environment; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Authentication.OAuth; -using Microsoft.AspNetCore.Authentication.Cookies; using DevnotMentor.Api.Services; using DevnotMentor.Api.Utilities.Email; using DevnotMentor.Api.Utilities.Security.Token; @@ -14,7 +11,6 @@ using DevnotMentor.Api.Repositories; using DevnotMentor.Api.Utilities.Email.SmtpMail; using DevnotMentor.Api.Utilities.Security.Token.Jwt; -using DevnotMentor.Api.Utilities; namespace DevnotMentor.Api.Helpers.Extensions { @@ -58,43 +54,5 @@ public static IServiceCollection AddRepositories(this IServiceCollection service return services; } - - public static IServiceCollection AddCustomAuthentication(this IServiceCollection services) - { - services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie() - .AddGitHub(options => - { - options.ClientId = EnvironmentService.StaticConfiguration["GitHub:Client:ID"]; - options.ClientSecret = EnvironmentService.StaticConfiguration["GitHub:Client:Secret"]; - options.Scope.Add("user:email"); - options.CallbackPath = new PathString("/auth/github/"); - - options.Events = new OAuthEvents - { - OnCreatingTicket = async creatingTicketContext => - { - var oAuthGitHubUser = await OAuthService.GetOAuthGitHubUserAsync(creatingTicketContext); - await OAuthService.SignInAsync(oAuthGitHubUser, creatingTicketContext.HttpContext); - } - }; - }) - .AddGoogle(options => - { - options.ClientId = EnvironmentService.StaticConfiguration["Google:Client:ID"]; - options.ClientSecret = EnvironmentService.StaticConfiguration["Google:Client:Secret"]; - options.CallbackPath = new PathString("/auth/google/"); - - options.Events = new OAuthEvents - { - OnCreatingTicket = async creatingTicketContext => - { - var oAuthGoogleUser = await OAuthService.GetOAuthGoogleUserAsync(creatingTicketContext); - await OAuthService.SignInAsync(oAuthGoogleUser, creatingTicketContext.HttpContext); - } - }; - }); - - return services; - } } } \ No newline at end of file diff --git a/Devnot.Mentor.Api/Startup.cs b/Devnot.Mentor.Api/Startup.cs index 1a0b5f3..411556e 100644 --- a/Devnot.Mentor.Api/Startup.cs +++ b/Devnot.Mentor.Api/Startup.cs @@ -9,7 +9,6 @@ using DevnotMentor.Api.ActionFilters; using DevnotMentor.Api.Configuration.Environment; using DevnotMentor.Api.Middlewares; -using Microsoft.AspNetCore.Http; using DevnotMentor.Api.Helpers.Extensions; namespace DevnotMentor.Api @@ -33,7 +32,6 @@ public void ConfigureServices(IServiceCollection services) services.AddAutoMapper(typeof(Startup)); services.AddCustomServices(); - services.AddCustomAuthentication(); services.AddRepositories(); services.AddCors(options => @@ -65,10 +63,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseDeveloperExceptionPage(); } - app.UseCookiePolicy(new CookiePolicyOptions() - { - MinimumSameSitePolicy = SameSiteMode.Lax - }); + app.UseRouting(); app.UseAuthentication(); app.UseCors("AllowMyOrigin"); From 3818d909044e6c91144da7faf0e5a972e3f3b180 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Sat, 7 Aug 2021 01:10:41 +0300 Subject: [PATCH 34/36] Refactor for client-side social login --- .../Controllers/AuthController.cs | 31 +++++---- .../Utilities/OAuth/OAuthDefaults.cs | 12 ++++ .../Utilities/OAuth/OAuthService.cs | 63 +++++++++++++++++++ .../Utilities/OAuthResponseService.cs | 55 ---------------- Devnot.Mentor.Api/Utilities/OAuthService.cs | 42 ------------- 5 files changed, 95 insertions(+), 108 deletions(-) create mode 100644 Devnot.Mentor.Api/Utilities/OAuth/OAuthDefaults.cs create mode 100644 Devnot.Mentor.Api/Utilities/OAuth/OAuthService.cs delete mode 100644 Devnot.Mentor.Api/Utilities/OAuthResponseService.cs delete mode 100644 Devnot.Mentor.Api/Utilities/OAuthService.cs diff --git a/Devnot.Mentor.Api/Controllers/AuthController.cs b/Devnot.Mentor.Api/Controllers/AuthController.cs index cbe7604..638200c 100644 --- a/Devnot.Mentor.Api/Controllers/AuthController.cs +++ b/Devnot.Mentor.Api/Controllers/AuthController.cs @@ -1,28 +1,37 @@ -using Microsoft.AspNetCore.Authentication; +using System.Threading.Tasks; +using DevnotMentor.Api.Services.Interfaces; +using DevnotMentor.Api.Utilities.OAuth; using Microsoft.AspNetCore.Mvc; namespace DevnotMentor.Api.Controllers { public class AuthController : BaseController { - [Route("/auth/okay")] - [HttpGet] - public IActionResult OK() + private readonly IUserService _userService; + + public AuthController(IUserService userService) { - return Ok("OAuth: okay"); + _userService = userService; } [Route("/auth/github")] - [HttpGet] - public IActionResult GitHubChallenge() + [HttpPost] + public async Task GitHubAsync([FromBody] string accesToken) { - return Challenge(new AuthenticationProperties() { RedirectUri = "/auth/okay" }, "GitHub"); + var oAuthGitHubUser = await OAuthService.GetOAuthGitHubUserAsync(accesToken); + var githubSignInResponse = await _userService.SignInAsync(oAuthGitHubUser); + + return githubSignInResponse.Success ? Success(githubSignInResponse) : BadRequest(); } + [Route("/auth/google")] - [HttpGet] - public IActionResult GoogleChallenge() + [HttpPost] + public async Task GoogleAsync([FromBody] string accesToken) { - return Challenge(new AuthenticationProperties() { RedirectUri = "/auth/okay" }, "Google"); + var oAuthGoogleUser = await OAuthService.GetOAuthGoogleUserAsync(accesToken); + var googleSignInResponse = await _userService.SignInAsync(oAuthGoogleUser); + + return googleSignInResponse.Success ? Success(googleSignInResponse) : BadRequest(); } } } \ No newline at end of file diff --git a/Devnot.Mentor.Api/Utilities/OAuth/OAuthDefaults.cs b/Devnot.Mentor.Api/Utilities/OAuth/OAuthDefaults.cs new file mode 100644 index 0000000..e318e43 --- /dev/null +++ b/Devnot.Mentor.Api/Utilities/OAuth/OAuthDefaults.cs @@ -0,0 +1,12 @@ +namespace DevnotMentor.Api.Utilities.OAuth +{ + public static class OAuthDefaults + { + public static string UserAgent = "DEVNOT MENTOR"; + + public static string GoogleUserInformationEndpoint = "https://www.googleapis.com/oauth2/v2/userinfo"; + + public static string GitHubUserInformationEndpoint = "https://api.github.com/user"; + public static string GitHubUserEmailEndpoint = "https://api.github.com/user/emails"; + } +} \ No newline at end of file diff --git a/Devnot.Mentor.Api/Utilities/OAuth/OAuthService.cs b/Devnot.Mentor.Api/Utilities/OAuth/OAuthService.cs new file mode 100644 index 0000000..943fa6b --- /dev/null +++ b/Devnot.Mentor.Api/Utilities/OAuth/OAuthService.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using DevnotMentor.Api.CustomEntities.Auth; +using DevnotMentor.Api.CustomEntities.Auth.Response; +using Newtonsoft.Json; + +namespace DevnotMentor.Api.Utilities.OAuth +{ + public static class OAuthService + { + /// + /// Get provider response as deserialized + /// + /// + /// Acces token which is coming from Client + /// Generic responses + /// Deserialized generic response. Example: , + private static async Task GetOAuthResponseAsDeserializedAsync(string endpoint, string accessToken) + { + HttpClient httpClient = new HttpClient(); + + HttpRequestMessage oauthRequest = new HttpRequestMessage(HttpMethod.Get, endpoint); + oauthRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + oauthRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + oauthRequest.Headers.Add("User-Agent", OAuthDefaults.UserAgent); + + HttpResponseMessage oauthResponse = await httpClient.SendAsync(oauthRequest); + + oauthRequest.Dispose(); + httpClient.Dispose(); + + var oauthResponseContentAsString = await oauthResponse.Content.ReadAsStringAsync(); + + return oauthResponse.IsSuccessStatusCode + ? JsonConvert.DeserializeObject(oauthResponseContentAsString) + : throw new System.Exception(oauthResponseContentAsString); // todo: return meaningful message for client + } + + public static async Task GetOAuthGitHubUserAsync(string accessToken) + { + var authGitHubResponse = await GetOAuthResponseAsDeserializedAsync( + OAuthDefaults.GitHubUserInformationEndpoint, + accessToken); + + authGitHubResponse.Emails = await GetOAuthResponseAsDeserializedAsync>( + OAuthDefaults.GitHubUserEmailEndpoint, + accessToken); + + return authGitHubResponse.MapToOAuthGitHubUser(); + } + + public static async Task GetOAuthGoogleUserAsync(string accessToken) + { + var authGoogleResponse = await GetOAuthResponseAsDeserializedAsync( + OAuthDefaults.GoogleUserInformationEndpoint, + accessToken); + + return authGoogleResponse.MapToOAuthGoogleUser(); + } + } +} \ No newline at end of file diff --git a/Devnot.Mentor.Api/Utilities/OAuthResponseService.cs b/Devnot.Mentor.Api/Utilities/OAuthResponseService.cs deleted file mode 100644 index e5f1ff0..0000000 --- a/Devnot.Mentor.Api/Utilities/OAuthResponseService.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Threading.Tasks; -using System.Net.Http; -using System.Net.Http.Headers; -using DevnotMentor.Api.CustomEntities.Auth.Response; -using Newtonsoft.Json; -using Microsoft.AspNetCore.Authentication.OAuth; -using System.Collections.Generic; - -namespace DevnotMentor.Api.Utilities -{ - public static class OAuthResponseService - { - /// - /// Common method to get OAuth response as string - /// - /// - /// - /// - private static async Task GetResponseContentAsStringAsync(HttpRequestMessage requestWithMethodAndURI, OAuthCreatingTicketContext creatingTicketContext) - { - requestWithMethodAndURI.Headers.Authorization = new AuthenticationHeaderValue("Bearer", creatingTicketContext.AccessToken); - requestWithMethodAndURI.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - var response = await creatingTicketContext.Backchannel.SendAsync(requestWithMethodAndURI, creatingTicketContext.HttpContext.RequestAborted); - - return await response.Content.ReadAsStringAsync(); - } - - /// - /// Common method to get user public informations from OAuth providers - /// - /// - /// Generic response - /// Deserialized generic response. Example: , - public static async Task GetUserPublicInformationsAsync(OAuthCreatingTicketContext creatingTicketContext) - { - var publicInfoRequest = new HttpRequestMessage(HttpMethod.Get, creatingTicketContext.Options.UserInformationEndpoint); - var publicInfoResponse = await GetResponseContentAsStringAsync(publicInfoRequest, creatingTicketContext); - - return JsonConvert.DeserializeObject(publicInfoResponse); - } - - /// - /// Get user emails from GitHub OAuth - /// - /// - /// A list of - public static async Task> GetGitHubEmailsAsync(OAuthCreatingTicketContext creatingTicketContext) - { - var emailRequest = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/user/emails"); - var emailResponse = await GetResponseContentAsStringAsync(emailRequest, creatingTicketContext); - - return JsonConvert.DeserializeObject>(emailResponse); - } - } -} \ No newline at end of file diff --git a/Devnot.Mentor.Api/Utilities/OAuthService.cs b/Devnot.Mentor.Api/Utilities/OAuthService.cs deleted file mode 100644 index 9a0210e..0000000 --- a/Devnot.Mentor.Api/Utilities/OAuthService.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Threading.Tasks; -using DevnotMentor.Api.CustomEntities.Auth; -using DevnotMentor.Api.CustomEntities.Auth.Response; -using Microsoft.AspNetCore.Authentication.OAuth; -using Microsoft.AspNetCore.Http; -using DevnotMentor.Api.Services.Interfaces; -using Microsoft.Extensions.DependencyInjection; - -namespace DevnotMentor.Api.Utilities -{ - public static class OAuthService - { - public static async Task GetOAuthGitHubUserAsync(OAuthCreatingTicketContext creatingTicketContext) - { - var authGitHubResponse = await OAuthResponseService.GetUserPublicInformationsAsync(creatingTicketContext); - authGitHubResponse.Emails = await OAuthResponseService.GetGitHubEmailsAsync(creatingTicketContext); - - return authGitHubResponse.MapToOAuthGitHubUser(); - } - - public static async Task GetOAuthGoogleUserAsync(OAuthCreatingTicketContext creatingTicketContext) - { - var authGoogleResponse = await OAuthResponseService.GetUserPublicInformationsAsync(creatingTicketContext); - return authGoogleResponse.MapToOAuthGoogleUser(); - } - - public static async Task SignInAsync(OAuthUser oAuthUser, HttpContext httpContext) - { - var userService = httpContext.RequestServices.GetService(); - var signInResponse = await userService.SignInAsync(oAuthUser); - if (signInResponse.Success) - { - httpContext.Response.Headers.Add("auth-token", signInResponse.Data.Token); - httpContext.Response.Headers.Add("auth-token-expiry-date", signInResponse.Data.ExpiryDate.ToString()); - } - else - { - httpContext.Response.StatusCode = 500; - } - } - } -} \ No newline at end of file From 244c25ce07ad631a26e05182bfd92871bcc56738 Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Sat, 7 Aug 2021 23:58:40 +0300 Subject: [PATCH 35/36] Add forgotten dispose for OAuthResponse --- .../Utilities/OAuth/OAuthService.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Devnot.Mentor.Api/Utilities/OAuth/OAuthService.cs b/Devnot.Mentor.Api/Utilities/OAuth/OAuthService.cs index 943fa6b..7f5ea0c 100644 --- a/Devnot.Mentor.Api/Utilities/OAuth/OAuthService.cs +++ b/Devnot.Mentor.Api/Utilities/OAuth/OAuthService.cs @@ -26,16 +26,17 @@ private static async Task GetOAuthResponseAsDeserializedAsync(oauthResponseContentAsString) - : throw new System.Exception(oauthResponseContentAsString); // todo: return meaningful message for client + return oauthResponse.IsSuccessStatusCode + ? JsonConvert.DeserializeObject(oauthResponseContentAsString) + : throw new System.Exception(oauthResponseContentAsString); // todo: return meaningful message for client + }; } public static async Task GetOAuthGitHubUserAsync(string accessToken) From b0d7997e9c786099c7420be1b4fb324e5c101def Mon Sep 17 00:00:00 2001 From: halilkocaoz Date: Sun, 8 Aug 2021 00:08:23 +0300 Subject: [PATCH 36/36] Remove social provider sections from appsettings --- .../Configuration/appsettings.development.json | 14 +------------- .../Configuration/appsettings.production.json | 12 ------------ .../Configuration/appsettings.test.json | 12 ------------ 3 files changed, 1 insertion(+), 37 deletions(-) diff --git a/Devnot.Mentor.Api/Configuration/appsettings.development.json b/Devnot.Mentor.Api/Configuration/appsettings.development.json index aac1981..dcfcbe9 100644 --- a/Devnot.Mentor.Api/Configuration/appsettings.development.json +++ b/Devnot.Mentor.Api/Configuration/appsettings.development.json @@ -1,18 +1,6 @@ { "ConnectionStrings": { - "SQLServerConnectionString": "Server=localhost;Database=MentorDB_Test;User Id=sa; Password=yourStrongPassword!;" - }, - "GitHub": { - "Client": { - "ID": "2a0aebc08c87e25bab16", - "Secret": "3110f6222264f39f04bda81740c32ca52c9fb814" - } - }, - "Google": { - "Client": { - "ID": "879674287725-fdm9glef0qbscorrb5okplsg2fp92kpv.apps.googleusercontent.com", - "Secret": "xjuEDtdOTEm1fnYMGAe0IeSH" - } + "SQLServerConnectionString": "Server=DESKTOP-AT6H87T;Database=MentorDB;Trusted_Connection=True;MultipleActiveResultSets=true" }, "JWT": { "Secret": "c9J2Ff5Uh2ISscBYxC4NJNw7SMB9FGTQ", diff --git a/Devnot.Mentor.Api/Configuration/appsettings.production.json b/Devnot.Mentor.Api/Configuration/appsettings.production.json index 4da8be3..dcfcbe9 100644 --- a/Devnot.Mentor.Api/Configuration/appsettings.production.json +++ b/Devnot.Mentor.Api/Configuration/appsettings.production.json @@ -2,18 +2,6 @@ "ConnectionStrings": { "SQLServerConnectionString": "Server=DESKTOP-AT6H87T;Database=MentorDB;Trusted_Connection=True;MultipleActiveResultSets=true" }, - "GitHub": { - "Client": { - "ID": "-", - "Secret": "-" - } - }, - "Google": { - "Client": { - "ID": "-", - "Secret": "-" - } - }, "JWT": { "Secret": "c9J2Ff5Uh2ISscBYxC4NJNw7SMB9FGTQ", "SecretExpirationInMinutes": 1440, diff --git a/Devnot.Mentor.Api/Configuration/appsettings.test.json b/Devnot.Mentor.Api/Configuration/appsettings.test.json index 4da8be3..dcfcbe9 100644 --- a/Devnot.Mentor.Api/Configuration/appsettings.test.json +++ b/Devnot.Mentor.Api/Configuration/appsettings.test.json @@ -2,18 +2,6 @@ "ConnectionStrings": { "SQLServerConnectionString": "Server=DESKTOP-AT6H87T;Database=MentorDB;Trusted_Connection=True;MultipleActiveResultSets=true" }, - "GitHub": { - "Client": { - "ID": "-", - "Secret": "-" - } - }, - "Google": { - "Client": { - "ID": "-", - "Secret": "-" - } - }, "JWT": { "Secret": "c9J2Ff5Uh2ISscBYxC4NJNw7SMB9FGTQ", "SecretExpirationInMinutes": 1440,