Merge branch 'main' into feat/expiry-links

This commit is contained in:
Lucas Smith
2025-10-06 16:39:34 +11:00
committed by GitHub
183 changed files with 10954 additions and 1513 deletions

View File

@ -0,0 +1,75 @@
/*
Warnings:
- A unique constraint covering the columns `[organisationAuthenticationPortalId]` on the table `Organisation` will be added. If there are existing duplicate values, this will fail.
- Added the required column `organisationAuthenticationPortalId` to the `Organisation` table without a default value. This is not possible if the table is not empty.
*/
-- AlterEnum
-- This migration adds more than one value to an enum.
-- With PostgreSQL versions 11 and earlier, this is not possible
-- in a single migration. This can be worked around by creating
-- multiple migrations, each migration adding only one value to
-- the enum.
ALTER TYPE "UserSecurityAuditLogType" ADD VALUE 'ACCOUNT_SSO_UNLINK';
ALTER TYPE "UserSecurityAuditLogType" ADD VALUE 'ORGANISATION_SSO_LINK';
ALTER TYPE "UserSecurityAuditLogType" ADD VALUE 'ORGANISATION_SSO_UNLINK';
-- AlterTable
ALTER TABLE "Account" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
-- [CUSTOM_CHANGE] This is supposed to be NOT NULL but we reapply it at the end.
ALTER TABLE "Organisation" ADD COLUMN "organisationAuthenticationPortalId" TEXT;
-- AlterTable
ALTER TABLE "VerificationToken" ADD COLUMN "metadata" JSONB;
-- CreateTable
CREATE TABLE "OrganisationAuthenticationPortal" (
"id" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT false,
"clientId" TEXT NOT NULL DEFAULT '',
"clientSecret" TEXT NOT NULL DEFAULT '',
"wellKnownUrl" TEXT NOT NULL DEFAULT '',
"defaultOrganisationRole" "OrganisationMemberRole" NOT NULL DEFAULT 'MEMBER',
"autoProvisionUsers" BOOLEAN NOT NULL DEFAULT true,
"allowedDomains" TEXT[] DEFAULT ARRAY[]::TEXT[],
"organisationId" TEXT, -- [CUSTOM_CHANGE] This is a temporary column for migration purposes.
CONSTRAINT "OrganisationAuthenticationPortal_pkey" PRIMARY KEY ("id")
);
-- [CUSTOM_CHANGE] Create default OrganisationAuthenticationPortal for all organisations
INSERT INTO "OrganisationAuthenticationPortal" ("id", "enabled", "clientId", "clientSecret", "wellKnownUrl", "defaultOrganisationRole", "autoProvisionUsers", "allowedDomains", "organisationId")
SELECT
generate_prefix_id('org_sso'),
false,
'',
'',
'',
'MEMBER',
true,
ARRAY[]::TEXT[],
o."id"
FROM "Organisation" o
WHERE o."organisationAuthenticationPortalId" IS NULL;
-- [CUSTOM_CHANGE] Update organisations with their corresponding organisationAuthenticationPortalId
UPDATE "Organisation" o
SET "organisationAuthenticationPortalId" = oap."id"
FROM "OrganisationAuthenticationPortal" oap
WHERE oap."organisationId" = o."id" AND o."organisationAuthenticationPortalId" IS NULL;
-- CreateIndex
CREATE UNIQUE INDEX "Organisation_organisationAuthenticationPortalId_key" ON "Organisation"("organisationAuthenticationPortalId");
-- AddForeignKey
ALTER TABLE "Organisation" ADD CONSTRAINT "Organisation_organisationAuthenticationPortalId_fkey" FOREIGN KEY ("organisationAuthenticationPortalId") REFERENCES "OrganisationAuthenticationPortal"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- [CUSTOM_CHANGE] Reapply NOT NULL constraint.
ALTER TABLE "Organisation" ALTER COLUMN "organisationAuthenticationPortalId" SET NOT NULL;
-- [CUSTOM_CHANGE] Drop temporary column.
ALTER TABLE "OrganisationAuthenticationPortal" DROP COLUMN "organisationId";

View File

@ -0,0 +1,5 @@
-- DropIndex
DROP INDEX "Recipient_documentId_email_key";
-- DropIndex
DROP INDEX "Recipient_templateId_email_key";

View File

