@rovela-ai/sdk 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/dist/admin/api/accept-invite.d.ts +65 -0
  2. package/dist/admin/api/accept-invite.d.ts.map +1 -0
  3. package/dist/admin/api/accept-invite.js +115 -0
  4. package/dist/admin/api/accept-invite.js.map +1 -0
  5. package/dist/admin/api/categories.d.ts.map +1 -1
  6. package/dist/admin/api/categories.js +21 -28
  7. package/dist/admin/api/categories.js.map +1 -1
  8. package/dist/admin/api/customers.d.ts.map +1 -1
  9. package/dist/admin/api/customers.js +17 -25
  10. package/dist/admin/api/customers.js.map +1 -1
  11. package/dist/admin/api/forgot-password.d.ts +39 -0
  12. package/dist/admin/api/forgot-password.d.ts.map +1 -0
  13. package/dist/admin/api/forgot-password.js +66 -0
  14. package/dist/admin/api/forgot-password.js.map +1 -0
  15. package/dist/admin/api/index.d.ts +6 -0
  16. package/dist/admin/api/index.d.ts.map +1 -1
  17. package/dist/admin/api/index.js +9 -0
  18. package/dist/admin/api/index.js.map +1 -1
  19. package/dist/admin/api/me.d.ts +72 -0
  20. package/dist/admin/api/me.d.ts.map +1 -0
  21. package/dist/admin/api/me.js +177 -0
  22. package/dist/admin/api/me.js.map +1 -0
  23. package/dist/admin/api/orders.d.ts.map +1 -1
  24. package/dist/admin/api/orders.js +21 -28
  25. package/dist/admin/api/orders.js.map +1 -1
  26. package/dist/admin/api/products.d.ts.map +1 -1
  27. package/dist/admin/api/products.js +33 -37
  28. package/dist/admin/api/products.js.map +1 -1
  29. package/dist/admin/api/refund.d.ts.map +1 -1
  30. package/dist/admin/api/refund.js +5 -7
  31. package/dist/admin/api/refund.js.map +1 -1
  32. package/dist/admin/api/reset-password.d.ts +49 -0
  33. package/dist/admin/api/reset-password.d.ts.map +1 -0
  34. package/dist/admin/api/reset-password.js +99 -0
  35. package/dist/admin/api/reset-password.js.map +1 -0
  36. package/dist/admin/api/return.d.ts.map +1 -1
  37. package/dist/admin/api/return.js +9 -12
  38. package/dist/admin/api/return.js.map +1 -1
  39. package/dist/admin/api/settings.d.ts.map +1 -1
  40. package/dist/admin/api/settings.js +9 -12
  41. package/dist/admin/api/settings.js.map +1 -1
  42. package/dist/admin/api/shipping.d.ts.map +1 -1
  43. package/dist/admin/api/shipping.js +65 -61
  44. package/dist/admin/api/shipping.js.map +1 -1
  45. package/dist/admin/api/stats.d.ts.map +1 -1
  46. package/dist/admin/api/stats.js +5 -7
  47. package/dist/admin/api/stats.js.map +1 -1
  48. package/dist/admin/api/stripe-status.d.ts.map +1 -1
  49. package/dist/admin/api/stripe-status.js +5 -7
  50. package/dist/admin/api/stripe-status.js.map +1 -1
  51. package/dist/admin/api/tax-zones.d.ts.map +1 -1
  52. package/dist/admin/api/tax-zones.js +21 -28
  53. package/dist/admin/api/tax-zones.js.map +1 -1
  54. package/dist/admin/api/users.d.ts +142 -0
  55. package/dist/admin/api/users.d.ts.map +1 -0
  56. package/dist/admin/api/users.js +356 -0
  57. package/dist/admin/api/users.js.map +1 -0
  58. package/dist/admin/components/AdminAcceptInviteForm.d.ts +3 -0
  59. package/dist/admin/components/AdminAcceptInviteForm.d.ts.map +1 -0
  60. package/dist/admin/components/AdminAcceptInviteForm.js +137 -0
  61. package/dist/admin/components/AdminAcceptInviteForm.js.map +1 -0
  62. package/dist/admin/components/AdminAccountPage.d.ts +10 -0
  63. package/dist/admin/components/AdminAccountPage.d.ts.map +1 -0
  64. package/dist/admin/components/AdminAccountPage.js +123 -0
  65. package/dist/admin/components/AdminAccountPage.js.map +1 -0
  66. package/dist/admin/components/AdminForgotPasswordForm.d.ts +8 -0
  67. package/dist/admin/components/AdminForgotPasswordForm.d.ts.map +1 -0
  68. package/dist/admin/components/AdminForgotPasswordForm.js +59 -0
  69. package/dist/admin/components/AdminForgotPasswordForm.js.map +1 -0
  70. package/dist/admin/components/AdminNav.d.ts.map +1 -1
  71. package/dist/admin/components/AdminNav.js +32 -4
  72. package/dist/admin/components/AdminNav.js.map +1 -1
  73. package/dist/admin/components/AdminResetPasswordForm.d.ts +12 -0
  74. package/dist/admin/components/AdminResetPasswordForm.d.ts.map +1 -0
  75. package/dist/admin/components/AdminResetPasswordForm.js +134 -0
  76. package/dist/admin/components/AdminResetPasswordForm.js.map +1 -0
  77. package/dist/admin/components/AdminUserMenu.d.ts.map +1 -1
  78. package/dist/admin/components/AdminUserMenu.js +2 -2
  79. package/dist/admin/components/AdminUserMenu.js.map +1 -1
  80. package/dist/admin/components/InviteUserDialog.d.ts +3 -0
  81. package/dist/admin/components/InviteUserDialog.d.ts.map +1 -0
  82. package/dist/admin/components/InviteUserDialog.js +127 -0
  83. package/dist/admin/components/InviteUserDialog.js.map +1 -0
  84. package/dist/admin/components/UsersTable.d.ts +3 -0
  85. package/dist/admin/components/UsersTable.d.ts.map +1 -0
  86. package/dist/admin/components/UsersTable.js +399 -0
  87. package/dist/admin/components/UsersTable.js.map +1 -0
  88. package/dist/admin/components/index.d.ts +9 -0
  89. package/dist/admin/components/index.d.ts.map +1 -1
  90. package/dist/admin/components/index.js +9 -0
  91. package/dist/admin/components/index.js.map +1 -1
  92. package/dist/admin/config.d.ts +15 -10
  93. package/dist/admin/config.d.ts.map +1 -1
  94. package/dist/admin/config.js +38 -11
  95. package/dist/admin/config.js.map +1 -1
  96. package/dist/admin/hooks/index.d.ts +4 -0
  97. package/dist/admin/hooks/index.d.ts.map +1 -1
  98. package/dist/admin/hooks/index.js +3 -0
  99. package/dist/admin/hooks/index.js.map +1 -1
  100. package/dist/admin/hooks/useAdminMe.d.ts +31 -0
  101. package/dist/admin/hooks/useAdminMe.d.ts.map +1 -0
  102. package/dist/admin/hooks/useAdminMe.js +103 -0
  103. package/dist/admin/hooks/useAdminMe.js.map +1 -0
  104. package/dist/admin/hooks/useAdminPermissions.d.ts +3 -0
  105. package/dist/admin/hooks/useAdminPermissions.d.ts.map +1 -0
  106. package/dist/admin/hooks/useAdminPermissions.js +51 -0
  107. package/dist/admin/hooks/useAdminPermissions.js.map +1 -0
  108. package/dist/admin/hooks/useAdminUsers.d.ts +3 -0
  109. package/dist/admin/hooks/useAdminUsers.d.ts.map +1 -0
  110. package/dist/admin/hooks/useAdminUsers.js +240 -0
  111. package/dist/admin/hooks/useAdminUsers.js.map +1 -0
  112. package/dist/admin/index.d.ts +4 -4
  113. package/dist/admin/index.d.ts.map +1 -1
  114. package/dist/admin/index.js +20 -2
  115. package/dist/admin/index.js.map +1 -1
  116. package/dist/admin/permissions.d.ts +92 -0
  117. package/dist/admin/permissions.d.ts.map +1 -0
  118. package/dist/admin/permissions.js +201 -0
  119. package/dist/admin/permissions.js.map +1 -0
  120. package/dist/admin/server/admin-invite.d.ts +122 -0
  121. package/dist/admin/server/admin-invite.d.ts.map +1 -0
  122. package/dist/admin/server/admin-invite.js +235 -0
  123. package/dist/admin/server/admin-invite.js.map +1 -0
  124. package/dist/admin/server/admin-password-reset.d.ts +87 -0
  125. package/dist/admin/server/admin-password-reset.d.ts.map +1 -0
  126. package/dist/admin/server/admin-password-reset.js +220 -0
  127. package/dist/admin/server/admin-password-reset.js.map +1 -0
  128. package/dist/admin/server/admin-self-service.d.ts +86 -0
  129. package/dist/admin/server/admin-self-service.d.ts.map +1 -0
  130. package/dist/admin/server/admin-self-service.js +188 -0
  131. package/dist/admin/server/admin-self-service.js.map +1 -0
  132. package/dist/admin/server/admin-service.d.ts.map +1 -1
  133. package/dist/admin/server/admin-service.js +21 -2
  134. package/dist/admin/server/admin-service.js.map +1 -1
  135. package/dist/admin/server/admin-session.d.ts +137 -0
  136. package/dist/admin/server/admin-session.d.ts.map +1 -0
  137. package/dist/admin/server/admin-session.js +229 -0
  138. package/dist/admin/server/admin-session.js.map +1 -0
  139. package/dist/admin/server/index.d.ts +7 -0
  140. package/dist/admin/server/index.d.ts.map +1 -1
  141. package/dist/admin/server/index.js +20 -0
  142. package/dist/admin/server/index.js.map +1 -1
  143. package/dist/admin/server/user-management.d.ts +223 -0
  144. package/dist/admin/server/user-management.d.ts.map +1 -0
  145. package/dist/admin/server/user-management.js +846 -0
  146. package/dist/admin/server/user-management.js.map +1 -0
  147. package/dist/admin/types.d.ts +153 -2
  148. package/dist/admin/types.d.ts.map +1 -1
  149. package/dist/auth/config.d.ts.map +1 -1
  150. package/dist/auth/config.js +11 -2
  151. package/dist/auth/config.js.map +1 -1
  152. package/dist/core/db/queries.d.ts +19 -13
  153. package/dist/core/db/queries.d.ts.map +1 -1
  154. package/dist/core/db/schema.d.ts +327 -9
  155. package/dist/core/db/schema.d.ts.map +1 -1
  156. package/dist/core/db/schema.js +80 -3
  157. package/dist/core/db/schema.js.map +1 -1
  158. package/dist/core/types.d.ts +19 -3
  159. package/dist/core/types.d.ts.map +1 -1
  160. package/dist/emails/index.d.ts +2 -2
  161. package/dist/emails/index.d.ts.map +1 -1
  162. package/dist/emails/index.js +3 -1
  163. package/dist/emails/index.js.map +1 -1
  164. package/dist/emails/send/admin-auth.d.ts +94 -0
  165. package/dist/emails/send/admin-auth.d.ts.map +1 -0
  166. package/dist/emails/send/admin-auth.js +118 -0
  167. package/dist/emails/send/admin-auth.js.map +1 -0
  168. package/dist/emails/send/index.d.ts +2 -0
  169. package/dist/emails/send/index.d.ts.map +1 -1
  170. package/dist/emails/send/index.js +4 -0
  171. package/dist/emails/send/index.js.map +1 -1
  172. package/dist/emails/templates/admin-invite.d.ts +40 -0
  173. package/dist/emails/templates/admin-invite.d.ts.map +1 -0
  174. package/dist/emails/templates/admin-invite.js +62 -0
  175. package/dist/emails/templates/admin-invite.js.map +1 -0
  176. package/dist/emails/templates/index.d.ts +1 -0
  177. package/dist/emails/templates/index.d.ts.map +1 -1
  178. package/dist/emails/templates/index.js +4 -0
  179. package/dist/emails/templates/index.js.map +1 -1
  180. package/dist/emails/types.d.ts +22 -1
  181. package/dist/emails/types.d.ts.map +1 -1
  182. package/package.json +21 -1
