From a2667c6d72fdd6b2a6774d6cafc77926ea394a68 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 4 Aug 2023 15:43:02 +0100 Subject: [PATCH 1/7] Removing the ability to set roles, builder and admin structure through basic public API. --- .../api/controllers/public/applications.ts | 18 ++++---- .../src/api/controllers/public/queries.ts | 6 ++- .../server/src/api/controllers/public/rows.ts | 13 +++--- .../src/api/controllers/public/tables.ts | 13 +++--- .../src/api/controllers/public/users.ts | 44 ++++++++++++------- packages/server/src/utilities/users.ts | 17 ++----- 6 files changed, 60 insertions(+), 51 deletions(-) diff --git a/packages/server/src/api/controllers/public/applications.ts b/packages/server/src/api/controllers/public/applications.ts index c4041bedcf..fd72db95d3 100644 --- a/packages/server/src/api/controllers/public/applications.ts +++ b/packages/server/src/api/controllers/public/applications.ts @@ -3,6 +3,8 @@ import { search as stringSearch, addRev } from "./utils" import * as controller from "../application" import * as deployController from "../deploy" import { Application } from "../../../definitions/common" +import { UserCtx } from "@budibase/types" +import { Next } from "koa" function fixAppID(app: Application, params: any) { if (!params) { @@ -14,7 +16,7 @@ function fixAppID(app: Application, params: any) { return app } -async function setResponseApp(ctx: any) { +async function setResponseApp(ctx: UserCtx) { const appId = ctx.body?.appId if (appId && (!ctx.params || !ctx.params.appId)) { ctx.params = { appId } @@ -28,14 +30,14 @@ async function setResponseApp(ctx: any) { } } -export async function search(ctx: any, next: any) { +export async function search(ctx: UserCtx, next: Next) { const { name } = ctx.request.body const apps = await dbCore.getAllApps({ all: true }) ctx.body = stringSearch(apps, name) await next() } -export async function create(ctx: any, next: any) { +export async function create(ctx: UserCtx, next: Next) { if (!ctx.request.body || !ctx.request.body.useTemplate) { ctx.request.body = { useTemplate: false, @@ -47,14 +49,14 @@ export async function create(ctx: any, next: any) { await next() } -export async function read(ctx: any, next: any) { +export async function read(ctx: UserCtx, next: Next) { await context.doInAppContext(ctx.params.appId, async () => { await setResponseApp(ctx) await next() }) } -export async function update(ctx: any, next: any) { +export async function update(ctx: UserCtx, next: Next) { ctx.request.body = await addRev(fixAppID(ctx.request.body, ctx.params)) await context.doInAppContext(ctx.params.appId, async () => { await controller.update(ctx) @@ -63,7 +65,7 @@ export async function update(ctx: any, next: any) { }) } -export async function destroy(ctx: any, next: any) { +export async function destroy(ctx: UserCtx, next: Next) { await context.doInAppContext(ctx.params.appId, async () => { // get the app before deleting it await setResponseApp(ctx) @@ -75,14 +77,14 @@ export async function destroy(ctx: any, next: any) { }) } -export async function unpublish(ctx: any, next: any) { +export async function unpublish(ctx: UserCtx, next: Next) { await context.doInAppContext(ctx.params.appId, async () => { await controller.unpublish(ctx) await next() }) } -export async function publish(ctx: any, next: any) { +export async function publish(ctx: UserCtx, next: Next) { await context.doInAppContext(ctx.params.appId, async () => { await deployController.publishApp(ctx) await next() diff --git a/packages/server/src/api/controllers/public/queries.ts b/packages/server/src/api/controllers/public/queries.ts index 57ec608379..3cb1ab3812 100644 --- a/packages/server/src/api/controllers/public/queries.ts +++ b/packages/server/src/api/controllers/public/queries.ts @@ -1,14 +1,16 @@ import { search as stringSearch } from "./utils" import * as queryController from "../query" +import { UserCtx } from "@budibase/types" +import { Next } from "koa" -export async function search(ctx: any, next: any) { +export async function search(ctx: UserCtx, next: Next) { await queryController.fetch(ctx) const { name } = ctx.request.body ctx.body = stringSearch(ctx.body, name) await next() } -export async function execute(ctx: any, next: any) { +export async function execute(ctx: UserCtx, next: Next) { // don't wrap this, already returns "data" await queryController.executeV2(ctx) await next() diff --git a/packages/server/src/api/controllers/public/rows.ts b/packages/server/src/api/controllers/public/rows.ts index 39cf85a2a3..16403b06c9 100644 --- a/packages/server/src/api/controllers/public/rows.ts +++ b/packages/server/src/api/controllers/public/rows.ts @@ -1,7 +1,8 @@ import * as rowController from "../row" import { addRev } from "./utils" -import { Row } from "@budibase/types" +import { Row, UserCtx } from "@budibase/types" import { convertBookmark } from "../../../utilities" +import { Next } from "koa" // makes sure that the user doesn't need to pass in the type, tableId or _id params for // the call to be correct @@ -21,7 +22,7 @@ export function fixRow(row: Row, params: any) { return row } -export async function search(ctx: any, next: any) { +export async function search(ctx: UserCtx, next: Next) { let { sort, paginate, bookmark, limit, query } = ctx.request.body // update the body to the correct format of the internal search if (!sort) { @@ -40,25 +41,25 @@ export async function search(ctx: any, next: any) { await next() } -export async function create(ctx: any, next: any) { +export async function create(ctx: UserCtx, next: Next) { ctx.request.body = fixRow(ctx.request.body, ctx.params) await rowController.save(ctx) await next() } -export async function read(ctx: any, next: any) { +export async function read(ctx: UserCtx, next: Next) { await rowController.fetchEnrichedRow(ctx) await next() } -export async function update(ctx: any, next: any) { +export async function update(ctx: UserCtx, next: Next) { const { tableId } = ctx.params ctx.request.body = await addRev(fixRow(ctx.request.body, ctx.params), tableId) await rowController.save(ctx) await next() } -export async function destroy(ctx: any, next: any) { +export async function destroy(ctx: UserCtx, next: Next) { const { tableId } = ctx.params // set the body as expected, with the _id and _rev fields ctx.request.body = await addRev( diff --git a/packages/server/src/api/controllers/public/tables.ts b/packages/server/src/api/controllers/public/tables.ts index a346a750da..7486172fa3 100644 --- a/packages/server/src/api/controllers/public/tables.ts +++ b/packages/server/src/api/controllers/public/tables.ts @@ -1,6 +1,7 @@ import { search as stringSearch, addRev } from "./utils" import * as controller from "../table" -import { Table } from "@budibase/types" +import { Table, UserCtx } from "@budibase/types" +import { Next } from "koa" function fixTable(table: Table, params: any) { if (!params || !table) { @@ -15,24 +16,24 @@ function fixTable(table: Table, params: any) { return table } -export async function search(ctx: any, next: any) { +export async function search(ctx: UserCtx, next: Next) { const { name } = ctx.request.body await controller.fetch(ctx) ctx.body = stringSearch(ctx.body, name) await next() } -export async function create(ctx: any, next: any) { +export async function create(ctx: UserCtx, next: Next) { await controller.save(ctx) await next() } -export async function read(ctx: any, next: any) { +export async function read(ctx: UserCtx, next: Next) { await controller.find(ctx) await next() } -export async function update(ctx: any, next: any) { +export async function update(ctx: UserCtx, next: Next) { ctx.request.body = await addRev( fixTable(ctx.request.body, ctx.params), ctx.params.tableId @@ -41,7 +42,7 @@ export async function update(ctx: any, next: any) { await next() } -export async function destroy(ctx: any, next: any) { +export async function destroy(ctx: UserCtx, next: Next) { await controller.destroy(ctx) ctx.body = ctx.table await next() diff --git a/packages/server/src/api/controllers/public/users.ts b/packages/server/src/api/controllers/public/users.ts index 7192077d04..7aaf520dc4 100644 --- a/packages/server/src/api/controllers/public/users.ts +++ b/packages/server/src/api/controllers/public/users.ts @@ -7,16 +7,32 @@ import { import { publicApiUserFix } from "../../../utilities/users" import { db as dbCore } from "@budibase/backend-core" import { search as stringSearch } from "./utils" -import { BBContext, User } from "@budibase/types" +import { UserCtx, User } from "@budibase/types" +import { Next } from "koa" -function isLoggedInUser(ctx: BBContext, user: User) { +function removeRoles(ctx: UserCtx, oldUser?: User) { + const user = ctx.request.body + if (user.builder) { + user.builder = oldUser?.builder || undefined + } + if (user.admin) { + user.admin = oldUser?.admin || undefined + } + if (user.roles) { + user.roles = oldUser?.roles || {} + } + ctx.request.body = user + return ctx +} + +function isLoggedInUser(ctx: UserCtx, user: User) { const loggedInId = ctx.user?._id const globalUserId = dbCore.getGlobalIDFromUserMetadataID(loggedInId!) // check both just incase return globalUserId === user._id || loggedInId === user._id } -function getUser(ctx: BBContext, userId?: string) { +function getUser(ctx: UserCtx, userId?: string) { if (userId) { ctx.params = { userId } } else if (!ctx.params?.userId) { @@ -25,42 +41,38 @@ function getUser(ctx: BBContext, userId?: string) { return readGlobalUser(ctx) } -export async function search(ctx: BBContext, next: any) { +export async function search(ctx: UserCtx, next: Next) { const { name } = ctx.request.body const users = await allGlobalUsers(ctx) ctx.body = stringSearch(users, name, "email") await next() } -export async function create(ctx: BBContext, next: any) { - const response = await saveGlobalUser(publicApiUserFix(ctx)) +export async function create(ctx: UserCtx, next: Next) { + ctx = publicApiUserFix(removeRoles(ctx)) + const response = await saveGlobalUser(ctx) ctx.body = await getUser(ctx, response._id) await next() } -export async function read(ctx: BBContext, next: any) { +export async function read(ctx: UserCtx, next: Next) { ctx.body = await readGlobalUser(ctx) await next() } -export async function update(ctx: BBContext, next: any) { +export async function update(ctx: UserCtx, next: Next) { const user = await readGlobalUser(ctx) ctx.request.body = { ...ctx.request.body, _rev: user._rev, } - // disallow updating your own role - always overwrite with DB roles - if (isLoggedInUser(ctx, user)) { - ctx.request.body.builder = user.builder - ctx.request.body.admin = user.admin - ctx.request.body.roles = user.roles - } - const response = await saveGlobalUser(publicApiUserFix(ctx)) + ctx = publicApiUserFix(removeRoles(ctx, user)) + const response = await saveGlobalUser(ctx) ctx.body = await getUser(ctx, response._id) await next() } -export async function destroy(ctx: BBContext, next: any) { +export async function destroy(ctx: UserCtx, next: Next) { const user = await getUser(ctx) // disallow deleting yourself if (isLoggedInUser(ctx, user)) { diff --git a/packages/server/src/utilities/users.ts b/packages/server/src/utilities/users.ts index 1498a79719..f841ec3646 100644 --- a/packages/server/src/utilities/users.ts +++ b/packages/server/src/utilities/users.ts @@ -1,9 +1,9 @@ import { InternalTables } from "../db/utils" import { getGlobalUser } from "./global" -import { context, db as dbCore, roles } from "@budibase/backend-core" -import { BBContext } from "@budibase/types" +import { context, roles } from "@budibase/backend-core" +import { UserCtx } from "@budibase/types" -export async function getFullUser(ctx: BBContext, userId: string) { +export async function getFullUser(ctx: UserCtx, userId: string) { const global = await getGlobalUser(userId) let metadata: any = {} @@ -29,21 +29,12 @@ export async function getFullUser(ctx: BBContext, userId: string) { } } -export function publicApiUserFix(ctx: BBContext) { +export function publicApiUserFix(ctx: UserCtx) { if (!ctx.request.body) { return ctx } if (!ctx.request.body._id && ctx.params.userId) { ctx.request.body._id = ctx.params.userId } - if (!ctx.request.body.roles) { - ctx.request.body.roles = {} - } else { - const newRoles: { [key: string]: any } = {} - for (let [appId, role] of Object.entries(ctx.request.body.roles)) { - newRoles[dbCore.getProdAppID(appId)] = role - } - ctx.request.body.roles = newRoles - } return ctx } From ec761c238712d4ac04cebbc0763caad596a74ad9 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 4 Aug 2023 18:01:45 +0100 Subject: [PATCH 2/7] Building out public API for role assignment and un-assignment - need to flesh out pro component. --- packages/server/specs/openapi.json | 275 ++++++++++++------ packages/server/specs/openapi.yaml | 195 ++++++++----- packages/server/specs/resources/index.ts | 2 + packages/server/specs/resources/roles.ts | 65 +++++ packages/server/specs/resources/user.ts | 32 -- .../src/api/controllers/public/roles.ts | 15 + .../server/src/api/routes/public/roles.ts | 54 ++++ packages/server/src/definitions/openapi.ts | 133 ++++++--- 8 files changed, 545 insertions(+), 226 deletions(-) create mode 100644 packages/server/specs/resources/roles.ts create mode 100644 packages/server/src/api/controllers/public/roles.ts create mode 100644 packages/server/src/api/routes/public/roles.ts diff --git a/packages/server/specs/openapi.json b/packages/server/specs/openapi.json index d97b09568c..1071a39c29 100644 --- a/packages/server/specs/openapi.json +++ b/packages/server/specs/openapi.json @@ -1519,34 +1519,6 @@ "forceResetPassword": { "description": "If set to true forces the user to reset their password on first login.", "type": "boolean" - }, - "builder": { - "description": "Describes if the user is a builder user or not.", - "type": "object", - "properties": { - "global": { - "description": "If set to true the user will be able to build any app in the system.", - "type": "boolean" - } - } - }, - "admin": { - "description": "Describes if the user is an admin user or not.", - "type": "object", - "properties": { - "global": { - "description": "If set to true the user will be able to administrate the system.", - "type": "boolean" - } - } - }, - "roles": { - "description": "Contains the roles of the user per app (assuming they are not a builder user).", - "type": "object", - "additionalProperties": { - "type": "string", - "description": "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN." - } } }, "required": [ @@ -1587,34 +1559,6 @@ "description": "If set to true forces the user to reset their password on first login.", "type": "boolean" }, - "builder": { - "description": "Describes if the user is a builder user or not.", - "type": "object", - "properties": { - "global": { - "description": "If set to true the user will be able to build any app in the system.", - "type": "boolean" - } - } - }, - "admin": { - "description": "Describes if the user is an admin user or not.", - "type": "object", - "properties": { - "global": { - "description": "If set to true the user will be able to administrate the system.", - "type": "boolean" - } - } - }, - "roles": { - "description": "Contains the roles of the user per app (assuming they are not a builder user).", - "type": "object", - "additionalProperties": { - "type": "string", - "description": "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN." - } - }, "_id": { "description": "The ID of the user.", "type": "string" @@ -1666,34 +1610,6 @@ "description": "If set to true forces the user to reset their password on first login.", "type": "boolean" }, - "builder": { - "description": "Describes if the user is a builder user or not.", - "type": "object", - "properties": { - "global": { - "description": "If set to true the user will be able to build any app in the system.", - "type": "boolean" - } - } - }, - "admin": { - "description": "Describes if the user is an admin user or not.", - "type": "object", - "properties": { - "global": { - "description": "If set to true the user will be able to administrate the system.", - "type": "boolean" - } - } - }, - "roles": { - "description": "Contains the roles of the user per app (assuming they are not a builder user).", - "type": "object", - "additionalProperties": { - "type": "string", - "description": "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN." - } - }, "_id": { "description": "The ID of the user.", "type": "string" @@ -1833,6 +1749,135 @@ "required": [ "name" ] + }, + "rolesAssign": { + "type": "object", + "properties": { + "builder": { + "type": "object", + "properties": { + "global": { + "type": "boolean" + } + }, + "description": "Add/remove global builder permissions from the list of users.", + "required": [ + "global" + ] + }, + "admin": { + "type": "object", + "properties": { + "global": { + "type": "boolean" + } + }, + "description": "Add/remove global admin permissions from the list of users.", + "required": [ + "global" + ] + }, + "role": { + "type": "object", + "properties": { + "roleId": { + "description": "The role ID, such as BASIC, ADMIN or a custom role ID.", + "type": "string" + }, + "appId": { + "description": "The app that the role relates to.", + "type": "string" + } + }, + "description": "Add/remove a per-app role, such as BASIC, ADMIN etc.", + "required": [ + "roleId", + "appId" + ] + }, + "userIds": { + "description": "The user IDs to be updated to add/remove the specified roles.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "userIds" + ] + }, + "rolesUnAssign": { + "type": "object", + "properties": { + "builder": { + "type": "object", + "properties": { + "global": { + "type": "boolean" + } + }, + "description": "Add/remove global builder permissions from the list of users.", + "required": [ + "global" + ] + }, + "admin": { + "type": "object", + "properties": { + "global": { + "type": "boolean" + } + }, + "description": "Add/remove global admin permissions from the list of users.", + "required": [ + "global" + ] + }, + "role": { + "type": "object", + "properties": { + "roleId": { + "description": "The role ID, such as BASIC, ADMIN or a custom role ID.", + "type": "string" + }, + "appId": { + "description": "The app that the role relates to.", + "type": "string" + } + }, + "description": "Add/remove a per-app role, such as BASIC, ADMIN etc.", + "required": [ + "roleId", + "appId" + ] + }, + "userIds": { + "description": "The user IDs to be updated to add/remove the specified roles.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "userIds" + ] + }, + "rolesOutput": { + "type": "object", + "properties": { + "userIds": { + "description": "The updated users' IDs", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "userIds" + ] } } }, @@ -2186,6 +2231,68 @@ } } }, + "/roles/assign": { + "post": { + "operationId": "roleAssign", + "summary": "Assign a role to a list of users", + "tags": [ + "roles" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/rolesAssign" + } + } + } + }, + "responses": { + "200": { + "description": "Returns a list of updated user IDs", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/rolesOutput" + } + } + } + } + } + } + }, + "/roles/unassign": { + "post": { + "operationId": "roleUnAssign", + "summary": "Un-assign a role from a list of users", + "tags": [ + "roles" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/rolesUnAssign" + } + } + } + }, + "responses": { + "200": { + "description": "Returns a list of updated user IDs", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/rolesOutput" + } + } + } + } + } + } + }, "/tables/{tableId}/rows": { "post": { "operationId": "rowCreate", diff --git a/packages/server/specs/openapi.yaml b/packages/server/specs/openapi.yaml index 86807c9981..aa7b3ddb51 100644 --- a/packages/server/specs/openapi.yaml +++ b/packages/server/specs/openapi.yaml @@ -1296,29 +1296,6 @@ components: description: If set to true forces the user to reset their password on first login. type: boolean - builder: - description: Describes if the user is a builder user or not. - type: object - properties: - global: - description: If set to true the user will be able to build any app in the - system. - type: boolean - admin: - description: Describes if the user is an admin user or not. - type: object - properties: - global: - description: If set to true the user will be able to administrate the system. - type: boolean - roles: - description: Contains the roles of the user per app (assuming they are not a - builder user). - type: object - additionalProperties: - type: string - description: A map of app ID (production app ID, minus the _dev component) to a - role ID, e.g. ADMIN. required: - email - roles @@ -1351,29 +1328,6 @@ components: description: If set to true forces the user to reset their password on first login. type: boolean - builder: - description: Describes if the user is a builder user or not. - type: object - properties: - global: - description: If set to true the user will be able to build any app in the - system. - type: boolean - admin: - description: Describes if the user is an admin user or not. - type: object - properties: - global: - description: If set to true the user will be able to administrate the system. - type: boolean - roles: - description: Contains the roles of the user per app (assuming they are not a - builder user). - type: object - additionalProperties: - type: string - description: A map of app ID (production app ID, minus the _dev component) to a - role ID, e.g. ADMIN. _id: description: The ID of the user. type: string @@ -1414,29 +1368,6 @@ components: description: If set to true forces the user to reset their password on first login. type: boolean - builder: - description: Describes if the user is a builder user or not. - type: object - properties: - global: - description: If set to true the user will be able to build any app in the - system. - type: boolean - admin: - description: Describes if the user is an admin user or not. - type: object - properties: - global: - description: If set to true the user will be able to administrate the system. - type: boolean - roles: - description: Contains the roles of the user per app (assuming they are not a - builder user). - type: object - additionalProperties: - type: string - description: A map of app ID (production app ID, minus the _dev component) to a - role ID, e.g. ADMIN. _id: description: The ID of the user. type: string @@ -1547,6 +1478,94 @@ components: insensitive starts with match. required: - name + rolesAssign: + type: object + properties: + builder: + type: object + properties: + global: + type: boolean + description: Add/remove global builder permissions from the list of users. + required: + - global + admin: + type: object + properties: + global: + type: boolean + description: Add/remove global admin permissions from the list of users. + required: + - global + role: + type: object + properties: + roleId: + description: The role ID, such as BASIC, ADMIN or a custom role ID. + type: string + appId: + description: The app that the role relates to. + type: string + description: Add/remove a per-app role, such as BASIC, ADMIN etc. + required: + - roleId + - appId + userIds: + description: The user IDs to be updated to add/remove the specified roles. + type: array + items: + type: string + required: + - userIds + rolesUnAssign: + type: object + properties: + builder: + type: object + properties: + global: + type: boolean + description: Add/remove global builder permissions from the list of users. + required: + - global + admin: + type: object + properties: + global: + type: boolean + description: Add/remove global admin permissions from the list of users. + required: + - global + role: + type: object + properties: + roleId: + description: The role ID, such as BASIC, ADMIN or a custom role ID. + type: string + appId: + description: The app that the role relates to. + type: string + description: Add/remove a per-app role, such as BASIC, ADMIN etc. + required: + - roleId + - appId + userIds: + description: The user IDs to be updated to add/remove the specified roles. + type: array + items: + type: string + required: + - userIds + rolesOutput: + type: object + properties: + userIds: + description: The updated users' IDs + type: array + items: + type: string + required: + - userIds security: - ApiKeyAuth: [] paths: @@ -1757,6 +1776,44 @@ paths: examples: queries: $ref: "#/components/examples/queries" + /roles/assign: + post: + operationId: roleAssign + summary: Assign a role to a list of users + tags: + - roles + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/rolesAssign" + responses: + "200": + description: Returns a list of updated user IDs + content: + application/json: + schema: + $ref: "#/components/schemas/rolesOutput" + /roles/unassign: + post: + operationId: roleUnAssign + summary: Un-assign a role from a list of users + tags: + - roles + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/rolesUnAssign" + responses: + "200": + description: Returns a list of updated user IDs + content: + application/json: + schema: + $ref: "#/components/schemas/rolesOutput" "/tables/{tableId}/rows": post: operationId: rowCreate diff --git a/packages/server/specs/resources/index.ts b/packages/server/specs/resources/index.ts index 6b8a1aa437..c06148b7de 100644 --- a/packages/server/specs/resources/index.ts +++ b/packages/server/specs/resources/index.ts @@ -5,6 +5,7 @@ import query from "./query" import user from "./user" import metrics from "./metrics" import misc from "./misc" +import roles from "./roles" export const examples = { ...application.getExamples(), @@ -23,4 +24,5 @@ export const schemas = { ...query.getSchemas(), ...user.getSchemas(), ...misc.getSchemas(), + ...roles.getSchemas(), } diff --git a/packages/server/specs/resources/roles.ts b/packages/server/specs/resources/roles.ts new file mode 100644 index 0000000000..02254261be --- /dev/null +++ b/packages/server/specs/resources/roles.ts @@ -0,0 +1,65 @@ +import { object } from "./utils" +import Resource from "./utils/Resource" + +const roleSchema = object( + { + builder: object( + { + global: { + type: "boolean", + }, + }, + { + description: + "Add/remove global builder permissions from the list of users.", + } + ), + admin: object( + { + global: { + type: "boolean", + }, + }, + { + description: + "Add/remove global admin permissions from the list of users.", + } + ), + role: object( + { + roleId: { + description: "The role ID, such as BASIC, ADMIN or a custom role ID.", + type: "string", + }, + appId: { + description: "The app that the role relates to.", + type: "string", + }, + }, + { description: "Add/remove a per-app role, such as BASIC, ADMIN etc." } + ), + userIds: { + description: + "The user IDs to be updated to add/remove the specified roles.", + type: "array", + items: { + type: "string", + }, + }, + }, + { required: ["userIds"] } +) + +export default new Resource().setSchemas({ + rolesAssign: roleSchema, + rolesUnAssign: roleSchema, + rolesOutput: object({ + userIds: { + description: "The updated users' IDs", + type: "array", + items: { + type: "string", + }, + }, + }), +}) diff --git a/packages/server/specs/resources/user.ts b/packages/server/specs/resources/user.ts index a7b9f1ddb9..9ec5388672 100644 --- a/packages/server/specs/resources/user.ts +++ b/packages/server/specs/resources/user.ts @@ -57,38 +57,6 @@ const userSchema = object( "If set to true forces the user to reset their password on first login.", type: "boolean", }, - builder: { - description: "Describes if the user is a builder user or not.", - type: "object", - properties: { - global: { - description: - "If set to true the user will be able to build any app in the system.", - type: "boolean", - }, - }, - }, - admin: { - description: "Describes if the user is an admin user or not.", - type: "object", - properties: { - global: { - description: - "If set to true the user will be able to administrate the system.", - type: "boolean", - }, - }, - }, - roles: { - description: - "Contains the roles of the user per app (assuming they are not a builder user).", - type: "object", - additionalProperties: { - type: "string", - description: - "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN.", - }, - }, }, { required: ["email", "roles"] } ) diff --git a/packages/server/src/api/controllers/public/roles.ts b/packages/server/src/api/controllers/public/roles.ts new file mode 100644 index 0000000000..3b70094ae1 --- /dev/null +++ b/packages/server/src/api/controllers/public/roles.ts @@ -0,0 +1,15 @@ +import { UserCtx } from "@budibase/types" +import { Next } from "koa" + +async function assign(ctx: UserCtx, next: Next) { + ctx.body = { message: "roles assigned" } +} + +async function unAssign(ctx: UserCtx, next: Next) { + ctx.body = { message: "roles un-assigned" } +} + +export default { + assign, + unAssign, +} diff --git a/packages/server/src/api/routes/public/roles.ts b/packages/server/src/api/routes/public/roles.ts new file mode 100644 index 0000000000..2332a0ffd0 --- /dev/null +++ b/packages/server/src/api/routes/public/roles.ts @@ -0,0 +1,54 @@ +import controller from "../../controllers/public/roles" +import Endpoint from "./utils/Endpoint" + +const write = [] + +/** + * @openapi + * /roles/assign: + * post: + * operationId: roleAssign + * summary: Assign a role to a list of users + * tags: + * - roles + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/rolesAssign' + * responses: + * 200: + * description: Returns a list of updated user IDs + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/rolesOutput' + */ +write.push(new Endpoint("post", "/roles/assign", controller.assign)) + +/** + * @openapi + * /roles/unassign: + * post: + * operationId: roleUnAssign + * summary: Un-assign a role from a list of users + * tags: + * - roles + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/rolesUnAssign' + * responses: + * 200: + * description: Returns a list of updated user IDs + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/rolesOutput' + */ +write.push(new Endpoint("post", "/roles/unassign", controller.unAssign)) + +export default { write, read: [] } diff --git a/packages/server/src/definitions/openapi.ts b/packages/server/src/definitions/openapi.ts index 5ca4990647..ee078d0821 100644 --- a/packages/server/src/definitions/openapi.ts +++ b/packages/server/src/definitions/openapi.ts @@ -34,6 +34,12 @@ export interface paths { /** Based on query properties (currently only name) search for queries. */ post: operations["querySearch"]; }; + "/roles/assign": { + post: operations["roleAssign"]; + }; + "/roles/unassign": { + post: operations["roleUnAssign"]; + }; "/tables/{tableId}/rows": { /** Creates a row within the specified table. */ post: operations["rowCreate"]; @@ -256,7 +262,8 @@ export interface components { | "auto" | "json" | "internal" - | "barcodeqr"; + | "barcodeqr" + | "bigint"; /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ constraints?: { /** @enum {string} */ @@ -362,7 +369,8 @@ export interface components { | "auto" | "json" | "internal" - | "barcodeqr"; + | "barcodeqr" + | "bigint"; /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ constraints?: { /** @enum {string} */ @@ -470,7 +478,8 @@ export interface components { | "auto" | "json" | "internal" - | "barcodeqr"; + | "barcodeqr" + | "bigint"; /** @description A constraint can be applied to the column which will be validated against when a row is saved. */ constraints?: { /** @enum {string} */ @@ -577,18 +586,8 @@ export interface components { lastName?: string; /** @description If set to true forces the user to reset their password on first login. */ forceResetPassword?: boolean; - /** @description Describes if the user is a builder user or not. */ - builder?: { - /** @description If set to true the user will be able to build any app in the system. */ - global?: boolean; - }; - /** @description Describes if the user is an admin user or not. */ - admin?: { - /** @description If set to true the user will be able to administrate the system. */ - global?: boolean; - }; - /** @description Contains the roles of the user per app (assuming they are not a builder user). */ - roles: { [key: string]: string }; + } & { + roles: unknown; }; userOutput: { data: { @@ -607,24 +606,14 @@ export interface components { lastName?: string; /** @description If set to true forces the user to reset their password on first login. */ forceResetPassword?: boolean; - /** @description Describes if the user is a builder user or not. */ - builder?: { - /** @description If set to true the user will be able to build any app in the system. */ - global?: boolean; - }; - /** @description Describes if the user is an admin user or not. */ - admin?: { - /** @description If set to true the user will be able to administrate the system. */ - global?: boolean; - }; - /** @description Contains the roles of the user per app (assuming they are not a builder user). */ - roles: { [key: string]: string }; /** @description The ID of the user. */ _id: string; + } & { + roles: unknown; }; }; userSearch: { - data: { + data: ({ /** @description The email address of the user, this must be unique. */ email: string; /** @description The password of the user if using password based login - this will never be returned. This can be left out of subsequent requests (updates) and will be enriched back into the user structure. */ @@ -640,21 +629,11 @@ export interface components { lastName?: string; /** @description If set to true forces the user to reset their password on first login. */ forceResetPassword?: boolean; - /** @description Describes if the user is a builder user or not. */ - builder?: { - /** @description If set to true the user will be able to build any app in the system. */ - global?: boolean; - }; - /** @description Describes if the user is an admin user or not. */ - admin?: { - /** @description If set to true the user will be able to administrate the system. */ - global?: boolean; - }; - /** @description Contains the roles of the user per app (assuming they are not a builder user). */ - roles: { [key: string]: string }; /** @description The ID of the user. */ _id: string; - }[]; + } & { + roles: unknown; + })[]; }; rowSearch: { query: { @@ -712,6 +691,48 @@ export interface components { /** @description The name to be used when searching - this will be used in a case insensitive starts with match. */ name: string; }; + rolesAssign: { + /** @description Add/remove global builder permissions from the list of users. */ + builder?: { + global: boolean; + }; + /** @description Add/remove global admin permissions from the list of users. */ + admin?: { + global: boolean; + }; + /** @description Add/remove a per-app role, such as BASIC, ADMIN etc. */ + role?: { + /** @description The role ID, such as BASIC, ADMIN or a custom role ID. */ + roleId: string; + /** @description The app that the role relates to. */ + appId: string; + }; + /** @description The user IDs to be updated to add/remove the specified roles. */ + userIds: string[]; + }; + rolesUnAssign: { + /** @description Add/remove global builder permissions from the list of users. */ + builder?: { + global: boolean; + }; + /** @description Add/remove global admin permissions from the list of users. */ + admin?: { + global: boolean; + }; + /** @description Add/remove a per-app role, such as BASIC, ADMIN etc. */ + role?: { + /** @description The role ID, such as BASIC, ADMIN or a custom role ID. */ + roleId: string; + /** @description The app that the role relates to. */ + appId: string; + }; + /** @description The user IDs to be updated to add/remove the specified roles. */ + userIds: string[]; + }; + rolesOutput: { + /** @description The updated users' IDs */ + userIds: string[]; + }; }; parameters: { /** @description The ID of the table which this request is targeting. */ @@ -907,6 +928,36 @@ export interface operations { }; }; }; + roleAssign: { + responses: { + /** Returns a list of updated user IDs */ + 200: { + content: { + "application/json": components["schemas"]["rolesOutput"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["rolesAssign"]; + }; + }; + }; + roleUnAssign: { + responses: { + /** Returns a list of updated user IDs */ + 200: { + content: { + "application/json": components["schemas"]["rolesOutput"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["rolesUnAssign"]; + }; + }; + }; /** Creates a row within the specified table. */ rowCreate: { parameters: { From b3e89890606f3ca4c37d16dc906f129eaa05f22f Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 15 Aug 2023 14:19:36 +0100 Subject: [PATCH 3/7] Adding pro integration. --- packages/pro | 2 +- packages/server/specs/resources/user.ts | 34 +++++++++++++++++++ .../src/api/controllers/public/users.ts | 20 ++--------- packages/types/src/sdk/licensing/feature.ts | 1 + 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/packages/pro b/packages/pro index 9b9c8cc08f..bf719cb968 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 9b9c8cc08f271bfc5dd401860f344f6eb336ab35 +Subproject commit bf719cb968a13183225696a74fb40a78668a54a6 diff --git a/packages/server/specs/resources/user.ts b/packages/server/specs/resources/user.ts index 9ec5388672..d00ed02f81 100644 --- a/packages/server/specs/resources/user.ts +++ b/packages/server/specs/resources/user.ts @@ -57,6 +57,40 @@ const userSchema = object( "If set to true forces the user to reset their password on first login.", type: "boolean", }, + builder: { + description: + "Describes if the user is a builder user or not. This field can only be set on a business or enterprise license.", + type: "object", + properties: { + global: { + description: + "If set to true the user will be able to build any app in the system.", + type: "boolean", + }, + }, + }, + admin: { + description: + "Describes if the user is an admin user or not. This field can only be set on a business or enterprise license.", + type: "object", + properties: { + global: { + description: + "If set to true the user will be able to administrate the system.", + type: "boolean", + }, + }, + }, + roles: { + description: + "Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license.", + type: "object", + additionalProperties: { + type: "string", + description: + "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN.", + }, + }, }, { required: ["email", "roles"] } ) diff --git a/packages/server/src/api/controllers/public/users.ts b/packages/server/src/api/controllers/public/users.ts index 7aaf520dc4..ddaa7a678e 100644 --- a/packages/server/src/api/controllers/public/users.ts +++ b/packages/server/src/api/controllers/public/users.ts @@ -9,21 +9,7 @@ import { db as dbCore } from "@budibase/backend-core" import { search as stringSearch } from "./utils" import { UserCtx, User } from "@budibase/types" import { Next } from "koa" - -function removeRoles(ctx: UserCtx, oldUser?: User) { - const user = ctx.request.body - if (user.builder) { - user.builder = oldUser?.builder || undefined - } - if (user.admin) { - user.admin = oldUser?.admin || undefined - } - if (user.roles) { - user.roles = oldUser?.roles || {} - } - ctx.request.body = user - return ctx -} +import { sdk } from "@budibase/pro" function isLoggedInUser(ctx: UserCtx, user: User) { const loggedInId = ctx.user?._id @@ -49,7 +35,7 @@ export async function search(ctx: UserCtx, next: Next) { } export async function create(ctx: UserCtx, next: Next) { - ctx = publicApiUserFix(removeRoles(ctx)) + ctx = publicApiUserFix(await sdk.publicApi.userSaveUpdate(ctx)) const response = await saveGlobalUser(ctx) ctx.body = await getUser(ctx, response._id) await next() @@ -66,7 +52,7 @@ export async function update(ctx: UserCtx, next: Next) { ...ctx.request.body, _rev: user._rev, } - ctx = publicApiUserFix(removeRoles(ctx, user)) + ctx = publicApiUserFix(await sdk.publicApi.userSaveUpdate(ctx, user)) const response = await saveGlobalUser(ctx) ctx.body = await getUser(ctx, response._id) await next() diff --git a/packages/types/src/sdk/licensing/feature.ts b/packages/types/src/sdk/licensing/feature.ts index 20f6813409..a1ace01e48 100644 --- a/packages/types/src/sdk/licensing/feature.ts +++ b/packages/types/src/sdk/licensing/feature.ts @@ -11,6 +11,7 @@ export enum Feature { SYNC_AUTOMATIONS = "syncAutomations", APP_BUILDERS = "appBuilders", OFFLINE = "offline", + USER_ROLE_PUBLIC_API = "userRolePublicApi", } export type PlanFeatures = { [key in PlanType]: Feature[] | undefined } From e97c042e954f8530396e2cf9f1be85d3ff7fd08b Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 15 Aug 2023 15:35:52 +0100 Subject: [PATCH 4/7] Adding test cases. --- .../tests/core/utilities/mocks/licenses.ts | 4 + packages/pro | 2 +- .../src/api/routes/public/tests/users.spec.js | 38 ------ .../src/api/routes/public/tests/users.spec.ts | 126 ++++++++++++++++++ yarn.lock | 30 +++-- 5 files changed, 151 insertions(+), 49 deletions(-) delete mode 100644 packages/server/src/api/routes/public/tests/users.spec.js create mode 100644 packages/server/src/api/routes/public/tests/users.spec.ts diff --git a/packages/backend-core/tests/core/utilities/mocks/licenses.ts b/packages/backend-core/tests/core/utilities/mocks/licenses.ts index 6747282040..14a1f1f4d3 100644 --- a/packages/backend-core/tests/core/utilities/mocks/licenses.ts +++ b/packages/backend-core/tests/core/utilities/mocks/licenses.ts @@ -86,6 +86,10 @@ export const useAuditLogs = () => { return useFeature(Feature.AUDIT_LOGS) } +export const usePublicApiUserRoles = () => { + return useFeature(Feature.USER_ROLE_PUBLIC_API) +} + export const useScimIntegration = () => { return useFeature(Feature.SCIM) } diff --git a/packages/pro b/packages/pro index bf719cb968..162b4efd14 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit bf719cb968a13183225696a74fb40a78668a54a6 +Subproject commit 162b4efd148fc31aa50b76318c6d4a6eee2a8074 diff --git a/packages/server/src/api/routes/public/tests/users.spec.js b/packages/server/src/api/routes/public/tests/users.spec.js deleted file mode 100644 index 1daa611df8..0000000000 --- a/packages/server/src/api/routes/public/tests/users.spec.js +++ /dev/null @@ -1,38 +0,0 @@ -const setup = require("../../tests/utilities") -const { generateMakeRequest } = require("./utils") - -const workerRequests = require("../../../../utilities/workerRequests") - -let config = setup.getConfig() -let apiKey, globalUser, makeRequest - -beforeAll(async () => { - await config.init() - globalUser = await config.globalUser() - apiKey = await config.generateApiKey(globalUser._id) - makeRequest = generateMakeRequest(apiKey) - workerRequests.readGlobalUser.mockReturnValue(globalUser) -}) - -afterAll(setup.afterAll) - -describe("check user endpoints", () => { - it("should not allow a user to update their own roles", async () => { - const res = await makeRequest("put", `/users/${globalUser._id}`, { - ...globalUser, - roles: { - "app_1": "ADMIN", - } - }) - expect(workerRequests.saveGlobalUser.mock.lastCall[0].body.data.roles["app_1"]).toBeUndefined() - expect(res.status).toBe(200) - expect(res.body.data.roles["app_1"]).toBeUndefined() - }) - - it("should not allow a user to delete themselves", async () => { - const res = await makeRequest("delete", `/users/${globalUser._id}`) - expect(res.status).toBe(405) - expect(workerRequests.deleteGlobalUser.mock.lastCall).toBeUndefined() - }) -}) - diff --git a/packages/server/src/api/routes/public/tests/users.spec.ts b/packages/server/src/api/routes/public/tests/users.spec.ts new file mode 100644 index 0000000000..c81acca1df --- /dev/null +++ b/packages/server/src/api/routes/public/tests/users.spec.ts @@ -0,0 +1,126 @@ +import * as setup from "../../tests/utilities" +import { generateMakeRequest, MakeRequestResponse } from "./utils" +import { User } from "@budibase/types" +import { mocks } from "@budibase/backend-core/tests" + +import * as workerRequests from "../../../../utilities/workerRequests" + +const mockedWorkerReq = jest.mocked(workerRequests) + +let config = setup.getConfig() +let apiKey: string, globalUser: User, makeRequest: MakeRequestResponse + +beforeAll(async () => { + await config.init() + globalUser = await config.globalUser() + apiKey = await config.generateApiKey(globalUser._id) + makeRequest = generateMakeRequest(apiKey) + mockedWorkerReq.readGlobalUser.mockImplementation(() => + Promise.resolve(globalUser) + ) +}) + +afterAll(setup.afterAll) + +function base() { + return { + tenantId: config.getTenantId(), + firstName: "Test", + lastName: "Test", + } +} + +function updateMock() { + mockedWorkerReq.readGlobalUser.mockImplementation(ctx => ctx.request.body) +} + +describe("check user endpoints", () => { + it("should not allow a user to update their own roles", async () => { + const res = await makeRequest("put", `/users/${globalUser._id}`, { + ...globalUser, + roles: { + app_1: "ADMIN", + }, + }) + expect( + mockedWorkerReq.saveGlobalUser.mock.lastCall?.[0].body.data.roles["app_1"] + ).toBeUndefined() + expect(res.status).toBe(200) + expect(res.body.data.roles["app_1"]).toBeUndefined() + }) + + it("should not allow a user to delete themselves", async () => { + const res = await makeRequest("delete", `/users/${globalUser._id}`) + expect(res.status).toBe(405) + expect(mockedWorkerReq.deleteGlobalUser.mock.lastCall).toBeUndefined() + }) +}) + +describe("no user role update in free", () => { + beforeAll(() => { + updateMock() + }) + + it("should not allow 'roles' to be updated", async () => { + const res = await makeRequest("post", "/users", { + ...base(), + roles: { app_a: "BASIC" }, + }) + expect(res.status).toBe(200) + expect(res.body.data.roles["app_a"]).toBeUndefined() + }) + + it("should not allow 'admin' to be updated", async () => { + const res = await makeRequest("post", "/users", { + ...base(), + admin: { global: true }, + }) + expect(res.status).toBe(200) + expect(res.body.data.admin).toBeUndefined() + }) + + it("should not allow 'builder' to be updated", async () => { + const res = await makeRequest("post", "/users", { + ...base(), + builder: { global: true }, + }) + expect(res.status).toBe(200) + expect(res.body.data.builder).toBeUndefined() + }) +}) + +describe("no user role update in business", () => { + beforeAll(() => { + updateMock() + mocks.licenses.usePublicApiUserRoles() + }) + + it("should allow 'roles' to be updated", async () => { + const res = await makeRequest("post", "/users", { + ...base(), + roles: { app_a: "BASIC" }, + }) + expect(res.status).toBe(200) + expect(res.body.data.roles["app_a"]).toBe("BASIC") + }) + + it("should allow 'admin' to be updated", async () => { + mocks.licenses.usePublicApiUserRoles() + const res = await makeRequest("post", "/users", { + ...base(), + admin: { global: true }, + }) + expect(res.status).toBe(200) + expect(res.body.data.admin.global).toBe(true) + }) + + it("should allow 'builder' to be updated", async () => { + mocks.licenses.usePublicApiUserRoles() + const res = await makeRequest("post", "/users", { + ...base(), + builder: { global: true }, + }) + expect(res.status).toBe(200) + expect(res.body.data.builder.global).toBe(true) + }) +}) diff --git a/yarn.lock b/yarn.lock index 827c94a176..4f5e0db2e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10283,7 +10283,7 @@ denque@^1.1.0: resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf" integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== -denque@^2.0.1, denque@^2.1.0: +denque@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== @@ -16986,6 +16986,11 @@ long@^5.0.0: resolved "https://registry.yarnpkg.com/long/-/long-5.2.1.tgz#e27595d0083d103d2fa2c20c7699f8e0c92b897f" integrity sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A== +long@^5.2.1: + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== + lookpath@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/lookpath/-/lookpath-1.1.0.tgz#932d68371a2f0b4a5644f03d6a2b4728edba96d2" @@ -17052,6 +17057,11 @@ lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== +lru-cache@^8.0.0: + version "8.0.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-8.0.5.tgz#983fe337f3e176667f8e567cfcce7cb064ea214e" + integrity sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA== + lru-cache@^9.0.0: version "9.0.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.0.1.tgz#ac061ed291f8b9adaca2b085534bb1d3b61bef83" @@ -17952,17 +17962,17 @@ mute-stream@~1.0.0: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== -mysql2@2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-2.3.3.tgz#944f3deca4b16629052ff8614fbf89d5552545a0" - integrity sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA== +mysql2@3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.5.2.tgz#a06050e1514e9ac15711a8b883ffd51cb44b2dc8" + integrity sha512-cptobmhYkYeTBIFp2c0piw2+gElpioga1rUw5UidHvo8yaHijMZoo8A3zyBVoo/K71f7ZFvrShA9iMIy9dCzCA== dependencies: - denque "^2.0.1" + denque "^2.1.0" generate-function "^2.3.1" iconv-lite "^0.6.3" - long "^4.0.0" - lru-cache "^6.0.0" - named-placeholders "^1.1.2" + long "^5.2.1" + lru-cache "^8.0.0" + named-placeholders "^1.1.3" seq-queue "^0.0.5" sqlstring "^2.3.2" @@ -17975,7 +17985,7 @@ mz@^2.4.0, mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -named-placeholders@^1.1.2: +named-placeholders@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.3.tgz#df595799a36654da55dda6152ba7a137ad1d9351" integrity sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w== From cb49906d36a2bcee33e2b4beb5af10c70ad29709 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 15 Aug 2023 15:55:56 +0100 Subject: [PATCH 5/7] Updating pro reference. --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index 162b4efd14..7394675d71 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 162b4efd148fc31aa50b76318c6d4a6eee2a8074 +Subproject commit 7394675d71f1a0f8c5471644ed03a079683297a2 From a6a70c2d0918a4efb23a9a51415c23a689e728fd Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 15 Aug 2023 16:46:21 +0100 Subject: [PATCH 6/7] Building out the role assignment/unassignment APIs as new components of the public API. --- packages/backend-core/src/users/db.ts | 20 +++++++++----- packages/pro | 2 +- packages/server/specs/resources/roles.ts | 7 +++++ .../src/api/controllers/public/roles.ts | 27 +++++++++++++++---- .../src/api/controllers/public/users.ts | 4 +-- packages/types/src/api/index.ts | 1 + packages/types/src/api/public/.keep | 0 packages/types/src/api/public/index.ts | 1 + packages/types/src/api/public/roles.ts | 16 +++++++++++ 9 files changed, 64 insertions(+), 14 deletions(-) delete mode 100644 packages/types/src/api/public/.keep create mode 100644 packages/types/src/api/public/index.ts create mode 100644 packages/types/src/api/public/roles.ts diff --git a/packages/backend-core/src/users/db.ts b/packages/backend-core/src/users/db.ts index 55cc97bb1c..14140cba81 100644 --- a/packages/backend-core/src/users/db.ts +++ b/packages/backend-core/src/users/db.ts @@ -1,30 +1,30 @@ import env from "../environment" import * as eventHelpers from "./events" import * as accounts from "../accounts" +import * as accountSdk from "../accounts" import * as cache from "../cache" -import { getIdentity, getTenantId, getGlobalDB } from "../context" +import { getGlobalDB, getIdentity, getTenantId } from "../context" import * as dbUtils from "../db" import { EmailUnavailableError, HTTPError } from "../errors" import * as platform from "../platform" import * as sessions from "../security/sessions" import * as usersCore from "./users" import { + Account, AllDocsResponse, BulkUserCreated, BulkUserDeleted, + isSSOAccount, + isSSOUser, RowResponse, SaveUserOpts, User, - Account, - isSSOUser, - isSSOAccount, UserStatus, } from "@budibase/types" -import * as accountSdk from "../accounts" import { - validateUniqueUser, getAccountHolderFromUserIds, isAdmin, + validateUniqueUser, } from "./utils" import { searchExistingEmails } from "./lookup" import { hash } from "../utils" @@ -179,6 +179,14 @@ export class UserDB { return user } + static async bulkGet(userIds: string[]) { + return await usersCore.bulkGetGlobalUsersById(userIds) + } + + static async bulkUpdate(users: User[]) { + return await usersCore.bulkUpdateGlobalUsers(users) + } + static async save(user: User, opts: SaveUserOpts = {}): Promise { // default booleans to true if (opts.hashPassword == null) { diff --git a/packages/pro b/packages/pro index 7394675d71..1fc0fe26ec 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 7394675d71f1a0f8c5471644ed03a079683297a2 +Subproject commit 1fc0fe26ec843794c4b80d25e593e3f6edd34840 diff --git a/packages/server/specs/resources/roles.ts b/packages/server/specs/resources/roles.ts index 02254261be..f4fd870b7b 100644 --- a/packages/server/specs/resources/roles.ts +++ b/packages/server/specs/resources/roles.ts @@ -3,6 +3,13 @@ import Resource from "./utils/Resource" const roleSchema = object( { + appBuilder: object({ + appId: { + description: + "The app that the users should have app builder privileges granted for.", + type: "string", + }, + }), builder: object( { global: { diff --git a/packages/server/src/api/controllers/public/roles.ts b/packages/server/src/api/controllers/public/roles.ts index 3b70094ae1..1ff11c48c2 100644 --- a/packages/server/src/api/controllers/public/roles.ts +++ b/packages/server/src/api/controllers/public/roles.ts @@ -1,12 +1,29 @@ -import { UserCtx } from "@budibase/types" +import { + UserCtx, + RoleAssignmentResponse, + RoleAssignmentRequest, +} from "@budibase/types" import { Next } from "koa" +import { sdk } from "@budibase/pro" -async function assign(ctx: UserCtx, next: Next) { - ctx.body = { message: "roles assigned" } +async function assign( + ctx: UserCtx, + next: Next +) { + const { userIds, ...assignmentProps } = ctx.request.body + await sdk.publicApi.roles.assign(userIds, assignmentProps) + ctx.body = { userIds } + await next() } -async function unAssign(ctx: UserCtx, next: Next) { - ctx.body = { message: "roles un-assigned" } +async function unAssign( + ctx: UserCtx, + next: Next +) { + const { userIds, ...unAssignmentProps } = ctx.request.body + await sdk.publicApi.roles.unAssign(userIds, unAssignmentProps) + ctx.body = { userIds } + await next() } export default { diff --git a/packages/server/src/api/controllers/public/users.ts b/packages/server/src/api/controllers/public/users.ts index ddaa7a678e..bb6fc3a6e7 100644 --- a/packages/server/src/api/controllers/public/users.ts +++ b/packages/server/src/api/controllers/public/users.ts @@ -35,7 +35,7 @@ export async function search(ctx: UserCtx, next: Next) { } export async function create(ctx: UserCtx, next: Next) { - ctx = publicApiUserFix(await sdk.publicApi.userSaveUpdate(ctx)) + ctx = publicApiUserFix(await sdk.publicApi.users.roleCheck(ctx)) const response = await saveGlobalUser(ctx) ctx.body = await getUser(ctx, response._id) await next() @@ -52,7 +52,7 @@ export async function update(ctx: UserCtx, next: Next) { ...ctx.request.body, _rev: user._rev, } - ctx = publicApiUserFix(await sdk.publicApi.userSaveUpdate(ctx, user)) + ctx = publicApiUserFix(await sdk.publicApi.users.roleCheck(ctx, user)) const response = await saveGlobalUser(ctx) ctx.body = await getUser(ctx, response._id) await next() diff --git a/packages/types/src/api/index.ts b/packages/types/src/api/index.ts index 5fa77b18ea..9339ae7147 100644 --- a/packages/types/src/api/index.ts +++ b/packages/types/src/api/index.ts @@ -1,2 +1,3 @@ export * from "./account" export * from "./web" +export * from "./public" diff --git a/packages/types/src/api/public/.keep b/packages/types/src/api/public/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/types/src/api/public/index.ts b/packages/types/src/api/public/index.ts new file mode 100644 index 0000000000..2a3a9dddba --- /dev/null +++ b/packages/types/src/api/public/index.ts @@ -0,0 +1 @@ +export * from "./roles" diff --git a/packages/types/src/api/public/roles.ts b/packages/types/src/api/public/roles.ts new file mode 100644 index 0000000000..fbef8af8b1 --- /dev/null +++ b/packages/types/src/api/public/roles.ts @@ -0,0 +1,16 @@ +export interface RoleAssignmentRequest { + role?: { + appId: string + roleId: string + } + appBuilder?: { + appId: string + } + builder?: boolean + admin?: boolean + userIds: string[] +} + +export interface RoleAssignmentResponse { + userIds: string[] +} From 8f81a16340ad6eae770fb165a6100c25cb4b02ab Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 15 Aug 2023 17:33:22 +0100 Subject: [PATCH 7/7] Updating types to be based on the open API definition rather than types. --- packages/server/specs/openapi.json | 162 ++++++++++++++---- packages/server/specs/openapi.yaml | 145 ++++++++++++---- packages/server/specs/resources/roles.ts | 55 +++--- .../api/controllers/public/mapping/types.ts | 4 + .../src/api/controllers/public/roles.ts | 19 +- .../server/src/api/routes/public/roles.ts | 2 + packages/server/src/definitions/openapi.ts | 82 ++++++--- packages/types/src/api/index.ts | 1 - packages/types/src/api/public/.keep | 0 packages/types/src/api/public/index.ts | 1 - packages/types/src/api/public/roles.ts | 16 -- 11 files changed, 340 insertions(+), 147 deletions(-) create mode 100644 packages/types/src/api/public/.keep delete mode 100644 packages/types/src/api/public/index.ts delete mode 100644 packages/types/src/api/public/roles.ts diff --git a/packages/server/specs/openapi.json b/packages/server/specs/openapi.json index 1071a39c29..1e5718c5b5 100644 --- a/packages/server/specs/openapi.json +++ b/packages/server/specs/openapi.json @@ -1519,6 +1519,34 @@ "forceResetPassword": { "description": "If set to true forces the user to reset their password on first login.", "type": "boolean" + }, + "builder": { + "description": "Describes if the user is a builder user or not. This field can only be set on a business or enterprise license.", + "type": "object", + "properties": { + "global": { + "description": "If set to true the user will be able to build any app in the system.", + "type": "boolean" + } + } + }, + "admin": { + "description": "Describes if the user is an admin user or not. This field can only be set on a business or enterprise license.", + "type": "object", + "properties": { + "global": { + "description": "If set to true the user will be able to administrate the system.", + "type": "boolean" + } + } + }, + "roles": { + "description": "Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license.", + "type": "object", + "additionalProperties": { + "type": "string", + "description": "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN." + } } }, "required": [ @@ -1559,6 +1587,34 @@ "description": "If set to true forces the user to reset their password on first login.", "type": "boolean" }, + "builder": { + "description": "Describes if the user is a builder user or not. This field can only be set on a business or enterprise license.", + "type": "object", + "properties": { + "global": { + "description": "If set to true the user will be able to build any app in the system.", + "type": "boolean" + } + } + }, + "admin": { + "description": "Describes if the user is an admin user or not. This field can only be set on a business or enterprise license.", + "type": "object", + "properties": { + "global": { + "description": "If set to true the user will be able to administrate the system.", + "type": "boolean" + } + } + }, + "roles": { + "description": "Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license.", + "type": "object", + "additionalProperties": { + "type": "string", + "description": "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN." + } + }, "_id": { "description": "The ID of the user.", "type": "string" @@ -1610,6 +1666,34 @@ "description": "If set to true forces the user to reset their password on first login.", "type": "boolean" }, + "builder": { + "description": "Describes if the user is a builder user or not. This field can only be set on a business or enterprise license.", + "type": "object", + "properties": { + "global": { + "description": "If set to true the user will be able to build any app in the system.", + "type": "boolean" + } + } + }, + "admin": { + "description": "Describes if the user is an admin user or not. This field can only be set on a business or enterprise license.", + "type": "object", + "properties": { + "global": { + "description": "If set to true the user will be able to administrate the system.", + "type": "boolean" + } + } + }, + "roles": { + "description": "Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license.", + "type": "object", + "additionalProperties": { + "type": "string", + "description": "A map of app ID (production app ID, minus the _dev component) to a role ID, e.g. ADMIN." + } + }, "_id": { "description": "The ID of the user.", "type": "string" @@ -1753,29 +1837,26 @@ "rolesAssign": { "type": "object", "properties": { - "builder": { + "appBuilder": { "type": "object", "properties": { - "global": { - "type": "boolean" + "appId": { + "description": "The app that the users should have app builder privileges granted for.", + "type": "string" } }, - "description": "Add/remove global builder permissions from the list of users.", + "description": "Allow setting users to builders per app.", "required": [ - "global" + "appId" ] }, + "builder": { + "type": "boolean", + "description": "Add/remove global builder permissions from the list of users." + }, "admin": { - "type": "object", - "properties": { - "global": { - "type": "boolean" - } - }, - "description": "Add/remove global admin permissions from the list of users.", - "required": [ - "global" - ] + "type": "boolean", + "description": "Add/remove global admin permissions from the list of users." }, "role": { "type": "object", @@ -1810,29 +1891,26 @@ "rolesUnAssign": { "type": "object", "properties": { - "builder": { + "appBuilder": { "type": "object", "properties": { - "global": { - "type": "boolean" + "appId": { + "description": "The app that the users should have app builder privileges granted for.", + "type": "string" } }, - "description": "Add/remove global builder permissions from the list of users.", + "description": "Allow setting users to builders per app.", "required": [ - "global" + "appId" ] }, + "builder": { + "type": "boolean", + "description": "Add/remove global builder permissions from the list of users." + }, "admin": { - "type": "object", - "properties": { - "global": { - "type": "boolean" - } - }, - "description": "Add/remove global admin permissions from the list of users.", - "required": [ - "global" - ] + "type": "boolean", + "description": "Add/remove global admin permissions from the list of users." }, "role": { "type": "object", @@ -1867,16 +1945,24 @@ "rolesOutput": { "type": "object", "properties": { - "userIds": { - "description": "The updated users' IDs", - "type": "array", - "items": { - "type": "string" - } + "data": { + "type": "object", + "properties": { + "userIds": { + "description": "The updated users' IDs", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "userIds" + ] } }, "required": [ - "userIds" + "data" ] } } @@ -2235,6 +2321,7 @@ "post": { "operationId": "roleAssign", "summary": "Assign a role to a list of users", + "description": "This is a business/enterprise only endpoint", "tags": [ "roles" ], @@ -2266,6 +2353,7 @@ "post": { "operationId": "roleUnAssign", "summary": "Un-assign a role from a list of users", + "description": "This is a business/enterprise only endpoint", "tags": [ "roles" ], diff --git a/packages/server/specs/openapi.yaml b/packages/server/specs/openapi.yaml index aa7b3ddb51..07320917b8 100644 --- a/packages/server/specs/openapi.yaml +++ b/packages/server/specs/openapi.yaml @@ -1296,6 +1296,32 @@ components: description: If set to true forces the user to reset their password on first login. type: boolean + builder: + description: Describes if the user is a builder user or not. This field can only + be set on a business or enterprise license. + type: object + properties: + global: + description: If set to true the user will be able to build any app in the + system. + type: boolean + admin: + description: Describes if the user is an admin user or not. This field can only + be set on a business or enterprise license. + type: object + properties: + global: + description: If set to true the user will be able to administrate the system. + type: boolean + roles: + description: Contains the roles of the user per app (assuming they are not a + builder user). This field can only be set on a business or + enterprise license. + type: object + additionalProperties: + type: string + description: A map of app ID (production app ID, minus the _dev component) to a + role ID, e.g. ADMIN. required: - email - roles @@ -1328,6 +1354,32 @@ components: description: If set to true forces the user to reset their password on first login. type: boolean + builder: + description: Describes if the user is a builder user or not. This field can only + be set on a business or enterprise license. + type: object + properties: + global: + description: If set to true the user will be able to build any app in the + system. + type: boolean + admin: + description: Describes if the user is an admin user or not. This field can only + be set on a business or enterprise license. + type: object + properties: + global: + description: If set to true the user will be able to administrate the system. + type: boolean + roles: + description: Contains the roles of the user per app (assuming they are not a + builder user). This field can only be set on a business or + enterprise license. + type: object + additionalProperties: + type: string + description: A map of app ID (production app ID, minus the _dev component) to a + role ID, e.g. ADMIN. _id: description: The ID of the user. type: string @@ -1368,6 +1420,32 @@ components: description: If set to true forces the user to reset their password on first login. type: boolean + builder: + description: Describes if the user is a builder user or not. This field can only + be set on a business or enterprise license. + type: object + properties: + global: + description: If set to true the user will be able to build any app in the + system. + type: boolean + admin: + description: Describes if the user is an admin user or not. This field can only + be set on a business or enterprise license. + type: object + properties: + global: + description: If set to true the user will be able to administrate the system. + type: boolean + roles: + description: Contains the roles of the user per app (assuming they are not a + builder user). This field can only be set on a business or + enterprise license. + type: object + additionalProperties: + type: string + description: A map of app ID (production app ID, minus the _dev component) to a + role ID, e.g. ADMIN. _id: description: The ID of the user. type: string @@ -1481,22 +1559,22 @@ components: rolesAssign: type: object properties: + appBuilder: + type: object + properties: + appId: + description: The app that the users should have app builder privileges granted + for. + type: string + description: Allow setting users to builders per app. + required: + - appId builder: - type: object - properties: - global: - type: boolean + type: boolean description: Add/remove global builder permissions from the list of users. - required: - - global admin: - type: object - properties: - global: - type: boolean + type: boolean description: Add/remove global admin permissions from the list of users. - required: - - global role: type: object properties: @@ -1520,22 +1598,22 @@ components: rolesUnAssign: type: object properties: + appBuilder: + type: object + properties: + appId: + description: The app that the users should have app builder privileges granted + for. + type: string + description: Allow setting users to builders per app. + required: + - appId builder: - type: object - properties: - global: - type: boolean + type: boolean description: Add/remove global builder permissions from the list of users. - required: - - global admin: - type: object - properties: - global: - type: boolean + type: boolean description: Add/remove global admin permissions from the list of users. - required: - - global role: type: object properties: @@ -1559,13 +1637,18 @@ components: rolesOutput: type: object properties: - userIds: - description: The updated users' IDs - type: array - items: - type: string + data: + type: object + properties: + userIds: + description: The updated users' IDs + type: array + items: + type: string + required: + - userIds required: - - userIds + - data security: - ApiKeyAuth: [] paths: @@ -1780,6 +1863,7 @@ paths: post: operationId: roleAssign summary: Assign a role to a list of users + description: This is a business/enterprise only endpoint tags: - roles requestBody: @@ -1799,6 +1883,7 @@ paths: post: operationId: roleUnAssign summary: Un-assign a role from a list of users + description: This is a business/enterprise only endpoint tags: - roles requestBody: diff --git a/packages/server/specs/resources/roles.ts b/packages/server/specs/resources/roles.ts index f4fd870b7b..1033d640ce 100644 --- a/packages/server/specs/resources/roles.ts +++ b/packages/server/specs/resources/roles.ts @@ -3,35 +3,26 @@ import Resource from "./utils/Resource" const roleSchema = object( { - appBuilder: object({ - appId: { - description: - "The app that the users should have app builder privileges granted for.", - type: "string", - }, - }), - builder: object( + appBuilder: object( { - global: { - type: "boolean", + appId: { + description: + "The app that the users should have app builder privileges granted for.", + type: "string", }, }, - { - description: - "Add/remove global builder permissions from the list of users.", - } - ), - admin: object( - { - global: { - type: "boolean", - }, - }, - { - description: - "Add/remove global admin permissions from the list of users.", - } + { description: "Allow setting users to builders per app." } ), + builder: { + type: "boolean", + description: + "Add/remove global builder permissions from the list of users.", + }, + admin: { + type: "boolean", + description: + "Add/remove global admin permissions from the list of users.", + }, role: object( { roleId: { @@ -61,12 +52,14 @@ export default new Resource().setSchemas({ rolesAssign: roleSchema, rolesUnAssign: roleSchema, rolesOutput: object({ - userIds: { - description: "The updated users' IDs", - type: "array", - items: { - type: "string", + data: object({ + userIds: { + description: "The updated users' IDs", + type: "array", + items: { + type: "string", + }, }, - }, + }), }), }) diff --git a/packages/server/src/api/controllers/public/mapping/types.ts b/packages/server/src/api/controllers/public/mapping/types.ts index e3c8719d87..9fea9b7213 100644 --- a/packages/server/src/api/controllers/public/mapping/types.ts +++ b/packages/server/src/api/controllers/public/mapping/types.ts @@ -16,6 +16,10 @@ export type CreateRowParams = components["schemas"]["row"] export type User = components["schemas"]["userOutput"]["data"] export type CreateUserParams = components["schemas"]["user"] +export type RoleAssignRequest = components["schemas"]["rolesAssign"] +export type RoleUnAssignRequest = components["schemas"]["rolesUnAssign"] +export type RoleAssignmentResponse = components["schemas"]["rolesOutput"] + export type SearchInputParams = | components["schemas"]["nameSearch"] | components["schemas"]["rowSearch"] diff --git a/packages/server/src/api/controllers/public/roles.ts b/packages/server/src/api/controllers/public/roles.ts index 1ff11c48c2..362f25da58 100644 --- a/packages/server/src/api/controllers/public/roles.ts +++ b/packages/server/src/api/controllers/public/roles.ts @@ -1,28 +1,29 @@ -import { - UserCtx, - RoleAssignmentResponse, - RoleAssignmentRequest, -} from "@budibase/types" +import { UserCtx } from "@budibase/types" import { Next } from "koa" import { sdk } from "@budibase/pro" +import { + RoleAssignmentResponse, + RoleUnAssignRequest, + RoleAssignRequest, +} from "./mapping/types" async function assign( - ctx: UserCtx, + ctx: UserCtx, next: Next ) { const { userIds, ...assignmentProps } = ctx.request.body await sdk.publicApi.roles.assign(userIds, assignmentProps) - ctx.body = { userIds } + ctx.body = { data: { userIds } } await next() } async function unAssign( - ctx: UserCtx, + ctx: UserCtx, next: Next ) { const { userIds, ...unAssignmentProps } = ctx.request.body await sdk.publicApi.roles.unAssign(userIds, unAssignmentProps) - ctx.body = { userIds } + ctx.body = { data: { userIds } } await next() } diff --git a/packages/server/src/api/routes/public/roles.ts b/packages/server/src/api/routes/public/roles.ts index 2332a0ffd0..905f364cbe 100644 --- a/packages/server/src/api/routes/public/roles.ts +++ b/packages/server/src/api/routes/public/roles.ts @@ -9,6 +9,7 @@ const write = [] * post: * operationId: roleAssign * summary: Assign a role to a list of users + * description: This is a business/enterprise only endpoint * tags: * - roles * requestBody: @@ -33,6 +34,7 @@ write.push(new Endpoint("post", "/roles/assign", controller.assign)) * post: * operationId: roleUnAssign * summary: Un-assign a role from a list of users + * description: This is a business/enterprise only endpoint * tags: * - roles * requestBody: diff --git a/packages/server/src/definitions/openapi.ts b/packages/server/src/definitions/openapi.ts index ee078d0821..fe5c17b218 100644 --- a/packages/server/src/definitions/openapi.ts +++ b/packages/server/src/definitions/openapi.ts @@ -35,9 +35,11 @@ export interface paths { post: operations["querySearch"]; }; "/roles/assign": { + /** This is a business/enterprise only endpoint */ post: operations["roleAssign"]; }; "/roles/unassign": { + /** This is a business/enterprise only endpoint */ post: operations["roleUnAssign"]; }; "/tables/{tableId}/rows": { @@ -586,8 +588,18 @@ export interface components { lastName?: string; /** @description If set to true forces the user to reset their password on first login. */ forceResetPassword?: boolean; - } & { - roles: unknown; + /** @description Describes if the user is a builder user or not. This field can only be set on a business or enterprise license. */ + builder?: { + /** @description If set to true the user will be able to build any app in the system. */ + global?: boolean; + }; + /** @description Describes if the user is an admin user or not. This field can only be set on a business or enterprise license. */ + admin?: { + /** @description If set to true the user will be able to administrate the system. */ + global?: boolean; + }; + /** @description Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license. */ + roles: { [key: string]: string }; }; userOutput: { data: { @@ -606,14 +618,24 @@ export interface components { lastName?: string; /** @description If set to true forces the user to reset their password on first login. */ forceResetPassword?: boolean; + /** @description Describes if the user is a builder user or not. This field can only be set on a business or enterprise license. */ + builder?: { + /** @description If set to true the user will be able to build any app in the system. */ + global?: boolean; + }; + /** @description Describes if the user is an admin user or not. This field can only be set on a business or enterprise license. */ + admin?: { + /** @description If set to true the user will be able to administrate the system. */ + global?: boolean; + }; + /** @description Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license. */ + roles: { [key: string]: string }; /** @description The ID of the user. */ _id: string; - } & { - roles: unknown; }; }; userSearch: { - data: ({ + data: { /** @description The email address of the user, this must be unique. */ email: string; /** @description The password of the user if using password based login - this will never be returned. This can be left out of subsequent requests (updates) and will be enriched back into the user structure. */ @@ -629,11 +651,21 @@ export interface components { lastName?: string; /** @description If set to true forces the user to reset their password on first login. */ forceResetPassword?: boolean; + /** @description Describes if the user is a builder user or not. This field can only be set on a business or enterprise license. */ + builder?: { + /** @description If set to true the user will be able to build any app in the system. */ + global?: boolean; + }; + /** @description Describes if the user is an admin user or not. This field can only be set on a business or enterprise license. */ + admin?: { + /** @description If set to true the user will be able to administrate the system. */ + global?: boolean; + }; + /** @description Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license. */ + roles: { [key: string]: string }; /** @description The ID of the user. */ _id: string; - } & { - roles: unknown; - })[]; + }[]; }; rowSearch: { query: { @@ -692,14 +724,15 @@ export interface components { name: string; }; rolesAssign: { + /** @description Allow setting users to builders per app. */ + appBuilder?: { + /** @description The app that the users should have app builder privileges granted for. */ + appId: string; + }; /** @description Add/remove global builder permissions from the list of users. */ - builder?: { - global: boolean; - }; + builder?: boolean; /** @description Add/remove global admin permissions from the list of users. */ - admin?: { - global: boolean; - }; + admin?: boolean; /** @description Add/remove a per-app role, such as BASIC, ADMIN etc. */ role?: { /** @description The role ID, such as BASIC, ADMIN or a custom role ID. */ @@ -711,14 +744,15 @@ export interface components { userIds: string[]; }; rolesUnAssign: { + /** @description Allow setting users to builders per app. */ + appBuilder?: { + /** @description The app that the users should have app builder privileges granted for. */ + appId: string; + }; /** @description Add/remove global builder permissions from the list of users. */ - builder?: { - global: boolean; - }; + builder?: boolean; /** @description Add/remove global admin permissions from the list of users. */ - admin?: { - global: boolean; - }; + admin?: boolean; /** @description Add/remove a per-app role, such as BASIC, ADMIN etc. */ role?: { /** @description The role ID, such as BASIC, ADMIN or a custom role ID. */ @@ -730,8 +764,10 @@ export interface components { userIds: string[]; }; rolesOutput: { - /** @description The updated users' IDs */ - userIds: string[]; + data: { + /** @description The updated users' IDs */ + userIds: string[]; + }; }; }; parameters: { @@ -928,6 +964,7 @@ export interface operations { }; }; }; + /** This is a business/enterprise only endpoint */ roleAssign: { responses: { /** Returns a list of updated user IDs */ @@ -943,6 +980,7 @@ export interface operations { }; }; }; + /** This is a business/enterprise only endpoint */ roleUnAssign: { responses: { /** Returns a list of updated user IDs */ diff --git a/packages/types/src/api/index.ts b/packages/types/src/api/index.ts index 9339ae7147..5fa77b18ea 100644 --- a/packages/types/src/api/index.ts +++ b/packages/types/src/api/index.ts @@ -1,3 +1,2 @@ export * from "./account" export * from "./web" -export * from "./public" diff --git a/packages/types/src/api/public/.keep b/packages/types/src/api/public/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/types/src/api/public/index.ts b/packages/types/src/api/public/index.ts deleted file mode 100644 index 2a3a9dddba..0000000000 --- a/packages/types/src/api/public/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./roles" diff --git a/packages/types/src/api/public/roles.ts b/packages/types/src/api/public/roles.ts deleted file mode 100644 index fbef8af8b1..0000000000 --- a/packages/types/src/api/public/roles.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface RoleAssignmentRequest { - role?: { - appId: string - roleId: string - } - appBuilder?: { - appId: string - } - builder?: boolean - admin?: boolean - userIds: string[] -} - -export interface RoleAssignmentResponse { - userIds: string[] -}