Skip to content

Commit 11b0531

Browse files
authored
Merge pull request #7745 from microting/feature/grpc-auth-service
Add shared gRPC auth service for mobile clients
2 parents 414a91a + de001d8 commit 11b0531

File tree

7 files changed

+308
-0
lines changed

7 files changed

+308
-0
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
using System.Threading.Tasks;
2+
using eFormAPI.Web.Abstractions;
3+
using eFormAPI.Web.Grpc;
4+
using eFormAPI.Web.Infrastructure.Models.Auth;
5+
using eFormAPI.Web.Services.GrpcServices;
6+
using eFormAPI.Web.Tests.Helpers;
7+
using Microting.eFormApi.BasePn.Infrastructure.Models.API;
8+
using Microting.eFormApi.BasePn.Infrastructure.Models.Auth;
9+
using NSubstitute;
10+
using NUnit.Framework;
11+
12+
namespace eFormAPI.Web.Tests.GrpcServices;
13+
14+
[TestFixture]
15+
public class EformAuthGrpcServiceTests
16+
{
17+
private IAuthService _authService;
18+
private EformAuthGrpcService _grpcService;
19+
20+
[SetUp]
21+
public void SetUp()
22+
{
23+
_authService = Substitute.For<IAuthService>();
24+
_grpcService = new EformAuthGrpcService(_authService);
25+
}
26+
27+
[Test]
28+
public async Task AuthenticateUser_Success_ReturnsTokenAndUser()
29+
{
30+
_authService.AuthenticateUser(Arg.Any<LoginModel>())
31+
.Returns(new OperationDataResult<EformAuthorizeResult>(
32+
true, "OK", new EformAuthorizeResult
33+
{
34+
AccessToken = "jwt-token-abc",
35+
FirstName = "John",
36+
LastName = "Doe"
37+
}));
38+
39+
var request = new AuthenticateUserRequest
40+
{
41+
Username = "user@test.com",
42+
Password = "password123"
43+
};
44+
45+
var response = await _grpcService.AuthenticateUser(
46+
request, TestServerCallContextFactory.Create());
47+
48+
Assert.That(response.Success, Is.True);
49+
Assert.That(response.Model, Is.Not.Null);
50+
Assert.That(response.Model.AccessToken, Is.EqualTo("jwt-token-abc"));
51+
Assert.That(response.Model.FirstName, Is.EqualTo("John"));
52+
Assert.That(response.Model.LastName, Is.EqualTo("Doe"));
53+
54+
await _authService.Received(1).AuthenticateUser(
55+
Arg.Is<LoginModel>(m => m.Username == "user@test.com" && m.Password == "password123"));
56+
}
57+
58+
[Test]
59+
public async Task AuthenticateUser_Failure_ReturnsError()
60+
{
61+
_authService.AuthenticateUser(Arg.Any<LoginModel>())
62+
.Returns(new OperationDataResult<EformAuthorizeResult>(
63+
false, "Invalid credentials"));
64+
65+
var request = new AuthenticateUserRequest
66+
{
67+
Username = "user@test.com",
68+
Password = "wrong"
69+
};
70+
71+
var response = await _grpcService.AuthenticateUser(
72+
request, TestServerCallContextFactory.Create());
73+
74+
Assert.That(response.Success, Is.False);
75+
Assert.That(response.Message, Is.EqualTo("Invalid credentials"));
76+
Assert.That(response.Model, Is.Null);
77+
}
78+
79+
[Test]
80+
public async Task RefreshToken_Success_ReturnsNewToken()
81+
{
82+
_authService.RefreshToken()
83+
.Returns(new OperationDataResult<EformAuthorizeResult>(
84+
true, "OK", new EformAuthorizeResult
85+
{
86+
AccessToken = "refreshed-token-xyz",
87+
FirstName = "Jane",
88+
LastName = "Smith"
89+
}));
90+
91+
var response = await _grpcService.RefreshToken(
92+
new RefreshTokenRequest(), TestServerCallContextFactory.Create());
93+
94+
Assert.That(response.Success, Is.True);
95+
Assert.That(response.Model, Is.Not.Null);
96+
Assert.That(response.Model.AccessToken, Is.EqualTo("refreshed-token-xyz"));
97+
Assert.That(response.Model.FirstName, Is.EqualTo("Jane"));
98+
Assert.That(response.Model.LastName, Is.EqualTo("Smith"));
99+
}
100+
101+
[Test]
102+
public async Task RefreshToken_Failure_ReturnsError()
103+
{
104+
_authService.RefreshToken()
105+
.Returns(new OperationDataResult<EformAuthorizeResult>(
106+
false, "Token expired"));
107+
108+
var response = await _grpcService.RefreshToken(
109+
new RefreshTokenRequest(), TestServerCallContextFactory.Create());
110+
111+
Assert.That(response.Success, Is.False);
112+
Assert.That(response.Message, Is.EqualTo("Token expired"));
113+
Assert.That(response.Model, Is.Null);
114+
}
115+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using Grpc.Core;
6+
7+
namespace eFormAPI.Web.Tests.Helpers;
8+
9+
public static class TestServerCallContextFactory
10+
{
11+
public static ServerCallContext Create(
12+
Metadata requestHeaders = null,
13+
CancellationToken cancellationToken = default)
14+
{
15+
return new TestCallContext(requestHeaders ?? new Metadata(), cancellationToken);
16+
}
17+
18+
private class TestCallContext : ServerCallContext
19+
{
20+
public TestCallContext(Metadata requestHeaders, CancellationToken ct)
21+
{
22+
RequestHeadersCore = requestHeaders;
23+
CancellationTokenCore = ct;
24+
DeadlineCore = DateTime.UtcNow.AddHours(1);
25+
}
26+
27+
protected override string MethodCore => "TestMethod";
28+
protected override string HostCore => "localhost";
29+
protected override string PeerCore => "ipv4:127.0.0.1:0";
30+
protected override DateTime DeadlineCore { get; }
31+
protected override Metadata RequestHeadersCore { get; }
32+
protected override CancellationToken CancellationTokenCore { get; }
33+
protected override Metadata ResponseTrailersCore => new();
34+
protected override Status StatusCore { get; set; }
35+
protected override WriteOptions WriteOptionsCore { get; set; }
36+
37+
protected override AuthContext AuthContextCore =>
38+
new(string.Empty, new Dictionary<string, List<AuthProperty>>());
39+
40+
protected override ContextPropagationToken CreatePropagationTokenCore(
41+
ContextPropagationOptions options) => throw new NotImplementedException();
42+
43+
protected override Task WriteResponseHeadersAsyncCore(Metadata responseHeaders) =>
44+
Task.CompletedTask;
45+
}
46+
}

eFormAPI/eFormAPI.Web.Tests/eFormAPI.Web.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1414
<PrivateAssets>all</PrivateAssets>
1515
</PackageReference>
16+
<PackageReference Include="NSubstitute" Version="5.3.0" />
1617
<PackageReference Include="NUnit3TestAdapter" Version="6.2.0" />
1718
</ItemGroup>
1819

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
syntax = "proto3";
2+
3+
package eform;
4+
5+
option csharp_namespace = "eFormAPI.Web.Grpc";
6+
7+
message AuthenticateUserRequest {
8+
string username = 1;
9+
string password = 2;
10+
string grant_type = 3;
11+
}
12+
13+
message AuthenticateUserResponse {
14+
bool success = 1;
15+
string message = 2;
16+
AuthUserModel model = 3;
17+
}
18+
19+
message AuthUserModel {
20+
string access_token = 1;
21+
string first_name = 2;
22+
string last_name = 3;
23+
}
24+
25+
message RefreshTokenRequest {}
26+
27+
message RefreshTokenResponse {
28+
bool success = 1;
29+
string message = 2;
30+
AuthUserModel model = 3;
31+
}
32+
33+
service EformAuthService {
34+
rpc AuthenticateUser(AuthenticateUserRequest) returns (AuthenticateUserResponse);
35+
rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse);
36+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using eFormAPI.Web.Abstractions;
4+
using eFormAPI.Web.Grpc;
5+
using Grpc.Core;
6+
using Microting.eFormApi.BasePn.Infrastructure.Models.Auth;
7+
8+
namespace eFormAPI.Web.Services.GrpcServices;
9+
10+
public class EformAuthGrpcService : Grpc.EformAuthService.EformAuthServiceBase
11+
{
12+
private readonly IAuthService _authService;
13+
14+
public EformAuthGrpcService(IAuthService authService)
15+
{
16+
_authService = authService;
17+
}
18+
19+
public override async Task<AuthenticateUserResponse> AuthenticateUser(
20+
AuthenticateUserRequest request, ServerCallContext context)
21+
{
22+
try
23+
{
24+
var loginModel = new LoginModel
25+
{
26+
Username = request.Username,
27+
Password = request.Password
28+
};
29+
30+
var result = await _authService.AuthenticateUser(loginModel);
31+
32+
var response = new AuthenticateUserResponse
33+
{
34+
Success = result.Success,
35+
Message = result.Message ?? ""
36+
};
37+
38+
if (result.Success && result.Model != null)
39+
{
40+
response.Model = new AuthUserModel
41+
{
42+
AccessToken = result.Model.AccessToken ?? "",
43+
FirstName = result.Model.FirstName ?? "",
44+
LastName = result.Model.LastName ?? ""
45+
};
46+
}
47+
48+
return response;
49+
}
50+
catch (Exception ex)
51+
{
52+
return new AuthenticateUserResponse
53+
{
54+
Success = false,
55+
Message = ex.Message
56+
};
57+
}
58+
}
59+
60+
public override async Task<RefreshTokenResponse> RefreshToken(
61+
RefreshTokenRequest request, ServerCallContext context)
62+
{
63+
try
64+
{
65+
var result = await _authService.RefreshToken();
66+
67+
var response = new RefreshTokenResponse
68+
{
69+
Success = result.Success,
70+
Message = result.Message ?? ""
71+
};
72+
73+
if (result.Success && result.Model != null)
74+
{
75+
response.Model = new AuthUserModel
76+
{
77+
AccessToken = result.Model.AccessToken ?? "",
78+
FirstName = result.Model.FirstName ?? "",
79+
LastName = result.Model.LastName ?? ""
80+
};
81+
}
82+
83+
return response;
84+
}
85+
catch (Exception ex)
86+
{
87+
return new RefreshTokenResponse
88+
{
89+
Success = false,
90+
Message = ex.Message
91+
};
92+
}
93+
}
94+
}

eFormAPI/eFormAPI.Web/Startup.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,9 @@ public void ConfigureServices(IServiceCollection services)
318318
}
319319
ConnectServices(services);
320320

