MMOframework
Why AI Playground Libraries Documentation Search Agency
FR EN
Index

Scopes

MMO.oauth · 1 symbol · generated 27 Oct 2026

class

Scopes

scopes.js:13

How mmo.oauth decides a scope, in the browser: the same rules as the server's (src/functions.php, classes/tWithRoles.php), so the scopes' editor shows what a rule does before it is saved, and why.

class MMO.oauth.Scopes
Namespace
MMO.oauth
A scope is a path of names separated by dots (`pages.settings.roles`), with an action after a `#` (`data#edit`). To decide `a.b#edit`, the first rule set among `a.b#edit`, `a.b`, `a#edit`, `a` gives the answer, nothing set denies: a rule without an action decides every action below it.
MMO.oauth.Scopes.resolve({'data': true, 'data#edit': false}, 'data.main.orders#edit').value; // false

Index

split(uid) static
ancestors(path) static
candidates(uid) static
resolve(scopes, uid) staticDecide a scope, as \MMO\oauth\isAllowed does
isAllowed(scopes, uid) static
merge(source, scopes, set) staticMerge scopes into source, as \MMO\oauth\mergeScopes does: a deny is always set, an allow only where source does not already deny it (unless set)
combine(roles, own) staticThe scopes of a client: its roles merged in their order, then its own rules, which win (tWithRoles::getScopes)
context(roles, own) staticThe rules that apply to a role or a client, ready to explain many scopes (explain)
explain(context, uid) staticWhy uid is allowed or denied
index(catalog) staticWhat the application knows of the scopes: built once from the catalog (user/scopes/catalog.json)
actionsOf(index, path) staticThe actions checked under path: its own and its parents'
check(index, uid) staticCheck a rule's name
traps(own, scopes) staticThe rules allowing a path without an action under a rule denying an action: the trap of the order of resolution, a.b is looked for before a#edit, so allowing a.b gives back the edit a#edit …

Methods13

split(uid)

staticscopes.js:19
static split(uid)
uidstring
returns!Array<?string>[the path, the action or null]
Scopes.split('data.main#edit'); // ['data.main', 'edit']

ancestors(path)

staticscopes.js:28
static ancestors(path)
pathstringwithout its action
returns!Array<string>path and its parents, the closest first: a.b.c => [a.b.c, a.b, a]

candidates(uid)

staticscopes.js:38
static candidates(uid)
uidstringthe scope asked
returns!Array<string>the rules looked for, in order: a.b#edit, a.b, a#edit, a

resolve(scopes, uid)

staticscopes.js:54

Decide a scope, as \MMO\oauth\isAllowed does

static resolve(scopes, uid)
scopes!Object<string, boolean>rule => allowed
uidstring
returnsvalue: ?boolean, rule: ?string, tried: !Array<string>} value null when no rule is set (denied); rule the one that decided; tried the rules looked for until then

isAllowed(scopes, uid)

staticscopes.js:66
static isAllowed(scopes, uid)
scopes
uid
returnsbooleantrue if every scope of uid (space-separated) is allowed

merge(source, scopes, set)

staticscopes.js:78

Merge scopes into source, as \MMO\oauth\mergeScopes does: a deny is always set, an allow only where source does not already deny it (unless set)

static merge(source, scopes, set = false)
source!Object<string, boolean>changed
scopes!Object<string, boolean>
set = falsebooleantrue to set every rule as it is
returns!Object<string, boolean>source

combine(roles, own)

staticscopes.js:96

The scopes of a client: its roles merged in their order, then its own rules, which win (tWithRoles::getScopes)

static combine(roles, own = {})
roles!Array<!Object<string, boolean>>the scopes of each role, in order
own = {}!Object<string, boolean>
returns!Object<string, boolean>

context(roles, own)

staticscopes.js:108

The rules that apply to a role or a client, ready to explain many scopes (explain)

static context(roles, own)
roles
own!Object<string, boolean>the rules set on the role or the client edited
returnsroles: !Array, own: !Object<string, boolean>, scopes: !Object<string, boolean>, reversed: ?Object<string, boolean>}

explain(context, uid)

staticscopes.js:126

Why uid is allowed or denied

static explain(context, uid)
context
uidstring
returnsvalue: boolean, rule: ?string, tried: !Array<string>, source: string, roles: !Array<string>, orderDependent: boolean} source own (a rule set here), roles (set by roles, their labels), combined (a rule the merge of the roles made: a deny of one role pushed onto a rule of another) or none (nothing set: denied)
Scopes.explain(Scopes.context([admin, user], {}), 'pages.settings').roles; // ['user']

index(catalog)

staticscopes.js:147

What the application knows of the scopes: built once from the catalog (user/scopes/catalog.json)

static index(catalog)
catalog
returnspaths: !Set<string>, actions: !Object<string, !Object<string, string>>, open: !Array<string>}

actionsOf(index, path)

staticscopes.js:166

The actions checked under path: its own and its parents'

static actionsOf(index, path)
index
path
returns!Object<string, string>action => text

check(index, uid)

staticscopes.js:180

Check a rule's name

static check(index, uid)
index
uidstring
returns?{code: string, path: string, action: ?string} null when the application knows it: malformed, unknown (no part of the application declares this name) or action (nothing checks this action here)
Scopes.check(index, 'data.main.orders#update'); // {code: 'action', ...}: only show/create/edit/delete/status...

traps(own, scopes)

staticscopes.js:201

The rules allowing a path without an action under a rule denying an action: the trap of the order of resolution, a.b is looked for before a#edit, so allowing a.b gives back the edit a#edit refuses

static traps(own, scopes)
own!Object<string, boolean>the rules set here
scopes!Object<string, boolean>all the rules that apply (own, or combined with the roles)
returns!Array<{rule: string, denied: string, action: string, fix: string>} fix: the rule to deny to keep the action denied
Scopes.traps({'data.main.orders': true}, {'data#edit': false, 'data.main.orders': true}); // [{rule: 'data.main.orders', denied: 'data#edit', action: 'edit', fix: 'data.main.orders#edit'}]