Skip to main content

System Groups and Permissions

Oidis uses a group-based authorization system built around method patterns — wildcard strings that control which backend methods a user can call. Every user belongs to one or more groups, each carrying its own set of authorized methods. Five predefined system groups provide the framework's built-in roles, and projects can add custom groups for application-specific access control.

Package: io-oidis-hub (backend), io-oidis-hub-gui (frontend API)

System Groups​

The framework defines five system groups in AuthManager. These are created automatically on first startup and cannot be deleted (marked Readonly):

GroupPermissionsPurpose
Admin* (all methods)Full administrative access — manages users, groups, settings, and all application features
SysAdmin#SysAdminSystem administration — server diagnostics, system logs, cache management
Agent* (all methods)Request-forwarding agents — full access for automated processes behind firewalls (see Request Forwarding)
OrganizationOwner#OrganizationOwner + organization management methodsManages own organization — can edit members, view organization profile
Member#OrganizationMemberBasic authenticated user — can access own profile and organization data
note

Admin and Agent both have * (all methods) access. The difference is semantic: Admin is for human administrators, Agent is for automated request-forwarding processes.

ISystemGroups Interface​

On the frontend, system groups are accessed via the ISystemGroups interface returned by AuthManagerConnector.getSystemGroups():

export interface ISystemGroups {
Admin : Group;
SysAdmin : Group;
Agent : Group;
OrganizationOwner : Group;
Member : Group;
}

Each property is a full Group object with Id, Name, AuthorizedMethods, Hidden, and Readonly fields. Access these via UserContext:

const groups : ISystemGroups = this.context.getGroups();
const isOrgOwner : boolean = await this.auth.HasGroup(groups.OrganizationOwner);

The Group Model​

Groups are stored as @Entity("group") documents in MongoDB:

@Entity("group")
export class Group extends BaseModel<Group> {
@Property({type: String})
public declare Name : string;

@Property({type: [String], default: [], converter: new ArrayConverter()})
public declare AuthorizedMethods : string[];

@Property({type: Boolean, required: false, default: false})
public declare Hidden : boolean;
}
PropertyTypeDescription
NamestringDisplay name (unique)
AuthorizedMethodsstring[]Array of method patterns this group can access
HiddenbooleanExcluded from UI group listings (default: false)
ReadonlybooleanSystem-protected — cannot be deleted (inherited from BaseModel)
DeletedbooleanSoft-delete flag (inherited from BaseModel)

User-Group Relationship​

Each user has two group references on the User model:

@Property({type: [Group], converter: new GroupListToRefConverter()})
public declare Groups : Group[];

@Property({type: Group, converter: new GroupToRefConverter()})
public declare DefaultGroup : Group;
  • Groups — explicit group memberships (Admin, SysAdmin, custom groups, etc.)
  • DefaultGroup — the user's personal group, which carries user-specific permissions

Method Authorization Patterns​

The permission system uses wildcard string matching via StringUtils.PatternMatched(). Authorization methods on a group are string patterns:

PatternMatchesExample
*All methodsAdmin, Agent groups
Namespace.*All methods in a namespaceIo.Oidis.Hub.Utils.*
Namespace.MethodA specific methodIo.Oidis.Hub.Utils.AuthManager.getUserProfile
#FeatureFeature flag#TokenManager, #SysAdmin, #OrganizationMember

Feature Flags​

