Forge
csharp4405de34
1using IntercomService.Data;
2using Microsoft.EntityFrameworkCore;
3
4namespace IntercomService.Services;
5
6public sealed class TeamInviteService(IntercomDbContext db)
7{
8 public async Task<(TeamInviteEntity Invite, string PlainToken)?> CreateInviteAsync(
9 string teamId,
10 string createdByMemberId,
11 string teamRole,
12 TimeSpan ttl,
13 int maxUses,
14 CancellationToken ct)
15 {
16 if (!TeamRoles.All.Contains(teamRole) || string.Equals(teamRole, TeamRoles.Agent, StringComparison.Ordinal))
17 return null;
18
19 var plain = Convert.ToBase64String(System.Security.Cryptography.RandomNumberGenerator.GetBytes(32))
20 .TrimEnd('=')
21 .Replace('+', '-')
22 .Replace('/', '_');
23
24 var invite = new TeamInviteEntity
25 {
26 InviteId = Guid.NewGuid().ToString("N"),
27 TeamId = teamId,
28 TokenHash = JwtTokenService.HashToken(plain),
29 TeamRole = teamRole,
30 MaxUses = Math.Max(1, maxUses),
31 UseCount = 0,
32 CreatedByMemberId = createdByMemberId,
33 ExpiresAtUtc = DateTimeOffset.UtcNow.Add(ttl),
34 CreatedAtUtc = DateTimeOffset.UtcNow,
35 };
36
37 db.TeamInvites.Add(invite);
38 await db.SaveChangesAsync(ct).ConfigureAwait(false);
39 return (invite, plain);
40 }
41
42 public async Task<(string TeamId, string TeamRole)?> TryConsumeInviteAsync(
43 string teamId,
44 string plainToken,
45 CancellationToken ct)
46 {
47 var hash = JwtTokenService.HashToken(plainToken);
48 var invite = await db.TeamInvites
49 .FirstOrDefaultAsync(
50 x => x.TeamId == teamId
51 && x.TokenHash == hash
52 && x.ExpiresAtUtc >= DateTimeOffset.UtcNow,
53 ct)
54 .ConfigureAwait(false);
55
56 if (invite is null || invite.UseCount >= invite.MaxUses)
57 return null;
58
59 invite.UseCount++;
60 await db.SaveChangesAsync(ct).ConfigureAwait(false);
61 return (invite.TeamId, invite.TeamRole);
62 }
63
64 public async Task<bool> IsValidInviteAsync(string teamId, string plainToken, CancellationToken ct)
65 {
66 var hash = JwtTokenService.HashToken(plainToken);
67 return await db.TeamInvites.AsNoTracking()
68 .AnyAsync(
69 x => x.TeamId == teamId
70 && x.TokenHash == hash
71 && x.ExpiresAtUtc >= DateTimeOffset.UtcNow
72 && x.UseCount < x.MaxUses,
73 ct)
74 .ConfigureAwait(false);
75 }
76}
77
View only · write via MCP/CIDE