Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0b7230a
Merge branch 'dev' into current branch, accepting dev version of apps…
InnetaSh Jun 15, 2026
e6daa79
Merge remote-tracking branch 'origin/dev' into dev
InnetaSh Jun 15, 2026
2db197a
Merge remote-tracking branch 'origin/dev' into dev
InnetaSh Jun 16, 2026
6e3d11c
Merge branch 'dev' of https://github.com/project-studying-dotnet/Stre…
InnetaSh Jun 16, 2026
4c456c4
add models, mediatR,update controller for google login
InnetaSh Jun 16, 2026
5086644
fix bud in AuthController
InnetaSh Jun 16, 2026
a108ab7
add googleServise, Validatore, rests
InnetaSh Jun 16, 2026
5d64695
fix controller, servise, add mediatr, validators, tests for google login
InnetaSh Jun 16, 2026
4d17d8a
fix conflicts
InnetaSh Jun 16, 2026
c092a00
fix
InnetaSh Jun 16, 2026
3b90be2
Merge branch 'dev' into task/269/LogIn-using-Google-Auth/Implement-ba…
InnetaSh Jun 16, 2026
75780e1
fix sonar issues
InnetaSh Jun 16, 2026
9dfd63d
Merge branch 'task/269/LogIn-using-Google-Auth/Implement-backend' of …
InnetaSh Jun 16, 2026
e488f0f
Merge branch 'dev' into task/269/LogIn-using-Google-Auth/Implement-ba…
InnetaSh Jun 16, 2026
5178f15
fix dublicate in rhe code
InnetaSh Jun 16, 2026
839cc8e
Merge branch 'task/269/LogIn-using-Google-Auth/Implement-backend' of …
InnetaSh Jun 16, 2026
bee7715
fix
InnetaSh Jun 16, 2026
2e9f78d
fix
InnetaSh Jun 16, 2026
7d30378
Merge branch 'dev' into task/269/LogIn-using-Google-Auth/Implement-ba…
InnetaSh Jun 18, 2026
7810bff
Merge branch 'dev' into task/269/LogIn-using-Google-Auth/Implement-ba…
InnetaSh Jun 18, 2026
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
74 changes: 38 additions & 36 deletions Streetcode/DbUpdate/Program.cs
Original file line number Diff line number Diff line change
@@ -1,54 +1,56 @@
using DbUp;
using Microsoft.Extensions.Configuration;

public class Program
namespace Streetcode.DbUpdate
{
static int Main(string[] args)
public class Program
{
string migrationPath = Path.Combine(Directory.GetCurrentDirectory(),
"Streetcode.DAL", "Persistence", "ScriptsMigration");
private static int Main(string[] args)
{
string migrationPath = Path.Combine(Directory.GetCurrentDirectory(), "Streetcode.DAL", "Persistence", "ScriptsMigration");

var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Local";
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Local";

var configuration = new ConfigurationBuilder()
.SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "Streetcode.WebApi"))
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables("STREETCODE_")
.Build();
var configuration = new ConfigurationBuilder()
.SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "Streetcode.WebApi"))
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables("STREETCODE_")
.Build();

var connectionString = configuration.GetConnectionString("DefaultConnection");
var connectionString = configuration.GetConnectionString("DefaultConnection");

string pathToScript = "";
string pathToScript = "";

Console.WriteLine("Enter '-m' to MIGRATE or '-s' to SEED db:");
pathToScript = Console.ReadLine();
Console.WriteLine("Enter '-m' to MIGRATE or '-s' to SEED db:");
var command = Console.ReadLine();

Check warning on line 26 in Streetcode/DbUpdate/Program.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unused local variable 'command'.

See more on https://sonarcloud.io/project/issues?id=project-studying-dotnet_Streetcode-Server-May2026&issues=AZ7Rl0tG83DoD6J_QceZ&open=AZ7Rl0tG83DoD6J_QceZ&pullRequest=281

pathToScript = migrationPath;

var upgrader =
DeployChanges.To
.SqlDatabase(connectionString)
.WithScriptsFromFileSystem(pathToScript)
.LogToConsole()
.Build();
pathToScript = migrationPath;

var result = upgrader.PerformUpgrade();
var upgrader =
DeployChanges.To
.SqlDatabase(connectionString)
.WithScriptsFromFileSystem(pathToScript)
.LogToConsole()
.Build();

if (!result.Successful)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(result.Error);
Console.ResetColor();
var result = upgrader.PerformUpgrade();

