bugfix: invitation link copy option disapearing fixed. route problem for unauthorizrd users opening invitation link fixed.

This commit is contained in:
2026-05-17 00:02:13 +03:30
parent 627219df58
commit 2d43254af2
14 changed files with 561 additions and 205 deletions

View File

@@ -288,8 +288,11 @@ export class OrganizationService {
if (!invitation) {
throw new NotFoundException('Invitation not found');
}
if (invitation.acceptedAt || invitation.revokedAt) {
throw new BadRequestException('Only pending invitations can provide a link');
if (invitation.acceptedAt) {
throw new BadRequestException('This invitation has already been accepted');
}
if (invitation.revokedAt) {
throw new BadRequestException('This invitation is no longer valid');
}
const plainToken = this.generateInviteToken();

View File

@@ -55,6 +55,19 @@ export class StaffController {
return this.staffService.invite(req.user.id, organizationId, dto);
}
@Post('members/:membershipId/invitation-link')
@UseGuards(JwtAuthGuard)
@ApiOperation({
summary: 'Regenerate and return invite link for a pending staff member',
})
getInvitationLink(
@Req() req: { user: { id: string; organizationId?: string } },
@Param('membershipId') membershipId: string,
) {
const organizationId = this.staffService.getOrganizationIdFromUser(req.user);
return this.staffService.getInvitationLink(req.user.id, organizationId, membershipId);
}
@Patch('members/:membershipId')
@UseGuards(JwtAuthGuard)
@ApiOperation({ summary: 'Update staff member name and/or permissions' })

View File

@@ -224,6 +224,62 @@ export class StaffService {
};
}
async getInvitationLink(userId: string, organizationId: string, membershipId: string) {
const actor = await this.getActorMembership(userId, organizationId);
if (!actor || !this.canEditStaff(actor)) {
throw new ForbiddenException('You cannot invite or manage staff');
}
const membership = await this.prisma.membership.findFirst({
where: { id: membershipId, organizationId },
include: {
user: { select: { email: true } },
invitations: { orderBy: { createdAt: 'desc' }, take: 1 },
},
});
if (!membership) {
throw new NotFoundException('Member not found');
}
if (membership.isOwner) {
throw new BadRequestException('Owner does not use an invitation link');
}
if (membership.isActive) {
throw new BadRequestException('This member has already accepted their invitation');
}
const invitation = membership.invitations[0];
if (!invitation) {
throw new BadRequestException('No invitation found for this member');
}
if (invitation.acceptedAt) {
throw new BadRequestException('This invitation has already been accepted');
}
if (invitation.revokedAt) {
throw new BadRequestException('This invitation is no longer valid');
}
const plainToken = this.generateInviteToken();
const tokenHash = this.hashInviteToken(plainToken);
await this.prisma.staffInvitation.update({
where: { id: invitation.id },
data: {
tokenHash,
expiresAt: this.getInviteExpiryDate(),
},
});
return {
success: true,
data: {
membershipId: membership.id,
invitationId: invitation.id,
email: membership.user.email,
invitationUrl: this.buildInviteUrl(plainToken),
},
};
}
async previewInvite(token: string) {
const invitation = await this.findValidInvitation(token);
const org = invitation.membership.organization;
@@ -383,7 +439,8 @@ export class StaffService {
if (m.isOwner || m.isActive) return 'ACTIVE';
const invitation = m.invitations[0];
if (!invitation) return 'EXPIRED';
if (invitation.acceptedAt || invitation.revokedAt) return 'ACTIVE';
if (invitation.acceptedAt) return 'ACTIVE';
if (invitation.revokedAt) return 'EXPIRED';
return invitation.expiresAt.getTime() > Date.now() ? 'PENDING' : 'EXPIRED';
}