321+
// gRPC
322+
services.AddGrpc();
323+
321324
// plugins
322325
services.AddEFormPlugins(Program.EnabledPlugins);
323326
}
@@ -381,6 +384,14 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
381384

382385
// Plugins
383386
app.UseEFormPlugins(Program.EnabledPlugins);
387+
388+
// gRPC
389+
app.UseRouting();
390+
app.UseEndpoints(endpoints =>
391+
{
392+
endpoints.MapGrpcService<Services.GrpcServices.EformAuthGrpcService>();
393+
});
394+
384395
// Route all unknown requests to app root
385396
app.UseAngularMiddleware(env);
386397
FixUserToWorkerLinks();

eFormAPI/eFormAPI.Web/eFormAPI.Web.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
<PackageReference Include="Sentry" Version="6.3.0" />
6363
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
6464
<PackageReference Include="McMaster.NETCore.Plugins" Version="2.0.0" />
65+
<PackageReference Include="Grpc.AspNetCore" Version="2.71.0" />
6566
<PackageReference Include="sendgrid" Version="9.29.3" />
6667
<PackageReference Include="System.Configuration.ConfigurationManager" Version="10.0.5" />
6768
<PackageReference Include="System.Threading.AccessControl" Version="10.0.5" />
@@ -71,4 +72,8 @@
7172
<Folder Include="Plugins" />
7273
</ItemGroup>
7374

75+
<ItemGroup>
76+
<Protobuf Include="Protos\auth.proto" GrpcServices="Server" />
77+
</ItemGroup>
78+
7479
</Project>

0 commit comments

Comments
 (0)