@@ -0,0 +1,188 @@
1
+ /**
2
+ * @rovela/sdk/admin/server/admin-self-service
3
+ *
4
+ * Self-service helpers for logged-in admins — change their own password,
5
+ * edit their own profile. Never accepts an `actor` vs `target` distinction:
6
+ * these operations always act on the caller themselves, enforced by the
7
+ * API layer passing `session.user.id` as the adminId.
8
+ *
9
+ * # What these do that admin-service.ts doesn't
10
+ *
11
+ * `admin-service.ts` has the raw CRUD helpers (`updateAdmin`,
12
+ * `updateAdminPassword`). Those are called from multiple contexts —
13
+ * forgot-password flow, invite acceptance, emergency reset, user-management
14
+ * actions. This file wraps them in self-service semantics:
15
+ *
16
+ * 1. `changeOwnPassword` requires the current password as proof of
17
+ * identity. Mere session possession isn't enough — we want defense
18
+ * against "attacker on coffee shop laptop" scenarios where the
19
+ * session cookie is borrowed.
20
+ *
21
+ * 2. `updateOwnProfile` checks email uniqueness against every other
22
+ * admin (not just-not-self) before persisting, to surface clean
23
+ * error codes to the caller.
24
+ *
25
+ * Both helpers return typed discriminated unions — they never throw on
26
+ * business errors, only on unexpected infra failures (DB connectivity,
27
+ * which the API layer catches).
28
+ */
29
+ import { eq, and, ne } from 'drizzle-orm';
30
+ import { getDb } from '../../core/db/client';
31
+ import * as schema from '../../core/db/schema';
32
+ import { verifyPassword, validatePassword } from '../../auth/server/password';
33
+ import { findAdminById, updateAdminPassword, updateAdmin } from './admin-service';
34
+ import { deleteAdminPasswordResetTokens } from './admin-password-reset';
35
+ import { invalidateAdminSession } from './admin-session';
36
+ // =============================================================================
37
+ // Change own password
38
+ // =============================================================================
39
+ /**
40
+ * Change the logged-in admin's password.
41
+ *
42
+ * Requires the current password as proof of identity. Validates the new
43
+ * password via the shared `validatePassword` helper (min 8 chars). On
44
+ * success, bumps `session_version` (via `updateAdminPassword`), cleans up
45
+ * any stale reset tokens, and invalidates the in-memory session cache so
46
+ * other active sessions for this admin are kicked out on their next
47
+ * request.
48
+ *
49
+ * The admin's CURRENT session (the one that initiated this change) also
50
+ * carries a now-stale `sessionVersion` JWT claim, which means the next
51
+ * request from that session will also fail `requireAdmin`'s version
52
+ * check → forced logout. That's correct behavior: after a password
53
+ * rotation, the user must re-authenticate with the new password.
54
+ *
55
+ * If that's undesirable (the UI would need to immediately re-sign-in),
56
+ * the API handler can call `nextAuthSignIn` server-side after a
57
+ * successful change. For Phase 4, we accept the forced re-login as the
58
+ * honest behavior.
59
+ */
60
+ export async function changeOwnPassword(adminId, currentPassword, newPassword) {
61
+ // 1. Load the current admin
62
+ const admin = await findAdminById(adminId);
63
+ if (!admin) {
64
+ return {
65
+ ok: false,
66
+ error: { code: 'NOT_FOUND', message: 'Admin not found.' },
67
+ };
68
+ }
69
+ // 2. No password on file → invited admin can't self-service
70
+ if (!admin.passwordHash) {
71
+ return {
72
+ ok: false,
73
+ error: {
74
+ code: 'INVALID_CREDENTIALS',
75
+ message: 'Current password is incorrect.',
76
+ },
77
+ };
78
+ }
79
+ // 3. Verify current password — same generic error message on failure
80
+ // so we never reveal whether the account exists or is in a weird state.
81
+ const isValid = await verifyPassword(currentPassword, admin.passwordHash);
82
+ if (!isValid) {
83
+ return {
84
+ ok: false,
85
+ error: {
86
+ code: 'INVALID_CREDENTIALS',
87
+ message: 'Current password is incorrect.',
88
+ },
89
+ };
90
+ }
91
+ // 4. Validate new password strength
92
+ const check = validatePassword(newPassword);
93
+ if (!check.valid) {
94
+ return {
95
+ ok: false,
96
+ error: {
97
+ code: 'VALIDATION_ERROR',
98
+ message: check.error || 'Invalid new password.',
99
+ },
100
+ };
101
+ }
102
+ // 5. Update (bumps session_version internally)
103
+ await updateAdminPassword(adminId, newPassword);
104
+ // 6. Defensive cleanup — any stale reset tokens shouldn't grant
105
+ // post-rotation access.
106
+ await deleteAdminPasswordResetTokens(adminId);
107
+ // 7. Flush the 30s per-admin status cache so the next request reads
108
+ // the new session_version immediately.
109
+ invalidateAdminSession(adminId);
110
+ return { ok: true };
111
+ }
112
+ // =============================================================================
113
+ // Update own profile
114
+ // =============================================================================
115
+ /**
116
+ * Update the logged-in admin's name and/or email.
117
+ *
118
+ * Email uniqueness is checked against every OTHER admin (not self). If
119
+ * both fields are omitted, returns a VALIDATION_ERROR so the caller gets
120
+ * a clean error rather than a silent no-op.
121
+ *
122
+ * Changing email does NOT require a separate re-verification — the admin
123
+ * is already authenticated and the client-side confirmation dialog in
124
+ * `AdminAccountPage` warns them about the forgot-password implication.
125
+ *
126
+ * No sessionVersion bump — profile changes don't invalidate sessions.
127
+ */
128
+ export async function updateOwnProfile(adminId, data) {
129
+ const name = typeof data.name === 'string' ? data.name.trim() : undefined;
130
+ const email = typeof data.email === 'string' ? data.email.trim().toLowerCase() : undefined;
131
+ if (name === undefined && email === undefined) {
132
+ return {
133
+ ok: false,
134
+ error: {
135
+ code: 'VALIDATION_ERROR',
136
+ message: 'Provide at least one field to update (name or email).',
137
+ },
138
+ };
139
+ }
140
+ // Validate name length (if provided)
141
+ if (name !== undefined && name.length < 2) {
142
+ return {
143
+ ok: false,
144
+ error: {
145
+ code: 'VALIDATION_ERROR',
146
+ message: 'Name must be at least 2 characters.',
147
+ },
148
+ };
149
+ }
150
+ // Validate email format (if provided)
151
+ if (email !== undefined && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
152
+ return {
153
+ ok: false,
154
+ error: {
155
+ code: 'VALIDATION_ERROR',
156
+ message: 'Please enter a valid email address.',
157
+ },
158
+ };
159
+ }
160
+ // Email uniqueness — only if email is being changed
161
+ if (email !== undefined) {
162
+ const db = getDb();
163
+ const [collision] = await db
164
+ .select({ id: schema.storeAdmins.id })
165
+ .from(schema.storeAdmins)
166
+ .where(and(eq(schema.storeAdmins.email, email), ne(schema.storeAdmins.id, adminId)))
167
+ .limit(1);
168
+ if (collision) {
169
+ return {
170
+ ok: false,
171
+ error: {
172
+ code: 'EMAIL_EXISTS',
173
+ message: 'Another admin already uses this email.',
174
+ },
175
+ };
176
+ }
177
+ }
178
+ // Delegate to the shared helper
179
+ const updated = await updateAdmin(adminId, { name, email });
180
+ if (!updated) {
181
+ return {
182
+ ok: false,
183
+ error: { code: 'NOT_FOUND', message: 'Admin not found.' },
184
+ };
185
+ }
186
+ return { ok: true, admin: updated };
187
+ }
188
+ //# sourceMappingURL=admin-self-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-self-service.js","sourceRoot":"","sources":["../../../src/admin/server/admin-self-service.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"admin-service.d.ts","sourceRoot":"","sources":["../../../src/admin/server/admin-service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,MAAM,MAAM,sBAAsB,CAAA;AAE9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAM5C,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC,UAAU,CAAA;CACzB;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,IAAI,CAAA;IACb,KAAK,EAAE,MAAM,CAAC,UAAU,CAAA;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,KAAK,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,qBAAqB,GAAG,iBAAiB,CAAA;CAChD;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,uBAAuB,GAAG,sBAAsB,CAAC,CA6B3D;AAMD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,OAAO,GAAG,OAAiB,GAChC,OAAO,CAAC,iBAAiB,CAAC,CA0B5B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAwB9B;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,CAUnC;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,CAUnC;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACtC,OAAO,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,CAyBnC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGtE;AAED;;;GAGG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAQnD"}
1
+ {"version":3,"file":"admin-service.d.ts","sourceRoot":"","sources":["../../../src/admin/server/admin-service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,MAAM,MAAM,sBAAsB,CAAA;AAE9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAM5C,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC,UAAU,CAAA;CACzB;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,IAAI,CAAA;IACb,KAAK,EAAE,MAAM,CAAC,UAAU,CAAA;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,KAAK,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,qBAAqB,GAAG,iBAAiB,CAAA;CAChD;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,uBAAuB,GAAG,sBAAsB,CAAC,CA0C3D;AAMD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,OAAO,GAAG,OAAiB,GAChC,OAAO,CAAC,iBAAiB,CAAC,CA0B5B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAwB9B;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,CAUnC;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,CAUnC;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACtC,OAAO,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,CAyBnC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAgBf;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGtE;AAED;;;GAGG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAQnD"}
@@ -4,7 +4,7 @@
4
4
  * Admin CRUD operations.
5
5
  * Each store has its own database (via Neon branches) - no tenant isolation needed.
6
6
  */
7
- import { eq } from 'drizzle-orm';
7
+ import { eq, sql } from 'drizzle-orm';
8
8
  import { getDb } from '../../core/db/client';
9
9
  import * as schema from '../../core/db/schema';
10
10
  import { hashPassword, verifyPassword } from '../../auth/server/password';
@@ -43,6 +43,18 @@ export async function authenticateAdmin(email, password) {
43
43
  code: 'INVALID_CREDENTIALS',
44
44
  };
45
45
  }