@ -90,6 +90,9 @@ model TeamProfile {
enum UserSecurityAuditLogType {
ACCOUNT_PROFILE_UPDATE
ACCOUNT_SSO_LINK
ACCOUNT_SSO_UNLINK
ORGANISATION_SSO_LINK
ORGANISATION_SSO_UNLINK
AUTH_2FA_DISABLE
AUTH_2FA_ENABLE
PASSKEY_CREATED
@ -157,6 +160,7 @@ model VerificationToken {
completed Boolean @default(false)
expires DateTime
createdAt DateTime @default(now())
metadata Json?
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
@ -277,13 +281,15 @@ model OrganisationClaim {
}
model Account {
id String @id @default(cuid())
id String @id @default(cuid())
// When this record was created, unrelated to anything passed back by the provider.
createdAt DateTime @default(now())
userId Int
type String
provider String
providerAccountId String
refresh_token String? @db.Text
access_token String? @db.Text
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
// Some providers return created_at so we need to make it optional
created_at Int?
@ -291,7 +297,7 @@ model Account {
ext_expires_in Int?
token_type String?
scope String?
id_token String? @db.Text
id_token String? @db.Text
session_state String?
password String?
@ -525,8 +531,6 @@ model Recipient {
fields Field[]
signatures Signature[]
@@unique([documentId, email])
@@unique([templateId, email])
@@index([documentId])
@@index([templateId])
@@index([token])
@ -636,6 +640,9 @@ model Organisation {
organisationGlobalSettingsId String @unique
organisationGlobalSettings OrganisationGlobalSettings @relation(fields: [organisationGlobalSettingsId], references: [id])
organisationAuthenticationPortalId String @unique
organisationAuthenticationPortal OrganisationAuthenticationPortal @relation(fields: [organisationAuthenticationPortalId], references: [id])
}
model OrganisationMember {
@ -1034,3 +1041,18 @@ model OrganisationEmail {
organisationGlobalSettings OrganisationGlobalSettings[]
teamGlobalSettings TeamGlobalSettings[]
}
model OrganisationAuthenticationPortal {
id String @id
organisation Organisation?
enabled Boolean @default(false)
clientId String @default("")
clientSecret String @default("")
wellKnownUrl String @default("")
defaultOrganisationRole OrganisationMemberRole @default(MEMBER)
autoProvisionUsers Boolean @default(true)
allowedDomains String[] @default([])
}

View File

@ -1,10 +1,8 @@
import type { OrganisationMemberRole, OrganisationType } from '@prisma/client';
import { OrganisationMemberInviteStatus, type User } from '@prisma/client';
import { nanoid } from 'nanoid';
import { OrganisationGroupType, type User } from '@prisma/client';
import { hashSync } from '@documenso/lib/server-only/auth/hash';
import { acceptOrganisationInvitation } from '@documenso/lib/server-only/organisation/accept-organisation-invitation';
import { prefixedId } from '@documenso/lib/universal/id';
import { addUserToOrganisation } from '@documenso/lib/server-only/organisation/accept-organisation-invitation';
import { prisma } from '..';
import { seedTestEmail } from './users';
@ -27,6 +25,13 @@ export const seedOrganisationMembers = async ({
const createdMembers: User[] = [];
const organisationGroups = await prisma.organisationGroup.findMany({
where: {
organisationId,
type: OrganisationGroupType.INTERNAL_ORGANISATION,
},
});
for (const member of members) {
const email = member.email ?? seedTestEmail();
@ -53,33 +58,15 @@ export const seedOrganisationMembers = async ({
email: newUser.email,
organisationRole: member.organisationRole,
});
await addUserToOrganisation({
userId: newUser.id,
organisationId,
organisationGroups,
organisationMemberRole: member.organisationRole,
});
}
await prisma.organisationMemberInvite.createMany({
data: membersToInvite.map((invite) => ({
id: prefixedId('member_invite'),
email: invite.email,
organisationId,
organisationRole: invite.organisationRole,
token: nanoid(32),
})),
});
const invites = await prisma.organisationMemberInvite.findMany({
where: {
organisationId,
status: OrganisationMemberInviteStatus.PENDING,
},
});
await Promise.all(
invites.map(async (invite) => {
await acceptOrganisationInvitation({
token: invite.token,
});
}),
);
return createdMembers;
};