@accounter/server
Version:
Accounter GraphQL server
123 lines • 5.59 kB
JavaScript
import { GraphQLError } from 'graphql';
import { ALLOWED_ROLES, invalidTokenError, mapAuth0Error } from '../helpers/invitations.helper.js';
import { AcceptInvitationsProvider } from '../providers/accept-invitations.provider.js';
import { AuthContextProvider } from '../providers/auth-context.provider.js';
import { Auth0ManagementProvider } from '../providers/auth0-management.provider.js';
import { InvitationsProvider } from '../providers/invitations.provider.js';
export const invitationsResolvers = {
Query: {
listInvitations: async (_, __, { injector }) => {
try {
return await injector.get(InvitationsProvider).listInvitations();
}
catch (error) {
if (error instanceof GraphQLError) {
throw error;
}
throw new GraphQLError('Failed to list invitations', {
originalError: error instanceof Error ? error : undefined,
extensions: { code: 'INVITATION_LIST_FAILED' },
});
}
},
},
Mutation: {
createInvitation: async (_, { email, roleId }, { injector }) => {
const authContext = await injector.get(AuthContextProvider).getAuthContext();
if (!authContext?.user || !authContext.tenant?.businessId) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' },
});
}
if (authContext.user.roleId !== 'business_owner') {
throw new GraphQLError('Requires role: business_owner', {
extensions: { code: 'FORBIDDEN' },
});
}
if (!ALLOWED_ROLES.has(roleId)) {
throw new GraphQLError(`Invalid roleId: ${roleId}`, {
extensions: { code: 'BAD_USER_INPUT' },
});
}
const normalizedEmail = email.trim().toLowerCase();
const businessId = authContext.tenant.businessId;
const invitedByUserId = authContext.user.userId;
const existingInvitation = await injector
.get(InvitationsProvider)
.getInvitationByEmailLoader.load(normalizedEmail);
if ((existingInvitation?.length ?? 0) > 0) {
throw new GraphQLError('An active invitation already exists for this user', {
extensions: { code: 'INVITATION_ALREADY_PENDING' },
});
}
let auth0UserId;
try {
auth0UserId = await injector
.get(Auth0ManagementProvider)
.createBlockedUser(normalizedEmail);
}
catch (error) {
throw mapAuth0Error(error);
}
try {
return await injector.get(InvitationsProvider).insertInvitation({
email: normalizedEmail,
roleId,
auth0UserId,
invitedByUserId,
ownerId: businessId,
});
}
catch (error) {
// If DB operations fail, attempt to clean up the created Auth0 user to prevent dangling resources.
await injector
.get(Auth0ManagementProvider)
.deleteUser(auth0UserId)
.catch(cleanupError => {
console.error(`Failed to cleanup Auth0 user ${auth0UserId} after DB error:`, cleanupError);
});
// Re-throw the original error to ensure the GraphQL operation fails correctly.
throw error;
}
},
acceptInvitation: async (_, { token }, { injector }) => {
const authContextProvider = injector.get(AuthContextProvider);
const authContext = await authContextProvider.getAuthContext();
const jwtIdentity = authContext ? null : await authContextProvider.getJwtIdentity();
const auth0UserId = authContext?.user?.auth0UserId ?? jwtIdentity?.auth0UserId ?? null;
const authenticatedUserEmail = authContext?.user?.email ?? jwtIdentity?.email ?? null;
const normalizedToken = token.trim();
if (!normalizedToken) {
throw invalidTokenError();
}
try {
return await injector
.get(AcceptInvitationsProvider)
.acceptInvitation(normalizedToken, auth0UserId, authenticatedUserEmail);
}
catch (error) {
if (error instanceof GraphQLError) {
throw error;
}
throw new GraphQLError('Failed to accept invitation', {
extensions: { code: 'INVITATION_ACCEPT_FAILED' },
});
}
},
revokeInvitation: async (_, { id }, { injector }) => {
try {
return await injector.get(InvitationsProvider).revokeInvitation(id);
}
catch (error) {
if (error instanceof GraphQLError) {
throw error;
}
throw new GraphQLError('Failed to revoke invitation', {
originalError: error instanceof Error ? error : undefined,
extensions: { code: 'INVITATION_REVOCATION_FAILED' },
});
}
},
},
};
//# sourceMappingURL=invitations.resolver.js.map