♻️ Refined custom apps

This commit is contained in:
LittleSheep 2025-06-29 20:32:08 +08:00
parent cdeed3c318
commit c4ea15097e
10 changed files with 4448 additions and 141 deletions

View File

@ -197,30 +197,6 @@ public class AppDatabase(
.HasIndex(p => p.SearchVector)
.HasMethod("GIN");
modelBuilder.Entity<CustomApp>()
.Property(c => c.RedirectUris)
.HasConversion(
v => string.Join(",", v),
v => v.Split(",", StringSplitOptions.RemoveEmptyEntries).ToArray());
modelBuilder.Entity<CustomApp>()
.Property(c => c.PostLogoutRedirectUris)
.HasConversion(
v => v != null ? string.Join(",", v) : "",
v => !string.IsNullOrEmpty(v) ? v.Split(",", StringSplitOptions.RemoveEmptyEntries) : Array.Empty<string>());
modelBuilder.Entity<CustomApp>()
.Property(c => c.AllowedScopes)
.HasConversion(
v => v != null ? string.Join(" ", v) : "",
v => !string.IsNullOrEmpty(v) ? v.Split(" ", StringSplitOptions.RemoveEmptyEntries) : new[] { "openid", "profile", "email" });
modelBuilder.Entity<CustomApp>()
.Property(c => c.AllowedGrantTypes)
.HasConversion(
v => v != null ? string.Join(" ", v) : "",
v => !string.IsNullOrEmpty(v) ? v.Split(" ", StringSplitOptions.RemoveEmptyEntries) : new[] { "authorization_code", "refresh_token" });
modelBuilder.Entity<CustomAppSecret>()
.HasIndex(s => s.Secret)
.IsUnique();

View File

@ -158,8 +158,8 @@ public class OidcProviderService(
);
// Add scopes as claims if provided
var effectiveScopes = scopes?.ToList() ?? client.AllowedScopes?.ToList() ?? new List<string>();
if (effectiveScopes.Any())
var effectiveScopes = scopes?.ToList() ?? client.OauthConfig!.AllowedScopes?.ToList() ?? [];
if (effectiveScopes.Count != 0)
{
tokenDescriptor.Subject.AddClaims(
effectiveScopes.Select(scope => new Claim("scope", scope)));
@ -221,7 +221,7 @@ public class OidcProviderService(
return string.Equals(secret, hashedSecret, StringComparison.Ordinal);
}
public async Task<string> GenerateAuthorizationCodeForExistingSessionAsync(
public async Task<string> GenerateAuthorizationCodeForReuseSessionAsync(
Session session,
Guid clientId,
string redirectUri,

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,182 @@
using DysonNetwork.Sphere.Account;
using DysonNetwork.Sphere.Developer;
using DysonNetwork.Sphere.Storage;
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime;
#nullable disable
namespace DysonNetwork.Sphere.Data.Migrations
{
/// <inheritdoc />
public partial class CustomAppsRefine : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "allow_offline_access",
table: "custom_apps");
migrationBuilder.DropColumn(
name: "allowed_grant_types",
table: "custom_apps");
migrationBuilder.DropColumn(
name: "allowed_scopes",
table: "custom_apps");
migrationBuilder.DropColumn(
name: "client_uri",
table: "custom_apps");
migrationBuilder.DropColumn(
name: "logo_uri",
table: "custom_apps");
migrationBuilder.DropColumn(
name: "post_logout_redirect_uris",
table: "custom_apps");
migrationBuilder.DropColumn(
name: "redirect_uris",
table: "custom_apps");
migrationBuilder.DropColumn(
name: "require_pkce",
table: "custom_apps");
migrationBuilder.DropColumn(
name: "verified_at",
table: "custom_apps");
migrationBuilder.RenameColumn(
name: "verified_as",
table: "custom_apps",
newName: "description");
migrationBuilder.AddColumn<CloudFileReferenceObject>(
name: "background",
table: "custom_apps",
type: "jsonb",
nullable: true);
migrationBuilder.AddColumn<CustomAppLinks>(
name: "links",
table: "custom_apps",
type: "jsonb",
nullable: true);
migrationBuilder.AddColumn<CustomAppOauthConfig>(
name: "oauth_config",
table: "custom_apps",
type: "jsonb",
nullable: true);
migrationBuilder.AddColumn<CloudFileReferenceObject>(
name: "picture",
table: "custom_apps",
type: "jsonb",
nullable: true);
migrationBuilder.AddColumn<VerificationMark>(
name: "verification",
table: "custom_apps",
type: "jsonb",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "background",
table: "custom_apps");
migrationBuilder.DropColumn(
name: "links",
table: "custom_apps");
migrationBuilder.DropColumn(
name: "oauth_config",
table: "custom_apps");
migrationBuilder.DropColumn(
name: "picture",
table: "custom_apps");
migrationBuilder.DropColumn(
name: "verification",
table: "custom_apps");
migrationBuilder.RenameColumn(
name: "description",
table: "custom_apps",
newName: "verified_as");
migrationBuilder.AddColumn<bool>(
name: "allow_offline_access",
table: "custom_apps",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "allowed_grant_types",
table: "custom_apps",
type: "character varying(256)",
maxLength: 256,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "allowed_scopes",
table: "custom_apps",
type: "character varying(256)",
maxLength: 256,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "client_uri",
table: "custom_apps",
type: "character varying(1024)",
maxLength: 1024,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "logo_uri",
table: "custom_apps",
type: "character varying(4096)",
maxLength: 4096,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "post_logout_redirect_uris",
table: "custom_apps",
type: "character varying(4096)",
maxLength: 4096,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "redirect_uris",
table: "custom_apps",
type: "character varying(4096)",
maxLength: 4096,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<bool>(
name: "require_pkce",
table: "custom_apps",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<Instant>(
name: "verified_at",
table: "custom_apps",
type: "timestamp with time zone",
nullable: true);
}
}
}

View File

@ -1,5 +1,8 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using DysonNetwork.Sphere.Account;
using DysonNetwork.Sphere.Storage;
using NodaTime;
namespace DysonNetwork.Sphere.Developer;
@ -12,17 +15,38 @@ public enum CustomAppStatus
Suspended
}
public class CustomApp : ModelBase
public class CustomApp : ModelBase, IIdentifiedResource
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(1024)] public string Slug { get; set; } = null!;
[MaxLength(1024)] public string Name { get; set; } = null!;
[MaxLength(4096)] public string? Description { get; set; }
public CustomAppStatus Status { get; set; } = CustomAppStatus.Developing;
public Instant? VerifiedAt { get; set; }
[MaxLength(4096)] public string? VerifiedAs { get; set; }
// OIDC/OAuth specific properties
[MaxLength(4096)] public string? LogoUri { get; set; }
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; }
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; }
[Column(TypeName = "jsonb")] public VerificationMark? Verification { get; set; }
[Column(TypeName = "jsonb")] public CustomAppOauthConfig? OauthConfig { get; set; }
[Column(TypeName = "jsonb")] public CustomAppLinks? Links { get; set; }
[JsonIgnore] public ICollection<CustomAppSecret> Secrets { get; set; } = new List<CustomAppSecret>();
public Guid PublisherId { get; set; }
public Publisher.Publisher Developer { get; set; } = null!;
[NotMapped] public string ResourceIdentifier => "custom-app/" + Id;
}
public class CustomAppLinks : ModelBase
{
[MaxLength(8192)] public string? HomePage { get; set; }
[MaxLength(8192)] public string? PrivacyPolicy { get; set; }
[MaxLength(8192)] public string? TermsOfService { get; set; }
}
public class CustomAppOauthConfig : ModelBase
{
[MaxLength(1024)] public string? ClientUri { get; set; }
[MaxLength(4096)] public string[] RedirectUris { get; set; } = [];
[MaxLength(4096)] public string[]? PostLogoutRedirectUris { get; set; }
@ -30,11 +54,6 @@ public class CustomApp : ModelBase
[MaxLength(256)] public string[] AllowedGrantTypes { get; set; } = ["authorization_code", "refresh_token"];
public bool RequirePkce { get; set; } = true;
public bool AllowOfflineAccess { get; set; } = false;
[JsonIgnore] public ICollection<CustomAppSecret> Secrets { get; set; } = new List<CustomAppSecret>();
public Guid PublisherId { get; set; }
public Publisher.Publisher Developer { get; set; } = null!;
}
public class CustomAppSecret : ModelBase

