@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
@@ -0,0 +1,102 @@
1
+ const TRANSFER_EXPIRY_HOURS = 48;
2
+ export async function initiateTransfer(pool, adapter, { orgId, fromUserId, toUserId, baseUrl, }) {
3
+ // Validate target is an active admin (not owner)
4
+ const targetMembership = await pool.query(`SELECT role FROM tm_memberships WHERE org_id = $1 AND user_id = $2 AND removed_at IS NULL`, [orgId, toUserId]);
5
+ if (targetMembership.rows.length === 0) {
6
+ throw new Error('Target user is not a member of this organization');
7
+ }
8
+ if (targetMembership.rows[0].role !== 'admin') {
9
+ throw new Error('Ownership can only be transferred to an admin member');
10
+ }
11
+ // Check no pending transfer exists
12
+ const pending = await pool.query(`SELECT id FROM tm_ownership_transfers
13
+ WHERE org_id = $1 AND status = 'pending' AND expires_at > NOW()`, [orgId]);
14
+ if (pending.rows.length > 0) {
15
+ throw new Error('A pending ownership transfer already exists for this organization');
16
+ }
17
+ const expiresAt = new Date(Date.now() + TRANSFER_EXPIRY_HOURS * 60 * 60 * 1000);
18
+ const result = await pool.query(`INSERT INTO tm_ownership_transfers (org_id, from_user_id, to_user_id, status, expires_at)
19
+ VALUES ($1, $2, $3, 'pending', $4)
20
+ RETURNING *`, [orgId, fromUserId, toUserId, expiresAt]);
21
+ const transfer = result.rows[0];
22
+ // Send email to target user
23
+ const orgResult = await pool.query(`SELECT name FROM tm_organizations WHERE id = $1`, [orgId]);
24
+ const fromUser = await adapter.getUserById(fromUserId);
25
+ const toUser = await adapter.getUserById(toUserId);
26
+ const orgName = orgResult.rows[0]?.name ?? 'Unknown Organization';
27
+ const fromName = fromUser?.name ?? fromUser?.email ?? 'The current owner';
28
+ if (toUser?.email) {
29
+ try {
30
+ await adapter.sendOwnershipTransferEmail({
31
+ to: toUser.email,
32
+ orgName,
33
+ fromName,
34
+ transferUrl: `${baseUrl}/orgs/${orgId}/transfer/accept`,
35
+ });
36
+ }
37
+ catch (e) {
38
+ adapter.logger.warn('[ownership] Failed to send transfer email', {
39
+ transferId: transfer.id,
40
+ error: e.message,
41
+ });
42
+ }
43
+ }
44
+ return transfer;
45
+ }
46
+ export async function acceptTransfer(pool, adapter, { orgId, acceptingUserId }) {
47
+ const client = await pool.connect();
48
+ try {
49
+ await client.query('BEGIN');
50
+ const transferResult = await client.query(`SELECT * FROM tm_ownership_transfers
51
+ WHERE org_id = $1 AND status = 'pending' AND expires_at > NOW()
52
+ FOR UPDATE`, [orgId]);
53
+ if (transferResult.rows.length === 0) {
54
+ throw new Error('No valid pending transfer found for this organization');
55
+ }
56
+ const transfer = transferResult.rows[0];
57
+ if (transfer.to_user_id !== acceptingUserId) {
58
+ throw new Error('Only the designated recipient can accept this transfer');
59
+ }
60
+ // Atomic: demote old owner first (avoids two-owner constraint), then promote new owner
61
+ await client.query(`UPDATE tm_memberships SET role = 'admin', updated_at = NOW()
62
+ WHERE org_id = $1 AND user_id = $2 AND removed_at IS NULL`, [orgId, transfer.from_user_id]);
63
+ await client.query(`UPDATE tm_memberships SET role = 'owner', updated_at = NOW()
64
+ WHERE org_id = $1 AND user_id = $2 AND removed_at IS NULL`, [orgId, transfer.to_user_id]);
65
+ // Update denormalized owner_user_id on org
66
+ await client.query(`UPDATE tm_organizations SET owner_user_id = $1, updated_at = NOW() WHERE id = $2`, [transfer.to_user_id, orgId]);
67
+ // Mark transfer as accepted
68
+ await client.query(`UPDATE tm_ownership_transfers
69
+ SET status = 'accepted', accepted_at = NOW()
70
+ WHERE id = $1`, [transfer.id]);
71
+ await client.query('COMMIT');
72
+ }
73
+ catch (e) {
74
+ await client.query('ROLLBACK');
75
+ throw e;
76
+ }
77
+ finally {
78
+ client.release();
79
+ }
80
+ }
81
+ export async function cancelTransfer(pool, { orgId, cancelledByUserId }) {
82
+ const result = await pool.query(`UPDATE tm_ownership_transfers
83
+ SET status = 'cancelled', cancelled_at = NOW(), cancelled_by_user_id = $1
84
+ WHERE org_id = $2 AND status = 'pending'`, [cancelledByUserId, orgId]);
85
+ if ((result.rowCount ?? 0) === 0) {
86
+ throw new Error('No pending transfer found to cancel');
87
+ }
88
+ }
89
+ export async function getPendingTransfer(pool, orgId) {
90
+ const result = await pool.query(`SELECT * FROM tm_ownership_transfers
91
+ WHERE org_id = $1 AND status = 'pending' AND expires_at > NOW()
92
+ ORDER BY initiated_at DESC
93
+ LIMIT 1`, [orgId]);
94
+ return result.rows[0] ?? null;
95
+ }
96
+ export async function expireTransfers(pool) {
97
+ const result = await pool.query(`UPDATE tm_ownership_transfers
98
+ SET status = 'expired'
99
+ WHERE status = 'pending' AND expires_at <= NOW()`);
100
+ return result.rowCount ?? 0;
101
+ }
102
+ //# sourceMappingURL=ownership.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ownership.service.js","sourceRoot":"","sources":["../../../src/server/services/ownership.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { Pool } from 'pg';
2
+ import type { ServerModuleAdapter } from '../types.js';
3
+ export declare function requestPasswordReset(pool: Pool, adapter: ServerModuleAdapter, { email, baseUrl, triggeredBySuperAdmin: _triggeredBySuperAdmin, }: {
4
+ email: string;
5
+ baseUrl: string;
6
+ triggeredBySuperAdmin?: boolean;
7
+ }): Promise<void>;
8
+ export declare function resetPassword(pool: Pool, adapter: ServerModuleAdapter, { token, newPassword }: {
9
+ token: string;
10
+ newPassword: string;
11
+ }): Promise<void>;
12
+ //# sourceMappingURL=password-reset.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-reset.service.d.ts","sourceRoot":"","sources":["../../../src/server/services/password-reset.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,KAAK,EAA0B,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAQ/E,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,mBAAmB,EAC5B,EACE,KAAK,EACL,OAAO,EACP,qBAAqB,EAAE,sBAA8B,GACtD,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAAE,GACrE,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAED,wBAAsB,aAAa,CACjC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,mBAAmB,EAC5B,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAC7D,OAAO,CAAC,IAAI,CAAC,CAsCf"}
@@ -0,0 +1,54 @@
1
+ import { generateToken, sha256 } from '../crypto.js';
2
+ import { writeAuditEvent } from './audit.service.js';
3
+ const RESET_EXPIRY_HOURS = 2;
4
+ const RATE_LIMIT_MAX = 3;
5
+ const RATE_LIMIT_WINDOW_HOURS = 1;
6
+ export async function requestPasswordReset(pool, adapter, { email, baseUrl, triggeredBySuperAdmin: _triggeredBySuperAdmin = false, }) {
7
+ // Always return success — do not leak whether email exists
8
+ const user = await adapter.findUserByEmail(email);
9
+ if (!user)
10
+ return;
11
+ // Rate limit: 3 requests per email per hour
12
+ const rateCheck = await pool.query(`SELECT COUNT(*) FROM tm_password_reset_requests
13
+ WHERE user_id = $1 AND created_at > NOW() - INTERVAL '${RATE_LIMIT_WINDOW_HOURS} hours'
14
+ AND used_at IS NULL`, [user.id]);
15
+ const count = parseInt(rateCheck.rows[0].count, 10);
16
+ if (count >= RATE_LIMIT_MAX) {
17
+ // Silently return — don't reveal rate limit to potential attackers
18
+ return;
19
+ }
20
+ const token = generateToken(32);
21
+ const tokenHash = sha256(token);
22
+ const expiresAt = new Date(Date.now() + RESET_EXPIRY_HOURS * 60 * 60 * 1000);
23
+ await pool.query(`INSERT INTO tm_password_reset_requests (user_id, token_hash, expires_at)
24
+ VALUES ($1, $2, $3)`, [user.id, tokenHash, expiresAt]);
25
+ const resetUrl = `${baseUrl}/reset-password?token=${token}`;
26
+ await adapter.sendPasswordResetEmail({ to: email, resetUrl });
27
+ }
28
+ export async function resetPassword(pool, adapter, { token, newPassword }) {
29
+ if (!newPassword || newPassword.length < 8) {
30
+ throw new Error('Password must be at least 8 characters');
31
+ }
32
+ const tokenHash = sha256(token);
33
+ const result = await pool.query(`SELECT * FROM tm_password_reset_requests
34
+ WHERE token_hash = $1 AND used_at IS NULL AND expires_at > NOW()`, [tokenHash]);
35
+ if (result.rows.length === 0) {
36
+ throw new Error('Invalid or expired password reset token');
37
+ }
38
+ const request = result.rows[0];
39
+ const hashedPassword = await adapter.hashPassword(newPassword);
40
+ await adapter.setUserPassword(request.user_id, hashedPassword);
41
+ // Mark token as used
42
+ await pool.query(`UPDATE tm_password_reset_requests SET used_at = NOW() WHERE id = $1`, [request.id]);
43
+ // Invalidate all sessions for security
44
+ await adapter.invalidateAllUserSessions(request.user_id);
45
+ await writeAuditEvent({
46
+ pool,
47
+ orgId: null,
48
+ actorUserId: request.user_id,
49
+ action: 'user.password_reset',
50
+ targetType: 'user',
51
+ targetId: request.user_id,
52
+ });
53
+ }
54
+ //# sourceMappingURL=password-reset.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-reset.service.js","sourceRoot":"","sources":["../../../src/server/services/password-reset.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import type { Pool } from 'pg';
2
+ import type { TmOrganization, TmMembership, OrgRole, ServerModuleAdapter } from '../types.js';
3
+ export declare function listAllOrgs(pool: Pool): Promise<TmOrganization[]>;
4
+ export declare function getOrgForAdmin(pool: Pool, orgId: number): Promise<TmOrganization & {
5
+ memberCount: number;
6
+ }>;
7
+ export declare function getUserForAdmin(pool: Pool, adapter: ServerModuleAdapter, userId: number): Promise<{
8
+ id: number;
9
+ email: string;
10
+ name?: string;
11
+ memberships: TmMembership[];
12
+ }>;
13
+ export declare function restoreOrg(pool: Pool, { orgId, superAdminUserId, reason, }: {
14
+ orgId: number;
15
+ superAdminUserId: number;
16
+ reason: string;
17
+ }): Promise<void>;
18
+ export declare function appointOwner(pool: Pool, { orgId, targetUserId, superAdminUserId, reason, }: {
19
+ orgId: number;
20
+ targetUserId: number;
21
+ superAdminUserId: number;
22
+ reason: string;
23
+ }): Promise<void>;
24
+ export declare function hardDeleteOrg(pool: Pool, { orgId, superAdminUserId, legalBasis, }: {
25
+ orgId: number;
26
+ superAdminUserId: number;
27
+ legalBasis: string;
28
+ }): Promise<void>;
29
+ export declare function addMemberAdmin(pool: Pool, { orgId, userId, role, superAdminUserId, reason, }: {
30
+ orgId: number;
31
+ userId: number;
32
+ role: OrgRole;
33
+ superAdminUserId: number;
34
+ reason: string;
35
+ }): Promise<void>;
36
+ export declare function removeMemberAdmin(pool: Pool, { orgId, userId, superAdminUserId, reason, }: {
37
+ orgId: number;
38
+ userId: number;
39
+ superAdminUserId: number;
40
+ reason: string;
41
+ }): Promise<void>;
42
+ export declare function lockUser(pool: Pool, adapter: ServerModuleAdapter, { userId, superAdminUserId, reason, }: {
43
+ userId: number;
44
+ superAdminUserId: number;
45
+ reason: string;
46
+ }): Promise<void>;
47
+ export declare function unlockUser(pool: Pool, adapter: ServerModuleAdapter, { userId, superAdminUserId, reason, }: {
48
+ userId: number;
49
+ superAdminUserId: number;
50
+ reason: string;
51
+ }): Promise<void>;
52
+ export declare function triggerPasswordReset(pool: Pool, adapter: ServerModuleAdapter, { userId, superAdminUserId, reason, baseUrl, }: {
53
+ userId: number;
54
+ superAdminUserId: number;
55
+ reason: string;
56
+ baseUrl: string;
57
+ }): Promise<void>;
58
+ export declare function seedSuperAdmin(pool: Pool, adapter: ServerModuleAdapter, email: string): Promise<void>;
59
+ //# sourceMappingURL=super-admin.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"super-admin.service.d.ts","sourceRoot":"","sources":["../../../src/server/services/super-admin.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,187 @@
1
+ import { writeAuditEvent } from './audit.service.js';
2
+ import { requestPasswordReset } from './password-reset.service.js';
3
+ export async function listAllOrgs(pool) {
4
+ const result = await pool.query(`SELECT * FROM tm_organizations ORDER BY created_at DESC`);
5
+ return result.rows;
6
+ }
7
+ export async function getOrgForAdmin(pool, orgId) {
8
+ const result = await pool.query(`SELECT o.*,
9
+ COUNT(m.id) FILTER (WHERE m.removed_at IS NULL) AS member_count
10
+ FROM tm_organizations o
11
+ LEFT JOIN tm_memberships m ON m.org_id = o.id
12
+ WHERE o.id = $1
13
+ GROUP BY o.id`, [orgId]);
14
+ if (result.rows.length === 0)
15
+ throw new Error('Organization not found');
16
+ const row = result.rows[0];
17
+ return { ...row, memberCount: parseInt(row.member_count, 10) };
18
+ }
19
+ export async function getUserForAdmin(pool, adapter, userId) {
20
+ const user = await adapter.getUserById(userId);
21
+ if (!user)
22
+ throw new Error('User not found');
23
+ const memberships = await pool.query(`SELECT * FROM tm_memberships WHERE user_id = $1 ORDER BY joined_at DESC`, [userId]);
24
+ return { ...user, memberships: memberships.rows };
25
+ }
26
+ export async function restoreOrg(pool, { orgId, superAdminUserId, reason, }) {
27
+ const result = await pool.query(`UPDATE tm_organizations
28
+ SET deleted_at = NULL, delete_scheduled_for = NULL, deleted_by_user_id = NULL, updated_at = NOW()
29
+ WHERE id = $1 AND deleted_at IS NOT NULL`, [orgId]);
30
+ if ((result.rowCount ?? 0) === 0)
31
+ throw new Error('Organization not found or not deleted');
32
+ await writeAuditEvent({
33
+ pool,
34
+ orgId,
35
+ actorUserId: superAdminUserId,
36
+ actorType: 'super_admin',
37
+ action: 'org.restored',
38
+ targetType: 'org',
39
+ targetId: orgId,
40
+ reason,
41
+ });
42
+ }
43
+ export async function appointOwner(pool, { orgId, targetUserId, superAdminUserId, reason, }) {
44
+ const client = await pool.connect();
45
+ try {
46
+ await client.query('BEGIN');
47
+ // Demote current owner to admin
48
+ await client.query(`UPDATE tm_memberships SET role = 'admin', updated_at = NOW()
49
+ WHERE org_id = $1 AND role = 'owner' AND removed_at IS NULL`, [orgId]);
50
+ // Upsert new owner
51
+ await client.query(`INSERT INTO tm_memberships (org_id, user_id, role, joined_at)
52
+ VALUES ($1, $2, 'owner', NOW())
53
+ ON CONFLICT (org_id, user_id) WHERE removed_at IS NULL
54
+ DO UPDATE SET role = 'owner', removed_at = NULL, updated_at = NOW()`, [orgId, targetUserId]);
55
+ // Update denormalized owner field
56
+ await client.query(`UPDATE tm_organizations SET owner_user_id = $1, updated_at = NOW() WHERE id = $2`, [targetUserId, orgId]);
57
+ await client.query('COMMIT');
58
+ }
59
+ catch (e) {
60
+ await client.query('ROLLBACK');
61
+ throw e;
62
+ }
63
+ finally {
64
+ client.release();
65
+ }
66
+ await writeAuditEvent({
67
+ pool,
68
+ orgId,
69
+ actorUserId: superAdminUserId,
70
+ actorType: 'super_admin',
71
+ action: 'org.owner_appointed',
72
+ targetType: 'user',
73
+ targetId: targetUserId,
74
+ reason,
75
+ });
76
+ }
77
+ export async function hardDeleteOrg(pool, { orgId, superAdminUserId, legalBasis, }) {
78
+ if (!legalBasis || legalBasis.trim().length < 10) {
79
+ throw new Error('A legal basis of at least 10 characters is required for hard delete');
80
+ }
81
+ await writeAuditEvent({
82
+ pool,
83
+ orgId,
84
+ actorUserId: superAdminUserId,
85
+ actorType: 'super_admin',
86
+ action: 'org.hard_deleted',
87
+ targetType: 'org',
88
+ targetId: orgId,
89
+ reason: legalBasis,
90
+ });
91
+ // Hard delete — cascades to memberships, invitations, etc.
92
+ await pool.query(`DELETE FROM tm_organizations WHERE id = $1`, [orgId]);
93
+ }
94
+ export async function addMemberAdmin(pool, { orgId, userId, role, superAdminUserId, reason, }) {
95
+ await pool.query(`INSERT INTO tm_memberships (org_id, user_id, role, joined_at)
96
+ VALUES ($1, $2, $3, NOW())
97
+ ON CONFLICT (org_id, user_id)
98
+ DO UPDATE SET role = EXCLUDED.role, removed_at = NULL, removed_by_user_id = NULL,
99
+ removal_reason = NULL, updated_at = NOW()`, [orgId, userId, role]);
100
+ await writeAuditEvent({
101
+ pool,
102
+ orgId,
103
+ actorUserId: superAdminUserId,
104
+ actorType: 'super_admin',
105
+ action: 'org.member_added_admin',
106
+ targetType: 'user',
107
+ targetId: userId,
108
+ after: { role },
109
+ reason,
110
+ });
111
+ }
112
+ export async function removeMemberAdmin(pool, { orgId, userId, superAdminUserId, reason, }) {
113
+ await pool.query(`UPDATE tm_memberships
114
+ SET removed_at = NOW(), removed_by_user_id = $1, removal_reason = $2, updated_at = NOW()
115
+ WHERE org_id = $3 AND user_id = $4 AND removed_at IS NULL`, [superAdminUserId, reason, orgId, userId]);
116
+ await writeAuditEvent({
117
+ pool,
118
+ orgId,
119
+ actorUserId: superAdminUserId,
120
+ actorType: 'super_admin',
121
+ action: 'org.member_removed_admin',
122
+ targetType: 'user',
123
+ targetId: userId,
124
+ reason,
125
+ });
126
+ }
127
+ export async function lockUser(pool, adapter, { userId, superAdminUserId, reason, }) {
128
+ await pool.query(`INSERT INTO tm_user_locks (user_id, locked_by, reason, locked_at)
129
+ VALUES ($1, $2, $3, NOW())
130
+ ON CONFLICT (user_id) DO UPDATE SET locked_by = EXCLUDED.locked_by,
131
+ reason = EXCLUDED.reason, locked_at = EXCLUDED.locked_at, unlocked_at = NULL`, [userId, superAdminUserId, reason]);
132
+ await adapter.invalidateAllUserSessions(userId);
133
+ await writeAuditEvent({
134
+ pool,
135
+ orgId: null,
136
+ actorUserId: superAdminUserId,
137
+ actorType: 'super_admin',
138
+ action: 'user.locked',
139
+ targetType: 'user',
140
+ targetId: userId,
141
+ reason,
142
+ });
143
+ }
144
+ export async function unlockUser(pool, adapter, { userId, superAdminUserId, reason, }) {
145
+ await pool.query(`UPDATE tm_user_locks SET unlocked_at = NOW() WHERE user_id = $1 AND unlocked_at IS NULL`, [userId]);
146
+ await writeAuditEvent({
147
+ pool,
148
+ orgId: null,
149
+ actorUserId: superAdminUserId,
150
+ actorType: 'super_admin',
151
+ action: 'user.unlocked',
152
+ targetType: 'user',
153
+ targetId: userId,
154
+ reason,
155
+ });
156
+ }
157
+ export async function triggerPasswordReset(pool, adapter, { userId, superAdminUserId, reason, baseUrl, }) {
158
+ const user = await adapter.getUserById(userId);
159
+ if (!user)
160
+ throw new Error('User not found');
161
+ await requestPasswordReset(pool, adapter, {
162
+ email: user.email,
163
+ baseUrl,
164
+ triggeredBySuperAdmin: true,
165
+ });
166
+ await writeAuditEvent({
167
+ pool,
168
+ orgId: null,
169
+ actorUserId: superAdminUserId,
170
+ actorType: 'super_admin',
171
+ action: 'user.password_reset_triggered',
172
+ targetType: 'user',
173
+ targetId: userId,
174
+ reason,
175
+ });
176
+ }
177
+ export async function seedSuperAdmin(pool, adapter, email) {
178
+ const user = await adapter.findUserByEmail(email);
179
+ if (!user) {
180
+ // User not found — skip silently (idempotent)
181
+ return;
182
+ }
183
+ await pool.query(`INSERT INTO tm_super_admins (user_id, granted_at)
184
+ VALUES ($1, NOW())
185
+ ON CONFLICT (user_id) DO NOTHING`, [user.id]);
186
+ }
187
+ //# sourceMappingURL=super-admin.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"super-admin.service.js","sourceRoot":"","sources":["../../../src/server/services/super-admin.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAEnE,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAU;IAC1C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B,yDAAyD,CAC1D,CAAC;IACF,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAU,EACV,KAAa;IAEb,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B;;;;;mBAKe,EACf,CAAC,KAAK,CAAC,CACR,CAAC;IACF,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IACxE,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3B,OAAO,EAAE,GAAG,GAAG,EAAE,WAAW,EAAE,QAAQ,CAAC,GAAG,CAAC,YAAY,EAAE,EAAE,CAAC,EAAE,CAAC;AACjE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAU,EACV,OAA4B,EAC5B,MAAc;IAEd,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAE7C,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,KAAK,CAClC,yEAAyE,EACzE,CAAC,MAAM,CAAC,CACT,CAAC;IACF,OAAO,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC,IAAI,EAAE,CAAC;AACpD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAU,EACV,EACE,KAAK,EACL,gBAAgB,EAChB,MAAM,GACsD;IAE9D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B;;8CAE0C,EAC1C,CAAC,KAAK,CAAC,CACR,CAAC;IACF,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAE3F,MAAM,eAAe,CAAC;QACpB,IAAI;QACJ,KAAK;QACL,WAAW,EAAE,gBAAgB;QAC7B,SAAS,EAAE,aAAa;QACxB,MAAM,EAAE,cAAc;QACtB,UAAU,EAAE,KAAK;QACjB,QAAQ,EAAE,KAAK;QACf,MAAM;KACP,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAU,EACV,EACE,KAAK,EACL,YAAY,EACZ,gBAAgB,EAChB,MAAM,GAC4E;IAEpF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAE5B,gCAAgC;QAChC,MAAM,MAAM,CAAC,KAAK,CAChB;mEAC6D,EAC7D,CAAC,KAAK,CAAC,CACR,CAAC;QAEF,mBAAmB;QACnB,MAAM,MAAM,CAAC,KAAK,CAChB;;;2EAGqE,EACrE,CAAC,KAAK,EAAE,YAAY,CAAC,CACtB,CAAC;QAEF,kCAAkC;QAClC,MAAM,MAAM,CAAC,KAAK,CAChB,kFAAkF,EAClF,CAAC,YAAY,EAAE,KAAK,CAAC,CACtB,CAAC;QAEF,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAC/B,MAAM,CAAC,CAAC;IACV,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;IAED,MAAM,eAAe,CAAC;QACpB,IAAI;QACJ,KAAK;QACL,WAAW,EAAE,gBAAgB;QAC7B,SAAS,EAAE,aAAa;QACxB,MAAM,EAAE,qBAAqB;QAC7B,UAAU,EAAE,MAAM;QAClB,QAAQ,EAAE,YAAY;QACtB,MAAM;KACP,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAU,EACV,EACE,KAAK,EACL,gBAAgB,EAChB,UAAU,GACsD;IAElE,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAC;IACzF,CAAC;IAED,MAAM,eAAe,CAAC;QACpB,IAAI;QACJ,KAAK;QACL,WAAW,EAAE,gBAAgB;QAC7B,SAAS,EAAE,aAAa;QACxB,MAAM,EAAE,kBAAkB;QAC1B,UAAU,EAAE,KAAK;QACjB,QAAQ,EAAE,KAAK;QACf,MAAM,EAAE,UAAU;KACnB,CAAC,CAAC;IAEH,2DAA2D;IAC3D,MAAM,IAAI,CAAC,KAAK,CAAC,4CAA4C,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAU,EACV,EACE,KAAK,EACL,MAAM,EACN,IAAI,EACJ,gBAAgB,EAChB,MAAM,GACqF;IAE7F,MAAM,IAAI,CAAC,KAAK,CACd;;;;6DAIyD,EACzD,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,CACtB,CAAC;IAEF,MAAM,eAAe,CAAC;QACpB,IAAI;QACJ,KAAK;QACL,WAAW,EAAE,gBAAgB;QAC7B,SAAS,EAAE,aAAa;QACxB,MAAM,EAAE,wBAAwB;QAChC,UAAU,EAAE,MAAM;QAClB,QAAQ,EAAE,MAAM;QAChB,KAAK,EAAE,EAAE,IAAI,EAAE;QACf,MAAM;KACP,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAU,EACV,EACE,KAAK,EACL,MAAM,EACN,gBAAgB,EAChB,MAAM,GACsE;IAE9E,MAAM,IAAI,CAAC,KAAK,CACd;;+DAE2D,EAC3D,CAAC,gBAAgB,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAC1C,CAAC;IAEF,MAAM,eAAe,CAAC;QACpB,IAAI;QACJ,KAAK;QACL,WAAW,EAAE,gBAAgB;QAC7B,SAAS,EAAE,aAAa;QACxB,MAAM,EAAE,0BAA0B;QAClC,UAAU,EAAE,MAAM;QAClB,QAAQ,EAAE,MAAM;QAChB,MAAM;KACP,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAU,EACV,OAA4B,EAC5B,EACE,MAAM,EACN,gBAAgB,EAChB,MAAM,GACuD;IAE/D,MAAM,IAAI,CAAC,KAAK,CACd;;;oFAGgF,EAChF,CAAC,MAAM,EAAE,gBAAgB,EAAE,MAAM,CAAC,CACnC,CAAC;IAEF,MAAM,OAAO,CAAC,yBAAyB,CAAC,MAAM,CAAC,CAAC;IAEhD,MAAM,eAAe,CAAC;QACpB,IAAI;QACJ,KAAK,EAAE,IAAI;QACX,WAAW,EAAE,gBAAgB;QAC7B,SAAS,EAAE,aAAa;QACxB,MAAM,EAAE,aAAa;QACrB,UAAU,EAAE,MAAM;QAClB,QAAQ,EAAE,MAAM;QAChB,MAAM;KACP,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAU,EACV,OAA4B,EAC5B,EACE,MAAM,EACN,gBAAgB,EAChB,MAAM,GACuD;IAE/D,MAAM,IAAI,CAAC,KAAK,CACd,yFAAyF,EACzF,CAAC,MAAM,CAAC,CACT,CAAC;IAEF,MAAM,eAAe,CAAC;QACpB,IAAI;QACJ,KAAK,EAAE,IAAI;QACX,WAAW,EAAE,gBAAgB;QAC7B,SAAS,EAAE,aAAa;QACxB,MAAM,EAAE,eAAe;QACvB,UAAU,EAAE,MAAM;QAClB,QAAQ,EAAE,MAAM;QAChB,MAAM;KACP,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,IAAU,EACV,OAA4B,EAC5B,EACE,MAAM,EACN,gBAAgB,EAChB,MAAM,EACN,OAAO,GACuE;IAEhF,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAE7C,MAAM,oBAAoB,CAAC,IAAI,EAAE,OAAO,EAAE;QACxC,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,OAAO;QACP,qBAAqB,EAAE,IAAI;KAC5B,CAAC,CAAC;IAEH,MAAM,eAAe,CAAC;QACpB,IAAI;QACJ,KAAK,EAAE,IAAI;QACX,WAAW,EAAE,gBAAgB;QAC7B,SAAS,EAAE,aAAa;QACxB,MAAM,EAAE,+BAA+B;QACvC,UAAU,EAAE,MAAM;QAClB,QAAQ,EAAE,MAAM;QAChB,MAAM;KACP,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAU,EACV,OAA4B,EAC5B,KAAa;IAEb,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAClD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,8CAA8C;QAC9C,OAAO;IACT,CAAC;IAED,MAAM,IAAI,CAAC,KAAK,CACd;;sCAEkC,EAClC,CAAC,IAAI,CAAC,EAAE,CAAC,CACV,CAAC;AACJ,CAAC"}
@@ -0,0 +1,186 @@
1
+ import type { Request } from 'express';
2
+ export type OrgRole = 'owner' | 'admin' | 'member' | 'viewer';
3
+ export type AuditActorType = 'user' | 'super_admin';
4
+ export type TransferStatus = 'pending' | 'accepted' | 'cancelled' | 'expired';
5
+ export interface TmOrganization {
6
+ id: number;
7
+ name: string;
8
+ slug: string;
9
+ owner_user_id: number;
10
+ settings: Record<string, unknown>;
11
+ deleted_at: Date | null;
12
+ delete_scheduled_for: Date | null;
13
+ deleted_by_user_id: number | null;
14
+ created_at: Date;
15
+ updated_at: Date;
16
+ }
17
+ export interface TmMembership {
18
+ id: number;
19
+ org_id: number;
20
+ user_id: number;
21
+ role: OrgRole;
22
+ joined_at: Date;
23
+ removed_at: Date | null;
24
+ removed_by_user_id: number | null;
25
+ removal_reason: string | null;
26
+ created_at: Date;
27
+ updated_at: Date;
28
+ }
29
+ export interface TmInvitation {
30
+ id: number;
31
+ org_id: number;
32
+ invited_by_user_id: number;
33
+ email: string;
34
+ role: OrgRole;
35
+ token_hash: string;
36
+ code_encrypted: string;
37
+ expires_at: Date;
38
+ accepted_at: Date | null;
39
+ revoked_at: Date | null;
40
+ revoked_by_user_id: number | null;
41
+ resent_count: number;
42
+ created_at: Date;
43
+ updated_at: Date;
44
+ }
45
+ export interface TmAuditEvent {
46
+ id: string;
47
+ org_id: number | null;
48
+ actor_user_id: number | null;
49
+ actor_type: AuditActorType;
50
+ action: string;
51
+ target_type: string | null;
52
+ target_id: string | null;
53
+ before_state: Record<string, unknown> | null;
54
+ after_state: Record<string, unknown> | null;
55
+ ip: string | null;
56
+ user_agent: string | null;
57
+ reason: string | null;
58
+ created_at: Date;
59
+ }
60
+ export interface TmOwnershipTransfer {
61
+ id: number;
62
+ org_id: number;
63
+ from_user_id: number;
64
+ to_user_id: number;
65
+ status: TransferStatus;
66
+ initiated_at: Date;
67
+ accepted_at: Date | null;
68
+ cancelled_at: Date | null;
69
+ cancelled_by_user_id: number | null;
70
+ expires_at: Date;
71
+ }
72
+ export interface TmPasswordResetRequest {
73
+ id: number;
74
+ user_id: number;
75
+ token_hash: string;
76
+ expires_at: Date;
77
+ used_at: Date | null;
78
+ created_at: Date;
79
+ }
80
+ export interface ServerModuleAdapter {
81
+ getCurrentUserId(req: Request): Promise<number | null>;
82
+ getOrganizationIdForUser(userId: number): Promise<number | null>;
83
+ isUserOrgAdmin(userId: number, orgId: number): Promise<boolean>;
84
+ logger: {
85
+ info(message: string, meta?: Record<string, unknown>): void;
86
+ warn(message: string, meta?: Record<string, unknown>): void;
87
+ error(message: string, meta?: Record<string, unknown>): void;
88
+ };
89
+ getUserById(userId: number): Promise<{
90
+ id: number;
91
+ email: string;
92
+ name?: string;
93
+ } | null>;
94
+ getUsersByIds(userIds: number[]): Promise<Array<{
95
+ id: number;
96
+ email: string;
97
+ name?: string;
98
+ }>>;
99
+ findUserByEmail(email: string): Promise<{
100
+ id: number;
101
+ email: string;
102
+ } | null>;
103
+ createUserFromInvite(data: {
104
+ email: string;
105
+ orgId: number;
106
+ role: OrgRole;
107
+ }): Promise<{
108
+ id: number;
109
+ email: string;
110
+ }>;
111
+ setUserPassword(userId: number, passwordHash: string): Promise<void>;
112
+ hashPassword(plaintext: string): Promise<string>;
113
+ verifyPassword(plaintext: string, hash: string): Promise<boolean>;
114
+ invalidateAllUserSessions(userId: number): Promise<void>;
115
+ sendInviteEmail(data: {
116
+ to: string;
117
+ orgName: string;
118
+ inviterName: string;
119
+ role: OrgRole;
120
+ magicLinkUrl: string;
121
+ code: string;
122
+ }): Promise<void>;
123
+ sendOwnershipTransferEmail(data: {
124
+ to: string;
125
+ orgName: string;
126
+ fromName: string;
127
+ transferUrl: string;
128
+ }): Promise<void>;
129
+ sendEmailChangeVerification(data: {
130
+ to: string;
131
+ verifyUrl: string;
132
+ }): Promise<void>;
133
+ sendEmailChangeOldNotice(data: {
134
+ to: string;
135
+ newEmail: string;
136
+ cancelUrl: string;
137
+ }): Promise<void>;
138
+ sendEmailChangedFinalNotice(data: {
139
+ to: string;
140
+ oldEmail: string;
141
+ newEmail: string;
142
+ }): Promise<void>;
143
+ sendPasswordResetEmail(data: {
144
+ to: string;
145
+ resetUrl: string;
146
+ }): Promise<void>;
147
+ sendOrgDeletionNotice(data: {
148
+ to: string;
149
+ orgName: string;
150
+ scheduledFor: Date;
151
+ }): Promise<void>;
152
+ emitNotification(data: {
153
+ userId: number;
154
+ type: string;
155
+ payload: Record<string, unknown>;
156
+ }): Promise<void>;
157
+ }
158
+ export interface TeamManagementFeatureFlags {
159
+ enableInvites?: boolean;
160
+ enableAuditLog?: boolean;
161
+ enableOwnershipTransfer?: boolean;
162
+ enableEmailChange?: boolean;
163
+ enablePasswordReset?: boolean;
164
+ enableSuperAdmin?: boolean;
165
+ enableSharedAccess?: boolean;
166
+ enableHardDelete?: boolean;
167
+ }
168
+ export interface TeamManagementConfig {
169
+ featureFlags?: TeamManagementFeatureFlags;
170
+ baseUrl?: string;
171
+ }
172
+ export interface TeamManagementServerModule {
173
+ router: import('express').Router;
174
+ runMigrations(): Promise<{
175
+ applied: string[];
176
+ skipped: string[];
177
+ }>;
178
+ /** Alias for runMigrations — for test convenience */
179
+ migrate(): Promise<{
180
+ applied: string[];
181
+ skipped: string[];
182
+ }>;
183
+ }
184
+ export declare const ROLE_HIERARCHY: Record<OrgRole, number>;
185
+ export declare function roleAtLeast(userRole: OrgRole, required: OrgRole): boolean;
186
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ // Role permission helpers — values are spaced by 10 so future roles can be inserted
2
+ export const ROLE_HIERARCHY = { viewer: 10, member: 20, admin: 30, owner: 40 };
3
+ export function roleAtLeast(userRole, required) {
4
+ return ROLE_HIERARCHY[userRole] >= ROLE_HIERARCHY[required];
5
+ }
6
+ //# sourceMappingURL=types.js.map