Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip for dk #1

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/API/Endpoints/Auth/LoginWithGoogle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Threading;
using System.Threading.Tasks;
using API.Routes;
using Application.Commands.Auth;
using Application.Common.DTOs.Auth;
using Application.Common.Models;
using Ardalis.ApiEndpoints;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;

namespace API.Endpoints.Auth;

[Route(AuthRoutes.LoginWithGoogle)]
public class LoginWithGoogle : BaseAsyncEndpoint
.WithRequest<GoogleLoginUserRequest>
.WithResponse<IResponse<string>>
{
private readonly IMediator _mediator;

public LoginWithGoogle(IMediator mediator) => _mediator = mediator;

[HttpPost,
SwaggerOperation(Description = "Signs in or signs up provided google user and return token",
Summary = "Sign In or Sing Up wiht Google Account",
OperationId = "Auth.LoginWithGoogle",
Tags = new[] { "Auth" }),
SwaggerResponse(200, "User logged in successfully", typeof(IResponse<string>)),
Produces("application/json"), Consumes("application/json")]
public override async Task<ActionResult<IResponse<string>>> HandleAsync(
[SwaggerRequestBody("User google login payload", Required = true)]
GoogleLoginUserRequest loginUserRequest,
CancellationToken cancellationToken = new()) =>
Ok(await _mediator.Send(new GoogleLoginCommand(loginUserRequest), cancellationToken));
}
35 changes: 35 additions & 0 deletions src/API/Endpoints/Auth/LoginWithPhone.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Threading;
using System.Threading.Tasks;
using API.Routes;
using Application.Commands.Auth;
using Application.Common.DTOs.Auth;
using Application.Common.Models;
using Ardalis.ApiEndpoints;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;

namespace API.Endpoints.Auth;

[Route(AuthRoutes.LoginWithPhone)]
public class LoginWithPhone : BaseAsyncEndpoint
.WithRequest<PhoneLoginUserRequest>
.WithResponse<IResponse<string>>
{
private readonly IMediator _mediator;

public LoginWithPhone(IMediator mediator) => _mediator = mediator;

[HttpPost,
SwaggerOperation(Description = "Signs in or signs up provided user and return token",
Summary = "Sign In or Sing Up wiht phone number",
OperationId = "Auth.LoginWithPhone",
Tags = new[] { "Auth" }),
SwaggerResponse(200, "User logged in successfully", typeof(IResponse<string>)),
Produces("application/json"), Consumes("application/json")]
public override async Task<ActionResult<IResponse<string>>> HandleAsync(
[SwaggerRequestBody("User phone login payload", Required = true)]
PhoneLoginUserRequest loginUserRequest,
CancellationToken cancellationToken = new()) =>
Ok(await _mediator.Send(new PhoneLoginUserCommand(loginUserRequest), cancellationToken));
}
28 changes: 13 additions & 15 deletions src/API/Program.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System;
using System.Threading.Tasks;
using Infrastructure.Persistence;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
Expand Down Expand Up @@ -33,19 +31,19 @@ public static async Task Main(string[] args)
Log.Information("Application Starting...");
var host = CreateHostBuilder(args).Build();
using var scope = host.Services.CreateScope();
var serviceProvider = scope.ServiceProvider;
try
{
var context = serviceProvider.GetService<ApplicationDbContext>();
if (context!.Database.IsSqlServer())
await context.Database.MigrateAsync();
// Seed database here
}
catch (Exception e)
{
Log.Fatal(e,"An error occurred while migrating the database.");
throw;
}
//var serviceProvider = scope.ServiceProvider;
//try
//{
// var context = serviceProvider.GetService<ApplicationDbContext>();
// if (context!.Database.IsSqlServer())
// await context.Database.MigrateAsync();
// // Seed database here
//}
//catch (Exception e)
//{
// Log.Fatal(e,"An error occurred while migrating the database.");
// throw;
//}

