mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-30 21:01:54 +08:00
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
DB migration compat / No migration changes (skipped) (push) Has been cancelled
228 lines
5.4 KiB
TypeScript
228 lines
5.4 KiB
TypeScript
import { StandardOAuthProviderType } from "@/generated/prisma/client";
|
|
import { KnownErrors } from "@hexclave/shared";
|
|
import { StatusError } from "@hexclave/shared/dist/utils/errors";
|
|
import { ProviderType, standardProviders } from "@hexclave/shared/dist/utils/oauth";
|
|
import { typedToUppercase } from "@hexclave/shared/dist/utils/strings";
|
|
import { listPermissions } from "./permissions";
|
|
import { Tenancy } from "./tenancies";
|
|
import { PrismaTransaction } from "./types";
|
|
|
|
|
|
async function _getTeamMembership(
|
|
tx: PrismaTransaction,
|
|
options: {
|
|
tenancyId: string,
|
|
teamId: string, userId: string,
|
|
}
|
|
) {
|
|
return await tx.teamMember.findUnique({
|
|
where: {
|
|
tenancyId_projectUserId_teamId: {
|
|
tenancyId: options.tenancyId,
|
|
projectUserId: options.userId,
|
|
teamId: options.teamId,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function ensureTeamMembershipExists(
|
|
tx: PrismaTransaction,
|
|
options: {
|
|
tenancyId: string,
|
|
teamId: string,
|
|
userId: string,
|
|
}
|
|
) {
|
|
await ensureUserExists(tx, { tenancyId: options.tenancyId, userId: options.userId });
|
|
|
|
const member = await _getTeamMembership(tx, options);
|
|
|
|
if (!member) {
|
|
throw new KnownErrors.TeamMembershipNotFound(options.teamId, options.userId);
|
|
}
|
|
}
|
|
|
|
export async function ensureTeamMembershipDoesNotExist(
|
|
tx: PrismaTransaction,
|
|
options: {
|
|
tenancyId: string,
|
|
teamId: string,
|
|
userId: string,
|
|
}
|
|
) {
|
|
const member = await _getTeamMembership(tx, options);
|
|
|
|
if (member) {
|
|
throw new KnownErrors.TeamMembershipAlreadyExists();
|
|
}
|
|
}
|
|
|
|
export async function ensureTeamExists(
|
|
tx: PrismaTransaction,
|
|
options: {
|
|
tenancyId: string,
|
|
teamId: string,
|
|
}
|
|
) {
|
|
const team = await tx.team.findUnique({
|
|
where: {
|
|
tenancyId_teamId: {
|
|
tenancyId: options.tenancyId,
|
|
teamId: options.teamId,
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!team) {
|
|
throw new KnownErrors.TeamNotFound(options.teamId);
|
|
}
|
|
}
|
|
|
|
export async function ensureUserTeamPermissionExists(
|
|
tx: PrismaTransaction,
|
|
options: {
|
|
tenancy: Tenancy,
|
|
teamId: string,
|
|
userId: string,
|
|
permissionId: string,
|
|
errorType: 'required' | 'not-exist',
|
|
recursive: boolean,
|
|
}
|
|
) {
|
|
await ensureTeamMembershipExists(tx, {
|
|
tenancyId: options.tenancy.id,
|
|
teamId: options.teamId,
|
|
userId: options.userId,
|
|
});
|
|
|
|
const result = await listPermissions(tx, {
|
|
scope: 'team',
|
|
tenancy: options.tenancy,
|
|
teamId: options.teamId,
|
|
userId: options.userId,
|
|
permissionId: options.permissionId,
|
|
recursive: options.recursive,
|
|
});
|
|
|
|
if (result.length === 0) {
|
|
if (options.errorType === 'not-exist') {
|
|
throw new KnownErrors.TeamPermissionNotFound(options.teamId, options.userId, options.permissionId);
|
|
} else {
|
|
throw new KnownErrors.TeamPermissionRequired(options.teamId, options.userId, options.permissionId);
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function ensureProjectPermissionExists(
|
|
tx: PrismaTransaction,
|
|
options: {
|
|
tenancy: Tenancy,
|
|
userId: string,
|
|
permissionId: string,
|
|
errorType: 'required' | 'not-exist',
|
|
recursive: boolean,
|
|
}
|
|
) {
|
|
await ensureUserExists(tx, {
|
|
tenancyId: options.tenancy.id,
|
|
userId: options.userId,
|
|
});
|
|
|
|
const result = await listPermissions(tx, {
|
|
scope: 'project',
|
|
tenancy: options.tenancy,
|
|
userId: options.userId,
|
|
permissionId: options.permissionId,
|
|
recursive: options.recursive,
|
|
});
|
|
|
|
if (result.length === 0) {
|
|
if (options.errorType === 'not-exist') {
|
|
throw new KnownErrors.PermissionNotFound(options.permissionId);
|
|
} else {
|
|
throw new KnownErrors.ProjectPermissionRequired(options.userId, options.permissionId);
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function ensureUserExists(
|
|
tx: PrismaTransaction,
|
|
options: {
|
|
tenancyId: string,
|
|
userId: string,
|
|
}
|
|
) {
|
|
const user = await tx.projectUser.findUnique({
|
|
where: {
|
|
tenancyId_projectUserId: {
|
|
tenancyId: options.tenancyId,
|
|
projectUserId: options.userId,
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!user) {
|
|
throw new KnownErrors.UserNotFound();
|
|
}
|
|
}
|
|
|
|
export function ensureStandardProvider(
|
|
providerId: ProviderType
|
|
): Lowercase<StandardOAuthProviderType> {
|
|
if (!standardProviders.includes(providerId as any)) {
|
|
throw new KnownErrors.InvalidStandardOAuthProviderId(providerId);
|
|
}
|
|
return providerId as any;
|
|
}
|
|
|
|
export async function ensureContactChannelDoesNotExists(
|
|
tx: PrismaTransaction,
|
|
options: {
|
|
tenancyId: string,
|
|
userId: string,
|
|
type: 'email',
|
|
value: string,
|
|
}
|
|
) {
|
|
const contactChannel = await tx.contactChannel.findUnique({
|
|
where: {
|
|
tenancyId_projectUserId_type_value: {
|
|
tenancyId: options.tenancyId,
|
|
projectUserId: options.userId,
|
|
type: typedToUppercase(options.type),
|
|
value: options.value,
|
|
},
|
|
},
|
|
});
|
|
|
|
if (contactChannel) {
|
|
throw new StatusError(StatusError.BadRequest, 'Contact channel already exists');
|
|
}
|
|
}
|
|
|
|
export async function ensureContactChannelExists(
|
|
tx: PrismaTransaction,
|
|
options: {
|
|
tenancyId: string,
|
|
userId: string,
|
|
contactChannelId: string,
|
|
}
|
|
) {
|
|
const contactChannel = await tx.contactChannel.findUnique({
|
|
where: {
|
|
tenancyId_projectUserId_id: {
|
|
tenancyId: options.tenancyId,
|
|
projectUserId: options.userId,
|
|
id: options.contactChannelId,
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!contactChannel) {
|
|
throw new StatusError(StatusError.BadRequest, 'Contact channel not found');
|
|
}
|
|
|
|
return contactChannel;
|
|
}
|