@auth-gate/rbac 0.8.1 → 0.9.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/{chunk-5YEMXO7B.mjs → chunk-5CSBBETH.mjs} +31 -4
- package/dist/cli.cjs +146 -18
- package/dist/cli.mjs +118 -15
- package/dist/index.cjs +125 -5
- package/dist/index.d.cts +128 -7
- package/dist/index.d.ts +128 -7
- package/dist/index.mjs +94 -2
- package/package.json +4 -2
|
@@ -150,6 +150,21 @@ function validateRbacConfig(config) {
|
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
|
+
const defaultRoles = [];
|
|
154
|
+
for (const key of roleKeys) {
|
|
155
|
+
const role = roles[key];
|
|
156
|
+
if (role.isDefault !== void 0 && typeof role.isDefault !== "boolean") {
|
|
157
|
+
throw new Error(`Role "${key}".isDefault must be a boolean.`);
|
|
158
|
+
}
|
|
159
|
+
if (role.isDefault === true) {
|
|
160
|
+
defaultRoles.push(key);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (defaultRoles.length > 1) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Only one role may have isDefault: true. Found ${defaultRoles.length}: ${defaultRoles.join(", ")}.`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
153
168
|
for (const key of roleKeys) {
|
|
154
169
|
const role = roles[key];
|
|
155
170
|
if (role.inherits !== void 0) {
|
|
@@ -258,7 +273,6 @@ var CONFIG_FILENAMES = [
|
|
|
258
273
|
"authgate.rbac.mjs"
|
|
259
274
|
];
|
|
260
275
|
async function loadRbacConfig(cwd) {
|
|
261
|
-
var _a, _b;
|
|
262
276
|
let configPath = null;
|
|
263
277
|
for (const filename of CONFIG_FILENAMES) {
|
|
264
278
|
const candidate = resolve(cwd, filename);
|
|
@@ -273,8 +287,12 @@ async function loadRbacConfig(cwd) {
|
|
|
273
287
|
Run \`npx @auth-gate/rbac init\` to create one.`
|
|
274
288
|
);
|
|
275
289
|
}
|
|
290
|
+
return loadRbacConfigFromPath(configPath, cwd);
|
|
291
|
+
}
|
|
292
|
+
async function loadRbacConfigFromPath(configPath, cwd) {
|
|
293
|
+
var _a, _b;
|
|
276
294
|
const { createJiti } = await import("jiti");
|
|
277
|
-
const jiti = createJiti(cwd, { interopDefault: true });
|
|
295
|
+
const jiti = createJiti(cwd != null ? cwd : process.cwd(), { interopDefault: true });
|
|
278
296
|
const mod = await jiti.import(configPath);
|
|
279
297
|
const raw = (_b = (_a = mod.default) != null ? _a : mod.rbac) != null ? _b : mod;
|
|
280
298
|
const config = raw && typeof raw === "object" && "_config" in raw ? raw._config : raw;
|
|
@@ -302,7 +320,7 @@ function hashConditionSource(fn) {
|
|
|
302
320
|
return createHash("sha256").update(fn.toString()).digest("hex").slice(0, 16);
|
|
303
321
|
}
|
|
304
322
|
function computeRbacDiff(config, server, memberCounts) {
|
|
305
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
323
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
|
|
306
324
|
const resourceOps = [];
|
|
307
325
|
const roleOps = [];
|
|
308
326
|
const conditionOps = [];
|
|
@@ -393,6 +411,11 @@ function computeRbacDiff(config, server, memberCounts) {
|
|
|
393
411
|
if (existingInherits !== configInherits) {
|
|
394
412
|
changes.push("inherits changed");
|
|
395
413
|
}
|
|
414
|
+
const existingDefault = (_l = existing.isDefault) != null ? _l : false;
|
|
415
|
+
const configDefault = (_m = role.isDefault) != null ? _m : false;
|
|
416
|
+
if (existingDefault !== configDefault) {
|
|
417
|
+
changes.push(`isDefault: ${existingDefault} -> ${configDefault}`);
|
|
418
|
+
}
|
|
396
419
|
if (changes.length > 0) {
|
|
397
420
|
roleOps.push({ type: "update", key, role, existing, changes });
|
|
398
421
|
}
|
|
@@ -402,7 +425,7 @@ function computeRbacDiff(config, server, memberCounts) {
|
|
|
402
425
|
for (const [key, role] of serverRoleByKey) {
|
|
403
426
|
if (renameMap.has(key)) continue;
|
|
404
427
|
if (role.isActive) {
|
|
405
|
-
const members = (
|
|
428
|
+
const members = (_n = memberCounts[role.configKey]) != null ? _n : 0;
|
|
406
429
|
if (members > 0) hasDestructive = true;
|
|
407
430
|
roleOps.push({ type: "archive", key, existing: role, assignedMembers: members });
|
|
408
431
|
}
|
|
@@ -489,6 +512,9 @@ function formatRbacDiff(diff, dryRun) {
|
|
|
489
512
|
chalk.dim(` inherits: [${[...op.role.inherits].join(", ")}]`)
|
|
490
513
|
);
|
|
491
514
|
}
|
|
515
|
+
if (op.role.isDefault) {
|
|
516
|
+
lines.push(chalk.dim(` isDefault: true`));
|
|
517
|
+
}
|
|
492
518
|
} else if (op.type === "update") {
|
|
493
519
|
lines.push(chalk.yellow(` ~ UPDATE role "${op.key}"`));
|
|
494
520
|
for (const change of op.changes) {
|
|
@@ -626,6 +652,7 @@ export {
|
|
|
626
652
|
__spreadValues,
|
|
627
653
|
validateRbacConfig,
|
|
628
654
|
loadRbacConfig,
|
|
655
|
+
loadRbacConfigFromPath,
|
|
629
656
|
hashConditionSource,
|
|
630
657
|
computeRbacDiff,
|
|
631
658
|
formatRbacDiff,
|
package/dist/cli.cjs
CHANGED
|
@@ -170,6 +170,21 @@ function validateRbacConfig(config) {
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
|
+
const defaultRoles = [];
|
|
174
|
+
for (const key of roleKeys) {
|
|
175
|
+
const role = roles[key];
|
|
176
|
+
if (role.isDefault !== void 0 && typeof role.isDefault !== "boolean") {
|
|
177
|
+
throw new Error(`Role "${key}".isDefault must be a boolean.`);
|
|
178
|
+
}
|
|
179
|
+
if (role.isDefault === true) {
|
|
180
|
+
defaultRoles.push(key);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (defaultRoles.length > 1) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`Only one role may have isDefault: true. Found ${defaultRoles.length}: ${defaultRoles.join(", ")}.`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
173
188
|
for (const key of roleKeys) {
|
|
174
189
|
const role = roles[key];
|
|
175
190
|
if (role.inherits !== void 0) {
|
|
@@ -276,7 +291,6 @@ var CONFIG_FILENAMES = [
|
|
|
276
291
|
"authgate.rbac.mjs"
|
|
277
292
|
];
|
|
278
293
|
async function loadRbacConfig(cwd) {
|
|
279
|
-
var _a, _b;
|
|
280
294
|
let configPath = null;
|
|
281
295
|
for (const filename of CONFIG_FILENAMES) {
|
|
282
296
|
const candidate = (0, import_path.resolve)(cwd, filename);
|
|
@@ -291,8 +305,12 @@ async function loadRbacConfig(cwd) {
|
|
|
291
305
|
Run \`npx @auth-gate/rbac init\` to create one.`
|
|
292
306
|
);
|
|
293
307
|
}
|
|
308
|
+
return loadRbacConfigFromPath(configPath, cwd);
|
|
309
|
+
}
|
|
310
|
+
async function loadRbacConfigFromPath(configPath, cwd) {
|
|
311
|
+
var _a, _b;
|
|
294
312
|
const { createJiti } = await import("jiti");
|
|
295
|
-
const jiti = createJiti(cwd, { interopDefault: true });
|
|
313
|
+
const jiti = createJiti(cwd != null ? cwd : process.cwd(), { interopDefault: true });
|
|
296
314
|
const mod = await jiti.import(configPath);
|
|
297
315
|
const raw = (_b = (_a = mod.default) != null ? _a : mod.rbac) != null ? _b : mod;
|
|
298
316
|
const config = raw && typeof raw === "object" && "_config" in raw ? raw._config : raw;
|
|
@@ -320,7 +338,7 @@ function hashConditionSource(fn) {
|
|
|
320
338
|
return createHash("sha256").update(fn.toString()).digest("hex").slice(0, 16);
|
|
321
339
|
}
|
|
322
340
|
function computeRbacDiff(config, server, memberCounts) {
|
|
323
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
341
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
|
|
324
342
|
const resourceOps = [];
|
|
325
343
|
const roleOps = [];
|
|
326
344
|
const conditionOps = [];
|
|
@@ -411,6 +429,11 @@ function computeRbacDiff(config, server, memberCounts) {
|
|
|
411
429
|
if (existingInherits !== configInherits) {
|
|
412
430
|
changes.push("inherits changed");
|
|
413
431
|
}
|
|
432
|
+
const existingDefault = (_l = existing.isDefault) != null ? _l : false;
|
|
433
|
+
const configDefault = (_m = role.isDefault) != null ? _m : false;
|
|
434
|
+
if (existingDefault !== configDefault) {
|
|
435
|
+
changes.push(`isDefault: ${existingDefault} -> ${configDefault}`);
|
|
436
|
+
}
|
|
414
437
|
if (changes.length > 0) {
|
|
415
438
|
roleOps.push({ type: "update", key, role, existing, changes });
|
|
416
439
|
}
|
|
@@ -420,7 +443,7 @@ function computeRbacDiff(config, server, memberCounts) {
|
|
|
420
443
|
for (const [key, role] of serverRoleByKey) {
|
|
421
444
|
if (renameMap.has(key)) continue;
|
|
422
445
|
if (role.isActive) {
|
|
423
|
-
const members = (
|
|
446
|
+
const members = (_n = memberCounts[role.configKey]) != null ? _n : 0;
|
|
424
447
|
if (members > 0) hasDestructive = true;
|
|
425
448
|
roleOps.push({ type: "archive", key, existing: role, assignedMembers: members });
|
|
426
449
|
}
|
|
@@ -507,6 +530,9 @@ function formatRbacDiff(diff, dryRun) {
|
|
|
507
530
|
import_chalk.default.dim(` inherits: [${[...op.role.inherits].join(", ")}]`)
|
|
508
531
|
);
|
|
509
532
|
}
|
|
533
|
+
if (op.role.isDefault) {
|
|
534
|
+
lines.push(import_chalk.default.dim(` isDefault: true`));
|
|
535
|
+
}
|
|
510
536
|
} else if (op.type === "update") {
|
|
511
537
|
lines.push(import_chalk.default.yellow(` ~ UPDATE role "${op.key}"`));
|
|
512
538
|
for (const change of op.changes) {
|
|
@@ -640,10 +666,76 @@ var RbacSyncClient = class {
|
|
|
640
666
|
}
|
|
641
667
|
};
|
|
642
668
|
|
|
669
|
+
// src/project-config-loader.ts
|
|
670
|
+
var import_path2 = require("path");
|
|
671
|
+
var import_fs2 = require("fs");
|
|
672
|
+
var CONFIG_FILENAMES2 = [
|
|
673
|
+
"authgate.config.ts",
|
|
674
|
+
"authgate.config.js",
|
|
675
|
+
"authgate.config.mjs"
|
|
676
|
+
];
|
|
677
|
+
var AUTO_ENV_FILES = [".env", ".env.local"];
|
|
678
|
+
async function resolveProjectConfig(cwd) {
|
|
679
|
+
var _a, _b, _c;
|
|
680
|
+
await autoLoadEnvFiles(cwd);
|
|
681
|
+
const config = await loadProjectConfigFile(cwd);
|
|
682
|
+
if (!config) {
|
|
683
|
+
return {
|
|
684
|
+
connection: {
|
|
685
|
+
baseUrl: process.env.AUTHGATE_BASE_URL,
|
|
686
|
+
apiKey: process.env.AUTHGATE_API_KEY
|
|
687
|
+
},
|
|
688
|
+
rbacConfigPath: null,
|
|
689
|
+
billingConfigPath: null
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
const rawConnection = (_a = config.connection) != null ? _a : {};
|
|
693
|
+
const connection = {
|
|
694
|
+
baseUrl: (_b = rawConnection.baseUrl) != null ? _b : process.env.AUTHGATE_BASE_URL,
|
|
695
|
+
apiKey: (_c = rawConnection.apiKey) != null ? _c : process.env.AUTHGATE_API_KEY
|
|
696
|
+
};
|
|
697
|
+
return {
|
|
698
|
+
connection,
|
|
699
|
+
rbacConfigPath: config.rbacConfig ? (0, import_path2.resolve)(cwd, config.rbacConfig) : null,
|
|
700
|
+
billingConfigPath: config.billingConfig ? (0, import_path2.resolve)(cwd, config.billingConfig) : null
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
async function loadProjectConfigFile(cwd) {
|
|
704
|
+
var _a;
|
|
705
|
+
let configPath = null;
|
|
706
|
+
for (const filename of CONFIG_FILENAMES2) {
|
|
707
|
+
const candidate = (0, import_path2.resolve)(cwd, filename);
|
|
708
|
+
if ((0, import_fs2.existsSync)(candidate)) {
|
|
709
|
+
configPath = candidate;
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
if (!configPath) return null;
|
|
714
|
+
const { createJiti } = await import("jiti");
|
|
715
|
+
const jiti = createJiti(cwd, { interopDefault: true });
|
|
716
|
+
const mod = await jiti.import(configPath);
|
|
717
|
+
const config = (_a = mod.default) != null ? _a : mod;
|
|
718
|
+
return config;
|
|
719
|
+
}
|
|
720
|
+
async function autoLoadEnvFiles(cwd) {
|
|
721
|
+
let dotenv;
|
|
722
|
+
try {
|
|
723
|
+
dotenv = await import("dotenv");
|
|
724
|
+
} catch (e) {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
for (const file of AUTO_ENV_FILES) {
|
|
728
|
+
const filePath = (0, import_path2.resolve)(cwd, file);
|
|
729
|
+
if ((0, import_fs2.existsSync)(filePath)) {
|
|
730
|
+
dotenv.config({ path: filePath });
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
643
735
|
// src/cli.ts
|
|
644
736
|
var import_chalk2 = __toESM(require("chalk"), 1);
|
|
645
|
-
var
|
|
646
|
-
var
|
|
737
|
+
var import_fs3 = require("fs");
|
|
738
|
+
var import_path3 = require("path");
|
|
647
739
|
var HELP = `
|
|
648
740
|
Usage: @auth-gate/rbac <command> [options]
|
|
649
741
|
|
|
@@ -658,7 +750,14 @@ Options (sync):
|
|
|
658
750
|
--strict Treat warnings as errors (recommended for CI/CD)
|
|
659
751
|
--json Output diff as JSON
|
|
660
752
|
|
|
661
|
-
|
|
753
|
+
Options (init):
|
|
754
|
+
--config Generate a starter authgate.config.ts instead
|
|
755
|
+
|
|
756
|
+
Config file:
|
|
757
|
+
If authgate.config.ts exists, env vars and connection details are
|
|
758
|
+
loaded from it automatically. See defineConfig() for the API.
|
|
759
|
+
|
|
760
|
+
Environment (when no authgate.config.ts):
|
|
662
761
|
AUTHGATE_API_KEY Your project API key (required for sync)
|
|
663
762
|
AUTHGATE_BASE_URL AuthGate instance URL (required for sync)
|
|
664
763
|
`;
|
|
@@ -670,7 +769,8 @@ async function main() {
|
|
|
670
769
|
process.exit(0);
|
|
671
770
|
}
|
|
672
771
|
if (command === "init") {
|
|
673
|
-
|
|
772
|
+
const configFlag = args.includes("--config");
|
|
773
|
+
await runInit({ config: configFlag });
|
|
674
774
|
return;
|
|
675
775
|
}
|
|
676
776
|
if (command === "check") {
|
|
@@ -689,9 +789,29 @@ async function main() {
|
|
|
689
789
|
console.log(HELP);
|
|
690
790
|
process.exit(1);
|
|
691
791
|
}
|
|
692
|
-
async function runInit() {
|
|
693
|
-
|
|
694
|
-
|
|
792
|
+
async function runInit(opts) {
|
|
793
|
+
if (opts.config) {
|
|
794
|
+
const configPath2 = (0, import_path3.resolve)(process.cwd(), "authgate.config.ts");
|
|
795
|
+
if ((0, import_fs3.existsSync)(configPath2)) {
|
|
796
|
+
console.error(import_chalk2.default.yellow(`Config file already exists: ${configPath2}`));
|
|
797
|
+
process.exit(1);
|
|
798
|
+
}
|
|
799
|
+
const template2 = `import { defineConfig } from "@auth-gate/core";
|
|
800
|
+
|
|
801
|
+
export default defineConfig({
|
|
802
|
+
connection: {
|
|
803
|
+
baseUrl: process.env.AUTHGATE_URL,
|
|
804
|
+
apiKey: process.env.AUTHGATE_API_KEY,
|
|
805
|
+
},
|
|
806
|
+
});
|
|
807
|
+
`;
|
|
808
|
+
(0, import_fs3.writeFileSync)(configPath2, template2, "utf-8");
|
|
809
|
+
console.log(import_chalk2.default.green(`Created ${configPath2}`));
|
|
810
|
+
console.log(import_chalk2.default.dim("Edit connection details, then run: npx @auth-gate/rbac sync"));
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
const configPath = (0, import_path3.resolve)(process.cwd(), "authgate.rbac.ts");
|
|
814
|
+
if ((0, import_fs3.existsSync)(configPath)) {
|
|
695
815
|
console.error(import_chalk2.default.yellow(`Config file already exists: ${configPath}`));
|
|
696
816
|
process.exit(1);
|
|
697
817
|
}
|
|
@@ -726,6 +846,7 @@ export const rbac = defineRbac({
|
|
|
726
846
|
},
|
|
727
847
|
viewer: {
|
|
728
848
|
name: "Viewer",
|
|
849
|
+
isDefault: true,
|
|
729
850
|
grants: {
|
|
730
851
|
documents: { read: true },
|
|
731
852
|
},
|
|
@@ -736,14 +857,16 @@ export const rbac = defineRbac({
|
|
|
736
857
|
// Default export for CLI compatibility
|
|
737
858
|
export default rbac;
|
|
738
859
|
`;
|
|
739
|
-
(0,
|
|
860
|
+
(0, import_fs3.writeFileSync)(configPath, template, "utf-8");
|
|
740
861
|
console.log(import_chalk2.default.green(`Created ${configPath}`));
|
|
741
862
|
console.log(import_chalk2.default.dim("Edit your resources and roles, then run: npx @auth-gate/rbac sync"));
|
|
742
863
|
}
|
|
743
864
|
async function runCheck() {
|
|
865
|
+
const cwd = process.cwd();
|
|
866
|
+
const projectConfig = await resolveProjectConfig(cwd);
|
|
744
867
|
let config;
|
|
745
868
|
try {
|
|
746
|
-
config = await
|
|
869
|
+
config = projectConfig.rbacConfigPath ? await loadRbacConfigFromPath(projectConfig.rbacConfigPath, cwd) : await loadRbacConfig(cwd);
|
|
747
870
|
} catch (err) {
|
|
748
871
|
console.error(import_chalk2.default.red(`Config error: ${err.message}`));
|
|
749
872
|
process.exit(1);
|
|
@@ -758,19 +881,24 @@ async function runCheck() {
|
|
|
758
881
|
console.log(import_chalk2.default.dim(`${resourceCount} resource${resourceCount > 1 ? "s" : ""}, ${roleCount} role${roleCount > 1 ? "s" : ""}, ${permCount} permission${permCount > 1 ? "s" : ""}`));
|
|
759
882
|
}
|
|
760
883
|
async function runSync(opts) {
|
|
761
|
-
const
|
|
762
|
-
const
|
|
884
|
+
const cwd = process.cwd();
|
|
885
|
+
const projectConfig = await resolveProjectConfig(cwd);
|
|
886
|
+
const { baseUrl, apiKey } = projectConfig.connection;
|
|
763
887
|
if (!apiKey) {
|
|
764
|
-
console.error(import_chalk2.default.red(
|
|
888
|
+
console.error(import_chalk2.default.red(
|
|
889
|
+
"Missing API key. Set AUTHGATE_API_KEY or configure connection in authgate.config.ts."
|
|
890
|
+
));
|
|
765
891
|
process.exit(1);
|
|
766
892
|
}
|
|
767
893
|
if (!baseUrl) {
|
|
768
|
-
console.error(import_chalk2.default.red(
|
|
894
|
+
console.error(import_chalk2.default.red(
|
|
895
|
+
"Missing base URL. Set AUTHGATE_BASE_URL or configure connection in authgate.config.ts."
|
|
896
|
+
));
|
|
769
897
|
process.exit(1);
|
|
770
898
|
}
|
|
771
899
|
let config;
|
|
772
900
|
try {
|
|
773
|
-
config = await
|
|
901
|
+
config = projectConfig.rbacConfigPath ? await loadRbacConfigFromPath(projectConfig.rbacConfigPath, cwd) : await loadRbacConfig(cwd);
|
|
774
902
|
} catch (err) {
|
|
775
903
|
console.error(import_chalk2.default.red(`Config error: ${err.message}`));
|
|
776
904
|
process.exit(1);
|
package/dist/cli.mjs
CHANGED
|
@@ -4,13 +4,80 @@ import {
|
|
|
4
4
|
__spreadValues,
|
|
5
5
|
computeRbacDiff,
|
|
6
6
|
formatRbacDiff,
|
|
7
|
-
loadRbacConfig
|
|
8
|
-
|
|
7
|
+
loadRbacConfig,
|
|
8
|
+
loadRbacConfigFromPath
|
|
9
|
+
} from "./chunk-5CSBBETH.mjs";
|
|
10
|
+
|
|
11
|
+
// src/project-config-loader.ts
|
|
12
|
+
import { resolve } from "path";
|
|
13
|
+
import { existsSync } from "fs";
|
|
14
|
+
var CONFIG_FILENAMES = [
|
|
15
|
+
"authgate.config.ts",
|
|
16
|
+
"authgate.config.js",
|
|
17
|
+
"authgate.config.mjs"
|
|
18
|
+
];
|
|
19
|
+
var AUTO_ENV_FILES = [".env", ".env.local"];
|
|
20
|
+
async function resolveProjectConfig(cwd) {
|
|
21
|
+
var _a, _b, _c;
|
|
22
|
+
await autoLoadEnvFiles(cwd);
|
|
23
|
+
const config = await loadProjectConfigFile(cwd);
|
|
24
|
+
if (!config) {
|
|
25
|
+
return {
|
|
26
|
+
connection: {
|
|
27
|
+
baseUrl: process.env.AUTHGATE_BASE_URL,
|
|
28
|
+
apiKey: process.env.AUTHGATE_API_KEY
|
|
29
|
+
},
|
|
30
|
+
rbacConfigPath: null,
|
|
31
|
+
billingConfigPath: null
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const rawConnection = (_a = config.connection) != null ? _a : {};
|
|
35
|
+
const connection = {
|
|
36
|
+
baseUrl: (_b = rawConnection.baseUrl) != null ? _b : process.env.AUTHGATE_BASE_URL,
|
|
37
|
+
apiKey: (_c = rawConnection.apiKey) != null ? _c : process.env.AUTHGATE_API_KEY
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
connection,
|
|
41
|
+
rbacConfigPath: config.rbacConfig ? resolve(cwd, config.rbacConfig) : null,
|
|
42
|
+
billingConfigPath: config.billingConfig ? resolve(cwd, config.billingConfig) : null
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function loadProjectConfigFile(cwd) {
|
|
46
|
+
var _a;
|
|
47
|
+
let configPath = null;
|
|
48
|
+
for (const filename of CONFIG_FILENAMES) {
|
|
49
|
+
const candidate = resolve(cwd, filename);
|
|
50
|
+
if (existsSync(candidate)) {
|
|
51
|
+
configPath = candidate;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (!configPath) return null;
|
|
56
|
+
const { createJiti } = await import("jiti");
|
|
57
|
+
const jiti = createJiti(cwd, { interopDefault: true });
|
|
58
|
+
const mod = await jiti.import(configPath);
|
|
59
|
+
const config = (_a = mod.default) != null ? _a : mod;
|
|
60
|
+
return config;
|
|
61
|
+
}
|
|
62
|
+
async function autoLoadEnvFiles(cwd) {
|
|
63
|
+
let dotenv;
|
|
64
|
+
try {
|
|
65
|
+
dotenv = await import("dotenv");
|
|
66
|
+
} catch (e) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
for (const file of AUTO_ENV_FILES) {
|
|
70
|
+
const filePath = resolve(cwd, file);
|
|
71
|
+
if (existsSync(filePath)) {
|
|
72
|
+
dotenv.config({ path: filePath });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
9
76
|
|
|
10
77
|
// src/cli.ts
|
|
11
78
|
import chalk from "chalk";
|
|
12
|
-
import { writeFileSync, existsSync } from "fs";
|
|
13
|
-
import { resolve } from "path";
|
|
79
|
+
import { writeFileSync, existsSync as existsSync2 } from "fs";
|
|
80
|
+
import { resolve as resolve2 } from "path";
|
|
14
81
|
var HELP = `
|
|
15
82
|
Usage: @auth-gate/rbac <command> [options]
|
|
16
83
|
|
|
@@ -25,7 +92,14 @@ Options (sync):
|
|
|
25
92
|
--strict Treat warnings as errors (recommended for CI/CD)
|
|
26
93
|
--json Output diff as JSON
|
|
27
94
|
|
|
28
|
-
|
|
95
|
+
Options (init):
|
|
96
|
+
--config Generate a starter authgate.config.ts instead
|
|
97
|
+
|
|
98
|
+
Config file:
|
|
99
|
+
If authgate.config.ts exists, env vars and connection details are
|
|
100
|
+
loaded from it automatically. See defineConfig() for the API.
|
|
101
|
+
|
|
102
|
+
Environment (when no authgate.config.ts):
|
|
29
103
|
AUTHGATE_API_KEY Your project API key (required for sync)
|
|
30
104
|
AUTHGATE_BASE_URL AuthGate instance URL (required for sync)
|
|
31
105
|
`;
|
|
@@ -37,7 +111,8 @@ async function main() {
|
|
|
37
111
|
process.exit(0);
|
|
38
112
|
}
|
|
39
113
|
if (command === "init") {
|
|
40
|
-
|
|
114
|
+
const configFlag = args.includes("--config");
|
|
115
|
+
await runInit({ config: configFlag });
|
|
41
116
|
return;
|
|
42
117
|
}
|
|
43
118
|
if (command === "check") {
|
|
@@ -56,9 +131,29 @@ async function main() {
|
|
|
56
131
|
console.log(HELP);
|
|
57
132
|
process.exit(1);
|
|
58
133
|
}
|
|
59
|
-
async function runInit() {
|
|
60
|
-
|
|
61
|
-
|
|
134
|
+
async function runInit(opts) {
|
|
135
|
+
if (opts.config) {
|
|
136
|
+
const configPath2 = resolve2(process.cwd(), "authgate.config.ts");
|
|
137
|
+
if (existsSync2(configPath2)) {
|
|
138
|
+
console.error(chalk.yellow(`Config file already exists: ${configPath2}`));
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
const template2 = `import { defineConfig } from "@auth-gate/core";
|
|
142
|
+
|
|
143
|
+
export default defineConfig({
|
|
144
|
+
connection: {
|
|
145
|
+
baseUrl: process.env.AUTHGATE_URL,
|
|
146
|
+
apiKey: process.env.AUTHGATE_API_KEY,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
`;
|
|
150
|
+
writeFileSync(configPath2, template2, "utf-8");
|
|
151
|
+
console.log(chalk.green(`Created ${configPath2}`));
|
|
152
|
+
console.log(chalk.dim("Edit connection details, then run: npx @auth-gate/rbac sync"));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const configPath = resolve2(process.cwd(), "authgate.rbac.ts");
|
|
156
|
+
if (existsSync2(configPath)) {
|
|
62
157
|
console.error(chalk.yellow(`Config file already exists: ${configPath}`));
|
|
63
158
|
process.exit(1);
|
|
64
159
|
}
|
|
@@ -93,6 +188,7 @@ export const rbac = defineRbac({
|
|
|
93
188
|
},
|
|
94
189
|
viewer: {
|
|
95
190
|
name: "Viewer",
|
|
191
|
+
isDefault: true,
|
|
96
192
|
grants: {
|
|
97
193
|
documents: { read: true },
|
|
98
194
|
},
|
|
@@ -108,9 +204,11 @@ export default rbac;
|
|
|
108
204
|
console.log(chalk.dim("Edit your resources and roles, then run: npx @auth-gate/rbac sync"));
|
|
109
205
|
}
|
|
110
206
|
async function runCheck() {
|
|
207
|
+
const cwd = process.cwd();
|
|
208
|
+
const projectConfig = await resolveProjectConfig(cwd);
|
|
111
209
|
let config;
|
|
112
210
|
try {
|
|
113
|
-
config = await
|
|
211
|
+
config = projectConfig.rbacConfigPath ? await loadRbacConfigFromPath(projectConfig.rbacConfigPath, cwd) : await loadRbacConfig(cwd);
|
|
114
212
|
} catch (err) {
|
|
115
213
|
console.error(chalk.red(`Config error: ${err.message}`));
|
|
116
214
|
process.exit(1);
|
|
@@ -125,19 +223,24 @@ async function runCheck() {
|
|
|
125
223
|
console.log(chalk.dim(`${resourceCount} resource${resourceCount > 1 ? "s" : ""}, ${roleCount} role${roleCount > 1 ? "s" : ""}, ${permCount} permission${permCount > 1 ? "s" : ""}`));
|
|
126
224
|
}
|
|
127
225
|
async function runSync(opts) {
|
|
128
|
-
const
|
|
129
|
-
const
|
|
226
|
+
const cwd = process.cwd();
|
|
227
|
+
const projectConfig = await resolveProjectConfig(cwd);
|
|
228
|
+
const { baseUrl, apiKey } = projectConfig.connection;
|
|
130
229
|
if (!apiKey) {
|
|
131
|
-
console.error(chalk.red(
|
|
230
|
+
console.error(chalk.red(
|
|
231
|
+
"Missing API key. Set AUTHGATE_API_KEY or configure connection in authgate.config.ts."
|
|
232
|
+
));
|
|
132
233
|
process.exit(1);
|
|
133
234
|
}
|
|
134
235
|
if (!baseUrl) {
|
|
135
|
-
console.error(chalk.red(
|
|
236
|
+
console.error(chalk.red(
|
|
237
|
+
"Missing base URL. Set AUTHGATE_BASE_URL or configure connection in authgate.config.ts."
|
|
238
|
+
));
|
|
136
239
|
process.exit(1);
|
|
137
240
|
}
|
|
138
241
|
let config;
|
|
139
242
|
try {
|
|
140
|
-
config = await
|
|
243
|
+
config = projectConfig.rbacConfigPath ? await loadRbacConfigFromPath(projectConfig.rbacConfigPath, cwd) : await loadRbacConfig(cwd);
|
|
141
244
|
} catch (err) {
|
|
142
245
|
console.error(chalk.red(`Config error: ${err.message}`));
|
|
143
246
|
process.exit(1);
|
package/dist/index.cjs
CHANGED
|
@@ -32,10 +32,13 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
RbacSyncClient: () => RbacSyncClient,
|
|
34
34
|
computeRbacDiff: () => computeRbacDiff,
|
|
35
|
+
createRoleManagement: () => createRoleManagement,
|
|
36
|
+
defineConfig: () => import_core.defineConfig,
|
|
35
37
|
defineRbac: () => defineRbac,
|
|
36
38
|
formatRbacDiff: () => formatRbacDiff,
|
|
37
39
|
hashConditionSource: () => hashConditionSource,
|
|
38
40
|
loadRbacConfig: () => loadRbacConfig,
|
|
41
|
+
loadRbacConfigFromPath: () => loadRbacConfigFromPath,
|
|
39
42
|
validateRbacConfig: () => validateRbacConfig
|
|
40
43
|
});
|
|
41
44
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -169,6 +172,21 @@ function validateRbacConfig(config) {
|
|
|
169
172
|
}
|
|
170
173
|
}
|
|
171
174
|
}
|
|
175
|
+
const defaultRoles = [];
|
|
176
|
+
for (const key of roleKeys) {
|
|
177
|
+
const role = roles[key];
|
|
178
|
+
if (role.isDefault !== void 0 && typeof role.isDefault !== "boolean") {
|
|
179
|
+
throw new Error(`Role "${key}".isDefault must be a boolean.`);
|
|
180
|
+
}
|
|
181
|
+
if (role.isDefault === true) {
|
|
182
|
+
defaultRoles.push(key);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (defaultRoles.length > 1) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Only one role may have isDefault: true. Found ${defaultRoles.length}: ${defaultRoles.join(", ")}.`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
172
190
|
for (const key of roleKeys) {
|
|
173
191
|
const role = roles[key];
|
|
174
192
|
if (role.inherits !== void 0) {
|
|
@@ -277,7 +295,6 @@ var CONFIG_FILENAMES = [
|
|
|
277
295
|
"authgate.rbac.mjs"
|
|
278
296
|
];
|
|
279
297
|
async function loadRbacConfig(cwd) {
|
|
280
|
-
var _a, _b;
|
|
281
298
|
let configPath = null;
|
|
282
299
|
for (const filename of CONFIG_FILENAMES) {
|
|
283
300
|
const candidate = (0, import_path.resolve)(cwd, filename);
|
|
@@ -292,8 +309,12 @@ async function loadRbacConfig(cwd) {
|
|
|
292
309
|
Run \`npx @auth-gate/rbac init\` to create one.`
|
|
293
310
|
);
|
|
294
311
|
}
|
|
312
|
+
return loadRbacConfigFromPath(configPath, cwd);
|
|
313
|
+
}
|
|
314
|
+
async function loadRbacConfigFromPath(configPath, cwd) {
|
|
315
|
+
var _a, _b;
|
|
295
316
|
const { createJiti } = await import("jiti");
|
|
296
|
-
const jiti = createJiti(cwd, { interopDefault: true });
|
|
317
|
+
const jiti = createJiti(cwd != null ? cwd : process.cwd(), { interopDefault: true });
|
|
297
318
|
const mod = await jiti.import(configPath);
|
|
298
319
|
const raw = (_b = (_a = mod.default) != null ? _a : mod.rbac) != null ? _b : mod;
|
|
299
320
|
const config = raw && typeof raw === "object" && "_config" in raw ? raw._config : raw;
|
|
@@ -321,7 +342,7 @@ function hashConditionSource(fn) {
|
|
|
321
342
|
return createHash("sha256").update(fn.toString()).digest("hex").slice(0, 16);
|
|
322
343
|
}
|
|
323
344
|
function computeRbacDiff(config, server, memberCounts) {
|
|
324
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
345
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
|
|
325
346
|
const resourceOps = [];
|
|
326
347
|
const roleOps = [];
|
|
327
348
|
const conditionOps = [];
|
|
@@ -412,6 +433,11 @@ function computeRbacDiff(config, server, memberCounts) {
|
|
|
412
433
|
if (existingInherits !== configInherits) {
|
|
413
434
|
changes.push("inherits changed");
|
|
414
435
|
}
|
|
436
|
+
const existingDefault = (_l = existing.isDefault) != null ? _l : false;
|
|
437
|
+
const configDefault = (_m = role.isDefault) != null ? _m : false;
|
|
438
|
+
if (existingDefault !== configDefault) {
|
|
439
|
+
changes.push(`isDefault: ${existingDefault} -> ${configDefault}`);
|
|
440
|
+
}
|
|
415
441
|
if (changes.length > 0) {
|
|
416
442
|
roleOps.push({ type: "update", key, role, existing, changes });
|
|
417
443
|
}
|
|
@@ -421,7 +447,7 @@ function computeRbacDiff(config, server, memberCounts) {
|
|
|
421
447
|
for (const [key, role] of serverRoleByKey) {
|
|
422
448
|
if (renameMap.has(key)) continue;
|
|
423
449
|
if (role.isActive) {
|
|
424
|
-
const members = (
|
|
450
|
+
const members = (_n = memberCounts[role.configKey]) != null ? _n : 0;
|
|
425
451
|
if (members > 0) hasDestructive = true;
|
|
426
452
|
roleOps.push({ type: "archive", key, existing: role, assignedMembers: members });
|
|
427
453
|
}
|
|
@@ -571,6 +597,9 @@ function formatRbacDiff(diff, dryRun) {
|
|
|
571
597
|
import_chalk.default.dim(` inherits: [${[...op.role.inherits].join(", ")}]`)
|
|
572
598
|
);
|
|
573
599
|
}
|
|
600
|
+
if (op.role.isDefault) {
|
|
601
|
+
lines.push(import_chalk.default.dim(` isDefault: true`));
|
|
602
|
+
}
|
|
574
603
|
} else if (op.type === "update") {
|
|
575
604
|
lines.push(import_chalk.default.yellow(` ~ UPDATE role "${op.key}"`));
|
|
576
605
|
for (const change of op.changes) {
|
|
@@ -641,8 +670,96 @@ function formatRbacDiff(diff, dryRun) {
|
|
|
641
670
|
return lines.join("\n");
|
|
642
671
|
}
|
|
643
672
|
|
|
673
|
+
// src/role-management.ts
|
|
674
|
+
function createRoleManagement(config) {
|
|
675
|
+
const baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
676
|
+
const apiKey = config.apiKey;
|
|
677
|
+
async function request(method, path, body) {
|
|
678
|
+
var _a, _b;
|
|
679
|
+
const url = `${baseUrl}${path}`;
|
|
680
|
+
const headers = {
|
|
681
|
+
Authorization: `Bearer ${apiKey}`,
|
|
682
|
+
"Content-Type": "application/json"
|
|
683
|
+
};
|
|
684
|
+
const res = await fetch(url, {
|
|
685
|
+
method,
|
|
686
|
+
headers,
|
|
687
|
+
body: body ? JSON.stringify(body) : void 0
|
|
688
|
+
});
|
|
689
|
+
if (!res.ok) {
|
|
690
|
+
const text = await res.text();
|
|
691
|
+
let message;
|
|
692
|
+
try {
|
|
693
|
+
const json = JSON.parse(text);
|
|
694
|
+
message = (_b = (_a = json.error) != null ? _a : json.message) != null ? _b : "Unknown error";
|
|
695
|
+
} catch (e) {
|
|
696
|
+
message = text.length > 500 ? text.slice(0, 500) + "..." : text;
|
|
697
|
+
}
|
|
698
|
+
if (apiKey) {
|
|
699
|
+
message = message.replaceAll(apiKey, "[REDACTED]");
|
|
700
|
+
}
|
|
701
|
+
throw new Error(`API error (${res.status}): ${message}`);
|
|
702
|
+
}
|
|
703
|
+
return res.json();
|
|
704
|
+
}
|
|
705
|
+
return {
|
|
706
|
+
async listRoles() {
|
|
707
|
+
const result = await request("GET", "/api/v1/roles");
|
|
708
|
+
return result.data.map(toRole);
|
|
709
|
+
},
|
|
710
|
+
async createRole(input) {
|
|
711
|
+
var _a, _b;
|
|
712
|
+
const result = await request("POST", "/api/v1/roles", {
|
|
713
|
+
key: input.key,
|
|
714
|
+
name: input.name,
|
|
715
|
+
description: input.description,
|
|
716
|
+
is_default: (_a = input.isDefault) != null ? _a : false,
|
|
717
|
+
permissions: (_b = input.permissions) != null ? _b : []
|
|
718
|
+
});
|
|
719
|
+
return toRole(result.role);
|
|
720
|
+
},
|
|
721
|
+
async updateRole(roleId, input) {
|
|
722
|
+
const body = {};
|
|
723
|
+
if (input.name !== void 0) body.name = input.name;
|
|
724
|
+
if (input.description !== void 0) body.description = input.description;
|
|
725
|
+
if (input.permissions !== void 0) body.permissions = input.permissions;
|
|
726
|
+
if (input.isDefault !== void 0) body.is_default = input.isDefault;
|
|
727
|
+
const result = await request(
|
|
728
|
+
"PATCH",
|
|
729
|
+
`/api/v1/roles/${encodeURIComponent(roleId)}`,
|
|
730
|
+
body
|
|
731
|
+
);
|
|
732
|
+
return toRole(result.role);
|
|
733
|
+
},
|
|
734
|
+
async deleteRole(roleId) {
|
|
735
|
+
await request(
|
|
736
|
+
"DELETE",
|
|
737
|
+
`/api/v1/roles/${encodeURIComponent(roleId)}`
|
|
738
|
+
);
|
|
739
|
+
},
|
|
740
|
+
async setDefaultRole(roleId) {
|
|
741
|
+
return this.updateRole(roleId, { isDefault: true });
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
function toRole(raw) {
|
|
746
|
+
return {
|
|
747
|
+
id: raw.id,
|
|
748
|
+
projectId: raw.project_id,
|
|
749
|
+
key: raw.key,
|
|
750
|
+
name: raw.name,
|
|
751
|
+
description: raw.description,
|
|
752
|
+
isDefault: raw.is_default,
|
|
753
|
+
permissions: raw.permissions,
|
|
754
|
+
createdAt: raw.created_at,
|
|
755
|
+
updatedAt: raw.updated_at
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
|
|
644
759
|
// src/index.ts
|
|
760
|
+
var import_core = require("@auth-gate/core");
|
|
645
761
|
function defineRbac(config, opts) {
|
|
762
|
+
var _a;
|
|
646
763
|
if ((opts == null ? void 0 : opts.validate) !== false) {
|
|
647
764
|
validateRbacConfig(config);
|
|
648
765
|
}
|
|
@@ -656,7 +773,7 @@ function defineRbac(config, opts) {
|
|
|
656
773
|
}
|
|
657
774
|
const roles = {};
|
|
658
775
|
for (const [key, role] of Object.entries(config.roles)) {
|
|
659
|
-
roles[key] = { key, name: role.name, grants: role.grants };
|
|
776
|
+
roles[key] = { key, name: role.name, grants: role.grants, isDefault: (_a = role.isDefault) != null ? _a : false };
|
|
660
777
|
}
|
|
661
778
|
const permissions = {};
|
|
662
779
|
for (const [key, resource] of Object.entries(config.resources)) {
|
|
@@ -679,9 +796,12 @@ function defineRbac(config, opts) {
|
|
|
679
796
|
0 && (module.exports = {
|
|
680
797
|
RbacSyncClient,
|
|
681
798
|
computeRbacDiff,
|
|
799
|
+
createRoleManagement,
|
|
800
|
+
defineConfig,
|
|
682
801
|
defineRbac,
|
|
683
802
|
formatRbacDiff,
|
|
684
803
|
hashConditionSource,
|
|
685
804
|
loadRbacConfig,
|
|
805
|
+
loadRbacConfigFromPath,
|
|
686
806
|
validateRbacConfig
|
|
687
807
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { AuthGateConnection, AuthGateProjectConfig, defineConfig } from '@auth-gate/core';
|
|
2
|
+
|
|
1
3
|
/** A resource definition: declares available actions and optional scopes. */
|
|
2
4
|
interface ResourceConfig {
|
|
3
5
|
actions: readonly string[];
|
|
@@ -39,6 +41,8 @@ interface RoleConfig {
|
|
|
39
41
|
description?: string;
|
|
40
42
|
inherits?: readonly string[];
|
|
41
43
|
grants: Record<string, Record<string, GrantValue>>;
|
|
44
|
+
/** Mark this role as the default for new org members. Only one role may be default. */
|
|
45
|
+
isDefault?: boolean;
|
|
42
46
|
/** Previous config key if this role was renamed. */
|
|
43
47
|
renamedFrom?: string;
|
|
44
48
|
}
|
|
@@ -80,6 +84,63 @@ type InferScopes<T, Resource extends string> = T extends {
|
|
|
80
84
|
type InferConditionKeys<T> = T extends {
|
|
81
85
|
conditions: infer C;
|
|
82
86
|
} ? keyof C & string : never;
|
|
87
|
+
/** Extract role keys that have `isDefault: true`. */
|
|
88
|
+
type DefaultRoleKeys<Roles> = {
|
|
89
|
+
[K in keyof Roles]: Roles[K] extends {
|
|
90
|
+
isDefault: true;
|
|
91
|
+
} ? K : never;
|
|
92
|
+
}[keyof Roles];
|
|
93
|
+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
94
|
+
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;
|
|
95
|
+
/**
|
|
96
|
+
* Compile-time constraint: if more than one role has `isDefault: true`,
|
|
97
|
+
* intersect those roles' `isDefault` with an error string so TypeScript
|
|
98
|
+
* produces a descriptive message.
|
|
99
|
+
*
|
|
100
|
+
* Error reads: Type 'true' is not assignable to type '"ERROR: ..."'.
|
|
101
|
+
*/
|
|
102
|
+
type ValidateSingleDefault<T> = T extends {
|
|
103
|
+
roles: infer Roles;
|
|
104
|
+
} ? IsUnion<DefaultRoleKeys<Roles>> extends true ? {
|
|
105
|
+
roles: {
|
|
106
|
+
[K in DefaultRoleKeys<Roles> & keyof Roles]: {
|
|
107
|
+
isDefault: "ERROR: Only one role may have isDefault: true";
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
} : {} : {};
|
|
111
|
+
/** Extract valid action string literals from a resource config. */
|
|
112
|
+
type ValidActions<R> = R extends {
|
|
113
|
+
actions: readonly (infer U)[];
|
|
114
|
+
} ? U & string : never;
|
|
115
|
+
/**
|
|
116
|
+
* Compile-time constraint: validates grant resource/action keys against
|
|
117
|
+
* declared resources at the type level.
|
|
118
|
+
*
|
|
119
|
+
* - **Invalid resource key** → descriptive error naming the resource.
|
|
120
|
+
* - **Invalid action key** → descriptive error listing valid actions.
|
|
121
|
+
* - **Valid grants** → passes through original types transparently.
|
|
122
|
+
*
|
|
123
|
+
* Used as `T & ValidateConfig<T>` in the `defineRbac()` parameter type.
|
|
124
|
+
*
|
|
125
|
+
* @note Extra properties on role objects (e.g. `isFakeProp`) are caught
|
|
126
|
+
* by the runtime `validateRbacConfig()` which runs by default.
|
|
127
|
+
*/
|
|
128
|
+
type ValidateConfig<T> = T extends {
|
|
129
|
+
resources: infer R extends Record<string, ResourceConfig>;
|
|
130
|
+
roles: infer Roles;
|
|
131
|
+
} ? {
|
|
132
|
+
roles: {
|
|
133
|
+
[RK in keyof Roles]: Roles[RK] extends {
|
|
134
|
+
grants: infer G;
|
|
135
|
+
} ? {
|
|
136
|
+
grants: {
|
|
137
|
+
[ResK in keyof G]: ResK extends keyof R ? keyof G[ResK] extends ValidActions<R[ResK & keyof R]> ? {
|
|
138
|
+
[ActK in keyof G[ResK]]: G[ResK][ActK];
|
|
139
|
+
} : `ERROR: invalid action(s) in "${ResK & string}" grants. Valid actions: ${ValidActions<R[ResK & keyof R]>}` : `ERROR: resource "${ResK & string}" is not declared in resources`;
|
|
140
|
+
};
|
|
141
|
+
} : Roles[RK];
|
|
142
|
+
};
|
|
143
|
+
} : {};
|
|
83
144
|
/** A structured resource with a `key` constant and an `actions` map of `"resource:action"` strings. */
|
|
84
145
|
type StructuredResource<T, K extends string> = {
|
|
85
146
|
readonly key: K;
|
|
@@ -91,7 +152,7 @@ type StructuredResource<T, K extends string> = {
|
|
|
91
152
|
type StructuredResources<T> = {
|
|
92
153
|
readonly [K in InferResourceKeys<T>]: StructuredResource<T, K>;
|
|
93
154
|
};
|
|
94
|
-
/** A structured role with `key`, `name`, and `
|
|
155
|
+
/** A structured role with `key`, `name`, `grants`, and optional `isDefault` from the config. */
|
|
95
156
|
type StructuredRole<T, K extends string> = {
|
|
96
157
|
readonly key: K;
|
|
97
158
|
readonly name: T extends {
|
|
@@ -104,6 +165,11 @@ type StructuredRole<T, K extends string> = {
|
|
|
104
165
|
} ? K extends keyof R ? R[K] extends {
|
|
105
166
|
grants: infer G;
|
|
106
167
|
} ? Readonly<G> : {} : {} : {};
|
|
168
|
+
readonly isDefault: T extends {
|
|
169
|
+
roles: infer R;
|
|
170
|
+
} ? K extends keyof R ? R[K] extends {
|
|
171
|
+
isDefault: infer D;
|
|
172
|
+
} ? D : false : false : false;
|
|
107
173
|
};
|
|
108
174
|
/** Map of all roles as structured objects: `roles.admin.key` -> `"admin"`. */
|
|
109
175
|
type StructuredRoles<T> = {
|
|
@@ -121,7 +187,7 @@ type StructuredPermissions<T> = {
|
|
|
121
187
|
* Carries phantom types that downstream factories use to constrain
|
|
122
188
|
* permission/role string parameters -- enabling full IDE autocomplete.
|
|
123
189
|
*/
|
|
124
|
-
interface TypedRbac<T
|
|
190
|
+
interface TypedRbac<T> {
|
|
125
191
|
/** The raw RBAC config. Access resources, roles, etc. via `rbac._config`. */
|
|
126
192
|
readonly _config: T;
|
|
127
193
|
/**
|
|
@@ -155,6 +221,11 @@ type ResourceKey<A> = A extends TypedRbac<infer T> ? InferResourceKeys<T> : stri
|
|
|
155
221
|
declare function validateRbacConfig(config: unknown): RbacConfig;
|
|
156
222
|
|
|
157
223
|
declare function loadRbacConfig(cwd: string): Promise<RbacConfig>;
|
|
224
|
+
/**
|
|
225
|
+
* Load RBAC config from a specific file path.
|
|
226
|
+
* Used when `authgate.config.ts` specifies `rbacConfig`.
|
|
227
|
+
*/
|
|
228
|
+
declare function loadRbacConfigFromPath(configPath: string, cwd?: string): Promise<RbacConfig>;
|
|
158
229
|
|
|
159
230
|
interface ServerResource {
|
|
160
231
|
id: string;
|
|
@@ -172,6 +243,7 @@ interface ServerRole {
|
|
|
172
243
|
grants: Record<string, Record<string, GrantValue>> | null;
|
|
173
244
|
inherits: string[];
|
|
174
245
|
resolvedPermissions: string[];
|
|
246
|
+
isDefault: boolean;
|
|
175
247
|
isActive: boolean;
|
|
176
248
|
managedBy: string;
|
|
177
249
|
version: number;
|
|
@@ -272,12 +344,61 @@ declare class RbacSyncClient {
|
|
|
272
344
|
|
|
273
345
|
declare function formatRbacDiff(diff: DiffResult, dryRun: boolean): string;
|
|
274
346
|
|
|
347
|
+
interface RoleManagementConfig {
|
|
348
|
+
baseUrl: string;
|
|
349
|
+
apiKey: string;
|
|
350
|
+
}
|
|
351
|
+
interface Role {
|
|
352
|
+
id: string;
|
|
353
|
+
projectId: string;
|
|
354
|
+
key: string;
|
|
355
|
+
name: string;
|
|
356
|
+
description: string | null;
|
|
357
|
+
isDefault: boolean;
|
|
358
|
+
permissions: string[];
|
|
359
|
+
createdAt: string;
|
|
360
|
+
updatedAt: string;
|
|
361
|
+
}
|
|
362
|
+
interface CreateRoleInput {
|
|
363
|
+
key: string;
|
|
364
|
+
name: string;
|
|
365
|
+
description?: string;
|
|
366
|
+
isDefault?: boolean;
|
|
367
|
+
permissions?: string[];
|
|
368
|
+
}
|
|
369
|
+
interface UpdateRoleInput {
|
|
370
|
+
name?: string;
|
|
371
|
+
description?: string | null;
|
|
372
|
+
permissions?: string[];
|
|
373
|
+
isDefault?: boolean;
|
|
374
|
+
}
|
|
375
|
+
interface RoleManagementClient {
|
|
376
|
+
listRoles(): Promise<Role[]>;
|
|
377
|
+
createRole(input: CreateRoleInput): Promise<Role>;
|
|
378
|
+
updateRole(roleId: string, input: UpdateRoleInput): Promise<Role>;
|
|
379
|
+
deleteRole(roleId: string): Promise<void>;
|
|
380
|
+
setDefaultRole(roleId: string): Promise<Role>;
|
|
381
|
+
}
|
|
382
|
+
declare function createRoleManagement(config: RoleManagementConfig): RoleManagementClient;
|
|
383
|
+
|
|
384
|
+
/** Base config shape used as the generic constraint for `defineRbac()`. */
|
|
385
|
+
type DefineRbacConfig = {
|
|
386
|
+
resources: Record<string, ResourceConfig>;
|
|
387
|
+
conditions?: Record<string, ConditionFn>;
|
|
388
|
+
roles: Record<string, {
|
|
389
|
+
name: string;
|
|
390
|
+
description?: string;
|
|
391
|
+
inherits?: readonly string[];
|
|
392
|
+
isDefault?: boolean;
|
|
393
|
+
renamedFrom?: string;
|
|
394
|
+
grants: Record<string, Record<string, GrantValue>>;
|
|
395
|
+
}>;
|
|
396
|
+
};
|
|
275
397
|
/**
|
|
276
398
|
* Define your RBAC config. Use this in your `authgate.rbac.ts` config file.
|
|
277
399
|
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
* and server-side checks.
|
|
400
|
+
* Grants are validated at compile-time: if a role references an undeclared
|
|
401
|
+
* resource or action, TypeScript flags it as a type error.
|
|
281
402
|
*
|
|
282
403
|
* @example
|
|
283
404
|
* ```ts
|
|
@@ -310,8 +431,8 @@ declare function formatRbacDiff(diff: DiffResult, dryRun: boolean): string;
|
|
|
310
431
|
* rbac.permissions.documents.write // "documents:write"
|
|
311
432
|
* ```
|
|
312
433
|
*/
|
|
313
|
-
declare function defineRbac<const T extends
|
|
434
|
+
declare function defineRbac<const T extends DefineRbacConfig>(config: T & ValidateConfig<T> & ValidateSingleDefault<T>, opts?: {
|
|
314
435
|
validate?: boolean;
|
|
315
436
|
}): TypedRbac<T>;
|
|
316
437
|
|
|
317
|
-
export { type ApplyResult, type ConditionContext, type ConditionFn, type ConditionOp, type DiffResult, type GrantValue, type InferActions, type InferConditionKeys, type InferPermissions, type InferResourceKeys, type InferRoleKeys, type InferScopes, type Permission, type RbacConfig, RbacSyncClient, type RbacSyncClientConfig, type ResourceConfig, type ResourceKey, type ResourceOp, type RoleConfig, type RoleKey, type RoleOp, type ServerCondition, type ServerResource, type ServerRole, type ServerState, type TypedRbac, computeRbacDiff, defineRbac, formatRbacDiff, hashConditionSource, loadRbacConfig, validateRbacConfig };
|
|
438
|
+
export { type ApplyResult, type ConditionContext, type ConditionFn, type ConditionOp, type CreateRoleInput, type DiffResult, type GrantValue, type InferActions, type InferConditionKeys, type InferPermissions, type InferResourceKeys, type InferRoleKeys, type InferScopes, type Permission, type RbacConfig, RbacSyncClient, type RbacSyncClientConfig, type ResourceConfig, type ResourceKey, type ResourceOp, type Role, type RoleConfig, type RoleKey, type RoleManagementClient, type RoleManagementConfig, type RoleOp, type ServerCondition, type ServerResource, type ServerRole, type ServerState, type TypedRbac, type UpdateRoleInput, computeRbacDiff, createRoleManagement, defineRbac, formatRbacDiff, hashConditionSource, loadRbacConfig, loadRbacConfigFromPath, validateRbacConfig };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { AuthGateConnection, AuthGateProjectConfig, defineConfig } from '@auth-gate/core';
|
|
2
|
+
|
|
1
3
|
/** A resource definition: declares available actions and optional scopes. */
|
|
2
4
|
interface ResourceConfig {
|
|
3
5
|
actions: readonly string[];
|
|
@@ -39,6 +41,8 @@ interface RoleConfig {
|
|
|
39
41
|
description?: string;
|
|
40
42
|
inherits?: readonly string[];
|
|
41
43
|
grants: Record<string, Record<string, GrantValue>>;
|
|
44
|
+
/** Mark this role as the default for new org members. Only one role may be default. */
|
|
45
|
+
isDefault?: boolean;
|
|
42
46
|
/** Previous config key if this role was renamed. */
|
|
43
47
|
renamedFrom?: string;
|
|
44
48
|
}
|
|
@@ -80,6 +84,63 @@ type InferScopes<T, Resource extends string> = T extends {
|
|
|
80
84
|
type InferConditionKeys<T> = T extends {
|
|
81
85
|
conditions: infer C;
|
|
82
86
|
} ? keyof C & string : never;
|
|
87
|
+
/** Extract role keys that have `isDefault: true`. */
|
|
88
|
+
type DefaultRoleKeys<Roles> = {
|
|
89
|
+
[K in keyof Roles]: Roles[K] extends {
|
|
90
|
+
isDefault: true;
|
|
91
|
+
} ? K : never;
|
|
92
|
+
}[keyof Roles];
|
|
93
|
+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
94
|
+
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;
|
|
95
|
+
/**
|
|
96
|
+
* Compile-time constraint: if more than one role has `isDefault: true`,
|
|
97
|
+
* intersect those roles' `isDefault` with an error string so TypeScript
|
|
98
|
+
* produces a descriptive message.
|
|
99
|
+
*
|
|
100
|
+
* Error reads: Type 'true' is not assignable to type '"ERROR: ..."'.
|
|
101
|
+
*/
|
|
102
|
+
type ValidateSingleDefault<T> = T extends {
|
|
103
|
+
roles: infer Roles;
|
|
104
|
+
} ? IsUnion<DefaultRoleKeys<Roles>> extends true ? {
|
|
105
|
+
roles: {
|
|
106
|
+
[K in DefaultRoleKeys<Roles> & keyof Roles]: {
|
|
107
|
+
isDefault: "ERROR: Only one role may have isDefault: true";
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
} : {} : {};
|
|
111
|
+
/** Extract valid action string literals from a resource config. */
|
|
112
|
+
type ValidActions<R> = R extends {
|
|
113
|
+
actions: readonly (infer U)[];
|
|
114
|
+
} ? U & string : never;
|
|
115
|
+
/**
|
|
116
|
+
* Compile-time constraint: validates grant resource/action keys against
|
|
117
|
+
* declared resources at the type level.
|
|
118
|
+
*
|
|
119
|
+
* - **Invalid resource key** → descriptive error naming the resource.
|
|
120
|
+
* - **Invalid action key** → descriptive error listing valid actions.
|
|
121
|
+
* - **Valid grants** → passes through original types transparently.
|
|
122
|
+
*
|
|
123
|
+
* Used as `T & ValidateConfig<T>` in the `defineRbac()` parameter type.
|
|
124
|
+
*
|
|
125
|
+
* @note Extra properties on role objects (e.g. `isFakeProp`) are caught
|
|
126
|
+
* by the runtime `validateRbacConfig()` which runs by default.
|
|
127
|
+
*/
|
|
128
|
+
type ValidateConfig<T> = T extends {
|
|
129
|
+
resources: infer R extends Record<string, ResourceConfig>;
|
|
130
|
+
roles: infer Roles;
|
|
131
|
+
} ? {
|
|
132
|
+
roles: {
|
|
133
|
+
[RK in keyof Roles]: Roles[RK] extends {
|
|
134
|
+
grants: infer G;
|
|
135
|
+
} ? {
|
|
136
|
+
grants: {
|
|
137
|
+
[ResK in keyof G]: ResK extends keyof R ? keyof G[ResK] extends ValidActions<R[ResK & keyof R]> ? {
|
|
138
|
+
[ActK in keyof G[ResK]]: G[ResK][ActK];
|
|
139
|
+
} : `ERROR: invalid action(s) in "${ResK & string}" grants. Valid actions: ${ValidActions<R[ResK & keyof R]>}` : `ERROR: resource "${ResK & string}" is not declared in resources`;
|
|
140
|
+
};
|
|
141
|
+
} : Roles[RK];
|
|
142
|
+
};
|
|
143
|
+
} : {};
|
|
83
144
|
/** A structured resource with a `key` constant and an `actions` map of `"resource:action"` strings. */
|
|
84
145
|
type StructuredResource<T, K extends string> = {
|
|
85
146
|
readonly key: K;
|
|
@@ -91,7 +152,7 @@ type StructuredResource<T, K extends string> = {
|
|
|
91
152
|
type StructuredResources<T> = {
|
|
92
153
|
readonly [K in InferResourceKeys<T>]: StructuredResource<T, K>;
|
|
93
154
|
};
|
|
94
|
-
/** A structured role with `key`, `name`, and `
|
|
155
|
+
/** A structured role with `key`, `name`, `grants`, and optional `isDefault` from the config. */
|
|
95
156
|
type StructuredRole<T, K extends string> = {
|
|
96
157
|
readonly key: K;
|
|
97
158
|
readonly name: T extends {
|
|
@@ -104,6 +165,11 @@ type StructuredRole<T, K extends string> = {
|
|
|
104
165
|
} ? K extends keyof R ? R[K] extends {
|
|
105
166
|
grants: infer G;
|
|
106
167
|
} ? Readonly<G> : {} : {} : {};
|
|
168
|
+
readonly isDefault: T extends {
|
|
169
|
+
roles: infer R;
|
|
170
|
+
} ? K extends keyof R ? R[K] extends {
|
|
171
|
+
isDefault: infer D;
|
|
172
|
+
} ? D : false : false : false;
|
|
107
173
|
};
|
|
108
174
|
/** Map of all roles as structured objects: `roles.admin.key` -> `"admin"`. */
|
|
109
175
|
type StructuredRoles<T> = {
|
|
@@ -121,7 +187,7 @@ type StructuredPermissions<T> = {
|
|
|
121
187
|
* Carries phantom types that downstream factories use to constrain
|
|
122
188
|
* permission/role string parameters -- enabling full IDE autocomplete.
|
|
123
189
|
*/
|
|
124
|
-
interface TypedRbac<T
|
|
190
|
+
interface TypedRbac<T> {
|
|
125
191
|
/** The raw RBAC config. Access resources, roles, etc. via `rbac._config`. */
|
|
126
192
|
readonly _config: T;
|
|
127
193
|
/**
|
|
@@ -155,6 +221,11 @@ type ResourceKey<A> = A extends TypedRbac<infer T> ? InferResourceKeys<T> : stri
|
|
|
155
221
|
declare function validateRbacConfig(config: unknown): RbacConfig;
|
|
156
222
|
|
|
157
223
|
declare function loadRbacConfig(cwd: string): Promise<RbacConfig>;
|
|
224
|
+
/**
|
|
225
|
+
* Load RBAC config from a specific file path.
|
|
226
|
+
* Used when `authgate.config.ts` specifies `rbacConfig`.
|
|
227
|
+
*/
|
|
228
|
+
declare function loadRbacConfigFromPath(configPath: string, cwd?: string): Promise<RbacConfig>;
|
|
158
229
|
|
|
159
230
|
interface ServerResource {
|
|
160
231
|
id: string;
|
|
@@ -172,6 +243,7 @@ interface ServerRole {
|
|
|
172
243
|
grants: Record<string, Record<string, GrantValue>> | null;
|
|
173
244
|
inherits: string[];
|
|
174
245
|
resolvedPermissions: string[];
|
|
246
|
+
isDefault: boolean;
|
|
175
247
|
isActive: boolean;
|
|
176
248
|
managedBy: string;
|
|
177
249
|
version: number;
|
|
@@ -272,12 +344,61 @@ declare class RbacSyncClient {
|
|
|
272
344
|
|
|
273
345
|
declare function formatRbacDiff(diff: DiffResult, dryRun: boolean): string;
|
|
274
346
|
|
|
347
|
+
interface RoleManagementConfig {
|
|
348
|
+
baseUrl: string;
|
|
349
|
+
apiKey: string;
|
|
350
|
+
}
|
|
351
|
+
interface Role {
|
|
352
|
+
id: string;
|
|
353
|
+
projectId: string;
|
|
354
|
+
key: string;
|
|
355
|
+
name: string;
|
|
356
|
+
description: string | null;
|
|
357
|
+
isDefault: boolean;
|
|
358
|
+
permissions: string[];
|
|
359
|
+
createdAt: string;
|
|
360
|
+
updatedAt: string;
|
|
361
|
+
}
|
|
362
|
+
interface CreateRoleInput {
|
|
363
|
+
key: string;
|
|
364
|
+
name: string;
|
|
365
|
+
description?: string;
|
|
366
|
+
isDefault?: boolean;
|
|
367
|
+
permissions?: string[];
|
|
368
|
+
}
|
|
369
|
+
interface UpdateRoleInput {
|
|
370
|
+
name?: string;
|
|
371
|
+
description?: string | null;
|
|
372
|
+
permissions?: string[];
|
|
373
|
+
isDefault?: boolean;
|
|
374
|
+
}
|
|
375
|
+
interface RoleManagementClient {
|
|
376
|
+
listRoles(): Promise<Role[]>;
|
|
377
|
+
createRole(input: CreateRoleInput): Promise<Role>;
|
|
378
|
+
updateRole(roleId: string, input: UpdateRoleInput): Promise<Role>;
|
|
379
|
+
deleteRole(roleId: string): Promise<void>;
|
|
380
|
+
setDefaultRole(roleId: string): Promise<Role>;
|
|
381
|
+
}
|
|
382
|
+
declare function createRoleManagement(config: RoleManagementConfig): RoleManagementClient;
|
|
383
|
+
|
|
384
|
+
/** Base config shape used as the generic constraint for `defineRbac()`. */
|
|
385
|
+
type DefineRbacConfig = {
|
|
386
|
+
resources: Record<string, ResourceConfig>;
|
|
387
|
+
conditions?: Record<string, ConditionFn>;
|
|
388
|
+
roles: Record<string, {
|
|
389
|
+
name: string;
|
|
390
|
+
description?: string;
|
|
391
|
+
inherits?: readonly string[];
|
|
392
|
+
isDefault?: boolean;
|
|
393
|
+
renamedFrom?: string;
|
|
394
|
+
grants: Record<string, Record<string, GrantValue>>;
|
|
395
|
+
}>;
|
|
396
|
+
};
|
|
275
397
|
/**
|
|
276
398
|
* Define your RBAC config. Use this in your `authgate.rbac.ts` config file.
|
|
277
399
|
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
* and server-side checks.
|
|
400
|
+
* Grants are validated at compile-time: if a role references an undeclared
|
|
401
|
+
* resource or action, TypeScript flags it as a type error.
|
|
281
402
|
*
|
|
282
403
|
* @example
|
|
283
404
|
* ```ts
|
|
@@ -310,8 +431,8 @@ declare function formatRbacDiff(diff: DiffResult, dryRun: boolean): string;
|
|
|
310
431
|
* rbac.permissions.documents.write // "documents:write"
|
|
311
432
|
* ```
|
|
312
433
|
*/
|
|
313
|
-
declare function defineRbac<const T extends
|
|
434
|
+
declare function defineRbac<const T extends DefineRbacConfig>(config: T & ValidateConfig<T> & ValidateSingleDefault<T>, opts?: {
|
|
314
435
|
validate?: boolean;
|
|
315
436
|
}): TypedRbac<T>;
|
|
316
437
|
|
|
317
|
-
export { type ApplyResult, type ConditionContext, type ConditionFn, type ConditionOp, type DiffResult, type GrantValue, type InferActions, type InferConditionKeys, type InferPermissions, type InferResourceKeys, type InferRoleKeys, type InferScopes, type Permission, type RbacConfig, RbacSyncClient, type RbacSyncClientConfig, type ResourceConfig, type ResourceKey, type ResourceOp, type RoleConfig, type RoleKey, type RoleOp, type ServerCondition, type ServerResource, type ServerRole, type ServerState, type TypedRbac, computeRbacDiff, defineRbac, formatRbacDiff, hashConditionSource, loadRbacConfig, validateRbacConfig };
|
|
438
|
+
export { type ApplyResult, type ConditionContext, type ConditionFn, type ConditionOp, type CreateRoleInput, type DiffResult, type GrantValue, type InferActions, type InferConditionKeys, type InferPermissions, type InferResourceKeys, type InferRoleKeys, type InferScopes, type Permission, type RbacConfig, RbacSyncClient, type RbacSyncClientConfig, type ResourceConfig, type ResourceKey, type ResourceOp, type Role, type RoleConfig, type RoleKey, type RoleManagementClient, type RoleManagementConfig, type RoleOp, type ServerCondition, type ServerResource, type ServerRole, type ServerState, type TypedRbac, type UpdateRoleInput, computeRbacDiff, createRoleManagement, defineRbac, formatRbacDiff, hashConditionSource, loadRbacConfig, loadRbacConfigFromPath, validateRbacConfig };
|
package/dist/index.mjs
CHANGED
|
@@ -4,11 +4,100 @@ import {
|
|
|
4
4
|
formatRbacDiff,
|
|
5
5
|
hashConditionSource,
|
|
6
6
|
loadRbacConfig,
|
|
7
|
+
loadRbacConfigFromPath,
|
|
7
8
|
validateRbacConfig
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-5CSBBETH.mjs";
|
|
10
|
+
|
|
11
|
+
// src/role-management.ts
|
|
12
|
+
function createRoleManagement(config) {
|
|
13
|
+
const baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
14
|
+
const apiKey = config.apiKey;
|
|
15
|
+
async function request(method, path, body) {
|
|
16
|
+
var _a, _b;
|
|
17
|
+
const url = `${baseUrl}${path}`;
|
|
18
|
+
const headers = {
|
|
19
|
+
Authorization: `Bearer ${apiKey}`,
|
|
20
|
+
"Content-Type": "application/json"
|
|
21
|
+
};
|
|
22
|
+
const res = await fetch(url, {
|
|
23
|
+
method,
|
|
24
|
+
headers,
|
|
25
|
+
body: body ? JSON.stringify(body) : void 0
|
|
26
|
+
});
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
const text = await res.text();
|
|
29
|
+
let message;
|
|
30
|
+
try {
|
|
31
|
+
const json = JSON.parse(text);
|
|
32
|
+
message = (_b = (_a = json.error) != null ? _a : json.message) != null ? _b : "Unknown error";
|
|
33
|
+
} catch (e) {
|
|
34
|
+
message = text.length > 500 ? text.slice(0, 500) + "..." : text;
|
|
35
|
+
}
|
|
36
|
+
if (apiKey) {
|
|
37
|
+
message = message.replaceAll(apiKey, "[REDACTED]");
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`API error (${res.status}): ${message}`);
|
|
40
|
+
}
|
|
41
|
+
return res.json();
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
async listRoles() {
|
|
45
|
+
const result = await request("GET", "/api/v1/roles");
|
|
46
|
+
return result.data.map(toRole);
|
|
47
|
+
},
|
|
48
|
+
async createRole(input) {
|
|
49
|
+
var _a, _b;
|
|
50
|
+
const result = await request("POST", "/api/v1/roles", {
|
|
51
|
+
key: input.key,
|
|
52
|
+
name: input.name,
|
|
53
|
+
description: input.description,
|
|
54
|
+
is_default: (_a = input.isDefault) != null ? _a : false,
|
|
55
|
+
permissions: (_b = input.permissions) != null ? _b : []
|
|
56
|
+
});
|
|
57
|
+
return toRole(result.role);
|
|
58
|
+
},
|
|
59
|
+
async updateRole(roleId, input) {
|
|
60
|
+
const body = {};
|
|
61
|
+
if (input.name !== void 0) body.name = input.name;
|
|
62
|
+
if (input.description !== void 0) body.description = input.description;
|
|
63
|
+
if (input.permissions !== void 0) body.permissions = input.permissions;
|
|
64
|
+
if (input.isDefault !== void 0) body.is_default = input.isDefault;
|
|
65
|
+
const result = await request(
|
|
66
|
+
"PATCH",
|
|
67
|
+
`/api/v1/roles/${encodeURIComponent(roleId)}`,
|
|
68
|
+
body
|
|
69
|
+
);
|
|
70
|
+
return toRole(result.role);
|
|
71
|
+
},
|
|
72
|
+
async deleteRole(roleId) {
|
|
73
|
+
await request(
|
|
74
|
+
"DELETE",
|
|
75
|
+
`/api/v1/roles/${encodeURIComponent(roleId)}`
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
async setDefaultRole(roleId) {
|
|
79
|
+
return this.updateRole(roleId, { isDefault: true });
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function toRole(raw) {
|
|
84
|
+
return {
|
|
85
|
+
id: raw.id,
|
|
86
|
+
projectId: raw.project_id,
|
|
87
|
+
key: raw.key,
|
|
88
|
+
name: raw.name,
|
|
89
|
+
description: raw.description,
|
|
90
|
+
isDefault: raw.is_default,
|
|
91
|
+
permissions: raw.permissions,
|
|
92
|
+
createdAt: raw.created_at,
|
|
93
|
+
updatedAt: raw.updated_at
|
|
94
|
+
};
|
|
95
|
+
}
|
|
9
96
|
|
|
10
97
|
// src/index.ts
|
|
98
|
+
import { defineConfig } from "@auth-gate/core";
|
|
11
99
|
function defineRbac(config, opts) {
|
|
100
|
+
var _a;
|
|
12
101
|
if ((opts == null ? void 0 : opts.validate) !== false) {
|
|
13
102
|
validateRbacConfig(config);
|
|
14
103
|
}
|
|
@@ -22,7 +111,7 @@ function defineRbac(config, opts) {
|
|
|
22
111
|
}
|
|
23
112
|
const roles = {};
|
|
24
113
|
for (const [key, role] of Object.entries(config.roles)) {
|
|
25
|
-
roles[key] = { key, name: role.name, grants: role.grants };
|
|
114
|
+
roles[key] = { key, name: role.name, grants: role.grants, isDefault: (_a = role.isDefault) != null ? _a : false };
|
|
26
115
|
}
|
|
27
116
|
const permissions = {};
|
|
28
117
|
for (const [key, resource] of Object.entries(config.resources)) {
|
|
@@ -44,9 +133,12 @@ function defineRbac(config, opts) {
|
|
|
44
133
|
export {
|
|
45
134
|
RbacSyncClient,
|
|
46
135
|
computeRbacDiff,
|
|
136
|
+
createRoleManagement,
|
|
137
|
+
defineConfig,
|
|
47
138
|
defineRbac,
|
|
48
139
|
formatRbacDiff,
|
|
49
140
|
hashConditionSource,
|
|
50
141
|
loadRbacConfig,
|
|
142
|
+
loadRbacConfigFromPath,
|
|
51
143
|
validateRbacConfig
|
|
52
144
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@auth-gate/rbac",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"chalk": "^5.4.0",
|
|
23
|
-
"
|
|
23
|
+
"dotenv": "^17.2.4",
|
|
24
|
+
"jiti": "^2.4.0",
|
|
25
|
+
"@auth-gate/core": "0.9.1"
|
|
24
26
|
},
|
|
25
27
|
"devDependencies": {
|
|
26
28
|
"tsup": "^8.0.0",
|