feature(auth): replace clerk.com with custom auth (#103)

* feature(auth): replace clerk.com with custom auth

* minor fixes

* remove notification preferences

* decrease live events interval

fix(api): cookies..

# Conflicts:
#	.gitignore
#	apps/api/src/index.ts
#	apps/dashboard/src/app/providers.tsx
#	packages/trpc/src/trpc.ts
This commit is contained in:
Carl-Gerhard Lindesvärd
2024-12-18 21:30:39 +01:00
committed by Carl-Gerhard Lindesvärd
parent f28802b1c2
commit d31d9924a5
151 changed files with 18484 additions and 12853 deletions

View File

@@ -1,12 +1,13 @@
import { clerkClient } from '@clerk/fastify';
import { pathOr } from 'ramda';
import { z } from 'zod';
import { db } from '@openpanel/db';
import { connectUserToOrganization, db } from '@openpanel/db';
import { zInviteUser } from '@openpanel/validation';
import { generateSecureId } from '@openpanel/common/server/id';
import { sendEmail } from '@openpanel/email';
import { addDays } from 'date-fns';
import { getOrganizationAccess } from '../access';
import { TRPCAccessError } from '../errors';
import { TRPCAccessError, TRPCBadRequestError } from '../errors';
import { createTRPCRouter, protectedProcedure } from '../trpc';
export const organizationRouter = createTRPCRouter({
@@ -59,68 +60,101 @@ export const organizationRouter = createTRPCRouter({
},
});
let invitationId: string | undefined;
const alreadyMember = await db.member.findFirst({
where: {
userId: userExists?.id,
organizationId: input.organizationId,
},
});
if (!userExists) {
const ticket = await clerkClient.invitations.createInvitation({
emailAddress: email,
notify: true,
});
invitationId = ticket.id;
if (alreadyMember && userExists) {
throw TRPCBadRequestError(
'User is already a member of the organization',
);
}
return db.member.create({
const alreadyInvited = await db.invite.findFirst({
where: {
email,
organizationId: input.organizationId,
},
});
if (alreadyInvited) {
throw TRPCBadRequestError(
'User is already invited to the organization',
);
}
const invite = await db.invite.create({
data: {
id: generateSecureId('invite'),
email,
organizationId: input.organizationId,
role: input.role,
invitedById: ctx.session.userId,
meta: {
access: input.access,
invitationId,
createdById: ctx.session.userId,
projectAccess: input.access || [],
expiresAt: addDays(new Date(), 3),
},
include: {
organization: {
select: {
name: true,
},
},
},
});
if (userExists) {
const member = await connectUserToOrganization({
user: userExists,
inviteId: invite.id,
});
return {
type: 'is_member',
member,
};
}
await sendEmail('invite', {
to: email,
data: {
url: `${process.env.NEXT_PUBLIC_DASHBOARD_URL}/onboarding?inviteId=${invite.id}`,
organizationName: invite.organization.name,
},
});
return {
type: 'is_invited',
invite,
};
}),
revokeInvite: protectedProcedure
.input(
z.object({
memberId: z.string(),
inviteId: z.string(),
}),
)
.mutation(async ({ input, ctx }) => {
const member = await db.member.findUniqueOrThrow({
const invite = await db.invite.findUniqueOrThrow({
where: {
id: input.memberId,
id: input.inviteId,
},
});
const access = await getOrganizationAccess({
userId: ctx.session.userId,
organizationId: member.organizationId,
organizationId: invite.organizationId,
});
if (access?.role !== 'org:admin') {
throw TRPCAccessError('You do not have access to this project');
}
const invitationId = pathOr<string | undefined>(
undefined,
['meta', 'invitationId'],
member,
);
if (invitationId) {
await clerkClient.invitations
.revokeInvitation(invitationId)
.catch(() => {
// Ignore errors, this will throw if the invitation is already accepted
});
}
return db.member.delete({
return db.invite.delete({
where: {
id: input.memberId,
id: input.inviteId,
},
});
}),