@elevasis/core 0.10.0 → 0.11.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/index.d.ts +69 -159
- package/dist/index.js +324 -613
- package/dist/organization-model/index.d.ts +69 -159
- package/dist/organization-model/index.js +324 -613
- package/dist/test-utils/index.d.ts +192 -45
- package/dist/test-utils/index.js +260 -600
- package/package.json +1 -1
- package/src/__tests__/template-core-compatibility.test.ts +73 -91
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +94 -182
- package/src/auth/multi-tenancy/index.ts +20 -17
- package/src/auth/multi-tenancy/memberships/api-schemas.ts +142 -126
- package/src/auth/multi-tenancy/memberships/index.ts +26 -22
- package/src/auth/multi-tenancy/permissions.test.ts +42 -0
- package/src/auth/multi-tenancy/permissions.ts +104 -0
- package/src/organization-model/README.md +102 -97
- package/src/organization-model/__tests__/defaults.test.ts +19 -6
- package/src/organization-model/__tests__/domains/resource-mappings.test.ts +24 -93
- package/src/organization-model/__tests__/graph.test.ts +82 -894
- package/src/organization-model/__tests__/resolve.test.ts +59 -690
- package/src/organization-model/__tests__/schema.test.ts +83 -407
- package/src/organization-model/contracts.ts +4 -3
- package/src/organization-model/defaults.ts +277 -141
- package/src/organization-model/domains/features.ts +31 -22
- package/src/organization-model/domains/navigation.ts +27 -20
- package/src/organization-model/foundation.ts +42 -54
- package/src/organization-model/graph/build.ts +42 -217
- package/src/organization-model/graph/index.ts +4 -4
- package/src/organization-model/graph/link.ts +10 -0
- package/src/organization-model/graph/schema.ts +21 -16
- package/src/organization-model/graph/types.ts +10 -10
- package/src/organization-model/helpers.ts +74 -0
- package/src/organization-model/index.ts +7 -7
- package/src/organization-model/organization-graph.mdx +89 -272
- package/src/organization-model/organization-model.mdx +152 -320
- package/src/organization-model/published.ts +20 -19
- package/src/organization-model/resolve.ts +8 -33
- package/src/organization-model/schema.ts +63 -205
- package/src/organization-model/types.ts +12 -11
- package/src/platform/constants/versions.ts +3 -3
- package/src/platform/registry/__tests__/command-view.test.ts +6 -5
- package/src/platform/registry/__tests__/resource-link.test.ts +30 -0
- package/src/platform/registry/__tests__/resource-registry.integration.test.ts +15 -15
- package/src/platform/registry/command-view.ts +10 -12
- package/src/platform/registry/index.ts +93 -93
- package/src/platform/registry/resource-link.ts +32 -0
- package/src/platform/registry/resource-registry.ts +917 -876
- package/src/platform/registry/serialization.ts +56 -73
- package/src/platform/registry/serialized-types.ts +17 -12
- package/src/platform/registry/types.ts +14 -43
- package/src/reference/_generated/contracts.md +94 -182
- package/src/reference/glossary.md +71 -105
- package/src/scaffold-registry/__tests__/index.test.ts +125 -1
- package/src/scaffold-registry/__tests__/schema.test.ts +48 -20
- package/src/scaffold-registry/index.ts +236 -188
- package/src/scaffold-registry/schema.ts +47 -22
- package/src/supabase/database.types.ts +2880 -2719
- package/src/test-utils/fixtures/memberships.ts +82 -80
- package/src/platform/registry/domains.ts +0 -165
|
@@ -1,126 +1,142 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memberships Domain - Zod Validation Schemas
|
|
3
|
-
*
|
|
4
|
-
* Validation schemas for membership management endpoints.
|
|
5
|
-
* Includes request bodies, query params, and path params.
|
|
6
|
-
*
|
|
7
|
-
* Security:
|
|
8
|
-
* - All schemas use .strict() to prevent mass assignment attacks
|
|
9
|
-
* - UUID validation prevents invalid references
|
|
10
|
-
* - Role enum validation prevents privilege escalation
|
|
11
|
-
* - organizationId never accepted in body (from JWT when needed)
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { z } from 'zod'
|
|
15
|
-
|
|
16
|
-
// ============================================================================
|
|
17
|
-
// Shared Schemas
|
|
18
|
-
// ============================================================================
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Membership role validation
|
|
22
|
-
* Restricts to valid role slugs only
|
|
23
|
-
*
|
|
24
|
-
* Security: Prevents privilege escalation by limiting role values
|
|
25
|
-
*/
|
|
26
|
-
export const MembershipRoleSchema = z.enum(['admin', 'member'])
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Membership status validation
|
|
30
|
-
* Note: Database constraint only allows 'active' | 'inactive'
|
|
31
|
-
*/
|
|
32
|
-
export const MembershipStatusSchema = z.enum(['active', 'inactive'])
|
|
33
|
-
|
|
34
|
-
// ============================================================================
|
|
35
|
-
// Path Parameters
|
|
36
|
-
// ============================================================================
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Validate membership ID in URL path
|
|
40
|
-
* Used by: GET/PUT/DELETE /memberships/:id
|
|
41
|
-
*/
|
|
42
|
-
export const MembershipIdParamSchema = z
|
|
43
|
-
.object({
|
|
44
|
-
id: z.string().min(1) // WorkOS membership IDs can be various formats
|
|
45
|
-
})
|
|
46
|
-
.strict()
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
*
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* -
|
|
76
|
-
* -
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* -
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Memberships Domain - Zod Validation Schemas
|
|
3
|
+
*
|
|
4
|
+
* Validation schemas for membership management endpoints.
|
|
5
|
+
* Includes request bodies, query params, and path params.
|
|
6
|
+
*
|
|
7
|
+
* Security:
|
|
8
|
+
* - All schemas use .strict() to prevent mass assignment attacks
|
|
9
|
+
* - UUID validation prevents invalid references
|
|
10
|
+
* - Role enum validation prevents privilege escalation
|
|
11
|
+
* - organizationId never accepted in body (from JWT when needed)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { z } from 'zod'
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Shared Schemas
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Membership role validation
|
|
22
|
+
* Restricts to valid role slugs only
|
|
23
|
+
*
|
|
24
|
+
* Security: Prevents privilege escalation by limiting role values
|
|
25
|
+
*/
|
|
26
|
+
export const MembershipRoleSchema = z.enum(['admin', 'member'])
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Membership status validation
|
|
30
|
+
* Note: Database constraint only allows 'active' | 'inactive'
|
|
31
|
+
*/
|
|
32
|
+
export const MembershipStatusSchema = z.enum(['active', 'inactive'])
|
|
33
|
+
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Path Parameters
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validate membership ID in URL path
|
|
40
|
+
* Used by: GET/PUT/DELETE /memberships/:id
|
|
41
|
+
*/
|
|
42
|
+
export const MembershipIdParamSchema = z
|
|
43
|
+
.object({
|
|
44
|
+
id: z.string().min(1) // WorkOS membership IDs can be various formats
|
|
45
|
+
})
|
|
46
|
+
.strict()
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Validate organization ID (Supabase UUID) in URL path
|
|
50
|
+
* Used by: GET /memberships/my-permissions/:orgId
|
|
51
|
+
*/
|
|
52
|
+
export const OrgIdParamSchema = z
|
|
53
|
+
.object({
|
|
54
|
+
orgId: z.string().uuid()
|
|
55
|
+
})
|
|
56
|
+
.strict()
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Response shape for GET /memberships/my-permissions/:orgId
|
|
60
|
+
*/
|
|
61
|
+
export const MyOrgPermissionsResponseSchema = z.object({
|
|
62
|
+
permissions: z.array(z.string())
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// Request Bodies
|
|
67
|
+
// ============================================================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Create new membership
|
|
71
|
+
* POST /memberships
|
|
72
|
+
*
|
|
73
|
+
* Security:
|
|
74
|
+
* - userId must be valid (string format for WorkOS)
|
|
75
|
+
* - organizationId must be valid (string format for WorkOS)
|
|
76
|
+
* - roleSlug enum prevents privilege escalation
|
|
77
|
+
* - Strict mode prevents injection
|
|
78
|
+
*/
|
|
79
|
+
export const CreateMembershipSchema = z
|
|
80
|
+
.object({
|
|
81
|
+
userId: z.string().min(1),
|
|
82
|
+
organizationId: z.string().min(1),
|
|
83
|
+
roleSlug: MembershipRoleSchema.default('member')
|
|
84
|
+
})
|
|
85
|
+
.strict()
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Update membership role
|
|
89
|
+
* PUT /memberships/:id
|
|
90
|
+
*
|
|
91
|
+
* Security:
|
|
92
|
+
* - Only roleSlug can be updated
|
|
93
|
+
* - Enum validation prevents privilege escalation
|
|
94
|
+
*/
|
|
95
|
+
export const UpdateMembershipSchema = z
|
|
96
|
+
.object({
|
|
97
|
+
roleSlug: MembershipRoleSchema
|
|
98
|
+
})
|
|
99
|
+
.strict()
|
|
100
|
+
|
|
101
|
+
// ============================================================================
|
|
102
|
+
// Query Parameters
|
|
103
|
+
// ============================================================================
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* List memberships with filters
|
|
107
|
+
* GET /memberships
|
|
108
|
+
*
|
|
109
|
+
* Filters:
|
|
110
|
+
* - userId: Filter by user
|
|
111
|
+
* - organizationId: Filter by organization
|
|
112
|
+
*
|
|
113
|
+
* Security:
|
|
114
|
+
* - Requires at least one filter parameter
|
|
115
|
+
* - String IDs validated for WorkOS format
|
|
116
|
+
*/
|
|
117
|
+
export const ListMembershipsQuerySchema = z
|
|
118
|
+
.object({
|
|
119
|
+
userId: z.string().optional(),
|
|
120
|
+
organizationId: z.string().optional(),
|
|
121
|
+
limit: z.coerce.number().int().min(1).max(100).optional(),
|
|
122
|
+
before: z.string().optional(), // WorkOS pagination cursor
|
|
123
|
+
after: z.string().optional() // WorkOS pagination cursor
|
|
124
|
+
})
|
|
125
|
+
.strict()
|
|
126
|
+
.refine((data) => data.userId || data.organizationId, {
|
|
127
|
+
message: 'Either userId or organizationId must be provided'
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// TypeScript Type Exports
|
|
132
|
+
// ============================================================================
|
|
133
|
+
|
|
134
|
+
// Export inferred types for use in route handlers
|
|
135
|
+
export type CreateMembershipInput = z.infer<typeof CreateMembershipSchema>
|
|
136
|
+
export type UpdateMembershipInput = z.infer<typeof UpdateMembershipSchema>
|
|
137
|
+
export type ListMembershipsQuery = z.infer<typeof ListMembershipsQuerySchema>
|
|
138
|
+
export type MembershipIdParam = z.infer<typeof MembershipIdParamSchema>
|
|
139
|
+
export type MembershipRole = z.infer<typeof MembershipRoleSchema>
|
|
140
|
+
export type MembershipStatus = z.infer<typeof MembershipStatusSchema>
|
|
141
|
+
export type OrgIdParam = z.infer<typeof OrgIdParamSchema>
|
|
142
|
+
export type MyOrgPermissionsResponse = z.infer<typeof MyOrgPermissionsResponseSchema>
|
|
@@ -1,22 +1,26 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Membership types - browser-safe exports only
|
|
3
|
-
* For WorkOS types and transforms, use @repo/core/server
|
|
4
|
-
*/
|
|
5
|
-
export * from './membership'
|
|
6
|
-
export * from './supabase'
|
|
7
|
-
|
|
8
|
-
// Validation schemas
|
|
9
|
-
export {
|
|
10
|
-
CreateMembershipSchema,
|
|
11
|
-
UpdateMembershipSchema,
|
|
12
|
-
ListMembershipsQuerySchema,
|
|
13
|
-
MembershipIdParamSchema,
|
|
14
|
-
MembershipRoleSchema,
|
|
15
|
-
MembershipStatusSchema,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
type
|
|
19
|
-
type
|
|
20
|
-
type
|
|
21
|
-
type
|
|
22
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Membership types - browser-safe exports only
|
|
3
|
+
* For WorkOS types and transforms, use @repo/core/server
|
|
4
|
+
*/
|
|
5
|
+
export * from './membership'
|
|
6
|
+
export * from './supabase'
|
|
7
|
+
|
|
8
|
+
// Validation schemas
|
|
9
|
+
export {
|
|
10
|
+
CreateMembershipSchema,
|
|
11
|
+
UpdateMembershipSchema,
|
|
12
|
+
ListMembershipsQuerySchema,
|
|
13
|
+
MembershipIdParamSchema,
|
|
14
|
+
MembershipRoleSchema,
|
|
15
|
+
MembershipStatusSchema,
|
|
16
|
+
OrgIdParamSchema,
|
|
17
|
+
MyOrgPermissionsResponseSchema,
|
|
18
|
+
type CreateMembershipInput,
|
|
19
|
+
type UpdateMembershipInput,
|
|
20
|
+
type ListMembershipsQuery,
|
|
21
|
+
type MembershipIdParam,
|
|
22
|
+
type MembershipRole,
|
|
23
|
+
type MembershipStatus,
|
|
24
|
+
type OrgIdParam,
|
|
25
|
+
type MyOrgPermissionsResponse
|
|
26
|
+
} from './api-schemas'
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { PERMISSIONS, PERMISSION_CATALOG, isPermissionKey } from './permissions'
|
|
3
|
+
|
|
4
|
+
describe('permissions catalog', () => {
|
|
5
|
+
const permissionValues = Object.values(PERMISSIONS)
|
|
6
|
+
const catalogKeys = PERMISSION_CATALOG.map((p) => p.key)
|
|
7
|
+
|
|
8
|
+
it('every PERMISSIONS value appears as a key in PERMISSION_CATALOG', () => {
|
|
9
|
+
for (const value of permissionValues) {
|
|
10
|
+
expect(catalogKeys).toContain(value)
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('every PERMISSION_CATALOG key exists as a value in PERMISSIONS', () => {
|
|
15
|
+
for (const key of catalogKeys) {
|
|
16
|
+
expect(permissionValues).toContain(key)
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('every catalog entry has a non-empty description and a boolean isOrgGrantable', () => {
|
|
21
|
+
for (const entry of PERMISSION_CATALOG) {
|
|
22
|
+
expect(typeof entry.description).toBe('string')
|
|
23
|
+
expect(entry.description.trim().length).toBeGreaterThan(0)
|
|
24
|
+
expect(typeof entry.isOrgGrantable).toBe('boolean')
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('catalog has no duplicate keys', () => {
|
|
29
|
+
const seen = new Set<string>()
|
|
30
|
+
for (const key of catalogKeys) {
|
|
31
|
+
expect(seen.has(key), `duplicate catalog key: ${key}`).toBe(false)
|
|
32
|
+
seen.add(key)
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('isPermissionKey returns true for every catalog key and false for unknown strings', () => {
|
|
37
|
+
for (const key of catalogKeys) {
|
|
38
|
+
expect(isPermissionKey(key)).toBe(true)
|
|
39
|
+
}
|
|
40
|
+
expect(isPermissionKey('not.a.permission')).toBe(false)
|
|
41
|
+
})
|
|
42
|
+
})
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical permission catalog.
|
|
3
|
+
*
|
|
4
|
+
* Source of truth for the permission keys used by:
|
|
5
|
+
* - RLS policies in Supabase (via has_org_permission(org_id, key))
|
|
6
|
+
* - API middleware (via requireOrganizationPermission(key))
|
|
7
|
+
* - UI hooks (via useOrganizationPermissions().hasPermission(key))
|
|
8
|
+
*
|
|
9
|
+
* The DB table `org_rol_permissions` mirrors this constant. Reconciliation
|
|
10
|
+
* runs at API boot (insert-or-update only — never auto-delete; see the
|
|
11
|
+
* deletion runbook in the auth-role-system-redesign doc).
|
|
12
|
+
*
|
|
13
|
+
* Adding a permission:
|
|
14
|
+
* 1. Add an entry below.
|
|
15
|
+
* 2. Add a row to the migration / via reconcilePermissionCatalog at boot.
|
|
16
|
+
* 3. Reference it in RLS / middleware as needed.
|
|
17
|
+
* 4. Optionally grant it to one or more system roles in org_rol_grants.
|
|
18
|
+
*
|
|
19
|
+
* Removing a permission: follow the deletion runbook — never just delete
|
|
20
|
+
* the entry. Existing role grants and policy references must be cleared first.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export const PERMISSIONS = {
|
|
24
|
+
ORG_READ: 'org.read',
|
|
25
|
+
ORG_MANAGE: 'org.manage',
|
|
26
|
+
ORG_DELETE: 'org.delete',
|
|
27
|
+
MEMBERS_MANAGE: 'members.manage',
|
|
28
|
+
ROLES_MANAGE: 'roles.manage',
|
|
29
|
+
SECRETS_MANAGE: 'secrets.manage',
|
|
30
|
+
OPERATIONS_READ: 'operations.read',
|
|
31
|
+
OPERATIONS_MANAGE: 'operations.manage',
|
|
32
|
+
WORK_MANAGE: 'work.manage'
|
|
33
|
+
} as const
|
|
34
|
+
|
|
35
|
+
export type PermissionKey = (typeof PERMISSIONS)[keyof typeof PERMISSIONS]
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Static metadata for each permission. Mirrored into org_rol_permissions on
|
|
39
|
+
* boot reconciliation. is_org_grantable=false means the permission is reserved
|
|
40
|
+
* to system roles only — custom roles cannot include it (privilege-escalation guard).
|
|
41
|
+
*/
|
|
42
|
+
export interface PermissionDescriptor {
|
|
43
|
+
key: PermissionKey
|
|
44
|
+
description: string
|
|
45
|
+
isOrgGrantable: boolean
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const PERMISSION_CATALOG: readonly PermissionDescriptor[] = [
|
|
49
|
+
{
|
|
50
|
+
key: 'org.read',
|
|
51
|
+
description: 'Read organization profile and listings',
|
|
52
|
+
isOrgGrantable: true
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
key: 'org.manage',
|
|
56
|
+
description: 'Update organization settings',
|
|
57
|
+
isOrgGrantable: false
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
key: 'org.delete',
|
|
61
|
+
description: 'Delete the organization (owner-only)',
|
|
62
|
+
isOrgGrantable: false
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
key: 'members.manage',
|
|
66
|
+
description: 'Invite, remove, and reassign roles for members',
|
|
67
|
+
isOrgGrantable: false
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
key: 'roles.manage',
|
|
71
|
+
description: 'Grant or revoke privileged system roles (owner, admin) within the organization',
|
|
72
|
+
isOrgGrantable: false
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
key: 'secrets.manage',
|
|
76
|
+
description: 'Create, update, and delete API keys and credentials',
|
|
77
|
+
isOrgGrantable: false
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: 'operations.read',
|
|
81
|
+
description: 'View executions, sessions, schedules, and command queue',
|
|
82
|
+
isOrgGrantable: true
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: 'operations.manage',
|
|
86
|
+
description: 'Run and modify executions, sessions, schedules, queue',
|
|
87
|
+
isOrgGrantable: true
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
key: 'work.manage',
|
|
91
|
+
description: 'Create and edit business-domain records (acq_*, prj_*)',
|
|
92
|
+
isOrgGrantable: true
|
|
93
|
+
}
|
|
94
|
+
] as const
|
|
95
|
+
|
|
96
|
+
const PERMISSION_KEY_SET: ReadonlySet<string> = new Set(PERMISSION_CATALOG.map((p) => p.key))
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Type guard. Use at trust boundaries (request input, third-party data) before
|
|
100
|
+
* passing a string to `has_org_permission` / `requireOrganizationPermission`.
|
|
101
|
+
*/
|
|
102
|
+
export function isPermissionKey(value: unknown): value is PermissionKey {
|
|
103
|
+
return typeof value === 'string' && PERMISSION_KEY_SET.has(value)
|
|
104
|
+
}
|