Skip to content

Commit 294c458

Browse files
authored
Merge pull request #13 from argon-chat/feature/fusion-auth
Feature/fusion auth
2 parents 126be2d + 16990ba commit 294c458

File tree

8 files changed

+227
-74
lines changed

8 files changed

+227
-74
lines changed

src/Argon.Api/Argon.Api.csproj

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,46 @@
1-
<Project Sdk="Microsoft.NET.Sdk.Web">
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<PropertyGroup>
44
<TargetFramework>net8.0</TargetFramework>
55
<Nullable>enable</Nullable>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
8+
<UserSecretsId>b90ebea2-7ea4-447f-b92f-46da1cfd6437</UserSecretsId>
89
</PropertyGroup>
910

1011
<ItemGroup>
11-
<PackageReference Include="Flurl.Http" Version="4.0.2"/>
12-
<PackageReference Include="Flurl.Http.Newtonsoft" Version="0.9.1"/>
13-
<PackageReference Include="MemoryPack" Version="1.21.3"/>
14-
<PackageReference Include="MemoryPack.Generator" Version="1.21.3">
15-
<PrivateAssets>all</PrivateAssets>
16-
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
12+
<PackageReference Include="Flurl.Http" Version="4.0.2" />
13+
<PackageReference Include="Flurl.Http.Newtonsoft" Version="0.9.1" />
14+
<PackageReference Include="MemoryPack" Version="1.21.3" />
15+
<PackageReference Include="MessagePipe" Version="1.8.1" />
16+
<PackageReference Include="MessagePipe.Analyzer" Version="1.8.1">
17+
<PrivateAssets>all</PrivateAssets>
18+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1719
</PackageReference>
18-
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10"/>
19-
<PackageReference Include="ActualLab.Fusion" Version="9.5.52"/>
20-
<PackageReference Include="ActualLab.Fusion.EntityFramework.Npgsql" Version="9.5.52"/>
21-
<PackageReference Include="ActualLab.Rpc.Server" Version="9.5.52"/>
22-
<PackageReference Include="Argon.Sfu.Protocol" Version="1.26.0"/>
20+
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
21+
<PackageReference Include="ActualLab.Fusion" Version="9.5.52" />
22+
<PackageReference Include="ActualLab.Fusion.EntityFramework.Npgsql" Version="9.5.52" />
23+
<PackageReference Include="ActualLab.Rpc.Server" Version="9.5.52" />
24+
<PackageReference Include="Argon.Sfu.Protocol" Version="1.26.0" />
2325
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10">
2426
<PrivateAssets>all</PrivateAssets>
2527
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2628
</PackageReference>
27-
<PackageReference Include="Microsoft.Orleans.Persistence.AdoNet" Version="8.2.0"/>
28-
<PackageReference Include="Microsoft.Orleans.Runtime" Version="8.2.0"/>
29-
<PackageReference Include="Microsoft.Orleans.Sdk" Version="8.2.0"/>
30-
<PackageReference Include="Microsoft.Orleans.Serialization" Version="8.2.0"/>
31-
<PackageReference Include="Microsoft.Orleans.Server" Version="8.2.0"/>
32-
<PackageReference Include="Npgsql" Version="8.0.5"/>
33-
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
34-
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.1.2"/>
29+
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
30+
<PackageReference Include="Microsoft.Orleans.Persistence.AdoNet" Version="8.2.0" />
31+
<PackageReference Include="Microsoft.Orleans.Runtime" Version="8.2.0" />
32+
<PackageReference Include="Microsoft.Orleans.Sdk" Version="8.2.0" />
33+
<PackageReference Include="Microsoft.Orleans.Serialization" Version="8.2.0" />
34+
<PackageReference Include="Microsoft.Orleans.Server" Version="8.2.0" />
35+
<PackageReference Include="Npgsql" Version="8.0.5" />
36+
<PackageReference Include="R3" Version="1.2.9" />
37+
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
38+
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.1.2" />
3539
</ItemGroup>
3640

3741
<ItemGroup>
38-
<ProjectReference Include="..\Argon.Contracts\Argon.Contracts.csproj"/>
39-
<ProjectReference Include="..\ServiceDefaults\ServiceDefaults.csproj"/>
42+
<ProjectReference Include="..\Argon.Contracts\Argon.Contracts.csproj" />
43+
<ProjectReference Include="..\ServiceDefaults\ServiceDefaults.csproj" />
4044
</ItemGroup>
4145

4246
<ItemGroup>
@@ -45,4 +49,8 @@
4549
</Content>
4650
</ItemGroup>
4751

52+
<ItemGroup>
53+
<Folder Include="Features\Jwt\" />
54+
</ItemGroup>
55+
4856
</Project>

src/Argon.Api/Extensions/JwtExtension.cs