await host.RunAsync();
}
Expand Down
2 changes: 2 additions & 0 deletions src/API/Routes/AuthRoutes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ public static class AuthRoutes
{
public const string Register = "api/Auth/Register";
public const string Login = "api/Auth/Login";
public const string LoginWithGoogle = "api/Auth/LoginWithGoogle";
public const string LoginWithPhone = "api/Auth/LoginWithPhone";
public const string Refresh = "api/Auth/Refresh";
public const string LogOut = "api/Auth/LogOut";
}
15 changes: 2 additions & 13 deletions src/API/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,7 @@
}
},
"AllowedHosts": "*",
"UseInMemoryDatabase": true,
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=UserDb;Trusted_Connection=True;MultipleActiveResultSets=true;"
},
"UseInMemoryDatabase": false,
"Cors":[],
"LongRunningRequestTime":500,
"JwtSettings":{
"AccessTokenSecret":"my_too_strong_access_secret_key",
"RefreshTokenSecret":"my_too_strong_refresh_secret_key",
"AccessTokenExpirationMinutes":0.3,
"RefreshTokenExpirationMinutes":60,
"Issuer":"https://localhost:5001",
"Audience":"https://localhost:5001"
}
"LongRunningRequestTime":500
}
23 changes: 14 additions & 9 deletions src/API/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,23 @@
}
},
"AllowedHosts": "*",
"UseInMemoryDatabase": true,
"UseInMemoryDatabase": false,
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=UserDb;Trusted_Connection=True;MultipleActiveResultSets=true;"
"UsersDbReadOnlyConnection": "Server=localhost;Database=DevasevaUsers;uid=root;password=Test$123;SSLMode=Required",
"UsersDbWriteConnection": "Server=localhost;Database=DevasevaUsers;uid=root;password=Test$123;SSLMode=Required"
},
"Cors":[],
"LongRunningRequestTime":500,
"JwtSettings":{
"AccessTokenSecret":"my_too_strong_access_secret_key",
"RefreshTokenSecret":"my_too_strong_refresh_secret_key",
"AccessTokenExpirationMinutes":0.3,
"RefreshTokenExpirationMinutes":60,
"Issuer":"https://localhost:5001",
"Audience":"https://localhost:5001"
"JwtSettings": {
"AccessTokenSecret": "gUkXp2s5v8y/B?E(H+MbQeThVmYq3t6w9z$C&F)J@NcRfUjXnZr4u7x!A%D*G-Ka",
"RefreshTokenSecret": "dRgUkXp2s5v8y/B?D(G+KbPeShVmYq3t",
"AccessTokenExpirationMinutes": 10080,
"RefreshTokenExpirationMinutes": 100800,
"Issuer": "https://localhost:5001",
"Audience": "https://localhost:5001",
"Google": {
"ClientId": "833381955269-73058j9r1cmcp76vb83ijgu7tnvgn0ep.apps.googleusercontent.com",
"ClientSecret": "zjg7E6gTh_RYw_q6sTj4mShm"
}
}
}
1 change: 1 addition & 0 deletions src/Application/Application.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="10.3.0" />
<PackageReference Include="Forbid" Version="1.0.0" />
<PackageReference Include="Forbid.Extensions.Microsoft.DependencyInjection" Version="1.0.0" />
<PackageReference Include="Google.Apis.Auth" Version="1.57.0" />
<PackageReference Include="Mapster" Version="7.2.0" />
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.0" />
<PackageReference Include="Mapster.EFCore" Version="5.1.0" />
Expand Down
60 changes: 60 additions & 0 deletions src/Application/Commands/Auth/GoogleLoginCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System.Threading;
using System.Threading.Tasks;
using Application.Common.DTOs.Auth;
using Application.Common.Interfaces;
using Application.Common.Models;
using Application.Common.Settings;
using Application.Common.Wrappers;
using Domain.Entities;
using Forbids;
using Google.Apis.Auth;
using Microsoft.AspNetCore.Identity;

namespace Application.Commands.Auth;

