| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
- using System.Security.Cryptography;
- using System.Text;
- using Application.Abstractions.Authentication;
- using Application.Abstractions.Data;
- using Application.Abstractions.Messaging;
- using Domain.Entities.Developers.ValueObject;
- using Microsoft.EntityFrameworkCore;
- using SharedKernel.Results;
- namespace Application.Features.Api.OAuth.IssueToken;
- internal sealed class Handler(
- IAppDbContext db,
- IJwtTokenProvider jwtTokenProvider
- ) : ICommandHandler<Command, Result<Response>>
- {
- private const int AccessTokenTtlSeconds = 3600;
- public async Task<Result<Response>> Handle(Command request, CancellationToken ct)
- {
- if (!string.Equals(request.GrantType, "client_credentials", StringComparison.Ordinal))
- {
- return Result.Failure<Response>(Error.Problem("OAuth.UnsupportedGrant", "Only grant_type=client_credentials is supported."));
- }
- if (string.IsNullOrWhiteSpace(request.ClientID) || string.IsNullOrWhiteSpace(request.ClientSecret))
- {
- return Result.Failure<Response>(Error.Problem("OAuth.InvalidClient", "client_id and client_secret are required."));
- }
- var credential = await db.ApiCredential
- .AsTracking()
- .Include(c => c.Application)
- .FirstOrDefaultAsync(c => c.ClientID == request.ClientID, ct);
- if (credential is null || !credential.IsActive)
- {
- return Result.Failure<Response>(Error.Unauthorized("OAuth.InvalidCredentials", "Invalid client credentials."));
- }
- var secretHash = HashSecret(request.ClientSecret);
- if (!CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(secretHash), Encoding.UTF8.GetBytes(credential.SecretHash)))
- {
- return Result.Failure<Response>(Error.Unauthorized("OAuth.InvalidCredentials", "Invalid client credentials."));
- }
- var app = credential.Application;
- if (app is null || app.Status != ApplicationStatus.Active)
- {
- return Result.Failure<Response>(Error.Problem("OAuth.AppNotActive", "Application is not active."));
- }
- var grantedScopes = await db.ApiApplicationScope
- .Where(c => c.ApplicationID == app.ID)
- .Select(c => c.Scope)
- .ToListAsync(ct);
- var requestedScopes = (request.Scope ?? string.Empty)
- .Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
- var allowedScopes = requestedScopes.Length == 0
- ? grantedScopes
- : requestedScopes.Where(s => grantedScopes.Contains(s)).ToList();
- if (requestedScopes.Length > 0 && allowedScopes.Count == 0)
- {
- return Result.Failure<Response>(Error.Problem("OAuth.InvalidScope", "Requested scopes are not granted."));
- }
- credential.TouchLastUsed();
- await db.SaveChangesAsync(ct);
- var ttl = TimeSpan.FromSeconds(AccessTokenTtlSeconds);
- var accessToken = jwtTokenProvider.CreateOAuth2AccessToken(
- app.ID,
- credential.ClientID,
- app.OwnerMemberID,
- allowedScopes,
- ttl
- );
- return Result.Success(new Response(
- accessToken,
- "Bearer",
- AccessTokenTtlSeconds,
- string.Join(' ', allowedScopes)
- ));
- }
- private static string HashSecret(string secret)
- {
- var bytes = Encoding.UTF8.GetBytes(secret);
- var hash = SHA256.HashData(bytes);
- return Convert.ToHexString(hash).ToLowerInvariant();
- }
- }
|