View File

@ -1,63 +1,129 @@
using System.ComponentModel.DataAnnotations;
using DysonNetwork.Sphere.Account;
using DysonNetwork.Sphere.Publisher;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace DysonNetwork.Sphere.Developer;
[ApiController]
[Route("/developers/apps")]
public class CustomAppController(CustomAppService customAppService, PublisherService ps) : ControllerBase
[Route("/developers/{pubName}/apps")]
public class CustomAppController(CustomAppService customApps, PublisherService ps) : ControllerBase
{
public record CreateAppRequest(Guid PublisherId, string Name, string Slug);
public record UpdateAppRequest(string Name, string Slug);
public record CustomAppRequest(
[MaxLength(1024)] string? Slug,
[MaxLength(1024)] string? Name,
[MaxLength(4096)] string? Description,
string? PictureId,
string? BackgroundId,
CustomAppStatus? Status,
CustomAppLinks? Links,
CustomAppOauthConfig? OauthConfig
);
[HttpGet]
public async Task<IActionResult> ListApps([FromQuery] Guid publisherId)
public async Task<IActionResult> ListApps([FromRoute] string pubName)
{
var apps = await customAppService.GetAppsByPublisherAsync(publisherId);
var publisher = await ps.GetPublisherByName(pubName);
if (publisher is null) return NotFound();
var apps = await customApps.GetAppsByPublisherAsync(publisher.Id);
return Ok(apps);
}
[HttpGet("{id:guid}")]
public async Task<IActionResult> GetApp(Guid id)
public async Task<IActionResult> GetApp([FromRoute] string pubName, Guid id)
{
var app = await customAppService.GetAppAsync(id);
var publisher = await ps.GetPublisherByName(pubName);
if (publisher is null) return NotFound();
var app = await customApps.GetAppAsync(id, publisherId: publisher.Id);
if (app == null)
{
return NotFound();
}
return Ok(app);
}
[HttpPost]
public async Task<IActionResult> CreateApp([FromBody] CreateAppRequest request)
[Authorize]
public async Task<IActionResult> CreateApp([FromRoute] string pubName, [FromBody] CustomAppRequest request)
{
var app = await customAppService.CreateAppAsync(request.PublisherId, request.Name, request.Slug);
if (app == null)
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
if (string.IsNullOrWhiteSpace(request.Name) || string.IsNullOrWhiteSpace(request.Slug))
return BadRequest("Name and slug are required");
var publisher = await ps.GetPublisherByName(pubName);
if (publisher is null) return NotFound();
if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Editor))
return StatusCode(403, "You must be an editor of the publisher to create a custom app");
if (!await ps.HasFeature(publisher.Id, PublisherFeatureFlag.Develop))
return StatusCode(403, "Publisher must be a developer to create a custom app");
try
{
return BadRequest("Invalid publisher ID or missing developer feature flag");
var app = await customApps.CreateAppAsync(publisher, request);
return Ok(app);
}
catch (InvalidOperationException ex)
{
return BadRequest(ex.Message);
}
return CreatedAtAction(nameof(GetApp), new { id = app.Id }, app);
}
[HttpPatch("{id:guid}")]
public async Task<IActionResult> UpdateApp(Guid id, [FromBody] UpdateAppRequest request)
[Authorize]
public async Task<IActionResult> UpdateApp(
[FromRoute] string pubName,
[FromRoute] Guid id,
[FromBody] CustomAppRequest request
)
{
var app = await customAppService.UpdateAppAsync(id, request.Name, request.Slug);
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
var publisher = await ps.GetPublisherByName(pubName);
if (publisher is null) return NotFound();
if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Editor))
return StatusCode(403, "You must be an editor of the publisher to update a custom app");
var app = await customApps.GetAppAsync(id, publisherId: publisher.Id);
if (app == null)
{
return NotFound();
try
{
app = await customApps.UpdateAppAsync(app, request);
return Ok(app);
}
catch (InvalidOperationException ex)
{
return BadRequest(ex.Message);
}
return Ok(app);
}
[HttpDelete("{id:guid}")]
public async Task<IActionResult> DeleteApp(Guid id)
[Authorize]
public async Task<IActionResult> DeleteApp(
[FromRoute] string pubName,
[FromRoute] Guid id
)
{
var result = await customAppService.DeleteAppAsync(id);
if (!result)
{
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
var publisher = await ps.GetPublisherByName(pubName);
if (publisher is null) return NotFound();
if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Editor))
return StatusCode(403, "You must be an editor of the publisher to delete a custom app");
var app = await customApps.GetAppAsync(id, publisherId: publisher.Id);
if (app == null)
return NotFound();
var result = await customApps.DeleteAppAsync(id);
if (!result)
return NotFound();
}
return NoContent();
}
}

