UNPKG

@accounter/server

Version:
128 lines 5.3 kB
import { __decorate, __metadata } from "tslib"; import { createHash, randomBytes, randomUUID } from 'node:crypto'; import DataLoader from 'dataloader'; import { GraphQLError } from 'graphql'; import { Injectable, Scope } from 'graphql-modules'; import { sql } from '@pgtyped/runtime'; import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js'; import { TenantAwareDBClient } from '../../app-providers/tenant-db-client.js'; import { AuditLogsProvider } from '../../common/providers/audit-logs.provider.js'; import { BusinessUsersProvider } from './business-users.provider.js'; const INVITATION_EXPIRATION_PERIOD_MS = 7 * 24 * 60 * 60 * 1000; // 7 days const getActiveInvitationsByEmails = sql ` SELECT id, email FROM accounter_schema.invitations WHERE business_id = $ownerId AND lower(email) = ANY(SELECT lower(u) FROM unnest($emails::varchar[]) AS u) AND accepted_at IS NULL AND expires_at > NOW();`; const getInvitationsByTokens = sql ` SELECT id, business_id, role_id, auth0_user_id, accepted_at, expires_at, token_hash FROM accounter_schema.invitations WHERE token_hash IN $$tokenHashes;`; const insertInvitation = sql ` INSERT INTO accounter_schema.invitations ( user_id, business_id, email, role_id, token_hash, auth0_user_created, auth0_user_id, invited_by_user_id, invited_by_business_id, expires_at ) VALUES ($userId, $ownerId, $email, $roleId, $tokenHash, $auth0UserCreated, $auth0UserId, $invitedByUserId, $invitedByBusinessId, $expiresAt) RETURNING id, email, business_id, role_id, expires_at; `; let InvitationsProvider = class InvitationsProvider { db; adminContextProvider; businessUsersProvider; auditLogsProvider; constructor(db, adminContextProvider, businessUsersProvider, auditLogsProvider) { this.db = db; this.adminContextProvider = adminContextProvider; this.businessUsersProvider = businessUsersProvider; this.auditLogsProvider = auditLogsProvider; } async batchInvitationsByEmails(emails) { const { ownerId } = await this.adminContextProvider.getVerifiedAdminContext(); const invitations = await getActiveInvitationsByEmails.run({ emails: [...emails], ownerId, }, this.db); return emails.map(email => invitations.filter(t => t.email.toLowerCase() === email.toLowerCase())); } getInvitationByEmailLoader = new DataLoader((emails) => this.batchInvitationsByEmails(emails), { cacheKeyFn: email => email.toLowerCase(), }); async batchInvitationsByTokens(tokenHashes) { const invitations = await getInvitationsByTokens.run({ tokenHashes: [...tokenHashes], }, this.db); return tokenHashes.map(tokenHash => invitations.filter(t => t.token_hash === tokenHash)); } getInvitationByTokensLoader = new DataLoader((tokenHashes) => this.batchInvitationsByTokens(tokenHashes)); async insertInvitation({ email, roleId, auth0UserId, invitedByUserId, ownerId, }) { return this.db.transaction(async (client) => { const token = randomBytes(32).toString('hex'); const tokenHash = createHash('sha256').update(token).digest('hex'); const localUserId = randomUUID(); const invitationParams = { userId: localUserId, email, roleId, tokenHash, auth0UserCreated: true, auth0UserId, invitedByUserId, invitedByBusinessId: ownerId, ownerId, expiresAt: new Date(Date.now() + INVITATION_EXPIRATION_PERIOD_MS), }; await this.businessUsersProvider.insertBusinessUser({ userId: localUserId, auth0UserId: null, roleId, ownerId, }); const insertedInvitation = await insertInvitation.run(invitationParams, client); const [invitation] = insertedInvitation; if (!invitation) { throw new GraphQLError('Invitation creation failed: no record returned from DB.', { extensions: { code: 'INTERNAL_SERVER_ERROR' }, }); } await this.auditLogsProvider.insertAuditLog({ ownerId: invitation.business_id, userId: invitedByUserId, auth0UserId, action: 'INVITATION_CREATED', entity: 'Invitation', entityId: invitation.id, details: { email, roleId, auth0UserId }, }); return { id: invitation.id, email: invitation.email, roleId: invitation.role_id, expiresAt: invitation.expires_at, token, }; }); } }; InvitationsProvider = __decorate([ Injectable({ scope: Scope.Operation, global: true, }), __metadata("design:paramtypes", [TenantAwareDBClient, AdminContextProvider, BusinessUsersProvider, AuditLogsProvider]) ], InvitationsProvider); export { InvitationsProvider }; //# sourceMappingURL=invitations.provider.js.map