@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
package/src/index.ts DELETED
@@ -1,24 +0,0 @@
1
- // ─── Server exports (Node.js + tsc, DO NOT import in browser bundles) ─────────
2
- export { createServerModule } from './server/index.js';
3
- export type {
4
- ServerModuleAdapter,
5
- TeamManagementConfig,
6
- TeamManagementServerModule,
7
- OrgRole,
8
- AuditActorType,
9
- TransferStatus,
10
- TmOrganization,
11
- TmMembership,
12
- TmInvitation,
13
- TmAuditEvent,
14
- TmOwnershipTransfer,
15
- TmPasswordResetRequest,
16
- TeamManagementFeatureFlags,
17
- } from './server/types.js';
18
-
19
- // ─── Shared types (safe everywhere) ───────────────────────────────────────────
20
- export type { TeamMember, TeamInvite } from './shared/types.js';
21
-
22
- // NOTE: Client exports live in packages/team-management/src/client/index.ts
23
- // Import them in your bundler (Vite/webpack) via the "client" export condition:
24
- // import { PlaceholderPage } from '@varshylinc/team-management/client'
@@ -1,47 +0,0 @@
1
- import { createCipheriv, createDecipheriv, randomBytes, createHash } from 'crypto';
2
-
3
- const ALGORITHM = 'aes-256-gcm';
4
- const IV_LENGTH = 12;
5
-
6
- function getKey(): Buffer {
7
- const secret = process.env.TM_ENCRYPTION_KEY;
8
- if (!secret) throw new Error('TM_ENCRYPTION_KEY environment variable is required');
9
- return createHash('sha256').update(secret).digest();
10
- }
11
-
12
- export function encrypt(plaintext: string): string {
13
- const key = getKey();
14
- const iv = randomBytes(IV_LENGTH);
15
- const cipher = createCipheriv(ALGORITHM, key, iv);
16
- const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
17
- const tag = cipher.getAuthTag();
18
- return `${iv.toString('hex')}:${tag.toString('hex')}:${encrypted.toString('hex')}`;
19
- }
20
-
21
- export function decrypt(ciphertext: string): string {
22
- const key = getKey();
23
- const parts = ciphertext.split(':');
24
- if (parts.length !== 3) throw new Error('Invalid ciphertext format');
25
- const [ivHex, tagHex, encHex] = parts;
26
- const iv = Buffer.from(ivHex, 'hex');
27
- const tag = Buffer.from(tagHex, 'hex');
28
- const encrypted = Buffer.from(encHex, 'hex');
29
- const decipher = createDecipheriv(ALGORITHM, key, iv);
30
- decipher.setAuthTag(tag);
31
- return decipher.update(encrypted).toString('utf8') + decipher.final('utf8');
32
- }
33
-
34
- export function sha256(input: string): string {
35
- return createHash('sha256').update(input).digest('hex');
36
- }
37
-
38
- export function generateToken(byteLength = 32): string {
39
- return randomBytes(byteLength).toString('hex');
40
- }
41
-
42
- export function generateSixDigitCode(): string {
43
- // Cryptographically random 6-digit code (100000-999999)
44
- const rand = randomBytes(4).readUInt32BE(0);
45
- return String(100000 + (rand % 900000));
46
- }
47
-
@@ -1,167 +0,0 @@
1
- import { Router } from 'express';
2
- import type { Pool } from 'pg';
3
- import { readFileSync } from 'fs';
4
- import { join } from 'path';
5
- import { createHealthRouter } from './routes/health.routes.js';
6
- import { createOrgsRouter } from './routes/orgs.routes.js';
7
- import { createInvitationsRouter } from './routes/invitations.routes.js';
8
- import { createMeRouter } from './routes/me.routes.js';
9
- import { createTransferRouter } from './routes/transfer.routes.js';
10
- import { createAuditRouter } from './routes/audit.routes.js';
11
- import { createAdminRouter } from './routes/admin.routes.js';
12
- import { seedSuperAdmin } from './services/super-admin.service.js';
13
- import type {
14
- ServerModuleAdapter,
15
- TeamManagementConfig,
16
- TeamManagementServerModule,
17
- TeamManagementFeatureFlags,
18
- } from './types.js';
19
-
20
- const migrationsDir = join(new URL('.', import.meta.url).pathname, 'migrations');
21
-
22
- // All migrations in order. Append new migrations here — never reorder or remove.
23
- const MIGRATIONS: Array<{ name: string; file: string }> = [
24
- { name: '0001_create_tm_schema_migrations', file: join(migrationsDir, '0001_create_tm_schema_migrations.sql') },
25
- { name: '0002_create_tm_organizations', file: join(migrationsDir, '0002_create_tm_organizations.sql') },
26
- { name: '0003_create_tm_memberships', file: join(migrationsDir, '0003_create_tm_memberships.sql') },
27
- { name: '0004_create_tm_invitations', file: join(migrationsDir, '0004_create_tm_invitations.sql') },
28
- { name: '0005_create_tm_audit_events', file: join(migrationsDir, '0005_create_tm_audit_events.sql') },
29
- { name: '0006_create_tm_email_change_requests', file: join(migrationsDir, '0006_create_tm_email_change_requests.sql') },
30
- { name: '0007_create_tm_ownership_transfers', file: join(migrationsDir, '0007_create_tm_ownership_transfers.sql') },
31
- { name: '0008_create_tm_super_admins', file: join(migrationsDir, '0008_create_tm_super_admins.sql') },
32
- { name: '0009_create_tm_password_reset_requests',file: join(migrationsDir, '0009_create_tm_password_reset_requests.sql') },
33
- { name: '0010_create_tm_shared_access', file: join(migrationsDir, '0010_create_tm_shared_access.sql') },
34
- { name: '0011_seed_super_admin', file: join(migrationsDir, '0011_seed_super_admin.sql') },
35
- { name: '0012_create_tm_user_locks', file: join(migrationsDir, '0012_create_tm_user_locks.sql') },
36
- ];
37
-
38
- // Default feature flags for v0.1.0
39
- const DEFAULT_FLAGS: Required<TeamManagementFeatureFlags> = {
40
- enableInvites: true,
41
- enableAuditLog: true,
42
- enableOwnershipTransfer: true,
43
- enableEmailChange: true,
44
- enablePasswordReset: true,
45
- enableSuperAdmin: false,
46
- enableSharedAccess: false,
47
- enableHardDelete: false,
48
- };
49
-
50
- // ── Standalone migration runner ─────────────────────────────────────────────
51
- // Exported so tests and globalSetup can call it directly without a full module.
52
- export async function runMigrations(
53
- db: Pool,
54
- logger: { info(msg: string): void } = { info: () => {} }
55
- ): Promise<{ applied: string[]; skipped: string[] }> {
56
- const applied: string[] = [];
57
- const skipped: string[] = [];
58
-
59
- // Boot step: ensure ledger table exists (idempotent — CREATE TABLE IF NOT EXISTS)
60
- const ledgerSql = readFileSync(
61
- join(migrationsDir, '0001_create_tm_schema_migrations.sql'),
62
- 'utf-8'
63
- );
64
- await db.query(ledgerSql);
65
-
66
- for (const migration of MIGRATIONS) {
67
- const result = await db.query(
68
- 'SELECT id FROM tm_schema_migrations WHERE migration = $1',
69
- [migration.name]
70
- );
71
-
72
- if (result.rows.length > 0) {
73
- skipped.push(migration.name);
74
- logger.info(`[team-management] skipped: ${migration.name}`);
75
- continue;
76
- }
77
-
78
- const sql = readFileSync(migration.file, 'utf-8');
79
- await db.query(sql);
80
- await db.query(
81
- 'INSERT INTO tm_schema_migrations (migration) VALUES ($1)',
82
- [migration.name]
83
- );
84
-
85
- applied.push(migration.name);
86
- logger.info(`[team-management] applied: ${migration.name}`);
87
- }
88
-
89
- return { applied, skipped };
90
- }
91
-
92
- /**
93
- * createServerModule — entry point for host products.
94
- *
95
- * Usage (production):
96
- * const tm = createServerModule({ adapter, db, config });
97
- * await tm.runMigrations();
98
- * app.use('/api/team', tm.router);
99
- *
100
- * Usage (test shorthand — pool and features accepted as aliases):
101
- * const tm = createServerModule({ adapter, pool, features });
102
- */
103
- export function createServerModule(opts: {
104
- adapter: ServerModuleAdapter;
105
- /** pg Pool — use `db` or `pool` interchangeably */
106
- db?: Pool;
107
- /** Alias for db */
108
- pool?: Pool;
109
- /** Full config object (takes precedence over individual fields below) */
110
- config?: TeamManagementConfig;
111
- /** Shorthand for config.featureFlags */
112
- features?: Partial<TeamManagementFeatureFlags>;
113
- /** Shorthand for config.baseUrl */
114
- baseUrl?: string;
115
- }): TeamManagementServerModule {
116
- const db = (opts.db ?? opts.pool)!;
117
- const featureFlags: Partial<TeamManagementFeatureFlags> =
118
- opts.config?.featureFlags ?? opts.features ?? {};
119
- const baseUrl = opts.config?.baseUrl ?? opts.baseUrl ?? '';
120
- const config: TeamManagementConfig = { featureFlags, baseUrl };
121
- const { adapter } = opts;
122
-
123
- const flags: Required<TeamManagementFeatureFlags> = {
124
- ...DEFAULT_FLAGS,
125
- ...featureFlags,
126
- };
127
-
128
- // ── Super-admin seed on boot ────────────────────────────────────────────────
129
-
130
- if (flags.enableSuperAdmin) {
131
- const superAdminEmail = process.env.TM_SUPER_ADMIN_EMAIL;
132
- if (superAdminEmail) {
133
- seedSuperAdmin(db, adapter, superAdminEmail).catch(e => {
134
- adapter.logger.warn('[team-management] seedSuperAdmin failed', {
135
- error: (e as Error).message,
136
- });
137
- });
138
- }
139
- }
140
-
141
- // ── Routers ─────────────────────────────────────────────────────────────────
142
-
143
- const { router: healthRouter } = createHealthRouter(db, config);
144
- const orgsRouter = createOrgsRouter(db, adapter, flags);
145
- const invitationsRouter = createInvitationsRouter(db, adapter, flags, baseUrl);
146
- const meRouter = createMeRouter(db, adapter, flags, baseUrl);
147
- const transferRouter = createTransferRouter(db, adapter, flags, baseUrl);
148
- const auditRouter = createAuditRouter(db, adapter, flags);
149
- const adminRouter = createAdminRouter(db, adapter, flags, baseUrl);
150
-
151
- const router = Router();
152
-
153
- router.use(healthRouter);
154
- router.use('/me', meRouter);
155
- router.use('/orgs', orgsRouter);
156
- router.use('/orgs', invitationsRouter);
157
- router.use('/orgs', transferRouter);
158
- router.use('/orgs', auditRouter);
159
- router.use('/invitations', invitationsRouter);
160
- router.use('/admin', adminRouter);
161
-
162
- const migrate = () => runMigrations(db, adapter.logger);
163
-
164
- return { router, runMigrations: migrate, migrate };
165
- }
166
-
167
- export type { ServerModuleAdapter, TeamManagementConfig, TeamManagementServerModule };
@@ -1,48 +0,0 @@
1
- import type { Request, Response, NextFunction } from 'express';
2
- import type { Pool } from 'pg';
3
- import type { ServerModuleAdapter, OrgRole } from '../types.js';
4
-
5
- export interface AuthenticatedRequest extends Request {
6
- userId: number;
7
- orgId: number;
8
- userRole: OrgRole;
9
- }
10
-
11
- export function requireMembership(pool: Pool, adapter: ServerModuleAdapter) {
12
- return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
13
- try {
14
- const userId = await adapter.getCurrentUserId(req);
15
- if (!userId) {
16
- res.status(401).json({ error: 'Authentication required' });
17
- return;
18
- }
19
-
20
- const orgIdParam = parseInt(req.params.orgId || req.params.id || '', 10);
21
- if (isNaN(orgIdParam)) {
22
- res.status(400).json({ error: 'Invalid org ID' });
23
- return;
24
- }
25
-
26
- const result = await pool.query(
27
- `SELECT m.role FROM tm_memberships m
28
- JOIN tm_organizations o ON o.id = m.org_id
29
- WHERE m.org_id = $1 AND m.user_id = $2 AND m.removed_at IS NULL
30
- AND o.deleted_at IS NULL`,
31
- [orgIdParam, userId]
32
- );
33
-
34
- if (result.rows.length === 0) {
35
- res.status(403).json({ error: 'You are not a member of this organization' });
36
- return;
37
- }
38
-
39
- (req as AuthenticatedRequest).userId = userId;
40
- (req as AuthenticatedRequest).orgId = orgIdParam;
41
- (req as AuthenticatedRequest).userRole = result.rows[0].role as OrgRole;
42
- next();
43
- } catch (e) {
44
- adapter.logger.error('[requireMembership]', { error: (e as Error).message });
45
- res.status(500).json({ error: 'Authentication check failed' });
46
- }
47
- };
48
- }
@@ -1,19 +0,0 @@
1
- import type { Request, Response, NextFunction } from 'express';
2
- import type { OrgRole } from '../types.js';
3
- import { roleAtLeast } from '../types.js';
4
- import type { AuthenticatedRequest } from './require-membership.js';
5
-
6
- export function requireRole(minimumRole: OrgRole) {
7
- return (req: Request, res: Response, next: NextFunction): void => {
8
- const authReq = req as AuthenticatedRequest;
9
- if (!authReq.userRole) {
10
- res.status(401).json({ error: 'Authentication required' });
11
- return;
12
- }
13
- if (!roleAtLeast(authReq.userRole, minimumRole)) {
14
- res.status(403).json({ error: `Requires ${minimumRole} role or higher` });
15
- return;
16
- }
17
- next();
18
- };
19
- }
@@ -1,32 +0,0 @@
1
- import type { Request, Response, NextFunction } from 'express';
2
- import type { Pool } from 'pg';
3
- import type { ServerModuleAdapter, TeamManagementFeatureFlags } from '../types.js';
4
-
5
- export function requireSuperAdmin(pool: Pool, adapter: ServerModuleAdapter, flags: TeamManagementFeatureFlags) {
6
- return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
7
- if (!flags.enableSuperAdmin) {
8
- res.status(404).json({ error: 'Not found' });
9
- return;
10
- }
11
- try {
12
- const userId = await adapter.getCurrentUserId(req);
13
- if (!userId) {
14
- res.status(401).json({ error: 'Authentication required' });
15
- return;
16
- }
17
- const result = await pool.query(
18
- `SELECT user_id FROM tm_super_admins WHERE user_id = $1 AND revoked_at IS NULL`,
19
- [userId]
20
- );
21
- if (result.rows.length === 0) {
22
- res.status(403).json({ error: 'Super-admin access required' });
23
- return;
24
- }
25
- (req as Request & { superAdminUserId: number }).superAdminUserId = userId;
26
- next();
27
- } catch (e) {
28
- adapter.logger.error('[requireSuperAdmin]', { error: (e as Error).message });
29
- res.status(500).json({ error: 'Authorization check failed' });
30
- }
31
- };
32
- }
@@ -1,13 +0,0 @@
1
- -- Migration: 0001_create_tm_schema_migrations
2
- -- Creates the ledger table that tracks which team-management
3
- -- migrations have been applied. All tm_* tables are owned by
4
- -- the team-management module — host products never query them directly.
5
-
6
- CREATE TABLE IF NOT EXISTS tm_schema_migrations (
7
- id SERIAL PRIMARY KEY,
8
- migration VARCHAR(255) NOT NULL UNIQUE,
9
- applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
10
- );
11
-
12
- CREATE INDEX IF NOT EXISTS idx_tm_schema_migrations_migration
13
- ON tm_schema_migrations (migration);
@@ -1,14 +0,0 @@
1
- CREATE TABLE IF NOT EXISTS tm_organizations (
2
- id SERIAL PRIMARY KEY,
3
- name TEXT NOT NULL,
4
- slug TEXT NOT NULL UNIQUE,
5
- owner_user_id INTEGER NOT NULL, -- denormalized for quick lookup
6
- settings JSONB NOT NULL DEFAULT '{}',
7
- deleted_at TIMESTAMPTZ,
8
- delete_scheduled_for TIMESTAMPTZ,
9
- deleted_by_user_id INTEGER,
10
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
11
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
12
- );
13
- CREATE UNIQUE INDEX IF NOT EXISTS idx_tm_orgs_slug ON tm_organizations(slug) WHERE deleted_at IS NULL;
14
- CREATE INDEX IF NOT EXISTS idx_tm_orgs_owner ON tm_organizations(owner_user_id);
@@ -1,24 +0,0 @@
1
- -- If the type already exists, ignore
2
- DO $$ BEGIN
3
- CREATE TYPE tm_role AS ENUM ('owner', 'admin', 'member', 'viewer');
4
- EXCEPTION WHEN duplicate_object THEN NULL;
5
- END $$;
6
-
7
- CREATE TABLE IF NOT EXISTS tm_memberships (
8
- id SERIAL PRIMARY KEY,
9
- org_id INTEGER NOT NULL REFERENCES tm_organizations(id) ON DELETE CASCADE,
10
- user_id INTEGER NOT NULL,
11
- role tm_role NOT NULL DEFAULT 'member',
12
- joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
13
- removed_at TIMESTAMPTZ,
14
- removed_by_user_id INTEGER,
15
- removal_reason TEXT,
16
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
17
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
18
- );
19
- -- Only one active membership per user per org
20
- CREATE UNIQUE INDEX IF NOT EXISTS idx_tm_memberships_active ON tm_memberships(org_id, user_id) WHERE removed_at IS NULL;
21
- -- Only one owner per org (active)
22
- CREATE UNIQUE INDEX IF NOT EXISTS idx_tm_memberships_owner ON tm_memberships(org_id) WHERE role = 'owner' AND removed_at IS NULL;
23
- CREATE INDEX IF NOT EXISTS idx_tm_memberships_user ON tm_memberships(user_id);
24
- CREATE INDEX IF NOT EXISTS idx_tm_memberships_org ON tm_memberships(org_id);
@@ -1,22 +0,0 @@
1
- CREATE TABLE IF NOT EXISTS tm_invitations (
2
- id SERIAL PRIMARY KEY,
3
- org_id INTEGER NOT NULL REFERENCES tm_organizations(id) ON DELETE CASCADE,
4
- invited_by_user_id INTEGER NOT NULL,
5
- email TEXT NOT NULL,
6
- role tm_role NOT NULL DEFAULT 'member',
7
- token_hash TEXT NOT NULL UNIQUE, -- SHA-256 of the magic-link token
8
- code_encrypted TEXT NOT NULL, -- AES-256-GCM encrypted 6-digit code
9
- expires_at TIMESTAMPTZ NOT NULL, -- NOW() + 7 days
10
- accepted_at TIMESTAMPTZ,
11
- revoked_at TIMESTAMPTZ,
12
- revoked_by_user_id INTEGER,
13
- resent_count INTEGER NOT NULL DEFAULT 0,
14
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
15
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
16
- );
17
- -- Only one pending (non-accepted, non-revoked) invite per email per org
18
- -- Note: expires_at check omitted from predicate (NOW() is not IMMUTABLE); enforced at query time
19
- CREATE UNIQUE INDEX IF NOT EXISTS idx_tm_invitations_pending ON tm_invitations(org_id, email) WHERE accepted_at IS NULL AND revoked_at IS NULL;
20
- CREATE INDEX IF NOT EXISTS idx_tm_invitations_org ON tm_invitations(org_id);
21
- CREATE INDEX IF NOT EXISTS idx_tm_invitations_email ON tm_invitations(email);
22
-
@@ -1,17 +0,0 @@
1
- CREATE TABLE IF NOT EXISTS tm_audit_events (
2
- id BIGSERIAL PRIMARY KEY,
3
- org_id INTEGER REFERENCES tm_organizations(id) ON DELETE SET NULL,
4
- actor_user_id INTEGER,
5
- actor_type TEXT NOT NULL DEFAULT 'user' CHECK (actor_type IN ('user', 'super_admin')),
6
- action TEXT NOT NULL,
7
- target_type TEXT,
8
- target_id TEXT,
9
- before_state JSONB,
10
- after_state JSONB,
11
- ip TEXT,
12
- user_agent TEXT,
13
- reason TEXT, -- required for super_admin actions
14
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
15
- );
16
- CREATE INDEX IF NOT EXISTS idx_tm_audit_org ON tm_audit_events(org_id, created_at DESC);
17
- CREATE INDEX IF NOT EXISTS idx_tm_audit_actor ON tm_audit_events(actor_user_id);
@@ -1,13 +0,0 @@
1
- CREATE TABLE IF NOT EXISTS tm_email_change_requests (
2
- id SERIAL PRIMARY KEY,
3
- user_id INTEGER NOT NULL,
4
- old_email TEXT,
5
- new_email TEXT NOT NULL,
6
- verify_token_hash TEXT NOT NULL UNIQUE, -- SHA-256 of verification token
7
- cancel_token_hash TEXT NOT NULL UNIQUE, -- SHA-256 of cancellation token
8
- expires_at TIMESTAMPTZ NOT NULL, -- NOW() + 24 hours
9
- verified_at TIMESTAMPTZ,
10
- cancelled_at TIMESTAMPTZ,
11
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
12
- );
13
- CREATE INDEX IF NOT EXISTS idx_tm_email_change_user ON tm_email_change_requests(user_id, created_at DESC);
@@ -1,22 +0,0 @@
1
- DO $$ BEGIN
2
- CREATE TYPE tm_transfer_status AS ENUM ('pending', 'accepted', 'cancelled', 'expired');
3
- EXCEPTION WHEN duplicate_object THEN NULL;
4
- END $$;
5
-
6
- CREATE TABLE IF NOT EXISTS tm_ownership_transfers (
7
- id SERIAL PRIMARY KEY,
8
- org_id INTEGER NOT NULL REFERENCES tm_organizations(id) ON DELETE CASCADE,
9
- from_user_id INTEGER NOT NULL,
10
- to_user_id INTEGER NOT NULL,
11
- status tm_transfer_status NOT NULL DEFAULT 'pending',
12
- initiated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
13
- accepted_at TIMESTAMPTZ,
14
- cancelled_at TIMESTAMPTZ,
15
- cancelled_by_user_id INTEGER,
16
- expires_at TIMESTAMPTZ NOT NULL, -- NOW() + 7 days
17
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
18
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
19
- );
20
- -- Only one pending transfer per org
21
- CREATE UNIQUE INDEX IF NOT EXISTS idx_tm_transfers_pending ON tm_ownership_transfers(org_id) WHERE status = 'pending';
22
- CREATE INDEX IF NOT EXISTS idx_tm_transfers_org ON tm_ownership_transfers(org_id);
@@ -1,8 +0,0 @@
1
- CREATE TABLE IF NOT EXISTS tm_super_admins (
2
- user_id INTEGER PRIMARY KEY,
3
- granted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
4
- granted_by_user_id INTEGER,
5
- notes TEXT,
6
- revoked_at TIMESTAMPTZ
7
- );
8
- CREATE INDEX IF NOT EXISTS idx_tm_super_admins_active ON tm_super_admins(user_id) WHERE revoked_at IS NULL;
@@ -1,9 +0,0 @@
1
- CREATE TABLE IF NOT EXISTS tm_password_reset_requests (
2
- id SERIAL PRIMARY KEY,
3
- user_id INTEGER NOT NULL,
4
- token_hash TEXT NOT NULL UNIQUE, -- SHA-256 of reset token
5
- expires_at TIMESTAMPTZ NOT NULL, -- NOW() + 1 hour
6
- used_at TIMESTAMPTZ,
7
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
8
- );
9
- CREATE INDEX IF NOT EXISTS idx_tm_pwd_reset_user ON tm_password_reset_requests(user_id, created_at DESC);
@@ -1,8 +0,0 @@
1
- -- tm_shared_access: reserved for v0.2.0 (cross-org vendor access).
2
- -- Empty scaffold -- no columns beyond id. Do not add anything here in v0.1.0.
3
- CREATE TABLE IF NOT EXISTS tm_shared_access (
4
- id SERIAL PRIMARY KEY,
5
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
6
- );
7
- -- NOTE: This table is intentionally empty in v0.1.0.
8
- -- Schema will be defined in the v0.2.0 migration.
@@ -1,15 +0,0 @@
1
- -- Seed super-admin for vaakapila@gmail.com.
2
- -- This migration is ALWAYS run but only inserts if:
3
- -- 1. A user with that email exists in the host app's user table.
4
- -- 2. The row does not already exist in tm_super_admins.
5
- -- The host adapter is responsible for user existence; this migration
6
- -- does nothing if the user does not exist (host-managed users table
7
- -- is outside module scope). The demo-host seeds this user before
8
- -- running migrations.
9
- --
10
- -- Since tm_super_admins uses host user_id (INTEGER), and we cannot
11
- -- JOIN across host tables from inside the module, this seed is
12
- -- handled by the createServerModule boot sequence when
13
- -- enableSuperAdmin=true (see index.ts seedSuperAdmin()).
14
- -- This file is a no-op placeholder to reserve the migration slot.
15
- SELECT 1; -- placeholder: actual seed runs in application boot
@@ -1,7 +0,0 @@
1
- CREATE TABLE IF NOT EXISTS tm_user_locks (
2
- user_id INTEGER PRIMARY KEY,
3
- locked_by INTEGER NOT NULL,
4
- reason TEXT NOT NULL,
5
- locked_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
6
- unlocked_at TIMESTAMPTZ
7
- );