@lightdash/cli 0.1952.0 → 0.1954.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.
@@ -56,6 +56,8 @@ const askPermissionToStoreWarehouseCredentials = async () => {
56
56
  return savedAnswer;
57
57
  };
58
58
  const createProject = async (options) => {
59
+ // Check permissions before proceeding
60
+ await (0, apiClient_1.checkProjectCreationPermission)(options.type);
59
61
  const dbtVersion = await (0, getDbtVersion_1.getDbtVersion)();
60
62
  const absoluteProjectPath = path_1.default.resolve(options.projectDir);
61
63
  const context = await (0, context_1.getDbtContext)({ projectDir: absoluteProjectPath });
@@ -1,4 +1,4 @@
1
- import { ApiResponse } from '@lightdash/common';
1
+ import { ApiResponse, ProjectType, type LightdashUserWithAbilityRules } from '@lightdash/common';
2
2
  import { BodyInit } from 'node-fetch';
3
3
  type LightdashApiProps = {
4
4
  method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT';
@@ -6,5 +6,7 @@ type LightdashApiProps = {
6
6
  body: BodyInit | undefined;
7
7
  };
8
8
  export declare const lightdashApi: <T extends ApiResponse["results"]>({ method, url, body, }: LightdashApiProps) => Promise<T>;
9
+ export declare const getUserContext: () => Promise<LightdashUserWithAbilityRules>;
10
+ export declare const checkProjectCreationPermission: (projectType: ProjectType) => Promise<void>;
9
11
  export declare const checkLightdashVersion: () => Promise<void>;
10
12
  export {};
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.checkLightdashVersion = exports.lightdashApi = void 0;
3
+ exports.checkLightdashVersion = exports.checkProjectCreationPermission = exports.getUserContext = exports.lightdashApi = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const ability_1 = require("@casl/ability");
5
6
  const common_1 = require("@lightdash/common");
6
7
  const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
7
8
  const url_1 = require("url");
@@ -45,6 +46,37 @@ const lightdashApi = async ({ method, url, body, }) => {
45
46
  });
46
47
  };
47
48
  exports.lightdashApi = lightdashApi;
49
+ const getUserContext = async () => (0, exports.lightdashApi)({
50
+ method: 'GET',
51
+ url: `/api/v1/user`,
52
+ body: undefined,
53
+ });
54
+ exports.getUserContext = getUserContext;
55
+ const checkProjectCreationPermission = async (projectType) => {
56
+ try {
57
+ const user = await (0, exports.getUserContext)();
58
+ // Build CASL ability from user's ability rules (same as backend)
59
+ const ability = new ability_1.Ability(user.abilityRules);
60
+ // Check if user has permission to create project of the specified type
61
+ const canCreate = ability.can('create', (0, ability_1.subject)('Project', {
62
+ organizationUuid: user.organizationUuid || '',
63
+ type: projectType,
64
+ }));
65
+ if (!canCreate) {
66
+ const projectTypeText = projectType === common_1.ProjectType.PREVIEW ? 'preview ' : '';
67
+ throw new common_1.ForbiddenError(`You don't have permission to create ${projectTypeText}projects.`);
68
+ }
69
+ }
70
+ catch (err) {
71
+ if (err instanceof common_1.ForbiddenError ||
72
+ err instanceof common_1.AuthorizationError) {
73
+ throw err;
74
+ }
75
+ globalState_1.default.debug(`Failed to check permissions: ${err}`);
76
+ // If we can't check permissions, we'll let the API call fail with proper error
77
+ }
78
+ };
79
+ exports.checkProjectCreationPermission = checkProjectCreationPermission;
48
80
  const checkLightdashVersion = async () => {
49
81
  try {
50
82
  const health = await (0, exports.lightdashApi)({
package/dist/index.js CHANGED
@@ -363,8 +363,18 @@ ${styles.bold('Examples:')}
363
363
  .option('--no-defer', 'dbt property. Do not resolve unselected nodes by deferring to the manifest within the --state directory.', undefined)
364
364
  .action(diagnostics_1.diagnosticsHandler);
365
365
  const errorHandler = (err) => {
366
- console.error(styles.error((0, common_1.getErrorMessage)(err)));
367
- if (err.name === 'AuthorizationError') {
366
+ // Use error message with fallback for safety
367
+ const errorMessage = (0, common_1.getErrorMessage)(err) || 'An unexpected error occurred';
368
+ console.error(styles.error(errorMessage));
369
+ if (err.name === 'ForbiddenError' || err instanceof common_1.ForbiddenError) {
370
+ // For permission errors, show clear message with fallback
371
+ const permissionMessage = err.message || "You don't have permission to perform this action";
372
+ if (permissionMessage !== errorMessage) {
373
+ console.error(styles.error(permissionMessage));
374
+ }
375
+ console.error(`\nšŸ’” Contact your Lightdash administrator to request project creation access or if you believe this is incorrect.\n`);
376
+ }
377
+ else if (err.name === 'AuthorizationError') {
368
378
  console.error(`Looks like you did not authenticate or the personal access token expired.\n\nšŸ‘€ See https://docs.lightdash.com/guides/cli/cli-authentication for help and examples`);
369
379
  }
370
380
  else if (!(err instanceof common_1.LightdashError)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightdash/cli",
3
- "version": "0.1952.0",
3
+ "version": "0.1954.0",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "lightdash": "dist/index.js"
@@ -11,6 +11,7 @@
11
11
  ],
12
12
  "dependencies": {
13
13
  "@actions/core": "^1.11.1",
14
+ "@casl/ability": "^5.4.3",
14
15
  "@types/columnify": "^1.5.1",
15
16
  "ajv": "^8.11.0",
16
17
  "ajv-formats": "^2.1.1",
@@ -31,8 +32,8 @@
31
32
  "parse-node-version": "^2.0.0",
32
33
  "unique-names-generator": "^4.7.1",
33
34
  "uuid": "^11.0.3",
34
- "@lightdash/common": "0.1952.0",
35
- "@lightdash/warehouses": "0.1952.0"
35
+ "@lightdash/warehouses": "0.1954.0",
36
+ "@lightdash/common": "0.1954.0"
36
37
  },
37
38
  "description": "Lightdash CLI tool",
38
39
  "devDependencies": {