View File

@ -1,39 +1,71 @@
using DysonNetwork.Sphere.Publisher;
using DysonNetwork.Sphere.Storage;
using Microsoft.EntityFrameworkCore;
namespace DysonNetwork.Sphere.Developer;
public class CustomAppService(AppDatabase db, PublisherService ps)
public class CustomAppService(AppDatabase db, FileReferenceService fileRefService)
{
public async Task<CustomApp?> CreateAppAsync(Guid publisherId, string name, string slug)
public async Task<CustomApp?> CreateAppAsync(
Publisher.Publisher pub,
CustomAppController.CustomAppRequest request
)
{
var publisher = await db.Publishers.FirstOrDefaultAsync(p => p.Id == publisherId);
if (publisher == null)
{
return null;
}
if (!await ps.HasFeature(publisherId, "developer"))
{
return null;
}
var app = new CustomApp
{
Name = name,
Slug = slug,
PublisherId = publisher.Id
Slug = request.Slug!,
Name = request.Name!,
Description = request.Description,
Status = request.Status ?? CustomAppStatus.Developing,
Links = request.Links,
OauthConfig = request.OauthConfig,
PublisherId = pub.Id
};
if (request.PictureId is not null)
{
var picture = await db.Files.Where(f => f.Id == request.PictureId).FirstOrDefaultAsync();
if (picture is null)
throw new InvalidOperationException("Invalid picture id, unable to find the file on cloud.");
app.Picture = picture.ToReferenceObject();
// Create a new reference
await fileRefService.CreateReferenceAsync(
picture.Id,
"custom-apps.picture",
app.ResourceIdentifier
);
}
if (request.BackgroundId is not null)
{
var background = await db.Files.Where(f => f.Id == request.BackgroundId).FirstOrDefaultAsync();
if (background is null)
throw new InvalidOperationException("Invalid picture id, unable to find the file on cloud.");
app.Background = background.ToReferenceObject();
// Create a new reference
await fileRefService.CreateReferenceAsync(
background.Id,
"custom-apps.background",
app.ResourceIdentifier
);
}
db.CustomApps.Add(app);
await db.SaveChangesAsync();
return app;
}
public async Task<CustomApp?> GetAppAsync(Guid id)
public async Task<CustomApp?> GetAppAsync(Guid id, Guid? publisherId = null)
{
return await db.CustomApps.FindAsync(id);
var query = db.CustomApps.Where(a => a.Id == id).AsQueryable();
if (publisherId.HasValue)
query = query.Where(a => a.PublisherId == publisherId.Value);
return await query.FirstOrDefaultAsync();
}
public async Task<List<CustomApp>> GetAppsByPublisherAsync(Guid publisherId)
@ -41,17 +73,60 @@ public class CustomAppService(AppDatabase db, PublisherService ps)
return await db.CustomApps.Where(a => a.PublisherId == publisherId).ToListAsync();
}
public async Task<CustomApp?> UpdateAppAsync(Guid id, string name, string slug)
public async Task<CustomApp?> UpdateAppAsync(CustomApp app, CustomAppController.CustomAppRequest request)
{
var app = await db.CustomApps.FindAsync(id);
if (app == null)
if (request.Slug is not null)
app.Slug = request.Slug;
if (request.Name is not null)
app.Name = request.Name;
if (request.Description is not null)
app.Description = request.Description;
if (request.Status is not null)
app.Status = request.Status.Value;
if (request.Links is not null)
app.Links = request.Links;
if (request.OauthConfig is not null)
app.OauthConfig = request.OauthConfig;
if (request.PictureId is not null)
{
return null;
var picture = await db.Files.Where(f => f.Id == request.PictureId).FirstOrDefaultAsync();
if (picture is null)
throw new InvalidOperationException("Invalid picture id, unable to find the file on cloud.");
if (app.Picture is not null)
await fileRefService.DeleteResourceReferencesAsync(app.ResourceIdentifier, "custom-apps.picture");
app.Picture = picture.ToReferenceObject();
// Create a new reference
await fileRefService.CreateReferenceAsync(
picture.Id,
"custom-apps.picture",
app.ResourceIdentifier
);
}
app.Name = name;
app.Slug = slug;
if (request.BackgroundId is not null)
{
var background = await db.Files.Where(f => f.Id == request.BackgroundId).FirstOrDefaultAsync();
if (background is null)
throw new InvalidOperationException("Invalid picture id, unable to find the file on cloud.");
if (app.Background is not null)
await fileRefService.DeleteResourceReferencesAsync(app.ResourceIdentifier, "custom-apps.background");
app.Background = background.ToReferenceObject();
// Create a new reference
await fileRefService.CreateReferenceAsync(
background.Id,
"custom-apps.background",
app.ResourceIdentifier
);
}
db.Update(app);
await db.SaveChangesAsync();
return app;
@ -68,6 +143,8 @@ public class CustomAppService(AppDatabase db, PublisherService ps)
db.CustomApps.Remove(app);
await db.SaveChangesAsync();
await fileRefService.DeleteResourceReferencesAsync(app.ResourceIdentifier);
return true;
}
}