if (!result.Successful)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(result.Error);
Console.ResetColor();
#if DEBUG
Console.ReadLine();
Console.ReadLine();
#endif
return -1;
}
return -1;
}

Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Success!");
Console.ResetColor();
return 0;
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Success!");
Console.ResetColor();
return 0;
}
}
}
38 changes: 38 additions & 0 deletions Streetcode/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Streetcode.Auth Service

- GoogleAuth:
---
## 1. Configure GoogleAuth__ClientId

```bash
dotnet user-secrets set "GoogleAuth:ClientId" "123721973387-7i3rs06c8iui5lrb805f22o0k2s6gk1o.apps.googleusercontent.com"
```

## 2. Create .env file in repository root example

```env
DB_PASSWORD=Admin@1234
RABBITMQ_USERNAME=guest
RABBITMQ_PASSWORD=guest

JWT_KEY=StreetcodeSuperSecretJwtKeyForDevelopmentOnly1234567890
JWT_ISSUER=Streetcode
JWT_AUDIENCE=StreetcodeUsers
JWT_ACCESS_TOKEN_LIFETIME_IN_MINUTES=120
JWT_REFRESH_TOKEN_LIFETIME_IN_DAYS=7


EmailConfiguration:From = your@email.com
EmailConfiguration:SmtpServer = smtp.gmail.com
EmailConfiguration:Port = 465
EmailConfiguration:UserName = your@email.com
EmailConfiguration:Password = your-app-password

ADMIN_EMAIL=admin@gmail.com
ADMIN_PASSWORD=admin@1234

GoogleAuth__ClientId=123721973387-7i3rs06c8iui5lrb805f22o0k2s6gk1o.apps.googleusercontent.com
```


---
33 changes: 32 additions & 1 deletion Streetcode/Streetcode.Auth/Controllers/Users/AuthController.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
using Google.Apis.Auth;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Streetcode.Auth.Extensions;
using Streetcode.Auth.MediatR.Users.Login;
using Streetcode.Auth.MediatR.Users.LoginGoogle;
using Streetcode.Auth.MediatR.Users.Logout;
using Streetcode.Auth.MediatR.Users.RefreshToken;
using Streetcode.Auth.MediatR.Users.Register;
using Streetcode.Auth.Models.DTO;
using Streetcode.Auth.Services.Interfaces.Users;

namespace Streetcode.Auth.Controllers.Users;

Expand All @@ -15,10 +18,12 @@ namespace Streetcode.Auth.Controllers.Users;
public class AuthController : ControllerBase
{
private readonly IMediator _mediator;
private readonly IGoogleAuthService _googleAuthService;

public AuthController(IMediator mediator)
public AuthController(IMediator mediator, IGoogleAuthService googleAuthService)
{
_mediator = mediator;
_googleAuthService = googleAuthService;
}

[AllowAnonymous]
Expand All @@ -29,6 +34,32 @@ public async Task<IActionResult> Login(UserLoginDto request)
return this.ToActionResult(result);
}

[HttpPost("google-login")]
public async Task<IActionResult> GoogleLogin([FromBody] GoogleLoginRequest request)
{
try
{
var payload = await _googleAuthService.ValidateTokenAsync(request.IdToken);

if (payload == null)
return Unauthorized("Invalid Google Token");

var loginDto = new GoogleLoginRequestDto
{
Email = payload.Email,
Name = payload.GivenName,
Surname = payload.FamilyName
};

var result = await _mediator.Send(new GoogleLoginCommand(loginDto));
return this.ToActionResult(result);
}
catch (Exception)
{
return Unauthorized("Invalid Google Token");
}
}

[AllowAnonymous]
[HttpPost("register")]
public async Task<IActionResult> Register(UserRegisterDto request)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using FluentResults;
using MediatR;
using Streetcode.Auth.Models.DTO;

