diff --git a/Flawless-Version-Control.sln b/Flawless-Version-Control.sln index e037c95..cad6658 100644 --- a/Flawless-Version-Control.sln +++ b/Flawless-Version-Control.sln @@ -8,6 +8,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flawless.Client.Avanonia", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flawless.Server", "Flawless.Server\Flawless.Server.csproj", "{17EA1F4E-7D4E-485D-BDB5-18BBC3FB8DF9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flawless.Test.ConsoleApplication", "Flawless.Test.ConsoleApplication\Flawless.Test.ConsoleApplication.csproj", "{29FF9E82-23A4-4F3C-82B6-7ABC72D5990D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -30,5 +32,9 @@ Global {17EA1F4E-7D4E-485D-BDB5-18BBC3FB8DF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {17EA1F4E-7D4E-485D-BDB5-18BBC3FB8DF9}.Release|Any CPU.ActiveCfg = Release|Any CPU {17EA1F4E-7D4E-485D-BDB5-18BBC3FB8DF9}.Release|Any CPU.Build.0 = Release|Any CPU + {29FF9E82-23A4-4F3C-82B6-7ABC72D5990D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29FF9E82-23A4-4F3C-82B6-7ABC72D5990D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29FF9E82-23A4-4F3C-82B6-7ABC72D5990D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29FF9E82-23A4-4F3C-82B6-7ABC72D5990D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Flawless-Version-Control.sln.DotSettings.user b/Flawless-Version-Control.sln.DotSettings.user new file mode 100644 index 0000000..982ffe9 --- /dev/null +++ b/Flawless-Version-Control.sln.DotSettings.user @@ -0,0 +1,4 @@ + + ForceIncluded + ForceIncluded + ForceIncluded \ No newline at end of file diff --git a/Flawless.Client/Flawless.Client.csproj b/Flawless.Client/Flawless.Client.csproj index bab7876..e6faf30 100644 --- a/Flawless.Client/Flawless.Client.csproj +++ b/Flawless.Client/Flawless.Client.csproj @@ -7,23 +7,14 @@ - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - Server - Public - True - True - obj\Debug\net8.0\ - MSBuild:Compile - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/Flawless.Client/Protos/greet.proto b/Flawless.Client/Protos/greet.proto deleted file mode 100644 index 78a1153..0000000 --- a/Flawless.Client/Protos/greet.proto +++ /dev/null @@ -1,21 +0,0 @@ -syntax = "proto3"; - -option csharp_namespace = "Flawless.Server"; - -package greet; - -// The greeting service definition. -service Greeter { - // Sends a greeting - rpc SayHello (HelloRequest) returns (HelloReply); -} - -// The request message containing the user's name. -message HelloRequest { - string name = 1; -} - -// The response message containing the greetings. -message HelloReply { - string message = 1; -} diff --git a/Flawless.Client/TestFlight.cs b/Flawless.Client/TestFlight.cs deleted file mode 100644 index 754543f..0000000 --- a/Flawless.Client/TestFlight.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Flawless.Client; - -public class TestFlight -{ -} \ No newline at end of file diff --git a/Flawless.Server/Flawless.Server.csproj b/Flawless.Server/Flawless.Server.csproj index 398a782..45f718e 100644 --- a/Flawless.Server/Flawless.Server.csproj +++ b/Flawless.Server/Flawless.Server.csproj @@ -7,11 +7,13 @@ - - - - + + + + + Protos\auth.proto + diff --git a/Flawless.Server/Program.cs b/Flawless.Server/Program.cs index 8ab227f..3bbd001 100644 --- a/Flawless.Server/Program.cs +++ b/Flawless.Server/Program.cs @@ -1,16 +1,51 @@ using Flawless.Server.Services; +using Flawless.Server.Utility; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; -var builder = WebApplication.CreateBuilder(args); +bool init = true; +while (init) +{ + init = false; + var builder = WebApplication.CreateBuilder(args); + builder.Services.AddGrpc(x => + { + }); -// Add services to the container. -builder.Services.AddGrpc(); + builder.Services.AddAuthentication(x => + { + x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(o => + { + o.TokenValidationParameters = new TokenValidationParameters() + { + ValidateIssuer = true, + RequireExpirationTime = true, + ValidateAudience = true, + ValidateIssuerSigningKey = true, + IssuerSigningKey = AuthUtility.SecurityKey, + ValidIssuer = AuthUtility.Issuer, + ValidAudience = AuthUtility.Audience, + ClockSkew = TimeSpan.Zero, + }; + }); -var app = builder.Build(); + builder.Services.AddAuthorization(); -// Configure the HTTP request pipeline. -app.MapGrpcService(); -app.MapGet("/", - () => - "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); + using var app = builder.Build(); -app.Run(); \ No newline at end of file + // Enable call router + app.UseRouting(); + + // Enable authentication + app.UseAuthentication(); + app.UseAuthorization(); + + // Configure the HTTP request pipeline. + app.MapGrpcService(); + app.MapGet("/", + () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); + + app.Run(); +} diff --git a/Flawless.Server/Protos/greet.proto b/Flawless.Server/Protos/greet.proto deleted file mode 100644 index 78a1153..0000000 --- a/Flawless.Server/Protos/greet.proto +++ /dev/null @@ -1,21 +0,0 @@ -syntax = "proto3"; - -option csharp_namespace = "Flawless.Server"; - -package greet; - -// The greeting service definition. -service Greeter { - // Sends a greeting - rpc SayHello (HelloRequest) returns (HelloReply); -} - -// The request message containing the user's name. -message HelloRequest { - string name = 1; -} - -// The response message containing the greetings. -message HelloReply { - string message = 1; -} diff --git a/Flawless.Server/Services/AuthService.cs b/Flawless.Server/Services/AuthService.cs new file mode 100644 index 0000000..ae71999 --- /dev/null +++ b/Flawless.Server/Services/AuthService.cs @@ -0,0 +1,58 @@ +using Flawless.Api; +using Flawless.Server.Utility; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using Microsoft.AspNetCore.Authorization; + +namespace Flawless.Server.Services; + +public class AuthService : Auth.AuthBase +{ + + private ILogger _logger; + + public AuthService(ILogger logger) + { + _logger = logger; + } + + public override Task GainToken(AuthRequest request, ServerCallContext context) + { + + if (request.UserName != "admin") + { + return Task.FromResult(new AuthResult() + { + Token = "", + Result = -1, + Message = "Invalid username or password" + }); + } + + var token = AuthUtility.GenerateJwtToken(request.UserName, request.Expires); + + _logger.LogInformation($"User '{request.UserName}' has been login in.'"); + return Task.FromResult(new AuthResult + { + Token = token, + Result = 0 + }); + } + + [Authorize] + public override Task GetUserInfo(Empty request, ServerCallContext context) + { + return Task.FromResult(new AuthUserInfo + { + UserName = context.GetHttpContext().User.Identity?.Name ?? string.Empty, + IsSystemAdmin = true, + UserId = 1000 + }); + } + + [Authorize] + public override Task Validate(Empty request, ServerCallContext context) + { + return Task.FromResult(new Empty()); + } +} \ No newline at end of file diff --git a/Flawless.Server/Services/GreeterService.cs b/Flawless.Server/Services/GreeterService.cs deleted file mode 100644 index 9e7ab88..0000000 --- a/Flawless.Server/Services/GreeterService.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Grpc.Core; -using Flawless.Server; - -namespace Flawless.Server.Services; - -public class GreeterService : Greeter.GreeterBase -{ - private readonly ILogger _logger; - - public GreeterService(ILogger logger) - { - _logger = logger; - } - - public override Task SayHello(HelloRequest request, ServerCallContext context) - { - return Task.FromResult(new HelloReply - { - Message = "Hello " + request.Name - }); - } -} \ No newline at end of file diff --git a/Flawless.Server/Utility/AuthUtility.cs b/Flawless.Server/Utility/AuthUtility.cs new file mode 100644 index 0000000..650efb8 --- /dev/null +++ b/Flawless.Server/Utility/AuthUtility.cs @@ -0,0 +1,72 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using Microsoft.IdentityModel.Tokens; + +namespace Flawless.Server.Utility; + +public static class AuthUtility +{ + private static JwtSecurityTokenHandler _tokenHandler = new(); + + private static SymmetricSecurityKey? _key; + + public static string GenerateSecret( + string randomRange = "abcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()_+=-", + int length = 256 / 8) + { + var rng = Random.Shared; + + String ran = ""; + for (int i = 0; i < length; i++) + { + int x = rng.Next(randomRange.Length); + ran += randomRange[x]; + } + + return ran; + } + + public static string JwtSecret { get; private set; } = GenerateSecret(); + + public static string Issuer { get; private set; } = Environment.GetEnvironmentVariable("issuer") ?? "jwt"; + + public static string Audience { get; private set; } = Environment.GetEnvironmentVariable("audience") ?? "jwt"; + + public static SymmetricSecurityKey SecurityKey + { + get + { + if (_key == null) _key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtSecret)); + return _key; + } + } + + public static void ResetKey(string issuer, string audience, string? keySecret = null) + { + JwtSecret = keySecret ?? GenerateSecret(); + Issuer = issuer; + Audience = audience; + _key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtSecret)); + } + + public static string GenerateJwtToken(string username, uint expires) + { + var credentials = new SigningCredentials(SecurityKey, SecurityAlgorithms.HmacSha256Signature); + var claims = new List + { + new (ClaimTypes.Name, username), + }; + + var token = _tokenHandler.CreateJwtSecurityToken( + issuer: Issuer, + audience: Audience, + subject: new ClaimsIdentity(claims), + expires: DateTime.Now.AddSeconds(expires), + issuedAt: DateTime.Now, + notBefore: DateTime.Now, + signingCredentials: credentials); + + return _tokenHandler.WriteToken(token); + } +} \ No newline at end of file diff --git a/Flawless.Shared/Protos/auth.proto b/Flawless.Shared/Protos/auth.proto new file mode 100644 index 0000000..46c76b1 --- /dev/null +++ b/Flawless.Shared/Protos/auth.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; +option csharp_namespace = "Flawless.Api"; + +import "google/protobuf/empty.proto"; + +service Auth { + rpc GainToken(AuthRequest) returns (AuthResult); + rpc GetUserInfo(google.protobuf.Empty) returns (AuthUserInfo); + rpc Validate(google.protobuf.Empty) returns (google.protobuf.Empty); +} + +message RegisterAuthRequest { + string user_name = 1; + string password = 2; +} + +message AuthRequest { + string user_name = 1; + string password = 2; + uint32 expires = 3; +} + +message AuthResult { + int32 result = 1; + string message = 2; + string token = 3; +} + +message AuthUserInfo { + uint64 user_id = 1; + string user_name = 2; + uint64 last_login = 3; + bool is_system_admin = 4; +} diff --git a/Flawless.Test.ConsoleApplication/Flawless.Test.ConsoleApplication.csproj b/Flawless.Test.ConsoleApplication/Flawless.Test.ConsoleApplication.csproj new file mode 100644 index 0000000..45191a0 --- /dev/null +++ b/Flawless.Test.ConsoleApplication/Flawless.Test.ConsoleApplication.csproj @@ -0,0 +1,21 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/Flawless.Test.ConsoleApplication/Program.cs b/Flawless.Test.ConsoleApplication/Program.cs new file mode 100644 index 0000000..ccfad5e --- /dev/null +++ b/Flawless.Test.ConsoleApplication/Program.cs @@ -0,0 +1,31 @@ +// See https://aka.ms/new-console-template for more information + +using Flawless.Api; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using Grpc.Net.Client; + +var path = "http://localhost:5150"; + +var rpcChannel = GrpcChannel.ForAddress(path); +var authService = new Auth.AuthClient(rpcChannel); + +var result = await authService.GainTokenAsync(new AuthRequest() +{ + UserName = "admin", + Expires = 10, + Password = "password" +}); + + +if (result.Result == 0) +{ + Console.WriteLine($"Token granted: {result.Token}"); + + // Thread.Sleep(5 * 1000); + var userInfo = await authService.GetUserInfoAsync(new Empty(), new Metadata() + { + { "Authorization", $"Bearer {result.Token}" } + }); + Console.WriteLine($"UserName: {userInfo.UserName}\nUID: {userInfo.UserId}\nIs Admin: {userInfo.IsSystemAdmin}"); +} \ No newline at end of file