@objectstack/platform-objects 6.5.1 → 6.6.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/index.js CHANGED
@@ -425,6 +425,30 @@ var SysAccount = data.ObjectSchema.create({
425
425
  description: "OAuth and authentication provider accounts",
426
426
  titleFormat: "{provider_id} - {account_id}",
427
427
  compactLayout: ["provider_id", "user_id", "account_id"],
428
+ // Custom actions — sysadmins routinely need to revoke a user's OAuth
429
+ // link (e.g. when an SSO provider is decommissioned or the user
430
+ // requests it). Better-auth exposes `/unlink-account { providerId,
431
+ // accountId }` for this. The form is locked to the row's values so
432
+ // it acts as a one-click confirmation rather than a free-form edit.
433
+ actions: [
434
+ {
435
+ name: "unlink_account",
436
+ label: "Unlink Account",
437
+ icon: "unlink",
438
+ variant: "danger",
439
+ mode: "delete",
440
+ locations: ["list_item", "record_header"],
441
+ type: "api",
442
+ target: "/api/v1/auth/unlink-account",
443
+ confirmText: "Unlink this identity link? The user will no longer be able to sign in with this provider until they re-link it from their account settings.",
444
+ successMessage: "Identity link removed",
445
+ refreshAfter: true,
446
+ params: [
447
+ { name: "providerId", field: "provider_id", defaultFromRow: true, required: true },
448
+ { name: "accountId", field: "account_id", defaultFromRow: true, required: true }
449
+ ]
450
+ }
451
+ ],
428
452
  listViews: {
429
453
  mine: {
430
454
  type: "grid",
@@ -1979,6 +2003,96 @@ var SysOauthApplication = data.ObjectSchema.create({
1979
2003
  displayNameField: "name",
1980
2004
  titleFormat: "{name}",
1981
2005
  compactLayout: ["name", "client_id", "type", "disabled"],
2006
+ // Custom actions — all OAuth-application mutations are routed through
2007
+ // better-auth's `@better-auth/oauth-provider` endpoints (and a thin
2008
+ // ObjectStack-added auth route for the enable/disable toggle) rather
2009
+ // than the generic data layer, so server-side validation, secret
2010
+ // hashing, and audit hooks all run. The generic `delete` API method
2011
+ // is intentionally dropped from `apiMethods` below so the only delete
2012
+ // path is the better-auth wrapper.
2013
+ //
2014
+ // Upstream gap (better-auth 1.6.11): the stock `/admin/oauth2/update-client`
2015
+ // endpoint's Zod body schema does NOT accept the `disabled` flag, even
2016
+ // though the column exists and the runtime honours it. We bridge the
2017
+ // gap with `POST /api/v1/auth/admin/oauth2/toggle-disabled`, registered
2018
+ // by plugin-auth, which writes through better-auth's own adapter under
2019
+ // the auth namespace (no generic data-layer bypass). When upstream
2020
+ // ships `disabled` support, retarget the enable/disable actions and
2021
+ // delete the bridge route.
2022
+ actions: [
2023
+ {
2024
+ name: "disable_oauth_application",
2025
+ label: "Disable OAuth Application",
2026
+ icon: "pause-circle",
2027
+ variant: "secondary",
2028
+ mode: "custom",
2029
+ locations: ["list_item", "record_header"],
2030
+ type: "api",
2031
+ method: "POST",
2032
+ target: "/api/v1/auth/admin/oauth2/toggle-disabled",
2033
+ confirmText: "Disable this OAuth application? Active access/refresh tokens issued to it will continue to be rejected at the token, authorize, and introspect endpoints. Existing integrations will stop working immediately.",
2034
+ successMessage: "OAuth application disabled",
2035
+ refreshAfter: true,
2036
+ visible: "!record.disabled",
2037
+ bodyExtra: { disabled: true },
2038
+ params: [
2039
+ { name: "client_id", field: "client_id", defaultFromRow: true, required: true }
2040
+ ]
2041
+ },
2042
+ {
2043
+ name: "enable_oauth_application",
2044
+ label: "Enable OAuth Application",
2045
+ icon: "play-circle",
2046
+ variant: "primary",
2047
+ mode: "custom",
2048
+ locations: ["list_item", "record_header"],
2049
+ type: "api",
2050
+ method: "POST",
2051
+ target: "/api/v1/auth/admin/oauth2/toggle-disabled",
2052
+ confirmText: "Re-enable this OAuth application? Token issuance, authorization, and introspection will resume immediately.",
2053
+ successMessage: "OAuth application enabled",
2054
+ refreshAfter: true,
2055
+ visible: "record.disabled",
2056
+ bodyExtra: { disabled: false },
2057
+ params: [
2058
+ { name: "client_id", field: "client_id", defaultFromRow: true, required: true }
2059
+ ]
2060
+ },
2061
+ {
2062
+ name: "rotate_client_secret",
2063
+ label: "Rotate Client Secret",
2064
+ icon: "refresh-cw",
2065
+ variant: "secondary",
2066
+ mode: "custom",
2067
+ locations: ["list_item", "record_header"],
2068
+ type: "api",
2069
+ method: "POST",
2070
+ target: "/api/v1/auth/oauth2/client/rotate-secret",
2071
+ confirmText: "Rotate this OAuth client's secret? The previous secret will stop working immediately and any integrations using it will break until they are updated with the new secret. The new secret is shown only once.",
2072
+ successMessage: "Client secret rotated \u2014 copy the new value from the response now.",
2073
+ refreshAfter: true,
2074
+ params: [
2075
+ { name: "client_id", field: "client_id", defaultFromRow: true, required: true }
2076
+ ]
2077
+ },
2078
+ {
2079
+ name: "delete_oauth_application",
2080
+ label: "Delete OAuth Application",
2081
+ icon: "trash-2",
2082
+ variant: "danger",
2083
+ mode: "delete",
2084
+ locations: ["list_item", "record_header"],
2085
+ type: "api",
2086
+ method: "POST",
2087
+ target: "/api/v1/auth/oauth2/delete-client",
2088
+ confirmText: "Permanently delete this OAuth application? All issued tokens and consents will be invalidated and integrations using this client_id will stop working immediately. This cannot be undone.",
2089
+ successMessage: "OAuth application deleted",
2090
+ refreshAfter: true,
2091
+ params: [
2092
+ { name: "client_id", field: "client_id", defaultFromRow: true, required: true }
2093
+ ]
2094
+ }
2095
+ ],
1982
2096
  listViews: {
1983
2097
  active: {
1984
2098
  type: "grid",
@@ -2210,7 +2324,12 @@ var SysOauthApplication = data.ObjectSchema.create({
2210
2324
  trackHistory: true,
2211
2325
  searchable: true,
2212
2326
  apiEnabled: true,
2213
- apiMethods: ["get", "list", "delete"],
2327
+ // All mutations (create/update/delete) must go through better-auth's
2328
+ // oauth-provider endpoints under /api/v1/auth/{admin/,}oauth2/* — the
2329
+ // generic data layer is read-only for this object so sysadmins cannot
2330
+ // bypass server-side OAuth validation. The Delete row action above is
2331
+ // wired to /api/v1/auth/oauth2/delete-client.
2332
+ apiMethods: ["get", "list"],
2214
2333
  trash: false,
2215
2334
  mru: false
2216
2335
  }
@@ -2493,6 +2612,83 @@ var SysRole = data.ObjectSchema.create({
2493
2612
  displayNameField: "label",
2494
2613
  titleFormat: "{label}",
2495
2614
  compactLayout: ["label", "name", "active", "is_default"],
2615
+ // Custom actions — system roles drive RBAC and are edited rarely but
2616
+ // require the four high-frequency sysadmin affordances every IdP
2617
+ // (Salesforce, ServiceNow, Okta) ships: activate/deactivate (lifecycle
2618
+ // without losing assignments), mark default (auto-assign to new users),
2619
+ // and clone (template for new roles). All operations hit the generic
2620
+ // data CRUD endpoint exposed by `apiEnabled` — no custom server route
2621
+ // required because `managedBy: 'config'` allows direct mutation.
2622
+ actions: [
2623
+ {
2624
+ name: "activate_role",
2625
+ label: "Activate Role",
2626
+ icon: "circle-check",
2627
+ variant: "secondary",
2628
+ mode: "custom",
2629
+ locations: ["list_item", "record_header"],
2630
+ type: "api",
2631
+ method: "PATCH",
2632
+ target: "/api/v1/data/sys_role/{id}",
2633
+ bodyExtra: { active: true },
2634
+ successMessage: "Role activated",
2635
+ refreshAfter: true
2636
+ },
2637
+ {
2638
+ name: "deactivate_role",
2639
+ label: "Deactivate Role",
2640
+ icon: "circle-off",
2641
+ variant: "danger",
2642
+ mode: "custom",
2643
+ locations: ["list_item", "record_header"],
2644
+ type: "api",
2645
+ method: "PATCH",
2646
+ target: "/api/v1/data/sys_role/{id}",
2647
+ bodyExtra: { active: false },
2648
+ confirmText: "Deactivate this role? Users with the role keep their assignment but the role stops granting permissions until re-activated.",
2649
+ successMessage: "Role deactivated",
2650
+ refreshAfter: true
2651
+ },
2652
+ {
2653
+ name: "set_default_role",
2654
+ label: "Set as Default",
2655
+ icon: "star",
2656
+ variant: "secondary",
2657
+ mode: "custom",
2658
+ locations: ["list_item", "record_header"],
2659
+ type: "api",
2660
+ method: "PATCH",
2661
+ target: "/api/v1/data/sys_role/{id}",
2662
+ bodyExtra: { is_default: true },
2663
+ confirmText: "Make this the default role for new users? Existing users are unaffected.",
2664
+ successMessage: "Default role updated",
2665
+ refreshAfter: true
2666
+ },
2667
+ {
2668
+ // Clone — POST a new sys_role row pre-filled from the source. The
2669
+ // dialog asks only for the new API name / label so the operator
2670
+ // can rename atomically; permissions JSON is copied wholesale via
2671
+ // defaultFromRow.
2672
+ name: "clone_role",
2673
+ label: "Clone Role",
2674
+ icon: "copy",
2675
+ variant: "secondary",
2676
+ mode: "custom",
2677
+ locations: ["list_item", "record_header"],
2678
+ type: "api",
2679
+ method: "POST",
2680
+ target: "/api/v1/data/sys_role",
2681
+ bodyExtra: { is_default: false, active: true },
2682
+ successMessage: "Role cloned",
2683
+ refreshAfter: true,
2684
+ params: [
2685
+ { name: "label", label: "New Display Name", type: "text", required: true },
2686
+ { name: "name", label: "New API Name", type: "text", required: true, helpText: "Unique snake_case machine name" },
2687
+ { field: "description", defaultFromRow: true },
2688
+ { field: "permissions", defaultFromRow: true }
2689
+ ]
2690
+ }
2691
+ ],
2496
2692
  listViews: {
2497
2693
  active: {
2498
2694
  type: "grid",
@@ -2619,6 +2815,64 @@ var SysPermissionSet = data.ObjectSchema.create({
2619
2815
  displayNameField: "label",
2620
2816
  titleFormat: "{label}",
2621
2817
  compactLayout: ["label", "name", "active"],
2818
+ // Custom actions — permission sets are templates assigned to roles or
2819
+ // users (via sys_role_permission_set / sys_user_permission_set). The
2820
+ // sysadmin operations that don't live on the parent-detail tabs are
2821
+ // lifecycle (activate/deactivate without losing assignments) and
2822
+ // clone (build a new permset by tweaking an existing one). Both hit
2823
+ // the generic data CRUD endpoint — managedBy: 'config' permits it.
2824
+ actions: [
2825
+ {
2826
+ name: "activate_permission_set",
2827
+ label: "Activate",
2828
+ icon: "circle-check",
2829
+ variant: "secondary",
2830
+ mode: "custom",
2831
+ locations: ["list_item", "record_header"],
2832
+ type: "api",
2833
+ method: "PATCH",
2834
+ target: "/api/v1/data/sys_permission_set/{id}",
2835
+ bodyExtra: { active: true },
2836
+ successMessage: "Permission set activated",
2837
+ refreshAfter: true
2838
+ },
2839
+ {
2840
+ name: "deactivate_permission_set",
2841
+ label: "Deactivate",
2842
+ icon: "circle-off",
2843
+ variant: "danger",
2844
+ mode: "custom",
2845
+ locations: ["list_item", "record_header"],
2846
+ type: "api",
2847
+ method: "PATCH",
2848
+ target: "/api/v1/data/sys_permission_set/{id}",
2849
+ bodyExtra: { active: false },
2850
+ confirmText: "Deactivate this permission set? Existing assignments stay in place but stop granting access until re-activated.",
2851
+ successMessage: "Permission set deactivated",
2852
+ refreshAfter: true
2853
+ },
2854
+ {
2855
+ name: "clone_permission_set",
2856
+ label: "Clone",
2857
+ icon: "copy",
2858
+ variant: "secondary",
2859
+ mode: "custom",
2860
+ locations: ["list_item", "record_header"],
2861
+ type: "api",
2862
+ method: "POST",
2863
+ target: "/api/v1/data/sys_permission_set",
2864
+ bodyExtra: { active: true },
2865
+ successMessage: "Permission set cloned",
2866
+ refreshAfter: true,
2867
+ params: [
2868
+ { name: "label", label: "New Display Name", type: "text", required: true },
2869
+ { name: "name", label: "New API Name", type: "text", required: true, helpText: "Unique snake_case machine name" },
2870
+ { field: "description", defaultFromRow: true },
2871
+ { field: "object_permissions", defaultFromRow: true },
2872
+ { field: "field_permissions", defaultFromRow: true }
2873
+ ]
2874
+ }
2875
+ ],
2622
2876
  listViews: {
2623
2877
  active: {
2624
2878
  type: "grid",