@spinajs/rbac-http-user 2.0.501 → 2.0.504

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 (53) hide show
  1. package/lib/cjs/2fa/Default2FaToken.d.ts +8 -0
  2. package/lib/cjs/2fa/Default2FaToken.d.ts.map +1 -1
  3. package/lib/cjs/2fa/Default2FaToken.js +25 -3
  4. package/lib/cjs/2fa/Default2FaToken.js.map +1 -1
  5. package/lib/cjs/actions/2fa.d.ts +22 -3
  6. package/lib/cjs/actions/2fa.d.ts.map +1 -1
  7. package/lib/cjs/actions/2fa.js +44 -3
  8. package/lib/cjs/actions/2fa.js.map +1 -1
  9. package/lib/cjs/config/rbac-http.d.ts.map +1 -1
  10. package/lib/cjs/config/rbac-http.js +6 -0
  11. package/lib/cjs/config/rbac-http.js.map +1 -1
  12. package/lib/cjs/controllers/LoginController.d.ts.map +1 -1
  13. package/lib/cjs/controllers/LoginController.js +10 -1
  14. package/lib/cjs/controllers/LoginController.js.map +1 -1
  15. package/lib/cjs/controllers/TwoFactorAuthController.d.ts +20 -1
  16. package/lib/cjs/controllers/TwoFactorAuthController.d.ts.map +1 -1
  17. package/lib/cjs/controllers/TwoFactorAuthController.js +45 -2
  18. package/lib/cjs/controllers/TwoFactorAuthController.js.map +1 -1
  19. package/lib/cjs/controllers/TwoFactorAuthUserController.d.ts +83 -4
  20. package/lib/cjs/controllers/TwoFactorAuthUserController.d.ts.map +1 -1
  21. package/lib/cjs/controllers/TwoFactorAuthUserController.js +156 -12
  22. package/lib/cjs/controllers/TwoFactorAuthUserController.js.map +1 -1
  23. package/lib/cjs/policies/2FaPolicy.d.ts.map +1 -1
  24. package/lib/cjs/policies/2FaPolicy.js +27 -6
  25. package/lib/cjs/policies/2FaPolicy.js.map +1 -1
  26. package/lib/mjs/2fa/Default2FaToken.d.ts +8 -0
  27. package/lib/mjs/2fa/Default2FaToken.d.ts.map +1 -1
  28. package/lib/mjs/2fa/Default2FaToken.js +25 -3
  29. package/lib/mjs/2fa/Default2FaToken.js.map +1 -1
  30. package/lib/mjs/actions/2fa.d.ts +22 -3
  31. package/lib/mjs/actions/2fa.d.ts.map +1 -1
  32. package/lib/mjs/actions/2fa.js +41 -3
  33. package/lib/mjs/actions/2fa.js.map +1 -1
  34. package/lib/mjs/config/rbac-http.d.ts.map +1 -1
  35. package/lib/mjs/config/rbac-http.js +6 -0
  36. package/lib/mjs/config/rbac-http.js.map +1 -1
  37. package/lib/mjs/controllers/LoginController.d.ts.map +1 -1
  38. package/lib/mjs/controllers/LoginController.js +10 -1
  39. package/lib/mjs/controllers/LoginController.js.map +1 -1
  40. package/lib/mjs/controllers/TwoFactorAuthController.d.ts +20 -1
  41. package/lib/mjs/controllers/TwoFactorAuthController.d.ts.map +1 -1
  42. package/lib/mjs/controllers/TwoFactorAuthController.js +48 -5
  43. package/lib/mjs/controllers/TwoFactorAuthController.js.map +1 -1
  44. package/lib/mjs/controllers/TwoFactorAuthUserController.d.ts +83 -4
  45. package/lib/mjs/controllers/TwoFactorAuthUserController.d.ts.map +1 -1
  46. package/lib/mjs/controllers/TwoFactorAuthUserController.js +160 -16
  47. package/lib/mjs/controllers/TwoFactorAuthUserController.js.map +1 -1
  48. package/lib/mjs/policies/2FaPolicy.d.ts.map +1 -1
  49. package/lib/mjs/policies/2FaPolicy.js +26 -5
  50. package/lib/mjs/policies/2FaPolicy.js.map +1 -1
  51. package/lib/tsconfig.cjs.tsbuildinfo +1 -1
  52. package/lib/tsconfig.mjs.tsbuildinfo +1 -1
  53. package/package.json +15 -15
