@pcg/auth 1.0.0-alpha.0 → 1.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ var ActionScopes = class {
8
8
  anyScope = false;
9
9
  idsMap = /* @__PURE__ */ new Map();
10
10
  constructor(scopes) {
11
- if (scopes) this.array = scopes ?? [];
11
+ if (scopes) this.array = scopes;
12
12
  }
13
13
  /**
14
14
  * Set id scope (e.g. 'id#jsu:123')
@@ -87,7 +87,8 @@ var ActionScopes = class {
87
87
  * [org#hci, [org#hci, review]]
88
88
  **/
89
89
  resolveScopeId(scope) {
90
- return this.idsMap.has(scope) ? `${scope}#${this.idsMap.get(scope)}` : scope;
90
+ const id = this.idsMap.get(scope);
91
+ return id !== void 0 ? `${scope}#${id}` : scope;
91
92
  }
92
93
  };
93
94
 
@@ -121,10 +122,10 @@ const normalizeScopes = (scopes) => {
121
122
  */
122
123
  const isGranted = (user, permission, actionScopes = []) => {
123
124
  const [service, module, resource, action] = permission.split(":");
124
- if (!user?.resolvedPermissions || user.resolvedPermissions.length === 0) return false;
125
+ if (user.resolvedPermissions.length === 0) return false;
125
126
  const userPermissions = user.resolvedPermissions;
126
127
  const scopes = normalizeScopes(actionScopes);
127
- const permissionCanBeExecutedInScopes = (perm, scopes$1) => !perm.scopes || perm.scopes.length === 0 || scopes$1.canBeExecutedInScopes(perm.scopes);
128
+ const permissionCanBeExecutedInScopes = (perm, scopes$1) => perm.scopes.length === 0 || scopes$1.canBeExecutedInScopes(perm.scopes);
128
129
  const regexp = /* @__PURE__ */ new RegExp(`^(${service}|\\*):(${module}|\\*):(${resource}|\\*):(${action}|\\*)$`);
129
130
  if (userPermissions.some((perm) => regexp.test(perm.id) && permissionCanBeExecutedInScopes(perm, scopes))) return true;
130
131
  return false;
@@ -143,7 +144,7 @@ const encodeScopes = (scopes) => {
143
144
  * // js:core:episodes[org,published]:get
144
145
  */
145
146
  const injectScopesIntoPermission = (permission, scopesToInject) => {
146
- const { id, scopes = [] } = resolvePermission(permission);
147
+ const { id, scopes } = resolvePermission(permission);
147
148
  const [service, module, resource, action] = id.split(":");
148
149
  concatScopes(scopes, scopesToInject);
149
150
  return `${service}:${module}:${resource}${encodeScopes(scopes)}:${action}`;
@@ -172,7 +173,7 @@ const concatScopes = (set, items) => {
172
173
  const resolvePermission = (permission) => {
173
174
  const matches = /\[(?<scopes>.*?)\]/u.exec(permission);
174
175
  if (matches?.[0] && matches.groups?.scopes) {
175
- const scopes = String(matches.groups.scopes).split(",").map((s) => {
176
+ const scopes = matches.groups.scopes.split(",").map((s) => {
176
177
  if (s.includes("+")) return s.split("+");
177
178
  return s;
178
179
  });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["scopes","scopes: (string | string[])[]","resolvedPermissions: ResolvedPermission[]","result: ResolvedPermission[]","result: (string | string[])[]","scopes: (string | string[])[]","result: (string | string[])[]"],"sources":["../src/permissions/action-scopes.ts","../src/permissions/helpers.ts","../src/permissions/scopes-bulder.ts"],"sourcesContent":["export type ActionScopesArray = (string | string[])[];\n\n/**\n * Action scopes are used to describe\n * what scopes are required to execute an action.\n */\nexport class ActionScopes {\n public array: ActionScopesArray = [];\n public anyScope = false;\n private idsMap = new Map<string, string>();\n\n constructor(scopes?: ActionScopesArray) {\n if (scopes) {\n this.array = scopes ?? [];\n }\n }\n\n /**\n * Set id scope (e.g. 'id#jsu:123')\n * @param id - entity id\n * @returns - this\n * @example\n * scopes.setEntityId('jsu:123')\n *\n * // scopes.getArray() = ['id#jsu:123']\n */\n setEntityId(id: string): this {\n this.set(`id#${id}`);\n\n return this;\n }\n\n /**\n * Set scope (e.g. 'org', 'org+review')\n * @param scope - scope name (e.g. 'org', 'org+review')\n * @param id - entity id (e.g. 'jsorg:hci')\n * @returns - this\n * @example\n * scopes.set('my')\n *\n * // scopes.getArray() = ['my']\n *\n * scopes.set('org', 'jsorg:hci')\n *\n * // scopes.getArray() = ['org#jsorg:hci']\n */\n set(scope: string, id?: string): this {\n if (id && /^[A-Za-z_]+$/.test(scope)) {\n this.idsMap.set(scope, id);\n }\n\n if (scope.includes('+')) {\n const subscopes = scope.split('+');\n this.array.push(\n subscopes.map((subscope) => this.resolveScopeId(subscope)),\n );\n } else {\n this.array.push(this.resolveScopeId(scope));\n }\n\n return this;\n }\n\n has(scope: string): boolean {\n return this.array.includes(scope);\n }\n\n getArray(): ActionScopesArray {\n return [...this.array];\n }\n\n /**\n * Permission scopes must cover auth scopes determined in auth context.\n * @param permissionScopes\n */\n canBeExecutedInScopes(\n permissionScopes: readonly (string | string[])[],\n ): boolean {\n // if scopes has * then it can be executed in any scope\n if (this.array.includes('*')) {\n return true;\n }\n\n /* action scopes = ['user#jsu:123', 'org#hci', ['org#hci', 'draft']]\n /* permission scopes: ['user#jsu:123', ['org#hci', 'draft']]\n\n /**\n * empty user permission scopes = full access to action\n */\n if (permissionScopes.length === 0) {\n return true;\n }\n\n /**\n * Empty auth scopes but user has permission scopes = no access to action\n *\n * action scopes = []\n * permission scopes = ['org#hci']\n *\n * Example:\n * User try to get list of all entities\n * But user only has permission to entity from org#hci\n */\n if (this.array.length === 0) {\n return false;\n }\n\n return this.array.some((authScope) => {\n if (Array.isArray(authScope)) {\n // scopes ['org#hci', 'draft'] == permissionScope ['org#hci', 'draft']\n\n return permissionScopes.some((permissionScope) => {\n if (Array.isArray(permissionScope)) {\n return permissionScope.every(\n (sub) => authScope.includes(sub),\n );\n }\n\n return authScope.includes(permissionScope);\n });\n }\n\n return permissionScopes.includes(authScope); // 'user#jsu:123' == 'user#jsu:123'\n });\n }\n\n /**\n * Resolve presaved scopes\n * @example\n * authCtx.setScope('org', 'jsorg:hci')\n * [org#hci]\n *\n * authCtx.setScope('org+review');\n * [org#hci, [org#hci, review]]\n **/\n private resolveScopeId(scope: string) {\n return this.idsMap.has(scope)\n ? `${scope}#${this.idsMap.get(scope)}`\n : scope;\n }\n}\n","import {\n ReadonlyResolvedPermission, ResolvedPermission, ResolvedPermissionGroup,\n} from '../types/permissions.js';\nimport { IUser } from '../types/user.js';\nimport { ActionScopes, ActionScopesArray } from './action-scopes.js';\n\n/**\n * Normilize scopes to ActionScopes\n * @example\n * const scopes = normalizeScopes(['org#hci', 'user#hcu:xxxxx') // ActionScopes\n *\n * scopes.set('my')\n */\nconst normalizeScopes = (\n scopes: ActionScopesArray | ActionScopes,\n): ActionScopes => {\n if (!(scopes instanceof ActionScopes)) {\n return new ActionScopes(scopes);\n }\n\n return scopes as ActionScopes;\n};\n\n/**\n * Check if user has access to permission\n * @example\n *\n * // Check episode access\n * const scopes = new ActionScopes();\n * scopes.setEntityId(episode.id); // id#js:core:episodes:xxxxx\n * scopes.set('org', episode.organizationId); // org#hcorg:hci\n *\n * isGranted(user, 'js:core:episodes:get', scopes)\n * @param user - user object with resolvedPermissions\n * @param permission - permission string (e.g. 'js:core:episodes:get')\n * @param actionScopes - action scopes (e.g. ['org#hcorg:hci'] or ActionScopes instance)\n * @returns - true if user has access to permission\n */\nexport const isGranted = (\n user: IUser,\n permission: string,\n actionScopes: ActionScopesArray | ActionScopes = [],\n): boolean => {\n const [service, module, resource, action] = permission.split(':');\n\n if (!user?.resolvedPermissions || user.resolvedPermissions.length === 0) {\n return false;\n }\n\n const userPermissions = user.resolvedPermissions;\n\n const scopes = normalizeScopes(actionScopes);\n\n const permissionCanBeExecutedInScopes = (perm: ReadonlyResolvedPermission, scopes: ActionScopes) =>\n (!perm.scopes || perm.scopes.length === 0 || scopes.canBeExecutedInScopes(perm.scopes));\n\n const regexp = new RegExp(`^(${service}|\\\\*):(${module}|\\\\*):(${resource}|\\\\*):(${action}|\\\\*)$`);\n\n // novajs:*:*:* or novajs:*:*[org]:*\n // novajs:module:*:* or novajs:module:*[org]:*\n // novajs:module:resource:* or novajs:module:resource:[org]:*\n // novajs:modules:resource:create\n // *:modules:resource:create\n if (userPermissions.some(\n (perm) => regexp.test(perm.id) &&\n permissionCanBeExecutedInScopes(perm, scopes))\n ) {\n return true;\n }\n\n return false;\n};\n\nexport const encodeScopes = (scopes: (string | string[])[]): string => {\n const scopesString = scopes\n .map((s) => (Array.isArray(s) ? s.join('+') : s))\n .join(',');\n\n return scopesString.length > 0 ? `[${scopesString}]` : '';\n};\n\n/**\n * Inject scopes into permission\n * @param permission - permission string (e.g. 'js:core:episodes:get')\n * @param scopesToInject - scopes to inject (e.g. ['org', 'published'])\n * @returns - permission string with injected scopes (e.g. 'js:core:episodes[org,published]:get')\n * @example\n * injectScopesIntoPermission('js:core:episodes:get', ['org', 'published'])\n * // js:core:episodes[org,published]:get\n */\nexport const injectScopesIntoPermission = (\n permission: string,\n scopesToInject: (string | string[])[],\n) => {\n const { id, scopes = [] } = resolvePermission(permission);\n\n const [service, module, resource, action] = id.split(':');\n\n concatScopes(scopes, scopesToInject);\n\n return `${service}:${module}:${resource}${encodeScopes(scopes)}:${action}`;\n};\n\n/**\n * Concat scopes\n * @param set - set of scopes\n * @param items - items to concat\n * @example\n * concatScopes(['org#hci'], ['org#dv'])\n * // ['org#hci', 'org#dv']\n */\nexport const concatScopes = (\n set: (string | string[])[],\n items: (string | string[])[],\n): void => {\n for (const item of items) {\n if (Array.isArray(item)) {\n // check if scopes not includes array with subscopes rp2Scope\n if (\n !set.some(\n (scope) =>\n Array.isArray(scope) &&\n scope.length === item.length &&\n scope.every((s) => item.includes(s)),\n )\n ) {\n set.push(item);\n }\n } else if (!set.includes(item)) {\n set.push(item);\n }\n }\n};\n\n/**\n * Resolve permission string to object with scopes\n * @param permission - permission string\n * @returns - resolved permission object\n * @example\n * resolvePermission('js:core:episodes[org,published]:get')\n * // { id: 'js:core:episodes:get', scopes: ['org', 'published'] }\n */\nexport const resolvePermission = (permission: string): ResolvedPermission => {\n const matches = /\\[(?<scopes>.*?)\\]/u.exec(permission);\n\n if (matches?.[0] && matches.groups?.scopes) {\n const scopes: (string | string[])[] = String(matches.groups.scopes)\n .split(',') // scopes with OR logic\n .map((s) => {\n if (s.includes('+')) {\n return s.split('+'); // scopes with AND logic\n }\n\n return s;\n });\n\n return {\n id: permission.replace(matches[0], ''),\n scopes,\n };\n }\n\n return {\n id: permission,\n scopes: [],\n };\n};\n\n/**\n * Resolve permissions to array of resolved permissions\n * @param permissions - array of permissions\n * @returns - array of resolved permissions\n * @example\n * resolvePermissions(['js:core:episodes[org]:get', 'js:core:episodes[published]:get'])\n * // [{ id: 'js:core:episodes:get', scopes: ['org', 'published'] }]\n */\nexport const resolvePermissions = (\n permissions: string[],\n): ResolvedPermission[] => {\n const resolvedPermissions: ResolvedPermission[] = [];\n\n for (const permission of permissions) {\n const { id, scopes } = resolvePermission(permission);\n\n const currentPermission = resolvedPermissions.find(\n (p) => p.id === id,\n );\n\n if (currentPermission) {\n concatScopes(currentPermission.scopes, scopes);\n } else {\n resolvedPermissions.push({\n id,\n scopes,\n });\n }\n }\n\n return resolvedPermissions;\n};\n\n/**\n * Resolve permission group string to object with scopes\n * @param permissionGroup - permission group string\n * @returns - resolved permission group object\n * @example\n * resolvePermissionGroup('g:js:core:episodes[org]:read')\n * // { id: 'g:js:core:episodes:read', scopes: ['org'] }\n */\nexport const resolvePermissionGroup = (permissionGroup: string) => {\n // Currently it has the same algorithm\n return resolvePermission(permissionGroup);\n};\n\n/**\n * Resolve permission groups to array of resolved permission groups\n * @param permissionGroups - array of permission groups\n * @returns - array of resolved permission groups\n * @example\n * resolvePermissionGroups(['g:js:core:episodes[org]:read', 'g:js:core:episodes[published]:read'])\n * // [{ id: 'g:js:core:episodes:read', scopes: ['org', 'published'] }]\n */\nexport const resolvePermissionGroups = (\n permissionGroups: string[],\n): ResolvedPermissionGroup[] => {\n // Currently it has the same algorithm\n return resolvePermissions(permissionGroups);\n};\n\nexport const mergeResolvedPermissions = (\n array1: ResolvedPermission[],\n array2: ResolvedPermission[],\n) => {\n const result: ResolvedPermission[] = [];\n\n for (const rp1 of array1) {\n const rps2 = array2.filter((rp2) => rp2.id === rp1.id);\n\n /* Empty scopes = has full access */\n if (\n rp1.scopes.length === 0 ||\n rps2.some((rp2) => rp2.id === rp1.id && rp2.scopes.length === 0)\n ) {\n result.push({\n id: rp1.id,\n scopes: [],\n });\n\n continue;\n }\n\n // [org#hci, user#hcu:xxxxx, [org#hci, review]]\n const scopes = [...rp1.scopes];\n for (const rp2 of rps2) {\n concatScopes(scopes, rp2.scopes);\n }\n\n result.push({\n id: rp1.id,\n scopes,\n });\n }\n\n for (const rp2 of array2) {\n if (!result.some((rp) => rp.id === rp2.id)) {\n result.push(rp2);\n }\n }\n\n return result;\n};\n\n/**\n * Replace scope in array of scopes\n * @param scopes - array of scopes\n * @param from - scope to replace\n * @param to - scope to replace with\n * @returns - array of scopes with replaced scopes\n * @example\n * replaceScope(['org#jsorg:xxx', ['org#jsorg:xxx', 'published']], 'org#jsorg:xxx', 'org#jsorg:hci')\n * // ['org#jsorg:hci', ['org#jsorg:hci', 'published']]\n */\nexport const replaceScope = (\n scopes: (string | string[])[], // [org#jsorg:xxx, [org#jsorg:xxx, published]]\n from: string,\n to: string | (string | string[])[],\n) => {\n const result: (string | string[])[] = [];\n for (const scope of scopes) {\n if (Array.isArray(scope)) {\n result.push(replaceScope(scope, from, to) as string[]);\n } else if (scope === from) {\n if (Array.isArray(to)) {\n result.push(...to);\n } else {\n result.push(to);\n }\n } else {\n result.push(scope);\n }\n }\n\n return result;\n};\n","import { concatScopes } from './helpers.js';\n\nexport class ScopesBulder {\n constructor(private scopes: (string | string[])[] = []) {}\n\n static fromBulder(builder: ScopesBulder) {\n return new ScopesBulder(builder.scopes.slice());\n }\n\n build() {\n return JSON.parse(JSON.stringify(this.scopes)) as (string | string[])[];\n }\n\n clone() {\n return ScopesBulder.fromBulder(this);\n }\n\n /**\n * Append scope or scopes to the current scopes\n * @param scope - scope or array of scopes to append\n * @example\n * append('org#jsorg:hci')\n * // ['org#xxx'] => ['org#xxx', 'org#jsorg:hci']\n * append(['org#hci', 'lang#en])\n * // ['org#xxx'] => ['org#xxx', ['org#hci', 'lang#en']]\n */\n append(scope: string | string[]) {\n this.scopes.push(scope);\n }\n\n /**\n * Extend current scopes with the given scopes\n * @param scopes - scope or array of scopes to extend with\n * @example\n * extend(['lang#en', 'lang#de'])\n * // ['published', 'draft'] => ['published', 'draft', 'lang#en', 'lang#de']\n */\n extend(scopes: (string | string[])[] | ScopesBulder) {\n if (Array.isArray(scopes)) {\n concatScopes(this.scopes, scopes);\n } else if (scopes instanceof ScopesBulder) {\n concatScopes(this.scopes, scopes.scopes);\n }\n }\n\n /**\n * Join all scopes with the given scope\n * @param scope - scope to join with\n * @example\n * join('org#jsorg:hci', 'before')\n * // ['published', 'draft'] => [['org#jsorg:hci', 'published'], ['org#jsorg:hci', 'draft']]\n * join(['lang#en', 'lang#de'])\n * // ['published', 'draft'] => [['published', 'lang#en'], ['published', 'lang#de'], ['draft', 'lang#en'], ['draft', 'lang#de']]\n */\n join(scopeOrScopes: string | (string | string[])[], pos: 'before' | 'after' = 'after') {\n const result: (string | string[])[] = [];\n\n const scopesToJoin = Array.isArray(scopeOrScopes) ? scopeOrScopes : [scopeOrScopes];\n\n if (scopesToJoin.length === 0) {\n return;\n }\n\n const currentScopesCopy = this.build();\n for (const scope of scopesToJoin) {\n const scopeToJoin = Array.isArray(scope) ? scope : [scope];\n\n for (const s of currentScopesCopy) {\n if (Array.isArray(s)) {\n result.push(pos === 'before' ? [...scopeToJoin, ...s] : [...s, ...scopeToJoin]);\n } else {\n result.push(pos === 'before' ? [...scopeToJoin, s] : [s, ...scopeToJoin]);\n }\n }\n }\n\n this.scopes = result;\n }\n\n /**\n * Replace prefix in all scopes\n * @param from - prefix to replace\n * @param to - prefix to replace with\n * @example\n * replacePrefix('org', 'id')\n * ['org#jsorg:hci'] => ['id#jsorg:hci']\n */\n replacePrefix(from: string, to: string) {\n const result: (string | string[])[] = [];\n for (const scope of this.scopes) {\n if (Array.isArray(scope)) {\n result.push(scope.map((s) => s.replace(`${from}#`, `${to}#`)));\n } else {\n result.push(scope.replace(`${from}#`, `${to}#`));\n }\n }\n this.scopes = result;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["scopes","scopes: (string | string[])[]","resolvedPermissions: ResolvedPermission[]","result: ResolvedPermission[]","result: (string | string[])[]","scopes: (string | string[])[]","result: (string | string[])[]"],"sources":["../src/permissions/action-scopes.ts","../src/permissions/helpers.ts","../src/permissions/scopes-bulder.ts"],"sourcesContent":["export type ActionScopesArray = (string | string[])[];\n\n/**\n * Action scopes are used to describe\n * what scopes are required to execute an action.\n */\nexport class ActionScopes {\n public array: ActionScopesArray = [];\n public anyScope = false;\n private idsMap = new Map<string, string>();\n\n constructor(scopes?: ActionScopesArray) {\n if (scopes) {\n this.array = scopes;\n }\n }\n\n /**\n * Set id scope (e.g. 'id#jsu:123')\n * @param id - entity id\n * @returns - this\n * @example\n * scopes.setEntityId('jsu:123')\n *\n * // scopes.getArray() = ['id#jsu:123']\n */\n setEntityId(id: string): this {\n this.set(`id#${id}`);\n\n return this;\n }\n\n /**\n * Set scope (e.g. 'org', 'org+review')\n * @param scope - scope name (e.g. 'org', 'org+review')\n * @param id - entity id (e.g. 'jsorg:hci')\n * @returns - this\n * @example\n * scopes.set('my')\n *\n * // scopes.getArray() = ['my']\n *\n * scopes.set('org', 'jsorg:hci')\n *\n * // scopes.getArray() = ['org#jsorg:hci']\n */\n set(scope: string, id?: string): this {\n if (id && /^[A-Za-z_]+$/.test(scope)) {\n this.idsMap.set(scope, id);\n }\n\n if (scope.includes('+')) {\n const subscopes = scope.split('+');\n this.array.push(\n subscopes.map((subscope) => this.resolveScopeId(subscope)),\n );\n } else {\n this.array.push(this.resolveScopeId(scope));\n }\n\n return this;\n }\n\n has(scope: string): boolean {\n return this.array.includes(scope);\n }\n\n getArray(): ActionScopesArray {\n return [...this.array];\n }\n\n /**\n * Permission scopes must cover auth scopes determined in auth context.\n * @param permissionScopes\n */\n canBeExecutedInScopes(\n permissionScopes: readonly (string | string[])[],\n ): boolean {\n // if scopes has * then it can be executed in any scope\n if (this.array.includes('*')) {\n return true;\n }\n\n /* action scopes = ['user#jsu:123', 'org#hci', ['org#hci', 'draft']]\n /* permission scopes: ['user#jsu:123', ['org#hci', 'draft']]\n\n /**\n * empty user permission scopes = full access to action\n */\n if (permissionScopes.length === 0) {\n return true;\n }\n\n /**\n * Empty auth scopes but user has permission scopes = no access to action\n *\n * action scopes = []\n * permission scopes = ['org#hci']\n *\n * Example:\n * User try to get list of all entities\n * But user only has permission to entity from org#hci\n */\n if (this.array.length === 0) {\n return false;\n }\n\n return this.array.some((authScope) => {\n if (Array.isArray(authScope)) {\n // scopes ['org#hci', 'draft'] == permissionScope ['org#hci', 'draft']\n\n return permissionScopes.some((permissionScope) => {\n if (Array.isArray(permissionScope)) {\n return permissionScope.every(\n (sub) => authScope.includes(sub),\n );\n }\n\n return authScope.includes(permissionScope);\n });\n }\n\n return permissionScopes.includes(authScope); // 'user#jsu:123' == 'user#jsu:123'\n });\n }\n\n /**\n * Resolve presaved scopes\n * @example\n * authCtx.setScope('org', 'jsorg:hci')\n * [org#hci]\n *\n * authCtx.setScope('org+review');\n * [org#hci, [org#hci, review]]\n **/\n private resolveScopeId(scope: string) {\n const id = this.idsMap.get(scope);\n return id !== undefined\n ? `${scope}#${id}`\n : scope;\n }\n}\n","import {\n type ReadonlyResolvedPermission, type ResolvedPermission, type ResolvedPermissionGroup,\n} from '../types/permissions.js';\nimport { type IUser } from '../types/user.js';\nimport { ActionScopes, type ActionScopesArray } from './action-scopes.js';\n\n/**\n * Normilize scopes to ActionScopes\n * @example\n * const scopes = normalizeScopes(['org#hci', 'user#hcu:xxxxx') // ActionScopes\n *\n * scopes.set('my')\n */\nconst normalizeScopes = (\n scopes: ActionScopesArray | ActionScopes,\n): ActionScopes => {\n if (!(scopes instanceof ActionScopes)) {\n return new ActionScopes(scopes);\n }\n\n return scopes;\n};\n\n/**\n * Check if user has access to permission\n * @example\n *\n * // Check episode access\n * const scopes = new ActionScopes();\n * scopes.setEntityId(episode.id); // id#js:core:episodes:xxxxx\n * scopes.set('org', episode.organizationId); // org#hcorg:hci\n *\n * isGranted(user, 'js:core:episodes:get', scopes)\n * @param user - user object with resolvedPermissions\n * @param permission - permission string (e.g. 'js:core:episodes:get')\n * @param actionScopes - action scopes (e.g. ['org#hcorg:hci'] or ActionScopes instance)\n * @returns - true if user has access to permission\n */\nexport const isGranted = (\n user: IUser,\n permission: string,\n actionScopes: ActionScopesArray | ActionScopes = [],\n): boolean => {\n const [service, module, resource, action] = permission.split(':');\n\n if (user.resolvedPermissions.length === 0) {\n return false;\n }\n\n const userPermissions = user.resolvedPermissions;\n\n const scopes = normalizeScopes(actionScopes);\n\n const permissionCanBeExecutedInScopes = (perm: ReadonlyResolvedPermission, scopes: ActionScopes) =>\n (perm.scopes.length === 0 || scopes.canBeExecutedInScopes(perm.scopes));\n\n const regexp = new RegExp(`^(${service}|\\\\*):(${module}|\\\\*):(${resource}|\\\\*):(${action}|\\\\*)$`);\n\n // novajs:*:*:* or novajs:*:*[org]:*\n // novajs:module:*:* or novajs:module:*[org]:*\n // novajs:module:resource:* or novajs:module:resource:[org]:*\n // novajs:modules:resource:create\n // *:modules:resource:create\n if (userPermissions.some(\n (perm) => regexp.test(perm.id)\n && permissionCanBeExecutedInScopes(perm, scopes))\n ) {\n return true;\n }\n\n return false;\n};\n\nexport const encodeScopes = (scopes: (string | string[])[]): string => {\n const scopesString = scopes\n .map((s) => (Array.isArray(s) ? s.join('+') : s))\n .join(',');\n\n return scopesString.length > 0 ? `[${scopesString}]` : '';\n};\n\n/**\n * Inject scopes into permission\n * @param permission - permission string (e.g. 'js:core:episodes:get')\n * @param scopesToInject - scopes to inject (e.g. ['org', 'published'])\n * @returns - permission string with injected scopes (e.g. 'js:core:episodes[org,published]:get')\n * @example\n * injectScopesIntoPermission('js:core:episodes:get', ['org', 'published'])\n * // js:core:episodes[org,published]:get\n */\nexport const injectScopesIntoPermission = (\n permission: string,\n scopesToInject: (string | string[])[],\n) => {\n const { id, scopes } = resolvePermission(permission);\n\n const [service, module, resource, action] = id.split(':');\n\n concatScopes(scopes, scopesToInject);\n\n return `${service}:${module}:${resource}${encodeScopes(scopes)}:${action}`;\n};\n\n/**\n * Concat scopes\n * @param set - set of scopes\n * @param items - items to concat\n * @example\n * concatScopes(['org#hci'], ['org#dv'])\n * // ['org#hci', 'org#dv']\n */\nexport const concatScopes = (\n set: (string | string[])[],\n items: (string | string[])[],\n): void => {\n for (const item of items) {\n if (Array.isArray(item)) {\n // check if scopes not includes array with subscopes rp2Scope\n if (\n !set.some(\n (scope) =>\n Array.isArray(scope)\n && scope.length === item.length\n && scope.every((s) => item.includes(s)),\n )\n ) {\n set.push(item);\n }\n } else if (!set.includes(item)) {\n set.push(item);\n }\n }\n};\n\n/**\n * Resolve permission string to object with scopes\n * @param permission - permission string\n * @returns - resolved permission object\n * @example\n * resolvePermission('js:core:episodes[org,published]:get')\n * // { id: 'js:core:episodes:get', scopes: ['org', 'published'] }\n */\nexport const resolvePermission = (permission: string): ResolvedPermission => {\n const matches = /\\[(?<scopes>.*?)\\]/u.exec(permission);\n\n if (matches?.[0] && matches.groups?.scopes) {\n const scopes: (string | string[])[] = matches.groups.scopes\n .split(',') // scopes with OR logic\n .map((s) => {\n if (s.includes('+')) {\n return s.split('+'); // scopes with AND logic\n }\n\n return s;\n });\n\n return {\n id: permission.replace(matches[0], ''),\n scopes,\n };\n }\n\n return {\n id: permission,\n scopes: [],\n };\n};\n\n/**\n * Resolve permissions to array of resolved permissions\n * @param permissions - array of permissions\n * @returns - array of resolved permissions\n * @example\n * resolvePermissions(['js:core:episodes[org]:get', 'js:core:episodes[published]:get'])\n * // [{ id: 'js:core:episodes:get', scopes: ['org', 'published'] }]\n */\nexport const resolvePermissions = (\n permissions: string[],\n): ResolvedPermission[] => {\n const resolvedPermissions: ResolvedPermission[] = [];\n\n for (const permission of permissions) {\n const { id, scopes } = resolvePermission(permission);\n\n const currentPermission = resolvedPermissions.find(\n (p) => p.id === id,\n );\n\n if (currentPermission) {\n concatScopes(currentPermission.scopes, scopes);\n } else {\n resolvedPermissions.push({\n id,\n scopes,\n });\n }\n }\n\n return resolvedPermissions;\n};\n\n/**\n * Resolve permission group string to object with scopes\n * @param permissionGroup - permission group string\n * @returns - resolved permission group object\n * @example\n * resolvePermissionGroup('g:js:core:episodes[org]:read')\n * // { id: 'g:js:core:episodes:read', scopes: ['org'] }\n */\nexport const resolvePermissionGroup = (permissionGroup: string) => {\n // Currently it has the same algorithm\n return resolvePermission(permissionGroup);\n};\n\n/**\n * Resolve permission groups to array of resolved permission groups\n * @param permissionGroups - array of permission groups\n * @returns - array of resolved permission groups\n * @example\n * resolvePermissionGroups(['g:js:core:episodes[org]:read', 'g:js:core:episodes[published]:read'])\n * // [{ id: 'g:js:core:episodes:read', scopes: ['org', 'published'] }]\n */\nexport const resolvePermissionGroups = (\n permissionGroups: string[],\n): ResolvedPermissionGroup[] => {\n // Currently it has the same algorithm\n return resolvePermissions(permissionGroups);\n};\n\nexport const mergeResolvedPermissions = (\n array1: ResolvedPermission[],\n array2: ResolvedPermission[],\n) => {\n const result: ResolvedPermission[] = [];\n\n for (const rp1 of array1) {\n const rps2 = array2.filter((rp2) => rp2.id === rp1.id);\n\n /* Empty scopes = has full access */\n if (\n rp1.scopes.length === 0\n || rps2.some((rp2) => rp2.id === rp1.id && rp2.scopes.length === 0)\n ) {\n result.push({\n id: rp1.id,\n scopes: [],\n });\n\n continue;\n }\n\n // [org#hci, user#hcu:xxxxx, [org#hci, review]]\n const scopes = [...rp1.scopes];\n for (const rp2 of rps2) {\n concatScopes(scopes, rp2.scopes);\n }\n\n result.push({\n id: rp1.id,\n scopes,\n });\n }\n\n for (const rp2 of array2) {\n if (!result.some((rp) => rp.id === rp2.id)) {\n result.push(rp2);\n }\n }\n\n return result;\n};\n\n/**\n * Replace scope in array of scopes\n * @param scopes - array of scopes\n * @param from - scope to replace\n * @param to - scope to replace with\n * @returns - array of scopes with replaced scopes\n * @example\n * replaceScope(['org#jsorg:xxx', ['org#jsorg:xxx', 'published']], 'org#jsorg:xxx', 'org#jsorg:hci')\n * // ['org#jsorg:hci', ['org#jsorg:hci', 'published']]\n */\nexport const replaceScope = (\n scopes: (string | string[])[], // [org#jsorg:xxx, [org#jsorg:xxx, published]]\n from: string,\n to: string | (string | string[])[],\n) => {\n const result: (string | string[])[] = [];\n for (const scope of scopes) {\n if (Array.isArray(scope)) {\n result.push(replaceScope(scope, from, to) as string[]);\n } else if (scope === from) {\n if (Array.isArray(to)) {\n result.push(...to);\n } else {\n result.push(to);\n }\n } else {\n result.push(scope);\n }\n }\n\n return result;\n};\n","import { concatScopes } from './helpers.js';\n\nexport class ScopesBulder {\n constructor(private scopes: (string | string[])[] = []) {}\n\n static fromBulder(builder: ScopesBulder) {\n return new ScopesBulder(builder.scopes.slice());\n }\n\n build() {\n return JSON.parse(JSON.stringify(this.scopes)) as (string | string[])[];\n }\n\n clone() {\n return ScopesBulder.fromBulder(this);\n }\n\n /**\n * Append scope or scopes to the current scopes\n * @param scope - scope or array of scopes to append\n * @example\n * append('org#jsorg:hci')\n * // ['org#xxx'] => ['org#xxx', 'org#jsorg:hci']\n * append(['org#hci', 'lang#en])\n * // ['org#xxx'] => ['org#xxx', ['org#hci', 'lang#en']]\n */\n append(scope: string | string[]) {\n this.scopes.push(scope);\n }\n\n /**\n * Extend current scopes with the given scopes\n * @param scopes - scope or array of scopes to extend with\n * @example\n * extend(['lang#en', 'lang#de'])\n * // ['published', 'draft'] => ['published', 'draft', 'lang#en', 'lang#de']\n */\n extend(scopes: (string | string[])[] | ScopesBulder) {\n if (Array.isArray(scopes)) {\n concatScopes(this.scopes, scopes);\n } else if (scopes instanceof ScopesBulder) {\n concatScopes(this.scopes, scopes.scopes);\n }\n }\n\n /**\n * Join all scopes with the given scope\n * @param scope - scope to join with\n * @example\n * join('org#jsorg:hci', 'before')\n * // ['published', 'draft'] => [['org#jsorg:hci', 'published'], ['org#jsorg:hci', 'draft']]\n * join(['lang#en', 'lang#de'])\n * // ['published', 'draft'] => [['published', 'lang#en'], ['published', 'lang#de'], ['draft', 'lang#en'], ['draft', 'lang#de']]\n */\n join(scopeOrScopes: string | (string | string[])[], pos: 'before' | 'after' = 'after') {\n const result: (string | string[])[] = [];\n\n const scopesToJoin = Array.isArray(scopeOrScopes) ? scopeOrScopes : [scopeOrScopes];\n\n if (scopesToJoin.length === 0) {\n return;\n }\n\n const currentScopesCopy = this.build();\n for (const scope of scopesToJoin) {\n const scopeToJoin = Array.isArray(scope) ? scope : [scope];\n\n for (const s of currentScopesCopy) {\n if (Array.isArray(s)) {\n result.push(pos === 'before' ? [...scopeToJoin, ...s] : [...s, ...scopeToJoin]);\n } else {\n result.push(pos === 'before' ? [...scopeToJoin, s] : [s, ...scopeToJoin]);\n }\n }\n }\n\n this.scopes = result;\n }\n\n /**\n * Replace prefix in all scopes\n * @param from - prefix to replace\n * @param to - prefix to replace with\n * @example\n * replacePrefix('org', 'id')\n * ['org#jsorg:hci'] => ['id#jsorg:hci']\n */\n replacePrefix(from: string, to: string) {\n const result: (string | string[])[] = [];\n for (const scope of this.scopes) {\n if (Array.isArray(scope)) {\n result.push(scope.map((s) => s.replace(`${from}#`, `${to}#`)));\n } else {\n result.push(scope.replace(`${from}#`, `${to}#`));\n }\n }\n this.scopes = result;\n }\n}\n"],"mappings":";;;;;AAMA,IAAa,eAAb,MAA0B;CACxB,AAAO,QAA2B,EAAE;CACpC,AAAO,WAAW;CAClB,AAAQ,yBAAS,IAAI,KAAqB;CAE1C,YAAY,QAA4B;AACtC,MAAI,OACF,MAAK,QAAQ;;;;;;;;;;;CAajB,YAAY,IAAkB;AAC5B,OAAK,IAAI,MAAM,KAAK;AAEpB,SAAO;;;;;;;;;;;;;;;;CAiBT,IAAI,OAAe,IAAmB;AACpC,MAAI,MAAM,eAAe,KAAK,MAAM,CAClC,MAAK,OAAO,IAAI,OAAO,GAAG;AAG5B,MAAI,MAAM,SAAS,IAAI,EAAE;GACvB,MAAM,YAAY,MAAM,MAAM,IAAI;AAClC,QAAK,MAAM,KACT,UAAU,KAAK,aAAa,KAAK,eAAe,SAAS,CAAC,CAC3D;QAED,MAAK,MAAM,KAAK,KAAK,eAAe,MAAM,CAAC;AAG7C,SAAO;;CAGT,IAAI,OAAwB;AAC1B,SAAO,KAAK,MAAM,SAAS,MAAM;;CAGnC,WAA8B;AAC5B,SAAO,CAAC,GAAG,KAAK,MAAM;;;;;;CAOxB,sBACE,kBACS;AAET,MAAI,KAAK,MAAM,SAAS,IAAI,CAC1B,QAAO;AAST,MAAI,iBAAiB,WAAW,EAC9B,QAAO;;;;;;;;;;;AAaT,MAAI,KAAK,MAAM,WAAW,EACxB,QAAO;AAGT,SAAO,KAAK,MAAM,MAAM,cAAc;AACpC,OAAI,MAAM,QAAQ,UAAU,CAG1B,QAAO,iBAAiB,MAAM,oBAAoB;AAChD,QAAI,MAAM,QAAQ,gBAAgB,CAChC,QAAO,gBAAgB,OACpB,QAAQ,UAAU,SAAS,IAAI,CACjC;AAGH,WAAO,UAAU,SAAS,gBAAgB;KAC1C;AAGJ,UAAO,iBAAiB,SAAS,UAAU;IAC3C;;;;;;;;;;;CAYJ,AAAQ,eAAe,OAAe;EACpC,MAAM,KAAK,KAAK,OAAO,IAAI,MAAM;AACjC,SAAO,OAAO,SACV,GAAG,MAAM,GAAG,OACZ;;;;;;;;;;;;;AC9HR,MAAM,mBACJ,WACiB;AACjB,KAAI,EAAE,kBAAkB,cACtB,QAAO,IAAI,aAAa,OAAO;AAGjC,QAAO;;;;;;;;;;;;;;;;;AAkBT,MAAa,aACX,MACA,YACA,eAAiD,EAAE,KACvC;CACZ,MAAM,CAAC,SAAS,QAAQ,UAAU,UAAU,WAAW,MAAM,IAAI;AAEjE,KAAI,KAAK,oBAAoB,WAAW,EACtC,QAAO;CAGT,MAAM,kBAAkB,KAAK;CAE7B,MAAM,SAAS,gBAAgB,aAAa;CAE5C,MAAM,mCAAmC,MAAkC,aACxE,KAAK,OAAO,WAAW,KAAKA,SAAO,sBAAsB,KAAK,OAAO;CAExE,MAAM,yBAAS,IAAI,OAAO,KAAK,QAAQ,SAAS,OAAO,SAAS,SAAS,SAAS,OAAO,QAAQ;AAOjG,KAAI,gBAAgB,MACjB,SAAS,OAAO,KAAK,KAAK,GAAG,IACzB,gCAAgC,MAAM,OAAO,CAAC,CAEnD,QAAO;AAGT,QAAO;;AAGT,MAAa,gBAAgB,WAA0C;CACrE,MAAM,eAAe,OAClB,KAAK,MAAO,MAAM,QAAQ,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,EAAG,CAChD,KAAK,IAAI;AAEZ,QAAO,aAAa,SAAS,IAAI,IAAI,aAAa,KAAK;;;;;;;;;;;AAYzD,MAAa,8BACX,YACA,mBACG;CACH,MAAM,EAAE,IAAI,WAAW,kBAAkB,WAAW;CAEpD,MAAM,CAAC,SAAS,QAAQ,UAAU,UAAU,GAAG,MAAM,IAAI;AAEzD,cAAa,QAAQ,eAAe;AAEpC,QAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,WAAW,aAAa,OAAO,CAAC,GAAG;;;;;;;;;;AAWpE,MAAa,gBACX,KACA,UACS;AACT,MAAK,MAAM,QAAQ,MACjB,KAAI,MAAM,QAAQ,KAAK,EAErB;MACE,CAAC,IAAI,MACF,UACC,MAAM,QAAQ,MAAM,IACjB,MAAM,WAAW,KAAK,UACtB,MAAM,OAAO,MAAM,KAAK,SAAS,EAAE,CAAC,CAC1C,CAED,KAAI,KAAK,KAAK;YAEP,CAAC,IAAI,SAAS,KAAK,CAC5B,KAAI,KAAK,KAAK;;;;;;;;;;AAapB,MAAa,qBAAqB,eAA2C;CAC3E,MAAM,UAAU,sBAAsB,KAAK,WAAW;AAEtD,KAAI,UAAU,MAAM,QAAQ,QAAQ,QAAQ;EAC1C,MAAMC,SAAgC,QAAQ,OAAO,OAClD,MAAM,IAAI,CACV,KAAK,MAAM;AACV,OAAI,EAAE,SAAS,IAAI,CACjB,QAAO,EAAE,MAAM,IAAI;AAGrB,UAAO;IACP;AAEJ,SAAO;GACL,IAAI,WAAW,QAAQ,QAAQ,IAAI,GAAG;GACtC;GACD;;AAGH,QAAO;EACL,IAAI;EACJ,QAAQ,EAAE;EACX;;;;;;;;;;AAWH,MAAa,sBACX,gBACyB;CACzB,MAAMC,sBAA4C,EAAE;AAEpD,MAAK,MAAM,cAAc,aAAa;EACpC,MAAM,EAAE,IAAI,WAAW,kBAAkB,WAAW;EAEpD,MAAM,oBAAoB,oBAAoB,MAC3C,MAAM,EAAE,OAAO,GACjB;AAED,MAAI,kBACF,cAAa,kBAAkB,QAAQ,OAAO;MAE9C,qBAAoB,KAAK;GACvB;GACA;GACD,CAAC;;AAIN,QAAO;;;;;;;;;;AAWT,MAAa,0BAA0B,oBAA4B;AAEjE,QAAO,kBAAkB,gBAAgB;;;;;;;;;;AAW3C,MAAa,2BACX,qBAC8B;AAE9B,QAAO,mBAAmB,iBAAiB;;AAG7C,MAAa,4BACX,QACA,WACG;CACH,MAAMC,SAA+B,EAAE;AAEvC,MAAK,MAAM,OAAO,QAAQ;EACxB,MAAM,OAAO,OAAO,QAAQ,QAAQ,IAAI,OAAO,IAAI,GAAG;AAGtD,MACE,IAAI,OAAO,WAAW,KACnB,KAAK,MAAM,QAAQ,IAAI,OAAO,IAAI,MAAM,IAAI,OAAO,WAAW,EAAE,EACnE;AACA,UAAO,KAAK;IACV,IAAI,IAAI;IACR,QAAQ,EAAE;IACX,CAAC;AAEF;;EAIF,MAAM,SAAS,CAAC,GAAG,IAAI,OAAO;AAC9B,OAAK,MAAM,OAAO,KAChB,cAAa,QAAQ,IAAI,OAAO;AAGlC,SAAO,KAAK;GACV,IAAI,IAAI;GACR;GACD,CAAC;;AAGJ,MAAK,MAAM,OAAO,OAChB,KAAI,CAAC,OAAO,MAAM,OAAO,GAAG,OAAO,IAAI,GAAG,CACxC,QAAO,KAAK,IAAI;AAIpB,QAAO;;;;;;;;;;;;AAaT,MAAa,gBACX,QACA,MACA,OACG;CACH,MAAMC,SAAgC,EAAE;AACxC,MAAK,MAAM,SAAS,OAClB,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,KAAK,aAAa,OAAO,MAAM,GAAG,CAAa;UAC7C,UAAU,KACnB,KAAI,MAAM,QAAQ,GAAG,CACnB,QAAO,KAAK,GAAG,GAAG;KAElB,QAAO,KAAK,GAAG;KAGjB,QAAO,KAAK,MAAM;AAItB,QAAO;;;;;AC5ST,IAAa,eAAb,MAAa,aAAa;CACxB,YAAY,AAAQC,SAAgC,EAAE,EAAE;EAApC;;CAEpB,OAAO,WAAW,SAAuB;AACvC,SAAO,IAAI,aAAa,QAAQ,OAAO,OAAO,CAAC;;CAGjD,QAAQ;AACN,SAAO,KAAK,MAAM,KAAK,UAAU,KAAK,OAAO,CAAC;;CAGhD,QAAQ;AACN,SAAO,aAAa,WAAW,KAAK;;;;;;;;;;;CAYtC,OAAO,OAA0B;AAC/B,OAAK,OAAO,KAAK,MAAM;;;;;;;;;CAUzB,OAAO,QAA8C;AACnD,MAAI,MAAM,QAAQ,OAAO,CACvB,cAAa,KAAK,QAAQ,OAAO;WACxB,kBAAkB,aAC3B,cAAa,KAAK,QAAQ,OAAO,OAAO;;;;;;;;;;;CAa5C,KAAK,eAA+C,MAA0B,SAAS;EACrF,MAAMC,SAAgC,EAAE;EAExC,MAAM,eAAe,MAAM,QAAQ,cAAc,GAAG,gBAAgB,CAAC,cAAc;AAEnF,MAAI,aAAa,WAAW,EAC1B;EAGF,MAAM,oBAAoB,KAAK,OAAO;AACtC,OAAK,MAAM,SAAS,cAAc;GAChC,MAAM,cAAc,MAAM,QAAQ,MAAM,GAAG,QAAQ,CAAC,MAAM;AAE1D,QAAK,MAAM,KAAK,kBACd,KAAI,MAAM,QAAQ,EAAE,CAClB,QAAO,KAAK,QAAQ,WAAW,CAAC,GAAG,aAAa,GAAG,EAAE,GAAG,CAAC,GAAG,GAAG,GAAG,YAAY,CAAC;OAE/E,QAAO,KAAK,QAAQ,WAAW,CAAC,GAAG,aAAa,EAAE,GAAG,CAAC,GAAG,GAAG,YAAY,CAAC;;AAK/E,OAAK,SAAS;;;;;;;;;;CAWhB,cAAc,MAAc,IAAY;EACtC,MAAMA,SAAgC,EAAE;AACxC,OAAK,MAAM,SAAS,KAAK,OACvB,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,KAAK,MAAM,KAAK,MAAM,EAAE,QAAQ,GAAG,KAAK,IAAI,GAAG,GAAG,GAAG,CAAC,CAAC;MAE9D,QAAO,KAAK,MAAM,QAAQ,GAAG,KAAK,IAAI,GAAG,GAAG,GAAG,CAAC;AAGpD,OAAK,SAAS"}
package/package.json CHANGED
@@ -1,10 +1,22 @@
1
1
  {
2
2
  "name": "@pcg/auth",
3
- "version": "1.0.0-alpha.0",
3
+ "version": "1.0.0-alpha.1",
4
4
  "description": "Authorization library",
5
+ "license": "MIT",
6
+ "author": {
7
+ "email": "code@deepvision.team",
8
+ "name": "DeepVision Code"
9
+ },
10
+ "contributors": [
11
+ "Vitaliy Angolenko <v.angolenko@deepvision.software>",
12
+ "Sergii Sadovyi <s.sadovyi@deepvision.software>"
13
+ ],
5
14
  "type": "module",
6
15
  "main": "dist/index.js",
7
16
  "types": "dist/index.d.ts",
17
+ "files": [
18
+ "dist"
19
+ ],
8
20
  "devDependencies": {
9
21
  "@vitest/ui": "^4.0.8",
10
22
  "vitest": "^4.0.8"
@@ -14,6 +26,6 @@
14
26
  "build": "tsdown",
15
27
  "test": "vitest run",
16
28
  "test:watch": "vitest",
17
- "lint": "eslint \"src/**/*.ts\""
29
+ "lint": "eslint \"src/**/*.ts\" --fix"
18
30
  }
19
31
  }
@@ -1,15 +0,0 @@
1
-
2
- 
3
- > @pcg/auth@1.0.0-alpha.0 build /Users/serg_sadovyi/prj/deepvisionsoftware.github/pcg/packages/auth
4
- > tsdown
5
-
6
- ℹ tsdown v0.15.12 powered by rolldown v1.0.0-beta.45
7
- ℹ Using tsdown config: /Users/serg_sadovyi/prj/deepvisionsoftware.github/pcg/packages/auth/tsdown.config.ts
8
- ℹ entry: src/index.ts
9
- ℹ tsconfig: ../../tsconfig.build.json
10
- ℹ Build start
11
- ℹ dist/index.js 11.71 kB │ gzip: 3.11 kB
12
- ℹ dist/index.js.map 20.80 kB │ gzip: 5.58 kB
13
- ℹ dist/index.d.ts  8.62 kB │ gzip: 2.00 kB
14
- ℹ 3 files, total: 41.13 kB
15
- ✔ Build complete in 577ms
package/src/index.ts DELETED
@@ -1,4 +0,0 @@
1
- export * from './permissions/index.js';
2
- export * from './types/permissions.js';
3
- export * from './types/user.js';
4
-
@@ -1,141 +0,0 @@
1
- export type ActionScopesArray = (string | string[])[];
2
-
3
- /**
4
- * Action scopes are used to describe
5
- * what scopes are required to execute an action.
6
- */
7
- export class ActionScopes {
8
- public array: ActionScopesArray = [];
9
- public anyScope = false;
10
- private idsMap = new Map<string, string>();
11
-
12
- constructor(scopes?: ActionScopesArray) {
13
- if (scopes) {
14
- this.array = scopes ?? [];
15
- }
16
- }
17
-
18
- /**
19
- * Set id scope (e.g. 'id#jsu:123')
20
- * @param id - entity id
21
- * @returns - this
22
- * @example
23
- * scopes.setEntityId('jsu:123')
24
- *
25
- * // scopes.getArray() = ['id#jsu:123']
26
- */
27
- setEntityId(id: string): this {
28
- this.set(`id#${id}`);
29
-
30
- return this;
31
- }
32
-
33
- /**
34
- * Set scope (e.g. 'org', 'org+review')
35
- * @param scope - scope name (e.g. 'org', 'org+review')
36
- * @param id - entity id (e.g. 'jsorg:hci')
37
- * @returns - this
38
- * @example
39
- * scopes.set('my')
40
- *
41
- * // scopes.getArray() = ['my']
42
- *
43
- * scopes.set('org', 'jsorg:hci')
44
- *
45
- * // scopes.getArray() = ['org#jsorg:hci']
46
- */
47
- set(scope: string, id?: string): this {
48
- if (id && /^[A-Za-z_]+$/.test(scope)) {
49
- this.idsMap.set(scope, id);
50
- }
51
-
52
- if (scope.includes('+')) {
53
- const subscopes = scope.split('+');
54
- this.array.push(
55
- subscopes.map((subscope) => this.resolveScopeId(subscope)),
56
- );
57
- } else {
58
- this.array.push(this.resolveScopeId(scope));
59
- }
60
-
61
- return this;
62
- }
63
-
64
- has(scope: string): boolean {
65
- return this.array.includes(scope);
66
- }
67
-
68
- getArray(): ActionScopesArray {
69
- return [...this.array];
70
- }
71
-
72
- /**
73
- * Permission scopes must cover auth scopes determined in auth context.
74
- * @param permissionScopes
75
- */
76
- canBeExecutedInScopes(
77
- permissionScopes: readonly (string | string[])[],
78
- ): boolean {
79
- // if scopes has * then it can be executed in any scope
80
- if (this.array.includes('*')) {
81
- return true;
82
- }
83
-
84
- /* action scopes = ['user#jsu:123', 'org#hci', ['org#hci', 'draft']]
85
- /* permission scopes: ['user#jsu:123', ['org#hci', 'draft']]
86
-
87
- /**
88
- * empty user permission scopes = full access to action
89
- */
90
- if (permissionScopes.length === 0) {
91
- return true;
92
- }
93
-
94
- /**
95
- * Empty auth scopes but user has permission scopes = no access to action
96
- *
97
- * action scopes = []
98
- * permission scopes = ['org#hci']
99
- *
100
- * Example:
101
- * User try to get list of all entities
102
- * But user only has permission to entity from org#hci
103
- */
104
- if (this.array.length === 0) {
105
- return false;
106
- }
107
-
108
- return this.array.some((authScope) => {
109
- if (Array.isArray(authScope)) {
110
- // scopes ['org#hci', 'draft'] == permissionScope ['org#hci', 'draft']
111
-
112
- return permissionScopes.some((permissionScope) => {
113
- if (Array.isArray(permissionScope)) {
114
- return permissionScope.every(
115
- (sub) => authScope.includes(sub),
116
- );
117
- }
118
-
119
- return authScope.includes(permissionScope);
120
- });
121
- }
122
-
123
- return permissionScopes.includes(authScope); // 'user#jsu:123' == 'user#jsu:123'
124
- });
125
- }
126
-
127
- /**
128
- * Resolve presaved scopes
129
- * @example
130
- * authCtx.setScope('org', 'jsorg:hci')
131
- * [org#hci]
132
- *
133
- * authCtx.setScope('org+review');
134
- * [org#hci, [org#hci, review]]
135
- **/
136
- private resolveScopeId(scope: string) {
137
- return this.idsMap.has(scope)
138
- ? `${scope}#${this.idsMap.get(scope)}`
139
- : scope;
140
- }
141
- }
@@ -1,304 +0,0 @@
1
- import {
2
- ReadonlyResolvedPermission, ResolvedPermission, ResolvedPermissionGroup,
3
- } from '../types/permissions.js';
4
- import { IUser } from '../types/user.js';
5
- import { ActionScopes, ActionScopesArray } from './action-scopes.js';
6
-
7
- /**
8
- * Normilize scopes to ActionScopes
9
- * @example
10
- * const scopes = normalizeScopes(['org#hci', 'user#hcu:xxxxx') // ActionScopes
11
- *
12
- * scopes.set('my')
13
- */
14
- const normalizeScopes = (
15
- scopes: ActionScopesArray | ActionScopes,
16
- ): ActionScopes => {
17
- if (!(scopes instanceof ActionScopes)) {
18
- return new ActionScopes(scopes);
19
- }
20
-
21
- return scopes as ActionScopes;
22
- };
23
-
24
- /**
25
- * Check if user has access to permission
26
- * @example
27
- *
28
- * // Check episode access
29
- * const scopes = new ActionScopes();
30
- * scopes.setEntityId(episode.id); // id#js:core:episodes:xxxxx
31
- * scopes.set('org', episode.organizationId); // org#hcorg:hci
32
- *
33
- * isGranted(user, 'js:core:episodes:get', scopes)
34
- * @param user - user object with resolvedPermissions
35
- * @param permission - permission string (e.g. 'js:core:episodes:get')
36
- * @param actionScopes - action scopes (e.g. ['org#hcorg:hci'] or ActionScopes instance)
37
- * @returns - true if user has access to permission
38
- */
39
- export const isGranted = (
40
- user: IUser,
41
- permission: string,
42
- actionScopes: ActionScopesArray | ActionScopes = [],
43
- ): boolean => {
44
- const [service, module, resource, action] = permission.split(':');
45
-
46
- if (!user?.resolvedPermissions || user.resolvedPermissions.length === 0) {
47
- return false;
48
- }
49
-
50
- const userPermissions = user.resolvedPermissions;
51
-
52
- const scopes = normalizeScopes(actionScopes);
53
-
54
- const permissionCanBeExecutedInScopes = (perm: ReadonlyResolvedPermission, scopes: ActionScopes) =>
55
- (!perm.scopes || perm.scopes.length === 0 || scopes.canBeExecutedInScopes(perm.scopes));
56
-
57
- const regexp = new RegExp(`^(${service}|\\*):(${module}|\\*):(${resource}|\\*):(${action}|\\*)$`);
58
-
59
- // novajs:*:*:* or novajs:*:*[org]:*
60
- // novajs:module:*:* or novajs:module:*[org]:*
61
- // novajs:module:resource:* or novajs:module:resource:[org]:*
62
- // novajs:modules:resource:create
63
- // *:modules:resource:create
64
- if (userPermissions.some(
65
- (perm) => regexp.test(perm.id) &&
66
- permissionCanBeExecutedInScopes(perm, scopes))
67
- ) {
68
- return true;
69
- }
70
-
71
- return false;
72
- };
73
-
74
- export const encodeScopes = (scopes: (string | string[])[]): string => {
75
- const scopesString = scopes
76
- .map((s) => (Array.isArray(s) ? s.join('+') : s))
77
- .join(',');
78
-
79
- return scopesString.length > 0 ? `[${scopesString}]` : '';
80
- };
81
-
82
- /**
83
- * Inject scopes into permission
84
- * @param permission - permission string (e.g. 'js:core:episodes:get')
85
- * @param scopesToInject - scopes to inject (e.g. ['org', 'published'])
86
- * @returns - permission string with injected scopes (e.g. 'js:core:episodes[org,published]:get')
87
- * @example
88
- * injectScopesIntoPermission('js:core:episodes:get', ['org', 'published'])
89
- * // js:core:episodes[org,published]:get
90
- */
91
- export const injectScopesIntoPermission = (
92
- permission: string,
93
- scopesToInject: (string | string[])[],
94
- ) => {
95
- const { id, scopes = [] } = resolvePermission(permission);
96
-
97
- const [service, module, resource, action] = id.split(':');
98
-
99
- concatScopes(scopes, scopesToInject);
100
-
101
- return `${service}:${module}:${resource}${encodeScopes(scopes)}:${action}`;
102
- };
103
-
104
- /**
105
- * Concat scopes
106
- * @param set - set of scopes
107
- * @param items - items to concat
108
- * @example
109
- * concatScopes(['org#hci'], ['org#dv'])
110
- * // ['org#hci', 'org#dv']
111
- */
112
- export const concatScopes = (
113
- set: (string | string[])[],
114
- items: (string | string[])[],
115
- ): void => {
116
- for (const item of items) {
117
- if (Array.isArray(item)) {
118
- // check if scopes not includes array with subscopes rp2Scope
119
- if (
120
- !set.some(
121
- (scope) =>
122
- Array.isArray(scope) &&
123
- scope.length === item.length &&
124
- scope.every((s) => item.includes(s)),
125
- )
126
- ) {
127
- set.push(item);
128
- }
129
- } else if (!set.includes(item)) {
130
- set.push(item);
131
- }
132
- }
133
- };
134
-
135
- /**
136
- * Resolve permission string to object with scopes
137
- * @param permission - permission string
138
- * @returns - resolved permission object
139
- * @example
140
- * resolvePermission('js:core:episodes[org,published]:get')
141
- * // { id: 'js:core:episodes:get', scopes: ['org', 'published'] }
142
- */
143
- export const resolvePermission = (permission: string): ResolvedPermission => {
144
- const matches = /\[(?<scopes>.*?)\]/u.exec(permission);
145
-
146
- if (matches?.[0] && matches.groups?.scopes) {
147
- const scopes: (string | string[])[] = String(matches.groups.scopes)
148
- .split(',') // scopes with OR logic
149
- .map((s) => {
150
- if (s.includes('+')) {
151
- return s.split('+'); // scopes with AND logic
152
- }
153
-
154
- return s;
155
- });
156
-
157
- return {
158
- id: permission.replace(matches[0], ''),
159
- scopes,
160
- };
161
- }
162
-
163
- return {
164
- id: permission,
165
- scopes: [],
166
- };
167
- };
168
-
169
- /**
170
- * Resolve permissions to array of resolved permissions
171
- * @param permissions - array of permissions
172
- * @returns - array of resolved permissions
173
- * @example
174
- * resolvePermissions(['js:core:episodes[org]:get', 'js:core:episodes[published]:get'])
175
- * // [{ id: 'js:core:episodes:get', scopes: ['org', 'published'] }]
176
- */
177
- export const resolvePermissions = (
178
- permissions: string[],
179
- ): ResolvedPermission[] => {
180
- const resolvedPermissions: ResolvedPermission[] = [];
181
-
182
- for (const permission of permissions) {
183
- const { id, scopes } = resolvePermission(permission);
184
-
185
- const currentPermission = resolvedPermissions.find(
186
- (p) => p.id === id,
187
- );
188
-
189
- if (currentPermission) {
190
- concatScopes(currentPermission.scopes, scopes);
191
- } else {
192
- resolvedPermissions.push({
193
- id,
194
- scopes,
195
- });
196
- }
197
- }
198
-
199
- return resolvedPermissions;
200
- };
201
-
202
- /**
203
- * Resolve permission group string to object with scopes
204
- * @param permissionGroup - permission group string
205
- * @returns - resolved permission group object
206
- * @example
207
- * resolvePermissionGroup('g:js:core:episodes[org]:read')
208
- * // { id: 'g:js:core:episodes:read', scopes: ['org'] }
209
- */
210
- export const resolvePermissionGroup = (permissionGroup: string) => {
211
- // Currently it has the same algorithm
212
- return resolvePermission(permissionGroup);
213
- };
214
-
215
- /**
216
- * Resolve permission groups to array of resolved permission groups
217
- * @param permissionGroups - array of permission groups
218
- * @returns - array of resolved permission groups
219
- * @example
220
- * resolvePermissionGroups(['g:js:core:episodes[org]:read', 'g:js:core:episodes[published]:read'])
221
- * // [{ id: 'g:js:core:episodes:read', scopes: ['org', 'published'] }]
222
- */
223
- export const resolvePermissionGroups = (
224
- permissionGroups: string[],
225
- ): ResolvedPermissionGroup[] => {
226
- // Currently it has the same algorithm
227
- return resolvePermissions(permissionGroups);
228
- };
229
-
230
- export const mergeResolvedPermissions = (
231
- array1: ResolvedPermission[],
232
- array2: ResolvedPermission[],
233
- ) => {
234
- const result: ResolvedPermission[] = [];
235
-
236
- for (const rp1 of array1) {
237
- const rps2 = array2.filter((rp2) => rp2.id === rp1.id);
238
-
239
- /* Empty scopes = has full access */
240
- if (
241
- rp1.scopes.length === 0 ||
242
- rps2.some((rp2) => rp2.id === rp1.id && rp2.scopes.length === 0)
243
- ) {
244
- result.push({
245
- id: rp1.id,
246
- scopes: [],
247
- });
248
-
249
- continue;
250
- }
251
-
252
- // [org#hci, user#hcu:xxxxx, [org#hci, review]]
253
- const scopes = [...rp1.scopes];
254
- for (const rp2 of rps2) {
255
- concatScopes(scopes, rp2.scopes);
256
- }
257
-
258
- result.push({
259
- id: rp1.id,
260
- scopes,
261
- });
262
- }
263
-
264
- for (const rp2 of array2) {
265
- if (!result.some((rp) => rp.id === rp2.id)) {
266
- result.push(rp2);
267
- }
268
- }
269
-
270
- return result;
271
- };
272
-
273
- /**
274
- * Replace scope in array of scopes
275
- * @param scopes - array of scopes
276
- * @param from - scope to replace
277
- * @param to - scope to replace with
278
- * @returns - array of scopes with replaced scopes
279
- * @example
280
- * replaceScope(['org#jsorg:xxx', ['org#jsorg:xxx', 'published']], 'org#jsorg:xxx', 'org#jsorg:hci')
281
- * // ['org#jsorg:hci', ['org#jsorg:hci', 'published']]
282
- */
283
- export const replaceScope = (
284
- scopes: (string | string[])[], // [org#jsorg:xxx, [org#jsorg:xxx, published]]
285
- from: string,
286
- to: string | (string | string[])[],
287
- ) => {
288
- const result: (string | string[])[] = [];
289
- for (const scope of scopes) {
290
- if (Array.isArray(scope)) {
291
- result.push(replaceScope(scope, from, to) as string[]);
292
- } else if (scope === from) {
293
- if (Array.isArray(to)) {
294
- result.push(...to);
295
- } else {
296
- result.push(to);
297
- }
298
- } else {
299
- result.push(scope);
300
- }
301
- }
302
-
303
- return result;
304
- };
@@ -1,4 +0,0 @@
1
- export * from './action-scopes.js';
2
- export * from './helpers.js';
3
- export * from './scopes-bulder.js';
4
-