public record GoogleLoginCommand(GoogleLoginUserRequest LoginUserRequest) : IRequestWrapper<AuthenticateResponse>;

public class GoogleLoginCommandHandler : IHandlerWrapper<GoogleLoginCommand, AuthenticateResponse>
{
private readonly IAuthenticateService _authenticateService;
private readonly IForbid _forbid;
private readonly JwtSettings jwtSettings;
private readonly UserManager<User> _userManager;
private readonly SignInManager<User> _signInManager;
private const string GoogleProviderKey = "Google";

public GoogleLoginCommandHandler(UserManager<User> userManager, SignInManager<User> signInManager,
IAuthenticateService authenticateService, IForbid forbid, JwtSettings jwtSettings)
{
_userManager = userManager;
_signInManager = signInManager;
_authenticateService = authenticateService;
_forbid = forbid;
this.jwtSettings = jwtSettings;
}

public async Task<IResponse<AuthenticateResponse>> Handle(GoogleLoginCommand request, CancellationToken cancellationToken)
{
var payload = await GoogleJsonWebSignature.ValidateAsync(request.LoginUserRequest.ExternalAccessToken,
new GoogleJsonWebSignature.ValidationSettings()
{
Audience = new[] { jwtSettings.Google.ClientId }
});

var user = await _userManager.FindByLoginAsync(GoogleProviderKey, payload.Subject);
if (user == null)
{
user = new User()
{
Email = payload.Email,
UserName = payload.Email
};

await _userManager.CreateAsync(user);
await _userManager.AddLoginAsync(user, new UserLoginInfo(GoogleProviderKey, payload.Subject, GoogleProviderKey.ToUpperInvariant()));
}

await _signInManager.SignInAsync(user, false);
return Response.Success(await _authenticateService.Authenticate(user, cancellationToken));
}
}
19 changes: 12 additions & 7 deletions src/Application/Commands/Auth/LogOutCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,20 @@ public class LogOutCommandHandler : IHandlerWrapper<LogOutCommand,Unit>
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly SignInManager<User> _signInManager;
private readonly IApplicationDbContext _context;
private readonly IUsersDbReadOnlyContext _readOnlyContext;
private readonly IUsersDbWriteContext writeContext;
private readonly IForbid _forbid;

public LogOutCommandHandler(IHttpContextAccessor httpContextAccessor,SignInManager<User> signInManager,
IApplicationDbContext context,IForbid forbid)
public LogOutCommandHandler(IHttpContextAccessor httpContextAccessor,
SignInManager<User> signInManager,
IUsersDbReadOnlyContext readOnlyContext,
IUsersDbWriteContext writeContext,
IForbid forbid)
{
_httpContextAccessor = httpContextAccessor;
_signInManager = signInManager;
_context = context;
_readOnlyContext = readOnlyContext;
this.writeContext = writeContext;
_forbid = forbid;
}

Expand All @@ -38,11 +43,11 @@ public async Task<IResponse<Unit>> Handle(LogOutCommand request, CancellationTok
var userId = _httpContextAccessor.HttpContext?.User.FindFirstValue("id");
_forbid.NullOrEmpty(userId, UserNotFoundException.Instance);
await _signInManager.SignOutAsync();
var refreshTokens = await _context.RefreshTokens
var refreshTokens = await _readOnlyContext.RefreshTokens
.Where(x => x.UserId == userId)
.ToListAsync(cancellationToken);
_context.RefreshTokens.RemoveRange(refreshTokens);
await _context.SaveChangesAsync(cancellationToken);
writeContext.RefreshTokens.RemoveRange(refreshTokens);
await writeContext.SaveChangesAsync(cancellationToken);
return Response.Success(Unit.Value);
}
}
57 changes: 57 additions & 0 deletions src/Application/Commands/Auth/PhoneLoginUserCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Threading;
using System.Threading.Tasks;
using Application.Common.DTOs.Auth;
using Application.Common.Interfaces;
using Application.Common.Models;
using Application.Common.Wrappers;
using Domain.Entities;
using Domain.Exceptions;
using Forbids;
using Microsoft.AspNetCore.Identity;

