Skip to content

Commit a7af2f2

Browse files
committed
feat(verification): add verification endpoint
1 parent ecdbd3b commit a7af2f2

File tree

6 files changed

+138
-0
lines changed

6 files changed

+138
-0
lines changed

Micro.AppRegistration.Api/StartupExtensions/DependencyInjection.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Micro.AppRegistration.Api.ListApplications;
77
using Micro.AppRegistration.Api.Models;
88
using Micro.AppRegistration.Api.Uuid;
9+
using Micro.AppRegistration.Api.VerifySecret;
910
using Microsoft.AspNetCore.Identity;
1011
using Microsoft.Extensions.Configuration;
1112
using Microsoft.Extensions.DependencyInjection;
@@ -23,6 +24,7 @@ public static void ConfigureRequiredDependencies(this IServiceCollection service
2324
services.AddScoped<ICreateApplicationRepository, CreateApplicationRepository>();
2425
services.AddScoped<IListApplicationRepository, ListApplicationRepository>();
2526
services.AddScoped<IListApplicationService, ListApplicationsService>();
27+
services.AddScoped<IVerifySecretService, VerifySecretService>();
2628
services.AddSingleton<IPasswordHasher<Application>, PasswordHasher<Application>>();
2729
services.AddSingleton(SetupKeyStoreHttpClient(configuration.GetSection("Services").Get<Services>().KeyStore));
2830
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using System.ComponentModel.DataAnnotations;
3+
4+
namespace Micro.AppRegistration.Api.ValidationAttributes
5+
{
6+
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
7+
public class StartsWith : ValidationAttribute
8+
{
9+
private readonly string _prefix;
10+
11+
public StartsWith(string prefix) : base($"The {{0}} field must start with {prefix}")
12+
{
13+
_prefix = prefix;
14+
}
15+
16+
public override bool IsValid(object value)
17+
{
18+
return value != null && value is string stringVal && stringVal.StartsWith(_prefix);
19+
}
20+
}
21+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
3+
namespace Micro.AppRegistration.Api.VerifySecret
4+
{
5+
public class BadBasicAuthorizationDataException : Exception
6+
{
7+
public BadBasicAuthorizationDataException(string message, Exception innerException) : base(message, innerException)
8+
{
9+
}
10+
}
11+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System;
2+
using System.ComponentModel.DataAnnotations;
3+
using System.Text;
4+
using System.Threading.Tasks;
5+
using Micro.AppRegistration.Api.ValidationAttributes;
6+
using Microsoft.AspNetCore.Http;
7+
using Microsoft.AspNetCore.Mvc;
8+
9+
namespace Micro.AppRegistration.Api.VerifySecret
10+
{
11+
[ApiController]
12+
[Route("api/[controller]")]
13+
public class VerifySecretController : ControllerBase
14+
{
15+
private readonly IVerifySecretService _verifySecretService;
16+
17+
public VerifySecretController(IVerifySecretService verifySecretService)
18+
{
19+
_verifySecretService = verifySecretService;
20+
}
21+
22+
[HttpPost]
23+
public async Task<IActionResult> Verify([FromHeader(Name = "Authorization")] [Required] [StartsWith("Basic ")]
24+
string authorization)
25+
{
26+
try
27+
{
28+
var (appId, secret) = GetBasicAuthData(authorization);
29+
var result = await _verifySecretService.Verify(appId, secret);
30+
return Ok(new VerifySecretResponse
31+
{
32+
Success = result
33+
});
34+
}
35+
catch (BadBasicAuthorizationDataException e)
36+
{
37+
return BadRequest(new ProblemDetails
38+
{
39+
Title = "authorization data is invalid"
40+
});
41+
}
42+
catch (Exception e)
43+
{
44+
return StatusCode(StatusCodes.Status500InternalServerError, new ProblemDetails
45+
{
46+
Title = "error handling request"
47+
});
48+
}
49+
}
50+
51+
private static (string, string) GetBasicAuthData(string authorizationHeader)
52+
{
53+
try
54+
{
55+
var token = authorizationHeader.Substring("Basic ".Length).Trim();
56+
var parts = Encoding.UTF8.GetString(Convert.FromBase64String(token)).Split(":");
57+
return (parts[0], parts[1]);
58+
}
59+
catch (Exception e)
60+
{
61+
throw new BadBasicAuthorizationDataException("bad data", e);
62+
}
63+
}
64+
}
65+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Micro.AppRegistration.Api.VerifySecret
2+
{
3+
public class VerifySecretResponse
4+
{
5+
public bool Success { set; get; }
6+
}
7+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.Threading.Tasks;
2+
using Micro.AppRegistration.Api.ListApplications;
3+
using Micro.AppRegistration.Api.Models;
4+
using Microsoft.AspNetCore.Identity;
5+
6+
namespace Micro.AppRegistration.Api.VerifySecret
7+
{
8+
public interface IVerifySecretService
9+
{
10+
Task<bool> Verify(string appId, string secret);
11+
}
12+
public class VerifySecretService : IVerifySecretService
13+
{
14+
private readonly IPasswordHasher<Application> _secretHasher;
15+
private readonly IListApplicationRepository _applicationRepository;
16+
17+
public VerifySecretService(IPasswordHasher<Application> secretHasher, IListApplicationRepository applicationRepository)
18+
{
19+
_secretHasher = secretHasher;
20+
_applicationRepository = applicationRepository;
21+
}
22+
23+
public async Task<bool> Verify(string appId, string secret)
24+
{
25+
var application = await _applicationRepository.FindById(appId);
26+
var result = _secretHasher.VerifyHashedPassword(null, application.Secret, secret);
27+
// todo: if result returns a rehash needed, we need re-hash password and save it in database.
28+
// to be done after MVP
29+
return result == PasswordVerificationResult.Success || result == PasswordVerificationResult.SuccessRehashNeeded;
30+
}
31+
}
32+
}

0 commit comments

Comments
 (0)