Ajout des syncshell perma & temporaire

This commit is contained in:
2025-09-20 09:02:53 +02:00
parent 74dac9f506
commit f9df60dc88
6 changed files with 166 additions and 12 deletions

View File

@@ -259,7 +259,11 @@ public partial class MareHub
var user = await DbContext.Users.SingleAsync(u => u.UID == groupHasMigrated.Item2).ConfigureAwait(false); var user = await DbContext.Users.SingleAsync(u => u.UID == groupHasMigrated.Item2).ConfigureAwait(false);
await Clients.Users(groupPairsWithoutSelf.Select(p => p.GroupUserUID)).Client_GroupSendInfo(new GroupInfoDto(group.ToGroupData(), await Clients.Users(groupPairsWithoutSelf.Select(p => p.GroupUserUID)).Client_GroupSendInfo(new GroupInfoDto(group.ToGroupData(),
user.ToUserData(), group.GetGroupPermissions())).ConfigureAwait(false); user.ToUserData(), group.GetGroupPermissions())
{
IsTemporary = group.IsTemporary,
ExpiresAt = group.ExpiresAt,
}).ConfigureAwait(false);
} }
else else
{ {

View File

@@ -144,7 +144,11 @@ public partial class MareHub
var groupPairs = await DbContext.GroupPairs.Where(p => p.GroupGID == dto.Group.GID).Select(p => p.GroupUserUID).AsNoTracking().ToListAsync().ConfigureAwait(false); var groupPairs = await DbContext.GroupPairs.Where(p => p.GroupGID == dto.Group.GID).Select(p => p.GroupUserUID).AsNoTracking().ToListAsync().ConfigureAwait(false);
await Clients.Users(groupPairs).Client_GroupSendInfo(new GroupInfoDto(group.ToGroupData(), newOwnerPair.GroupUser.ToUserData(), group.GetGroupPermissions())).ConfigureAwait(false); await Clients.Users(groupPairs).Client_GroupSendInfo(new GroupInfoDto(group.ToGroupData(), newOwnerPair.GroupUser.ToUserData(), group.GetGroupPermissions())
{
IsTemporary = group.IsTemporary,
ExpiresAt = group.ExpiresAt,
}).ConfigureAwait(false);
} }
[Authorize(Policy = "Identified")] [Authorize(Policy = "Identified")]
@@ -232,9 +236,8 @@ public partial class MareHub
sanitizedAlias = sanitizedAlias[..50]; sanitizedAlias = sanitizedAlias[..50];
} }
var normalizedAlias = sanitizedAlias.ToLowerInvariant();
var aliasExists = await DbContext.Groups var aliasExists = await DbContext.Groups
.AnyAsync(g => g.Alias != null && g.Alias.ToLower() == normalizedAlias) .AnyAsync(g => g.Alias != null && EF.Functions.ILike(g.Alias!, sanitizedAlias))
.ConfigureAwait(false); .ConfigureAwait(false);
if (aliasExists) if (aliasExists)
{ {
@@ -249,6 +252,8 @@ public partial class MareHub
InvitesEnabled = true, InvitesEnabled = true,
OwnerUID = UserUID, OwnerUID = UserUID,
Alias = sanitizedAlias, Alias = sanitizedAlias,
IsTemporary = false,
ExpiresAt = null,
}; };
GroupPair initialPair = new() GroupPair initialPair = new()
@@ -265,12 +270,98 @@ public partial class MareHub
var self = await DbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false); var self = await DbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false);
await Clients.User(UserUID).Client_GroupSendFullInfo(new GroupFullInfoDto(newGroup.ToGroupData(), self.ToUserData(), GroupPermissions.NoneSet, GroupUserPermissions.NoneSet, GroupUserInfo.None)) await Clients.User(UserUID).Client_GroupSendFullInfo(new GroupFullInfoDto(newGroup.ToGroupData(), self.ToUserData(), GroupPermissions.NoneSet, GroupUserPermissions.NoneSet, GroupUserInfo.None)
.ConfigureAwait(false); {
IsTemporary = newGroup.IsTemporary,
ExpiresAt = newGroup.ExpiresAt,
}).ConfigureAwait(false);
_logger.LogCallInfo(MareHubLogger.Args(gid)); _logger.LogCallInfo(MareHubLogger.Args(gid));
return new GroupPasswordDto(newGroup.ToGroupData(), passwd); return new GroupPasswordDto(newGroup.ToGroupData(), passwd)
{
IsTemporary = newGroup.IsTemporary,
ExpiresAt = newGroup.ExpiresAt,
};
}
[Authorize(Policy = "Identified")]
public async Task<GroupPasswordDto> GroupCreateTemporary(DateTime expiresAtUtc)
{
_logger.LogCallInfo();
var now = DateTime.UtcNow;
if (expiresAtUtc.Kind == DateTimeKind.Unspecified)
{
expiresAtUtc = DateTime.SpecifyKind(expiresAtUtc, DateTimeKind.Utc);
}
if (expiresAtUtc <= now)
{
throw new System.Exception("Expiration must be in the future.");
}
if (expiresAtUtc > now.AddDays(7))
{
throw new System.Exception("Temporary syncshells may not last longer than 7 days.");
}
var existingGroupsByUser = await DbContext.Groups.CountAsync(u => u.OwnerUID == UserUID).ConfigureAwait(false);
var existingJoinedGroups = await DbContext.GroupPairs.CountAsync(u => u.GroupUserUID == UserUID).ConfigureAwait(false);
if (existingGroupsByUser >= _maxExistingGroupsByUser || existingJoinedGroups >= _maxJoinedGroupsByUser)
{
throw new System.Exception($"Max groups for user is {_maxExistingGroupsByUser}, max joined groups is {_maxJoinedGroupsByUser}.");
}
var gid = StringUtils.GenerateRandomString(9);
while (await DbContext.Groups.AnyAsync(g => g.GID == "UMB-" + gid).ConfigureAwait(false))
{
gid = StringUtils.GenerateRandomString(9);
}
gid = "UMB-" + gid;
var passwd = StringUtils.GenerateRandomString(16);
var sha = SHA256.Create();
var hashedPw = StringUtils.Sha256String(passwd);
Group newGroup = new()
{
GID = gid,
HashedPassword = hashedPw,
InvitesEnabled = true,
OwnerUID = UserUID,
Alias = null,
IsTemporary = true,
ExpiresAt = expiresAtUtc,
};
GroupPair initialPair = new()
{
GroupGID = newGroup.GID,
GroupUserUID = UserUID,
IsPaused = false,
IsPinned = true,
};
await DbContext.Groups.AddAsync(newGroup).ConfigureAwait(false);
await DbContext.GroupPairs.AddAsync(initialPair).ConfigureAwait(false);
await DbContext.SaveChangesAsync().ConfigureAwait(false);
var self = await DbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false);
await Clients.User(UserUID).Client_GroupSendFullInfo(new GroupFullInfoDto(newGroup.ToGroupData(), self.ToUserData(), GroupPermissions.NoneSet, GroupUserPermissions.NoneSet, GroupUserInfo.None)
{
IsTemporary = newGroup.IsTemporary,
ExpiresAt = newGroup.ExpiresAt,
}).ConfigureAwait(false);
_logger.LogCallInfo(MareHubLogger.Args(gid, "Temporary", expiresAtUtc));
return new GroupPasswordDto(newGroup.ToGroupData(), passwd)
{
IsTemporary = newGroup.IsTemporary,
ExpiresAt = newGroup.ExpiresAt,
};
} }
[Authorize(Policy = "Identified")] [Authorize(Policy = "Identified")]
@@ -395,7 +486,11 @@ public partial class MareHub
_logger.LogCallInfo(MareHubLogger.Args(aliasOrGid, "Success")); _logger.LogCallInfo(MareHubLogger.Args(aliasOrGid, "Success"));
await Clients.User(UserUID).Client_GroupSendFullInfo(new GroupFullInfoDto(group.ToGroupData(), group.Owner.ToUserData(), group.GetGroupPermissions(), newPair.GetGroupPairPermissions(), newPair.GetGroupPairUserInfo())).ConfigureAwait(false); await Clients.User(UserUID).Client_GroupSendFullInfo(new GroupFullInfoDto(group.ToGroupData(), group.Owner.ToUserData(), group.GetGroupPermissions(), newPair.GetGroupPairPermissions(), newPair.GetGroupPairUserInfo())
{
IsTemporary = group.IsTemporary,
ExpiresAt = group.ExpiresAt,
}).ConfigureAwait(false);
var self = DbContext.Users.Single(u => u.UID == UserUID); var self = DbContext.Users.Single(u => u.UID == UserUID);
@@ -547,7 +642,11 @@ public partial class MareHub
var groups = await DbContext.GroupPairs.Include(g => g.Group).Include(g => g.Group.Owner).Where(g => g.GroupUserUID == UserUID).AsNoTracking().ToListAsync().ConfigureAwait(false); var groups = await DbContext.GroupPairs.Include(g => g.Group).Include(g => g.Group.Owner).Where(g => g.GroupUserUID == UserUID).AsNoTracking().ToListAsync().ConfigureAwait(false);
return groups.Select(g => new GroupFullInfoDto(g.Group.ToGroupData(), g.Group.Owner.ToUserData(), return groups.Select(g => new GroupFullInfoDto(g.Group.ToGroupData(), g.Group.Owner.ToUserData(),
g.Group.GetGroupPermissions(), g.GetGroupPairPermissions(), g.GetGroupPairUserInfo())).ToList(); g.Group.GetGroupPermissions(), g.GetGroupPairPermissions(), g.GetGroupPairUserInfo())
{
IsTemporary = g.Group.IsTemporary,
ExpiresAt = g.Group.ExpiresAt,
}).ToList();
} }
[Authorize(Policy = "Identified")] [Authorize(Policy = "Identified")]

