Return error when adding auth contact channel in use by another account
Some checks failed
Runs E2E API Tests / build (20.x) (push) Has been cancelled
Runs E2E API Tests / build (22.x) (push) Has been cancelled
Lint & build / lint_and_build (20.x) (push) Has been cancelled
Lint & build / lint_and_build (22.x) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled

This commit is contained in:
Konstantin Wohlwend 2024-11-21 23:18:44 +01:00
parent ac9a85317c
commit 01eddd99a1
3 changed files with 43 additions and 4 deletions

View File

@ -129,12 +129,29 @@ export const contactChannelsCrudHandlers = createLazyProxy(() => createCrudHandl
}
const updatedContactChannel = await prismaClient.$transaction(async (tx) => {
await ensureContactChannelExists(tx, {
const existingContactChannel = await ensureContactChannelExists(tx, {
projectId: auth.project.id,
userId: params.user_id,
contactChannelId: params.contact_channel_id || throwErr("Missing contact channel id"),
});
// if usedForAuth is set to true, make sure no other account uses this channel for auth
if (data.used_for_auth) {
const existingWithSameChannel = await tx.contactChannel.findUnique({
where: {
projectId_type_value_usedForAuth: {
projectId: auth.project.id,
type: data.type !== undefined ? crudContactChannelTypeToPrisma(data.type) : existingContactChannel.type,
value: data.value !== undefined ? data.value : existingContactChannel.value,
usedForAuth: 'TRUE',
},
},
});
if (existingWithSameChannel && existingWithSameChannel.id !== existingContactChannel.id) {
throw new KnownErrors.ContactChannelAlreadyUsedForAuthBySomeoneElse(data.type ?? prismaContactChannelTypeToCrud(existingContactChannel.type));
}
}
if (data.is_primary) {
// mark all other channels as not primary
await tx.contactChannel.updateMany({
@ -215,3 +232,12 @@ export const contactChannelsCrudHandlers = createLazyProxy(() => createCrudHandl
};
}
}));
function crudContactChannelTypeToPrisma(type: "email") {
return typedToUppercase(type);
}
function prismaContactChannelTypeToCrud(type: "EMAIL") {
return typedToLowercase(type);
}

View File

@ -1,11 +1,11 @@
import { ProxiedOAuthProviderType, StandardOAuthProviderType } from "@prisma/client";
import { KnownErrors } from "@stackframe/stack-shared";
import { ProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects";
import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";
import { ProviderType, sharedProviders, standardProviders } from "@stackframe/stack-shared/dist/utils/oauth";
import { typedToUppercase } from "@stackframe/stack-shared/dist/utils/strings";
import { listUserTeamPermissions } from "./permissions";
import { PrismaTransaction } from "./types";
import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";
import { typedToUppercase } from "@stackframe/stack-shared/dist/utils/strings";
async function _getTeamMembership(
@ -198,4 +198,6 @@ export async function ensureContactChannelExists(
if (!contactChannel) {
throw new StatusError(StatusError.BadRequest, 'Contact channel not found');
}
return contactChannel;
}

View File

@ -1,7 +1,6 @@
import { StackAssertionError, StatusError, throwErr } from "./utils/errors";
import { identityArgs } from "./utils/functions";
import { Json } from "./utils/json";
import { filterUndefined } from "./utils/objects";
import { deindent } from "./utils/strings";
export type KnownErrorJson = {
@ -1187,6 +1186,17 @@ const OAuthProviderAccessDenied = createKnownErrorConstructor(
() => [] as const,
);
const ContactChannelAlreadyUsedForAuthBySomeoneElse = createKnownErrorConstructor(
KnownError,
"CONTACT_CHANNEL_ALREADY_USED_FOR_AUTH_BY_SOMEONE_ELSE",
(type: "email") => [
400,
`This ${type} is already used for authentication by another account.`,
{ type },
] as const,
(json) => [json.type] as const,
);
export type KnownErrors = {
[K in keyof typeof KnownErrors]: InstanceType<typeof KnownErrors[K]>;
};
@ -1283,6 +1293,7 @@ export const KnownErrors = {
InvalidAuthorizationCode,
TeamPermissionNotFound,
OAuthProviderAccessDenied,
ContactChannelAlreadyUsedForAuthBySomeoneElse,
} satisfies Record<string, KnownErrorConstructor<any, any>>;