1
0
Fork 0
mirror of synced 2024-10-03 10:36:59 +13:00

Merge pull request #9337 from Budibase/bug/#7220-removing-user-from-auth-does-not-remove-from-app

Bug - #7220 removing user from auth does not remove from app
This commit is contained in:
Adria Navarro 2023-01-16 10:49:27 +00:00 committed by GitHub
commit 70a01c48f1
8 changed files with 68 additions and 25 deletions

View file

@ -4,19 +4,21 @@ import { getGlobalUsers, getRawGlobalUser } from "../../utilities/global"
import { getFullUser } from "../../utilities/users" import { getFullUser } from "../../utilities/users"
import { import {
context, context,
constants,
roles as rolesCore, roles as rolesCore,
db as dbCore, db as dbCore,
} from "@budibase/backend-core" } from "@budibase/backend-core"
import { BBContext, User } from "@budibase/types" import { BBContext, Ctx, SyncUserRequest, User } from "@budibase/types"
import sdk from "../../sdk" import sdk from "../../sdk"
export async function syncUser(ctx: BBContext) { export async function syncUser(ctx: Ctx<SyncUserRequest>) {
let deleting = false, let deleting = false,
user: User | any user: User | any
const userId = ctx.params.id const userId = ctx.params.id
const previousUser = ctx.request.body?.previousUser
try { try {
user = await getRawGlobalUser(userId) user = (await getRawGlobalUser(userId)) as User
} catch (err: any) { } catch (err: any) {
if (err && err.status === 404) { if (err && err.status === 404) {
user = {} user = {}
@ -25,6 +27,11 @@ export async function syncUser(ctx: BBContext) {
throw err throw err
} }
} }
let previousApps = previousUser
? Object.keys(previousUser.roles).map(appId => appId)
: []
const roles = deleting ? {} : user.roles const roles = deleting ? {} : user.roles
// remove props which aren't useful to metadata // remove props which aren't useful to metadata
delete user.password delete user.password
@ -40,8 +47,9 @@ export async function syncUser(ctx: BBContext) {
.filter(entry => entry[1] !== rolesCore.BUILTIN_ROLE_IDS.PUBLIC) .filter(entry => entry[1] !== rolesCore.BUILTIN_ROLE_IDS.PUBLIC)
.map(([appId]) => appId) .map(([appId]) => appId)
} }
for (let prodAppId of prodAppIds) { for (let prodAppId of new Set([...prodAppIds, ...previousApps])) {
const roleId = roles[prodAppId] const roleId = roles[prodAppId]
const deleteFromApp = !roleId
const devAppId = dbCore.getDevelopmentAppID(prodAppId) const devAppId = dbCore.getDevelopmentAppID(prodAppId)
for (let appId of [prodAppId, devAppId]) { for (let appId of [prodAppId, devAppId]) {
if (!(await dbCore.dbExists(appId))) { if (!(await dbCore.dbExists(appId))) {
@ -54,24 +62,24 @@ export async function syncUser(ctx: BBContext) {
try { try {
metadata = await db.get(metadataId) metadata = await db.get(metadataId)
} catch (err) { } catch (err) {
if (deleting) { if (deleteFromApp) {
return return
} }
metadata = { metadata = {
tableId: InternalTables.USER_METADATA, tableId: InternalTables.USER_METADATA,
} }
} }
if (deleteFromApp) {
await db.remove(metadata)
return
}
// assign the roleId for the metadata doc // assign the roleId for the metadata doc
if (roleId) { if (roleId) {
metadata.roleId = roleId metadata.roleId = roleId
} }
let combined = !deleting let combined = sdk.users.combineMetadataAndUser(user, metadata)
? sdk.users.combineMetadataAndUser(user, metadata)
: {
...metadata,
status: constants.UserStatus.INACTIVE,
metadata: rolesCore.BUILTIN_ROLE_IDS.PUBLIC,
}
// if its null then there was no updates required // if its null then there was no updates required
if (combined) { if (combined) {
await db.put(combined) await db.put(combined)

View file

@ -171,9 +171,28 @@ describe("/users", () => {
.expect("Content-Type", /json/) .expect("Content-Type", /json/)
expect(res.body.message).toEqual('User synced.') expect(res.body.message).toEqual('User synced.')
}) })
it("should sync the user when a previous user is specified", async () => {
const app1 = await config.createApp('App 1')
const app2 = await config.createApp('App 2')
let user = await config.createUser(
undefined,
undefined,
undefined,
undefined,
false,
true,
{ [app1.appId]: 'ADMIN' })
let res = await request
.post(`/api/users/metadata/sync/${user._id}`)
.set(config.defaultHeaders())
.send({ previousUser: { ...user, roles: { ...user.roles, [app2.appId]: 'BASIC' } } })
.expect(200)
.expect("Content-Type", /json/)
expect(res.body.message).toEqual('User synced.')
})
}) })
}) })

View file

@ -25,6 +25,7 @@ export default async (ctx: BBContext, next: any) => {
if (!appCookie && !requestAppId) { if (!appCookie && !requestAppId) {
return next() return next()
} }
// check the app exists referenced in cookie // check the app exists referenced in cookie
if (appCookie) { if (appCookie) {
const appId = appCookie.appId const appId = appCookie.appId
@ -51,7 +52,7 @@ export default async (ctx: BBContext, next: any) => {
let appId: string | undefined, let appId: string | undefined,
roleId = roles.BUILTIN_ROLE_IDS.PUBLIC roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
if (!ctx.user) { if (!ctx.user?._id) {
// not logged in, try to set a cookie for public apps // not logged in, try to set a cookie for public apps
appId = requestAppId appId = requestAppId
} else if (requestAppId != null) { } else if (requestAppId != null) {
@ -96,7 +97,7 @@ export default async (ctx: BBContext, next: any) => {
// need to judge this only based on the request app ID, // need to judge this only based on the request app ID,
if ( if (
env.MULTI_TENANCY && env.MULTI_TENANCY &&
ctx.user && ctx.user?._id &&
requestAppId && requestAppId &&
!tenancy.isUserInAppTenant(requestAppId, ctx.user) !tenancy.isUserInAppTenant(requestAppId, ctx.user)
) { ) {

View file

@ -57,3 +57,7 @@ export interface CreateAdminUserRequest {
password: string password: string
tenantId: string tenantId: string
} }
export interface SyncUserRequest {
previousUser?: User
}

View file

@ -69,3 +69,7 @@ export interface AdminUser extends User {
global: boolean global: boolean
} }
} }
export function isUser(user: object): user is User {
return !!(user as User).roles
}

View file

@ -41,7 +41,7 @@ export interface UserCtx<RequestBody = any, ResponseBody = any>
} }
/** /**
* Deprecated: Use UserCtx / Ctx appropriately * @deprecated: Use UserCtx / Ctx appropriately
* Authenticated context. * Authenticated context.
*/ */
export interface BBContext extends Ctx { export interface BBContext extends Ctx {

View file

@ -31,6 +31,7 @@ import {
SearchUsersRequest, SearchUsersRequest,
User, User,
ThirdPartyUser, ThirdPartyUser,
isUser,
} from "@budibase/types" } from "@budibase/types"
import { sendEmail } from "../../utilities/email" import { sendEmail } from "../../utilities/email"
import { EmailTemplatePurpose } from "../../constants" import { EmailTemplatePurpose } from "../../constants"
@ -265,8 +266,9 @@ export const save = async (
await eventHelpers.handleSaveEvents(builtUser, dbUser) await eventHelpers.handleSaveEvents(builtUser, dbUser)
await addTenant(tenantId, _id, email) await addTenant(tenantId, _id, email)
await cache.user.invalidateUser(response.id) await cache.user.invalidateUser(response.id)
// let server know to sync user // let server know to sync user
await apps.syncUserInApps(_id) await apps.syncUserInApps(_id, dbUser)
await Promise.all(groupPromises) await Promise.all(groupPromises)
@ -572,7 +574,7 @@ export const destroy = async (id: string, currentUser: any) => {
await cache.user.invalidateUser(userId) await cache.user.invalidateUser(userId)
await sessions.invalidateSessions(userId, { reason: "deletion" }) await sessions.invalidateSessions(userId, { reason: "deletion" })
// let server know to sync user // let server know to sync user
await apps.syncUserInApps(userId) await apps.syncUserInApps(userId, dbUser)
} }
const bulkDeleteProcessing = async (dbUser: User) => { const bulkDeleteProcessing = async (dbUser: User) => {
@ -582,7 +584,7 @@ const bulkDeleteProcessing = async (dbUser: User) => {
await cache.user.invalidateUser(userId) await cache.user.invalidateUser(userId)
await sessions.invalidateSessions(userId, { reason: "bulk-deletion" }) await sessions.invalidateSessions(userId, { reason: "bulk-deletion" })
// let server know to sync user // let server know to sync user
await apps.syncUserInApps(userId) await apps.syncUserInApps(userId, dbUser)
} }
export const invite = async ( export const invite = async (

View file

@ -2,6 +2,7 @@ import fetch from "node-fetch"
import { constants, tenancy, logging } from "@budibase/backend-core" import { constants, tenancy, logging } from "@budibase/backend-core"
import { checkSlashesInUrl } from "../utilities" import { checkSlashesInUrl } from "../utilities"
import env from "../environment" import env from "../environment"
import { SyncUserRequest, User } from "@budibase/types"
async function makeAppRequest(url: string, method: string, body: any) { async function makeAppRequest(url: string, method: string, body: any) {
if (env.isTest()) { if (env.isTest()) {
@ -24,11 +25,15 @@ async function makeAppRequest(url: string, method: string, body: any) {
return fetch(checkSlashesInUrl(env.APPS_URL + url), request) return fetch(checkSlashesInUrl(env.APPS_URL + url), request)
} }
export async function syncUserInApps(userId: string) { export async function syncUserInApps(userId: string, previousUser?: User) {
const body: SyncUserRequest = {
previousUser,
}
const response = await makeAppRequest( const response = await makeAppRequest(
`/api/users/metadata/sync/${userId}`, `/api/users/metadata/sync/${userId}`,
"POST", "POST",
{} body
) )
if (response && response.status !== 200) { if (response && response.status !== 200) {
throw "Unable to sync user." throw "Unable to sync user."