namespace Streetcode.Auth.MediatR.Users.LoginGoogle
{
public record GoogleLoginCommand(GoogleLoginRequestDto googleLoginRequest)
: IRequest<Result<AuthResponseDto>>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using FluentResults;
using MediatR;
using Microsoft.AspNetCore.Identity;
using Streetcode.Auth.Models.DTO;
using Streetcode.Auth.Models.Entities;
using Streetcode.Auth.Services.Interfaces.Logging;
using Streetcode.Auth.Services.Interfaces.Users;
using Streetcode.Common.Enums;

namespace Streetcode.Auth.MediatR.Users.LoginGoogle
{
public class GoogleLoginHandler : IRequestHandler<GoogleLoginCommand, Result<AuthResponseDto>>
{
private readonly UserManager<User> _userManager;
private readonly IAuthService _authService;

private readonly ILoggerService _logger;

public GoogleLoginHandler(
UserManager<User> userManager,
IAuthService authService,
ILoggerService logger)
{
_userManager = userManager;
_authService = authService;

_logger = logger;
}

public async Task<Result<AuthResponseDto>> Handle(GoogleLoginCommand request, CancellationToken cancellationToken)
{
var loginDto = request.googleLoginRequest;
_logger.LogInformation($"Google login attempt for {loginDto.Email}");

var user = await _userManager.FindByEmailAsync(loginDto.Email);

if (user == null)
{
user = new User { Email = loginDto.Email, UserName = loginDto.Email, Name = loginDto.Name, Surname = loginDto.Surname };

var createResult = await _userManager.CreateAsync(user);
var errorResult = CheckIdentityResult(createResult, request, $"Failed to create user {loginDto.Email}");
if (errorResult != null) return errorResult;

var roleResult = await _userManager.AddToRoleAsync(user, UserRole.MainAdministrator.ToString());
errorResult = CheckIdentityResult(roleResult, request, $"Failed to add role for user {user.Id}");
if (errorResult != null) return errorResult;
}

var authResult = await _authService.CreateLoginResultAsync(user);
_logger.LogInformation($"User {user.Id} successfully logged in");
return Result.Ok(authResult);
}

private Result<AuthResponseDto> CheckIdentityResult(IdentityResult result, object request, string errorMessage)
{
if (result.Succeeded) return null!;

var errors = string.Join(", ", result.Errors.Select(e => e.Description));
_logger.LogError(request, $"{errorMessage}: {errors}");
return Result.Fail<AuthResponseDto>(errorMessage);
}
}
}
7 changes: 7 additions & 0 deletions Streetcode/Streetcode.Auth/Models/DTO/GoogleLoginRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Streetcode.Auth.Models.DTO
{
public class GoogleLoginRequest
{
public string IdToken { get; set; } = string.Empty;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Streetcode.Auth.Models.DTO
{
public class GoogleLoginRequestDto
{
public string Email { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Surname { get; set; } = string.Empty;
}
}
4 changes: 4 additions & 0 deletions Streetcode/Streetcode.Auth/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using Streetcode.Auth.MediatR.Behaviors;
using Streetcode.Auth.Services;
using Streetcode.Auth.Services.Interfaces;
using Streetcode.Auth.Services.Interfaces.Users;
using Streetcode.Auth.Services.Users;
using Streetcode.Common.Models;
using Streetcode.Shared.Web.Middleware;
using System.Reflection;
Expand All @@ -23,6 +25,8 @@
builder.Services.AddSingleton<IRabbitMqConnectionFactory, RabbitMqConnectionFactory>();
builder.Services.AddSingleton<IRabbitMqPublisher, RabbitMqPublisher>();

builder.Services.AddScoped<IGoogleAuthService, GoogleAuthService>();

builder.Services.AddControllers()
.AddJsonOptions(options =>
{
Expand Down
18 changes: 18 additions & 0 deletions Streetcode/Streetcode.Auth/Resources/ErrorMessages.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Streetcode/Streetcode.Auth/Resources/ErrorMessages.resx
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,10 @@
<data name="PasswordsDoNotMatch" xml:space="preserve">
<value>Passwords did not match.</value>
</data>
<data name="GoogleIDTokenIsRequired" xml:space="preserve">
<value>Google ID Token is required.</value>
</data>
<data name="InvalidTokenFormat" xml:space="preserve">
<value>Invalid Google Token Format.</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Google.Apis.Auth;

namespace Streetcode.Auth.Services.Interfaces.Users
{
public interface IGoogleAuthService
{
Task<GoogleJsonWebSignature.Payload?> ValidateTokenAsync(string idToken);
}
}
33 changes: 33 additions & 0 deletions Streetcode/Streetcode.Auth/Services/Users/GoogleAuthService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Google.Apis.Auth;
using Streetcode.Auth.Services.Interfaces.Users;

namespace Streetcode.Auth.Services.Users
{
public class GoogleAuthService : IGoogleAuthService
{
private readonly IConfiguration _configuration;

public GoogleAuthService(IConfiguration configuration)
{
_configuration = configuration;
}

public async Task<GoogleJsonWebSignature.Payload?> ValidateTokenAsync(string idToken)
{
try
{
var clientId = _configuration["GoogleAuth:ClientId"];
var settings = new GoogleJsonWebSignature.ValidationSettings()
{
Audience = new List<string> { clientId! }
};

return await GoogleJsonWebSignature.ValidateAsync(idToken, settings);
}
catch
{
return null;
}
}
}
}
Loading
Loading