@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.
- package/lib/cjs/config/rbac-http.d.ts +0 -15
- package/lib/cjs/config/rbac-http.d.ts.map +1 -1
- package/lib/cjs/config/rbac-http.js +6 -16
- package/lib/cjs/config/rbac-http.js.map +1 -1
- package/lib/cjs/controllers/ActiveRoleController.d.ts +2 -1
- package/lib/cjs/controllers/ActiveRoleController.d.ts.map +1 -1
- package/lib/cjs/controllers/ActiveRoleController.js +7 -17
- package/lib/cjs/controllers/ActiveRoleController.js.map +1 -1
- package/lib/cjs/controllers/ImpersonationController.d.ts +7 -9
- package/lib/cjs/controllers/ImpersonationController.d.ts.map +1 -1
- package/lib/cjs/controllers/ImpersonationController.js +31 -56
- package/lib/cjs/controllers/ImpersonationController.js.map +1 -1
- package/lib/cjs/controllers/LoginController.d.ts +10 -3
- package/lib/cjs/controllers/LoginController.d.ts.map +1 -1
- package/lib/cjs/controllers/LoginController.js +39 -40
- package/lib/cjs/controllers/LoginController.js.map +1 -1
- package/lib/cjs/controllers/PasswordResetController.d.ts +54 -0
- package/lib/cjs/controllers/PasswordResetController.d.ts.map +1 -0
- package/lib/cjs/controllers/PasswordResetController.js +129 -0
- package/lib/cjs/controllers/PasswordResetController.js.map +1 -0
- package/lib/cjs/controllers/SessionsController.d.ts +65 -0
- package/lib/cjs/controllers/SessionsController.d.ts.map +1 -0
- package/lib/cjs/controllers/SessionsController.js +143 -0
- package/lib/cjs/controllers/SessionsController.js.map +1 -0
- package/lib/cjs/controllers/TwoFactorAuthController.d.ts +24 -23
- package/lib/cjs/controllers/TwoFactorAuthController.d.ts.map +1 -1
- package/lib/cjs/controllers/TwoFactorAuthController.js +39 -72
- package/lib/cjs/controllers/TwoFactorAuthController.js.map +1 -1
- package/lib/cjs/controllers/TwoFactorAuthUserController.d.ts +98 -0
- package/lib/cjs/controllers/TwoFactorAuthUserController.d.ts.map +1 -0
- package/lib/cjs/controllers/TwoFactorAuthUserController.js +195 -0
- package/lib/cjs/controllers/TwoFactorAuthUserController.js.map +1 -0
- package/lib/cjs/controllers/UserController.d.ts +10 -7
- package/lib/cjs/controllers/UserController.d.ts.map +1 -1
- package/lib/cjs/controllers/UserController.js +56 -58
- package/lib/cjs/controllers/UserController.js.map +1 -1
- package/lib/cjs/controllers/UserMetadataController.d.ts +6 -12
- package/lib/cjs/controllers/UserMetadataController.d.ts.map +1 -1
- package/lib/cjs/controllers/UserMetadataController.js +23 -72
- package/lib/cjs/controllers/UserMetadataController.js.map +1 -1
- package/lib/cjs/dto/confirm-password-dto.d.ts +23 -0
- package/lib/cjs/dto/confirm-password-dto.d.ts.map +1 -0
- package/lib/cjs/dto/{restore-password-dto.js → confirm-password-dto.js} +16 -12
- package/lib/cjs/dto/confirm-password-dto.js.map +1 -0
- package/lib/cjs/dto/password-reset-dto.d.ts +58 -0
- package/lib/cjs/dto/password-reset-dto.d.ts.map +1 -0
- package/lib/cjs/dto/password-reset-dto.js +57 -0
- package/lib/cjs/dto/password-reset-dto.js.map +1 -0
- package/lib/cjs/handlers/DefaultLogoutHandler.d.ts +4 -1
- package/lib/cjs/handlers/DefaultLogoutHandler.d.ts.map +1 -1
- package/lib/cjs/handlers/DefaultLogoutHandler.js +20 -13
- package/lib/cjs/handlers/DefaultLogoutHandler.js.map +1 -1
- package/lib/cjs/handlers/ImpersonationLogoutHandler.d.ts +4 -7
- package/lib/cjs/handlers/ImpersonationLogoutHandler.d.ts.map +1 -1
- package/lib/cjs/handlers/ImpersonationLogoutHandler.js +30 -31
- package/lib/cjs/handlers/ImpersonationLogoutHandler.js.map +1 -1
- package/lib/cjs/index.d.ts +20 -0
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +22 -0
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/logout.d.ts +7 -0
- package/lib/cjs/logout.d.ts.map +1 -1
- package/lib/cjs/logout.js.map +1 -1
- package/lib/cjs/policies/2FaPolicy.d.ts +21 -1
- package/lib/cjs/policies/2FaPolicy.d.ts.map +1 -1
- package/lib/cjs/policies/2FaPolicy.js +31 -7
- package/lib/cjs/policies/2FaPolicy.js.map +1 -1
- package/lib/cjs/services/ImpersonationService.d.ts +90 -0
- package/lib/cjs/services/ImpersonationService.d.ts.map +1 -0
- package/lib/cjs/services/ImpersonationService.js +147 -0
- package/lib/cjs/services/ImpersonationService.js.map +1 -0
- package/lib/cjs/services/SessionCookies.d.ts +41 -0
- package/lib/cjs/services/SessionCookies.d.ts.map +1 -0
- package/lib/cjs/services/SessionCookies.js +62 -0
- package/lib/cjs/services/SessionCookies.js.map +1 -0
- package/lib/cjs/services/UserMetadataService.d.ts +53 -0
- package/lib/cjs/services/UserMetadataService.d.ts.map +1 -0
- package/lib/cjs/services/UserMetadataService.js +94 -0
- package/lib/cjs/services/UserMetadataService.js.map +1 -0
- package/lib/cjs/services/grants.d.ts +29 -0
- package/lib/cjs/services/grants.d.ts.map +1 -0
- package/lib/cjs/services/grants.js +43 -0
- package/lib/cjs/services/grants.js.map +1 -0
- package/lib/mjs/config/rbac-http.d.ts +0 -15
- package/lib/mjs/config/rbac-http.d.ts.map +1 -1
- package/lib/mjs/config/rbac-http.js +6 -16
- package/lib/mjs/config/rbac-http.js.map +1 -1
- package/lib/mjs/controllers/ActiveRoleController.d.ts +2 -1
- package/lib/mjs/controllers/ActiveRoleController.d.ts.map +1 -1
- package/lib/mjs/controllers/ActiveRoleController.js +8 -18
- package/lib/mjs/controllers/ActiveRoleController.js.map +1 -1
- package/lib/mjs/controllers/ImpersonationController.d.ts +7 -9
- package/lib/mjs/controllers/ImpersonationController.d.ts.map +1 -1
- package/lib/mjs/controllers/ImpersonationController.js +32 -57
- package/lib/mjs/controllers/ImpersonationController.js.map +1 -1
- package/lib/mjs/controllers/LoginController.d.ts +10 -3
- package/lib/mjs/controllers/LoginController.d.ts.map +1 -1
- package/lib/mjs/controllers/LoginController.js +42 -43
- package/lib/mjs/controllers/LoginController.js.map +1 -1
- package/lib/mjs/controllers/PasswordResetController.d.ts +54 -0
- package/lib/mjs/controllers/PasswordResetController.d.ts.map +1 -0
- package/lib/mjs/controllers/PasswordResetController.js +126 -0
- package/lib/mjs/controllers/PasswordResetController.js.map +1 -0
- package/lib/mjs/controllers/SessionsController.d.ts +65 -0
- package/lib/mjs/controllers/SessionsController.d.ts.map +1 -0
- package/lib/mjs/controllers/SessionsController.js +140 -0
- package/lib/mjs/controllers/SessionsController.js.map +1 -0
- package/lib/mjs/controllers/TwoFactorAuthController.d.ts +24 -23
- package/lib/mjs/controllers/TwoFactorAuthController.d.ts.map +1 -1
- package/lib/mjs/controllers/TwoFactorAuthController.js +43 -76
- package/lib/mjs/controllers/TwoFactorAuthController.js.map +1 -1
- package/lib/mjs/controllers/TwoFactorAuthUserController.d.ts +98 -0
- package/lib/mjs/controllers/TwoFactorAuthUserController.d.ts.map +1 -0
- package/lib/mjs/controllers/TwoFactorAuthUserController.js +192 -0
- package/lib/mjs/controllers/TwoFactorAuthUserController.js.map +1 -0
- package/lib/mjs/controllers/UserController.d.ts +10 -7
- package/lib/mjs/controllers/UserController.d.ts.map +1 -1
- package/lib/mjs/controllers/UserController.js +59 -28
- package/lib/mjs/controllers/UserController.js.map +1 -1
- package/lib/mjs/controllers/UserMetadataController.d.ts +6 -12
- package/lib/mjs/controllers/UserMetadataController.d.ts.map +1 -1
- package/lib/mjs/controllers/UserMetadataController.js +23 -72
- package/lib/mjs/controllers/UserMetadataController.js.map +1 -1
- package/lib/mjs/dto/confirm-password-dto.d.ts +23 -0
- package/lib/mjs/dto/confirm-password-dto.d.ts.map +1 -0
- package/lib/mjs/dto/{user-dto.js → confirm-password-dto.js} +15 -10
- package/lib/mjs/dto/confirm-password-dto.js.map +1 -0
- package/lib/mjs/dto/password-reset-dto.d.ts +58 -0
- package/lib/mjs/dto/password-reset-dto.d.ts.map +1 -0
- package/lib/mjs/dto/password-reset-dto.js +54 -0
- package/lib/mjs/dto/password-reset-dto.js.map +1 -0
- package/lib/mjs/handlers/DefaultLogoutHandler.d.ts +4 -1
- package/lib/mjs/handlers/DefaultLogoutHandler.d.ts.map +1 -1
- package/lib/mjs/handlers/DefaultLogoutHandler.js +23 -16
- package/lib/mjs/handlers/DefaultLogoutHandler.js.map +1 -1
- package/lib/mjs/handlers/ImpersonationLogoutHandler.d.ts +4 -7
- package/lib/mjs/handlers/ImpersonationLogoutHandler.d.ts.map +1 -1
- package/lib/mjs/handlers/ImpersonationLogoutHandler.js +31 -32
- package/lib/mjs/handlers/ImpersonationLogoutHandler.js.map +1 -1
- package/lib/mjs/index.d.ts +20 -0
- package/lib/mjs/index.d.ts.map +1 -1
- package/lib/mjs/index.js +22 -0
- package/lib/mjs/index.js.map +1 -1
- package/lib/mjs/logout.d.ts +7 -0
- package/lib/mjs/logout.d.ts.map +1 -1
- package/lib/mjs/logout.js.map +1 -1
- package/lib/mjs/policies/2FaPolicy.d.ts +21 -1
- package/lib/mjs/policies/2FaPolicy.d.ts.map +1 -1
- package/lib/mjs/policies/2FaPolicy.js +29 -6
- package/lib/mjs/policies/2FaPolicy.js.map +1 -1
- package/lib/mjs/services/ImpersonationService.d.ts +90 -0
- package/lib/mjs/services/ImpersonationService.d.ts.map +1 -0
- package/lib/mjs/services/ImpersonationService.js +144 -0
- package/lib/mjs/services/ImpersonationService.js.map +1 -0
- package/lib/mjs/services/SessionCookies.d.ts +41 -0
- package/lib/mjs/services/SessionCookies.d.ts.map +1 -0
- package/lib/mjs/services/SessionCookies.js +59 -0
- package/lib/mjs/services/SessionCookies.js.map +1 -0
- package/lib/mjs/services/UserMetadataService.d.ts +53 -0
- package/lib/mjs/services/UserMetadataService.d.ts.map +1 -0
- package/lib/mjs/services/UserMetadataService.js +91 -0
- package/lib/mjs/services/UserMetadataService.js.map +1 -0
- package/lib/mjs/services/grants.d.ts +29 -0
- package/lib/mjs/services/grants.d.ts.map +1 -0
- package/lib/mjs/services/grants.js +38 -0
- package/lib/mjs/services/grants.js.map +1 -0
- package/lib/tsconfig.cjs.tsbuildinfo +1 -1
- package/lib/tsconfig.mjs.tsbuildinfo +1 -1
- package/package.json +17 -11
- package/lib/cjs/controllers/AuthController.d.ts +0 -1
- package/lib/cjs/controllers/AuthController.d.ts.map +0 -1
- package/lib/cjs/controllers/AuthController.js +0 -211
- package/lib/cjs/controllers/AuthController.js.map +0 -1
- package/lib/cjs/controllers/FederatedLoginController.d.ts +0 -1
- package/lib/cjs/controllers/FederatedLoginController.d.ts.map +0 -1
- package/lib/cjs/controllers/FederatedLoginController.js +0 -38
- package/lib/cjs/controllers/FederatedLoginController.js.map +0 -1
- package/lib/cjs/dto/login-dto.d.ts +0 -17
- package/lib/cjs/dto/login-dto.d.ts.map +0 -1
- package/lib/cjs/dto/login-dto.js +0 -33
- package/lib/cjs/dto/login-dto.js.map +0 -1
- package/lib/cjs/dto/restore-password-dto.d.ts +0 -24
- package/lib/cjs/dto/restore-password-dto.d.ts.map +0 -1
- package/lib/cjs/dto/restore-password-dto.js.map +0 -1
- package/lib/cjs/dto/user-dto.d.ts +0 -22
- package/lib/cjs/dto/user-dto.d.ts.map +0 -1
- package/lib/cjs/dto/user-dto.js +0 -33
- package/lib/cjs/dto/user-dto.js.map +0 -1
- package/lib/cjs/fingerprint/FingerprintJs.d.ts +0 -1
- package/lib/cjs/fingerprint/FingerprintJs.d.ts.map +0 -1
- package/lib/cjs/fingerprint/FingerprintJs.js +0 -5
- package/lib/cjs/fingerprint/FingerprintJs.js.map +0 -1
- package/lib/cjs/middlewares/AttributeFilter.d.ts +0 -11
- package/lib/cjs/middlewares/AttributeFilter.d.ts.map +0 -1
- package/lib/cjs/middlewares/AttributeFilter.js +0 -19
- package/lib/cjs/middlewares/AttributeFilter.js.map +0 -1
- package/lib/cjs/policies/AllowFederatedLoginPolicy.d.ts +0 -7
- package/lib/cjs/policies/AllowFederatedLoginPolicy.d.ts.map +0 -1
- package/lib/cjs/policies/AllowFederatedLoginPolicy.js +0 -32
- package/lib/cjs/policies/AllowFederatedLoginPolicy.js.map +0 -1
- package/lib/cjs/policies/CaptchaPolicy.d.ts +0 -1
- package/lib/cjs/policies/CaptchaPolicy.d.ts.map +0 -1
- package/lib/cjs/policies/CaptchaPolicy.js +0 -1
- package/lib/cjs/policies/CaptchaPolicy.js.map +0 -1
- package/lib/mjs/controllers/AuthController.d.ts +0 -2
- package/lib/mjs/controllers/AuthController.d.ts.map +0 -1
- package/lib/mjs/controllers/AuthController.js +0 -212
- package/lib/mjs/controllers/AuthController.js.map +0 -1
- package/lib/mjs/controllers/FederatedLoginController.d.ts +0 -2
- package/lib/mjs/controllers/FederatedLoginController.d.ts.map +0 -1
- package/lib/mjs/controllers/FederatedLoginController.js +0 -39
- package/lib/mjs/controllers/FederatedLoginController.js.map +0 -1
- package/lib/mjs/dto/login-dto.d.ts +0 -17
- package/lib/mjs/dto/login-dto.d.ts.map +0 -1
- package/lib/mjs/dto/login-dto.js +0 -30
- package/lib/mjs/dto/login-dto.js.map +0 -1
- package/lib/mjs/dto/restore-password-dto.d.ts +0 -24
- package/lib/mjs/dto/restore-password-dto.d.ts.map +0 -1
- package/lib/mjs/dto/restore-password-dto.js +0 -31
- package/lib/mjs/dto/restore-password-dto.js.map +0 -1
- package/lib/mjs/dto/user-dto.d.ts +0 -22
- package/lib/mjs/dto/user-dto.d.ts.map +0 -1
- package/lib/mjs/dto/user-dto.js.map +0 -1
- package/lib/mjs/fingerprint/FingerprintJs.d.ts +0 -2
- package/lib/mjs/fingerprint/FingerprintJs.d.ts.map +0 -1
- package/lib/mjs/fingerprint/FingerprintJs.js +0 -6
- package/lib/mjs/fingerprint/FingerprintJs.js.map +0 -1
- package/lib/mjs/middlewares/AttributeFilter.d.ts +0 -11
- package/lib/mjs/middlewares/AttributeFilter.d.ts.map +0 -1
- package/lib/mjs/middlewares/AttributeFilter.js +0 -15
- package/lib/mjs/middlewares/AttributeFilter.js.map +0 -1
- package/lib/mjs/policies/AllowFederatedLoginPolicy.d.ts +0 -7
- package/lib/mjs/policies/AllowFederatedLoginPolicy.d.ts.map +0 -1
- package/lib/mjs/policies/AllowFederatedLoginPolicy.js +0 -28
- package/lib/mjs/policies/AllowFederatedLoginPolicy.js.map +0 -1
- package/lib/mjs/policies/CaptchaPolicy.d.ts +0 -2
- package/lib/mjs/policies/CaptchaPolicy.d.ts.map +0 -1
- package/lib/mjs/policies/CaptchaPolicy.js +0 -2
- package/lib/mjs/policies/CaptchaPolicy.js.map +0 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { BaseController, BadRequestResponse, Ok } from '@spinajs/http';
|
|
2
|
+
import { PasswordResetConfirmDto, PasswordResetRequestDto } from '../dto/password-reset-dto.js';
|
|
3
|
+
/** Reply shared by both reset routes — deliberately says nothing about the account. */
|
|
4
|
+
export interface IPasswordResetAck {
|
|
5
|
+
Ok: true;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Password reset for users who cannot log in.
|
|
9
|
+
*
|
|
10
|
+
* Two public, unauthenticated steps: request a token, then redeem it. The token
|
|
11
|
+
* itself is issued into the user's metadata by `passwordChangeRequest` and
|
|
12
|
+
* delivered out of band (the `UserPasswordChangeRequest` event is what an app
|
|
13
|
+
* hooks to send the mail) — it is never returned over HTTP, so possession of
|
|
14
|
+
* the mailbox is what authorizes the reset.
|
|
15
|
+
*
|
|
16
|
+
* Neither route reveals whether an account exists. Both answer the same way for
|
|
17
|
+
* a known and an unknown address, which is the whole point of a reset endpoint
|
|
18
|
+
* that anybody may call: an attacker must not be able to farm valid addresses
|
|
19
|
+
* from it, and a failed redemption must not distinguish "no such user" from
|
|
20
|
+
* "wrong token".
|
|
21
|
+
*
|
|
22
|
+
* NOTE: these routes are unauthenticated and cheap to call in a loop. Put a
|
|
23
|
+
* rate limit in front of them at the edge — this package does not provide one.
|
|
24
|
+
*
|
|
25
|
+
* @tags Authentication
|
|
26
|
+
*/
|
|
27
|
+
export declare class PasswordResetController extends BaseController {
|
|
28
|
+
/**
|
|
29
|
+
* Request a password reset
|
|
30
|
+
* Issues a reset token for the account and emits `UserPasswordChangeRequest` so the
|
|
31
|
+
* application can deliver it. Always succeeds, whether or not the address is known.
|
|
32
|
+
* @security []
|
|
33
|
+
* @returns {IPasswordResetAck} Acknowledgement — never indicates whether the account exists
|
|
34
|
+
*/
|
|
35
|
+
requestReset(payload: PasswordResetRequestDto): Promise<Ok<IPasswordResetAck>>;
|
|
36
|
+
/**
|
|
37
|
+
* Complete a password reset
|
|
38
|
+
* Redeems the token issued by `POST /auth/password/reset-request` and sets the new
|
|
39
|
+
* password. The token is single-use and expires after `rbac.password.passwordResetWaitTime`.
|
|
40
|
+
* @security []
|
|
41
|
+
* @returns {IPasswordResetAck} Acknowledgement of a successful reset
|
|
42
|
+
* @response 400 New passwords do not match, or the token is invalid or expired
|
|
43
|
+
*/
|
|
44
|
+
confirmReset(payload: PasswordResetConfirmDto): Promise<Ok<IPasswordResetAck> | BadRequestResponse>;
|
|
45
|
+
/**
|
|
46
|
+
* Issue a reset token into the user's metadata and emit
|
|
47
|
+
* `UserPasswordChangeRequest`. Wraps the module-level action so tests can
|
|
48
|
+
* stub it without a database.
|
|
49
|
+
*/
|
|
50
|
+
protected issueToken(email: string): Promise<unknown>;
|
|
51
|
+
/** Redeem a reset token. Wrapped for the same reason as {@link issueToken}. */
|
|
52
|
+
protected redeemToken(email: string, password: string, token: string): Promise<unknown>;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=PasswordResetController.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PasswordResetController.d.ts","sourceRoot":"","sources":["../../../src/controllers/PasswordResetController.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAkB,kBAAkB,EAAE,EAAE,EAAQ,MAAM,eAAe,CAAC;AAI7F,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEhG,uFAAuF;AACvF,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,IAAI,CAAC;CACV;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBACa,uBAAwB,SAAQ,cAAc;IACzD;;;;;;OAMG;IAGU,YAAY,CAAS,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC;IAcnG;;;;;;;OAOG;IAGU,YAAY,CAAS,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,EAAE,CAAC,iBAAiB,CAAC,GAAG,kBAAkB,CAAC;IA+BxH;;;;OAIG;IACH,SAAS,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIrD,+EAA+E;IAC/E,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAGxF"}
|
|
@@ -0,0 +1,126 @@
|
|
|
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
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
import { BaseController, BasePath, Body, BadRequestResponse, Ok, Post } from '@spinajs/http';
|
|
14
|
+
import { confirmPasswordReset, passwordChangeRequest } from '@spinajs/rbac';
|
|
15
|
+
import { ErrorCode, InvalidArgument } from '@spinajs/exceptions';
|
|
16
|
+
import { SkipModelPermission } from '@spinajs/rbac-http';
|
|
17
|
+
import { PasswordResetConfirmDto, PasswordResetRequestDto } from '../dto/password-reset-dto.js';
|
|
18
|
+
/**
|
|
19
|
+
* Password reset for users who cannot log in.
|
|
20
|
+
*
|
|
21
|
+
* Two public, unauthenticated steps: request a token, then redeem it. The token
|
|
22
|
+
* itself is issued into the user's metadata by `passwordChangeRequest` and
|
|
23
|
+
* delivered out of band (the `UserPasswordChangeRequest` event is what an app
|
|
24
|
+
* hooks to send the mail) — it is never returned over HTTP, so possession of
|
|
25
|
+
* the mailbox is what authorizes the reset.
|
|
26
|
+
*
|
|
27
|
+
* Neither route reveals whether an account exists. Both answer the same way for
|
|
28
|
+
* a known and an unknown address, which is the whole point of a reset endpoint
|
|
29
|
+
* that anybody may call: an attacker must not be able to farm valid addresses
|
|
30
|
+
* from it, and a failed redemption must not distinguish "no such user" from
|
|
31
|
+
* "wrong token".
|
|
32
|
+
*
|
|
33
|
+
* NOTE: these routes are unauthenticated and cheap to call in a loop. Put a
|
|
34
|
+
* rate limit in front of them at the edge — this package does not provide one.
|
|
35
|
+
*
|
|
36
|
+
* @tags Authentication
|
|
37
|
+
*/
|
|
38
|
+
let PasswordResetController = class PasswordResetController extends BaseController {
|
|
39
|
+
/**
|
|
40
|
+
* Request a password reset
|
|
41
|
+
* Issues a reset token for the account and emits `UserPasswordChangeRequest` so the
|
|
42
|
+
* application can deliver it. Always succeeds, whether or not the address is known.
|
|
43
|
+
* @security []
|
|
44
|
+
* @returns {IPasswordResetAck} Acknowledgement — never indicates whether the account exists
|
|
45
|
+
*/
|
|
46
|
+
async requestReset(payload) {
|
|
47
|
+
try {
|
|
48
|
+
await this.issueToken(payload.Email);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
// Swallowed on purpose. An unknown address, a deleted account or a
|
|
52
|
+
// banned one must produce exactly the response a valid address does —
|
|
53
|
+
// otherwise this route becomes an account enumeration oracle. Real
|
|
54
|
+
// faults ( mail queue, database ) are logged for operators.
|
|
55
|
+
this._log.warn(err, `Password reset request could not be processed for ${payload.Email}`);
|
|
56
|
+
}
|
|
57
|
+
return new Ok({ Ok: true });
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Complete a password reset
|
|
61
|
+
* Redeems the token issued by `POST /auth/password/reset-request` and sets the new
|
|
62
|
+
* password. The token is single-use and expires after `rbac.password.passwordResetWaitTime`.
|
|
63
|
+
* @security []
|
|
64
|
+
* @returns {IPasswordResetAck} Acknowledgement of a successful reset
|
|
65
|
+
* @response 400 New passwords do not match, or the token is invalid or expired
|
|
66
|
+
*/
|
|
67
|
+
async confirmReset(payload) {
|
|
68
|
+
if (payload.Password !== payload.ConfirmPassword) {
|
|
69
|
+
throw new InvalidArgument('password does not match');
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
await this.redeemToken(payload.Email, payload.Password, payload.Token);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
// One opaque failure for every rejection reason: unknown account, wrong
|
|
76
|
+
// token, expired token. Distinguishing them would tell a caller which
|
|
77
|
+
// addresses exist and let them probe tokens by the error they get back.
|
|
78
|
+
const isRedemptionFailure = err instanceof ErrorCode || err instanceof InvalidArgument;
|
|
79
|
+
if (!isRedemptionFailure) {
|
|
80
|
+
this._log.error(err, `Password reset failed unexpectedly for ${payload.Email}`);
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
this._log.warn(err, `Password reset rejected for ${payload.Email}`);
|
|
84
|
+
return new BadRequestResponse({
|
|
85
|
+
error: {
|
|
86
|
+
code: 'E_RESET_TOKEN_INVALID',
|
|
87
|
+
message: 'Password reset token is invalid or has expired',
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return new Ok({ Ok: true });
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Issue a reset token into the user's metadata and emit
|
|
95
|
+
* `UserPasswordChangeRequest`. Wraps the module-level action so tests can
|
|
96
|
+
* stub it without a database.
|
|
97
|
+
*/
|
|
98
|
+
issueToken(email) {
|
|
99
|
+
return passwordChangeRequest(email);
|
|
100
|
+
}
|
|
101
|
+
/** Redeem a reset token. Wrapped for the same reason as {@link issueToken}. */
|
|
102
|
+
redeemToken(email, password, token) {
|
|
103
|
+
return confirmPasswordReset(email, password, token);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
__decorate([
|
|
107
|
+
Post('password/reset-request'),
|
|
108
|
+
SkipModelPermission(),
|
|
109
|
+
__param(0, Body()),
|
|
110
|
+
__metadata("design:type", Function),
|
|
111
|
+
__metadata("design:paramtypes", [PasswordResetRequestDto]),
|
|
112
|
+
__metadata("design:returntype", Promise)
|
|
113
|
+
], PasswordResetController.prototype, "requestReset", null);
|
|
114
|
+
__decorate([
|
|
115
|
+
Post('password/reset'),
|
|
116
|
+
SkipModelPermission(),
|
|
117
|
+
__param(0, Body()),
|
|
118
|
+
__metadata("design:type", Function),
|
|
119
|
+
__metadata("design:paramtypes", [PasswordResetConfirmDto]),
|
|
120
|
+
__metadata("design:returntype", Promise)
|
|
121
|
+
], PasswordResetController.prototype, "confirmReset", null);
|
|
122
|
+
PasswordResetController = __decorate([
|
|
123
|
+
BasePath('auth')
|
|
124
|
+
], PasswordResetController);
|
|
125
|
+
export { PasswordResetController };
|
|
126
|
+
//# sourceMappingURL=PasswordResetController.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PasswordResetController.js","sourceRoot":"","sources":["../../../src/controllers/PasswordResetController.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { BaseController, NotFound, Ok } from '@spinajs/http';
|
|
2
|
+
import { SessionProvider, User as UserModel } from '@spinajs/rbac';
|
|
3
|
+
import type { ISession } from '@spinajs/rbac';
|
|
4
|
+
import { Log } from '@spinajs/log';
|
|
5
|
+
/** One live session of the calling user. */
|
|
6
|
+
export interface IUserSessionEntry {
|
|
7
|
+
/**
|
|
8
|
+
* Opaque, stable handle for the session. Deliberately NOT the session id:
|
|
9
|
+
* listing your own sessions must not hand out working credentials for them,
|
|
10
|
+
* because the response ( and anything that caches or logs it ) would then be
|
|
11
|
+
* as good as the cookies themselves.
|
|
12
|
+
*/
|
|
13
|
+
Handle: string;
|
|
14
|
+
/** ISO instant the session was opened */
|
|
15
|
+
Created: string;
|
|
16
|
+
/** ISO instant the session expires, or null when it never does */
|
|
17
|
+
Expires: string | null;
|
|
18
|
+
/** True for the session making this request */
|
|
19
|
+
Current: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Active sessions of the calling user — "where am I logged in", plus the means
|
|
23
|
+
* to end any of them.
|
|
24
|
+
*
|
|
25
|
+
* OWASP asks for exactly this: the user should be able to see concurrent
|
|
26
|
+
* sessions and terminate them remotely, which is how a person who suspects a
|
|
27
|
+
* stolen cookie gets rid of it without an administrator.
|
|
28
|
+
*
|
|
29
|
+
* @tags User
|
|
30
|
+
*/
|
|
31
|
+
export declare class SessionsController extends BaseController {
|
|
32
|
+
protected Log: Log;
|
|
33
|
+
protected SessionProvider: SessionProvider;
|
|
34
|
+
/**
|
|
35
|
+
* List own sessions
|
|
36
|
+
* Returns every live session of the authenticated user, newest first, with the
|
|
37
|
+
* one making the request flagged.
|
|
38
|
+
* @security cookieAuth
|
|
39
|
+
* @returns {IUserSessionEntry[]} Live sessions of the calling user
|
|
40
|
+
* @response 401 Unauthorized — valid session required
|
|
41
|
+
*/
|
|
42
|
+
list(user: UserModel, ssid: string): Promise<Ok<IUserSessionEntry[]>>;
|
|
43
|
+
/**
|
|
44
|
+
* Revoke one session
|
|
45
|
+
* Ends the session identified by its handle. Revoking the current session is
|
|
46
|
+
* allowed and logs the caller out.
|
|
47
|
+
* @security cookieAuth
|
|
48
|
+
* @param handle Session handle as returned by `GET /user/sessions`
|
|
49
|
+
* @response 200 Session revoked
|
|
50
|
+
* @response 401 Unauthorized — valid session required
|
|
51
|
+
* @response 404 No such session for this user
|
|
52
|
+
*/
|
|
53
|
+
revoke(user: UserModel, handle: string): Promise<Ok | NotFound>;
|
|
54
|
+
/**
|
|
55
|
+
* Revoke every other session
|
|
56
|
+
* Ends all sessions of the user except the one making the request — the
|
|
57
|
+
* "log out everywhere else" action after a suspected compromise.
|
|
58
|
+
* @security cookieAuth
|
|
59
|
+
* @response 200 Other sessions revoked
|
|
60
|
+
* @response 401 Unauthorized — valid session required
|
|
61
|
+
*/
|
|
62
|
+
revokeOthers(user: UserModel, ssid: string): Promise<Ok>;
|
|
63
|
+
protected toEntry(session: ISession, currentHandle: string | null): IUserSessionEntry;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=SessionsController.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SessionsController.d.ts","sourceRoot":"","sources":["../../../src/controllers/SessionsController.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAsB,QAAQ,EAAE,EAAE,EAAiB,MAAM,eAAe,CAAC;AAChG,OAAO,EAAE,eAAe,EAAE,IAAI,IAAI,SAAS,EAAiB,MAAM,eAAe,CAAC;AAClF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EAAE,GAAG,EAAU,MAAM,cAAc,CAAC;AAG3C,4CAA4C;AAC5C,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAEhB,kEAAkE;IAClE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAEvB,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;GASG;AACH,qBAGa,kBAAmB,SAAQ,cAAc;IAEpD,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;IAGnB,SAAS,CAAC,eAAe,EAAE,eAAe,CAAC;IAE3C;;;;;;;OAOG;IAGU,IAAI,CAAS,IAAI,EAAE,SAAS,EAAkB,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC,iBAAiB,EAAE,CAAC,CAAC;IAW1G;;;;;;;;;OASG;IAGU,MAAM,CAAS,IAAI,EAAE,SAAS,EAAW,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,EAAE,GAAG,QAAQ,CAAC;IAkB7F;;;;;;;OAOG;IAGU,YAAY,CAAS,IAAI,EAAE,SAAS,EAAkB,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC;IAkB7F,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,iBAAiB;CAUtF"}
|
|
@@ -0,0 +1,140 @@
|
|
|
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
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
import { BaseController, BasePath, Del, Get, NotFound, Ok, Param, Policy } from '@spinajs/http';
|
|
14
|
+
import { SessionProvider, User as UserModel, hashSessionId } from '@spinajs/rbac';
|
|
15
|
+
import { AutoinjectService } from '@spinajs/configuration';
|
|
16
|
+
import { Log, Logger } from '@spinajs/log';
|
|
17
|
+
import { AuthorizedPolicy, Permission, Resource, SessionId as SessionIdArg, User } from '@spinajs/rbac-http';
|
|
18
|
+
/**
|
|
19
|
+
* Active sessions of the calling user — "where am I logged in", plus the means
|
|
20
|
+
* to end any of them.
|
|
21
|
+
*
|
|
22
|
+
* OWASP asks for exactly this: the user should be able to see concurrent
|
|
23
|
+
* sessions and terminate them remotely, which is how a person who suspects a
|
|
24
|
+
* stolen cookie gets rid of it without an administrator.
|
|
25
|
+
*
|
|
26
|
+
* @tags User
|
|
27
|
+
*/
|
|
28
|
+
let SessionsController = class SessionsController extends BaseController {
|
|
29
|
+
/**
|
|
30
|
+
* List own sessions
|
|
31
|
+
* Returns every live session of the authenticated user, newest first, with the
|
|
32
|
+
* one making the request flagged.
|
|
33
|
+
* @security cookieAuth
|
|
34
|
+
* @returns {IUserSessionEntry[]} Live sessions of the calling user
|
|
35
|
+
* @response 401 Unauthorized — valid session required
|
|
36
|
+
*/
|
|
37
|
+
async list(user, ssid) {
|
|
38
|
+
const sessions = await this.SessionProvider.listByUser(user.Id);
|
|
39
|
+
const current = ssid ? hashSessionId(ssid) : null;
|
|
40
|
+
const entries = sessions
|
|
41
|
+
.map((s) => this.toEntry(s, current))
|
|
42
|
+
.sort((a, b) => (a.Created < b.Created ? 1 : -1));
|
|
43
|
+
return new Ok(entries);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Revoke one session
|
|
47
|
+
* Ends the session identified by its handle. Revoking the current session is
|
|
48
|
+
* allowed and logs the caller out.
|
|
49
|
+
* @security cookieAuth
|
|
50
|
+
* @param handle Session handle as returned by `GET /user/sessions`
|
|
51
|
+
* @response 200 Session revoked
|
|
52
|
+
* @response 401 Unauthorized — valid session required
|
|
53
|
+
* @response 404 No such session for this user
|
|
54
|
+
*/
|
|
55
|
+
async revoke(user, handle) {
|
|
56
|
+
// Resolved through the user's OWN session list, so a handle belonging to
|
|
57
|
+
// somebody else's session simply is not found here — the route cannot be
|
|
58
|
+
// used to end a stranger's session even if its handle leaks.
|
|
59
|
+
const sessions = await this.SessionProvider.listByUser(user.Id);
|
|
60
|
+
const match = sessions.find((s) => hashSessionId(s.SessionId) === handle);
|
|
61
|
+
if (!match) {
|
|
62
|
+
return new NotFound({ error: { code: 'E_SESSION_NOT_FOUND', message: 'No such session' } });
|
|
63
|
+
}
|
|
64
|
+
await this.SessionProvider.delete(match.SessionId);
|
|
65
|
+
this.Log.info(`Session revoked by its owner`, { Session: handle, User: user.Uuid });
|
|
66
|
+
return new Ok(null, { Headers: [{ Name: 'Cache-Control', Value: 'no-store' }] });
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Revoke every other session
|
|
70
|
+
* Ends all sessions of the user except the one making the request — the
|
|
71
|
+
* "log out everywhere else" action after a suspected compromise.
|
|
72
|
+
* @security cookieAuth
|
|
73
|
+
* @response 200 Other sessions revoked
|
|
74
|
+
* @response 401 Unauthorized — valid session required
|
|
75
|
+
*/
|
|
76
|
+
async revokeOthers(user, ssid) {
|
|
77
|
+
const sessions = await this.SessionProvider.listByUser(user.Id);
|
|
78
|
+
let revoked = 0;
|
|
79
|
+
for (const session of sessions) {
|
|
80
|
+
if (session.SessionId === ssid) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
await this.SessionProvider.delete(session.SessionId);
|
|
84
|
+
revoked++;
|
|
85
|
+
}
|
|
86
|
+
this.Log.info(`Revoked ${revoked} other session(s)`, { User: user.Uuid });
|
|
87
|
+
return new Ok({ Revoked: revoked }, { Headers: [{ Name: 'Cache-Control', Value: 'no-store' }] });
|
|
88
|
+
}
|
|
89
|
+
toEntry(session, currentHandle) {
|
|
90
|
+
const handle = hashSessionId(session.SessionId);
|
|
91
|
+
return {
|
|
92
|
+
Handle: handle,
|
|
93
|
+
Created: session.Creation?.toISO() ?? '',
|
|
94
|
+
Expires: session.Expiration?.toISO() ?? null,
|
|
95
|
+
Current: handle === currentHandle,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
__decorate([
|
|
100
|
+
Logger('rbac-session'),
|
|
101
|
+
__metadata("design:type", Log)
|
|
102
|
+
], SessionsController.prototype, "Log", void 0);
|
|
103
|
+
__decorate([
|
|
104
|
+
AutoinjectService('rbac.session'),
|
|
105
|
+
__metadata("design:type", SessionProvider)
|
|
106
|
+
], SessionsController.prototype, "SessionProvider", void 0);
|
|
107
|
+
__decorate([
|
|
108
|
+
Get('sessions'),
|
|
109
|
+
Permission(['readOwn']),
|
|
110
|
+
__param(0, User()),
|
|
111
|
+
__param(1, SessionIdArg()),
|
|
112
|
+
__metadata("design:type", Function),
|
|
113
|
+
__metadata("design:paramtypes", [UserModel, String]),
|
|
114
|
+
__metadata("design:returntype", Promise)
|
|
115
|
+
], SessionsController.prototype, "list", null);
|
|
116
|
+
__decorate([
|
|
117
|
+
Del('sessions/:handle'),
|
|
118
|
+
Permission(['updateOwn']),
|
|
119
|
+
__param(0, User()),
|
|
120
|
+
__param(1, Param()),
|
|
121
|
+
__metadata("design:type", Function),
|
|
122
|
+
__metadata("design:paramtypes", [UserModel, String]),
|
|
123
|
+
__metadata("design:returntype", Promise)
|
|
124
|
+
], SessionsController.prototype, "revoke", null);
|
|
125
|
+
__decorate([
|
|
126
|
+
Del('sessions'),
|
|
127
|
+
Permission(['updateOwn']),
|
|
128
|
+
__param(0, User()),
|
|
129
|
+
__param(1, SessionIdArg()),
|
|
130
|
+
__metadata("design:type", Function),
|
|
131
|
+
__metadata("design:paramtypes", [UserModel, String]),
|
|
132
|
+
__metadata("design:returntype", Promise)
|
|
133
|
+
], SessionsController.prototype, "revokeOthers", null);
|
|
134
|
+
SessionsController = __decorate([
|
|
135
|
+
BasePath('user'),
|
|
136
|
+
Resource('user'),
|
|
137
|
+
Policy(AuthorizedPolicy)
|
|
138
|
+
], SessionsController);
|
|
139
|
+
export { SessionsController };
|
|
140
|
+
//# sourceMappingURL=SessionsController.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SessionsController.js","sourceRoot":"","sources":["../../../src/controllers/SessionsController.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,41 +1,42 @@
|
|
|
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 {
|
|
5
|
-
import {
|
|
4
|
+
import { IEnable2faResponse, IUserWithGrants } from '@spinajs/rbac-http';
|
|
5
|
+
import { SessionCookieFactory } from '../services/SessionCookies.js';
|
|
6
6
|
/**
|
|
7
|
-
* Two-factor authentication
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* Two-factor authentication during login.
|
|
8
|
+
*
|
|
9
|
+
* These routes serve the narrow window between a successful password check and
|
|
10
|
+
* full authorization: the session is `Logged` but not `Authorized` and carries
|
|
11
|
+
* the `TwoFactorAuth` marker. Both class policies must hold, so the window
|
|
12
|
+
* closes the moment verification succeeds.
|
|
13
|
+
*
|
|
14
|
+
* Managing a TOTP device outside that window — enrolling for the first time, or
|
|
15
|
+
* turning 2FA off — lives on {@link TwoFactorAuthUserController} under
|
|
16
|
+
* `/user/2fa`, where the caller is fully authorized. Notably there is no
|
|
17
|
+
* "disable" here on purpose: a session that has not yet proven possession of
|
|
18
|
+
* the second factor must not be able to remove it, which would reduce the whole
|
|
19
|
+
* scheme to the password alone.
|
|
20
|
+
*
|
|
12
21
|
* @tags Two-Factor Authentication
|
|
13
22
|
*/
|
|
14
23
|
export declare class TwoFactorAuthController extends BaseController {
|
|
15
|
-
protected Queue: QueueService;
|
|
16
24
|
protected SessionProvider: SessionProvider;
|
|
17
|
-
protected
|
|
25
|
+
protected SessionCookies: SessionCookieFactory;
|
|
18
26
|
protected AC: AccessControl;
|
|
19
27
|
/**
|
|
20
|
-
*
|
|
21
|
-
* Generates a TOTP secret for
|
|
22
|
-
*
|
|
28
|
+
* Set up two-factor authentication during login
|
|
29
|
+
* Generates a TOTP secret for a user who must enrol before continuing — the state
|
|
30
|
+
* signalled by `TwoFactorInitRequired` at login when `rbac.twoFactorAuth.forceUser`
|
|
31
|
+
* is on. Returns the OTP provisioning URI to scan with an authenticator app; the
|
|
32
|
+
* caller then confirms with `POST /auth/2fa/verify`.
|
|
23
33
|
* @security cookieAuth
|
|
24
34
|
* @returns {IEnable2faResponse} OTP provisioning URI to scan with an authenticator app
|
|
25
35
|
* @response 400 Two-factor authentication is already enabled for this user
|
|
26
36
|
* @response 401 Unauthorized — valid session required
|
|
37
|
+
* @response 403 Session is not awaiting two-factor enrolment
|
|
27
38
|
*/
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Disable two-factor authentication
|
|
31
|
-
* Removes the TOTP secret and disables 2FA for the authenticated user.
|
|
32
|
-
* Throws if 2FA is not currently enabled for the user.
|
|
33
|
-
* @security cookieAuth
|
|
34
|
-
* @response 200 Two-factor authentication disabled successfully
|
|
35
|
-
* @response 400 Two-factor authentication is not enabled for this user
|
|
36
|
-
* @response 401 Unauthorized — valid session required
|
|
37
|
-
*/
|
|
38
|
-
disable2Fa(user: UserModel): Promise<Ok<any>>;
|
|
39
|
+
setup2fa(user: UserModel): Promise<Ok<IEnable2faResponse>>;
|
|
39
40
|
/**
|
|
40
41
|
* Verify TOTP token
|
|
41
42
|
* Validates the provided TOTP token against the user's 2FA secret. On success, marks the session
|
|
@@ -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,
|
|
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;IAW/E;;;;;;;;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;IAyC5J;;;;OAIG;IACH,SAAS,CAAC,oBAAoB,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG9E"}
|