Skip to content
Merged
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
13 changes: 13 additions & 0 deletions backend/CookifyAPI/CookifyAPI/Controllers/AuthController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,17 @@ public async Task<IActionResult> Confirm([FromBody] ConfirmOtpRequest request)
return Ok(response);
}

[HttpPost("google")]
public async Task<IActionResult> GoogleAuth([FromBody] GoogleAuthRequest request)
{
var response = await authService.GoogleAuthAsync(request);

if (response == null)
{
return BadRequest(new { message = "Invalid Google token" });
}

return Ok(response);
}

}
1 change: 1 addition & 0 deletions backend/CookifyAPI/CookifyAPI/CookifyAPI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<PackageReference Include="AutoMapper" Version="16.1.1" />
<PackageReference Include="CloudinaryDotNet" Version="1.28.0" />
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
<PackageReference Include="Google.Apis.Auth" Version="1.74.0" />
<PackageReference Include="MailKit" Version="4.16.0" />
<PackageReference Include="MeiliSearch" Version="0.18.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.15" />
Expand Down
2 changes: 2 additions & 0 deletions backend/CookifyAPI/CookifyAPI/Extensions/AuthSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ public record AuthSettings
public int AccessTokenExpirationMinutes { get; init; }
public int RefreshTokenExpirationDays { get; init; }
public bool SkipVerification { get; init; }

public string GoogleClientId { get; init; } = string.Empty;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using System.ComponentModel.DataAnnotations;

namespace CookifyAPI.Models.DTOs.Requests;

public record GoogleAuthRequest(
[Required] string IdToken
);
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using CookifyAPI.Models.DTOs.Requests;
using CookifyAPI.Models.DTOs.Responses;
using CookifyAPI.Models.Entities;
using Google.Apis.Auth;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
Expand Down Expand Up @@ -88,7 +89,55 @@ public async Task<IdentityResult> SignUpAsync(RegisterRequest request)

return null;
}


public async Task<AuthResponse?> GoogleAuthAsync(GoogleAuthRequest request)
{
GoogleJsonWebSignature.Payload payload;
try
{
// 1. Проверка токена от Google
var settings = new GoogleJsonWebSignature.ValidationSettings
{
Audience = new[] { _settings.GoogleClientId }
};
payload = await GoogleJsonWebSignature.ValidateAsync(request.IdToken, settings);
}
catch (InvalidJwtException ex)
{
// Токен подделан или просрочен
Console.WriteLine(ex.Message);
return null;
}

// 2. Ищем пользователя по Email
var user = await userManager.FindByEmailAsync(payload.Email);

if (user == null)
{
// 3. Регистрация нового пользователя "на лету"
user = new User
{
UserName = payload.Email, // В качестве логина используем email
Email = payload.Email,
EmailConfirmed = true, // Почта от Google уже подтверждена!
AvatarUrl = payload.Picture, // Берем аватарку из Google
CreatedAt = DateTime.UtcNow
};

// Создаем пользователя БЕЗ пароля
var result = await userManager.CreateAsync(user);

if (!result.Succeeded)
{
// Логируем ошибку, если что-то пошло не так (например, БД недоступна)
throw new Exception("Failed to create user via Google Auth");
}
}

// 4. Генерируем НАШИ токены и возвращаем их
return await UpdateTokens(user);
}

public async Task<AuthResponse?> VerifyCodeAsync(ConfirmOtpRequest request)
{
var user = await userManager.FindByEmailAsync(request.Login)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public async Task<KeysetPagedResult<RecipeListDto>> GetRecipesKeysetAsync(int? l
IQueryable<Recipe> query = context.Recipes
.AsNoTracking()
.AsSplitQuery()
.OrderBy(r => r.Id);
.OrderByDescending(r => r.Id);

if (lastId.HasValue) query = query.Where(r => r.Id > lastId.Value);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ public interface IAuthService {
Task<AuthResponse?> VerifyCodeAsync(ConfirmOtpRequest request);
Task<bool> SendOtpCodeAsync(string login);
Task<AuthResponse?> ResetPasswordAsync(ResetPasswordRequest request);
Task<AuthResponse?> GoogleAuthAsync(GoogleAuthRequest request);
}
3 changes: 3 additions & 0 deletions infrastructure/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ JWT_KEY=GenerateSomeLongRandomStringHereForDevelopment
SMTP_PASSWORD=get_app_password_from_google
SENDER_EMAIL=your_dev_email@gmail.com

# Google Authorization
GOOGLE_CLIENT_ID=your_id.apps.googleusercontent.com

# Meilisearch
MEILI_MASTER_KEY=YourSuperSecretMasterKey123!

Expand Down
2 changes: 2 additions & 0 deletions infrastructure/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ services:

- EmailSettings__Password=${SMTP_PASSWORD}
- EmailSettings__SenderEmail=${SENDER_EMAIL}

- AuthSettings__GoogleClientId=${GOOGLE_CLIENT_ID}

- Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}
- Cloudinary__ApiKey=${CLOUDINARY_API_KEY}
Expand Down
Loading