@varshylinc/team-management 0.1.0 → 0.2.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.
Files changed (254) hide show
  1. package/dist/client/actions.d.ts +20 -0
  2. package/dist/client/actions.d.ts.map +1 -0
  3. package/dist/client/actions.js +23 -0
  4. package/dist/client/actions.js.map +1 -0
  5. package/dist/client/api.d.ts +74 -0
  6. package/dist/client/api.d.ts.map +1 -0
  7. package/dist/client/api.js +245 -0
  8. package/dist/client/api.js.map +1 -0
  9. package/dist/client/components/AddMemberForm.d.ts +12 -0
  10. package/dist/client/components/AddMemberForm.d.ts.map +1 -0
  11. package/dist/client/components/AddMemberForm.js +37 -0
  12. package/dist/client/components/AddMemberForm.js.map +1 -0
  13. package/dist/client/components/AuditEventRow.d.ts +7 -0
  14. package/dist/client/components/AuditEventRow.d.ts.map +1 -0
  15. package/dist/client/components/AuditEventRow.js +20 -0
  16. package/dist/client/components/AuditEventRow.js.map +1 -0
  17. package/dist/client/components/CascadePreview.d.ts +11 -0
  18. package/dist/client/components/CascadePreview.d.ts.map +1 -0
  19. package/dist/client/components/CascadePreview.js +7 -0
  20. package/dist/client/components/CascadePreview.js.map +1 -0
  21. package/dist/client/components/DangerZoneCard.d.ts +10 -0
  22. package/dist/client/components/DangerZoneCard.d.ts.map +1 -0
  23. package/dist/client/components/DangerZoneCard.js +28 -0
  24. package/dist/client/components/DangerZoneCard.js.map +1 -0
  25. package/dist/client/components/InvitationCodeDisplay.d.ts +7 -0
  26. package/dist/client/components/InvitationCodeDisplay.d.ts.map +1 -0
  27. package/dist/client/components/InvitationCodeDisplay.js +26 -0
  28. package/dist/client/components/InvitationCodeDisplay.js.map +1 -0
  29. package/dist/client/components/InviteForm.d.ts +7 -0
  30. package/dist/client/components/InviteForm.d.ts.map +1 -0
  31. package/dist/client/components/InviteForm.js +35 -0
  32. package/dist/client/components/InviteForm.js.map +1 -0
  33. package/dist/client/components/MemberRow.d.ts +10 -0
  34. package/dist/client/components/MemberRow.d.ts.map +1 -0
  35. package/dist/client/components/MemberRow.js +17 -0
  36. package/dist/client/components/MemberRow.js.map +1 -0
  37. package/dist/client/components/OrgPeopleRoster.d.ts +11 -0
  38. package/dist/client/components/OrgPeopleRoster.d.ts.map +1 -0
  39. package/dist/client/components/OrgPeopleRoster.js +13 -0
  40. package/dist/client/components/OrgPeopleRoster.js.map +1 -0
  41. package/dist/client/components/PendingTransferBanner.d.ts +10 -0
  42. package/dist/client/components/PendingTransferBanner.d.ts.map +1 -0
  43. package/dist/client/components/PendingTransferBanner.js +48 -0
  44. package/dist/client/components/PendingTransferBanner.js.map +1 -0
  45. package/dist/client/components/PlaceholderCard.d.ts +7 -0
  46. package/dist/client/components/PlaceholderCard.d.ts.map +1 -0
  47. package/dist/client/components/PlaceholderCard.js +15 -0
  48. package/dist/client/components/PlaceholderCard.js.map +1 -0
  49. package/dist/client/components/RoleBadge.d.ts +5 -0
  50. package/dist/client/components/RoleBadge.d.ts.map +1 -0
  51. package/dist/client/components/RoleBadge.js +17 -0
  52. package/dist/client/components/RoleBadge.js.map +1 -0
  53. package/dist/client/components/RoleSelect.d.ts +10 -0
  54. package/dist/client/components/RoleSelect.d.ts.map +1 -0
  55. package/dist/client/components/RoleSelect.js +12 -0
  56. package/dist/client/components/RoleSelect.js.map +1 -0
  57. package/dist/client/components/SeatUsagePanel.d.ts +6 -0
  58. package/dist/client/components/SeatUsagePanel.d.ts.map +1 -0
  59. package/dist/client/components/SeatUsagePanel.js +13 -0
  60. package/dist/client/components/SeatUsagePanel.js.map +1 -0
  61. package/dist/client/hooks/useCurrentMembership.d.ts +8 -0
  62. package/dist/client/hooks/useCurrentMembership.d.ts.map +1 -0
  63. package/dist/client/hooks/useCurrentMembership.js +20 -0
  64. package/dist/client/hooks/useCurrentMembership.js.map +1 -0
  65. package/dist/client/hooks/useMembers.d.ts +10 -0
  66. package/dist/client/hooks/useMembers.d.ts.map +1 -0
  67. package/dist/client/hooks/useMembers.js +20 -0
  68. package/dist/client/hooks/useMembers.js.map +1 -0
  69. package/dist/client/hooks/useOrgMembers.d.ts +20 -0
  70. package/dist/client/hooks/useOrgMembers.d.ts.map +1 -0
  71. package/dist/client/hooks/useOrgMembers.js +63 -0
  72. package/dist/client/hooks/useOrgMembers.js.map +1 -0
  73. package/dist/client/hooks/usePendingInvitations.d.ts +8 -0
  74. package/dist/client/hooks/usePendingInvitations.d.ts.map +1 -0
  75. package/dist/client/hooks/usePendingInvitations.js +20 -0
  76. package/dist/client/hooks/usePendingInvitations.js.map +1 -0
  77. package/dist/client/hooks/usePendingTransfer.d.ts +8 -0
  78. package/dist/client/hooks/usePendingTransfer.d.ts.map +1 -0
  79. package/dist/client/hooks/usePendingTransfer.js +23 -0
  80. package/dist/client/hooks/usePendingTransfer.js.map +1 -0
  81. package/dist/client/index.d.ts +31 -0
  82. package/dist/client/index.d.ts.map +1 -0
  83. package/{src/client/index.ts → dist/client/index.js} +6 -54
  84. package/dist/client/index.js.map +1 -0
  85. package/dist/client/pages/AuditLogPage.d.ts +6 -0
  86. package/dist/client/pages/AuditLogPage.d.ts.map +1 -0
  87. package/dist/client/pages/AuditLogPage.js +51 -0
  88. package/dist/client/pages/AuditLogPage.js.map +1 -0
  89. package/dist/client/pages/EmailChangePage.d.ts +8 -0
  90. package/dist/client/pages/EmailChangePage.d.ts.map +1 -0
  91. package/dist/client/pages/EmailChangePage.js +52 -0
  92. package/dist/client/pages/EmailChangePage.js.map +1 -0
  93. package/dist/client/pages/InvitationAcceptPage.d.ts +11 -0
  94. package/dist/client/pages/InvitationAcceptPage.d.ts.map +1 -0
  95. package/dist/client/pages/InvitationAcceptPage.js +42 -0
  96. package/dist/client/pages/InvitationAcceptPage.js.map +1 -0
  97. package/dist/client/pages/InvitationCodePage.d.ts +6 -0
  98. package/dist/client/pages/InvitationCodePage.d.ts.map +1 -0
  99. package/dist/client/pages/InvitationCodePage.js +28 -0
  100. package/dist/client/pages/InvitationCodePage.js.map +1 -0
  101. package/dist/client/pages/MembersPage.d.ts +6 -0
  102. package/dist/client/pages/MembersPage.d.ts.map +1 -0
  103. package/dist/client/pages/MembersPage.js +67 -0
  104. package/dist/client/pages/MembersPage.js.map +1 -0
  105. package/dist/client/pages/OrgPeoplePage.d.ts +14 -0
  106. package/dist/client/pages/OrgPeoplePage.d.ts.map +1 -0
  107. package/dist/client/pages/OrgPeoplePage.js +40 -0
  108. package/dist/client/pages/OrgPeoplePage.js.map +1 -0
  109. package/dist/client/pages/OrgSettingsPage.d.ts +6 -0
  110. package/dist/client/pages/OrgSettingsPage.d.ts.map +1 -0
  111. package/dist/client/pages/OrgSettingsPage.js +78 -0
  112. package/dist/client/pages/OrgSettingsPage.js.map +1 -0
  113. package/dist/client/pages/OwnershipTransferPage.d.ts +6 -0
  114. package/dist/client/pages/OwnershipTransferPage.d.ts.map +1 -0
  115. package/dist/client/pages/OwnershipTransferPage.js +68 -0
  116. package/dist/client/pages/OwnershipTransferPage.js.map +1 -0
  117. package/dist/client/pages/PasswordResetPage.d.ts +6 -0
  118. package/dist/client/pages/PasswordResetPage.d.ts.map +1 -0
  119. package/dist/client/pages/PasswordResetPage.js +34 -0
  120. package/dist/client/pages/PasswordResetPage.js.map +1 -0
  121. package/dist/client/pages/PasswordResetRequestPage.d.ts +2 -0
  122. package/dist/client/pages/PasswordResetRequestPage.d.ts.map +1 -0
  123. package/dist/client/pages/PasswordResetRequestPage.js +27 -0
  124. package/dist/client/pages/PasswordResetRequestPage.js.map +1 -0
  125. package/dist/client/pages/PlaceholderPage.d.ts +7 -0
  126. package/dist/client/pages/PlaceholderPage.d.ts.map +1 -0
  127. package/dist/client/pages/PlaceholderPage.js +16 -0
  128. package/dist/client/pages/PlaceholderPage.js.map +1 -0
  129. package/dist/client/pages/SuperAdminDashboard.d.ts +2 -0
  130. package/dist/client/pages/SuperAdminDashboard.d.ts.map +1 -0
  131. package/dist/client/pages/SuperAdminDashboard.js +123 -0
  132. package/dist/client/pages/SuperAdminDashboard.js.map +1 -0
  133. package/dist/client/theme.d.ts +12 -0
  134. package/dist/client/theme.d.ts.map +1 -0
  135. package/dist/client/theme.js +16 -0
  136. package/dist/client/theme.js.map +1 -0
  137. package/dist/client/types.d.ts +78 -0
  138. package/dist/client/types.d.ts.map +1 -0
  139. package/dist/client/types.js +2 -0
  140. package/dist/client/types.js.map +1 -0
  141. package/dist/index.d.ts +3 -1
  142. package/dist/index.d.ts.map +1 -1
  143. package/dist/index.js +2 -1
  144. package/dist/index.js.map +1 -1
  145. package/dist/server/index.d.ts +2 -0
  146. package/dist/server/index.d.ts.map +1 -1
  147. package/dist/server/index.js +1 -0
  148. package/dist/server/index.js.map +1 -1
  149. package/dist/server/org-admin.d.ts +45 -0
  150. package/dist/server/org-admin.d.ts.map +1 -0
  151. package/dist/server/org-admin.js +63 -0
  152. package/dist/server/org-admin.js.map +1 -0
  153. package/dist/server/routes/orgs.routes.d.ts.map +1 -1
  154. package/dist/server/routes/orgs.routes.js +81 -12
  155. package/dist/server/routes/orgs.routes.js.map +1 -1
  156. package/dist/server/types.d.ts +2 -0
  157. package/dist/server/types.d.ts.map +1 -1
  158. package/dist/server/types.js.map +1 -1
  159. package/package.json +18 -11
  160. package/.eslintrc.cjs +0 -18
  161. package/CHANGELOG.md +0 -159
  162. package/src/client/api.ts +0 -314
  163. package/src/client/components/AuditEventRow.tsx +0 -59
  164. package/src/client/components/CascadePreview.tsx +0 -36
  165. package/src/client/components/DangerZoneCard.tsx +0 -103
  166. package/src/client/components/InvitationCodeDisplay.tsx +0 -48
  167. package/src/client/components/InviteForm.tsx +0 -77
  168. package/src/client/components/MemberRow.tsx +0 -69
  169. package/src/client/components/PendingTransferBanner.tsx +0 -98
  170. package/src/client/components/PlaceholderCard.tsx +0 -26
  171. package/src/client/components/RoleBadge.tsx +0 -26
  172. package/src/client/components/RoleSelect.tsx +0 -35
  173. package/src/client/hooks/.gitkeep +0 -0
  174. package/src/client/hooks/useCurrentMembership.ts +0 -24
  175. package/src/client/hooks/useMembers.ts +0 -24
  176. package/src/client/hooks/usePendingInvitations.ts +0 -24
  177. package/src/client/hooks/usePendingTransfer.ts +0 -27
  178. package/src/client/pages/AuditLogPage.tsx +0 -164
  179. package/src/client/pages/EmailChangePage.tsx +0 -144
  180. package/src/client/pages/InvitationAcceptPage.tsx +0 -163
  181. package/src/client/pages/InvitationCodePage.tsx +0 -108
  182. package/src/client/pages/MembersPage.tsx +0 -290
  183. package/src/client/pages/OrgSettingsPage.tsx +0 -185
  184. package/src/client/pages/OwnershipTransferPage.tsx +0 -163
  185. package/src/client/pages/PasswordResetPage.tsx +0 -104
  186. package/src/client/pages/PasswordResetRequestPage.tsx +0 -71
  187. package/src/client/pages/PlaceholderPage.tsx +0 -20
  188. package/src/client/pages/SuperAdminDashboard.tsx +0 -401
  189. package/src/client/types.ts +0 -78
  190. package/src/index.ts +0 -24
  191. package/src/server/crypto.ts +0 -47
  192. package/src/server/index.ts +0 -167
  193. package/src/server/middleware/require-membership.ts +0 -48
  194. package/src/server/middleware/require-role.ts +0 -19
  195. package/src/server/middleware/require-super-admin.ts +0 -32
  196. package/src/server/migrations/0001_create_tm_schema_migrations.sql +0 -13
  197. package/src/server/migrations/0002_create_tm_organizations.sql +0 -14
  198. package/src/server/migrations/0003_create_tm_memberships.sql +0 -24
  199. package/src/server/migrations/0004_create_tm_invitations.sql +0 -22
  200. package/src/server/migrations/0005_create_tm_audit_events.sql +0 -17
  201. package/src/server/migrations/0006_create_tm_email_change_requests.sql +0 -13
  202. package/src/server/migrations/0007_create_tm_ownership_transfers.sql +0 -22
  203. package/src/server/migrations/0008_create_tm_super_admins.sql +0 -8
  204. package/src/server/migrations/0009_create_tm_password_reset_requests.sql +0 -9
  205. package/src/server/migrations/0010_create_tm_shared_access.sql +0 -8
  206. package/src/server/migrations/0011_seed_super_admin.sql +0 -15
  207. package/src/server/migrations/0012_create_tm_user_locks.sql +0 -7
  208. package/src/server/routes/admin.routes.ts +0 -208
  209. package/src/server/routes/audit.routes.ts +0 -93
  210. package/src/server/routes/health.routes.ts +0 -46
  211. package/src/server/routes/invitations.routes.ts +0 -252
  212. package/src/server/routes/me.routes.ts +0 -143
  213. package/src/server/routes/orgs.routes.ts +0 -428
  214. package/src/server/routes/transfer.routes.ts +0 -110
  215. package/src/server/services/.gitkeep +0 -0
  216. package/src/server/services/audit.service.ts +0 -49
  217. package/src/server/services/email-change.service.ts +0 -178
  218. package/src/server/services/invitations.service.ts +0 -316
  219. package/src/server/services/memberships.service.ts +0 -129
  220. package/src/server/services/organizations.service.ts +0 -110
  221. package/src/server/services/ownership.service.ts +0 -170
  222. package/src/server/services/password-reset.service.ts +0 -94
  223. package/src/server/services/super-admin.service.ts +0 -321
  224. package/src/server/sql/.gitkeep +0 -0
  225. package/src/server/types.ts +0 -145
  226. package/src/shared/types.ts +0 -24
  227. package/tests/integration/audit-fires.test.ts +0 -288
  228. package/tests/integration/cascade-preview.test.ts +0 -157
  229. package/tests/integration/email-change.test.ts +0 -190
  230. package/tests/integration/feature-flags.test.ts +0 -213
  231. package/tests/integration/invitations-code.test.ts +0 -218
  232. package/tests/integration/invitations-expiry.test.ts +0 -216
  233. package/tests/integration/invitations-resend.test.ts +0 -241
  234. package/tests/integration/invitations-revoke.test.ts +0 -226
  235. package/tests/integration/invitations-switch-org.test.ts +0 -156
  236. package/tests/integration/invitations-token.test.ts +0 -221
  237. package/tests/integration/migrations.test.ts +0 -119
  238. package/tests/integration/only-owner-protections.test.ts +0 -130
  239. package/tests/integration/org-lifecycle.test.ts +0 -169
  240. package/tests/integration/ownership-transfer-cancel.test.ts +0 -171
  241. package/tests/integration/ownership-transfer-expire.test.ts +0 -171
  242. package/tests/integration/ownership-transfer-happy.test.ts +0 -184
  243. package/tests/integration/ownership-transfer-locks.test.ts +0 -146
  244. package/tests/integration/password-reset.test.ts +0 -200
  245. package/tests/integration/super-admin-actions.test.ts +0 -180
  246. package/tests/integration/super-admin-restrictions.test.ts +0 -209
  247. package/tests/setup/global-setup.ts +0 -20
  248. package/tests/unit/adapter-shape.test.ts +0 -330
  249. package/tests/unit/role-permissions.test.ts +0 -236
  250. package/tests/unit/validation.test.ts +0 -304
  251. package/tsconfig.client.json +0 -13
  252. package/tsconfig.json +0 -12
  253. package/tsconfig.tsbuildinfo +0 -1
  254. package/vitest.config.ts +0 -13
