@digibuffer/access 0.0.0-stage → 0.2.0
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/README.md +0 -3
- package/catalog.js +55 -0
- package/index.d.ts +26 -0
- package/index.js +84 -0
- package/package.json +16 -4
package/README.md
CHANGED
package/catalog.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every permission a DigiBuffer app checks, and what it means. Workflow draws its role screens from this list;
|
|
3
|
+
* each app checks its own with can(). A permission is either company-wide ("company") or for one product at a
|
|
4
|
+
* time ("product": its grant lists the products, by their app name such as "conepic").
|
|
5
|
+
*
|
|
6
|
+
* Adding one: add it here, bump the version, publish, then use it in the app. Never rename one in place: add the
|
|
7
|
+
* new name, move the apps, then remove the old.
|
|
8
|
+
*/
|
|
9
|
+
export const apps = Object.freeze({
|
|
10
|
+
billing: "Billing",
|
|
11
|
+
supportdesk: "Supportdesk",
|
|
12
|
+
digichat: "DigiChat",
|
|
13
|
+
knowledgehub: "KnowledgeHub",
|
|
14
|
+
digimail: "DigiMail",
|
|
15
|
+
digimarket: "DigiMarket",
|
|
16
|
+
digisender: "DigiSender",
|
|
17
|
+
preview: "Cloud Preview",
|
|
18
|
+
agentworkflows: "Agent Workflows",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/** @type {Readonly<Record<string, { app: keyof typeof apps; label: string; description: string; scope: "company" | "product" }>>} */
|
|
22
|
+
export const catalog = Object.freeze({
|
|
23
|
+
"billing.customers.view": { app: "billing", scope: "product", label: "See customers and payments", description: "Customers, plans, payments, invoices and credits." },
|
|
24
|
+
"billing.refunds.request": { app: "billing", scope: "product", label: "Request refunds", description: "Ask for a refund; someone who can approve refunds does it." },
|
|
25
|
+
"billing.refunds.approve": { app: "billing", scope: "product", label: "Approve refunds and change plans", description: "Refund, give or end plans, credits, grants and private offers." },
|
|
26
|
+
"billing.catalog.manage": { app: "billing", scope: "company", label: "Plans and prices", description: "Plans, prices, products and bundles." },
|
|
27
|
+
"billing.revenue.view": { app: "billing", scope: "company", label: "Revenue and GST", description: "Revenue, every invoice and the GST documents." },
|
|
28
|
+
|
|
29
|
+
"supportdesk.agent": { app: "supportdesk", scope: "product", label: "Support agent", description: "Take, assign and resolve tickets, be in the product's support channel, see the customer panel." },
|
|
30
|
+
"supportdesk.manage": { app: "supportdesk", scope: "product", label: "Run the support desk", description: "Help pages, chat on or off, the assistant and ticket routing." },
|
|
31
|
+
|
|
32
|
+
"digichat.admin": { app: "digichat", scope: "company", label: "DigiChat admin", description: "The admin page and deleting anyone's channel message." },
|
|
33
|
+
"digichat.channels.create": { app: "digichat", scope: "company", label: "Create channels", description: "Create channels (product and project channels come from Workflow)." },
|
|
34
|
+
|
|
35
|
+
"knowledgehub.admin": { app: "knowledgehub", scope: "company", label: "KnowledgeHub admin", description: "Assistants, storage and every space." },
|
|
36
|
+
|
|
37
|
+
"digimail.admin": { app: "digimail", scope: "company", label: "DigiMail admin", description: "Domains, backups, setup and shared mailboxes." },
|
|
38
|
+
|
|
39
|
+
"digimarket.leads.view": { app: "digimarket", scope: "product", label: "See leads", description: "Leads, events and call tasks." },
|
|
40
|
+
"digimarket.rules.manage": { app: "digimarket", scope: "product", label: "Manage event rules", description: "Event rules, event keys and Notify support team." },
|
|
41
|
+
|
|
42
|
+
"digisender.use": { app: "digisender", scope: "company", label: "Use DigiSender", description: "Sign in and send: campaigns, templates, contacts." },
|
|
43
|
+
"digisender.admin": { app: "digisender", scope: "company", label: "DigiSender admin", description: "Sending domains, providers, API keys and settings." },
|
|
44
|
+
|
|
45
|
+
"preview.use": { app: "preview", scope: "company", label: "Use Cloud Preview", description: "Sign in; previews of the Workflow projects they're on." },
|
|
46
|
+
"preview.admin": { app: "preview", scope: "company", label: "Cloud Preview admin", description: "Every project's previews, servers, registries and placement." },
|
|
47
|
+
|
|
48
|
+
"agentworkflows.use": { app: "agentworkflows", scope: "company", label: "Use Agent Workflows", description: "Sign in and build and run their own workflows." },
|
|
49
|
+
"agentworkflows.admin": { app: "agentworkflows", scope: "company", label: "Agent Workflows admin", description: "Shared MCP servers, models, API keys and everyone's runs." },
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export const isPermission = (name) => typeof name === "string" && Object.prototype.hasOwnProperty.call(catalog, name);
|
|
53
|
+
|
|
54
|
+
/** The catalog's permissions for one app */
|
|
55
|
+
export const permissionsOf = (app) => Object.keys(catalog).filter((p) => catalog[p].app === app);
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type AppKey = "billing" | "supportdesk" | "digichat" | "knowledgehub" | "digimail" | "digimarket" | "digisender" | "preview" | "agentworkflows";
|
|
2
|
+
export type Permission =
|
|
3
|
+
| "billing.customers.view" | "billing.refunds.request" | "billing.refunds.approve" | "billing.catalog.manage" | "billing.revenue.view"
|
|
4
|
+
| "supportdesk.agent" | "supportdesk.manage"
|
|
5
|
+
| "digichat.admin" | "digichat.channels.create"
|
|
6
|
+
| "knowledgehub.admin"
|
|
7
|
+
| "digimail.admin"
|
|
8
|
+
| "digimarket.leads.view" | "digimarket.rules.manage"
|
|
9
|
+
| "digisender.use" | "digisender.admin"
|
|
10
|
+
| "preview.use" | "preview.admin"
|
|
11
|
+
| "agentworkflows.use" | "agentworkflows.admin";
|
|
12
|
+
|
|
13
|
+
export interface PermissionInfo { app: AppKey; label: string; description: string; scope: "company" | "product" }
|
|
14
|
+
/** Permission → products ("*" for all) */
|
|
15
|
+
export type Access = Partial<Record<Permission, string[]>>;
|
|
16
|
+
|
|
17
|
+
export declare const ALL: "*";
|
|
18
|
+
export declare const apps: Readonly<Record<AppKey, string>>;
|
|
19
|
+
export declare const catalog: Readonly<Record<Permission, PermissionInfo>>;
|
|
20
|
+
export declare function isPermission(name: unknown): name is Permission;
|
|
21
|
+
export declare function permissionsOf(app: AppKey): Permission[];
|
|
22
|
+
export declare function parseAccess(claim: unknown): Access;
|
|
23
|
+
export declare function can(who: { access?: Access | null } | Access | null | undefined, permission: Permission, product?: string): boolean;
|
|
24
|
+
export declare function productsWith(who: { access?: Access | null } | Access | null | undefined, permission: Permission): "*" | string[];
|
|
25
|
+
export declare function forApp(access: unknown, app: AppKey): Access;
|
|
26
|
+
export declare function isReduced(before: unknown, after: unknown): boolean;
|
package/index.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { catalog, isPermission } from "./catalog.js";
|
|
2
|
+
|
|
3
|
+
export { apps, catalog, isPermission, permissionsOf } from "./catalog.js";
|
|
4
|
+
|
|
5
|
+
/** Every product: a company-wide permission is granted as ["*"] */
|
|
6
|
+
export const ALL = "*";
|
|
7
|
+
|
|
8
|
+
const SLUG = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The access claim as an app received it from BufferID, made safe to keep in a session: only known permissions,
|
|
12
|
+
* each with a list of product names (or "*"). Anything else is dropped. Never throws.
|
|
13
|
+
* @param {unknown} claim
|
|
14
|
+
* @returns {Record<string, string[]>}
|
|
15
|
+
*/
|
|
16
|
+
export function parseAccess(claim) {
|
|
17
|
+
/** @type {Record<string, string[]>} */
|
|
18
|
+
const out = {};
|
|
19
|
+
if (!claim || typeof claim !== "object" || Array.isArray(claim)) return out;
|
|
20
|
+
for (const [name, value] of Object.entries(claim)) {
|
|
21
|
+
if (!isPermission(name) || !Array.isArray(value)) continue;
|
|
22
|
+
const products = [...new Set(value.filter((v) => typeof v === "string" && (v === ALL || SLUG.test(v))))];
|
|
23
|
+
if (!products.length) continue;
|
|
24
|
+
out[name] = products.includes(ALL) ? [ALL] : products.sort();
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* May they? Nothing unless granted: an unknown permission, a missing grant or an empty session is no.
|
|
31
|
+
* A product permission needs the product (or "*"); a company permission needs "*".
|
|
32
|
+
* @param {{ access?: Record<string, string[]> | null } | Record<string, string[]> | null | undefined} who a session holding `access`, or the access itself
|
|
33
|
+
* @param {string} permission
|
|
34
|
+
* @param {string} [product]
|
|
35
|
+
*/
|
|
36
|
+
export function can(who, permission, product) {
|
|
37
|
+
if (!isPermission(permission)) return false;
|
|
38
|
+
const access = accessOf(who);
|
|
39
|
+
const granted = access?.[permission];
|
|
40
|
+
if (!Array.isArray(granted) || !granted.length) return false;
|
|
41
|
+
if (granted.includes(ALL)) return true;
|
|
42
|
+
if (catalog[permission].scope === "company") return false;
|
|
43
|
+
return typeof product === "string" && product !== ALL && granted.includes(product);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The products they have a permission for: "*" for all, else the names (empty: none).
|
|
48
|
+
* @returns {"*" | string[]}
|
|
49
|
+
*/
|
|
50
|
+
export function productsWith(who, permission) {
|
|
51
|
+
if (!isPermission(permission)) return [];
|
|
52
|
+
const granted = accessOf(who)?.[permission];
|
|
53
|
+
if (!Array.isArray(granted)) return [];
|
|
54
|
+
return granted.includes(ALL) ? ALL : [...granted];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Only one app's permissions (what BufferID hands that app) */
|
|
58
|
+
export function forApp(access, app) {
|
|
59
|
+
const all = parseAccess(access);
|
|
60
|
+
return Object.fromEntries(Object.entries(all).filter(([p]) => catalog[p].app === app));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Did they lose anything between two grants? (A permission gone, or a product gone from one.) Then they're signed
|
|
65
|
+
* out; additions wait for the next sign-in.
|
|
66
|
+
*/
|
|
67
|
+
export function isReduced(before, after) {
|
|
68
|
+
const a = parseAccess(before);
|
|
69
|
+
const b = parseAccess(after);
|
|
70
|
+
for (const [p, products] of Object.entries(a)) {
|
|
71
|
+
const now = b[p];
|
|
72
|
+
if (!now) return true;
|
|
73
|
+
if (now.includes(ALL)) continue;
|
|
74
|
+
if (products.includes(ALL)) return true;
|
|
75
|
+
if (products.some((x) => !now.includes(x))) return true;
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function accessOf(who) {
|
|
81
|
+
if (!who || typeof who !== "object") return null;
|
|
82
|
+
if ("access" in who) return /** @type {any} */ (who).access ?? null;
|
|
83
|
+
return /** @type {Record<string, string[]>} */ (who);
|
|
84
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@digibuffer/access",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"description": "DigiBuffer staff permissions: the catalog every app shares and the can() check. Holds no secrets and no list of who has what.",
|
|
4
|
+
"version": "0.2.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": ["index.js", "index.d.ts", "catalog.js", "README.md"],
|
|
7
|
+
"exports": {
|
|
8
|
+
"./package.json": "./package.json",
|
|
9
|
+
".": { "types": "./index.d.ts", "import": "./index.js", "default": "./index.js" }
|
|
10
|
+
},
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"scripts": { "test": "node --test test/*.test.js" },
|
|
13
|
+
"keywords": ["digibuffer", "permissions", "access"],
|
|
14
|
+
"license": "UNLICENSED",
|
|
15
|
+
"author": "Dilip",
|
|
16
|
+
"repository": { "type": "git", "url": "git+https://github.com/Digibuffer/upload-lib.git", "directory": "packages/access" },
|
|
17
|
+
"publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" }
|
|
18
|
+
}
|