Feature flags (prefixed with #) are special permission strings that don't map to a specific backend method. Instead, they enable logical features that can be checked with IsAuthorizedFor():

export abstract class AuthFeatures extends BaseEnum {
public static readonly TOKEN_MANAGER : string = "#TokenManager";
public static readonly SYSADMIN : string = "#SysAdmin";
public static readonly ORGANIZATION_MEMBER : string = "#OrganizationMember";
public static readonly ORGANIZATION_OWNER : string = "#OrganizationOwner";
}
tip

Application projects can define their own feature flags by adding # prefixed strings to a group's AuthorizedMethods. For example, #Cabinet for a cabinet-control feature that is checked with auth.IsAuthorizedFor("#Cabinet").

Authorization API​

Frontend (AuthManagerConnector)​

MethodReturnsDescription
HasGroup($group, $user?)Promise<boolean>Check if user belongs to a group. Defaults to current user.
IsAuthorizedFor($method)Promise<boolean>Check if current user can access a specific method or feature
getAuthorizedMethods()Promise<string[]>Get all methods accessible to the current user
getSystemGroups()Promise<ISystemGroups>Get references to the five system group objects

Backend (AuthManager)​

MethodReturnsDescription
HasGroup($group, $user)Promise<boolean>Check group membership by group object/name and user object/ID
IsAuthorizedFor($method, $token)Promise<boolean>Check method access with caching
getAuthorizedMethods($token?)Promise<string[]>Collect all methods from all groups (with caching)
CreateGroup($name, $methods[])Promise<string>Create a new custom group
DeleteGroup($idOrName)Promise<void>Soft-delete a group (system groups protected)
AddGroup($group, $user)Promise<void>Add a user to a group
RemoveGroup($group, $user)Promise<void>Remove a user from a group
setAuthorizedMethods($group, $methods[], $append?)Promise<boolean>Set or append permissions on a group

Authorization Cache​

For performance, AuthManager caches authorized methods per token:

this.authCache[$token] = {
methods : [], // cached list of all authorized methods
validated : true // token validation status
};

The cache is automatically cleared (resetAuthCache()) whenever groups or permissions are modified. Frontend code can force a reset via AuthManagerConnector.ResetCache().

Permission Resolution Order​

When getAuthorizedMethods() is called, permissions are accumulated from multiple sources (in order):

  1. defaultAccessibleMethods — public methods accessible without authentication (login, registration, password reset)
  2. AppSettings.DefaultAccessibleMethods — additional public methods configured in Application Settings
  3. defaultGroupAccess — methods accessible to any authenticated user (profile, tokens)
  4. AppSettings.DefaultGroupAccess — additional authenticated methods from Application Settings
  5. user.DefaultGroup.AuthorizedMethods — the user's personal group permissions
  6. user.Groups[].AuthorizedMethods — permissions from all explicit group memberships

The final result is the union of all methods from all sources. Permissions are additive — there is no "deny" mechanism.

warning

Because permissions are purely additive, removing a method from one group does not prevent access if another group still grants it. To revoke a specific permission, ensure it is removed from all groups the user belongs to.

Managing Groups​

Creating Custom Groups​

// Backend — AuthManager (via @Extern)
const groupId : string = await authManager.CreateGroup("Reviewer", [
"Io.Oidis.MyProject.Utils.ReviewManager.*",
"#ReviewDashboard"
]);

Assigning Users to Groups​

// Add user to group
await authManager.AddGroup("Reviewer", userId);

// Remove user from group
await authManager.RemoveGroup("Reviewer", userId);

Updating Permissions​

// Replace all permissions
await authManager.setAuthorizedMethods("Reviewer", [
"Io.Oidis.MyProject.Utils.ReviewManager.*",
"#ReviewDashboard",
"#ExportReports"
]);

// Append without removing existing
await authManager.setAuthorizedMethods("Reviewer", ["#ExportReports"], true);

Extending System Groups​

Projects that need custom system-level roles can extend the authorization system at every layer.

Step 1: Extend AuthManager​

Add custom system permissions in your AuthManager subclass:

// YourProject/Utils/AuthManager.ts
export class AuthManager extends Parent {
constructor() {
super();
this.addSystemPermissions("Reviewer", "#ReviewDashboard",
...this.generateAuthMethodsFrom<this>(this, (proto) => [
proto.getReviewQueue,
proto.SubmitReview
])
);
}
}

Step 2: Extend ISystemGroups​

// YourProject/Gui/Connectors/AuthManagerConnector.ts
import { ISystemGroups as Parent } from "@io-oidis-hub-gui/.../Connectors/AuthManagerConnector.js";
import { Group } from "../Models/Group.js";

export interface ISystemGroups extends Parent {
Reviewer : Group;
}

Step 3: Use in UserContext​

Check the custom group in your UserContext extension:

export class UserContext extends Parent {
public IsReviewer : boolean;

public async Load() : Promise<void> {
if (ObjectValidator.IsEmptyOrNull(this.getUserProfile())) {
await super.Load();
this.IsReviewer = false;
if (this.IsAuthenticated) {
this.IsReviewer = await this.getAuth().HasGroup(this.getGroups().Reviewer);
}
}
}
}

Quick Reference​

FilePackagePurpose
AuthManager.tsio-oidis-hubSystem permissions, addSystemPermissions(), AuthFeatures
GroupsManager.tsio-oidis-hubGroup CRUD (CreateGroup, AddGroup, HasGroup)
AuthMethodsManager.tsio-oidis-hubgetAuthorizedMethods, setAuthorizedMethods, IsAuthorizedFor
Group.ts (BE)io-oidis-hubBackend @Entity("group") model
Group.ts (FE)io-oidis-hub-guiFrontend Group model
AuthManagerConnector.tsio-oidis-hub-guiISystemGroups, frontend authorization API
User.ts (BE)io-oidis-hubGroups[] and DefaultGroup properties

See also: UserContext | Authentication | User Profile | @Extern