Permission Integration👑

The permission system built into generated code — three permission models, config entry points, and how to plug in your own role system.

Generated code ships with a role-based permission system split into operation, field, and data permissions. Config is centralized in permissions.json — edit config and it takes effect, no business-code changes.

Permission model

PermissionControlsExample
OperationWho can read/insert/update/delete per tableregular users can’t delete orders
FieldA role hides or read-onlys certain fieldsphone number hidden from support staff
DataScope visible data by ownershipcan only see data you created

Config entry point: permissions.json

Generated code contains a permissions.json structured as role → table → operation → field. A minimal example:

{
  "admin": { "users": { "query": true, "create": true, "update": true, "delete": true } },
  "user":  { "users": { "query": true, "update": true } }
}
  • admin has full access to users
  • user can only query and update — not delete

Edit this file and permissions take effect immediately — that’s the smallest “config-is-code” change point.

How it’s implemented in code

Permission rules map to a per-language mechanism:

LanguageImplementation
Java (Spring Boot)Annotations + AOP: declarative interception via @PreAuthorize, logic centralized in the framework layer
Go / TypeScript / PythonPermission middleware + utility functions, checked at the routing layer

You don’t write per-table if checks — permission checks are handled uniformly by the framework layer.

Plug in your own role system

Option A: reuse the default roles, only change config Use the roles in the generated code (e.g. admin / user) and change permissions.json to your rules.

Option B: integrate an existing user/role table If you already have a user & role system:

  1. Have the login state carry the current user’s role (see Login Integration)
  2. Map the role value to a key in permissions.json
  3. Permission checks read config by the mapped role

The smallest change is the “login state → role → permissions.json” chain; everything else is handled by the framework layer.

Plug in your own permission data source

If permissions.json doesn’t cover your scenario — permissions live in your own database, computed dynamically per user group, or backed by an external permission system — generated code leaves a permission data-source hook: every backend has one or two fixed decision functions that read permissions.json by default. Rewrite them and you’ve swapped the data source. Middleware, DAO, and field masking all call these functions, so changing one place rewires the whole chain.

Unified contract: (role, table, op) → { allowed, maskedFields } — whatever the data source, it only answers “can this role do this operation on this table, and which fields are masked”.

BackendFileFunctions / methods to rewrite
Java (Spring Boot)src/main/java/.../config/PermissionService.javaprovide your own @Primary PermissionService bean implementing canQuery / canInsert / canUpdate / canDelete / getMaskedFields
Goutils/permissionUtils.goCheckOpAccess(role, table, op), GetMaskedFields(role, table, op)
TypeScriptsrc/auth/permissions.tscheckOpPermission(role, table, op), getMaskedFields(role, table, op)
Pythonauth/permissions.pycheck_op_permission(role, table, op), get_masked_fields(role, table, op)

Java runs on the Spring container, and PermissionService is itself a bean — if you’d rather not touch generated files, just add a same-named bean marked @Primary in your own code to override it; Go / TypeScript / Python: edit the functions in the matching file directly.

Three ways to integrate

  1. Full source swap: rewrite the decision functions to query your own permission tables / user groups / external API
  2. Extend the default config: in the decision function, first compute against permissions.json, then merge your own source (e.g. whatever isn’t denied by default is then decided by your tables)
  3. Heterogeneous mode: your role model differs from the defaults — just keep the contract; how roles are defined and how role ↔ permission maps is up to you

Minimal example (Go, querying your own permission table)

// Rewrite CheckOpAccess to use your own permission table as the source
func CheckOpAccess(role, table, op string) bool {
	var allowed bool
	// Swap in your own query (illustrative; db is your connection)
	err := db.QueryRow(
		"SELECT COALESCE(MAX(allowed), false) FROM sys_role_permission WHERE role=$1 AND tbl=$2 AND op=$3",
		role, table, op,
	).Scan(&allowed)
	if err != nil { return false } // not found → deny (change to allow-by-default if you prefer)
	return allowed
}

Every backend permission file has the same “hook point” comment at the top — open the code and you’ll see which functions to change. Regeneration overwrites these files — manage your changes with git and merge after regenerating (see FAQ).

Field & data permissions

  • Field permissions: specify in config that a role hides or read-onlys certain fields on a table; the response layer masks automatically
  • Data permissions: used together with data ownership (see Advanced Settings), filtering query results by ownership

Notes

  • Function-permission configuration is done in the workbench’s Function Permission area (a subscription feature); the generated permissions.json is the landed result
  • Code generated from the free/basic tier contains placeholder permission logic; full permissions require generation under a subscription