namespace Application.Commands.Auth;

public record PhoneLoginUserCommand(PhoneLoginUserRequest LoginUserRequest) : IRequestWrapper<AuthenticateResponse>;

public class PhoneLoginUserCommandHandler : IHandlerWrapper<PhoneLoginUserCommand, AuthenticateResponse>
{
private readonly IAuthenticateService _authenticateService;
private readonly IForbid _forbid;
private readonly UserManager<User> _userManager;
private readonly SignInManager<User> _signInManager;

public PhoneLoginUserCommandHandler(UserManager<User> userManager,
SignInManager<User> signInManager,
IAuthenticateService authenticateService,
IForbid forbid)
{
_userManager = userManager;
_signInManager = signInManager;
_authenticateService = authenticateService;
_forbid = forbid;
}

public async Task<IResponse<AuthenticateResponse>> Handle(PhoneLoginUserCommand request, CancellationToken cancellationToken)
{
var user = new User
{
PhoneNumber = request.LoginUserRequest.Phone,
UserName = request.LoginUserRequest.Phone,
PhoneNumberConfirmed = true
};

var existingUser = await _userManager.FindByNameAsync(request.LoginUserRequest.Phone);
if (existingUser == null)
{
var createResult = await _userManager.CreateAsync(user);
_forbid.False(createResult.Succeeded, RegisterException.Instance);
}
else
{
user = existingUser;
}

await _signInManager.SignInAsync(user, false);
return Response.Success(await _authenticateService.Authenticate(user, cancellationToken));
}
}
20 changes: 13 additions & 7 deletions src/Application/Commands/Auth/RefreshCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,20 @@ public class RefreshCommandHandler : IHandlerWrapper<RefreshCommand,Authenticate
private readonly IAuthenticateService _authenticateService;
private readonly IForbid _forbid;
private readonly IRefreshTokenValidator _refreshTokenValidator;
private readonly IApplicationDbContext _context;
private readonly IUsersDbReadOnlyContext readOnlyContext;
private readonly IUsersDbWriteContext writeContext;
private readonly UserManager<User> _userManager;

public RefreshCommandHandler(IRefreshTokenValidator refreshTokenValidator, IApplicationDbContext context,
UserManager<User> userManager,IAuthenticateService authenticateService,IForbid forbid)
public RefreshCommandHandler(IRefreshTokenValidator refreshTokenValidator,
IUsersDbReadOnlyContext readOnlyContext,
IUsersDbWriteContext writeContext,
UserManager<User> userManager,
IAuthenticateService authenticateService,
IForbid forbid)
{
_refreshTokenValidator = refreshTokenValidator;
_context = context;
this.readOnlyContext = readOnlyContext;
this.writeContext = writeContext;
_userManager = userManager;
_authenticateService = authenticateService;
_forbid = forbid;
Expand All @@ -38,11 +44,11 @@ public async Task<IResponse<AuthenticateResponse>> Handle(RefreshCommand request
var isValidRefreshToken = _refreshTokenValidator.Validate(refreshRequest.RefreshToken);
_forbid.False(isValidRefreshToken, InvalidRefreshTokenException.Instance);
var refreshToken =
await _context.RefreshTokens.FirstOrDefaultAsync(x => x.Token == refreshRequest.RefreshToken,
await readOnlyContext.RefreshTokens.FirstOrDefaultAsync(x => x.Token == refreshRequest.RefreshToken,
cancellationToken);
_forbid.Null(refreshToken, InvalidRefreshTokenException.Instance);
_context.RefreshTokens.Remove(refreshToken);
await _context.SaveChangesAsync(cancellationToken);
writeContext.RefreshTokens.Remove(refreshToken);
await writeContext.SaveChangesAsync(cancellationToken);

var user = await _userManager.FindByIdAsync(refreshToken.UserId);
_forbid.Null(user, UserNotFoundException.Instance);
Expand Down
Loading