mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-07 22:37:54 +00:00
[WIP] feat: scoped api keys
This commit is contained in:
parent
e7ee593a85
commit
06cb160f95
45 changed files with 1264 additions and 154 deletions
|
|
@ -16,6 +16,8 @@ public class CoreConfig
|
|||
public string? SeqLogUrl { get; set; }
|
||||
public string? DispatchProxyUrl { get; set; }
|
||||
public string? DispatchProxyToken { get; set; }
|
||||
public string? InternalApiBaseUrl { get; set; }
|
||||
public string? InternalApiToken { get; set; }
|
||||
|
||||
public LogEventLevel ConsoleLogLevel { get; set; } = LogEventLevel.Debug;
|
||||
public LogEventLevel ElasticLogLevel { get; set; } = LogEventLevel.Information;
|
||||
|
|
|
|||
55
PluralKit.Core/Database/Repository/ModelRepository.ApiKey.cs
Normal file
55
PluralKit.Core/Database/Repository/ModelRepository.ApiKey.cs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
using Dapper;
|
||||
|
||||
using SqlKata;
|
||||
|
||||
namespace PluralKit.Core;
|
||||
|
||||
public partial class ModelRepository
|
||||
{
|
||||
public async Task<PKApiKey?> GetApiKey(Guid id)
|
||||
{
|
||||
var query = new Query("api_keys")
|
||||
.Select("id", "system", "scopes", "app", "name", "created")
|
||||
.SelectRaw("[kind]::text")
|
||||
.Where("id", id);
|
||||
|
||||
return await _db.QueryFirst<PKApiKey?>(query);
|
||||
}
|
||||
|
||||
public async Task<PKApiKey?> GetApiKeyByName(SystemId system, string name)
|
||||
{
|
||||
var query = new Query("api_keys")
|
||||
.Select("id", "system", "scopes", "app", "name", "created")
|
||||
.SelectRaw("[kind]::text")
|
||||
.Where("system", system)
|
||||
.WhereRaw("lower(name) = lower(?)", name.ToLower());
|
||||
|
||||
return await _db.QueryFirst<PKApiKey?>(query);
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<PKApiKey> GetSystemApiKeys(SystemId system)
|
||||
{
|
||||
var query = new Query("api_keys")
|
||||
.Select("id", "system", "scopes", "app", "name", "created")
|
||||
.SelectRaw("[kind]::text")
|
||||
.Where("system", system)
|
||||
.WhereRaw("[kind]::text not in ( 'dashboard' )")
|
||||
.OrderByDesc("created");
|
||||
|
||||
return _db.QueryStream<PKApiKey>(query);
|
||||
}
|
||||
|
||||
public async Task UpdateApiKey(Guid id, ApiKeyPatch patch)
|
||||
{
|
||||
_logger.Information("Updated API key {keyId}: {@ApiKeyPatch}", id, patch);
|
||||
var query = patch.Apply(new Query("api_keys").Where("id", id));
|
||||
await _db.ExecuteQuery(query, "returning *");
|
||||
}
|
||||
|
||||
public async Task DeleteApiKey(Guid id)
|
||||
{
|
||||
var query = new Query("api_keys").AsDelete().Where("id", id);
|
||||
await _db.ExecuteQuery(query);
|
||||
_logger.Information("Deleted ApiKey {keyId}", id);
|
||||
}
|
||||
}
|
||||
15
PluralKit.Core/Models/PKApiKey.cs
Normal file
15
PluralKit.Core/Models/PKApiKey.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using NodaTime;
|
||||
|
||||
namespace PluralKit.Core;
|
||||
|
||||
public class PKApiKey
|
||||
{
|
||||
public Guid Id { get; private set; }
|
||||
public SystemId System { get; private set; }
|
||||
public string Kind { get; private set; }
|
||||
public string[] Scopes { get; private set; }
|
||||
public Guid? App { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
|
||||
public Instant Created { get; private set; }
|
||||
}
|
||||
24
PluralKit.Core/Models/Patch/ApiKeyPatch.cs
Normal file
24
PluralKit.Core/Models/Patch/ApiKeyPatch.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using SqlKata;
|
||||
|
||||
namespace PluralKit.Core;
|
||||
|
||||
public class ApiKeyPatch: PatchObject
|
||||
{
|
||||
public Partial<string> Name { get; set; }
|
||||
|
||||
public override Query Apply(Query q) => q.ApplyPatch(wrapper => wrapper
|
||||
.With("name", Name)
|
||||
);
|
||||
|
||||
public JObject ToJson()
|
||||
{
|
||||
var o = new JObject();
|
||||
|
||||
if (Name.IsPresent)
|
||||
o.Add("name", Name.Value);
|
||||
|
||||
return o;
|
||||
}
|
||||
}
|
||||
72
PluralKit.Core/Services/ApiKeyService.cs
Normal file
72
PluralKit.Core/Services/ApiKeyService.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
using Autofac;
|
||||
|
||||
using System.Text;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace PluralKit.Core;
|
||||
|
||||
public class ApiKeyService
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
private readonly ILogger _logger;
|
||||
private readonly CoreConfig _cfg;
|
||||
private readonly ILifetimeScope _provider;
|
||||
|
||||
public ApiKeyService(ILogger logger, ILifetimeScope provider, CoreConfig cfg)
|
||||
{
|
||||
_logger = logger;
|
||||
_cfg = cfg;
|
||||
_provider = provider;
|
||||
|
||||
_client = new HttpClient();
|
||||
_client.DefaultRequestHeaders.Add("User-Agent", "PluralKitInternal");
|
||||
}
|
||||
|
||||
public async Task<string?> CreateUserApiKey(SystemId systemId, string keyName, string[] keyScopes, bool check = false)
|
||||
{
|
||||
if (_cfg.InternalApiBaseUrl == null || _cfg.InternalApiToken == null)
|
||||
throw new Exception("internal API config not set!");
|
||||
|
||||
if (!Uri.TryCreate(new Uri(_cfg.InternalApiBaseUrl), "/internal/apikey/user", out var uri))
|
||||
throw new Exception("internal API base invalid!?");
|
||||
|
||||
var repo = _provider.Resolve<ModelRepository>();
|
||||
var system = await repo.GetSystem(systemId);
|
||||
if (system == null)
|
||||
return null;
|
||||
|
||||
var reqData = new JObject();
|
||||
reqData.Add("check", check);
|
||||
reqData.Add("system", system.Id.Value);
|
||||
reqData.Add("name", keyName);
|
||||
reqData.Add("scopes", new JArray(keyScopes));
|
||||
|
||||
var req = new HttpRequestMessage()
|
||||
{
|
||||
RequestUri = uri,
|
||||
Method = HttpMethod.Post,
|
||||
Content = new StringContent(JsonConvert.SerializeObject(reqData), Encoding.UTF8, "application/json"),
|
||||
};
|
||||
req.Headers.Add("X-Pluralkit-InternalAuth", _cfg.InternalApiToken);
|
||||
|
||||
var res = await _client.SendAsync(req);
|
||||
var data = JsonConvert.DeserializeObject<JObject>(await res.Content.ReadAsStringAsync());
|
||||
|
||||
if (data.ContainsKey("error"))
|
||||
throw new Exception($"API key validation failed: {(data.Value<string>("error"))}");
|
||||
|
||||
if (data.Value<bool>("valid") != true)
|
||||
throw new Exception("API key validation failed: unknown error");
|
||||
|
||||
if (!data.ContainsKey("token"))
|
||||
return null;
|
||||
|
||||
return data.Value<string>("token");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue