1
0
Fork 0
mirror of synced 2024-07-05 14:31:17 +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,45 +383,55 @@ export const inviteAccept = async (
) => { ) => {
const { inviteCode, password, firstName, lastName } = ctx.request.body const { inviteCode, password, firstName, lastName } = ctx.request.body
try { try {
// info is an extension of the user object that was stored by global await locks.doWithLock(
const { email, info }: any = await cache.invite.getCode(inviteCode) {
await cache.invite.deleteCode(inviteCode) type: LockType.AUTO_EXTEND,
const user = await tenancy.doInTenant(info.tenantId, async () => { name: LockName.PROCESS_USER_INVITE,
let request: any = { resource: inviteCode,
firstName, },
lastName, async () => {
password, // info is an extension of the user object that was stored by global
email, const { email, info } = await cache.invite.getCode(inviteCode)
admin: { global: info?.admin?.global || false }, const user = await tenancy.doInTenant(info.tenantId, async () => {
roles: info.apps, let request: any = {
tenantId: info.tenantId, firstName,
} lastName,
let builder: { global: boolean; apps?: string[] } = { password,
global: info?.builder?.global || false, email,
} admin: { global: info?.admin?.global || false },
roles: info.apps,
tenantId: info.tenantId,
}
const builder: { global: boolean; apps?: string[] } = {
global: info?.builder?.global || false,
}
if (info?.builder?.apps) { if (info?.builder?.apps) {
builder.apps = info.builder.apps builder.apps = info.builder.apps
request.builder = builder request.builder = builder
} }
delete info.apps delete info.apps
request = { request = {
...request, ...request,
...info, ...info,
} }
const saved = await userSdk.db.save(request) const saved = await userSdk.db.save(request)
const db = tenancy.getGlobalDB() const db = tenancy.getGlobalDB()
const user = await db.get<User>(saved._id) const user = await db.get<User>(saved._id)
await events.user.inviteAccepted(user) await events.user.inviteAccepted(user)
return saved return saved
}) })
ctx.body = { await cache.invite.deleteCode(inviteCode)
_id: user._id!,
_rev: user._rev!, ctx.body = {
email: user.email, _id: user._id!,
} _rev: user._rev!,
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