View File

@@ -0,0 +1,40 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace MareSynchronosServer.Migrations
{
/// <inheritdoc />
public partial class GroupTemporary : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "expires_at",
table: "groups",
type: "timestamp with time zone",
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "is_temporary",
table: "groups",
type: "boolean",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "expires_at",
table: "groups");
migrationBuilder.DropColumn(
name: "is_temporary",
table: "groups");
}
}
}

View File

@@ -341,6 +341,10 @@ namespace MareSynchronosServer.Migrations
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("disable_vfx"); .HasColumnName("disable_vfx");
b.Property<DateTime?>("ExpiresAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("expires_at");
b.Property<bool>("IsPaused") b.Property<bool>("IsPaused")
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("is_paused"); .HasColumnName("is_paused");
@@ -454,6 +458,10 @@ namespace MareSynchronosServer.Migrations
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("hashed_password"); .HasColumnName("hashed_password");
b.Property<bool>("IsTemporary")
.HasColumnType("boolean")
.HasColumnName("is_temporary");
b.Property<bool>("InvitesEnabled") b.Property<bool>("InvitesEnabled")
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("invites_enabled"); .HasColumnName("invites_enabled");

View File

@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations; using System;
using System.ComponentModel.DataAnnotations;
namespace MareSynchronosShared.Models; namespace MareSynchronosShared.Models;
@@ -16,4 +17,6 @@ public class Group
public bool DisableSounds { get; set; } public bool DisableSounds { get; set; }
public bool DisableAnimations { get; set; } public bool DisableAnimations { get; set; }
public bool DisableVFX { get; set; } public bool DisableVFX { get; set; }
public bool IsTemporary { get; set; }
public DateTime? ExpiresAt { get; set; }
} }