@@ -1,236 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { roleAtLeast } from '../../src/server/types.js';
3
- import type { OrgRole } from '../../src/server/types.js';
4
-
5
- // ---------------------------------------------------------------------------
6
- // Permission matrix from spec
7
- //
8
- // ACTION | OWNER | ADMIN | MEMBER | VIEWER
9
- // ------------------------------------|-------|-------|--------|--------
10
- // View org info | ✓ | ✓ | ✓ | ✓
11
- // List members | ✓ | ✓ | ✓ | ✓
12
- // View audit log | ✓ | ✓ | ✗ | ✗
13
- // Edit org settings (name/slug) | ✓ | ✓ | ✗ | ✗
14
- // Invite member | ✓ | ✓ | ✗ | ✗
15
- // Revoke pending invite | ✓ | ✓ | ✗ | ✗
16
- // Change member role (not owner) | ✓ | ✓ | ✗ | ✗
17
- // Change another admin's role | ✓ | ✗ | ✗ | ✗
18
- // Remove member (not owner) | ✓ | ✓ | ✗ | ✗
19
- // Remove an admin | ✓ | ✗ | ✗ | ✗
20
- // Initiate ownership transfer | ✓ | ✗ | ✗ | ✗
21
- // Delete org | ✓ | ✗ | ✗ | ✗
22
- // Change own email | ✓ | ✓ | ✓ | ✓
23
- // Reset own password | ✓ | ✓ | ✓ | ✓
24
- // ---------------------------------------------------------------------------
25
-
26
- const ALL_ROLES: OrgRole[] = ['owner', 'admin', 'member', 'viewer'];
27
-
28
- // Helper: given a minimum role, returns whether each role in ALL_ROLES can perform the action.
29
- function canDo(minRole: OrgRole): Record<OrgRole, boolean> {
30
- return {
31
- owner: roleAtLeast('owner', minRole),
32
- admin: roleAtLeast('admin', minRole),
33
- member: roleAtLeast('member', minRole),
34
- viewer: roleAtLeast('viewer', minRole),
35
- };
36
- }
37
-
38
- // ---------------------------------------------------------------------------
39
- // View org info — minimum role: viewer (all roles allowed)
40
- // ---------------------------------------------------------------------------
41
- describe('View org info', () => {
42
- const perms = canDo('viewer');
43
-
44
- it('owner can view org info', () => expect(perms.owner).toBe(true));
45
- it('admin can view org info', () => expect(perms.admin).toBe(true));
46
- it('member can view org info', () => expect(perms.member).toBe(true));
47
- it('viewer can view org info', () => expect(perms.viewer).toBe(true));
48
- });
49
-
50
- // ---------------------------------------------------------------------------
51
- // List members — minimum role: viewer (all roles allowed)
52
- // ---------------------------------------------------------------------------
53
- describe('List members', () => {
54
- const perms = canDo('viewer');
55
-
56
- it('owner can list members', () => expect(perms.owner).toBe(true));
57
- it('admin can list members', () => expect(perms.admin).toBe(true));
58
- it('member can list members', () => expect(perms.member).toBe(true));
59
- it('viewer can list members', () => expect(perms.viewer).toBe(true));
60
- });
61
-
62
- // ---------------------------------------------------------------------------
63
- // View audit log — minimum role: admin
64
- // ---------------------------------------------------------------------------
65
- describe('View audit log', () => {
66
- const perms = canDo('admin');
67
-
68
- it('owner can view audit log', () => expect(perms.owner).toBe(true));
69
- it('admin can view audit log', () => expect(perms.admin).toBe(true));
70
- it('member cannot view audit log', () => expect(perms.member).toBe(false));
71
- it('viewer cannot view audit log', () => expect(perms.viewer).toBe(false));
72
- });
73
-
74
- // ---------------------------------------------------------------------------
75
- // Edit org settings (name/slug) — minimum role: admin
76
- // ---------------------------------------------------------------------------
77
- describe('Edit org settings (name/slug)', () => {
78
- const perms = canDo('admin');
79
-
80
- it('owner can edit org settings', () => expect(perms.owner).toBe(true));
81
- it('admin can edit org settings', () => expect(perms.admin).toBe(true));
82
- it('member cannot edit org settings', () => expect(perms.member).toBe(false));
83
- it('viewer cannot edit org settings', () => expect(perms.viewer).toBe(false));
84
- });
85
-
86
- // ---------------------------------------------------------------------------
87
- // Invite member — minimum role: admin
88
- // ---------------------------------------------------------------------------
89
- describe('Invite member', () => {
90
- const perms = canDo('admin');
91
-
92
- it('owner can invite member', () => expect(perms.owner).toBe(true));
93
- it('admin can invite member', () => expect(perms.admin).toBe(true));
94
- it('member cannot invite member', () => expect(perms.member).toBe(false));
95
- it('viewer cannot invite member', () => expect(perms.viewer).toBe(false));
96
- });
97
-
98
- // ---------------------------------------------------------------------------
99
- // Revoke pending invite — minimum role: admin
100
- // ---------------------------------------------------------------------------
101
- describe('Revoke pending invite', () => {
102
- const perms = canDo('admin');
103
-
104
- it('owner can revoke pending invite', () => expect(perms.owner).toBe(true));
105
- it('admin can revoke pending invite', () => expect(perms.admin).toBe(true));
106
- it('member cannot revoke pending invite', () => expect(perms.member).toBe(false));
107
- it('viewer cannot revoke pending invite', () => expect(perms.viewer).toBe(false));
108
- });
109
-
110
- // ---------------------------------------------------------------------------
111
- // Change member role (not owner) — minimum role: admin
112
- // ---------------------------------------------------------------------------
113
- describe('Change member role (not owner)', () => {
114
- const perms = canDo('admin');
115
-
116
- it('owner can change member role', () => expect(perms.owner).toBe(true));
117
- it('admin can change member role', () => expect(perms.admin).toBe(true));
118
- it('member cannot change member role', () => expect(perms.member).toBe(false));
119
- it('viewer cannot change member role', () => expect(perms.viewer).toBe(false));
120
- });
121
-
122
- // ---------------------------------------------------------------------------
123
- // Change another admin's role — minimum role: owner
124
- // ---------------------------------------------------------------------------
125
- describe("Change another admin's role", () => {
126
- const perms = canDo('owner');
127
-
128
- it("owner can change another admin's role", () => expect(perms.owner).toBe(true));
129
- it("admin cannot change another admin's role", () => expect(perms.admin).toBe(false));
130
- it("member cannot change another admin's role", () => expect(perms.member).toBe(false));
131
- it("viewer cannot change another admin's role", () => expect(perms.viewer).toBe(false));
132
- });
133
-
134
- // ---------------------------------------------------------------------------
135
- // Remove member (not owner) — minimum role: admin
136
- // ---------------------------------------------------------------------------
137
- describe('Remove member (not owner)', () => {
138
- const perms = canDo('admin');
139
-
140
- it('owner can remove member', () => expect(perms.owner).toBe(true));
141
- it('admin can remove member', () => expect(perms.admin).toBe(true));
142
- it('member cannot remove member', () => expect(perms.member).toBe(false));
143
- it('viewer cannot remove member', () => expect(perms.viewer).toBe(false));
144
- });
145
-
146
- // ---------------------------------------------------------------------------
147
- // Remove an admin — minimum role: owner
148
- // ---------------------------------------------------------------------------
149
- describe('Remove an admin', () => {
150
- const perms = canDo('owner');
151
-
152
- it('owner can remove an admin', () => expect(perms.owner).toBe(true));
153
- it('admin cannot remove an admin', () => expect(perms.admin).toBe(false));
154
- it('member cannot remove an admin', () => expect(perms.member).toBe(false));
155
- it('viewer cannot remove an admin', () => expect(perms.viewer).toBe(false));
156
- });
157
-
158
- // ---------------------------------------------------------------------------
159
- // Initiate ownership transfer — minimum role: owner
160
- // ---------------------------------------------------------------------------
161
- describe('Initiate ownership transfer', () => {
162
- const perms = canDo('owner');
163
-
164
- it('owner can initiate ownership transfer', () => expect(perms.owner).toBe(true));
165
- it('admin cannot initiate ownership transfer', () => expect(perms.admin).toBe(false));
166
- it('member cannot initiate ownership transfer', () => expect(perms.member).toBe(false));
167
- it('viewer cannot initiate ownership transfer', () => expect(perms.viewer).toBe(false));
168
- });
169
-
170
- // ---------------------------------------------------------------------------
171
- // Delete org — minimum role: owner
172
- // ---------------------------------------------------------------------------
173
- describe('Delete org', () => {
174
- const perms = canDo('owner');
175
-
176
- it('owner can delete org', () => expect(perms.owner).toBe(true));
177
- it('admin cannot delete org', () => expect(perms.admin).toBe(false));
178
- it('member cannot delete org', () => expect(perms.member).toBe(false));
179
- it('viewer cannot delete org', () => expect(perms.viewer).toBe(false));
180
- });
181
-
182
- // ---------------------------------------------------------------------------
183
- // Change own email — minimum role: viewer (all roles allowed)
184
- // ---------------------------------------------------------------------------
185
- describe('Change own email', () => {
186
- const perms = canDo('viewer');
187
-
188
- it('owner can change own email', () => expect(perms.owner).toBe(true));
189
- it('admin can change own email', () => expect(perms.admin).toBe(true));
190
- it('member can change own email', () => expect(perms.member).toBe(true));
191
- it('viewer can change own email', () => expect(perms.viewer).toBe(true));
192
- });
193
-
194
- // ---------------------------------------------------------------------------
195
- // Reset own password — minimum role: viewer (all roles allowed)
196
- // ---------------------------------------------------------------------------
197
- describe('Reset own password', () => {
198
- const perms = canDo('viewer');
199
-
200
- it('owner can reset own password', () => expect(perms.owner).toBe(true));
201
- it('admin can reset own password', () => expect(perms.admin).toBe(true));
202
- it('member can reset own password', () => expect(perms.member).toBe(true));
203
- it('viewer can reset own password', () => expect(perms.viewer).toBe(true));
204
- });
205
-
206
- // ---------------------------------------------------------------------------
207
- // Cross-cutting: exhaustive matrix spot-checks
208
- // ---------------------------------------------------------------------------
209
- describe('Full permission matrix — exhaustive spot-checks', () => {
210
- it('owner has highest privilege (passes all checks)', () => {
211
- for (const role of ALL_ROLES) {
212
- expect(roleAtLeast('owner', role)).toBe(true);
213
- }
214
- });
215
-
216
- it('viewer has lowest privilege (only passes viewer check)', () => {
217
- expect(roleAtLeast('viewer', 'viewer')).toBe(true);
218
- expect(roleAtLeast('viewer', 'member')).toBe(false);
219
- expect(roleAtLeast('viewer', 'admin')).toBe(false);
220
- expect(roleAtLeast('viewer', 'owner')).toBe(false);
221
- });
222
-
223
- it('admin passes admin and below, fails owner', () => {
224
- expect(roleAtLeast('admin', 'viewer')).toBe(true);
225
- expect(roleAtLeast('admin', 'member')).toBe(true);
226
- expect(roleAtLeast('admin', 'admin')).toBe(true);
227
- expect(roleAtLeast('admin', 'owner')).toBe(false);
228
- });
229
-
230
- it('member passes member and viewer, fails admin and owner', () => {
231
- expect(roleAtLeast('member', 'viewer')).toBe(true);
232
- expect(roleAtLeast('member', 'member')).toBe(true);
233
- expect(roleAtLeast('member', 'admin')).toBe(false);
234
- expect(roleAtLeast('member', 'owner')).toBe(false);
235
- });
236
- });
@@ -1,304 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
-
3
- // ---------------------------------------------------------------------------
4
- // Pure validation functions — no src imports needed.
5
- // These mirror the validation logic that the package enforces.
6
- // ---------------------------------------------------------------------------
7
-
8
- // Slug: lowercase alphanumeric + hyphens, 3-50 chars, no leading/trailing hyphens.
9
- function isValidSlug(slug: string): boolean {
10
- if (slug.length < 3 || slug.length > 50) return false;
11
- // Must start and end with alphanumeric, inner chars can include hyphens.
12
- return /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]{1}$/.test(slug) && slug.length >= 3;
13
- }
14
-
15
- // Email: basic RFC-ish check — local@domain.tld
16
- function isValidEmail(email: string): boolean {
17
- if (!email) return false;
18
- return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
19
- }
20
-
21
- // OrgRole: exactly one of the four valid roles.
22
- function isValidOrgRole(role: string): boolean {
23
- return ['owner', 'admin', 'member', 'viewer'].includes(role);
24
- }
25
-
26
- // Token: hex string, exactly 64 characters.
27
- function isValidToken(token: string): boolean {
28
- return /^[0-9a-f]{64}$/.test(token);
29
- }
30
-
31
- // ---------------------------------------------------------------------------
32
- // Slug validation
33
- // ---------------------------------------------------------------------------
34
- describe('Slug validation', () => {
35
- describe('valid slugs', () => {
36
- it('"demo-co" is valid', () => {
37
- expect(isValidSlug('demo-co')).toBe(true);
38
- });
39
-
40
- it('"acme" is valid', () => {
41
- expect(isValidSlug('acme')).toBe(true);
42
- });
43
-
44
- it('"my-org-123" is valid', () => {
45
- expect(isValidSlug('my-org-123')).toBe(true);
46
- });
47
-
48
- it('exactly 3 chars "abc" is valid (lower boundary)', () => {
49
- expect(isValidSlug('abc')).toBe(true);
50
- });
51
-
52
- it('exactly 50 chars is valid (upper boundary)', () => {
53
- const slug = 'a' + 'b'.repeat(48) + 'c'; // 50 chars
54
- expect(slug.length).toBe(50);
55
- expect(isValidSlug(slug)).toBe(true);
56
- });
57
-
58
- it('alphanumeric with numbers "org123" is valid', () => {
59
- expect(isValidSlug('org123')).toBe(true);
60
- });
61
-
62
- it('multiple hyphens in middle "my-big-org-2024" is valid', () => {
63
- expect(isValidSlug('my-big-org-2024')).toBe(true);
64
- });
65
- });
66
-
67
- describe('invalid slugs', () => {
68
- it('empty string is invalid', () => {
69
- expect(isValidSlug('')).toBe(false);
70
- });
71
-
72
- it('single char "a" is invalid (too short)', () => {
73
- expect(isValidSlug('a')).toBe(false);
74
- });
75
-
76
- it('two chars "ab" is invalid (too short)', () => {
77
- expect(isValidSlug('ab')).toBe(false);
78
- });
79
-
80
- it('leading hyphen "-start" is invalid', () => {
81
- expect(isValidSlug('-start')).toBe(false);
82
- });
83
-
84
- it('trailing hyphen "end-" is invalid', () => {
85
- expect(isValidSlug('end-')).toBe(false);
86
- });
87
-
88
- it('spaces "with spaces" is invalid', () => {
89
- expect(isValidSlug('with spaces')).toBe(false);
90
- });
91
-
92
- it('51 chars is invalid (too long)', () => {
93
- const slug = 'a'.repeat(51);
94
- expect(slug.length).toBe(51);
95
- expect(isValidSlug(slug)).toBe(false);
96
- });
97
-
98
- it('uppercase letters "MyOrg" is invalid', () => {
99
- expect(isValidSlug('MyOrg')).toBe(false);
100
- });
101
-
102
- it('underscores "my_org" are invalid', () => {
103
- expect(isValidSlug('my_org')).toBe(false);
104
- });
105
-
106
- it('special chars "org@co" are invalid', () => {
107
- expect(isValidSlug('org@co')).toBe(false);
108
- });
109
- });
110
- });
111
-
112
- // ---------------------------------------------------------------------------
113
- // Email validation
114
- // ---------------------------------------------------------------------------
115
- describe('Email validation', () => {
116
- describe('valid emails', () => {
117
- it('"user@example.com" is valid', () => {
118
- expect(isValidEmail('user@example.com')).toBe(true);
119
- });
120
-
121
- it('"a+b@x.co" is valid', () => {
122
- expect(isValidEmail('a+b@x.co')).toBe(true);
123
- });
124
-
125
- it('"firstname.lastname@domain.org" is valid', () => {
126
- expect(isValidEmail('firstname.lastname@domain.org')).toBe(true);
127
- });
128
-
129
- it('"user123@sub.domain.com" is valid', () => {
130
- expect(isValidEmail('user123@sub.domain.com')).toBe(true);
131
- });
132
- });
133
-
134
- describe('invalid emails', () => {
135
- it('"notanemail" is invalid (no @ or domain)', () => {
136
- expect(isValidEmail('notanemail')).toBe(false);
137
- });
138
-
139
- it('"@domain.com" is invalid (no local part)', () => {
140
- expect(isValidEmail('@domain.com')).toBe(false);
141
- });
142
-
143
- it('"user@" is invalid (no domain)', () => {
144
- expect(isValidEmail('user@')).toBe(false);
145
- });
146
-
147
- it('empty string is invalid', () => {
148
- expect(isValidEmail('')).toBe(false);
149
- });
150
-
151
- it('"user@domain" is invalid (no TLD)', () => {
152
- expect(isValidEmail('user@domain')).toBe(false);
153
- });
154
-
155
- it('"user @example.com" is invalid (space in local part)', () => {
156
- expect(isValidEmail('user @example.com')).toBe(false);
157
- });
158
- });
159
- });
160
-
161
- // ---------------------------------------------------------------------------
162
- // OrgRole enum validation
163
- // ---------------------------------------------------------------------------
164
- describe('OrgRole validation', () => {
165
- describe('valid roles', () => {
166
- it('"owner" is valid', () => {
167
- expect(isValidOrgRole('owner')).toBe(true);
168
- });
169
-
170
- it('"admin" is valid', () => {
171
- expect(isValidOrgRole('admin')).toBe(true);
172
- });
173
-
174
- it('"member" is valid', () => {
175
- expect(isValidOrgRole('member')).toBe(true);
176
- });
177
-
178
- it('"viewer" is valid', () => {
179
- expect(isValidOrgRole('viewer')).toBe(true);
180
- });
181
- });
182
-
183
- describe('invalid roles', () => {
184
- it('"superadmin" is invalid', () => {
185
- expect(isValidOrgRole('superadmin')).toBe(false);
186
- });
187
-
188
- it('"Owner" is invalid (case-sensitive)', () => {
189
- expect(isValidOrgRole('Owner')).toBe(false);
190
- });
191
-
192
- it('"ADMIN" is invalid (uppercase)', () => {
193
- expect(isValidOrgRole('ADMIN')).toBe(false);
194
- });
195
-
196
- it('empty string is invalid', () => {
197
- expect(isValidOrgRole('')).toBe(false);
198
- });
199
-
200
- it('"moderator" is invalid', () => {
201
- expect(isValidOrgRole('moderator')).toBe(false);
202
- });
203
-
204
- it('"guest" is invalid', () => {
205
- expect(isValidOrgRole('guest')).toBe(false);
206
- });
207
-
208
- it('"MEMBER" is invalid (uppercase)', () => {
209
- expect(isValidOrgRole('MEMBER')).toBe(false);
210
- });
211
-
212
- it('"Viewer" is invalid (mixed case)', () => {
213
- expect(isValidOrgRole('Viewer')).toBe(false);
214
- });
215
- });
216
- });
217
-
218
- // ---------------------------------------------------------------------------
219
- // Token format validation (hex, exactly 64 chars)
220
- // ---------------------------------------------------------------------------
221
- describe('Token format validation', () => {
222
- describe('valid tokens', () => {
223
- it('"a".repeat(64) is valid', () => {
224
- const token = 'a'.repeat(64);
225
- expect(token.length).toBe(64);
226
- expect(isValidToken(token)).toBe(true);
227
- });
228
-
229
- it('"abc123".repeat(10) + "ab" (64 hex chars) is valid', () => {
230
- // "abc123" * 10 = 60 chars + "ab" = 62? Let's compute correctly:
231
- // "abc123".repeat(10) = 60 chars, need 4 more: "abcd" => 64 total
232
- const token = 'abc123'.repeat(10) + 'abcd';
233
- expect(token.length).toBe(64);
234
- // all chars in 'abc123abcd' are hex (a,b,c,d,1,2,3)
235
- expect(isValidToken(token)).toBe(true);
236
- });
237
-
238
- it('all-zeros token "0".repeat(64) is valid', () => {
239
- const token = '0'.repeat(64);
240
- expect(isValidToken(token)).toBe(true);
241
- });
242
-
243
- it('all-f token "f".repeat(64) is valid', () => {
244
- const token = 'f'.repeat(64);
245
- expect(isValidToken(token)).toBe(true);
246
- });
247
-
248
- it('mixed hex chars covering 0-9 and a-f is valid', () => {
249
- // 64 chars using full hex alphabet
250
- const token = '0123456789abcdef'.repeat(4);
251
- expect(token.length).toBe(64);
252
- expect(isValidToken(token)).toBe(true);
253
- });
254
- });
255
-
256
- describe('invalid tokens', () => {
257
- it('"short" is invalid (too few chars)', () => {
258
- expect(isValidToken('short')).toBe(false);
259
- });
260
-
261
- it('"z".repeat(64) is invalid ("z" is not a hex digit)', () => {
262
- const token = 'z'.repeat(64);
263
- expect(token.length).toBe(64);
264
- expect(isValidToken(token)).toBe(false);
265
- });
266
-
267
- it('empty string is invalid', () => {
268
- expect(isValidToken('')).toBe(false);
269
- });
270
-
271
- it('63 hex chars is invalid (one too short)', () => {
272
- const token = 'a'.repeat(63);
273
- expect(token.length).toBe(63);
274
- expect(isValidToken(token)).toBe(false);
275
- });
276
-
277
- it('65 hex chars is invalid (one too long)', () => {
278
- const token = 'a'.repeat(65);
279
- expect(token.length).toBe(65);
280
- expect(isValidToken(token)).toBe(false);
281
- });
282
-
283
- it('uppercase hex "A".repeat(64) is invalid (must be lowercase)', () => {
284
- const token = 'A'.repeat(64);
285
- expect(isValidToken(token)).toBe(false);
286
- });
287
-
288
- it('token with hyphens is invalid', () => {
289
- // UUID-style, 32 hex + 4 hyphens = not valid token
290
- const token = 'a'.repeat(28) + '-' + 'b'.repeat(27) + '-' + 'cc';
291
- expect(isValidToken(token)).toBe(false);
292
- });
293
-
294
- it('token with spaces is invalid', () => {
295
- const token = 'a'.repeat(32) + ' ' + 'b'.repeat(31);
296
- expect(isValidToken(token)).toBe(false);
297
- });
298
-
299
- it('"g".repeat(64) is invalid ("g" is not hex)', () => {
300
- const token = 'g'.repeat(64);
301
- expect(isValidToken(token)).toBe(false);
302
- });
303
- });
304
- });
@@ -1,13 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "target": "ES2020",
5
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
- "module": "ESNext",
7
- "moduleResolution": "Bundler",
8
- "jsx": "react-jsx",
9
- "noEmit": true
10
- },
11
- "include": ["src/client/**/*", "src/shared/**/*"],
12
- "exclude": ["node_modules", "dist"]
13
- }
package/tsconfig.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src",
6
- "composite": true,
7
- "module": "NodeNext",
8
- "moduleResolution": "NodeNext"
9
- },
10
- "include": ["src/server/**/*", "src/shared/**/*", "src/index.ts"],
11
- "exclude": ["node_modules", "dist", "tests", "src/client"]
12
- }