@@ -63,7 +63,16 @@ let LoginController = class LoginController extends BaseController {
63
63
  session.Data.set('Logged', true);
64
64
  session.UserId = user.Id;
65
65
  const twoFaEnabledForUser = Boolean(user.Metadata[TWO_FA_METATADATA_KEYS.ENABLED]);
66
- if (this.TwoFactorAuthForceUser && !twoFaEnabledForUser) {
66
+ // `forceUser` alone used to be enough to park the user in
67
+ // TwoFactorInitRequired. But every mutating `/user/2fa*` and
68
+ // `/auth/2fa/setup` handler now calls `assertSystemEnabled()` and throws
69
+ // 403 when `rbac.twoFactorAuth.enabled` is off — so a user forced into
70
+ // this branch while the system switch is off would have no route back
71
+ // out: setup2fa can no longer both enrol AND activate them. The
72
+ // system-wide switch has to gate this branch too, or every user without
73
+ // 2FA is locked out of the product the moment forceUser is on and the
74
+ // switch is off.
75
+ if (this.TwoFactorAuthEnabled && this.TwoFactorAuthForceUser && !twoFaEnabledForUser) {
67
76
  this._log.trace('User logged in, 2fa init required', {
68
77
  Uuid: user.Uuid
69
78
  });
@@ -1 +1 @@
1
- {"version":3,"file":"LoginController.js","sourceRoot":"","sources":["../../../src/controllers/LoginController.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACzI,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAElF,OAAO,EAAE,YAAY,EAAE,IAAI,IAAI,YAAY,EAAE,OAAO,IAAI,eAAe,EAAE,SAAS,EAAE,WAAW,EAAmC,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAClL,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,OAAO,EAAE,aAAa,EAAkB,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAGnE;;;;;GAKG;AAEI,IAAM,eAAe,GAArB,MAAM,eAAgB,SAAQ,cAAc;IA2BjD;;;;;;;;;OASG;IAGU,AAAN,KAAK,CAAC,KAAK,CAAiB,MAAY,EAAe,IAAY,EAAU,WAAyB;QAC3G,IAAI,CAAC;YAEH,0DAA0D;YAC1D,2BAA2B;YAC3B,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;gBACnB,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC1C,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,KAAK,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;YAClE,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;YAElC,IAAI,MAAsB,CAAC;YAE3B,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YAEpC,8DAA8D;YAC9D,oEAAoE;YACpE,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YAClC,IAAI,UAAU,EAAE,CAAC;gBACf,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;YAC7C,CAAC;YAED,8BAA8B;YAC9B,sEAAsE;YACtE,+EAA+E;YAC/E,yDAAyD;YACzD,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YACjC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;YAEzB,MAAM,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC,CAAC;YAEnF,IAAI,IAAI,CAAC,sBAAsB,IAAI,CAAC,mBAAmB,EAAE,CAAC;gBACxD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,mCAAmC,EAAE;oBACnD,IAAI,EAAE,IAAI,CAAC,IAAI;iBAChB,CAAC,CAAC;gBAEH,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;gBACtC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;gBAExC,MAAM,GAAG,EAAE,qBAAqB,EAAE,IAAI,EAAE,CAAC;YAC3C,CAAC;iBACI,IAAI,IAAI,CAAC,oBAAoB,IAAI,mBAAmB,EAAE,CAAC;gBAE1D,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,8BAA8B,EAAE;oBAC9C,IAAI,EAAE,IAAI,CAAC,IAAI;iBAChB,CAAC,CAAC;gBAEH,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;gBACtC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;gBAExC,MAAM,GAAG,EAAE,qBAAqB,EAAE,IAAI,EAAE,CAAC;YAC3C,CAAC;iBAAM,CAAC;gBAEN,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;gBAErC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,iCAAiC,EAAE;oBACjD,IAAI,EAAE,IAAI,CAAC,IAAI;iBAChB,CAAC,CAAC;gBAEH,MAAM,GAAG,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;YAC1D,CAAC;YAED,uEAAuE;YACvE,0EAA0E;YAC1E,0DAA0D;YAC1D,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAEzC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE;gBAChC,sEAAsE;gBACtE,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,SAAS,CAAC;gBACzC,IAAI,EAAE,IAAI,CAAC,IAAI;aAChB,CAAC,CAAC;YAEH,OAAO,IAAI,EAAE,CAAC,MAAM,EAAE;gBACpB,QAAQ,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBAE9C,qEAAqE;gBACrE,6DAA6D;gBAC7D,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;aACxD,CAAC,CAAC;QAEL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,yEAAyE;YACzE,wEAAwE;YACxE,wEAAwE;YACxE,yEAAyE;YACzE,kEAAkE;YAClE,qEAAqE;YACrE,8CAA8C;YAC9C,MAAM,aAAa,GAAG,GAAG,YAAY,SAAS,IAAI,GAAG,YAAY,eAAe,CAAC;YAEjF,IAAI,CAAC,aAAa,EAAE,CAAC;gBACnB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAY,EAAE,iCAAiC,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;gBACrF,MAAM,GAAG,CAAC;YACZ,CAAC;YAED,MAAM,MAAM,GAAG,GAAG,YAAY,SAAS,CAAC,CAAC,CAAC,uBAAuB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,oBAAoB,CAAC;YAE/G,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAY,EAAE,oBAAoB,WAAW,EAAE,KAAK,KAAK,MAAM,EAAE,CAAC,CAAC;YAElF,OAAO,IAAI,YAAY,CAAC;gBACtB,KAAK,EAAE;oBACL,IAAI,EAAE,eAAe;oBACrB,OAAO,EAAE,6BAA6B;iBACvC;aACF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;OAYG;IAGU,AAAN,KAAK,CAAC,MAAM,CAAc,IAAY,EAAqB,OAAiB,EAAkB,IAAU;QAC7G,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,IAAI,EAAE,EAAE,CAAC;QAClB,CAAC;QAED,uEAAuE;QACvE,wEAAwE;QACxE,kCAAkC;QAClC,iEAAiE;QACjE,gDAAgD;QAChD,0EAA0E;QAC1E,sBAAsB;QACtB,wEAAwE;QACxE,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC;QAC/D,MAAM,MAAM,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;QAErE,MAAM,GAAG,GAAmB,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACzE,KAAK,MAAM,OAAO,IAAI,MAAM,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACzC,IAAI,MAAM,EAAE,CAAC;gBACX,OAAO,IAAI,EAAE,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,EAAE;oBACjC,QAAQ,EAAE,MAAM,CAAC,OAAO,IAAI,EAAE;oBAC9B,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,EAAE;iBAC9B,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,oEAAoE;QACpE,qEAAqE;QACrE,OAAO,IAAI,EAAE,EAAE,CAAC;IAClB,CAAC;IAED;;;;;;;;;OASG;IAGU,AAAN,KAAK,CAAC,MAAM,CAAiB,IAAU,EAAiB,UAAkB,EAAqB,OAAiB;QAErH,OAAO,IAAI,EAAE,CAAC;YACZ,GAAG,IAAI,CAAC,sBAAsB,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;YAEzD,yEAAyE;YACzE,uEAAuE;YACvE,uEAAuE;YACvE,yEAAyE;YACzE,yDAAyD;YACzD,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,UAAU,EAAE,UAAU,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YACxC,UAAU,EAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAyB,IAAI,IAAI;YAE3E,gEAAgE;YAChE,0EAA0E;YAC1E,uEAAuE;YACvE,kEAAkE;SACrC,CAAC,CAAC;IACnC,CAAC;CACF,CAAA;AAlOW;IADT,UAAU,EAAE;8BACY,aAAa;sDAAC;AAG7B;IADT,iBAAiB,CAAC,WAAW,CAAC;8BACP,YAAY;qDAAC;AAG3B;IADT,iBAAiB,CAAC,cAAc,CAAC;8BACP,eAAe;wDAAC;AAKjC;IAHT,MAAM,CAAC,4BAA4B,EAAE;QACpC,YAAY,EAAE,KAAK;KACpB,CAAC;;6DACsC;AAM9B;IAHT,MAAM,CAAC,8BAA8B,EAAE;QACtC,YAAY,EAAE,KAAK;KACpB,CAAC;;+DACwC;AAGhC;IADT,UAAU,CAAC,oBAAoB,CAAC;8BACP,oBAAoB;uDAAC;AAGrC;IADT,UAAU,CAAC,aAAa,CAAC;8BACZ,aAAa;2CAAC;AAcf;IAFZ,IAAI,EAAE;IACN,mBAAmB,EAAE;IACF,WAAA,YAAY,EAAE,CAAA;IAAgB,WAAA,SAAS,EAAE,CAAA;IAAgB,WAAA,IAAI,EAAE,CAAA;;qCAAxC,IAAI,UAAkD,YAAY;;4CA4G5G;AAiBY;IAFZ,IAAI,EAAE;IACN,MAAM,CAAC,YAAY,CAAC;IACA,WAAA,SAAS,EAAE,CAAA;IAAgB,WAAA,eAAe,EAAE,CAAA;IAAqB,WAAA,YAAY,EAAE,CAAA;;qDAAO,IAAI;;6CA8B9G;AAcY;IAFZ,GAAG,EAAE;IACL,MAAM,CAAC,YAAY,CAAC;IACA,WAAA,YAAY,EAAE,CAAA;IAAc,WAAA,WAAW,EAAE,CAAA;IAAsB,WAAA,eAAe,EAAE,CAAA;;qCAA3D,IAAI;;6CAmB7C;AAnOU,eAAe;IAD3B,QAAQ,CAAC,MAAM,CAAC;GACJ,eAAe,CAoO3B"}
1
+ {"version":3,"file":"LoginController.js","sourceRoot":"","sources":["../../../src/controllers/LoginController.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { TokenDto } from './../dto/token-dto.js';
2
2
  import { BaseController, Ok, ForbiddenResponse } from '@spinajs/http';
3
3
  import { ISession, SessionProvider, User as UserModel, AccessControl } from '@spinajs/rbac';
4
- import { IEnable2faResponse, IUserWithGrants } from '@spinajs/rbac-http';
4
+ import { IEnable2faResponse, IUserWithGrants, TwoFactorAuthConfig } from '@spinajs/rbac-http';
5
5
  import { SessionCookieFactory } from '../services/SessionCookies.js';
6
6
  /**
7
7
  * Two-factor authentication during login.
@@ -24,6 +24,7 @@ export declare class TwoFactorAuthController extends BaseController {
24
24
  protected SessionProvider: SessionProvider;
25
25
  protected SessionCookies: SessionCookieFactory;
26
26
  protected AC: AccessControl;
27
+ protected TwoFactorConfig: TwoFactorAuthConfig;
27
28
  /**
28
29
  * Set up two-factor authentication during login
29
30
  * Generates a TOTP secret for a user who must enrol before continuing — the state
@@ -53,5 +54,23 @@ export declare class TwoFactorAuthController extends BaseController {
53
54
  * verification without a TOTP/DB setup.
54
55
  */
55
56
  protected verifyTwoFactorToken(user: UserModel, token: string): Promise<void>;
57
+ /**
58
+ * Store the TOTP secret without switching 2fa on. Wrapped in a protected
59
+ * method for the same reason as {@link verifyTwoFactorToken}.
60
+ */
61
+ protected enrol(user: UserModel): Promise<unknown>;
62
+ /** Switch 2fa on for an account whose secret was just verified. */
63
+ protected activateEnrolment(user: UserModel): Promise<unknown>;
64
+ /**
65
+ * Refuse to start enrolment while 2FA is switched off system-wide.
66
+ *
67
+ * `TwoFacRouteEnabled` cannot do this job here either: @spinajs/http merges
68
+ * a route's policies into one gate that passes when ANY of them resolves,
69
+ * and this controller also carries `NotAuthorizedPolicy`, which succeeds
70
+ * for exactly the callers these routes serve — so `TwoFacRouteEnabled`
71
+ * rejecting cannot stop them. The check has to live where it cannot be
72
+ * short-circuited.
73
+ */
74
+ protected assertSystemEnabled(): void;
56
75
  }
57
76
  //# sourceMappingURL=TwoFactorAuthController.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TwoFactorAuthController.d.ts","sourceRoot":"","sources":["../../../src/controllers/TwoFactorAuthController.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAY,EAAE,EAAQ,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACtF,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,IAAI,SAAS,EAAE,aAAa,EAAqB,MAAM,eAAe,CAAC;AAO/G,OAAO,EAA6B,kBAAkB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAIpG,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAGrE;;;;;;;;;;;;;;;;GAgBG;AACH,qBAGa,uBAAwB,SAAQ,cAAc;IAEzD,SAAS,CAAC,eAAe,EAAE,eAAe,CAAC;IAG3C,SAAS,CAAC,cAAc,EAAE,oBAAoB,CAAC;IAG/C,SAAS,CAAC,EAAE,EAAE,aAAa,CAAC;IAE5B;;;;;;;;;;;OAWG;IAEU,QAAQ,CAAS,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAS9E;IAED;;;;;;;;OAQG;IAEU,WAAW,CAAS,MAAM,EAAE,SAAS,EAAU,KAAK,EAAE,QAAQ,EAAa,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,EAAE,CAAC,eAAe,CAAC,GAAG,iBAAiB,CAAC,CAuC3J;IAED;;;;OAIG;IACH,SAAS,CAAC,oBAAoB,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;CACF"}
1
+ {"version":3,"file":"TwoFactorAuthController.d.ts","sourceRoot":"","sources":["../../../src/controllers/TwoFactorAuthController.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAY,EAAE,EAAQ,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACtF,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,IAAI,SAAS,EAAE,aAAa,EAAqB,MAAM,eAAe,CAAC;AAO/G,OAAO,EAA6B,kBAAkB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAIzH,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAGrE;;;;;;;;;;;;;;;;GAgBG;AACH,qBAGa,uBAAwB,SAAQ,cAAc;IAEzD,SAAS,CAAC,eAAe,EAAE,eAAe,CAAC;IAG3C,SAAS,CAAC,cAAc,EAAE,oBAAoB,CAAC;IAG/C,SAAS,CAAC,EAAE,EAAE,aAAa,CAAC;IAG5B,SAAS,CAAC,eAAe,EAAE,mBAAmB,CAAC;IAE/C;;;;;;;;;;;OAWG;IAEU,QAAQ,CAAS,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAgB9E;IAED;;;;;;;;OAQG;IAEU,WAAW,CAAS,MAAM,EAAE,SAAS,EAAU,KAAK,EAAE,QAAQ,EAAa,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,EAAE,CAAC,eAAe,CAAC,GAAG,iBAAiB,CAAC,CA8C3J;IAED;;;;OAIG;IACH,SAAS,CAAC,oBAAoB,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;IAED;;;OAGG;IACH,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAEjD;IAED,mEAAmE;IACnE,SAAS,CAAC,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAE7D;IAED;;;;;;;;;OASG;IACH,SAAS,CAAC,mBAAmB,IAAI,IAAI,CAMpC;CACF"}
@@ -16,11 +16,11 @@ import { SessionProvider, User as UserModel, AccessControl, regenerateSession }
16
16
  import { Session } from '@spinajs/rbac-http';
17
17
  import { Body, Policy } from '@spinajs/http';
18
18
  import { TwoFacRouteEnabled } from '../policies/2FaPolicy.js';
19
- import { AutoinjectService } from '@spinajs/configuration';
19
+ import { AutoinjectService, Config } from '@spinajs/configuration';
20
20
  import { Autoinject } from '@spinajs/di';
21
21
  import { User, NotAuthorizedPolicy } from '@spinajs/rbac-http';
22
- import { auth2Fa, enableUser2Fa } from '../actions/2fa.js';
23
- import { InvalidOperation } from '@spinajs/exceptions';
22
+ import { auth2Fa, activateUser2Fa, beginUser2FaEnrolment } from '../actions/2fa.js';
23
+ import { BadRequest, Forbidden } from '@spinajs/exceptions';
24
24
  import { TWO_FA_METATADATA_KEYS } from '../2fa/Default2FaToken.js';
25
25
  import { SessionCookieFactory } from '../services/SessionCookies.js';
26
26
  import { activeRoleOf, buildUserWithGrants } from '../services/grants.js';
@@ -55,10 +55,15 @@ let TwoFactorAuthController = class TwoFactorAuthController extends BaseControll
55
55
  * @response 403 Session is not awaiting two-factor enrolment
56
56
  */
57
57
  async setup2fa(user) {
58
+ this.assertSystemEnabled();
58
59
  if (user.Metadata[TWO_FA_METATADATA_KEYS.ENABLED]) {
59
- throw new InvalidOperation(`User ${user.Uuid} already has 2fa enabled`);
60
+ throw new BadRequest(`User ${user.Uuid} already has 2fa enabled`);
60
61
  }
61
- const result = await enableUser2Fa(user);
62
+ // Re-running setup on an account that already started enrolling is allowed:
63
+ // it replaces an unconfirmed secret, which is exactly how a user who lost
64
+ // the QR gets a new one. There is nothing to steal — the caller could reach
65
+ // the same state from scratch.
66
+ const result = await this.enrol(user);
62
67
  return new Ok({
63
68
  otp: result,
64
69
  });
@@ -75,6 +80,12 @@ let TwoFactorAuthController = class TwoFactorAuthController extends BaseControll
75
80
  async verifyToken(logged, token, session) {
76
81
  try {
77
82
  await this.verifyTwoFactorToken(logged, token.Token);
83
+ // A pending enrolment (secret stored, `2fa:enabled` unset) becomes real
84
+ // here and nowhere else — this is the first moment the user has proven
85
+ // possession of the device.
86
+ if (!logged.Metadata[TWO_FA_METATADATA_KEYS.ENABLED]) {
87
+ await this.activateEnrolment(logged);
88
+ }
78
89
  // 2fa complete, mark as authorized
79
90
  // fron now on user is considered authorized
80
91
  session.Data.set('Authorized', true);
@@ -116,6 +127,34 @@ let TwoFactorAuthController = class TwoFactorAuthController extends BaseControll
116
127
  verifyTwoFactorToken(user, token) {
117
128
  return auth2Fa(user, token);
118
129
  }
130
+ /**
131
+ * Store the TOTP secret without switching 2fa on. Wrapped in a protected
132
+ * method for the same reason as {@link verifyTwoFactorToken}.
133
+ */
134
+ enrol(user) {
135
+ return beginUser2FaEnrolment(user);
136
+ }
137
+ /** Switch 2fa on for an account whose secret was just verified. */
138
+ activateEnrolment(user) {
139
+ return activateUser2Fa(user);
140
+ }
141
+ /**
142
+ * Refuse to start enrolment while 2FA is switched off system-wide.
143
+ *
144
+ * `TwoFacRouteEnabled` cannot do this job here either: @spinajs/http merges
145
+ * a route's policies into one gate that passes when ANY of them resolves,
146
+ * and this controller also carries `NotAuthorizedPolicy`, which succeeds
147
+ * for exactly the callers these routes serve — so `TwoFacRouteEnabled`
148
+ * rejecting cannot stop them. The check has to live where it cannot be
149
+ * short-circuited.
150
+ */
151
+ assertSystemEnabled() {
152
+ if (this.TwoFactorConfig?.enabled === false) {
153
+ throw Object.assign(new Forbidden('2 factor auth is not enabled'), {
154
+ error: { code: 'E_2FA_SYSTEM_DISABLED', message: '2 factor auth is not enabled' },
155
+ });
156
+ }
157
+ }
119
158
  };
120
159
  __decorate([
121
160
  AutoinjectService('rbac.session'),
@@ -129,6 +168,10 @@ __decorate([
129
168
  Autoinject(AccessControl),
130
169
  __metadata("design:type", AccessControl)
131
170
  ], TwoFactorAuthController.prototype, "AC", void 0);
171
+ __decorate([
172
+ Config('rbac.twoFactorAuth'),
173
+ __metadata("design:type", Object)
174
+ ], TwoFactorAuthController.prototype, "TwoFactorConfig", void 0);
132
175
  __decorate([
133
176
  Post('2fa/setup'),
134
177
  __param(0, User()),
@@ -1 +1 @@
1
- {"version":3,"file":"TwoFactorAuthController.js","sourceRoot":"","sources":["../../../src/controllers/TwoFactorAuthController.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TwoFactorAuthController.js","sourceRoot":"","sources":["../../../src/controllers/TwoFactorAuthController.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,26 @@
1
- import { BaseController, Ok, Unauthorized } from '@spinajs/http';
1
+ import { BaseController, ForbiddenResponse, Ok, Unauthorized } from '@spinajs/http';
2
2
  import { PasswordProvider, SessionProvider, User as UserModel } from '@spinajs/rbac';
3
3
  import type { ISession } from '@spinajs/rbac';
4
- import { IEnable2faResponse } from '@spinajs/rbac-http';
4
+ import { IEnable2faResponse, TwoFactorAuthConfig } from '@spinajs/rbac-http';
5
5
  import { ConfirmPasswordDto } from '../dto/confirm-password-dto.js';
6
+ import { TokenDto } from '../dto/token-dto.js';
6
7
  import { SessionCookieFactory } from '../services/SessionCookies.js';
7
8
  /** Whether the authenticated user currently has a TOTP device enrolled. */
8
9
  export interface ITwoFactorStatus {
9
10
  Enabled: boolean;
11
+ /**
12
+ * A secret was issued but never confirmed. The account is not protected — the
13
+ * distinction exists so the UI can say "you started this" rather than
14
+ * "you have no 2fa".
15
+ */
16
+ Pending: boolean;
17
+ /**
18
+ * Whether 2FA is switched on system-wide (`rbac.twoFactorAuth.enabled`). The
19
+ * frontend hides its 2FA controls when this is false; it is reported here
20
+ * rather than signalled by an error because the guarding policy cannot
21
+ * reject an authorized caller.
22
+ */
23
+ SystemEnabled: boolean;
10
24
  }
11
25
  /**
12
26
  * Self-service two-factor authentication.
@@ -22,12 +36,24 @@ export interface ITwoFactorStatus {
22
36
  * not be enough to attach an attacker-controlled authenticator or to strip the
23
37
  * second factor off the account.
24
38
  *
39
+ * `TwoFactorAuthEnabled` stays on the class as the guard for callers it is
40
+ * able to reject, but it cannot block an authorized caller here:
41
+ * `@spinajs/http` merges every policy on a route into ONE gate that lets the
42
+ * route run when ANY policy resolves, and `AuthorizedPolicy` below resolves
43
+ * for any logged-in caller — so once a session is authorized, that policy
44
+ * alone is enough to pass the gate regardless of what `TwoFactorAuthEnabled`
45
+ * decides. That is why the system-wide switch (`rbac.twoFactorAuth.enabled`)
46
+ * is *also* enforced directly in the mutating handlers, via
47
+ * {@link assertSystemEnabled}, which is the check that actually matters for
48
+ * an authorized caller.
49
+ *
25
50
  * @tags Two-Factor Settings
26
51
  */
27
52
  export declare class TwoFactorAuthUserController extends BaseController {
28
53
  protected PasswordProvider: PasswordProvider;
29
54
  protected SessionProvider: SessionProvider;
30
55
  protected SessionCookies: SessionCookieFactory;
56
+ protected TwoFactorConfig: TwoFactorAuthConfig;
31
57
  /**
32
58
  * Get own two-factor status
33
59
  * Reports whether the authenticated user currently has a TOTP device enrolled.
@@ -41,6 +67,8 @@ export declare class TwoFactorAuthUserController extends BaseController {
41
67
  * Enable own two-factor authentication
42
68
  * Generates a TOTP secret for the authenticated user and returns the OTP provisioning
43
69
  * URI to scan with an authenticator app. Requires the account password to be re-entered.
70
+ * 2FA stays off until the new code is confirmed with `POST /user/2fa/confirm` — a caller
71
+ * that stops after this call leaves the account unprotected, believing it is enrolled.
44
72
  * @security cookieAuth
45
73
  * @returns {IEnable2faResponse} OTP provisioning URI to scan with an authenticator app
46
74
  * @response 400 Two-factor authentication is already enabled for this user
@@ -48,6 +76,17 @@ export declare class TwoFactorAuthUserController extends BaseController {
48
76
  * @response 403 Forbidden — insufficient permissions
49
77
  */
50
78
  enable(user: UserModel, confirmation: ConfirmPasswordDto, session: ISession): Promise<Ok<IEnable2faResponse> | Unauthorized>;
79
+ /**
80
+ * Confirm own two-factor enrolment
81
+ * Verifies a code generated from the secret handed out by `POST /user/2fa/enable`
82
+ * or `POST /user/2fa/reset`, and only then switches 2FA on.
83
+ * @security cookieAuth
84
+ * @response 200 Two-factor authentication is now active
85
+ * @response 400 There is no pending enrolment to confirm
86
+ * @response 401 Unauthorized — valid session required
87
+ * @response 403 Invalid or expired TOTP code
88
+ */
89
+ confirm(user: UserModel, token: TokenDto, session: ISession): Promise<Ok | ForbiddenResponse>;
51
90
  /**
52
91
  * Disable own two-factor authentication
53
92
  * Removes the TOTP secret and disables 2FA for the authenticated user.
@@ -59,6 +98,43 @@ export declare class TwoFactorAuthUserController extends BaseController {
59
98
  * @response 403 Forbidden — insufficient permissions
60
99
  */
61
100
  disable(user: UserModel, confirmation: ConfirmPasswordDto, session: ISession): Promise<Ok | Unauthorized>;
101
+ /**
102
+ * Reset own two-factor authentication
103
+ * Removes the current TOTP device and issues a new secret in a single request,
104
+ * returning the provisioning URI to scan. 2FA stays off until the new code is
105
+ * confirmed with `POST /user/2fa/confirm`. Requires the account password.
106
+ *
107
+ * Folding disable-then-enable into one request removes the client-side gap
108
+ * where abandoning the flow between the two calls could strand the account
109
+ * without a second factor. It does not remove the server-side one: if
110
+ * `enrol` fails after `unenrol` already removed the old device, the account
111
+ * is left with no device at all — there is nothing to roll back to, since
112
+ * the old secret is already gone. That leaves the account in the ordinary
113
+ * `none` state, which every route here already handles: `GET /user/2fa`
114
+ * reports it and `POST /user/2fa/enable` starts a fresh enrolment.
115
+ *
116
+ * A caller who abandons before confirming is left password-only — the same
117
+ * gap as `enable`. The only built-in recovery is `rbac.twoFactorAuth.forceUser`
118
+ * pushing the account back through enrolment at next login, and this package's
119
+ * own default for that flag is `false` (see `src/config/rbac-http.ts`), so a
120
+ * consumer that has not set it will see the downgrade become silent and
121
+ * permanent rather than recovered.
122
+ * @security cookieAuth
123
+ * @returns {IEnable2faResponse} OTP provisioning URI to scan with an authenticator app
124
+ * @response 400 There is no two-factor device to reset
125
+ * @response 401 Unauthorized — valid session required, or password invalid
126
+ * @response 403 Forbidden — insufficient permissions
127
+ */
128
+ reset(user: UserModel, confirmation: ConfirmPasswordDto, session: ISession): Promise<Ok<IEnable2faResponse> | Unauthorized>;
129
+ /**
130
+ * Refuse a mutation while 2FA is switched off system-wide.
131
+ *
132
+ * The `TwoFactorAuthEnabled` policy cannot do this job: @spinajs/http merges
133
+ * a route's policies into one gate that passes when ANY of them resolves, and
134
+ * these routes also carry AuthorizedPolicy, which succeeds for every
135
+ * logged-in caller. The check has to live where it cannot be short-circuited.
136
+ */
137
+ protected assertSystemEnabled(): void;
62
138
  /**
63
139
  * Rotates the session id after the account's authentication requirements
64
140
  * changed, and returns the response options carrying the new cookie.
@@ -83,10 +159,13 @@ export declare class TwoFactorAuthUserController extends BaseController {
83
159
  }[];
84
160
  }>;
85
161
  /**
86
- * Generate and store the TOTP secret, returning the provisioning URI. Wraps
87
- * the module-level action so tests can stub it without a TOTP/DB setup.
162
+ * Store the TOTP secret and return the provisioning URI, leaving 2fa off until
163
+ * confirmed. Wraps the module-level action so tests can stub it without a
164
+ * TOTP/DB setup.
88
165
  */
89
166
  protected enrol(user: UserModel): Promise<unknown>;
167
+ /** Verify a code against the pending secret and switch 2fa on. */
168
+ protected confirmEnrolment(user: UserModel, token: string): Promise<unknown>;
90
169
  /** Clear the TOTP secret. Wrapped for the same reason as {@link enrol}. */
91
170
  protected unenrol(user: UserModel): Promise<unknown>;
92
171
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"TwoFactorAuthUserController.d.ts","sourceRoot":"","sources":["../../../src/controllers/TwoFactorAuthUserController.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAuB,EAAE,EAAgB,YAAY,EAAE,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,IAAI,IAAI,SAAS,EAAqB,MAAM,eAAe,CAAC;AACxG,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG9C,OAAO,EAAoB,kBAAkB,EAA0D,MAAM,oBAAoB,CAAC;AAGlI,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAIrE,2EAA2E;AAC3E,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAIa,2BAA4B,SAAQ,cAAc;IAE7D,SAAS,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAG7C,SAAS,CAAC,eAAe,EAAE,eAAe,CAAC;IAG3C,SAAS,CAAC,cAAc,EAAE,oBAAoB,CAAC;IAE/C;;;;;;;OAOG;IAGU,MAAM,CAAS,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAE1E;IAED;;;;;;;;;OASG;IAGU,MAAM,CAAS,IAAI,EAAE,SAAS,EAAU,YAAY,EAAE,kBAAkB,EAAqB,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,EAAE,CAAC,kBAAkB,CAAC,GAAG,YAAY,CAAC,CAkB3K;IAED;;;;;;;;;OASG;IAGU,OAAO,CAAS,IAAI,EAAE,SAAS,EAAU,YAAY,EAAE,kBAAkB,EAAqB,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,EAAE,GAAG,YAAY,CAAC,CAYxJ;IAED;;;;;;;;;OASG;IACH,UAAgB,MAAM,CAAC,OAAO,EAAE,QAAQ;;;;;;;;;;;;OAWvC;IAED;;;OAGG;IACH,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAEjD;IAED,2EAA2E;IAC3E,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAEnD;IAED;;;OAGG;IACH,UAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,YAAY,CAAC,CAY/F;CACF"}
1
+ {"version":3,"file":"TwoFactorAuthUserController.d.ts","sourceRoot":"","sources":["../../../src/controllers/TwoFactorAuthUserController.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAkB,iBAAiB,EAAO,EAAE,EAAgB,YAAY,EAAE,MAAM,eAAe,CAAC;AACvH,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,IAAI,IAAI,SAAS,EAAqB,MAAM,eAAe,CAAC;AACxG,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG9C,OAAO,EAAoB,kBAAkB,EAAoD,mBAAmB,EAAQ,MAAM,oBAAoB,CAAC;AAEvJ,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAKrE,2EAA2E;AAC3E,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IAEjB;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;;;;OAKG;IACH,aAAa,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAIa,2BAA4B,SAAQ,cAAc;IAE7D,SAAS,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAG7C,SAAS,CAAC,eAAe,EAAE,eAAe,CAAC;IAG3C,SAAS,CAAC,cAAc,EAAE,oBAAoB,CAAC;IAG/C,SAAS,CAAC,eAAe,EAAE,mBAAmB,CAAC;IAE/C;;;;;;;OAOG;IAGU,MAAM,CAAS,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAQ1E;IAED;;;;;;;;;;;OAWG;IAGU,MAAM,CAAS,IAAI,EAAE,SAAS,EAAU,YAAY,EAAE,kBAAkB,EAAqB,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,EAAE,CAAC,kBAAkB,CAAC,GAAG,YAAY,CAAC,CAmB3K;IAED;;;;;;;;;OASG;IAGU,OAAO,CAAS,IAAI,EAAE,SAAS,EAAU,KAAK,EAAE,QAAQ,EAAqB,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,EAAE,GAAG,iBAAiB,CAAC,CAiC5I;IAED;;;;;;;;;OASG;IAGU,OAAO,CAAS,IAAI,EAAE,SAAS,EAAU,YAAY,EAAE,kBAAkB,EAAqB,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,EAAE,GAAG,YAAY,CAAC,CAcxJ;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IAGU,KAAK,CAAS,IAAI,EAAE,SAAS,EAAU,YAAY,EAAE,kBAAkB,EAAqB,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,EAAE,CAAC,kBAAkB,CAAC,GAAG,YAAY,CAAC,CAgB1K;IAED;;;;;;;OAOG;IACH,SAAS,CAAC,mBAAmB,IAAI,IAAI,CAMpC;IAED;;;;;;;;;OASG;IACH,UAAgB,MAAM,CAAC,OAAO,EAAE,QAAQ;;;;;;;;;;;;OAWvC;IAED;;;;OAIG;IACH,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAEjD;IAED,kEAAkE;IAClE,SAAS,CAAC,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAE3E;IAED,2EAA2E;IAC3E,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAEnD;IAED;;;OAGG;IACH,UAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,YAAY,CAAC,CAY/F;CACF"}
@@ -10,17 +10,18 @@ var __metadata = (this && this.__metadata) || function (k, v) {
10
10
  var __param = (this && this.__param) || function (paramIndex, decorator) {
11
11
  return function (target, key) { decorator(target, key, paramIndex); }
12
12
  };
13
- import { BaseController, BasePath, Body, Get, Ok, Policy, Post, Unauthorized } from '@spinajs/http';
13
+ import { BaseController, BasePath, Body, ForbiddenResponse, Get, Ok, Policy, Post, Unauthorized } from '@spinajs/http';
14
14
  import { PasswordProvider, SessionProvider, User as UserModel, regenerateSession } from '@spinajs/rbac';
15
15
  import { Autoinject } from '@spinajs/di';
16
- import { AutoinjectService } from '@spinajs/configuration';
16
+ import { AutoinjectService, Config } from '@spinajs/configuration';
17
17
  import { AuthorizedPolicy, Permission, Resource, Session as SessionRouteArg, User } from '@spinajs/rbac-http';
18
- import { InvalidOperation } from '@spinajs/exceptions';
19
- import { TwoFactorAuthEnabled } from '../policies/2FaPolicy.js';
18
+ import { BadRequest, Forbidden } from '@spinajs/exceptions';
20
19
  import { ConfirmPasswordDto } from '../dto/confirm-password-dto.js';
20
+ import { TokenDto } from '../dto/token-dto.js';
21
21
  import { SessionCookieFactory } from '../services/SessionCookies.js';
22
- import { disableUser2Fa, enableUser2Fa } from '../actions/2fa.js';
22
+ import { beginUser2FaEnrolment, confirmUser2Fa, disableUser2Fa } from '../actions/2fa.js';
23
23
  import { TWO_FA_METATADATA_KEYS } from '../2fa/Default2FaToken.js';
24
+ import { TwoFactorAuthEnabled } from '../policies/2FaPolicy.js';
24
25
  /**
25
26
  * Self-service two-factor authentication.
26
27
  *
@@ -35,6 +36,17 @@ import { TWO_FA_METATADATA_KEYS } from '../2fa/Default2FaToken.js';
35
36
  * not be enough to attach an attacker-controlled authenticator or to strip the
36
37
  * second factor off the account.
37
38
  *
39
+ * `TwoFactorAuthEnabled` stays on the class as the guard for callers it is
40
+ * able to reject, but it cannot block an authorized caller here:
41
+ * `@spinajs/http` merges every policy on a route into ONE gate that lets the
42
+ * route run when ANY policy resolves, and `AuthorizedPolicy` below resolves
43
+ * for any logged-in caller — so once a session is authorized, that policy
44
+ * alone is enough to pass the gate regardless of what `TwoFactorAuthEnabled`
45
+ * decides. That is why the system-wide switch (`rbac.twoFactorAuth.enabled`)
46
+ * is *also* enforced directly in the mutating handlers, via
47
+ * {@link assertSystemEnabled}, which is the check that actually matters for
48
+ * an authorized caller.
49
+ *
38
50
  * @tags Two-Factor Settings
39
51
  */
40
52
  let TwoFactorAuthUserController = class TwoFactorAuthUserController extends BaseController {
@@ -47,12 +59,19 @@ let TwoFactorAuthUserController = class TwoFactorAuthUserController extends Base
47
59
  * @response 403 Forbidden — insufficient permissions
48
60
  */
49
61
  async status(user) {
50
- return new Ok({ Enabled: Boolean(user.Metadata[TWO_FA_METATADATA_KEYS.ENABLED]) });
62
+ const enabled = Boolean(user.Metadata[TWO_FA_METATADATA_KEYS.ENABLED]);
63
+ return new Ok({
64
+ Enabled: enabled,
65
+ Pending: !enabled && Boolean(user.Metadata[TWO_FA_METATADATA_KEYS.TOKEN]),
66
+ SystemEnabled: this.TwoFactorConfig?.enabled !== false,
67
+ });
51
68
  }
52
69
  /**
53
70
  * Enable own two-factor authentication
54
71
  * Generates a TOTP secret for the authenticated user and returns the OTP provisioning
55
72
  * URI to scan with an authenticator app. Requires the account password to be re-entered.
73
+ * 2FA stays off until the new code is confirmed with `POST /user/2fa/confirm` — a caller
74
+ * that stops after this call leaves the account unprotected, believing it is enrolled.
56
75
  * @security cookieAuth
57
76
  * @returns {IEnable2faResponse} OTP provisioning URI to scan with an authenticator app
58
77
  * @response 400 Two-factor authentication is already enabled for this user
@@ -60,21 +79,61 @@ let TwoFactorAuthUserController = class TwoFactorAuthUserController extends Base
60
79
  * @response 403 Forbidden — insufficient permissions
61
80
  */
62
81
  async enable(user, confirmation, session) {
82
+ this.assertSystemEnabled();
63
83
  if (user.Metadata[TWO_FA_METATADATA_KEYS.ENABLED]) {
64
- throw new InvalidOperation(`User ${user.Uuid} already has 2fa enabled`);
84
+ throw new BadRequest(`User ${user.Uuid} already has 2fa enabled`);
65
85
  }
66
86
  const confirmed = await this.confirmPassword(user, confirmation.Password);
67
87
  if (confirmed !== true) {
68
88
  return confirmed;
69
89
  }
70
- // NOTE: enrolment takes effect immediately the secret is stored and
71
- // `2fa:enabled` is set before the user has proven they scanned it. That is
72
- // the same contract the login-time setup route follows, and it is what the
73
- // login check reads. A user who never scans the returned URI locks
74
- // themselves out and needs an administrator 2FA reset to recover.
90
+ // The secret is stored but 2fa stays OFF until `POST /user/2fa/confirm`
91
+ // accepts a code generated from it. A user who never scans is left pending,
92
+ // which the login check treats exactly like having no device no lockout,
93
+ // and the next attempt simply issues a new secret.
75
94
  const result = await this.enrol(user);
76
95
  return new Ok({ otp: result }, await this.rotate(session));
77
96
  }
97
+ /**
98
+ * Confirm own two-factor enrolment
99
+ * Verifies a code generated from the secret handed out by `POST /user/2fa/enable`
100
+ * or `POST /user/2fa/reset`, and only then switches 2FA on.
101
+ * @security cookieAuth
102
+ * @response 200 Two-factor authentication is now active
103
+ * @response 400 There is no pending enrolment to confirm
104
+ * @response 401 Unauthorized — valid session required
105
+ * @response 403 Invalid or expired TOTP code
106
+ */
107
+ async confirm(user, token, session) {
108
+ this.assertSystemEnabled();
109
+ // `activate()` never clears the stored token, so an already-enabled
110
+ // account still has one and would otherwise be able to call this route
111
+ // again with a currently-valid code. Only the pending state — a token
112
+ // without 2fa being enabled yet — is an enrolment left to confirm.
113
+ if (user.Metadata[TWO_FA_METATADATA_KEYS.ENABLED] || !user.Metadata[TWO_FA_METATADATA_KEYS.TOKEN]) {
114
+ throw new BadRequest(`User ${user.Uuid} has no pending 2fa enrolment`);
115
+ }
116
+ try {
117
+ await this.confirmEnrolment(user, token.Token);
118
+ }
119
+ catch (err) {
120
+ // `confirmUser2Fa` throws `Unauthorized` for a rejected code — the only
121
+ // case this route should report as "wrong code". Anything else (a
122
+ // database outage during activation, for instance) must surface as a
123
+ // 500, not be misreported to the user as a bad code.
124
+ if (!(err instanceof Unauthorized)) {
125
+ throw err;
126
+ }
127
+ this._log.warn(`2fa confirmation rejected for ${user.Uuid}`, { error: err });
128
+ return new ForbiddenResponse({
129
+ error: { code: 'E_2FA_FAILED', message: '2fa check failed' },
130
+ });
131
+ }
132
+ // No password here on purpose: the password was confirmed by the enable or
133
+ // reset call that issued this secret, and the code is itself the proof this
134
+ // route exists to demand.
135
+ return new Ok(null, await this.rotate(session));
136
+ }
78
137
  /**
79
138
  * Disable own two-factor authentication
80
139
  * Removes the TOTP secret and disables 2FA for the authenticated user.
@@ -86,8 +145,9 @@ let TwoFactorAuthUserController = class TwoFactorAuthUserController extends Base
86
145
  * @response 403 Forbidden — insufficient permissions
87
146
  */
88
147
  async disable(user, confirmation, session) {
148
+ this.assertSystemEnabled();
89
149
  if (!user.Metadata[TWO_FA_METATADATA_KEYS.ENABLED]) {
90
- throw new InvalidOperation(`User ${user.Uuid} already has 2fa disabled`);
150
+ throw new BadRequest(`User ${user.Uuid} already has 2fa disabled`);
91
151
  }
92
152
  const confirmed = await this.confirmPassword(user, confirmation.Password);
93
153
  if (confirmed !== true) {
@@ -96,6 +156,61 @@ let TwoFactorAuthUserController = class TwoFactorAuthUserController extends Base
96
156
  await this.unenrol(user);
97
157
  return new Ok(null, await this.rotate(session));
98
158
  }
159
+ /**
160
+ * Reset own two-factor authentication
161
+ * Removes the current TOTP device and issues a new secret in a single request,
162
+ * returning the provisioning URI to scan. 2FA stays off until the new code is
163
+ * confirmed with `POST /user/2fa/confirm`. Requires the account password.
164
+ *
165
+ * Folding disable-then-enable into one request removes the client-side gap
166
+ * where abandoning the flow between the two calls could strand the account
167
+ * without a second factor. It does not remove the server-side one: if
168
+ * `enrol` fails after `unenrol` already removed the old device, the account
169
+ * is left with no device at all — there is nothing to roll back to, since
170
+ * the old secret is already gone. That leaves the account in the ordinary
171
+ * `none` state, which every route here already handles: `GET /user/2fa`
172
+ * reports it and `POST /user/2fa/enable` starts a fresh enrolment.
173
+ *
174
+ * A caller who abandons before confirming is left password-only — the same
175
+ * gap as `enable`. The only built-in recovery is `rbac.twoFactorAuth.forceUser`
176
+ * pushing the account back through enrolment at next login, and this package's
177
+ * own default for that flag is `false` (see `src/config/rbac-http.ts`), so a
178
+ * consumer that has not set it will see the downgrade become silent and
179
+ * permanent rather than recovered.
180
+ * @security cookieAuth
181
+ * @returns {IEnable2faResponse} OTP provisioning URI to scan with an authenticator app
182
+ * @response 400 There is no two-factor device to reset
183
+ * @response 401 Unauthorized — valid session required, or password invalid
184
+ * @response 403 Forbidden — insufficient permissions
185
+ */
186
+ async reset(user, confirmation, session) {
187
+ this.assertSystemEnabled();
188
+ if (!user.Metadata[TWO_FA_METATADATA_KEYS.ENABLED] && !user.Metadata[TWO_FA_METATADATA_KEYS.TOKEN]) {
189
+ throw new BadRequest(`User ${user.Uuid} has no 2fa to reset`);
190
+ }
191
+ const confirmed = await this.confirmPassword(user, confirmation.Password);
192
+ if (confirmed !== true) {
193
+ return confirmed;
194
+ }
195
+ await this.unenrol(user);
196
+ const result = await this.enrol(user);
197
+ return new Ok({ otp: result }, await this.rotate(session));
198
+ }
199
+ /**
200
+ * Refuse a mutation while 2FA is switched off system-wide.
201
+ *
202
+ * The `TwoFactorAuthEnabled` policy cannot do this job: @spinajs/http merges
203
+ * a route's policies into one gate that passes when ANY of them resolves, and
204
+ * these routes also carry AuthorizedPolicy, which succeeds for every
205
+ * logged-in caller. The check has to live where it cannot be short-circuited.
206
+ */
207
+ assertSystemEnabled() {
208
+ if (this.TwoFactorConfig?.enabled === false) {
209
+ throw Object.assign(new Forbidden('2 factor auth is not enabled'), {
210
+ error: { code: 'E_2FA_SYSTEM_DISABLED', message: '2 factor auth is not enabled' },
211
+ });
212
+ }
213
+ }
99
214
  /**
100
215
  * Rotates the session id after the account's authentication requirements
101
216
  * changed, and returns the response options carrying the new cookie.
@@ -117,11 +232,16 @@ let TwoFactorAuthUserController = class TwoFactorAuthUserController extends Base
117
232
  };
118
233
  }
119
234
  /**
120
- * Generate and store the TOTP secret, returning the provisioning URI. Wraps
121
- * the module-level action so tests can stub it without a TOTP/DB setup.
235
+ * Store the TOTP secret and return the provisioning URI, leaving 2fa off until
236
+ * confirmed. Wraps the module-level action so tests can stub it without a
237
+ * TOTP/DB setup.
122
238
  */
123
239
  enrol(user) {
124
- return enableUser2Fa(user);
240
+ return beginUser2FaEnrolment(user);
241
+ }
242
+ /** Verify a code against the pending secret and switch 2fa on. */
243
+ confirmEnrolment(user, token) {
244
+ return confirmUser2Fa(user, token);
125
245
  }
126
246
  /** Clear the TOTP secret. Wrapped for the same reason as {@link enrol}. */
127
247
  unenrol(user) {
@@ -154,6 +274,10 @@ __decorate([
154
274
  Autoinject(SessionCookieFactory),
155
275
  __metadata("design:type", SessionCookieFactory)
156
276
  ], TwoFactorAuthUserController.prototype, "SessionCookies", void 0);
277
+ __decorate([
278
+ Config('rbac.twoFactorAuth'),
279
+ __metadata("design:type", Object)
280
+ ], TwoFactorAuthUserController.prototype, "TwoFactorConfig", void 0);
157
281
  __decorate([
158
282
  Get('2fa'),
159
283
  Permission(['readOwn']),
@@ -172,6 +296,16 @@ __decorate([
172
296
  __metadata("design:paramtypes", [UserModel, ConfirmPasswordDto, Object]),
173
297
  __metadata("design:returntype", Promise)
174
298
  ], TwoFactorAuthUserController.prototype, "enable", null);
299
+ __decorate([
300
+ Post('2fa/confirm'),
301
+ Permission(['updateOwn']),
302
+ __param(0, User()),
303
+ __param(1, Body()),
304
+ __param(2, SessionRouteArg()),
305
+ __metadata("design:type", Function),
306
+ __metadata("design:paramtypes", [UserModel, TokenDto, Object]),
307
+ __metadata("design:returntype", Promise)
308
+ ], TwoFactorAuthUserController.prototype, "confirm", null);
175
309
  __decorate([
176
310
  Post('2fa/disable'),
177
311
  Permission(['updateOwn']),
@@ -182,6 +316,16 @@ __decorate([
182
316
  __metadata("design:paramtypes", [UserModel, ConfirmPasswordDto, Object]),
183
317
  __metadata("design:returntype", Promise)
184
318
  ], TwoFactorAuthUserController.prototype, "disable", null);
319
+ __decorate([
320
+ Post('2fa/reset'),
321
+ Permission(['updateOwn']),
322
+ __param(0, User()),
323
+ __param(1, Body()),
324
+ __param(2, SessionRouteArg()),
325
+ __metadata("design:type", Function),
326
+ __metadata("design:paramtypes", [UserModel, ConfirmPasswordDto, Object]),
327
+ __metadata("design:returntype", Promise)
328
+ ], TwoFactorAuthUserController.prototype, "reset", null);
185
329
  TwoFactorAuthUserController = __decorate([
186
330
  BasePath('user'),
187
331
  Resource('user'),