Skip to content

Commit 9ba75ea

Browse files
committed
Add new User UI
1 parent d9540e9 commit 9ba75ea

17 files changed

+1067
-486
lines changed

AiServer/AiServer.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,9 @@
2424
<ProjectReference Include="..\AiServer.ServiceInterface\AiServer.ServiceInterface.csproj" />
2525
<ProjectReference Include="..\AiServer.ServiceModel\AiServer.ServiceModel.csproj" />
2626
</ItemGroup>
27+
28+
<ItemGroup>
29+
<Folder Include="wwwroot\mjs\pages\" />
30+
</ItemGroup>
2731

2832
</Project>

AiServer/Configure.Auth.cs

Lines changed: 168 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
using ServiceStack.Auth;
1+
using System.Collections.Concurrent;
2+
using ServiceStack.Auth;
23
using AiServer.ServiceInterface;
4+
using ServiceStack.Configuration;
5+
using ServiceStack.Html;
6+
using ServiceStack.Web;
37

48
[assembly: HostingStartup(typeof(ConfigureAuth))]
59

@@ -23,4 +27,166 @@ public void Configure(IWebHostBuilder builder) => builder
2327
using var db = HostContext.AppHost.GetDbConnection();
2428
appHost.GetPlugin<ApiKeysFeature>().InitSchema(db);
2529
});
26-
}
30+
}
31+
32+
public class ApiKeyCredentialsProvider : AuthProvider
33+
{
34+
public override string Type => AuthenticateService.CredentialsProvider;
35+
public ConcurrentDictionary<string, IApiKey> ValidApiKeys { get; private set; } = new();
36+
37+
public ApiKeyCredentialsProvider()
38+
{
39+
Provider = AuthenticateService.CredentialsProvider;
40+
Sort = -1;
41+
Label = Provider.ToPascalCase();
42+
FormLayout =
43+
[
44+
Input.For<Authenticate>(x => x.UserName, c =>
45+
{
46+
c.Label = "Display Name";
47+
c.Required = true;
48+
}),
49+
50+
Input.For<Authenticate>(x => x.Password, c =>
51+
{
52+
c.Label = "API Key";
53+
c.Type = "Password";
54+
c.Required = true;
55+
}),
56+
57+
Input.For<Authenticate>(x => x.RememberMe)
58+
];
59+
}
60+
61+
public override void Configure(IServiceCollection services, AuthFeature authFeature)
62+
{
63+
}
64+
65+
public override void Register(IAppHost appHost, AuthFeature authFeature)
66+
{
67+
var feature = appHost.GetPlugin<ApiKeysFeature>();
68+
if (feature != null)
69+
{
70+
ValidApiKeys = feature.ValidApiKeys;
71+
}
72+
73+
appHost.PreRequestFilters.Add((req, res) =>
74+
{
75+
var session = req.GetSession();
76+
if (session.IsAuthenticated && session is AuthUserSession { RequestTokenSecret: not null } authSession)
77+
{
78+
if (appHost.Config.AdminAuthSecret == authSession.RequestTokenSecret)
79+
{
80+
req.Items[Keywords.AuthSecret] = appHost.Config.AdminAuthSecret;
81+
req.Items[Keywords.Authorization] = "Bearer " + appHost.Config.AdminAuthSecret;
82+
}
83+
if (ValidApiKeys.TryGetValue(authSession.RequestTokenSecret, out var _))
84+
{
85+
req.Items[Keywords.Authorization] = "Bearer " + authSession.RequestTokenSecret;
86+
}
87+
}
88+
});
89+
}
90+
91+
public override async Task<object?> AuthenticateAsync(IServiceBase authService, IAuthSession? session, Authenticate request, CancellationToken token = new())
92+
{
93+
var req = authService.Request;
94+
var authSecret = request.Password;
95+
var sessionId = session?.Id ?? Guid.NewGuid().ToString("n");
96+
session = null;
97+
if (HostContext.Config.AdminAuthSecret != null && HostContext.Config.AdminAuthSecret == authSecret)
98+
{
99+
var authSession = HostContext.AssertPlugin<AuthFeature>().AuthSecretSession;
100+
session = new AuthUserSession {
101+
Id = sessionId,
102+
DisplayName = authSession.DisplayName,
103+
UserName = authSession.UserName,
104+
UserAuthName = authSession.UserAuthName,
105+
AuthProvider = AuthenticateService.ApiKeyProvider,
106+
IsAuthenticated = authSession.IsAuthenticated,
107+
Roles = authSession.Roles,
108+
Permissions = authSession.Permissions,
109+
UserAuthId = authSession.UserAuthId,
110+
RequestTokenSecret = authSecret,
111+
};
112+
}
113+
114+
var apiKey = await GetValidApiKeyAsync(authSecret, req);
115+
if (apiKey != null)
116+
{
117+
var dbApiKey = (ApiKeysFeature.ApiKey)apiKey;
118+
session = new AuthUserSession
119+
{
120+
Id = sessionId,
121+
DisplayName = request.UserName,
122+
UserName = dbApiKey.Name,
123+
UserAuthName = dbApiKey.Name,
124+
AuthProvider = AuthenticateService.ApiKeyProvider,
125+
IsAuthenticated = true,
126+
Roles = dbApiKey.Scopes,
127+
Permissions = [],
128+
UserAuthId = $"{dbApiKey.Id}",
129+
RequestTokenSecret = apiKey.Key,
130+
};
131+
}
132+
133+
if (session != null)
134+
{
135+
session.IsAuthenticated = true;
136+
await SaveSessionAsync(session, req, token:token);
137+
138+
var response = new AuthenticateResponse
139+
{
140+
UserId = session.UserAuthId,
141+
UserName = session.UserName,
142+
SessionId = session.Id,
143+
DisplayName = session.DisplayName,
144+
ReferrerUrl = session.ReferrerUrl,
145+
AuthProvider = session.AuthProvider,
146+
};
147+
return response;
148+
}
149+
150+
throw HttpError.Unauthorized(ErrorMessages.ApiKeyInvalid.Localize(authService.Request));
151+
}
152+
153+
public async Task<IApiKey?> GetValidApiKeyAsync(string token, IRequest request)
154+
{
155+
if (string.IsNullOrEmpty(token))
156+
return null;
157+
158+
var source = request.TryResolve<IApiKeySource>();
159+
if (ValidApiKeys.TryGetValue(token, out var apiKey))
160+
return apiKey;
161+
162+
apiKey = await source.GetApiKeyAsync(token);
163+
if (apiKey != null)
164+
{
165+
ValidApiKeys[token] = apiKey;
166+
167+
if (apiKey.HasScope(RoleNames.Admin))
168+
return apiKey;
169+
170+
return apiKey;
171+
}
172+
173+
return null;
174+
}
175+
176+
async Task SaveSessionAsync(IAuthSession session, IRequest httpReq, CancellationToken token)
177+
{
178+
session.LastModified = DateTime.UtcNow;
179+
httpReq.Items[Keywords.Session] = session;
180+
181+
var sessionKey = SessionFeature.GetSessionKey(session.Id ?? httpReq.GetOrCreateSessionId());
182+
await httpReq.GetCacheClientAsync().CacheSetAsync(sessionKey, session, SessionFeature.DefaultSessionExpiry, token);
183+
}
184+
185+
public override bool IsAuthorized(IAuthSession session, IAuthTokens tokens, Authenticate? request = null)
186+
{
187+
if (session is AuthUserSession { RequestTokenSecret: not null } userSession)
188+
return HostContext.Config.AdminAuthSecret == userSession.RequestTokenSecret
189+
|| ValidApiKeys.ContainsKey(userSession.RequestTokenSecret);
190+
return false;
191+
}
192+
}

AiServer/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,6 @@
2525
options.MapEndpoints();
2626
});
2727

28+
app.MapFallbackToFile("index.html");
29+
2830
app.Run();

0 commit comments

Comments
 (0)