@varshylinc/team-management 0.1.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 (203) hide show
  1. package/.eslintrc.cjs +18 -0
  2. package/CHANGELOG.md +159 -0
  3. package/LICENSE +6 -0
  4. package/README.md +97 -0
  5. package/dist/index.d.ts +4 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +6 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/server/crypto.d.ts +6 -0
  10. package/dist/server/crypto.d.ts.map +1 -0
  11. package/dist/server/crypto.js +42 -0
  12. package/dist/server/crypto.js.map +1 -0
  13. package/dist/server/index.d.ts +34 -0
  14. package/dist/server/index.d.ts.map +1 -0
  15. package/dist/server/index.js +114 -0
  16. package/dist/server/index.js.map +1 -0
  17. package/dist/server/middleware/require-membership.d.ts +10 -0
  18. package/dist/server/middleware/require-membership.d.ts.map +1 -0
  19. package/dist/server/middleware/require-membership.js +33 -0
  20. package/dist/server/middleware/require-membership.js.map +1 -0
  21. package/dist/server/middleware/require-role.d.ts +4 -0
  22. package/dist/server/middleware/require-role.d.ts.map +1 -0
  23. package/dist/server/middleware/require-role.js +16 -0
  24. package/dist/server/middleware/require-role.js.map +1 -0
  25. package/dist/server/middleware/require-super-admin.d.ts +5 -0
  26. package/dist/server/middleware/require-super-admin.d.ts.map +1 -0
  27. package/dist/server/middleware/require-super-admin.js +27 -0
  28. package/dist/server/middleware/require-super-admin.js.map +1 -0
  29. package/dist/server/migrations/0001_create_tm_schema_migrations.sql +13 -0
  30. package/dist/server/migrations/0002_create_tm_organizations.sql +14 -0
  31. package/dist/server/migrations/0003_create_tm_memberships.sql +24 -0
  32. package/dist/server/migrations/0004_create_tm_invitations.sql +22 -0
  33. package/dist/server/migrations/0005_create_tm_audit_events.sql +17 -0
  34. package/dist/server/migrations/0006_create_tm_email_change_requests.sql +13 -0
  35. package/dist/server/migrations/0007_create_tm_ownership_transfers.sql +22 -0
  36. package/dist/server/migrations/0008_create_tm_super_admins.sql +8 -0
  37. package/dist/server/migrations/0009_create_tm_password_reset_requests.sql +9 -0
  38. package/dist/server/migrations/0010_create_tm_shared_access.sql +8 -0
  39. package/dist/server/migrations/0011_seed_super_admin.sql +15 -0
  40. package/dist/server/migrations/0012_create_tm_user_locks.sql +7 -0
  41. package/dist/server/routes/admin.routes.d.ts +5 -0
  42. package/dist/server/routes/admin.routes.d.ts.map +1 -0
  43. package/dist/server/routes/admin.routes.js +262 -0
  44. package/dist/server/routes/admin.routes.js.map +1 -0
  45. package/dist/server/routes/audit.routes.d.ts +5 -0
  46. package/dist/server/routes/audit.routes.d.ts.map +1 -0
  47. package/dist/server/routes/audit.routes.js +70 -0
  48. package/dist/server/routes/audit.routes.js.map +1 -0
  49. package/dist/server/routes/health.routes.d.ts +8 -0
  50. package/dist/server/routes/health.routes.d.ts.map +1 -0
  51. package/dist/server/routes/health.routes.js +39 -0
  52. package/dist/server/routes/health.routes.js.map +1 -0
  53. package/dist/server/routes/invitations.routes.d.ts +5 -0
  54. package/dist/server/routes/invitations.routes.d.ts.map +1 -0
  55. package/dist/server/routes/invitations.routes.js +232 -0
  56. package/dist/server/routes/invitations.routes.js.map +1 -0
  57. package/dist/server/routes/me.routes.d.ts +5 -0
  58. package/dist/server/routes/me.routes.d.ts.map +1 -0
  59. package/dist/server/routes/me.routes.js +188 -0
  60. package/dist/server/routes/me.routes.js.map +1 -0
  61. package/dist/server/routes/orgs.routes.d.ts +5 -0
  62. package/dist/server/routes/orgs.routes.d.ts.map +1 -0
  63. package/dist/server/routes/orgs.routes.js +371 -0
  64. package/dist/server/routes/orgs.routes.js.map +1 -0
  65. package/dist/server/routes/transfer.routes.d.ts +5 -0
  66. package/dist/server/routes/transfer.routes.d.ts.map +1 -0
  67. package/dist/server/routes/transfer.routes.js +108 -0
  68. package/dist/server/routes/transfer.routes.js.map +1 -0
  69. package/dist/server/services/audit.service.d.ts +20 -0
  70. package/dist/server/services/audit.service.d.ts.map +1 -0
  71. package/dist/server/services/audit.service.js +23 -0
  72. package/dist/server/services/audit.service.js.map +1 -0
  73. package/dist/server/services/email-change.service.d.ts +16 -0
  74. package/dist/server/services/email-change.service.d.ts.map +1 -0
  75. package/dist/server/services/email-change.service.js +107 -0
  76. package/dist/server/services/email-change.service.js.map +1 -0
  77. package/dist/server/services/invitations.service.d.ts +41 -0
  78. package/dist/server/services/invitations.service.d.ts.map +1 -0
  79. package/dist/server/services/invitations.service.js +214 -0
  80. package/dist/server/services/invitations.service.js.map +1 -0
  81. package/dist/server/services/memberships.service.d.ts +27 -0
  82. package/dist/server/services/memberships.service.d.ts.map +1 -0
  83. package/dist/server/services/memberships.service.js +69 -0
  84. package/dist/server/services/memberships.service.js.map +1 -0
  85. package/dist/server/services/organizations.service.d.ts +19 -0
  86. package/dist/server/services/organizations.service.d.ts.map +1 -0
  87. package/dist/server/services/organizations.service.js +61 -0
  88. package/dist/server/services/organizations.service.js.map +1 -0
  89. package/dist/server/services/ownership.service.d.ts +19 -0
  90. package/dist/server/services/ownership.service.d.ts.map +1 -0
  91. package/dist/server/services/ownership.service.js +102 -0
  92. package/dist/server/services/ownership.service.js.map +1 -0
  93. package/dist/server/services/password-reset.service.d.ts +12 -0
  94. package/dist/server/services/password-reset.service.d.ts.map +1 -0
  95. package/dist/server/services/password-reset.service.js +54 -0
  96. package/dist/server/services/password-reset.service.js.map +1 -0
  97. package/dist/server/services/super-admin.service.d.ts +59 -0
  98. package/dist/server/services/super-admin.service.d.ts.map +1 -0
  99. package/dist/server/services/super-admin.service.js +187 -0
  100. package/dist/server/services/super-admin.service.js.map +1 -0
  101. package/dist/server/types.d.ts +186 -0
  102. package/dist/server/types.d.ts.map +1 -0
  103. package/dist/server/types.js +6 -0
  104. package/dist/server/types.js.map +1 -0
  105. package/dist/shared/types.d.ts +23 -0
  106. package/dist/shared/types.d.ts.map +1 -0
  107. package/dist/shared/types.js +6 -0
  108. package/dist/shared/types.js.map +1 -0
  109. package/package.json +56 -0
  110. package/src/client/api.ts +314 -0
  111. package/src/client/components/AuditEventRow.tsx +59 -0
  112. package/src/client/components/CascadePreview.tsx +36 -0
  113. package/src/client/components/DangerZoneCard.tsx +103 -0
  114. package/src/client/components/InvitationCodeDisplay.tsx +48 -0
  115. package/src/client/components/InviteForm.tsx +77 -0
  116. package/src/client/components/MemberRow.tsx +69 -0
  117. package/src/client/components/PendingTransferBanner.tsx +98 -0
  118. package/src/client/components/PlaceholderCard.tsx +26 -0
  119. package/src/client/components/RoleBadge.tsx +26 -0
  120. package/src/client/components/RoleSelect.tsx +35 -0
  121. package/src/client/hooks/.gitkeep +0 -0
  122. package/src/client/hooks/useCurrentMembership.ts +24 -0
  123. package/src/client/hooks/useMembers.ts +24 -0
  124. package/src/client/hooks/usePendingInvitations.ts +24 -0
  125. package/src/client/hooks/usePendingTransfer.ts +27 -0
  126. package/src/client/index.ts +80 -0
  127. package/src/client/pages/AuditLogPage.tsx +164 -0
  128. package/src/client/pages/EmailChangePage.tsx +144 -0
  129. package/src/client/pages/InvitationAcceptPage.tsx +163 -0
  130. package/src/client/pages/InvitationCodePage.tsx +108 -0
  131. package/src/client/pages/MembersPage.tsx +290 -0
  132. package/src/client/pages/OrgSettingsPage.tsx +185 -0
  133. package/src/client/pages/OwnershipTransferPage.tsx +163 -0
  134. package/src/client/pages/PasswordResetPage.tsx +104 -0
  135. package/src/client/pages/PasswordResetRequestPage.tsx +71 -0
  136. package/src/client/pages/PlaceholderPage.tsx +20 -0
  137. package/src/client/pages/SuperAdminDashboard.tsx +401 -0
  138. package/src/client/types.ts +78 -0
  139. package/src/index.ts +24 -0
  140. package/src/server/crypto.ts +47 -0
  141. package/src/server/index.ts +167 -0
  142. package/src/server/middleware/require-membership.ts +48 -0
  143. package/src/server/middleware/require-role.ts +19 -0
  144. package/src/server/middleware/require-super-admin.ts +32 -0
  145. package/src/server/migrations/0001_create_tm_schema_migrations.sql +13 -0
  146. package/src/server/migrations/0002_create_tm_organizations.sql +14 -0
  147. package/src/server/migrations/0003_create_tm_memberships.sql +24 -0
  148. package/src/server/migrations/0004_create_tm_invitations.sql +22 -0
  149. package/src/server/migrations/0005_create_tm_audit_events.sql +17 -0
  150. package/src/server/migrations/0006_create_tm_email_change_requests.sql +13 -0
  151. package/src/server/migrations/0007_create_tm_ownership_transfers.sql +22 -0
  152. package/src/server/migrations/0008_create_tm_super_admins.sql +8 -0
  153. package/src/server/migrations/0009_create_tm_password_reset_requests.sql +9 -0
  154. package/src/server/migrations/0010_create_tm_shared_access.sql +8 -0
  155. package/src/server/migrations/0011_seed_super_admin.sql +15 -0
  156. package/src/server/migrations/0012_create_tm_user_locks.sql +7 -0
  157. package/src/server/routes/admin.routes.ts +208 -0
  158. package/src/server/routes/audit.routes.ts +93 -0
  159. package/src/server/routes/health.routes.ts +46 -0
  160. package/src/server/routes/invitations.routes.ts +252 -0
  161. package/src/server/routes/me.routes.ts +143 -0
  162. package/src/server/routes/orgs.routes.ts +428 -0
  163. package/src/server/routes/transfer.routes.ts +110 -0
  164. package/src/server/services/.gitkeep +0 -0
  165. package/src/server/services/audit.service.ts +49 -0
  166. package/src/server/services/email-change.service.ts +178 -0
  167. package/src/server/services/invitations.service.ts +316 -0
  168. package/src/server/services/memberships.service.ts +129 -0
  169. package/src/server/services/organizations.service.ts +110 -0
  170. package/src/server/services/ownership.service.ts +170 -0
  171. package/src/server/services/password-reset.service.ts +94 -0
  172. package/src/server/services/super-admin.service.ts +321 -0
  173. package/src/server/sql/.gitkeep +0 -0
  174. package/src/server/types.ts +145 -0
  175. package/src/shared/types.ts +24 -0
  176. package/tests/integration/audit-fires.test.ts +288 -0
  177. package/tests/integration/cascade-preview.test.ts +157 -0
  178. package/tests/integration/email-change.test.ts +190 -0
  179. package/tests/integration/feature-flags.test.ts +213 -0
  180. package/tests/integration/invitations-code.test.ts +218 -0
  181. package/tests/integration/invitations-expiry.test.ts +216 -0
  182. package/tests/integration/invitations-resend.test.ts +241 -0
  183. package/tests/integration/invitations-revoke.test.ts +226 -0
  184. package/tests/integration/invitations-switch-org.test.ts +156 -0
  185. package/tests/integration/invitations-token.test.ts +221 -0
  186. package/tests/integration/migrations.test.ts +119 -0
  187. package/tests/integration/only-owner-protections.test.ts +130 -0
  188. package/tests/integration/org-lifecycle.test.ts +169 -0
  189. package/tests/integration/ownership-transfer-cancel.test.ts +171 -0
  190. package/tests/integration/ownership-transfer-expire.test.ts +171 -0
  191. package/tests/integration/ownership-transfer-happy.test.ts +184 -0
  192. package/tests/integration/ownership-transfer-locks.test.ts +146 -0
  193. package/tests/integration/password-reset.test.ts +200 -0
  194. package/tests/integration/super-admin-actions.test.ts +180 -0
  195. package/tests/integration/super-admin-restrictions.test.ts +209 -0
  196. package/tests/setup/global-setup.ts +20 -0
  197. package/tests/unit/adapter-shape.test.ts +330 -0
  198. package/tests/unit/role-permissions.test.ts +236 -0
  199. package/tests/unit/validation.test.ts +304 -0
  200. package/tsconfig.client.json +13 -0
  201. package/tsconfig.json +12 -0
  202. package/tsconfig.tsbuildinfo +1 -0
  203. package/vitest.config.ts +13 -0
