@auth-gate/rbac 0.8.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/dist/chunk-HE57TIQI.mjs +566 -0
- package/dist/cli.cjs +789 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +217 -0
- package/dist/index.cjs +623 -0
- package/dist/index.d.cts +315 -0
- package/dist/index.d.ts +315 -0
- package/dist/index.mjs +49 -0
- package/package.json +35 -0
package/dist/cli.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
RbacSyncClient,
|
|
4
|
+
__spreadValues,
|
|
5
|
+
computeRbacDiff,
|
|
6
|
+
formatRbacDiff,
|
|
7
|
+
loadRbacConfig
|
|
8
|
+
} from "./chunk-HE57TIQI.mjs";
|
|
9
|
+
|
|
10
|
+
// src/cli.ts
|
|
11
|
+
import chalk from "chalk";
|
|
12
|
+
import { writeFileSync, existsSync } from "fs";
|
|
13
|
+
import { resolve } from "path";
|
|
14
|
+
var HELP = `
|
|
15
|
+
Usage: @auth-gate/rbac <command> [options]
|
|
16
|
+
|
|
17
|
+
Commands:
|
|
18
|
+
sync Preview or apply RBAC config changes
|
|
19
|
+
init Create a starter authgate.rbac.ts config file
|
|
20
|
+
check Validate config locally (no server round-trip)
|
|
21
|
+
|
|
22
|
+
Options (sync):
|
|
23
|
+
--apply Apply changes (default: dry-run only)
|
|
24
|
+
--force Allow archiving roles with assigned members
|
|
25
|
+
--strict Treat warnings as errors (recommended for CI/CD)
|
|
26
|
+
--json Output diff as JSON
|
|
27
|
+
|
|
28
|
+
Environment:
|
|
29
|
+
AUTHGATE_API_KEY Your project API key (required for sync)
|
|
30
|
+
AUTHGATE_BASE_URL AuthGate instance URL (required for sync)
|
|
31
|
+
`;
|
|
32
|
+
async function main() {
|
|
33
|
+
const args = process.argv.slice(2);
|
|
34
|
+
const command = args[0];
|
|
35
|
+
if (!command || command === "--help" || command === "-h") {
|
|
36
|
+
console.log(HELP);
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
if (command === "init") {
|
|
40
|
+
await runInit();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (command === "check") {
|
|
44
|
+
await runCheck();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (command === "sync") {
|
|
48
|
+
const apply = args.includes("--apply");
|
|
49
|
+
const force = args.includes("--force");
|
|
50
|
+
const strict = args.includes("--strict");
|
|
51
|
+
const json = args.includes("--json");
|
|
52
|
+
await runSync({ apply, force, strict, json });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
console.error(chalk.red(`Unknown command: ${command}`));
|
|
56
|
+
console.log(HELP);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
async function runInit() {
|
|
60
|
+
const configPath = resolve(process.cwd(), "authgate.rbac.ts");
|
|
61
|
+
if (existsSync(configPath)) {
|
|
62
|
+
console.error(chalk.yellow(`Config file already exists: ${configPath}`));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
const template = `import { defineRbac } from "@auth-gate/rbac";
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* RBAC config \u2014 resource/role/permission keys provide full IDE autocomplete.
|
|
69
|
+
*
|
|
70
|
+
* Sync: npx @auth-gate/rbac sync --apply
|
|
71
|
+
*/
|
|
72
|
+
export const rbac = defineRbac({
|
|
73
|
+
resources: {
|
|
74
|
+
documents: { actions: ["read", "write", "delete", "share"] },
|
|
75
|
+
billing: { actions: ["read", "manage"] },
|
|
76
|
+
members: { actions: ["invite", "remove", "update_role"] },
|
|
77
|
+
},
|
|
78
|
+
roles: {
|
|
79
|
+
admin: {
|
|
80
|
+
name: "Administrator",
|
|
81
|
+
grants: {
|
|
82
|
+
documents: { read: true, write: true, delete: true, share: true },
|
|
83
|
+
billing: { read: true, manage: true },
|
|
84
|
+
members: { invite: true, remove: true, update_role: true },
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
editor: {
|
|
88
|
+
name: "Editor",
|
|
89
|
+
grants: {
|
|
90
|
+
documents: { read: true, write: true },
|
|
91
|
+
billing: { read: true },
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
viewer: {
|
|
95
|
+
name: "Viewer",
|
|
96
|
+
grants: {
|
|
97
|
+
documents: { read: true },
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Default export for CLI compatibility
|
|
104
|
+
export default rbac;
|
|
105
|
+
`;
|
|
106
|
+
writeFileSync(configPath, template, "utf-8");
|
|
107
|
+
console.log(chalk.green(`Created ${configPath}`));
|
|
108
|
+
console.log(chalk.dim("Edit your resources and roles, then run: npx @auth-gate/rbac sync"));
|
|
109
|
+
}
|
|
110
|
+
async function runCheck() {
|
|
111
|
+
let config;
|
|
112
|
+
try {
|
|
113
|
+
config = await loadRbacConfig(process.cwd());
|
|
114
|
+
} catch (err) {
|
|
115
|
+
console.error(chalk.red(`Config error: ${err.message}`));
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
const resourceCount = Object.keys(config.resources).length;
|
|
119
|
+
const roleCount = Object.keys(config.roles).length;
|
|
120
|
+
let permCount = 0;
|
|
121
|
+
for (const resource of Object.values(config.resources)) {
|
|
122
|
+
permCount += resource.actions.length;
|
|
123
|
+
}
|
|
124
|
+
console.log(chalk.green("Config valid!"));
|
|
125
|
+
console.log(chalk.dim(`${resourceCount} resource${resourceCount > 1 ? "s" : ""}, ${roleCount} role${roleCount > 1 ? "s" : ""}, ${permCount} permission${permCount > 1 ? "s" : ""}`));
|
|
126
|
+
}
|
|
127
|
+
async function runSync(opts) {
|
|
128
|
+
const apiKey = process.env.AUTHGATE_API_KEY;
|
|
129
|
+
const baseUrl = process.env.AUTHGATE_BASE_URL;
|
|
130
|
+
if (!apiKey) {
|
|
131
|
+
console.error(chalk.red("Missing AUTHGATE_API_KEY environment variable."));
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
if (!baseUrl) {
|
|
135
|
+
console.error(chalk.red("Missing AUTHGATE_BASE_URL environment variable."));
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
let config;
|
|
139
|
+
try {
|
|
140
|
+
config = await loadRbacConfig(process.cwd());
|
|
141
|
+
} catch (err) {
|
|
142
|
+
console.error(chalk.red(`Config error: ${err.message}`));
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
const resourceCount = Object.keys(config.resources).length;
|
|
146
|
+
const roleCount = Object.keys(config.roles).length;
|
|
147
|
+
console.log(chalk.dim(`Loaded config: ${resourceCount} resource${resourceCount > 1 ? "s" : ""}, ${roleCount} role${roleCount > 1 ? "s" : ""}`));
|
|
148
|
+
const client = new RbacSyncClient({ baseUrl, apiKey });
|
|
149
|
+
let serverState;
|
|
150
|
+
let memberCounts;
|
|
151
|
+
try {
|
|
152
|
+
[serverState, memberCounts] = await Promise.all([
|
|
153
|
+
client.getState(),
|
|
154
|
+
client.getMemberCounts()
|
|
155
|
+
]);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
console.error(chalk.red(`Failed to fetch server state: ${err.message}`));
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
const diff = computeRbacDiff(config, serverState, memberCounts);
|
|
161
|
+
if (opts.json) {
|
|
162
|
+
console.log(JSON.stringify({
|
|
163
|
+
dryRun: !opts.apply,
|
|
164
|
+
resourceOps: diff.resourceOps.map((op) => ({ type: op.type, key: op.key })),
|
|
165
|
+
roleOps: diff.roleOps.map((op) => __spreadValues(__spreadValues({
|
|
166
|
+
type: op.type,
|
|
167
|
+
key: op.key
|
|
168
|
+
}, op.type === "rename" ? { oldKey: op.oldKey } : {}), op.type === "archive" ? { assignedMembers: op.assignedMembers } : {})),
|
|
169
|
+
conditionOps: diff.conditionOps.map((op) => ({ type: op.type, key: op.key })),
|
|
170
|
+
hasDestructive: diff.hasDestructive
|
|
171
|
+
}, null, 2));
|
|
172
|
+
process.exit(diff.hasDestructive && opts.strict ? 1 : 0);
|
|
173
|
+
}
|
|
174
|
+
console.log("");
|
|
175
|
+
console.log(formatRbacDiff(diff, !opts.apply));
|
|
176
|
+
if (!opts.apply) {
|
|
177
|
+
if (opts.strict && diff.hasDestructive) {
|
|
178
|
+
console.error(chalk.red.bold("\n --strict: destructive changes detected. Failing."));
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
process.exit(0);
|
|
182
|
+
}
|
|
183
|
+
if (diff.hasDestructive && !opts.force) {
|
|
184
|
+
console.error(chalk.red("\nDestructive changes require --force flag."));
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
const totalOps = diff.resourceOps.length + diff.roleOps.length + diff.conditionOps.length;
|
|
188
|
+
if (totalOps === 0) {
|
|
189
|
+
process.exit(0);
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
console.log("");
|
|
193
|
+
const result = await client.apply(config, opts.force);
|
|
194
|
+
console.log(chalk.green.bold("Sync complete!"));
|
|
195
|
+
if (result.created.length) console.log(chalk.green(` Created: ${result.created.join(", ")}`));
|
|
196
|
+
if (result.updated.length) console.log(chalk.yellow(` Updated: ${result.updated.join(", ")}`));
|
|
197
|
+
if (result.renamed.length) console.log(chalk.cyan(` Renamed: ${result.renamed.join(", ")}`));
|
|
198
|
+
if (result.archived.length) console.log(chalk.red(` Archived: ${result.archived.join(", ")}`));
|
|
199
|
+
if (result.warnings.length) {
|
|
200
|
+
for (const w of result.warnings) {
|
|
201
|
+
console.log(chalk.yellow(` Warning: ${w}`));
|
|
202
|
+
}
|
|
203
|
+
if (opts.strict) {
|
|
204
|
+
console.error(chalk.red.bold(`
|
|
205
|
+
--strict: ${result.warnings.length} warning${result.warnings.length > 1 ? "s" : ""} treated as errors.`));
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} catch (err) {
|
|
210
|
+
console.error(chalk.red(`Sync failed: ${err.message}`));
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
main().catch((err) => {
|
|
215
|
+
console.error(chalk.red(err.message));
|
|
216
|
+
process.exit(1);
|
|
217
|
+
});
|