@terreno/feature-flags 0.12.0 → 0.12.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.
@@ -77,9 +77,9 @@ var FeatureFlagsApp = /** @class */ (function () {
77
77
  }
78
78
  FeatureFlagsApp.prototype.register = function (app, openApi) {
79
79
  var _this = this;
80
- var _a;
80
+ var _a, _b;
81
81
  var basePath = (_a = this.options.basePath) !== null && _a !== void 0 ? _a : "/feature-flags";
82
- var routerOptions = __assign(__assign({}, (openApi ? { openApi: openApi } : {})), { permissions: {
82
+ var routerOptions = __assign(__assign({}, (openApi ? { openApi: openApi } : {})), { permissions: (_b = this.options.permissions) !== null && _b !== void 0 ? _b : {
83
83
  create: [api_1.Permissions.IsAdmin],
84
84
  delete: [api_1.Permissions.IsAdmin],
85
85
  list: [api_1.Permissions.IsAdmin],
@@ -109,10 +109,13 @@ var FeatureFlagsApp = /** @class */ (function () {
109
109
  }); }));
110
110
  // GET /feature-flags/segments — list registered segment names (admin only)
111
111
  app.get("".concat(basePath, "/segments"), (0, api_1.authenticateMiddleware)(), (0, api_1.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
112
- var user;
112
+ var user, allowed;
113
113
  return __generator(this, function (_a) {
114
114
  user = req.user;
115
- if (!(user === null || user === void 0 ? void 0 : user.admin)) {
115
+ allowed = this.options.segmentsPermission
116
+ ? this.options.segmentsPermission(user)
117
+ : Boolean(user === null || user === void 0 ? void 0 : user.admin);
118
+ if (!allowed) {
116
119
  throw new api_1.APIError({ status: 403, title: "Only admins can view segments" });
117
120
  }
118
121
  return [2 /*return*/, res.json({ data: Object.keys(this.segments) })];
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { FindExactlyOnePlugin, FindOneOrNonePlugin } from "@terreno/api";
1
+ import type { FindExactlyOnePlugin, FindOneOrNonePlugin, ModelRouterOptions } from "@terreno/api";
2
2
  import type mongoose from "mongoose";
3
3
  export interface FeatureFlagVariant {
4
4
  key: string;
@@ -32,5 +32,18 @@ export type SegmentFunction = (user: unknown) => boolean;
32
32
  export interface FeatureFlagsOptions {
33
33
  basePath?: string;
34
34
  segments?: Record<string, SegmentFunction>;
35
+ /**
36
+ * Permission predicates that gate the admin CRUD routes for flags. When
37
+ * omitted, defaults to requiring `Permissions.IsAdmin` (i.e. `user.admin === true`)
38
+ * on every method. Provide this when consumers need role-based gating
39
+ * other than the boolean `admin` flag.
40
+ */
41
+ permissions?: ModelRouterOptions<FeatureFlagDocument>["permissions"];
42
+ /**
43
+ * Predicate used to gate the `/segments` admin endpoint. Receives the
44
+ * authenticated user and should return true to allow the request. Defaults
45
+ * to checking `user.admin === true` when not provided.
46
+ */
47
+ segmentsPermission?: (user: unknown) => boolean;
35
48
  }
36
49
  export type EvaluationResult = Record<string, boolean | string | null>;
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "url": "https://github.com/FlourishHealth/terreno/issues"
5
5
  },
6
6
  "dependencies": {
7
- "@terreno/api": "0.12.0",
7
+ "@terreno/api": "0.12.1",
8
8
  "express": "^5.2.1",
9
9
  "lodash": "^4.17.23"
10
10
  },
@@ -51,5 +51,5 @@
51
51
  "test:coverage": "bun run ../scripts/check-coverage.ts"
52
52
  },
53
53
  "types": "dist/index.d.ts",
54
- "version": "0.12.0"
54
+ "version": "0.12.1"
55
55
  }
@@ -45,7 +45,7 @@ export class FeatureFlagsApp implements TerrenoPlugin {
45
45
  const basePath = this.options.basePath ?? "/feature-flags";
46
46
  const routerOptions: ModelRouterOptions<FeatureFlagDocument> = {
47
47
  ...(openApi ? {openApi: openApi as OpenApiMiddleware} : {}),
48
- permissions: {
48
+ permissions: this.options.permissions ?? {
49
49
  create: [Permissions.IsAdmin],
50
50
  delete: [Permissions.IsAdmin],
51
51
  list: [Permissions.IsAdmin],
@@ -83,8 +83,11 @@ export class FeatureFlagsApp implements TerrenoPlugin {
83
83
  `${basePath}/segments`,
84
84
  authenticateMiddleware(),
85
85
  asyncHandler(async (req: express.Request, res: express.Response) => {
86
- const user = req.user as {admin?: boolean} | undefined;
87
- if (!user?.admin) {
86
+ const user = req.user;
87
+ const allowed = this.options.segmentsPermission
88
+ ? this.options.segmentsPermission(user)
89
+ : Boolean((user as {admin?: boolean} | undefined)?.admin);
90
+ if (!allowed) {
88
91
  throw new APIError({status: 403, title: "Only admins can view segments"});
89
92
  }
90
93
 
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type {FindExactlyOnePlugin, FindOneOrNonePlugin} from "@terreno/api";
1
+ import type {FindExactlyOnePlugin, FindOneOrNonePlugin, ModelRouterOptions} from "@terreno/api";
2
2
  import type mongoose from "mongoose";
3
3
 
4
4
  export interface FeatureFlagVariant {
@@ -41,6 +41,19 @@ export type SegmentFunction = (user: unknown) => boolean;
41
41
  export interface FeatureFlagsOptions {
42
42
  basePath?: string;
43
43
  segments?: Record<string, SegmentFunction>;
44
+ /**
45
+ * Permission predicates that gate the admin CRUD routes for flags. When
46
+ * omitted, defaults to requiring `Permissions.IsAdmin` (i.e. `user.admin === true`)
47
+ * on every method. Provide this when consumers need role-based gating
48
+ * other than the boolean `admin` flag.
49
+ */
50
+ permissions?: ModelRouterOptions<FeatureFlagDocument>["permissions"];
51
+ /**
52
+ * Predicate used to gate the `/segments` admin endpoint. Receives the
53
+ * authenticated user and should return true to allow the request. Defaults
54
+ * to checking `user.admin === true` when not provided.
55
+ */
56
+ segmentsPermission?: (user: unknown) => boolean;
44
57
  }
45
58
 
46
59
  export type EvaluationResult = Record<string, boolean | string | null>;