1
0
Fork 0
mirror of synced 2024-07-03 13:30:46 +12:00

Use lock to prevent race conditions on invite

This commit is contained in:
Adria Navarro 2024-01-16 11:20:27 +01:00
parent 0ed8464aab
commit 0cce142571
2 changed files with 50 additions and 36 deletions

View file

@ -22,6 +22,7 @@ export enum LockName {
QUOTA_USAGE_EVENT = "quota_usage_event", QUOTA_USAGE_EVENT = "quota_usage_event",
APP_MIGRATION = "app_migrations", APP_MIGRATION = "app_migrations",
PROCESS_AUTO_COLUMNS = "process_auto_columns", PROCESS_AUTO_COLUMNS = "process_auto_columns",
PROCESS_USER_INVITE = "process_user_invite",
} }
export type LockOptions = { export type LockOptions = {

View file

@ -12,6 +12,8 @@ import {
InviteUserRequest, InviteUserRequest,
InviteUsersRequest, InviteUsersRequest,
InviteUsersResponse, InviteUsersResponse,
LockName,
LockType,
MigrationType, MigrationType,
SaveUserResponse, SaveUserResponse,
SearchUsersRequest, SearchUsersRequest,
@ -27,6 +29,7 @@ import {
platform, platform,
tenancy, tenancy,
db, db,
locks,
} from "@budibase/backend-core" } from "@budibase/backend-core"
import { checkAnyUserExists } from "../../../utilities/users" import { checkAnyUserExists } from "../../../utilities/users"
import { isEmailConfigured } from "../../../utilities/email" import { isEmailConfigured } from "../../../utilities/email"
@ -380,9 +383,15 @@ export const inviteAccept = async (
) => { ) => {
const { inviteCode, password, firstName, lastName } = ctx.request.body const { inviteCode, password, firstName, lastName } = ctx.request.body
try { try {
await locks.doWithLock(
{
type: LockType.AUTO_EXTEND,
name: LockName.PROCESS_USER_INVITE,
resource: inviteCode,
},
async () => {
// info is an extension of the user object that was stored by global // info is an extension of the user object that was stored by global
const { email, info }: any = await cache.invite.getCode(inviteCode) const { email, info } = await cache.invite.getCode(inviteCode)
await cache.invite.deleteCode(inviteCode)
const user = await tenancy.doInTenant(info.tenantId, async () => { const user = await tenancy.doInTenant(info.tenantId, async () => {
let request: any = { let request: any = {
firstName, firstName,
@ -393,7 +402,7 @@ export const inviteAccept = async (
roles: info.apps, roles: info.apps,
tenantId: info.tenantId, tenantId: info.tenantId,
} }
let builder: { global: boolean; apps?: string[] } = { const builder: { global: boolean; apps?: string[] } = {
global: info?.builder?.global || false, global: info?.builder?.global || false,
} }
@ -414,11 +423,15 @@ export const inviteAccept = async (
return saved return saved
}) })
await cache.invite.deleteCode(inviteCode)
ctx.body = { ctx.body = {
_id: user._id!, _id: user._id!,
_rev: user._rev!, _rev: user._rev!,
email: user.email, email: user.email,
} }
}
)
} catch (err: any) { } catch (err: any) {
if (err.code === ErrorCode.USAGE_LIMIT_EXCEEDED) { if (err.code === ErrorCode.USAGE_LIMIT_EXCEEDED) {
// explicitly re-throw limit exceeded errors // explicitly re-throw limit exceeded errors