46
+ // Block 'deactivated' and 'invited' admins. 'invited' admins have no
47
+ // password hash yet (they must accept their invite first); 'deactivated'
48
+ // admins are blocked entirely. Return the same generic error message as
49
+ // wrong-password so we don't reveal account state.
50
+ const adminStatus = admin.status ?? 'active';
51
+ if (adminStatus !== 'active' || !admin.passwordHash) {
52
+ return {
53
+ success: false,
54
+ error: 'Invalid email or password',
55
+ code: 'INVALID_CREDENTIALS',
56
+ };
57
+ }
46
58
  // Verify password
47
59
  const isValidPassword = await verifyPassword(password, admin.passwordHash);
48
60
  if (!isValidPassword) {
@@ -215,9 +227,16 @@ export async function updateAdmin(adminId, data) {
215
227
  export async function updateAdminPassword(adminId, newPassword) {
216
228
  const db = getDb();
217
229
  const passwordHash = await hashPassword(newPassword);
230
+ // Bump session_version atomically so all existing JWTs for this admin
231
+ // are invalidated on their next request. Every caller (self-service,
232
+ // forgot-password reset, accept-invite, emergency reset) gets this
233
+ // invalidation semantics consistently.
218
234
  await db
219
235
  .update(schema.storeAdmins)
220
- .set({ passwordHash })
236
+ .set({
237
+ passwordHash,
238
+ sessionVersion: sql `${schema.storeAdmins.sessionVersion} + 1`,
239
+ })
221
240
  .where(eq(schema.storeAdmins.id, adminId));
222
241
  }
223
242
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"admin-service.js","sourceRoot":"","sources":["../../../src/admin/server/admin-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"admin-service.js","sourceRoot":"","sources":["../../../src/admin/server/admin-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * @rovela/sdk/admin/server/admin-session
3
+ *
4
+ * Centralized session + permission check for admin API routes.
5
+ *
6
+ * # Why this exists
7
+ *
8
+ * Before this module, every admin API handler had its own local copy of a
9
+ * `requireAdmin()` helper — ten copies across products, orders, customers,
10
+ * refund, return, settings, stats, stripe-status, categories, shipping, and
11
+ * tax-zones. None of them enforced role-based permissions.
12
+ *
13
+ * # Which auth config we read
14
+ *
15
+ * The sandbox template mounts exactly ONE NextAuth endpoint at
16
+ * `/api/auth/[...nextauth]/route.ts`, wired to `createAuthOptions()` from
17
+ * `@rovela-ai/sdk/auth`. That unified config handles BOTH `credentials`
18
+ * (customer) and `admin-credentials` providers, and it's the config that
19
+ * writes the session cookie the browser sends back on every request.
20
+ *
21
+ * `requireAdmin` MUST read through the same config. Reading from a separate
22
+ * `createAdminAuthOptions()` (which used its own cookie name + SameSite) —
23
+ * as an earlier iteration of this file did — produced a read/write mismatch
24
+ * that rejected every admin API call with 401 regardless of DB state.
25
+ *
26
+ * This module replaces all ten legacy helpers with a single gatekeeper that:
27
+ * 1. Reads the NextAuth session via the unified `createAuthOptions()`.
28
+ * 2. Fetches a fresh admin row from the DB and confirms `status = 'active'`
29
+ * so that deactivated users are kicked out on their next request without
30
+ * waiting for the JWT to expire.
31
+ * 3. Optionally checks a specific `Permission` from the permission matrix.
32
+ * 4. Optionally enforces a minimum role rank via `meetsMinRole`.
33
+ * 5. Ensures the runtime schema migration has run (once per process) so
34
+ * stores on an older schema still work with the new SDK code paths.
35
+ *
36
+ * # Caching
37
+ *
38
+ * The DB status check is cached in-memory for 30 seconds per admin ID. This
39
+ * keeps the hot path fast (most admin routes already do a DB query for their
40
+ * actual work; one extra read would add ~10ms, and we want to avoid even
41
+ * that for back-to-back requests). Cache is invalidated via
42
+ * `invalidateAdminSession(adminId)` after any user-management operation that
43
+ * changes the admin's role or status, so the next request sees the new state
44
+ * without waiting for the 30-second TTL.
45
+ *
46
+ * # Schema assumptions
47
+ *
48
+ * This helper assumes the store's Neon database is already on the latest
49
+ * admin schema (v2). Schema is applied manually via Neon MCP once per store
50
+ * (master branch for new stores, per-branch for existing stores that want
51
+ * the feature). The helper tolerates missing fields gracefully: if the
52
+ * `status` column doesn't exist on an older store, it's read as undefined
53
+ * and defaulted to 'active' so existing owner sessions keep working.
54
+ *
55
+ * # Return contract
56
+ *
57
+ * `requireAdmin()` returns a discriminated union:
58
+ * - `{ ok: true, admin }` — caller proceeds
59
+ * - `{ ok: false, response }` — caller returns `response` directly
60
+ *
61
+ * This pattern keeps the happy path terse at call sites:
62
+ *
63
+ * ```ts
64
+ * const guard = await requireAdmin({ permission: 'products.write' })
65
+ * if (!guard.ok) return guard.response
66
+ * const { admin } = guard
67
+ * // ...proceed with admin.id, admin.role, etc.
68
+ * ```
69
+ */
70
+ import { NextResponse } from 'next/server';
71
+ import { type Permission } from '../permissions';
72
+ import type { AdminRole } from '../../core/types';
73
+ import type { StoreAdmin } from '../../core/db/schema';
74
+ import type { AdminApiError } from '../types';
75
+ export interface RequireAdminOptions {
76
+ /** Require a specific permission from the permission matrix. */
77
+ permission?: Permission;
78
+ /** Require at least this role rank (e.g. 'administrator' means owner OR administrator). */
79
+ minRole?: AdminRole;
80
+ }
81
+ export type RequireAdminResult = {
82
+ ok: true;
83
+ admin: StoreAdmin;
84
+ } | {
85
+ ok: false;
86
+ response: NextResponse<AdminApiError>;
87
+ };
88
+ /**
89
+ * Invalidate the session cache for a specific admin. Call this after any
90
+ * user-management operation that changes the admin's role or status, so the
91
+ * next request sees the new state without waiting for the 30s TTL.
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * await deactivateAdmin(adminId)
96
+ * invalidateAdminSession(adminId)
97
+ * ```
98
+ */
99
+ export declare function invalidateAdminSession(adminId: string): void;
100
+ /**
101
+ * Clear the entire admin session cache. Exposed for tests; rarely needed in
102
+ * production since per-admin invalidation is sufficient.
103
+ * @internal
104
+ */
105
+ export declare function __clearAdminSessionCache(): void;
106
+ /**
107
+ * Authenticate + authorize the current request as an admin.
108
+ *
109
+ * Resolves with either `{ ok: true, admin }` (caller may proceed) or
110
+ * `{ ok: false, response }` (caller returns `response` to the client).
111
+ *
112
+ * Never throws — any internal error is converted to a 401/403/500 response
113
+ * that's safe to return directly.
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * // List products (any authenticated admin with 'products.read')
118
+ * export async function GET(request: NextRequest) {
119
+ * const guard = await requireAdmin({ permission: 'products.read' })
120
+ * if (!guard.ok) return guard.response
121
+ * const { admin } = guard
122
+ * // ...proceed, admin.role is known, admin.status === 'active'
123
+ * }
124
+ * ```
125
+ *
126
+ * @example
127
+ * ```ts
128
+ * // Change a user's role (only owners)
129
+ * export async function PATCH(request: NextRequest) {
130
+ * const guard = await requireAdmin({ permission: 'users.write' })
131
+ * if (!guard.ok) return guard.response
132
+ * // ...
133
+ * }
134
+ * ```
135
+ */
136
+ export declare function requireAdmin(options?: RequireAdminOptions): Promise<RequireAdminResult>;
137
+ //# sourceMappingURL=admin-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-session.d.ts","sourceRoot":"","sources":["../../../src/admin/server/admin-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAI1C,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,gBAAgB,CAAA;AACvB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAM7C,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,2FAA2F;IAC3F,OAAO,CAAC,EAAE,SAAS,CAAA;CACpB;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GAC/B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,YAAY,CAAC,aAAa,CAAC,CAAA;CAAE,CAAA;AA+BxD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AAwBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,YAAY,CAChC,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,kBAAkB,CAAC,CA+E7B"}
@@ -0,0 +1,229 @@
1
+ /**
2
+ * @rovela/sdk/admin/server/admin-session
3
+ *
4
+ * Centralized session + permission check for admin API routes.
5
+ *
6
+ * # Why this exists
7
+ *
8
+ * Before this module, every admin API handler had its own local copy of a
9
+ * `requireAdmin()` helper — ten copies across products, orders, customers,
10
+ * refund, return, settings, stats, stripe-status, categories, shipping, and
11
+ * tax-zones. None of them enforced role-based permissions.
12
+ *
13
+ * # Which auth config we read
14
+ *
15
+ * The sandbox template mounts exactly ONE NextAuth endpoint at
16
+ * `/api/auth/[...nextauth]/route.ts`, wired to `createAuthOptions()` from
17
+ * `@rovela-ai/sdk/auth`. That unified config handles BOTH `credentials`
18
+ * (customer) and `admin-credentials` providers, and it's the config that
19
+ * writes the session cookie the browser sends back on every request.
20
+ *
21
+ * `requireAdmin` MUST read through the same config. Reading from a separate
22
+ * `createAdminAuthOptions()` (which used its own cookie name + SameSite) —
23
+ * as an earlier iteration of this file did — produced a read/write mismatch
24
+ * that rejected every admin API call with 401 regardless of DB state.
25
+ *
26
+ * This module replaces all ten legacy helpers with a single gatekeeper that:
27
+ * 1. Reads the NextAuth session via the unified `createAuthOptions()`.
28
+ * 2. Fetches a fresh admin row from the DB and confirms `status = 'active'`
29
+ * so that deactivated users are kicked out on their next request without
30
+ * waiting for the JWT to expire.
31
+ * 3. Optionally checks a specific `Permission` from the permission matrix.
32
+ * 4. Optionally enforces a minimum role rank via `meetsMinRole`.
33
+ * 5. Ensures the runtime schema migration has run (once per process) so
34
+ * stores on an older schema still work with the new SDK code paths.
35
+ *
36
+ * # Caching
37
+ *
38
+ * The DB status check is cached in-memory for 30 seconds per admin ID. This
39
+ * keeps the hot path fast (most admin routes already do a DB query for their
40
+ * actual work; one extra read would add ~10ms, and we want to avoid even
41
+ * that for back-to-back requests). Cache is invalidated via
42
+ * `invalidateAdminSession(adminId)` after any user-management operation that
43
+ * changes the admin's role or status, so the next request sees the new state
44
+ * without waiting for the 30-second TTL.
45
+ *
46
+ * # Schema assumptions
47
+ *
48
+ * This helper assumes the store's Neon database is already on the latest
49
+ * admin schema (v2). Schema is applied manually via Neon MCP once per store
50
+ * (master branch for new stores, per-branch for existing stores that want
51
+ * the feature). The helper tolerates missing fields gracefully: if the
52
+ * `status` column doesn't exist on an older store, it's read as undefined
53
+ * and defaulted to 'active' so existing owner sessions keep working.
54
+ *
55
+ * # Return contract
56
+ *
57
+ * `requireAdmin()` returns a discriminated union:
58
+ * - `{ ok: true, admin }` — caller proceeds
59
+ * - `{ ok: false, response }` — caller returns `response` directly
60
+ *
61
+ * This pattern keeps the happy path terse at call sites:
62
+ *
63
+ * ```ts
64
+ * const guard = await requireAdmin({ permission: 'products.write' })
65
+ * if (!guard.ok) return guard.response
66
+ * const { admin } = guard
67
+ * // ...proceed with admin.id, admin.role, etc.
68
+ * ```
69
+ */
70
+ import { NextResponse } from 'next/server';
71
+ import { getServerSession } from 'next-auth';
72
+ import { createAuthOptions } from '../../auth/config';
73
+ import { findAdminById } from './admin-service';
74
+ import { hasPermission, meetsMinRole, } from '../permissions';
75
+ const STATUS_CACHE_TTL_MS = 30 * 1000;
76
+ const statusCache = new Map();
77
+ function getCachedAdmin(adminId) {
78
+ const entry = statusCache.get(adminId);
79
+ if (!entry)
80
+ return null;
81
+ if (Date.now() > entry.expiresAt) {
82
+ statusCache.delete(adminId);
83
+ return null;
84
+ }
85
+ return entry.admin;
86
+ }
87
+ function setCachedAdmin(adminId, admin) {
88
+ statusCache.set(adminId, {
89
+ admin,
90
+ expiresAt: Date.now() + STATUS_CACHE_TTL_MS,
91
+ });
92
+ }
93
+ /**
94
+ * Invalidate the session cache for a specific admin. Call this after any
95
+ * user-management operation that changes the admin's role or status, so the
96
+ * next request sees the new state without waiting for the 30s TTL.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * await deactivateAdmin(adminId)
101
+ * invalidateAdminSession(adminId)
102
+ * ```
103
+ */
104
+ export function invalidateAdminSession(adminId) {
105
+ statusCache.delete(adminId);
106
+ }
107
+ /**
108
+ * Clear the entire admin session cache. Exposed for tests; rarely needed in
109
+ * production since per-admin invalidation is sufficient.
110
+ * @internal
111
+ */
112
+ export function __clearAdminSessionCache() {
113
+ statusCache.clear();
114
+ }
115
+ // =============================================================================
116
+ // Response helpers
117
+ // =============================================================================
118
+ function unauthorized() {
119
+ return NextResponse.json({ error: 'Unauthorized', code: 'UNAUTHORIZED' }, { status: 401 });
120
+ }
121
+ function forbidden() {
122
+ return NextResponse.json({ error: 'Forbidden', code: 'FORBIDDEN' }, { status: 403 });
123
+ }
124
+ // =============================================================================
125
+ // Main helper
126
+ // =============================================================================
127
+ /**
128
+ * Authenticate + authorize the current request as an admin.
129
+ *
130
+ * Resolves with either `{ ok: true, admin }` (caller may proceed) or
131
+ * `{ ok: false, response }` (caller returns `response` to the client).
132
+ *
133
+ * Never throws — any internal error is converted to a 401/403/500 response
134
+ * that's safe to return directly.
135
+ *
136
+ * @example
137
+ * ```ts
138
+ * // List products (any authenticated admin with 'products.read')
139
+ * export async function GET(request: NextRequest) {
140
+ * const guard = await requireAdmin({ permission: 'products.read' })
141
+ * if (!guard.ok) return guard.response
142
+ * const { admin } = guard
143
+ * // ...proceed, admin.role is known, admin.status === 'active'
144
+ * }
145
+ * ```
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * // Change a user's role (only owners)
150
+ * export async function PATCH(request: NextRequest) {
151
+ * const guard = await requireAdmin({ permission: 'users.write' })
152
+ * if (!guard.ok) return guard.response
153
+ * // ...
154
+ * }
155
+ * ```
156
+ */
157
+ export async function requireAdmin(options = {}) {
158
+ // 1. NextAuth session — read through the UNIFIED auth config. The sandbox
159
+ // template mounts one NextAuth endpoint (`/api/auth/[...nextauth]`) wired
160
+ // to `createAuthOptions()`, so that's the config that writes the session
161
+ // cookie. Reading from the same config guarantees the cookie name and
162
+ // SameSite policy match between write and read. See the file-level comment
163
+ // for the full rationale.
164
+ // The `as unknown as` dance is unavoidable: getServerSession's overloaded
165
+ // return type infers to `{}` in this generic context, and our session user
166
+ // carries a custom `role` field we need to read.
167
+ let rawSession;
168
+ try {
169
+ rawSession = await getServerSession(createAuthOptions());
170
+ }
171
+ catch (err) {
172
+ console.error('[requireAdmin] Failed to read session:', err);
173
+ return { ok: false, response: unauthorized() };
174
+ }
175
+ const sessionUser = rawSession?.user;
176
+ if (!sessionUser?.id) {
177
+ return { ok: false, response: unauthorized() };
178
+ }
179
+ // 2. Fresh DB status check (cached 30s per admin).
180
+ let admin = getCachedAdmin(sessionUser.id);
181
+ if (!admin) {
182
+ try {
183
+ const fetched = await findAdminById(sessionUser.id);
184
+ if (!fetched) {
185
+ return { ok: false, response: unauthorized() };
186
+ }
187
+ admin = fetched;
188
+ setCachedAdmin(sessionUser.id, admin);
189
+ }
190
+ catch (err) {
191
+ console.error('[requireAdmin] Failed to fetch admin row:', err);
192
+ return { ok: false, response: unauthorized() };
193
+ }
194
+ }
195
+ // 3. Status must be 'active'. 'invited' admins cannot use the dashboard
196
+ // until they accept their invite; 'deactivated' admins are blocked entirely.
197
+ // The `status` column may not exist on stores running an older schema — in
198
+ // that case the field is undefined and we treat it as 'active' (backward
199
+ // compatible for stores that haven't run the v2 migration yet).
200
+ const status = admin.status ?? 'active';
201
+ if (status !== 'active') {
202
+ return { ok: false, response: unauthorized() };
203
+ }
204
+ // 4. Session version check (Phase 4). Compare the JWT's embedded
205
+ // `sessionVersion` to the DB row's current version. On any mismatch,
206
+ // treat as forced logout.
207
+ //
208
+ // Backward compatibility: JWTs issued before Phase 4 have no
209
+ // sessionVersion claim (→ undefined → 0), and all DB rows start at 0
210
+ // after the migration, so pre-Phase-4 sessions match cleanly. The
211
+ // first password change for any admin bumps the DB version to 1;
212
+ // their old JWTs fail the match on the next request and get kicked
213
+ // out — which is exactly what we want.
214
+ const jwtVersion = sessionUser.sessionVersion ?? 0;
215
+ const dbVersion = admin.sessionVersion ?? 0;
216
+ if (jwtVersion !== dbVersion) {
217
+ return { ok: false, response: unauthorized() };
218
+ }
219
+ // 5. Permission check (if requested).
220
+ if (options.permission && !hasPermission(admin.role, options.permission)) {
221
+ return { ok: false, response: forbidden() };
222
+ }
223
+ // 6. Min-role check (if requested).
224
+ if (options.minRole && !meetsMinRole(admin.role, options.minRole)) {
225
+ return { ok: false, response: forbidden() };
226
+ }
227
+ return { ok: true, admin };
228
+ }
229
+ //# sourceMappingURL=admin-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-session.js","sourceRoot":"","sources":["../../../src/admin/server/admin-session.ts"],"names":[],"mappings":"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"}