package/.eslintrc.cjs ADDED
@@ -0,0 +1,18 @@
1
+ module.exports = {
2
+ root: true,
3
+ parser: '@typescript-eslint/parser',
4
+ parserOptions: {
5
+ project: ['./tsconfig.json', './tsconfig.client.json'],
6
+ tsconfigRootDir: __dirname,
7
+ },
8
+ plugins: ['@typescript-eslint'],
9
+ extends: [
10
+ 'eslint:recommended',
11
+ 'plugin:@typescript-eslint/recommended',
12
+ ],
13
+ rules: {
14
+ '@typescript-eslint/no-explicit-any': 'warn',
15
+ '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
16
+ },
17
+ env: { node: true, browser: true, es2022: true },
18
+ };
package/CHANGELOG.md ADDED
@@ -0,0 +1,159 @@
1
+ # Changelog — @varshylinc/team-management
2
+
3
+ ## 0.1.0 — 2026-05-08
4
+
5
+ First production-grade release. v0.0.1 was a stub with no business logic; v0.1.0 is the first version products can actually consume — a complete team management module with org model, RBAC, invitations, audit log, ownership transfer, email change, password reset, and super-admin back office.
6
+
7
+ ### Added
8
+
9
+ **Org management**
10
+ - `POST /orgs` — create organization (name, slug)
11
+ - `GET /orgs/:id` — fetch org details
12
+ - `PATCH /orgs/:id` — update org name and slug (admin+)
13
+ - `DELETE /orgs/:id` — soft-delete with 30-day grace period; must type org name to confirm (owner only)
14
+ - Org slug unique partial index (active orgs only)
15
+ - Soft-delete columns: `deleted_at`, `delete_scheduled_for`, `deleted_by_user_id`
16
+
17
+ **Member management**
18
+ - `GET /orgs/:id/members` — list active members with roles
19
+ - `GET /orgs/:id/members/former` — list removed members with removal metadata
20
+ - `DELETE /orgs/:id/members/:userId` — soft-remove member; records `removed_by_user_id` and `removal_reason`
21
+ - `PATCH /orgs/:id/members/:userId` — change member role (role-gated per matrix)
22
+ - `GET /orgs/:id/members/:userId/cascade-preview` — preview what will be affected by removal
23
+
24
+ **Invitations** (flag: `enableInvites`)
25
+ - `POST /orgs/:id/invitations` — invite by email; sends magic link + 6-digit code in same email; 7-day expiry
26
+ - `GET /orgs/:id/invitations` — list pending invitations (admin+)
27
+ - `DELETE /orgs/:id/invitations/:invId` — revoke pending invitation (admin+)
28
+ - `POST /orgs/:id/invitations/:invId/resend` — regenerate token + code, invalidate old (admin+)
29
+ - `GET /orgs/:id/invitations/:invId/code` — fetch decrypted 6-digit code for phone fallback (admin+)
30
+ - `GET /invitations/accept/:token` — public magic-link landing; shows org, role, inviter
31
+ - `POST /invitations/accept/code` — public code-entry; `{email, code}`
32
+ - Sub-A enforcement: member accepting while already in an org gets warning; owner is blocked entirely
33
+
34
+ **Audit log** (flag: `enableAuditLog`)
35
+ - `GET /orgs/:id/audit` — paginated audit events (admin+ only)
36
+ - 13 event types: `org.created`, `org.settings.updated`, `org.deleted`, `member.invited`, `member.invite_accepted`, `member.invite_revoked`, `member.removed`, `member.role_changed`, `ownership.transfer_initiated`, `ownership.transfer_accepted`, `ownership.transfer_cancelled`, `email.change_requested`, `email.change_completed`
37
+ - Each event stores: `actor_user_id`, `actor_type` (`user`/`super_admin`), `action`, `target_type`, `target_id`, `before` JSONB, `after` JSONB, `ip`, `user_agent`, `reason`, `created_at`
38
+ - Super-admin events appear as "Varshyl Support" (real identity never exposed to org)
39
+
40
+ **Ownership transfer** (flag: `enableOwnershipTransfer`)
41
+ - `POST /orgs/:id/transfer` — initiate transfer to an admin (owner only; target must already be admin)
42
+ - `GET /orgs/:id/transfer` — get pending transfer details
43
+ - `POST /orgs/:id/transfer/:transferId/accept` — accept transfer (atomic role swap)
44
+ - `DELETE /orgs/:id/transfer/:transferId` — cancel transfer (either party)
45
+ - Transfer locks: no second transfer, no org delete, no party removal during pending transfer
46
+ - 7-day expiration; auto-cancelled on expiry
47
+
48
+ **Email change** (flag: `enableEmailChange`)
49
+ - `POST /me/email-change` — request email change; sends verification to new + notice to old
50
+ - `GET /me/email-change/verify` — verify new email via token
51
+ - `POST /me/email-change/cancel` — cancel via old-email cancellation link; triggers mandatory password reset
52
+ - Rate limit: 3 requests per user per 24 hours
53
+ - Old-email notice contains cancel link with mandatory-reset semantics if clicked
54
+
55
+ **Password reset** (flag: `enablePasswordReset`)
56
+ - `POST /me/password-reset/request` — self-service; always returns 200 (no user enumeration)
57
+ - `POST /me/password-reset/confirm` — confirm with token + new password
58
+ - Rate limit: 3 requests per email per hour
59
+ - 1-hour token expiry
60
+
61
+ **Super-admin back office** (flag: `enableSuperAdmin`, default OFF)
62
+ - `GET /admin/orgs` — list all orgs including deleted
63
+ - `GET /admin/orgs/:id` — org details + members
64
+ - `POST /admin/orgs/:id/restore` — restore soft-deleted org during grace period
65
+ - `POST /admin/orgs/:id/appoint-owner` — appoint new owner (requires `reason`)
66
+ - `POST /admin/orgs/:id/hard-delete` — legal-compliance hard delete (requires `legal_basis`)
67
+ - `POST /admin/orgs/:id/members/add` — add member to any org (requires `reason`)
68
+ - `POST /admin/orgs/:id/members/remove` — remove member from any org (requires `reason`)
69
+ - `POST /admin/users/:id/lock` — lock user account (requires `reason`)
70
+ - `POST /admin/users/:id/unlock` — unlock account
71
+ - `POST /admin/users/:id/password-reset` — trigger reset link; super-admin never sees password
72
+ - All super-admin actions require `reason` text; write audit event with `actor_type='super_admin'`
73
+ - Cannot impersonate users; cannot modify product data; cannot set passwords directly
74
+
75
+ **Self-service**
76
+ - `GET /me/membership` — current user's org membership info
77
+
78
+ **Health**
79
+ - `GET /health` — module health with flag states
80
+
81
+ ### Adapter contract changes
82
+
83
+ 11 new required methods added to `ServerModuleAdapter` (all hosts must implement):
84
+
85
+ | Method | Purpose |
86
+ |--------|---------|
87
+ | `getUserById(userId)` | Look up user by ID |
88
+ | `getUsersByIds(userIds[])` | Batch user lookup |
89
+ | `findUserByEmail(email)` | Look up user by email |
90
+ | `createUserFromInvite({email, orgId, role})` | Create user on invite accept |
91
+ | `setUserPassword(userId, hash)` | Store new password hash |
92
+ | `hashPassword(plaintext)` | Hash a password |
93
+ | `verifyPassword(plaintext, hash)` | Verify password against hash |
94
+ | `invalidateAllUserSessions(userId)` | Invalidate all sessions for user |
95
+ | `sendInviteEmail({to, orgName, inviterName, role, magicLinkUrl, code})` | Send invitation email |
96
+ | `sendOwnershipTransferEmail({to, orgName, fromName, transferUrl})` | Send transfer notification |
97
+ | `sendEmailChangeVerification({to, verifyUrl})` | Send verification to new email |
98
+ | `sendEmailChangeOldNotice({to, newEmail, cancelUrl})` | Send notice to old email |
99
+ | `sendEmailChangedFinalNotice({to, oldEmail, newEmail})` | Send final confirmation |
100
+ | `sendPasswordResetEmail({to, resetUrl})` | Send reset link |
101
+ | `sendOrgDeletionNotice({to, orgName, scheduledFor})` | Notify members on org delete |
102
+ | `emitNotification({userId, type, payload})` | In-app notification hook |
103
+
104
+ ### Migrations included
105
+
106
+ | File | Description |
107
+ |------|-------------|
108
+ | `0001_create_tm_schema_migrations.sql` | Migration ledger table |
109
+ | `0002_create_tm_organizations.sql` | Organizations table |
110
+ | `0003_create_tm_memberships.sql` | Memberships with soft-delete columns |
111
+ | `0004_create_tm_invitations.sql` | Invitations with encrypted code storage |
112
+ | `0005_create_tm_audit_events.sql` | Audit events log |
113
+ | `0006_create_tm_email_change_requests.sql` | Email change requests |
114
+ | `0007_create_tm_ownership_transfers.sql` | Ownership transfer requests |
115
+ | `0008_create_tm_super_admins.sql` | Super-admin registry |
116
+ | `0009_create_tm_password_reset_requests.sql` | Password reset tokens |
117
+ | `0010_create_tm_shared_access.sql` | Shared access scaffold (empty, v0.2.0) |
118
+ | `0011_seed_super_admin.sql` | Super-admin seed (idempotent, flag-gated) |
119
+
120
+ All migrations are forward-only, idempotent (`IF NOT EXISTS`), and tracked in `tm_schema_migrations`.
121
+
122
+ ### Feature flags introduced
123
+
124
+ | Flag | Default | Description |
125
+ |------|---------|-------------|
126
+ | `enableInvites` | `true` | Invitation flows (magic link + code) |
127
+ | `enableAuditLog` | `true` | Audit log table and routes |
128
+ | `enableOwnershipTransfer` | `true` | Ownership transfer flow |
129
+ | `enableEmailChange` | `true` | Self-service email change |
130
+ | `enablePasswordReset` | `true` | Self-service password reset |
131
+ | `enableSuperAdmin` | `false` | Super-admin back office (Varshyl internal) |
132
+ | `enableSharedAccess` | `false` | Shared access scaffold (reserved v0.2.0) |
133
+ | `enableHardDelete` | `false` | Super-admin direct hard-delete |
134
+
135
+ Routes return `501 Not Implemented` when their flag is off.
136
+
137
+ ### Locked design decisions (set in v0.1.0; future major bumps may revise)
138
+
139
+ **Decision 1 — Org model:** Every user belongs to exactly one org at a time. Solo accounts = org of one. `tm_shared_access` scaffolded empty (v0.2.0 placeholder). Multi-contractor vendor case handled by existing vendor-invite system in ConstructIInv (out of scope).
140
+
141
+ **Decision 2 — Roles:** Four roles: `owner` (unique per org, billing, delete, transfer), `admin` (invite/remove/settings, no audit-visible super-admin identity), `member` (day-to-day, no team powers), `viewer` (read-only). Permission matrix enforced at middleware level.
142
+
143
+ **Decision 3 — Audit log:** Stored forever. Visible to admin+ only. 13 event types. Super-admin entries visible as "Varshyl Support" — real identity never exposed.
144
+
145
+ **Decision 4 — Invitations:** Magic link + 6-digit code in same email. AES-256-GCM encryption for codes. 7-day expiry. Sub-A: member accepting into new org gets explicit warning + atomic switch; owner is blocked until they transfer or delete current org.
146
+
147
+ **Decision 5 — Deletion:** Member removal is soft (`removed_at`). Org deletion is soft with 30-day grace; confirm by typing org name. Background sweep hard-deletes after grace period.
148
+
149
+ **Decision 6 — Email change:** Verification to new address + immediate notice to old. Cancel from old address triggers mandatory password reset. Rate limit: 3 per user per 24h.
150
+
151
+ **Decision 7 — Ownership transfer:** Target must already be admin. Confirm by typing org name (initiator) and email (recipient). Atomic role swap on accept. Locks: no second transfer, no org delete, no party removal.
152
+
153
+ **Decision 8 — Super-admin:** Flag-gated (default OFF). Seeded from `TM_SUPER_ADMIN_EMAIL` env. Cannot impersonate, cannot modify product data, cannot set passwords. All actions require reason text.
154
+
155
+ ---
156
+
157
+ ## v0.0.1 — 2026-04-28
158
+
159
+ Initial stub. Module scaffolding only — no business logic.
package/LICENSE ADDED
@@ -0,0 +1,6 @@
1
+ UNLICENSED
2
+
3
+ Copyright (c) 2026 Varshyl Inc. All rights reserved.
4
+
5
+ This software and associated documentation files are proprietary and confidential.
6
+ Unauthorized copying, distribution, modification, or use is strictly prohibited.
package/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # @varshylinc/team-management
2
+
3
+ > Shared team management module for Varshyl products. Install as a versioned package via git tag.
4
+
5
+ ## Status
6
+
7
+ **Stub** — architecture in place, features to be implemented in the Team Management design session.
8
+
9
+ ## 1. Installation
10
+
11
+ ```json
12
+ "dependencies": {
13
+ "@varshylinc/team-management": "github:VagishKapila/varshyl-toolkit#team-management-v0.0.1"
14
+ }
15
+ ```
16
+
17
+ ## 2. Server setup
18
+
19
+ ```ts
20
+ import { createServerModule } from '@varshylinc/team-management';
21
+ import type { ServerModuleAdapter } from '@varshylinc/team-management';
22
+
23
+ // Implement the adapter for your product
24
+ const adapter: ServerModuleAdapter = {
25
+ getCurrentUserId: async (req) => req.user?.id ?? null,
26
+ getOrganizationIdForUser: async (userId) => yourDb.getOrgId(userId),
27
+ isUserOrgAdmin: async (userId, orgId) => yourDb.checkAdmin(userId, orgId),
28
+ logger: console,
29
+ };
30
+
31
+ const tm = createServerModule({
32
+ adapter,
33
+ db: yourPgPool,
34
+ config: {
35
+ featureFlags: { enableInvites: false, enableAuditLog: false },
36
+ },
37
+ });
38
+
39
+ // Run on boot — idempotent, safe to call every startup
40
+ await tm.runMigrations();
41
+
42
+ // Mount the router
43
+ app.use('/api/team', tm.router);
44
+ ```
45
+
46
+ ## 3. Client setup
47
+
48
+ ```tsx
49
+ import { PlaceholderPage } from '@varshylinc/team-management/client';
50
+
51
+ // In your React router:
52
+ <Route path="/team" element={<PlaceholderPage />} />
53
+ ```
54
+
55
+ ## 4. DB ownership
56
+
57
+ All DB tables are prefixed `tm_*`. The module owns them exclusively.
58
+ Host products must not query these tables directly — use the module's API.
59
+
60
+ | Table | Purpose |
61
+ |---|---|
62
+ | `tm_schema_migrations` | Migration ledger — tracks applied migrations |
63
+
64
+ ## 5. Feature flags
65
+
66
+ | Flag | Default | Description |
67
+ |---|---|---|
68
+ | `enableInvites` | `false` | Enable invite flows (stub) |
69
+ | `enableAuditLog` | `false` | Enable audit log (stub) |
70
+
71
+ Routes for disabled flags return `501 Not Implemented`.
72
+
73
+ ## 6. API routes
74
+
75
+ | Method | Path | Description |
76
+ |---|---|---|
77
+ | GET | `/health` | Module health + DB connectivity check |
78
+ | GET | `/invites` | List invites (flag: `enableInvites`) |
79
+ | POST | `/invites` | Create invite (flag: `enableInvites`) |
80
+ | GET | `/audit` | Audit log (flag: `enableAuditLog`) |
81
+
82
+ ## 7. Adapter interface
83
+
84
+ See `src/server/types.ts` for the full `ServerModuleAdapter` interface.
85
+
86
+ ## 8. Versioning
87
+
88
+ Tags: `team-management-v<X.Y.Z>`. See `CHANGELOG.md` for history.
89
+
90
+ ## 9. Migrations
91
+
92
+ Migrations are plain SQL files in `src/server/migrations/`, named `NNNN_description.sql`.
93
+ `runMigrations()` applies them in order, skipping already-applied ones (ledger in `tm_schema_migrations`).
94
+
95
+ ## 10. Architecture
96
+
97
+ See `../../docs/SHARED_MODULE_ARCHITECTURE.md` for the full architecture doc.
@@ -0,0 +1,4 @@
1
+ export { createServerModule } from './server/index.js';
2
+ export type { ServerModuleAdapter, TeamManagementConfig, TeamManagementServerModule, OrgRole, AuditActorType, TransferStatus, TmOrganization, TmMembership, TmInvitation, TmAuditEvent, TmOwnershipTransfer, TmPasswordResetRequest, TeamManagementFeatureFlags, } from './server/types.js';
3
+ export type { TeamMember, TeamInvite } from './shared/types.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EACV,mBAAmB,EACnB,oBAAoB,EACpB,0BAA0B,EAC1B,OAAO,EACP,cAAc,EACd,cAAc,EACd,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,mBAAmB,CAAC;AAG3B,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ // ─── Server exports (Node.js + tsc, DO NOT import in browser bundles) ─────────
2
+ export { createServerModule } from './server/index.js';
3
+ // NOTE: Client exports live in packages/team-management/src/client/index.ts
4
+ // Import them in your bundler (Vite/webpack) via the "client" export condition:
5
+ // import { PlaceholderPage } from '@varshylinc/team-management/client'
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAoBvD,4EAA4E;AAC5E,gFAAgF;AAChF,yEAAyE"}
@@ -0,0 +1,6 @@
1
+ export declare function encrypt(plaintext: string): string;
2
+ export declare function decrypt(ciphertext: string): string;
3
+ export declare function sha256(input: string): string;
4
+ export declare function generateToken(byteLength?: number): string;
5
+ export declare function generateSixDigitCode(): string;
6
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/server/crypto.ts"],"names":[],"mappings":"AAWA,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAOjD;AAED,wBAAgB,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAWlD;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,wBAAgB,aAAa,CAAC,UAAU,SAAK,GAAG,MAAM,CAErD;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAI7C"}
@@ -0,0 +1,42 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes, createHash } from 'crypto';
2
+ const ALGORITHM = 'aes-256-gcm';
3
+ const IV_LENGTH = 12;
4
+ function getKey() {
5
+ const secret = process.env.TM_ENCRYPTION_KEY;
6
+ if (!secret)
7
+ throw new Error('TM_ENCRYPTION_KEY environment variable is required');
8
+ return createHash('sha256').update(secret).digest();
9
+ }
10
+ export function encrypt(plaintext) {
11
+ const key = getKey();
12
+ const iv = randomBytes(IV_LENGTH);
13
+ const cipher = createCipheriv(ALGORITHM, key, iv);
14
+ const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
15
+ const tag = cipher.getAuthTag();
16
+ return `${iv.toString('hex')}:${tag.toString('hex')}:${encrypted.toString('hex')}`;
17
+ }
18
+ export function decrypt(ciphertext) {
19
+ const key = getKey();
20
+ const parts = ciphertext.split(':');
21
+ if (parts.length !== 3)
22
+ throw new Error('Invalid ciphertext format');
23
+ const [ivHex, tagHex, encHex] = parts;
24
+ const iv = Buffer.from(ivHex, 'hex');
25
+ const tag = Buffer.from(tagHex, 'hex');
26
+ const encrypted = Buffer.from(encHex, 'hex');
27
+ const decipher = createDecipheriv(ALGORITHM, key, iv);
28
+ decipher.setAuthTag(tag);
29
+ return decipher.update(encrypted).toString('utf8') + decipher.final('utf8');
30
+ }
31
+ export function sha256(input) {
32
+ return createHash('sha256').update(input).digest('hex');
33
+ }
34
+ export function generateToken(byteLength = 32) {
35
+ return randomBytes(byteLength).toString('hex');
36
+ }
37
+ export function generateSixDigitCode() {
38
+ // Cryptographically random 6-digit code (100000-999999)
39
+ const rand = randomBytes(4).readUInt32BE(0);
40
+ return String(100000 + (rand % 900000));
41
+ }
42
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/server/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEnF,MAAM,SAAS,GAAG,aAAa,CAAC;AAChC,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB,SAAS,MAAM;IACb,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAC7C,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACnF,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,SAAiB;IACvC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACpF,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAChC,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;AACrF,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,UAAkB;IACxC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IACrE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;IACtC,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACrC,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAG,gBAAgB,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IACtD,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACzB,OAAO,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,UAAU,GAAG,EAAE;IAC3C,OAAO,WAAW,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,oBAAoB;IAClC,wDAAwD;IACxD,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC5C,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { Pool } from 'pg';
2
+ import type { ServerModuleAdapter, TeamManagementConfig, TeamManagementServerModule, TeamManagementFeatureFlags } from './types.js';
3
+ export declare function runMigrations(db: Pool, logger?: {
4
+ info(msg: string): void;
5
+ }): Promise<{
6
+ applied: string[];
7
+ skipped: string[];
8
+ }>;
9
+ /**
10
+ * createServerModule — entry point for host products.
11
+ *
12
+ * Usage (production):
13
+ * const tm = createServerModule({ adapter, db, config });
14
+ * await tm.runMigrations();
15
+ * app.use('/api/team', tm.router);
16
+ *
17
+ * Usage (test shorthand — pool and features accepted as aliases):
18
+ * const tm = createServerModule({ adapter, pool, features });
19
+ */
20
+ export declare function createServerModule(opts: {
21
+ adapter: ServerModuleAdapter;
22
+ /** pg Pool — use `db` or `pool` interchangeably */
23
+ db?: Pool;
24
+ /** Alias for db */
25
+ pool?: Pool;
26
+ /** Full config object (takes precedence over individual fields below) */
27
+ config?: TeamManagementConfig;
28
+ /** Shorthand for config.featureFlags */
29
+ features?: Partial<TeamManagementFeatureFlags>;
30
+ /** Shorthand for config.baseUrl */
31
+ baseUrl?: string;
32
+ }): TeamManagementServerModule;
33
+ export type { ServerModuleAdapter, TeamManagementConfig, TeamManagementServerModule };
34
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAW/B,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EACpB,0BAA0B,EAC1B,0BAA0B,EAC3B,MAAM,YAAY,CAAC;AAkCpB,wBAAsB,aAAa,CACjC,EAAE,EAAE,IAAI,EACR,MAAM,GAAE;IAAE,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAuB,GACvD,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAmCnD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,mDAAmD;IACnD,EAAE,CAAC,EAAE,IAAI,CAAC;IACV,mBAAmB;IACnB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,yEAAyE;IACzE,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAC/C,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,0BAA0B,CAkD7B;AAED,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,CAAC"}
@@ -0,0 +1,114 @@
1
+ import { Router } from 'express';
2
+ import { readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { createHealthRouter } from './routes/health.routes.js';
5
+ import { createOrgsRouter } from './routes/orgs.routes.js';
6
+ import { createInvitationsRouter } from './routes/invitations.routes.js';
7
+ import { createMeRouter } from './routes/me.routes.js';
8
+ import { createTransferRouter } from './routes/transfer.routes.js';
9
+ import { createAuditRouter } from './routes/audit.routes.js';
10
+ import { createAdminRouter } from './routes/admin.routes.js';
11
+ import { seedSuperAdmin } from './services/super-admin.service.js';
12
+ const migrationsDir = join(new URL('.', import.meta.url).pathname, 'migrations');
13
+ // All migrations in order. Append new migrations here — never reorder or remove.
14
+ const MIGRATIONS = [
15
+ { name: '0001_create_tm_schema_migrations', file: join(migrationsDir, '0001_create_tm_schema_migrations.sql') },
16
+ { name: '0002_create_tm_organizations', file: join(migrationsDir, '0002_create_tm_organizations.sql') },
17
+ { name: '0003_create_tm_memberships', file: join(migrationsDir, '0003_create_tm_memberships.sql') },
18
+ { name: '0004_create_tm_invitations', file: join(migrationsDir, '0004_create_tm_invitations.sql') },
19
+ { name: '0005_create_tm_audit_events', file: join(migrationsDir, '0005_create_tm_audit_events.sql') },
20
+ { name: '0006_create_tm_email_change_requests', file: join(migrationsDir, '0006_create_tm_email_change_requests.sql') },
21
+ { name: '0007_create_tm_ownership_transfers', file: join(migrationsDir, '0007_create_tm_ownership_transfers.sql') },
22
+ { name: '0008_create_tm_super_admins', file: join(migrationsDir, '0008_create_tm_super_admins.sql') },
23
+ { name: '0009_create_tm_password_reset_requests', file: join(migrationsDir, '0009_create_tm_password_reset_requests.sql') },
24
+ { name: '0010_create_tm_shared_access', file: join(migrationsDir, '0010_create_tm_shared_access.sql') },
25
+ { name: '0011_seed_super_admin', file: join(migrationsDir, '0011_seed_super_admin.sql') },
26
+ { name: '0012_create_tm_user_locks', file: join(migrationsDir, '0012_create_tm_user_locks.sql') },
27
+ ];
28
+ // Default feature flags for v0.1.0
29
+ const DEFAULT_FLAGS = {
30
+ enableInvites: true,
31
+ enableAuditLog: true,
32
+ enableOwnershipTransfer: true,
33
+ enableEmailChange: true,
34
+ enablePasswordReset: true,
35
+ enableSuperAdmin: false,
36
+ enableSharedAccess: false,
37
+ enableHardDelete: false,
38
+ };
39
+ // ── Standalone migration runner ─────────────────────────────────────────────
40
+ // Exported so tests and globalSetup can call it directly without a full module.
41
+ export async function runMigrations(db, logger = { info: () => { } }) {
42
+ const applied = [];
43
+ const skipped = [];
44
+ // Boot step: ensure ledger table exists (idempotent — CREATE TABLE IF NOT EXISTS)
45
+ const ledgerSql = readFileSync(join(migrationsDir, '0001_create_tm_schema_migrations.sql'), 'utf-8');
46
+ await db.query(ledgerSql);
47
+ for (const migration of MIGRATIONS) {
48
+ const result = await db.query('SELECT id FROM tm_schema_migrations WHERE migration = $1', [migration.name]);
49
+ if (result.rows.length > 0) {
50
+ skipped.push(migration.name);
51
+ logger.info(`[team-management] skipped: ${migration.name}`);
52
+ continue;
53
+ }
54
+ const sql = readFileSync(migration.file, 'utf-8');
55
+ await db.query(sql);
56
+ await db.query('INSERT INTO tm_schema_migrations (migration) VALUES ($1)', [migration.name]);
57
+ applied.push(migration.name);
58
+ logger.info(`[team-management] applied: ${migration.name}`);
59
+ }
60
+ return { applied, skipped };
61
+ }
62
+ /**
63
+ * createServerModule — entry point for host products.
64
+ *
65
+ * Usage (production):
66
+ * const tm = createServerModule({ adapter, db, config });
67
+ * await tm.runMigrations();
68
+ * app.use('/api/team', tm.router);
69
+ *
70
+ * Usage (test shorthand — pool and features accepted as aliases):
71
+ * const tm = createServerModule({ adapter, pool, features });
72
+ */
73
+ export function createServerModule(opts) {
74
+ const db = (opts.db ?? opts.pool);
75
+ const featureFlags = opts.config?.featureFlags ?? opts.features ?? {};
76
+ const baseUrl = opts.config?.baseUrl ?? opts.baseUrl ?? '';
77
+ const config = { featureFlags, baseUrl };
78
+ const { adapter } = opts;
79
+ const flags = {
80
+ ...DEFAULT_FLAGS,
81
+ ...featureFlags,
82
+ };
83
+ // ── Super-admin seed on boot ────────────────────────────────────────────────
84
+ if (flags.enableSuperAdmin) {
85
+ const superAdminEmail = process.env.TM_SUPER_ADMIN_EMAIL;
86
+ if (superAdminEmail) {
87
+ seedSuperAdmin(db, adapter, superAdminEmail).catch(e => {
88
+ adapter.logger.warn('[team-management] seedSuperAdmin failed', {
89
+ error: e.message,
90
+ });
91
+ });
92
+ }
93
+ }
94
+ // ── Routers ─────────────────────────────────────────────────────────────────
95
+ const { router: healthRouter } = createHealthRouter(db, config);
96
+ const orgsRouter = createOrgsRouter(db, adapter, flags);
97
+ const invitationsRouter = createInvitationsRouter(db, adapter, flags, baseUrl);
98
+ const meRouter = createMeRouter(db, adapter, flags, baseUrl);
99
+ const transferRouter = createTransferRouter(db, adapter, flags, baseUrl);
100
+ const auditRouter = createAuditRouter(db, adapter, flags);
101
+ const adminRouter = createAdminRouter(db, adapter, flags, baseUrl);
102
+ const router = Router();
103
+ router.use(healthRouter);
104
+ router.use('/me', meRouter);
105
+ router.use('/orgs', orgsRouter);
106
+ router.use('/orgs', invitationsRouter);
107
+ router.use('/orgs', transferRouter);
108
+ router.use('/orgs', auditRouter);
109
+ router.use('/invitations', invitationsRouter);
110
+ router.use('/admin', adminRouter);
111
+ const migrate = () => runMigrations(db, adapter.logger);
112
+ return { router, runMigrations: migrate, migrate };
113
+ }
114
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { Request, Response, NextFunction } from 'express';
2
+ import type { Pool } from 'pg';
3
+ import type { ServerModuleAdapter, OrgRole } from '../types.js';
4
+ export interface AuthenticatedRequest extends Request {
5
+ userId: number;
6
+ orgId: number;
7
+ userRole: OrgRole;
8
+ }
9
+ export declare function requireMembership(pool: Pool, adapter: ServerModuleAdapter): (req: Request, res: Response, next: NextFunction) => Promise<void>;
10
+ //# sourceMappingURL=require-membership.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-membership.d.ts","sourceRoot":"","sources":["../../../src/server/middleware/require-membership.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEhE,MAAM,WAAW,oBAAqB,SAAQ,OAAO;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,mBAAmB,IAC1D,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAoC9E"}
@@ -0,0 +1,33 @@
1
+ export function requireMembership(pool, adapter) {
2
+ return async (req, res, next) => {
3
+ try {
4
+ const userId = await adapter.getCurrentUserId(req);
5
+ if (!userId) {
6
+ res.status(401).json({ error: 'Authentication required' });
7
+ return;
8
+ }
9
+ const orgIdParam = parseInt(req.params.orgId || req.params.id || '', 10);
10
+ if (isNaN(orgIdParam)) {
11
+ res.status(400).json({ error: 'Invalid org ID' });
12
+ return;
13
+ }
14
+ const result = await pool.query(`SELECT m.role FROM tm_memberships m
15
+ JOIN tm_organizations o ON o.id = m.org_id
16
+ WHERE m.org_id = $1 AND m.user_id = $2 AND m.removed_at IS NULL
17
+ AND o.deleted_at IS NULL`, [orgIdParam, userId]);
18
+ if (result.rows.length === 0) {
19
+ res.status(403).json({ error: 'You are not a member of this organization' });
20
+ return;
21
+ }
22
+ req.userId = userId;
23
+ req.orgId = orgIdParam;
24
+ req.userRole = result.rows[0].role;
25
+ next();
26
+ }
27
+ catch (e) {
28
+ adapter.logger.error('[requireMembership]', { error: e.message });
29
+ res.status(500).json({ error: 'Authentication check failed' });
30
+ }
31
+ };
32
+ }
33
+ //# sourceMappingURL=require-membership.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-membership.js","sourceRoot":"","sources":["../../../src/server/middleware/require-membership.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,iBAAiB,CAAC,IAAU,EAAE,OAA4B;IACxE,OAAO,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAiB,EAAE;QAC9E,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;YACnD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;gBAC3D,OAAO;YACT,CAAC;YAED,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;YACzE,IAAI,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;gBACtB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;gBAClD,OAAO;YACT,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B;;;oCAG4B,EAC5B,CAAC,UAAU,EAAE,MAAM,CAAC,CACrB,CAAC;YAEF,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC7B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,2CAA2C,EAAE,CAAC,CAAC;gBAC7E,OAAO;YACT,CAAC;YAEA,GAA4B,CAAC,MAAM,GAAG,MAAM,CAAC;YAC7C,GAA4B,CAAC,KAAK,GAAG,UAAU,CAAC;YAChD,GAA4B,CAAC,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAe,CAAC;YACxE,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,EAAE,EAAE,KAAK,EAAG,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;YAC7E,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,6BAA6B,EAAE,CAAC,CAAC;QACjE,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Request, Response, NextFunction } from 'express';
2
+ import type { OrgRole } from '../types.js';
3
+ export declare function requireRole(minimumRole: OrgRole): (req: Request, res: Response, next: NextFunction) => void;
4
+ //# sourceMappingURL=require-role.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-role.d.ts","sourceRoot":"","sources":["../../../src/server/middleware/require-role.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAI3C,wBAAgB,WAAW,CAAC,WAAW,EAAE,OAAO,IACtC,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,IAAI,CAY/D"}
@@ -0,0 +1,16 @@
1
+ import { roleAtLeast } from '../types.js';
2
+ export function requireRole(minimumRole) {
3
+ return (req, res, next) => {
4
+ const authReq = req;
5
+ if (!authReq.userRole) {
6
+ res.status(401).json({ error: 'Authentication required' });
7
+ return;
8
+ }
9
+ if (!roleAtLeast(authReq.userRole, minimumRole)) {
10
+ res.status(403).json({ error: `Requires ${minimumRole} role or higher` });
11
+ return;
12
+ }
13
+ next();
14
+ };
15
+ }
16
+ //# sourceMappingURL=require-role.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-role.js","sourceRoot":"","sources":["../../../src/server/middleware/require-role.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,MAAM,UAAU,WAAW,CAAC,WAAoB;IAC9C,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;QAC/D,MAAM,OAAO,GAAG,GAA2B,CAAC;QAC5C,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;YACtB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;YAC3D,OAAO;QACT,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,CAAC;YAChD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,YAAY,WAAW,iBAAiB,EAAE,CAAC,CAAC;YAC1E,OAAO;QACT,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { Request, Response, NextFunction } from 'express';
2
+ import type { Pool } from 'pg';
3
+ import type { ServerModuleAdapter, TeamManagementFeatureFlags } from '../types.js';
4
+ export declare function requireSuperAdmin(pool: Pool, adapter: ServerModuleAdapter, flags: TeamManagementFeatureFlags): (req: Request, res: Response, next: NextFunction) => Promise<void>;
5
+ //# sourceMappingURL=require-super-admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-super-admin.d.ts","sourceRoot":"","sources":["../../../src/server/middleware/require-super-admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAEnF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,mBAAmB,EAAE,KAAK,EAAE,0BAA0B,IAC7F,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CA0B9E"}