Compare commits
No commits in common. "291941311808cbde7c937cc4e48dc4e20bdae413" and "b95fb76cd424bb401553f1417576380d5aca9594" have entirely different histories.
2919413118
...
b95fb76cd4
15 changed files with 50 additions and 149 deletions
|
@ -5,7 +5,6 @@ using Foxchat.Chat.Services;
|
||||||
using Foxchat.Core.Models;
|
using Foxchat.Core.Models;
|
||||||
using Foxchat.Core.Models.Http;
|
using Foxchat.Core.Models.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using ApiError = Foxchat.Core.ApiError;
|
using ApiError = Foxchat.Core.ApiError;
|
||||||
|
|
||||||
namespace Foxchat.Chat.Controllers.Api;
|
namespace Foxchat.Chat.Controllers.Api;
|
||||||
|
@ -17,9 +16,10 @@ public class GuildsController(ILogger logger, ChatContext db, UserResolverServic
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> CreateGuild([FromBody] GuildsApi.CreateGuildRequest req)
|
public async Task<IActionResult> CreateGuild([FromBody] GuildsApi.CreateGuildRequest req)
|
||||||
{
|
{
|
||||||
var (instance, _, userId) = HttpContext.GetSignatureWithUser();
|
var (instance, sig) = HttpContext.GetSignatureOrThrow();
|
||||||
|
if (sig.UserId == null) throw new ApiError.IncomingFederationError("This endpoint requires a user ID.");
|
||||||
|
|
||||||
var user = await userResolverService.ResolveUserAsync(instance, userId);
|
var user = await userResolverService.ResolveUserAsync(instance, sig.UserId);
|
||||||
|
|
||||||
var guild = new Guild
|
var guild = new Guild
|
||||||
{
|
{
|
||||||
|
@ -44,41 +44,4 @@ public class GuildsController(ILogger logger, ChatContext db, UserResolverServic
|
||||||
[new Channels.PartialChannel(defaultChannel.Id.ToString(), defaultChannel.Name)])
|
[new Channels.PartialChannel(defaultChannel.Id.ToString(), defaultChannel.Name)])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}")]
|
|
||||||
public async Task<IActionResult> GetGuild(Ulid id)
|
|
||||||
{
|
|
||||||
var (instance, _, userId) = HttpContext.GetSignatureWithUser();
|
|
||||||
var guild = await db.Guilds
|
|
||||||
.Include(g => g.Channels)
|
|
||||||
.FirstOrDefaultAsync(g =>
|
|
||||||
g.Id == id && g.Users.Any(u => u.RemoteUserId == userId && u.InstanceId == instance.Id));
|
|
||||||
if (guild == null) throw new ApiError.NotFound("Guild not found");
|
|
||||||
|
|
||||||
return Ok(new Guilds.Guild(
|
|
||||||
guild.Id.ToString(),
|
|
||||||
guild.Name,
|
|
||||||
[guild.OwnerId.ToString()],
|
|
||||||
guild.Channels.Select(c => new Channels.PartialChannel(c.Id.ToString(), c.Name))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("@me")]
|
|
||||||
public async Task<IActionResult> GetUserGuilds()
|
|
||||||
{
|
|
||||||
var (instance, _, userId) = HttpContext.GetSignatureWithUser();
|
|
||||||
var guilds = await db.Guilds
|
|
||||||
.Include(g => g.Channels)
|
|
||||||
.Where(g => g.Users.Any(u => u.RemoteUserId == userId && u.InstanceId == instance.Id))
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
var guildResponses = guilds.Select(g => new Guilds.Guild(
|
|
||||||
g.Id.ToString(),
|
|
||||||
g.Name,
|
|
||||||
[g.OwnerId.ToString()],
|
|
||||||
g.Channels.Select(c => new Channels.PartialChannel(c.Id.ToString(), c.Name))
|
|
||||||
));
|
|
||||||
|
|
||||||
return Ok(guildResponses);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
using Foxchat.Chat.Database.Models;
|
using Foxchat.Chat.Database.Models;
|
||||||
using Foxchat.Core;
|
using Foxchat.Core;
|
||||||
using Foxchat.Core.Database;
|
using Foxchat.Core.Database;
|
||||||
using Foxchat.Core.Extensions;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Design;
|
using Microsoft.EntityFrameworkCore.Design;
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
|
@ -11,7 +10,6 @@ namespace Foxchat.Chat.Database;
|
||||||
public class ChatContext : IDatabaseContext
|
public class ChatContext : IDatabaseContext
|
||||||
{
|
{
|
||||||
private readonly NpgsqlDataSource _dataSource;
|
private readonly NpgsqlDataSource _dataSource;
|
||||||
private readonly ILoggerFactory? _loggerFactory;
|
|
||||||
|
|
||||||
public override DbSet<Instance> Instance { get; set; }
|
public override DbSet<Instance> Instance { get; set; }
|
||||||
public DbSet<IdentityInstance> IdentityInstances { get; set; }
|
public DbSet<IdentityInstance> IdentityInstances { get; set; }
|
||||||
|
@ -20,7 +18,7 @@ public class ChatContext : IDatabaseContext
|
||||||
public DbSet<Channel> Channels { get; set; }
|
public DbSet<Channel> Channels { get; set; }
|
||||||
public DbSet<Message> Messages { get; set; }
|
public DbSet<Message> Messages { get; set; }
|
||||||
|
|
||||||
public ChatContext(InstanceConfig config, ILoggerFactory? loggerFactory)
|
public ChatContext(InstanceConfig config)
|
||||||
{
|
{
|
||||||
var connString = new NpgsqlConnectionStringBuilder(config.Database.Url)
|
var connString = new NpgsqlConnectionStringBuilder(config.Database.Url)
|
||||||
{
|
{
|
||||||
|
@ -31,14 +29,12 @@ public class ChatContext : IDatabaseContext
|
||||||
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connString);
|
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connString);
|
||||||
dataSourceBuilder.UseNodaTime();
|
dataSourceBuilder.UseNodaTime();
|
||||||
_dataSource = dataSourceBuilder.Build();
|
_dataSource = dataSourceBuilder.Build();
|
||||||
_loggerFactory = loggerFactory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
=> optionsBuilder
|
=> optionsBuilder
|
||||||
.UseNpgsql(_dataSource, o => o.UseNodaTime())
|
.UseNpgsql(_dataSource, o => o.UseNodaTime())
|
||||||
.UseSnakeCaseNamingConvention()
|
.UseSnakeCaseNamingConvention();
|
||||||
.UseLoggerFactory(_loggerFactory);
|
|
||||||
|
|
||||||
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
|
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
|
||||||
{
|
{
|
||||||
|
@ -77,6 +73,6 @@ public class DesignTimeIdentityContextFactory : IDesignTimeDbContextFactory<Chat
|
||||||
// Get the configuration as our config class
|
// Get the configuration as our config class
|
||||||
.Get<InstanceConfig>() ?? new();
|
.Get<InstanceConfig>() ?? new();
|
||||||
|
|
||||||
return new ChatContext(config, null);
|
return new ChatContext(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -76,11 +76,4 @@ public static class HttpContextExtensions
|
||||||
|
|
||||||
return ((IdentityInstance, SignatureData))obj!;
|
return ((IdentityInstance, SignatureData))obj!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static (IdentityInstance, SignatureData, string) GetSignatureWithUser(this HttpContext ctx)
|
|
||||||
{
|
|
||||||
var (instance, sig) = ctx.GetSignatureOrThrow();
|
|
||||||
if (sig.UserId == null) throw new ApiError.IncomingFederationError("This endpoint requires a user ID.");
|
|
||||||
return (instance, sig, sig.UserId);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -4,14 +4,13 @@ using Foxchat.Core;
|
||||||
using Foxchat.Chat;
|
using Foxchat.Chat;
|
||||||
using Foxchat.Chat.Database;
|
using Foxchat.Chat.Database;
|
||||||
using Foxchat.Chat.Extensions;
|
using Foxchat.Chat.Extensions;
|
||||||
using Foxchat.Core.Extensions;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
var config = builder.AddConfiguration<InstanceConfig>("chat.ini");
|
var config = builder.AddConfiguration<InstanceConfig>("chat.ini");
|
||||||
|
|
||||||
builder.AddSerilog();
|
builder.AddSerilog(config.LogEventLevel);
|
||||||
|
|
||||||
await BuildInfo.ReadBuildInfo();
|
await BuildInfo.ReadBuildInfo();
|
||||||
Log.Information("Starting Foxchat.Chat {Version} ({Hash})", BuildInfo.Version, BuildInfo.Hash);
|
Log.Information("Starting Foxchat.Chat {Version} ({Hash})", BuildInfo.Version, BuildInfo.Hash);
|
||||||
|
|
|
@ -2,6 +2,9 @@ Host = localhost
|
||||||
Port = 7610
|
Port = 7610
|
||||||
Domain = chat.fox.localhost
|
Domain = chat.fox.localhost
|
||||||
|
|
||||||
|
; The level to log things at. Valid settings: Verbose, Debug, Information, Warning, Error, Fatal
|
||||||
|
LogEventLevel = Debug
|
||||||
|
|
||||||
[Database]
|
[Database]
|
||||||
; The database URL in ADO.NET format.
|
; The database URL in ADO.NET format.
|
||||||
Url = "Host=localhost;Database=foxchat_cs_chat;Username=foxchat;Password=password"
|
Url = "Host=localhost;Database=foxchat_cs_chat;Username=foxchat;Password=password"
|
||||||
|
@ -10,11 +13,3 @@ Url = "Host=localhost;Database=foxchat_cs_chat;Username=foxchat;Password=passwor
|
||||||
Timeout = 5
|
Timeout = 5
|
||||||
; The maximum number of open connections. Defaults to 50.
|
; The maximum number of open connections. Defaults to 50.
|
||||||
MaxPoolSize = 500
|
MaxPoolSize = 500
|
||||||
|
|
||||||
[Logging]
|
|
||||||
; The level to log things at. Valid settings: Verbose, Debug, Information, Warning, Error, Fatal
|
|
||||||
LogEventLevel = Debug
|
|
||||||
; Whether to log SQL queries.
|
|
||||||
LogQueries = true
|
|
||||||
; Optional logging to Seq
|
|
||||||
SeqLogUrl = http://localhost:5341
|
|
|
@ -11,7 +11,9 @@ public class CoreConfig
|
||||||
|
|
||||||
public string Address => $"{(Secure ? "https" : "http")}://{Host}:{Port}";
|
public string Address => $"{(Secure ? "https" : "http")}://{Host}:{Port}";
|
||||||
|
|
||||||
public LoggingConfig Logging { get; set; } = new();
|
public LogEventLevel LogEventLevel { get; set; } = LogEventLevel.Debug;
|
||||||
|
public string? SeqLogUrl { get; set; }
|
||||||
|
|
||||||
public DatabaseConfig Database { get; set; } = new();
|
public DatabaseConfig Database { get; set; } = new();
|
||||||
|
|
||||||
public class DatabaseConfig
|
public class DatabaseConfig
|
||||||
|
@ -20,11 +22,4 @@ public class CoreConfig
|
||||||
public int? Timeout { get; set; }
|
public int? Timeout { get; set; }
|
||||||
public int? MaxPoolSize { get; set; }
|
public int? MaxPoolSize { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LoggingConfig
|
|
||||||
{
|
|
||||||
public LogEventLevel LogEventLevel { get; set; } = LogEventLevel.Debug;
|
|
||||||
public string? SeqLogUrl { get; set; }
|
|
||||||
public bool LogQueries { get; set; } = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -7,32 +7,30 @@ using NodaTime;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace Foxchat.Core.Extensions;
|
namespace Foxchat.Core;
|
||||||
|
|
||||||
public static class ServiceCollectionExtensions
|
public static class ServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds Serilog to this service collection. This method also initializes Serilog so it should be called as early as possible, before any log calls.
|
/// Adds Serilog to this service collection. This method also initializes Serilog so it should be called as early as possible, before any log calls.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void AddSerilog(this WebApplicationBuilder builder)
|
public static void AddSerilog(this WebApplicationBuilder builder, LogEventLevel level)
|
||||||
{
|
{
|
||||||
var config = builder.Configuration.Get<CoreConfig>() ?? new();
|
var config = builder.Configuration.Get<CoreConfig>() ?? new();
|
||||||
|
|
||||||
var logCfg = new LoggerConfiguration()
|
var logCfg = new LoggerConfiguration()
|
||||||
.Enrich.FromLogContext()
|
.Enrich.FromLogContext()
|
||||||
.MinimumLevel.Is(config.Logging.LogEventLevel)
|
.MinimumLevel.Is(level)
|
||||||
// ASP.NET's built in request logs are extremely verbose, so we use Serilog's instead.
|
// ASP.NET's built in request logs are extremely verbose, so we use Serilog's instead.
|
||||||
// Serilog doesn't disable the built-in logs, so we do it here.
|
// Serilog doesn't disable the built in logs so we do it here.
|
||||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
||||||
.MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command",
|
|
||||||
config.Logging.LogQueries ? LogEventLevel.Information : LogEventLevel.Warning)
|
|
||||||
.MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning)
|
.MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning)
|
||||||
.MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning)
|
.MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning)
|
||||||
.MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning)
|
.MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning)
|
||||||
.WriteTo.Console();
|
.WriteTo.Console();
|
||||||
|
|
||||||
if (config.Logging.SeqLogUrl != null)
|
if (config.SeqLogUrl != null)
|
||||||
logCfg.WriteTo.Seq(config.Logging.SeqLogUrl, restrictedToMinimumLevel: LogEventLevel.Verbose);
|
logCfg.WriteTo.Seq(config.SeqLogUrl, restrictedToMinimumLevel: LogEventLevel.Verbose);
|
||||||
|
|
||||||
Log.Logger = logCfg.CreateLogger();
|
Log.Logger = logCfg.CreateLogger();
|
||||||
|
|
||||||
|
@ -56,9 +54,9 @@ public static class ServiceCollectionExtensions
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T AddConfiguration<T>(this WebApplicationBuilder builder, string? configFile = null)
|
public static T AddConfiguration<T>(this WebApplicationBuilder builder, string? configFile = null) where T : class, new()
|
||||||
where T : class, new()
|
|
||||||
{
|
{
|
||||||
|
|
||||||
builder.Configuration.Sources.Clear();
|
builder.Configuration.Sources.Clear();
|
||||||
builder.Configuration.AddConfiguration(configFile);
|
builder.Configuration.AddConfiguration(configFile);
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ public class PasswordAuthController(ILogger logger, IdentityContext db, IClock c
|
||||||
var appToken =
|
var appToken =
|
||||||
HttpContext.GetToken() ??
|
HttpContext.GetToken() ??
|
||||||
throw new UnreachableException(); // GetApplicationOrThrow already gets the token and throws if it's null
|
throw new UnreachableException(); // GetApplicationOrThrow already gets the token and throws if it's null
|
||||||
var appScopes = appToken.Scopes.ExpandScopes();
|
var appScopes = appToken.ExpandScopes();
|
||||||
|
|
||||||
if (req.Scopes.Except(appScopes).Any())
|
if (req.Scopes.Except(appScopes).Any())
|
||||||
throw new ApiError.Forbidden("Cannot request token scopes that are not allowed for this token",
|
throw new ApiError.Forbidden("Cannot request token scopes that are not allowed for this token",
|
||||||
|
@ -54,7 +54,7 @@ public class PasswordAuthController(ILogger logger, IdentityContext db, IClock c
|
||||||
{
|
{
|
||||||
var app = HttpContext.GetApplicationOrThrow();
|
var app = HttpContext.GetApplicationOrThrow();
|
||||||
var appToken = HttpContext.GetToken() ?? throw new UnreachableException();
|
var appToken = HttpContext.GetToken() ?? throw new UnreachableException();
|
||||||
var appScopes = appToken.Scopes.ExpandScopes();
|
var appScopes = appToken.ExpandScopes();
|
||||||
|
|
||||||
if (req.Scopes.Except(appScopes).Any())
|
if (req.Scopes.Except(appScopes).Any())
|
||||||
throw new ApiError.Forbidden("Cannot request token scopes that are not allowed for this token",
|
throw new ApiError.Forbidden("Cannot request token scopes that are not allowed for this token",
|
||||||
|
|
|
@ -15,7 +15,7 @@ public class TokenController(ILogger logger, IdentityContext db, IClock clock) :
|
||||||
public async Task<IActionResult> PostToken([FromBody] PostTokenRequest req)
|
public async Task<IActionResult> PostToken([FromBody] PostTokenRequest req)
|
||||||
{
|
{
|
||||||
var app = await db.GetApplicationAsync(req.ClientId, req.ClientSecret);
|
var app = await db.GetApplicationAsync(req.ClientId, req.ClientSecret);
|
||||||
var appScopes = app.Scopes.ExpandScopes();
|
var appScopes = app.ExpandScopes();
|
||||||
|
|
||||||
var scopes = req.Scope.Split(' ');
|
var scopes = req.Scope.Split(' ');
|
||||||
if (scopes.Except(appScopes).Any())
|
if (scopes.Except(appScopes).Any())
|
||||||
|
@ -25,9 +25,9 @@ public class TokenController(ILogger logger, IdentityContext db, IClock clock) :
|
||||||
|
|
||||||
switch (req.GrantType)
|
switch (req.GrantType)
|
||||||
{
|
{
|
||||||
case OauthUtils.ClientCredentials:
|
case "client_credentials":
|
||||||
return await HandleClientCredentialsAsync(app, scopes);
|
return await HandleClientCredentialsAsync(app, scopes);
|
||||||
case OauthUtils.AuthorizationCode:
|
case "authorization_code":
|
||||||
// TODO
|
// TODO
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -1,17 +1,12 @@
|
||||||
using Foxchat.Core;
|
using Foxchat.Core;
|
||||||
using Foxchat.Core.Models;
|
using Foxchat.Core.Models;
|
||||||
using Foxchat.Identity.Database;
|
using Foxchat.Identity.Database;
|
||||||
using Foxchat.Identity.Database.Models;
|
|
||||||
using Foxchat.Identity.Middleware;
|
|
||||||
using Foxchat.Identity.Utils;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Foxchat.Identity.Controllers;
|
namespace Foxchat.Identity.Controllers;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[ClientAuthenticate]
|
|
||||||
[Route("/_fox/ident/users")]
|
[Route("/_fox/ident/users")]
|
||||||
public class UsersController(ILogger logger, InstanceConfig config, IdentityContext db) : ControllerBase
|
public class UsersController(ILogger logger, InstanceConfig config, IdentityContext db) : ControllerBase
|
||||||
{
|
{
|
||||||
|
@ -23,30 +18,4 @@ public class UsersController(ILogger logger, InstanceConfig config, IdentityCont
|
||||||
|
|
||||||
return Ok(new Users.User(user.Id.ToString(), user.Username, config.Domain, null));
|
return Ok(new Users.User(user.Id.ToString(), user.Username, config.Domain, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("@me")]
|
|
||||||
[Authorize("identify")]
|
|
||||||
public IActionResult GetMe()
|
|
||||||
{
|
|
||||||
var acct = HttpContext.GetAccountOrThrow();
|
|
||||||
var token = HttpContext.GetToken()!;
|
|
||||||
var showEmail = token.Scopes.ExpandScopes().Contains("email");
|
|
||||||
|
|
||||||
return Ok(new MeUser(
|
|
||||||
acct.Id,
|
|
||||||
acct.Username,
|
|
||||||
acct.Role,
|
|
||||||
null,
|
|
||||||
showEmail ? acct.Email : null
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public record MeUser(
|
|
||||||
Ulid Id,
|
|
||||||
string Username,
|
|
||||||
Account.AccountRole Role,
|
|
||||||
string? AvatarUrl,
|
|
||||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
|
||||||
string? Email
|
|
||||||
);
|
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
using Foxchat.Core;
|
using Foxchat.Core;
|
||||||
using Foxchat.Core.Database;
|
using Foxchat.Core.Database;
|
||||||
using Foxchat.Core.Extensions;
|
|
||||||
using Foxchat.Identity.Database.Models;
|
using Foxchat.Identity.Database.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Design;
|
using Microsoft.EntityFrameworkCore.Design;
|
||||||
|
@ -11,7 +10,6 @@ namespace Foxchat.Identity.Database;
|
||||||
public class IdentityContext : IDatabaseContext
|
public class IdentityContext : IDatabaseContext
|
||||||
{
|
{
|
||||||
private readonly NpgsqlDataSource _dataSource;
|
private readonly NpgsqlDataSource _dataSource;
|
||||||
private readonly ILoggerFactory? _loggerFactory;
|
|
||||||
|
|
||||||
public override DbSet<Instance> Instance { get; set; }
|
public override DbSet<Instance> Instance { get; set; }
|
||||||
public DbSet<Account> Accounts { get; set; }
|
public DbSet<Account> Accounts { get; set; }
|
||||||
|
@ -20,7 +18,7 @@ public class IdentityContext : IDatabaseContext
|
||||||
public DbSet<Token> Tokens { get; set; }
|
public DbSet<Token> Tokens { get; set; }
|
||||||
public DbSet<GuildAccount> GuildAccounts { get; set; }
|
public DbSet<GuildAccount> GuildAccounts { get; set; }
|
||||||
|
|
||||||
public IdentityContext(InstanceConfig config, ILoggerFactory? loggerFactory)
|
public IdentityContext(InstanceConfig config)
|
||||||
{
|
{
|
||||||
var connString = new NpgsqlConnectionStringBuilder(config.Database.Url)
|
var connString = new NpgsqlConnectionStringBuilder(config.Database.Url)
|
||||||
{
|
{
|
||||||
|
@ -31,14 +29,12 @@ public class IdentityContext : IDatabaseContext
|
||||||
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connString);
|
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connString);
|
||||||
dataSourceBuilder.UseNodaTime();
|
dataSourceBuilder.UseNodaTime();
|
||||||
_dataSource = dataSourceBuilder.Build();
|
_dataSource = dataSourceBuilder.Build();
|
||||||
_loggerFactory = loggerFactory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
=> optionsBuilder
|
=> optionsBuilder
|
||||||
.UseNpgsql(_dataSource, o => o.UseNodaTime())
|
.UseNpgsql(_dataSource, o => o.UseNodaTime())
|
||||||
.UseSnakeCaseNamingConvention()
|
.UseSnakeCaseNamingConvention();
|
||||||
.UseLoggerFactory(_loggerFactory);
|
|
||||||
|
|
||||||
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
|
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
|
||||||
{
|
{
|
||||||
|
@ -71,6 +67,6 @@ public class DesignTimeIdentityContextFactory : IDesignTimeDbContextFactory<Iden
|
||||||
// Get the configuration as our config class
|
// Get the configuration as our config class
|
||||||
.Get<InstanceConfig>() ?? new();
|
.Get<InstanceConfig>() ?? new();
|
||||||
|
|
||||||
return new IdentityContext(config, null);
|
return new IdentityContext(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,8 @@ public class ClientAuthorizationMiddleware(
|
||||||
var token = ctx.GetToken();
|
var token = ctx.GetToken();
|
||||||
if (token == null || token.Expires < clock.GetCurrentInstant())
|
if (token == null || token.Expires < clock.GetCurrentInstant())
|
||||||
throw new ApiError.Unauthorized("This endpoint requires an authenticated user.");
|
throw new ApiError.Unauthorized("This endpoint requires an authenticated user.");
|
||||||
if (attribute.Scopes.Length > 0 && attribute.Scopes.Except(token.Scopes.ExpandScopes()).Any())
|
if (attribute.Scopes.Length > 0 && attribute.Scopes.Except(token.ExpandScopes()).Any())
|
||||||
throw new ApiError.Forbidden("This endpoint requires ungranted scopes.", attribute.Scopes.Except(token.Scopes.ExpandScopes()));
|
throw new ApiError.Forbidden("This endpoint requires ungranted scopes.", attribute.Scopes.Except(token.ExpandScopes()));
|
||||||
|
|
||||||
await next(ctx);
|
await next(ctx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using Newtonsoft.Json.Serialization;
|
using Newtonsoft.Json.Serialization;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Foxchat.Core;
|
using Foxchat.Core;
|
||||||
using Foxchat.Core.Extensions;
|
|
||||||
using Foxchat.Identity;
|
using Foxchat.Identity;
|
||||||
using Foxchat.Identity.Database;
|
using Foxchat.Identity.Database;
|
||||||
using Foxchat.Identity.Services;
|
using Foxchat.Identity.Services;
|
||||||
|
@ -12,7 +11,7 @@ var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
var config = builder.AddConfiguration<InstanceConfig>("identity.ini");
|
var config = builder.AddConfiguration<InstanceConfig>("identity.ini");
|
||||||
|
|
||||||
builder.AddSerilog();
|
builder.AddSerilog(config.LogEventLevel);
|
||||||
|
|
||||||
await BuildInfo.ReadBuildInfo();
|
await BuildInfo.ReadBuildInfo();
|
||||||
Log.Information("Starting Foxchat.Identity {Version} ({Hash})", BuildInfo.Version, BuildInfo.Hash);
|
Log.Information("Starting Foxchat.Identity {Version} ({Hash})", BuildInfo.Version, BuildInfo.Hash);
|
||||||
|
|
|
@ -6,10 +6,7 @@ namespace Foxchat.Identity.Utils;
|
||||||
|
|
||||||
public static class OauthUtils
|
public static class OauthUtils
|
||||||
{
|
{
|
||||||
public const string ClientCredentials = "client_credentials";
|
public static readonly string[] Scopes = ["identify", "chat_client"];
|
||||||
public const string AuthorizationCode = "authorization_code";
|
|
||||||
|
|
||||||
public static readonly string[] Scopes = ["identify", "email", "guilds", "chat_client"];
|
|
||||||
|
|
||||||
private static readonly string[] ForbiddenSchemes = ["javascript", "file", "data", "mailto", "tel"];
|
private static readonly string[] ForbiddenSchemes = ["javascript", "file", "data", "mailto", "tel"];
|
||||||
private const string OobUri = "urn:ietf:wg:oauth:2.0:oob";
|
private const string OobUri = "urn:ietf:wg:oauth:2.0:oob";
|
||||||
|
@ -28,7 +25,11 @@ public static class OauthUtils
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string[] ExpandScopes(this string[] scopes) => scopes.Contains("chat_client")
|
public static string[] ExpandScopes(this Token token) => token.Scopes.Contains("chat_client")
|
||||||
? Scopes
|
? Scopes
|
||||||
: scopes;
|
: token.Scopes;
|
||||||
|
|
||||||
|
public static string[] ExpandScopes(this Application app) => app.Scopes.Contains("chat_client")
|
||||||
|
? Scopes
|
||||||
|
: app.Scopes;
|
||||||
}
|
}
|
|
@ -2,6 +2,11 @@ Host = localhost
|
||||||
Port = 7611
|
Port = 7611
|
||||||
Domain = id.fox.localhost
|
Domain = id.fox.localhost
|
||||||
|
|
||||||
|
; The level to log things at. Valid settings: Verbose, Debug, Information, Warning, Error, Fatal
|
||||||
|
LogEventLevel = Debug
|
||||||
|
; Optional logging to Seq
|
||||||
|
SeqLogUrl = http://localhost:5341
|
||||||
|
|
||||||
[Database]
|
[Database]
|
||||||
; The database URL in ADO.NET format.
|
; The database URL in ADO.NET format.
|
||||||
Url = "Host=localhost;Database=foxchat_cs_ident;Username=foxchat;Password=password"
|
Url = "Host=localhost;Database=foxchat_cs_ident;Username=foxchat;Password=password"
|
||||||
|
@ -10,11 +15,3 @@ Url = "Host=localhost;Database=foxchat_cs_ident;Username=foxchat;Password=passwo
|
||||||
Timeout = 5
|
Timeout = 5
|
||||||
; The maximum number of open connections. Defaults to 50.
|
; The maximum number of open connections. Defaults to 50.
|
||||||
MaxPoolSize = 500
|
MaxPoolSize = 500
|
||||||
|
|
||||||
[Logging]
|
|
||||||
; The level to log things at. Valid settings: Verbose, Debug, Information, Warning, Error, Fatal
|
|
||||||
LogEventLevel = Debug
|
|
||||||
; Whether to log SQL queries.
|
|
||||||
LogQueries = true
|
|
||||||
; Optional logging to Seq
|
|
||||||
SeqLogUrl = http://localhost:5341
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue