@spinajs/rbac-http-user 2.0.486 → 2.0.488

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 (239) hide show
  1. package/lib/cjs/config/rbac-http.d.ts +0 -15
  2. package/lib/cjs/config/rbac-http.d.ts.map +1 -1
  3. package/lib/cjs/config/rbac-http.js +6 -16
  4. package/lib/cjs/config/rbac-http.js.map +1 -1
  5. package/lib/cjs/controllers/ActiveRoleController.d.ts +2 -1
  6. package/lib/cjs/controllers/ActiveRoleController.d.ts.map +1 -1
  7. package/lib/cjs/controllers/ActiveRoleController.js +7 -17
  8. package/lib/cjs/controllers/ActiveRoleController.js.map +1 -1
  9. package/lib/cjs/controllers/ImpersonationController.d.ts +7 -9
  10. package/lib/cjs/controllers/ImpersonationController.d.ts.map +1 -1
  11. package/lib/cjs/controllers/ImpersonationController.js +31 -56
  12. package/lib/cjs/controllers/ImpersonationController.js.map +1 -1
  13. package/lib/cjs/controllers/LoginController.d.ts +10 -3
  14. package/lib/cjs/controllers/LoginController.d.ts.map +1 -1
  15. package/lib/cjs/controllers/LoginController.js +39 -40
  16. package/lib/cjs/controllers/LoginController.js.map +1 -1
  17. package/lib/cjs/controllers/PasswordResetController.d.ts +54 -0
  18. package/lib/cjs/controllers/PasswordResetController.d.ts.map +1 -0
  19. package/lib/cjs/controllers/PasswordResetController.js +129 -0
  20. package/lib/cjs/controllers/PasswordResetController.js.map +1 -0
  21. package/lib/cjs/controllers/SessionsController.d.ts +65 -0
  22. package/lib/cjs/controllers/SessionsController.d.ts.map +1 -0
  23. package/lib/cjs/controllers/SessionsController.js +143 -0
  24. package/lib/cjs/controllers/SessionsController.js.map +1 -0
  25. package/lib/cjs/controllers/TwoFactorAuthController.d.ts +24 -23
  26. package/lib/cjs/controllers/TwoFactorAuthController.d.ts.map +1 -1
  27. package/lib/cjs/controllers/TwoFactorAuthController.js +39 -72
  28. package/lib/cjs/controllers/TwoFactorAuthController.js.map +1 -1
  29. package/lib/cjs/controllers/TwoFactorAuthUserController.d.ts +98 -0
  30. package/lib/cjs/controllers/TwoFactorAuthUserController.d.ts.map +1 -0
  31. package/lib/cjs/controllers/TwoFactorAuthUserController.js +195 -0
  32. package/lib/cjs/controllers/TwoFactorAuthUserController.js.map +1 -0
  33. package/lib/cjs/controllers/UserController.d.ts +10 -7
  34. package/lib/cjs/controllers/UserController.d.ts.map +1 -1
  35. package/lib/cjs/controllers/UserController.js +56 -58
  36. package/lib/cjs/controllers/UserController.js.map +1 -1
  37. package/lib/cjs/controllers/UserMetadataController.d.ts +6 -12
  38. package/lib/cjs/controllers/UserMetadataController.d.ts.map +1 -1
  39. package/lib/cjs/controllers/UserMetadataController.js +23 -72
  40. package/lib/cjs/controllers/UserMetadataController.js.map +1 -1
  41. package/lib/cjs/dto/confirm-password-dto.d.ts +23 -0
  42. package/lib/cjs/dto/confirm-password-dto.d.ts.map +1 -0
  43. package/lib/cjs/dto/{restore-password-dto.js → confirm-password-dto.js} +16 -12
  44. package/lib/cjs/dto/confirm-password-dto.js.map +1 -0
  45. package/lib/cjs/dto/password-reset-dto.d.ts +58 -0
  46. package/lib/cjs/dto/password-reset-dto.d.ts.map +1 -0
  47. package/lib/cjs/dto/password-reset-dto.js +57 -0
  48. package/lib/cjs/dto/password-reset-dto.js.map +1 -0
  49. package/lib/cjs/handlers/DefaultLogoutHandler.d.ts +4 -1
  50. package/lib/cjs/handlers/DefaultLogoutHandler.d.ts.map +1 -1
  51. package/lib/cjs/handlers/DefaultLogoutHandler.js +20 -13
  52. package/lib/cjs/handlers/DefaultLogoutHandler.js.map +1 -1
  53. package/lib/cjs/handlers/ImpersonationLogoutHandler.d.ts +4 -7
  54. package/lib/cjs/handlers/ImpersonationLogoutHandler.d.ts.map +1 -1
  55. package/lib/cjs/handlers/ImpersonationLogoutHandler.js +30 -31
  56. package/lib/cjs/handlers/ImpersonationLogoutHandler.js.map +1 -1
  57. package/lib/cjs/index.d.ts +20 -0
  58. package/lib/cjs/index.d.ts.map +1 -1
  59. package/lib/cjs/index.js +22 -0
  60. package/lib/cjs/index.js.map +1 -1
  61. package/lib/cjs/logout.d.ts +7 -0
  62. package/lib/cjs/logout.d.ts.map +1 -1
  63. package/lib/cjs/logout.js.map +1 -1
  64. package/lib/cjs/policies/2FaPolicy.d.ts +21 -1
  65. package/lib/cjs/policies/2FaPolicy.d.ts.map +1 -1
  66. package/lib/cjs/policies/2FaPolicy.js +31 -7
  67. package/lib/cjs/policies/2FaPolicy.js.map +1 -1
  68. package/lib/cjs/services/ImpersonationService.d.ts +90 -0
  69. package/lib/cjs/services/ImpersonationService.d.ts.map +1 -0
  70. package/lib/cjs/services/ImpersonationService.js +147 -0
  71. package/lib/cjs/services/ImpersonationService.js.map +1 -0
  72. package/lib/cjs/services/SessionCookies.d.ts +41 -0
  73. package/lib/cjs/services/SessionCookies.d.ts.map +1 -0
  74. package/lib/cjs/services/SessionCookies.js +62 -0
  75. package/lib/cjs/services/SessionCookies.js.map +1 -0
  76. package/lib/cjs/services/UserMetadataService.d.ts +53 -0
  77. package/lib/cjs/services/UserMetadataService.d.ts.map +1 -0
  78. package/lib/cjs/services/UserMetadataService.js +94 -0
  79. package/lib/cjs/services/UserMetadataService.js.map +1 -0
  80. package/lib/cjs/services/grants.d.ts +29 -0
  81. package/lib/cjs/services/grants.d.ts.map +1 -0
  82. package/lib/cjs/services/grants.js +43 -0
  83. package/lib/cjs/services/grants.js.map +1 -0
  84. package/lib/mjs/config/rbac-http.d.ts +0 -15
  85. package/lib/mjs/config/rbac-http.d.ts.map +1 -1
  86. package/lib/mjs/config/rbac-http.js +6 -16
  87. package/lib/mjs/config/rbac-http.js.map +1 -1
  88. package/lib/mjs/controllers/ActiveRoleController.d.ts +2 -1
  89. package/lib/mjs/controllers/ActiveRoleController.d.ts.map +1 -1
  90. package/lib/mjs/controllers/ActiveRoleController.js +8 -18
  91. package/lib/mjs/controllers/ActiveRoleController.js.map +1 -1
  92. package/lib/mjs/controllers/ImpersonationController.d.ts +7 -9
  93. package/lib/mjs/controllers/ImpersonationController.d.ts.map +1 -1
  94. package/lib/mjs/controllers/ImpersonationController.js +32 -57
  95. package/lib/mjs/controllers/ImpersonationController.js.map +1 -1
  96. package/lib/mjs/controllers/LoginController.d.ts +10 -3
  97. package/lib/mjs/controllers/LoginController.d.ts.map +1 -1
  98. package/lib/mjs/controllers/LoginController.js +42 -43
  99. package/lib/mjs/controllers/LoginController.js.map +1 -1
  100. package/lib/mjs/controllers/PasswordResetController.d.ts +54 -0
  101. package/lib/mjs/controllers/PasswordResetController.d.ts.map +1 -0
  102. package/lib/mjs/controllers/PasswordResetController.js +126 -0
  103. package/lib/mjs/controllers/PasswordResetController.js.map +1 -0
  104. package/lib/mjs/controllers/SessionsController.d.ts +65 -0
  105. package/lib/mjs/controllers/SessionsController.d.ts.map +1 -0
  106. package/lib/mjs/controllers/SessionsController.js +140 -0
  107. package/lib/mjs/controllers/SessionsController.js.map +1 -0
  108. package/lib/mjs/controllers/TwoFactorAuthController.d.ts +24 -23
  109. package/lib/mjs/controllers/TwoFactorAuthController.d.ts.map +1 -1
  110. package/lib/mjs/controllers/TwoFactorAuthController.js +43 -76
  111. package/lib/mjs/controllers/TwoFactorAuthController.js.map +1 -1
  112. package/lib/mjs/controllers/TwoFactorAuthUserController.d.ts +98 -0
  113. package/lib/mjs/controllers/TwoFactorAuthUserController.d.ts.map +1 -0
  114. package/lib/mjs/controllers/TwoFactorAuthUserController.js +192 -0
  115. package/lib/mjs/controllers/TwoFactorAuthUserController.js.map +1 -0
  116. package/lib/mjs/controllers/UserController.d.ts +10 -7
  117. package/lib/mjs/controllers/UserController.d.ts.map +1 -1
  118. package/lib/mjs/controllers/UserController.js +59 -28
  119. package/lib/mjs/controllers/UserController.js.map +1 -1
  120. package/lib/mjs/controllers/UserMetadataController.d.ts +6 -12
  121. package/lib/mjs/controllers/UserMetadataController.d.ts.map +1 -1
  122. package/lib/mjs/controllers/UserMetadataController.js +23 -72
  123. package/lib/mjs/controllers/UserMetadataController.js.map +1 -1
  124. package/lib/mjs/dto/confirm-password-dto.d.ts +23 -0
  125. package/lib/mjs/dto/confirm-password-dto.d.ts.map +1 -0
  126. package/lib/mjs/dto/{user-dto.js → confirm-password-dto.js} +15 -10
  127. package/lib/mjs/dto/confirm-password-dto.js.map +1 -0
  128. package/lib/mjs/dto/password-reset-dto.d.ts +58 -0
  129. package/lib/mjs/dto/password-reset-dto.d.ts.map +1 -0
  130. package/lib/mjs/dto/password-reset-dto.js +54 -0
  131. package/lib/mjs/dto/password-reset-dto.js.map +1 -0
  132. package/lib/mjs/handlers/DefaultLogoutHandler.d.ts +4 -1
  133. package/lib/mjs/handlers/DefaultLogoutHandler.d.ts.map +1 -1
  134. package/lib/mjs/handlers/DefaultLogoutHandler.js +23 -16
  135. package/lib/mjs/handlers/DefaultLogoutHandler.js.map +1 -1
  136. package/lib/mjs/handlers/ImpersonationLogoutHandler.d.ts +4 -7
  137. package/lib/mjs/handlers/ImpersonationLogoutHandler.d.ts.map +1 -1
  138. package/lib/mjs/handlers/ImpersonationLogoutHandler.js +31 -32
  139. package/lib/mjs/handlers/ImpersonationLogoutHandler.js.map +1 -1
  140. package/lib/mjs/index.d.ts +20 -0
  141. package/lib/mjs/index.d.ts.map +1 -1
  142. package/lib/mjs/index.js +22 -0
  143. package/lib/mjs/index.js.map +1 -1
  144. package/lib/mjs/logout.d.ts +7 -0
  145. package/lib/mjs/logout.d.ts.map +1 -1
  146. package/lib/mjs/logout.js.map +1 -1
  147. package/lib/mjs/policies/2FaPolicy.d.ts +21 -1
  148. package/lib/mjs/policies/2FaPolicy.d.ts.map +1 -1
  149. package/lib/mjs/policies/2FaPolicy.js +29 -6
  150. package/lib/mjs/policies/2FaPolicy.js.map +1 -1
  151. package/lib/mjs/services/ImpersonationService.d.ts +90 -0
  152. package/lib/mjs/services/ImpersonationService.d.ts.map +1 -0
  153. package/lib/mjs/services/ImpersonationService.js +144 -0
  154. package/lib/mjs/services/ImpersonationService.js.map +1 -0
  155. package/lib/mjs/services/SessionCookies.d.ts +41 -0
  156. package/lib/mjs/services/SessionCookies.d.ts.map +1 -0
  157. package/lib/mjs/services/SessionCookies.js +59 -0
  158. package/lib/mjs/services/SessionCookies.js.map +1 -0
  159. package/lib/mjs/services/UserMetadataService.d.ts +53 -0
  160. package/lib/mjs/services/UserMetadataService.d.ts.map +1 -0
  161. package/lib/mjs/services/UserMetadataService.js +91 -0
  162. package/lib/mjs/services/UserMetadataService.js.map +1 -0
  163. package/lib/mjs/services/grants.d.ts +29 -0
  164. package/lib/mjs/services/grants.d.ts.map +1 -0
  165. package/lib/mjs/services/grants.js +38 -0
  166. package/lib/mjs/services/grants.js.map +1 -0
  167. package/lib/tsconfig.cjs.tsbuildinfo +1 -1
  168. package/lib/tsconfig.mjs.tsbuildinfo +1 -1
  169. package/package.json +17 -11
  170. package/lib/cjs/controllers/AuthController.d.ts +0 -1
  171. package/lib/cjs/controllers/AuthController.d.ts.map +0 -1
  172. package/lib/cjs/controllers/AuthController.js +0 -211
  173. package/lib/cjs/controllers/AuthController.js.map +0 -1
  174. package/lib/cjs/controllers/FederatedLoginController.d.ts +0 -1
  175. package/lib/cjs/controllers/FederatedLoginController.d.ts.map +0 -1
  176. package/lib/cjs/controllers/FederatedLoginController.js +0 -38
  177. package/lib/cjs/controllers/FederatedLoginController.js.map +0 -1
  178. package/lib/cjs/dto/login-dto.d.ts +0 -17
  179. package/lib/cjs/dto/login-dto.d.ts.map +0 -1
  180. package/lib/cjs/dto/login-dto.js +0 -33
  181. package/lib/cjs/dto/login-dto.js.map +0 -1
  182. package/lib/cjs/dto/restore-password-dto.d.ts +0 -24
  183. package/lib/cjs/dto/restore-password-dto.d.ts.map +0 -1
  184. package/lib/cjs/dto/restore-password-dto.js.map +0 -1
  185. package/lib/cjs/dto/user-dto.d.ts +0 -22
  186. package/lib/cjs/dto/user-dto.d.ts.map +0 -1
  187. package/lib/cjs/dto/user-dto.js +0 -33
  188. package/lib/cjs/dto/user-dto.js.map +0 -1
  189. package/lib/cjs/fingerprint/FingerprintJs.d.ts +0 -1
  190. package/lib/cjs/fingerprint/FingerprintJs.d.ts.map +0 -1
  191. package/lib/cjs/fingerprint/FingerprintJs.js +0 -5
  192. package/lib/cjs/fingerprint/FingerprintJs.js.map +0 -1
  193. package/lib/cjs/middlewares/AttributeFilter.d.ts +0 -11
  194. package/lib/cjs/middlewares/AttributeFilter.d.ts.map +0 -1
  195. package/lib/cjs/middlewares/AttributeFilter.js +0 -19
  196. package/lib/cjs/middlewares/AttributeFilter.js.map +0 -1
  197. package/lib/cjs/policies/AllowFederatedLoginPolicy.d.ts +0 -7
  198. package/lib/cjs/policies/AllowFederatedLoginPolicy.d.ts.map +0 -1
  199. package/lib/cjs/policies/AllowFederatedLoginPolicy.js +0 -32
  200. package/lib/cjs/policies/AllowFederatedLoginPolicy.js.map +0 -1
  201. package/lib/cjs/policies/CaptchaPolicy.d.ts +0 -1
  202. package/lib/cjs/policies/CaptchaPolicy.d.ts.map +0 -1
  203. package/lib/cjs/policies/CaptchaPolicy.js +0 -1
  204. package/lib/cjs/policies/CaptchaPolicy.js.map +0 -1
  205. package/lib/mjs/controllers/AuthController.d.ts +0 -2
  206. package/lib/mjs/controllers/AuthController.d.ts.map +0 -1
  207. package/lib/mjs/controllers/AuthController.js +0 -212
  208. package/lib/mjs/controllers/AuthController.js.map +0 -1
  209. package/lib/mjs/controllers/FederatedLoginController.d.ts +0 -2
  210. package/lib/mjs/controllers/FederatedLoginController.d.ts.map +0 -1
  211. package/lib/mjs/controllers/FederatedLoginController.js +0 -39
  212. package/lib/mjs/controllers/FederatedLoginController.js.map +0 -1
  213. package/lib/mjs/dto/login-dto.d.ts +0 -17
  214. package/lib/mjs/dto/login-dto.d.ts.map +0 -1
  215. package/lib/mjs/dto/login-dto.js +0 -30
  216. package/lib/mjs/dto/login-dto.js.map +0 -1
  217. package/lib/mjs/dto/restore-password-dto.d.ts +0 -24
  218. package/lib/mjs/dto/restore-password-dto.d.ts.map +0 -1
  219. package/lib/mjs/dto/restore-password-dto.js +0 -31
  220. package/lib/mjs/dto/restore-password-dto.js.map +0 -1
  221. package/lib/mjs/dto/user-dto.d.ts +0 -22
  222. package/lib/mjs/dto/user-dto.d.ts.map +0 -1
  223. package/lib/mjs/dto/user-dto.js.map +0 -1
  224. package/lib/mjs/fingerprint/FingerprintJs.d.ts +0 -2
  225. package/lib/mjs/fingerprint/FingerprintJs.d.ts.map +0 -1
  226. package/lib/mjs/fingerprint/FingerprintJs.js +0 -6
  227. package/lib/mjs/fingerprint/FingerprintJs.js.map +0 -1
  228. package/lib/mjs/middlewares/AttributeFilter.d.ts +0 -11
  229. package/lib/mjs/middlewares/AttributeFilter.d.ts.map +0 -1
  230. package/lib/mjs/middlewares/AttributeFilter.js +0 -15
  231. package/lib/mjs/middlewares/AttributeFilter.js.map +0 -1
  232. package/lib/mjs/policies/AllowFederatedLoginPolicy.d.ts +0 -7
  233. package/lib/mjs/policies/AllowFederatedLoginPolicy.d.ts.map +0 -1
  234. package/lib/mjs/policies/AllowFederatedLoginPolicy.js +0 -28
  235. package/lib/mjs/policies/AllowFederatedLoginPolicy.js.map +0 -1
  236. package/lib/mjs/policies/CaptchaPolicy.d.ts +0 -2
  237. package/lib/mjs/policies/CaptchaPolicy.d.ts.map +0 -1
  238. package/lib/mjs/policies/CaptchaPolicy.js +0 -2
  239. package/lib/mjs/policies/CaptchaPolicy.js.map +0 -1
@@ -0,0 +1,144 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Injectable } from '@spinajs/di';
11
+ import { AutoinjectService } from '@spinajs/configuration';
12
+ import { SessionProvider, User, UserImpersonationEnded, regenerateSession } from '@spinajs/rbac';
13
+ import { _ev } from '@spinajs/queue';
14
+ /** Session keys holding the impersonation block. */
15
+ export const IMPERSONATION_KEYS = {
16
+ IMPERSONATOR: 'Impersonator',
17
+ STARTED_AT: 'ImpersonationStartedAt',
18
+ ORIGINAL_ACTIVE_ROLE: 'OriginalActiveRole',
19
+ /**
20
+ * Numeric id of the impersonator, stashed because `session.UserId` is handed
21
+ * over to the target for the duration — see {@link ImpersonationService.start}.
22
+ */
23
+ ORIGINAL_USER_ID: 'OriginalUserId',
24
+ };
25
+ /**
26
+ * Owns the impersonation session block.
27
+ *
28
+ * The revert path is reachable two ways — explicitly via `DELETE /auth/impersonate`
29
+ * and implicitly when an impersonating user logs out — and both used to carry
30
+ * their own copy of the key juggling. The copies had already drifted: the logout
31
+ * handler dereferenced a deleted impersonator without a guard and answered a
32
+ * logout with a 500, while the controller handled that same case.
33
+ */
34
+ let ImpersonationService = class ImpersonationService {
35
+ /** True when the session is currently acting as somebody else. */
36
+ isActive(session) {
37
+ return Boolean(session?.Data.get(IMPERSONATION_KEYS.IMPERSONATOR));
38
+ }
39
+ /**
40
+ * Begin acting as `target`. The impersonator's own ActiveRole is stashed so
41
+ * `revert` can restore it, and the effective ActiveRole becomes the target's
42
+ * first role.
43
+ *
44
+ * Two things beyond the key juggling:
45
+ *
46
+ * - the session id is REGENERATED. Assuming another identity is a privilege
47
+ * change, and a privilege change on a session id that an attacker may
48
+ * already know is the definition of session fixation.
49
+ * - `session.UserId` is handed over to the target and the impersonator's own
50
+ * id is stashed. Ownership is what `deleteByUser` keys on, so while the
51
+ * impersonation runs, "log this user out everywhere" aimed at the target
52
+ * reaches the impersonating session too — which is exactly what an
53
+ * administrator killing a compromised account expects.
54
+ *
55
+ * @returns the regenerated session and the ActiveRole now in effect
56
+ */
57
+ async start(session, impersonator, target, startedAt) {
58
+ const previousActiveRole = session.Data.get('ActiveRole');
59
+ session.Data.set(IMPERSONATION_KEYS.IMPERSONATOR, impersonator.Uuid);
60
+ session.Data.set(IMPERSONATION_KEYS.ORIGINAL_USER_ID, session.UserId ?? impersonator.Id);
61
+ session.Data.set('User', target.Uuid);
62
+ session.Data.set(IMPERSONATION_KEYS.STARTED_AT, startedAt);
63
+ if (previousActiveRole !== undefined) {
64
+ session.Data.set(IMPERSONATION_KEYS.ORIGINAL_ACTIVE_ROLE, previousActiveRole);
65
+ }
66
+ const targetActiveRole = target.Role?.[0];
67
+ if (targetActiveRole) {
68
+ session.Data.set('ActiveRole', targetActiveRole);
69
+ }
70
+ session.UserId = target.Id;
71
+ const regenerated = await regenerateSession(this.SessionProvider, session);
72
+ return { Session: regenerated, ActiveRole: targetActiveRole };
73
+ }
74
+ /**
75
+ * End an active impersonation and put the original user back in the session.
76
+ *
77
+ * The session survives either way — reverting is not a logout. When the
78
+ * original user is gone the impersonation block is still cleared, so the
79
+ * session stops claiming an identity that cannot be resolved, and the caller
80
+ * is told to re-authenticate rather than being handed a 500.
81
+ */
82
+ async revert(session, target) {
83
+ if (!session) {
84
+ return { Status: 'not-impersonating' };
85
+ }
86
+ const impersonatorUuid = session.Data.get(IMPERSONATION_KEYS.IMPERSONATOR);
87
+ if (!impersonatorUuid) {
88
+ return { Status: 'not-impersonating' };
89
+ }
90
+ const original = await this.loadOriginal(impersonatorUuid);
91
+ if (!original) {
92
+ await this.clearBlock(session);
93
+ return { Status: 'impersonator-gone' };
94
+ }
95
+ session.Data.set('User', original.Uuid);
96
+ const restoredActiveRole = session.Data.get(IMPERSONATION_KEYS.ORIGINAL_ACTIVE_ROLE) ?? original.Role?.[0];
97
+ if (restoredActiveRole) {
98
+ session.Data.set('ActiveRole', restoredActiveRole);
99
+ }
100
+ // ownership goes back to the impersonator along with the identity
101
+ session.UserId = session.Data.get(IMPERSONATION_KEYS.ORIGINAL_USER_ID) ?? original.Id;
102
+ this.dropBlockKeys(session);
103
+ // Dropping an identity is a privilege change too — the id the target's
104
+ // session was reachable under does not survive into the impersonator's.
105
+ const regenerated = await regenerateSession(this.SessionProvider, session);
106
+ await this.emitEnded(original, target);
107
+ return { Status: 'reverted', Original: original, ActiveRole: restoredActiveRole, Session: regenerated };
108
+ }
109
+ /** Drops the impersonation keys and persists the session under the same id. */
110
+ async clearBlock(session) {
111
+ this.dropBlockKeys(session);
112
+ await this.SessionProvider.save(session);
113
+ }
114
+ /** Removes the impersonation keys from session data without persisting. */
115
+ dropBlockKeys(session) {
116
+ session.Data.delete(IMPERSONATION_KEYS.IMPERSONATOR);
117
+ session.Data.delete(IMPERSONATION_KEYS.STARTED_AT);
118
+ session.Data.delete(IMPERSONATION_KEYS.ORIGINAL_ACTIVE_ROLE);
119
+ session.Data.delete(IMPERSONATION_KEYS.ORIGINAL_USER_ID);
120
+ }
121
+ /**
122
+ * Load the original (impersonator) user, or undefined when the account is
123
+ * gone. Extracted so tests can stub it without a database.
124
+ */
125
+ async loadOriginal(uuid) {
126
+ return (await User.getByUuid(uuid));
127
+ }
128
+ /**
129
+ * Hook for tests to intercept event emission without stubbing the
130
+ * module-level `_ev` ESM binding.
131
+ */
132
+ emitEnded(original, target) {
133
+ return _ev(new UserImpersonationEnded(original, target))();
134
+ }
135
+ };
136
+ __decorate([
137
+ AutoinjectService('rbac.session'),
138
+ __metadata("design:type", SessionProvider)
139
+ ], ImpersonationService.prototype, "SessionProvider", void 0);
140
+ ImpersonationService = __decorate([
141
+ Injectable()
142
+ ], ImpersonationService);
143
+ export { ImpersonationService };
144
+ //# sourceMappingURL=ImpersonationService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImpersonationService.js","sourceRoot":"","sources":["../../../src/services/ImpersonationService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { ISession, ISessionCookieConfig } from '@spinajs/rbac';
2
+ /** Cookie descriptor accepted by the http `Coockies` response option. */
3
+ export interface ISessionCookie {
4
+ Name: string;
5
+ Value: string;
6
+ Options: Record<string, unknown>;
7
+ }
8
+ /**
9
+ * Default name of the session cookie. Kept for callers that only need the
10
+ * literal; the ACTIVE name comes from configuration and may carry a `__Host-`
11
+ * prefix, so read {@link SessionCookieFactory.Name} instead of assuming this.
12
+ */
13
+ export declare const SESSION_COOKIE_NAME = "ssid";
14
+ /**
15
+ * Builds the session cookie.
16
+ *
17
+ * Every place that opens, rotates, or clears a session used to inline the same
18
+ * literal — login, 2FA verify, active-role switch and logout each carried their
19
+ * own copy, and each had to remember `signed`, `httpOnly`, the configured
20
+ * overrides, and to derive maxAge from the session's real expiration. A drift in
21
+ * any one of them silently produces a cookie the next request cannot restore.
22
+ *
23
+ * The attributes themselves are decided by `@spinajs/rbac` so the session
24
+ * middleware — which renews the very same cookie on every request, from a
25
+ * package that cannot import this one — produces a byte-identical set.
26
+ */
27
+ export declare class SessionCookieFactory {
28
+ protected SessionCookieConfig: ISessionCookieConfig;
29
+ /** Configured cookie name, `__Host-` prefix included when enabled. */
30
+ get Name(): string;
31
+ /**
32
+ * Cookie carrying an active session. `maxAge` tracks the session's real
33
+ * expiration rather than a fixed constant, so sliding and absolute
34
+ * expiration strategies both produce a cookie that outlives exactly as long
35
+ * as the session behind it.
36
+ */
37
+ issue(session: ISession): ISessionCookie;
38
+ /** Same cookie, expired — clears the session client-side on logout. */
39
+ clear(): ISessionCookie;
40
+ }
41
+ //# sourceMappingURL=SessionCookies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionCookies.d.ts","sourceRoot":"","sources":["../../../src/services/SessionCookies.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAGpE,yEAAyE;AACzE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,SAAS,CAAC;AAE1C;;;;;;;;;;;;GAYG;AACH,qBACa,oBAAoB;IAE/B,SAAS,CAAC,mBAAmB,EAAE,oBAAoB,CAAC;IAEpD,sEAAsE;IACtE,IAAW,IAAI,IAAI,MAAM,CAExB;IAED;;;;;OAKG;IACI,KAAK,CAAC,OAAO,EAAE,QAAQ,GAAG,cAAc;IAI/C,uEAAuE;IAChE,KAAK,IAAI,cAAc;CAG/B"}
@@ -0,0 +1,59 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Config } from '@spinajs/configuration';
11
+ import { Injectable } from '@spinajs/di';
12
+ import { clearSessionCookie, sessionCookie, sessionCookieName } from '@spinajs/rbac';
13
+ /**
14
+ * Default name of the session cookie. Kept for callers that only need the
15
+ * literal; the ACTIVE name comes from configuration and may carry a `__Host-`
16
+ * prefix, so read {@link SessionCookieFactory.Name} instead of assuming this.
17
+ */
18
+ export const SESSION_COOKIE_NAME = 'ssid';
19
+ /**
20
+ * Builds the session cookie.
21
+ *
22
+ * Every place that opens, rotates, or clears a session used to inline the same
23
+ * literal — login, 2FA verify, active-role switch and logout each carried their
24
+ * own copy, and each had to remember `signed`, `httpOnly`, the configured
25
+ * overrides, and to derive maxAge from the session's real expiration. A drift in
26
+ * any one of them silently produces a cookie the next request cannot restore.
27
+ *
28
+ * The attributes themselves are decided by `@spinajs/rbac` so the session
29
+ * middleware — which renews the very same cookie on every request, from a
30
+ * package that cannot import this one — produces a byte-identical set.
31
+ */
32
+ let SessionCookieFactory = class SessionCookieFactory {
33
+ /** Configured cookie name, `__Host-` prefix included when enabled. */
34
+ get Name() {
35
+ return sessionCookieName(this.SessionCookieConfig);
36
+ }
37
+ /**
38
+ * Cookie carrying an active session. `maxAge` tracks the session's real
39
+ * expiration rather than a fixed constant, so sliding and absolute
40
+ * expiration strategies both produce a cookie that outlives exactly as long
41
+ * as the session behind it.
42
+ */
43
+ issue(session) {
44
+ return sessionCookie(session, this.SessionCookieConfig);
45
+ }
46
+ /** Same cookie, expired — clears the session client-side on logout. */
47
+ clear() {
48
+ return clearSessionCookie(this.SessionCookieConfig);
49
+ }
50
+ };
51
+ __decorate([
52
+ Config('rbac.session.cookie', {}),
53
+ __metadata("design:type", Object)
54
+ ], SessionCookieFactory.prototype, "SessionCookieConfig", void 0);
55
+ SessionCookieFactory = __decorate([
56
+ Injectable()
57
+ ], SessionCookieFactory);
58
+ export { SessionCookieFactory };
59
+ //# sourceMappingURL=SessionCookies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionCookies.js","sourceRoot":"","sources":["../../../src/services/SessionCookies.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AASrF;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAE1C;;;;;;;;;;;;GAYG;AAEI,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IAI/B,sEAAsE;IACtE,IAAW,IAAI;QACb,OAAO,iBAAiB,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACrD,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,OAAiB;QAC5B,OAAO,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAC1D,CAAC;IAED,uEAAuE;IAChE,KAAK;QACV,OAAO,kBAAkB,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACtD,CAAC;CACF,CAAA;AArBW;IADT,MAAM,CAAC,qBAAqB,EAAE,EAAE,CAAC;;iEACkB;AAFzC,oBAAoB;IADhC,UAAU,EAAE;GACA,oBAAoB,CAuBhC"}
@@ -0,0 +1,53 @@
1
+ import { UserMetadata } from '@spinajs/rbac';
2
+ import type { IFilterRequest, OrderDTO, PaginationDTO } from '@spinajs/orm-http';
3
+ import { FilterableUserMetadata } from '../models/FilterableUserMetadata.js';
4
+ import type { UserMetadataDto } from '../dto/metadata-dto.js';
5
+ /**
6
+ * Fallback page size. A request without a `limit` used to reach `take(0)`,
7
+ * which the query builder rejects ( "take count should be a positive number" ),
8
+ * so plain listing requests failed instead of returning the first page.
9
+ */
10
+ export declare const DEFAULT_PAGE_SIZE = 10;
11
+ /**
12
+ * User metadata access, always scoped to one owner.
13
+ *
14
+ * The controller exposes each operation twice — once for the caller's own
15
+ * metadata and once for an administrator addressing a user by UUID — and the
16
+ * two differed only in where the owner id came from. Keeping the queries here
17
+ * means the ownership predicate is written once.
18
+ *
19
+ * Ownership is filtered explicitly rather than left to the rbac query
20
+ * middleware: metadata queries resolve to the unsafe base model, so no
21
+ * automatic owner scoping fires for them.
22
+ */
23
+ export declare class UserMetadataService {
24
+ /** Paginated, filtered, ordered listing of one user's metadata. */
25
+ list(ownerId: number, pagination?: PaginationDTO, order?: OrderDTO, filter?: IFilterRequest): import("@spinajs/orm").ISelectQueryBuilder<FilterableUserMetadata[]> & import("@spinajs/orm").QueryScope;
26
+ /** A single entry of one user, by key. Rejects when absent. */
27
+ getByKey(ownerId: number, key: string): Promise<UserMetadata>;
28
+ /**
29
+ * Insert an entry, or update it when the key already exists.
30
+ *
31
+ * Ownership is forced onto the model — a `user_id` arriving in the request
32
+ * body is never trusted.
33
+ */
34
+ upsert(ownerId: number, metadata: UserMetadata): Promise<void>;
35
+ /**
36
+ * Update an entry addressed by either its Id or its Key.
37
+ *
38
+ * The Key/Id lookup is grouped and AND-ed with the ownership filter. Without
39
+ * the grouping the flat `Key = ? OR Id = ? AND user_id = ?` binds as
40
+ * `Key = ? OR (Id = ? AND user_id = ?)`, which would let any caller update
41
+ * another user's entry by its Key.
42
+ */
43
+ update(ownerId: number, idOrKey: string | number, data: UserMetadataDto): Promise<void>;
44
+ /**
45
+ * Delete an entry by id, scoped to its owner.
46
+ *
47
+ * `destroy()` refuses to build an unbounded DELETE, so the entry id goes in
48
+ * as the primary key and ownership is AND-ed on top of it — deleting by id
49
+ * alone would let any caller remove anybody's entry.
50
+ */
51
+ delete(ownerId: number, id: number): Promise<void>;
52
+ }
53
+ //# sourceMappingURL=UserMetadataService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserMetadataService.d.ts","sourceRoot":"","sources":["../../../src/services/UserMetadataService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACjF,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC;;;;;;;;;;;GAWG;AACH,qBACa,mBAAmB;IAC9B,mEAAmE;IAC5D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,cAAc;IAWlG,+DAA+D;IACxD,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;IAO5C;;;;;OAKG;IACU,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3E;;;;;;;OAOG;IACU,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAYpG;;;;;;OAMG;IACU,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGhE"}
@@ -0,0 +1,91 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Injectable } from '@spinajs/di';
8
+ import { UserMetadata } from '@spinajs/rbac';
9
+ import { InsertBehaviour, SortOrder } from '@spinajs/orm';
10
+ import { FilterableUserMetadata } from '../models/FilterableUserMetadata.js';
11
+ /**
12
+ * Fallback page size. A request without a `limit` used to reach `take(0)`,
13
+ * which the query builder rejects ( "take count should be a positive number" ),
14
+ * so plain listing requests failed instead of returning the first page.
15
+ */
16
+ export const DEFAULT_PAGE_SIZE = 10;
17
+ /**
18
+ * User metadata access, always scoped to one owner.
19
+ *
20
+ * The controller exposes each operation twice — once for the caller's own
21
+ * metadata and once for an administrator addressing a user by UUID — and the
22
+ * two differed only in where the owner id came from. Keeping the queries here
23
+ * means the ownership predicate is written once.
24
+ *
25
+ * Ownership is filtered explicitly rather than left to the rbac query
26
+ * middleware: metadata queries resolve to the unsafe base model, so no
27
+ * automatic owner scoping fires for them.
28
+ */
29
+ let UserMetadataService = class UserMetadataService {
30
+ /** Paginated, filtered, ordered listing of one user's metadata. */
31
+ list(ownerId, pagination, order, filter) {
32
+ const limit = pagination?.limit || DEFAULT_PAGE_SIZE;
33
+ return FilterableUserMetadata.select()
34
+ .where('user_id', ownerId)
35
+ .filter(filter?.filters ?? [], filter?.op)
36
+ .take(limit)
37
+ .skip(limit * (pagination?.page ?? 0))
38
+ .order(order?.column ?? 'Id', order?.order ?? SortOrder.DESC);
39
+ }
40
+ /** A single entry of one user, by key. Rejects when absent. */
41
+ getByKey(ownerId, key) {
42
+ return UserMetadata.where({
43
+ Key: key,
44
+ user_id: ownerId,
45
+ }).firstOrFail();
46
+ }
47
+ /**
48
+ * Insert an entry, or update it when the key already exists.
49
+ *
50
+ * Ownership is forced onto the model — a `user_id` arriving in the request
51
+ * body is never trusted.
52
+ */
53
+ async upsert(ownerId, metadata) {
54
+ metadata.user_id = ownerId;
55
+ await metadata.insert(InsertBehaviour.InsertOrUpdate);
56
+ }
57
+ /**
58
+ * Update an entry addressed by either its Id or its Key.
59
+ *
60
+ * The Key/Id lookup is grouped and AND-ed with the ownership filter. Without
61
+ * the grouping the flat `Key = ? OR Id = ? AND user_id = ?` binds as
62
+ * `Key = ? OR (Id = ? AND user_id = ?)`, which would let any caller update
63
+ * another user's entry by its Key.
64
+ */
65
+ async update(ownerId, idOrKey, data) {
66
+ await UserMetadata.update({
67
+ Key: data.Key,
68
+ Value: data.Value,
69
+ Type: data.Type,
70
+ })
71
+ .where(function () {
72
+ this.where('Key', idOrKey).orWhere('Id', idOrKey);
73
+ })
74
+ .andWhere('user_id', ownerId);
75
+ }
76
+ /**
77
+ * Delete an entry by id, scoped to its owner.
78
+ *
79
+ * `destroy()` refuses to build an unbounded DELETE, so the entry id goes in
80
+ * as the primary key and ownership is AND-ed on top of it — deleting by id
81
+ * alone would let any caller remove anybody's entry.
82
+ */
83
+ async delete(ownerId, id) {
84
+ await UserMetadata.destroy(id).andWhere('user_id', ownerId);
85
+ }
86
+ };
87
+ UserMetadataService = __decorate([
88
+ Injectable()
89
+ ], UserMetadataService);
90
+ export { UserMetadataService };
91
+ //# sourceMappingURL=UserMetadataService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserMetadataService.js","sourceRoot":"","sources":["../../../src/services/UserMetadataService.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAG7E;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAEpC;;;;;;;;;;;GAWG;AAEI,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAC9B,mEAAmE;IAC5D,IAAI,CAAC,OAAe,EAAE,UAA0B,EAAE,KAAgB,EAAE,MAAuB;QAChG,MAAM,KAAK,GAAG,UAAU,EAAE,KAAK,IAAI,iBAAiB,CAAC;QAErD,OAAO,sBAAsB,CAAC,MAAM,EAAE;aACnC,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC;aACzB,MAAM,CAAC,MAAM,EAAE,OAAO,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC;aACzC,IAAI,CAAC,KAAK,CAAC;aACX,IAAI,CAAC,KAAK,GAAG,CAAC,UAAU,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC;aACrC,KAAK,CAAC,KAAK,EAAE,MAAM,IAAI,IAAI,EAAE,KAAK,EAAE,KAAK,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC;IAClE,CAAC;IAED,+DAA+D;IACxD,QAAQ,CAAC,OAAe,EAAE,GAAW;QAC1C,OAAO,YAAY,CAAC,KAAK,CAAC;YACxB,GAAG,EAAE,GAAG;YACR,OAAO,EAAE,OAAO;SACjB,CAAC,CAAC,WAAW,EAAE,CAAC;IACnB,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,MAAM,CAAC,OAAe,EAAE,QAAsB;QACzD,QAAQ,CAAC,OAAO,GAAG,OAAO,CAAC;QAC3B,MAAM,QAAQ,CAAC,MAAM,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC;IACxD,CAAC;IAED;;;;;;;OAOG;IACI,KAAK,CAAC,MAAM,CAAC,OAAe,EAAE,OAAwB,EAAE,IAAqB;QAClF,MAAM,YAAY,CAAC,MAAM,CAAC;YACxB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,IAAI,EAAE,IAAI,CAAC,IAAI;SAChB,CAAC;aACC,KAAK,CAAC;YACL,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACpD,CAAC,CAAC;aACD,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAClC,CAAC;IAED;;;;;;OAMG;IACI,KAAK,CAAC,MAAM,CAAC,OAAe,EAAE,EAAU;QAC7C,MAAM,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC9D,CAAC;CACF,CAAA;AA9DY,mBAAmB;IAD/B,UAAU,EAAE;GACA,mBAAmB,CA8D/B"}
@@ -0,0 +1,29 @@
1
+ import { AccessControl, User } from '@spinajs/rbac';
2
+ import type { ISession } from '@spinajs/rbac';
3
+ import type { IGrantsMap, IUserWithGrants } from '@spinajs/rbac-http';
4
+ /**
5
+ * Role whose grants are in effect for a session.
6
+ *
7
+ * Enforcement (see the rbac http middleware) resolves permissions from the
8
+ * session's ActiveRole and falls back to the first assigned role, so anything
9
+ * reported to a client has to resolve it the same way — otherwise the client is
10
+ * told about actions the server will refuse.
11
+ */
12
+ export declare function activeRoleOf(user: User, session?: ISession | null): string | undefined;
13
+ /**
14
+ * Grants in effect for a single role, flattened through its `$extend` chain.
15
+ * An absent role yields an empty map rather than throwing — a user with no role
16
+ * assigned simply has no grants.
17
+ */
18
+ export declare function grantsFor(ac: AccessControl, activeRole: string | undefined): IGrantsMap;
19
+ /**
20
+ * The login-style user payload: the dehydrated user plus their active role and
21
+ * the grants that role resolves to.
22
+ *
23
+ * Login, 2FA verification and ending an impersonation all hand the client the
24
+ * same shape, and all three previously assembled it by hand. `Role` is restored
25
+ * explicitly because `dehydrateWithRelations` flattens the relation to a string
26
+ * while clients expect the array they can switch between.
27
+ */
28
+ export declare function buildUserWithGrants(user: User, activeRole: string | undefined, ac: AccessControl): IUserWithGrants;
29
+ //# sourceMappingURL=grants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grants.d.ts","sourceRoot":"","sources":["../../../src/services/grants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,IAAI,EAAiB,MAAM,eAAe,CAAC;AACnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAEtE;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAEtF;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,CAEvF;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,EAAE,EAAE,aAAa,GAAG,eAAe,CAOlH"}
@@ -0,0 +1,38 @@
1
+ import { _unwindGrants } from '@spinajs/rbac';
2
+ /**
3
+ * Role whose grants are in effect for a session.
4
+ *
5
+ * Enforcement (see the rbac http middleware) resolves permissions from the
6
+ * session's ActiveRole and falls back to the first assigned role, so anything
7
+ * reported to a client has to resolve it the same way — otherwise the client is
8
+ * told about actions the server will refuse.
9
+ */
10
+ export function activeRoleOf(user, session) {
11
+ return session?.Data.get('ActiveRole') ?? user.Role?.[0];
12
+ }
13
+ /**
14
+ * Grants in effect for a single role, flattened through its `$extend` chain.
15
+ * An absent role yields an empty map rather than throwing — a user with no role
16
+ * assigned simply has no grants.
17
+ */
18
+ export function grantsFor(ac, activeRole) {
19
+ return (activeRole ? _unwindGrants(activeRole, ac.getGrants()) : {});
20
+ }
21
+ /**
22
+ * The login-style user payload: the dehydrated user plus their active role and
23
+ * the grants that role resolves to.
24
+ *
25
+ * Login, 2FA verification and ending an impersonation all hand the client the
26
+ * same shape, and all three previously assembled it by hand. `Role` is restored
27
+ * explicitly because `dehydrateWithRelations` flattens the relation to a string
28
+ * while clients expect the array they can switch between.
29
+ */
30
+ export function buildUserWithGrants(user, activeRole, ac) {
31
+ return {
32
+ ...user.dehydrateWithRelations({ dateTimeFormat: 'iso' }),
33
+ Role: user.Role,
34
+ ActiveRole: activeRole,
35
+ Grants: grantsFor(ac, activeRole),
36
+ };
37
+ }
38
+ //# sourceMappingURL=grants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grants.js","sourceRoot":"","sources":["../../../src/services/grants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,aAAa,EAAE,MAAM,eAAe,CAAC;AAInE;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,IAAU,EAAE,OAAyB;IAChE,OAAQ,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,YAAY,CAAwB,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AACnF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,EAAiB,EAAE,UAA8B;IACzE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa,CAAC,UAAU,EAAE,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAe,CAAC;AACrF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAU,EAAE,UAA8B,EAAE,EAAiB;IAC/F,OAAO;QACL,GAAG,IAAI,CAAC,sBAAsB,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;QACzD,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,UAAU,EAAE,UAAU;QACtB,MAAM,EAAE,SAAS,CAAC,EAAE,EAAE,UAAU,CAAC;KACJ,CAAC;AAClC,CAAC"}