View File

@ -6,6 +6,7 @@ using DysonNetwork.Sphere;
using DysonNetwork.Sphere.Account;
using DysonNetwork.Sphere.Chat;
using DysonNetwork.Sphere.Connection.WebReader;
using DysonNetwork.Sphere.Developer;
using DysonNetwork.Sphere.Storage;
using DysonNetwork.Sphere.Wallet;
using Microsoft.EntityFrameworkCore;
@ -1507,25 +1508,9 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<bool>("AllowOfflineAccess")
.HasColumnType("boolean")
.HasColumnName("allow_offline_access");
b.Property<string>("AllowedGrantTypes")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("allowed_grant_types");
b.Property<string>("AllowedScopes")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("allowed_scopes");
b.Property<string>("ClientUri")
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("client_uri");
b.Property<CloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
@ -1535,10 +1520,14 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<string>("LogoUri")
b.Property<string>("Description")
.HasMaxLength(4096)
.HasColumnType("character varying(4096)")
.HasColumnName("logo_uri");
.HasColumnName("description");
b.Property<CustomAppLinks>("Links")
.HasColumnType("jsonb")
.HasColumnName("links");
b.Property<string>("Name")
.IsRequired()
@ -1546,25 +1535,18 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("character varying(1024)")
.HasColumnName("name");
b.Property<string>("PostLogoutRedirectUris")
.HasMaxLength(4096)
.HasColumnType("character varying(4096)")
.HasColumnName("post_logout_redirect_uris");
b.Property<CustomAppOauthConfig>("OauthConfig")
.HasColumnType("jsonb")
.HasColumnName("oauth_config");
b.Property<CloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
b.Property<Guid>("PublisherId")
.HasColumnType("uuid")
.HasColumnName("publisher_id");
b.Property<string>("RedirectUris")
.IsRequired()
.HasMaxLength(4096)
.HasColumnType("character varying(4096)")
.HasColumnName("redirect_uris");
b.Property<bool>("RequirePkce")
.HasColumnType("boolean")
.HasColumnName("require_pkce");
b.Property<string>("Slug")
.IsRequired()
.HasMaxLength(1024)
@ -1579,14 +1561,9 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<string>("VerifiedAs")
.HasMaxLength(4096)
.HasColumnType("character varying(4096)")
.HasColumnName("verified_as");
b.Property<Instant?>("VerifiedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("verified_at");
b.Property<VerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");
b.HasKey("Id")
.HasName("pk_custom_apps");
@ -3618,7 +3595,7 @@ namespace DysonNetwork.Sphere.Migrations
modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b =>
{
b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher")
.WithMany()
.WithMany("Features")
.HasForeignKey("PublisherId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
@ -3980,6 +3957,8 @@ namespace DysonNetwork.Sphere.Migrations
{
b.Navigation("Collections");
b.Navigation("Features");
b.Navigation("Members");
b.Navigation("Posts");

View File

@ -8,7 +8,7 @@ using DysonNetwork.Sphere.Developer;
namespace DysonNetwork.Sphere.Pages.Auth;
public class AuthorizeModel(OidcProviderService oidcService) : PageModel
public class AuthorizeModel(OidcProviderService oidcService, IConfiguration configuration) : PageModel
{
[BindProperty(SupportsGet = true)] public string? ReturnUrl { get; set; }
@ -71,10 +71,17 @@ public class AuthorizeModel(OidcProviderService oidcService) : PageModel
return NotFound("Client not found");
}
var config = client.OauthConfig;
if (config is null)
{
ModelState.AddModelError("client_id", "Client was not available for use OAuth / OIDC");
return BadRequest("Client was not enabled for OAuth / OIDC");
}
// Validate redirect URI for non-Developing apps
if (client.Status != CustomAppStatus.Developing)
{
if (!string.IsNullOrEmpty(RedirectUri) && !(client.RedirectUris?.Contains(RedirectUri) ?? false))
if (!string.IsNullOrEmpty(RedirectUri) && !(config.RedirectUris?.Contains(RedirectUri) ?? false))
{
return BadRequest(new ErrorResponse
{
@ -93,9 +100,10 @@ public class AuthorizeModel(OidcProviderService oidcService) : PageModel
}
// Show authorization page
var baseUrl = configuration["BaseUrl"];
AppName = client.Name;
AppLogo = client.LogoUri;
AppUri = client.ClientUri;
AppLogo = client.Picture is not null ? $"{baseUrl}/files/{client.Picture.Id}" : null;
AppUri = config.ClientUri;
RequestedScopes = (Scope ?? "openid profile").Split(' ').Distinct().ToArray();
return Page();
@ -114,7 +122,7 @@ public class AuthorizeModel(OidcProviderService oidcService) : PageModel
if (existingSession != null)
{
// Reuse existing session
authCode = await oidcService.GenerateAuthorizationCodeForExistingSessionAsync(
authCode = await oidcService.GenerateAuthorizationCodeForReuseSessionAsync(
session: existingSession,
clientId: ClientId,
redirectUri: RedirectUri,
@ -126,7 +134,7 @@ public class AuthorizeModel(OidcProviderService oidcService) : PageModel
}
else
{
// Create new session (existing flow)
// Create a new session (existing flow)
authCode = await oidcService.GenerateAuthorizationCodeAsync(
clientId: ClientId,
userId: currentUser.Id,

View File

@ -8,6 +8,13 @@ namespace DysonNetwork.Sphere.Publisher;
public class PublisherService(AppDatabase db, FileReferenceService fileRefService, ICacheService cache)
{
public async Task<Publisher?> GetPublisherByName(string name)
{
return await db.Publishers
.Where(e => e.Name == name)
.FirstOrDefaultAsync();
}
private const string UserPublishersCacheKey = "accounts:{0}:publishers";
public async Task<List<Publisher>> GetUserPublishers(Guid userId)
@ -336,7 +343,7 @@ public class PublisherService(AppDatabase db, FileReferenceService fileRefServic
f.PublisherId == publisherId && f.Flag == flag &&
(f.ExpiredAt == null || f.ExpiredAt > now)
);
if (featureFlag is not null) isEnabled = true;
isEnabled = featureFlag is not null;
await cache.SetAsync(cacheKey, isEnabled!.Value, TimeSpan.FromMinutes(5));
return isEnabled.Value;