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):
| Group | Permissions | Purpose |
|---|---|---|
| Admin | * (all methods) | Full administrative access — manages users, groups, settings, and all application features |
| SysAdmin | #SysAdmin | System 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 methods | Manages own organization — can edit members, view organization profile |
| Member | #OrganizationMember | Basic authenticated user — can access own profile and organization data |
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;
}
| Property | Type | Description |
|---|---|---|
Name | string | Display name (unique) |
AuthorizedMethods | string[] | Array of method patterns this group can access |
Hidden | boolean | Excluded from UI group listings (default: false) |
Readonly | boolean | System-protected — cannot be deleted (inherited from BaseModel) |
Deleted | boolean | Soft-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:
| Pattern | Matches | Example |
|---|---|---|
* | All methods | Admin, Agent groups |
Namespace.* | All methods in a namespace | Io.Oidis.Hub.Utils.* |
Namespace.Method | A specific method | Io.Oidis.Hub.Utils.AuthManager.getUserProfile |
#Feature | Feature 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";
}
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)
| Method | Returns | Description |
|---|---|---|
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)
| Method | Returns | Description |
|---|---|---|
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):
defaultAccessibleMethods— public methods accessible without authentication (login, registration, password reset)AppSettings.DefaultAccessibleMethods— additional public methods configured in Application SettingsdefaultGroupAccess— methods accessible to any authenticated user (profile, tokens)AppSettings.DefaultGroupAccess— additional authenticated methods from Application Settingsuser.DefaultGroup.AuthorizedMethods— the user's personal group permissionsuser.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.
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
| File | Package | Purpose |
|---|---|---|
AuthManager.ts | io-oidis-hub | System permissions, addSystemPermissions(), AuthFeatures |
GroupsManager.ts | io-oidis-hub | Group CRUD (CreateGroup, AddGroup, HasGroup) |
AuthMethodsManager.ts | io-oidis-hub | getAuthorizedMethods, setAuthorizedMethods, IsAuthorizedFor |
Group.ts (BE) | io-oidis-hub | Backend @Entity("group") model |
Group.ts (FE) | io-oidis-hub-gui | Frontend Group model |
AuthManagerConnector.ts | io-oidis-hub-gui | ISystemGroups, frontend authorization API |
User.ts (BE) | io-oidis-hub | Groups[] and DefaultGroup properties |
See also: UserContext | Authentication | User Profile | @Extern