Add per-family invite codes and admin roles
First member into a family becomes Admin; subsequent members default to Member. JWTs carry a family_role claim that is refreshed from the DB on each request so promotions and demotions take effect immediately. New /api/family endpoints let admins view the roster, regenerate the invite code, change roles, and remove members. Last-admin and self-removal guards prevent locking the family out of management. The /family page exposes the same actions in the UI; the bottom nav now links there. Members see the roster but not the invite code. Existing deployments get a one-time backfill at startup: any family with members but no admin gets its earliest-joined member promoted.
This commit is contained in:
@@ -29,11 +29,12 @@ public class JwtTokenServiceTests
|
||||
var service = BuildService();
|
||||
var user = new User { Id = 42, Name = "alice", PasswordHash = "x" };
|
||||
|
||||
var jwt = Decode(service.GenerateToken(user, familyId: 7));
|
||||
var jwt = Decode(service.GenerateToken(user, familyId: 7, role: FamilyRole.Admin));
|
||||
|
||||
await Assert.That(jwt.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value).IsEqualTo("42");
|
||||
await Assert.That(jwt.Claims.First(c => c.Type == ClaimTypes.Name).Value).IsEqualTo("alice");
|
||||
await Assert.That(jwt.Claims.First(c => c.Type == JwtTokenService.FamilyIdClaim).Value).IsEqualTo("7");
|
||||
await Assert.That(jwt.Claims.First(c => c.Type == JwtTokenService.FamilyRoleClaim).Value).IsEqualTo("Admin");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -42,7 +43,7 @@ public class JwtTokenServiceTests
|
||||
var service = BuildService();
|
||||
var user = new User { Id = 1, Name = "bob", PasswordHash = "x" };
|
||||
|
||||
var jwt = Decode(service.GenerateToken(user, familyId: 1));
|
||||
var jwt = Decode(service.GenerateToken(user, familyId: 1, role: FamilyRole.Member));
|
||||
|
||||
var expectedExpiry = DateTime.UtcNow.AddDays(30);
|
||||
var delta = (jwt.ValidTo - expectedExpiry).Duration();
|
||||
@@ -55,7 +56,7 @@ public class JwtTokenServiceTests
|
||||
var service = BuildService();
|
||||
var user = new User { Id = 7, Name = "carol", PasswordHash = "x" };
|
||||
|
||||
var token = service.GenerateToken(user, familyId: 1);
|
||||
var token = service.GenerateToken(user, familyId: 1, role: FamilyRole.Member);
|
||||
|
||||
var validator = new JwtSecurityTokenHandler();
|
||||
var parameters = new TokenValidationParameters
|
||||
@@ -78,7 +79,7 @@ public class JwtTokenServiceTests
|
||||
public async Task GenerateToken_with_different_secret_fails_validation()
|
||||
{
|
||||
var service = BuildService();
|
||||
var token = service.GenerateToken(new User { Id = 1, Name = "x", PasswordHash = "x" }, familyId: 1);
|
||||
var token = service.GenerateToken(new User { Id = 1, Name = "x", PasswordHash = "x" }, familyId: 1, role: FamilyRole.Member);
|
||||
|
||||
var validator = new JwtSecurityTokenHandler();
|
||||
var parameters = new TokenValidationParameters
|
||||
|
||||
Reference in New Issue
Block a user