Lines changed: 0 additions & 47 deletions
This file was deleted.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
namespace Argon.Api.Features.Jwt;
2+
3+
using Microsoft.AspNetCore.Authentication.JwtBearer;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Microsoft.IdentityModel.Tokens;
6+
using System.Text;
7+
using Microsoft.Extensions.Options;
8+
using Microsoft.AspNetCore.Authentication;
9+
using Microsoft.Extensions.DependencyInjection.Extensions;
10+
11+
public record JwtOptions
12+
{
13+
public required string Issuer { get; set; }
14+
public required string Audience { get; set; }
15+
// TODO use cert in production
16+
public required string Key { get; set; }
17+
public required TimeSpan Expires { get; set; }
18+
19+
public void Deconstruct(out string issuer, out string audience, out string key)
20+
{
21+
audience = this.Audience;
22+
issuer = this.Issuer;
23+
key = this.Key;
24+
}
25+
}
26+
27+
28+
public static class JwtFeature
29+
{
30+
public static IServiceCollection AddJwt(this WebApplicationBuilder builder)
31+
{
32+
builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection("Jwt"));
33+
builder.Services.AddKeyedSingleton(JwtBearerDefaults.AuthenticationScheme,
34+
(services, _) =>
35+
{
36+
var options = services.GetRequiredService<IOptions<JwtOptions>>();
37+
var (issuer, audience, key) = options.Value;
38+
return new TokenValidationParameters
39+
{
40+
ValidateIssuer = true,
41+
ValidateAudience = true,
42+
ValidateIssuerSigningKey = true,
43+
ValidIssuer = issuer,
44+
ValidAudience = audience,
45+
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)),
46+
ClockSkew = TimeSpan.Zero
47+
};
48+
});
49+
50+
builder.Services.AddAuthentication(options =>
51+
{
52+
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
53+
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
54+
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
55+
}).AddJwtBearer(o =>
56+
{
57+
o.Events = new JwtBearerEvents
58+
{
59+
OnMessageReceived = ctx =>
60+
{
61+
if (ctx.Request.Headers.TryGetValue("x-argon-token", out var value))
62+
{
63+
ctx.Token = value;
64+
return Task.CompletedTask;
65+
}
66+
67+
ctx.Response.StatusCode = 401;
68+
return Task.CompletedTask;
69+
}
70+
};
71+
});
72+
return builder.Services;
73+
}
74+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
namespace Argon.Api.Features.Rpc;
2+
3+
using ActualLab.Rpc.Infrastructure;
4+
using ActualLab.Rpc;
5+
using MemoryPack;
6+
using Microsoft.Extensions.Caching.Distributed;
7+
using ActualLab;
8+
using ActualLab.Reflection;
9+
using Grains;
10+
using Grains.Persistence.States;
11+
using Microsoft.AspNetCore.Authorization;
12+
using Orleans;
13+
14+
public class FusionAuthorizationMiddleware(IServiceProvider Services, IGrainFactory GrainFactory) : RpcInboundMiddleware(Services)
15+
{
16+
public AsyncLocal<string> Token = new AsyncLocal<string>();
17+
public override async Task OnBeforeCall(RpcInboundCall call)
18+
{
19+
var existAttribute = call.MethodDef.Method.GetAttributes<AuthorizeAttribute>(true, true).Count != 0;
20+
21+
if (!existAttribute)
22+
{
23+
await base.OnBeforeCall(call);
24+
return;
25+
}
26+
27+
var grain = GrainFactory.GetGrain<IFusionSession>(call.Context.Peer.Id);
28+
29+
var state = await grain.GetState();
30+
if (state.IsAuthorized)
31+
{
32+
await base.OnBeforeCall(call);
33+
return;
34+
}
35+
36+
call.Cancel();
37+
return;
38+
}
39+
}
40+
41+
public class FusionServiceContext(IGrainFactory GrainFactory) : IFusionServiceContext
42+
{
43+
public ValueTask<FusionSession> GetSessionState()
44+
{
45+
var current = RpcInboundContext.GetCurrent();
46+
var peerId = current.Peer.Id;
47+
48+
var grain = GrainFactory.GetGrain<IFusionSession>(peerId);
49+
50+
return grain.GetState();
51+
}
52+
}
53+
54+
public interface IFusionServiceContext
55+
{
56+
ValueTask<FusionSession> GetSessionState();
57+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Argon.Api.Grains.Persistence.States;
2+
3+
using Argon.Sfu;
4+
using MemoryPack;
5+
6+
[GenerateSerializer]
7+
[Serializable]
8+
[MemoryPackable]
9+
[Alias(nameof(FusionSession))]
10+
public partial class FusionSession
11+
{
12+
[Id(0)] public required Guid Id { get; set; } = Guid.Empty;
13+
[Id(1)] public required bool IsAuthorized { get; set; }
14+
}

src/Argon.Api/Grains/FusionGrain.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
namespace Argon.Api.Grains;
2+
3+
using System.IdentityModel.Tokens.Jwt;
4+
using Microsoft.IdentityModel.Tokens;
5+
using Persistence.States;
6+
7+
public class FusionGrain(
8+
[PersistentState("sessions", "OrleansStorage")]
9+
IPersistentState<FusionSession> sessionStorage,
10+
TokenValidationParameters JwtParameters) : Grain, IFusionSession
11+
{
12+
public async ValueTask<bool> AuthorizeAsync(string token)
13+
{
14+
var tokenHandler = new JwtSecurityTokenHandler();
15+
tokenHandler.ValidateToken(token, JwtParameters, out SecurityToken validatedToken);
16+
var jwt = (JwtSecurityToken)validatedToken;
17+
18+
sessionStorage.State.Id = Guid.Parse(jwt.Id);
19+
sessionStorage.State.IsAuthorized = true;
20+
await sessionStorage.WriteStateAsync();
21+
return true;
22+
}
23+
24+
public async ValueTask<FusionSession> GetState()
25+
{
26+
await sessionStorage.ReadStateAsync();
27+
return sessionStorage.State;
28+
}
29+
30+
}
31+
32+
33+
public interface IFusionSession : IGrainWithGuidKey
34+
{
35+
[Alias("AuthorizeAsync")]
36+
ValueTask<bool> AuthorizeAsync(string token);
37+
38+
[Alias("GetState")]
39+
ValueTask<FusionSession> GetState();
40+
}

src/Argon.Api/Program.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
using ActualLab.Fusion;
2+
using ActualLab.Fusion.Extensions;
23
using ActualLab.Rpc;
4+
using ActualLab.Rpc.Infrastructure;
35
using ActualLab.Rpc.Server;
46
using Argon.Api.Entities;
57
using Argon.Api.Extensions;
8+
using Argon.Api.Features.Jwt;
9+
using Argon.Api.Features.Rpc;
610
using Argon.Api.Filters;
711
using Argon.Api.Migrations;
812
using Argon.Api.Services;
@@ -11,6 +15,7 @@
1115

1216
var builder = WebApplication.CreateBuilder(args);
1317

18+
builder.AddJwt();
1419
builder.AddServiceDefaults();
1520
builder.AddRedisOutputCache("cache");
1621
builder.AddRabbitMQClient("rmq");
@@ -19,12 +24,13 @@
1924
builder.Services.AddFusion(RpcServiceMode.Server, true)
2025
.Rpc.AddServer<IUserAuthorization, UserAuthorization>()
2126
.AddServer<IUserInteraction, UserInteractionService>()
27+
.AddInboundMiddleware<FusionAuthorizationMiddleware>()
2228
.AddWebSocketServer(true);
2329
builder.AddSwaggerWithAuthHeader();
24-
builder.AddJwt();
2530
builder.Services.AddAuthorization();
2631
builder.AddSelectiveForwardingUnit();
2732
builder.Services.AddTransient<UserManagerService>();
33+
builder.Services.AddTransient<IFusionServiceContext, FusionServiceContext>();
2834
builder.AddOrleans();
2935
var app = builder.Build();
3036
app.UseSwagger();
@@ -34,7 +40,8 @@
3440
app.UseAuthorization();
3541
app.MapControllers();
3642
app.MapDefaultEndpoints();
43+
app.UseWebSockets();
3744
app.MapRpcWebSocketServer();
3845
var buildTime = File.GetLastWriteTimeUtc(typeof(Program).Assembly.Location);
3946
app.MapGet("/", () => new { buildTime });
40-
await app.WarpUp<ApplicationDbContext>().RunAsync();
47+
await app.WarpUp<ApplicationDbContext>().RunAsync();

src/Argon.Api/Properties/launchSettings.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"http": {
1313
"commandName": "Project",
1414
"dotnetRunMessages": true,
15-
"launchBrowser": true,
15+
"launchBrowser": false,
1616
"launchUrl": "swagger",
1717
"applicationUrl": "http://localhost:5100",
1818
"environmentVariables": {
@@ -22,7 +22,7 @@
2222
"https": {
2323
"commandName": "Project",
2424
"dotnetRunMessages": true,
25-
"launchBrowser": true,
25+
"launchBrowser": false,
2626
"launchUrl": "swagger",
2727
"applicationUrl": "https://localhost:7206;http://localhost:5100",
2828
"environmentVariables": {
@@ -31,7 +31,7 @@
3131
},
3232
"IIS Express": {
3333
"commandName": "IISExpress",
34-
"launchBrowser": true,
34+
"launchBrowser": false,
3535
"launchUrl": "swagger",
3636
"environmentVariables": {
3737
"ASPNETCORE_ENVIRONMENT": "Development"

0 commit comments

Comments
 (0)