Add two-factor authentication support for users and subusers

- Extend `users_o` and `subusers_o` with `two_factor_enabled` and `two_factor_secret` properties.
- Implement methods for managing 2FA (`isTwoFactorEnabled`, `setTwoFactorSecret`, `verify_2fa_code`) in authentication logic.
- Add 2FA handling in login flows for both users and subusers, including token generation and validation.
- Introduce `totp` class for TOTP-based authentication, including QR code generation and code verification.
- Add test cases for 2FA functionality (`TwoFactorAuthTest.php`) and coverage for login scenarios with 2FA.
- Update OpenAPI specifications to include 2FA flows (`auth/2fa/setup`, `auth/2fa/enable`, `auth/2fa/verify`, `auth/2fa/disable`).
This commit is contained in:
Jeppe Bundgaard
2026-02-23 17:00:18 +01:00
parent 827fafd46b
commit f6b526f4ef
9 changed files with 899 additions and 21 deletions
+175 -18
View File
@@ -831,12 +831,24 @@ paths:
content:
application/json:
schema:
type: object
properties:
session:
type: string
description: Newly generated subuser session token
example: "2f7a8c0e-9b1d-4c6a-91a9-1a2b3c4d5e6f"
oneOf:
- type: object
required: [session]
properties:
session:
type: string
description: Newly generated subuser session token
example: "2f7a8c0e-9b1d-4c6a-91a9-1a2b3c4d5e6f"
- type: object
required: [2fa_required, 2fa_token]
properties:
2fa_required:
type: boolean
example: true
2fa_token:
type: string
description: Temporary 2FA verification token
example: "557a3e7b1a2b..."
'400': { $ref: '#/components/responses/BadRequest' }
'404': { $ref: '#/components/responses/NotFound' }
'500': { $ref: '#/components/responses/InternalServerError' }
@@ -1040,12 +1052,24 @@ paths:
content:
application/json:
schema:
type: object
properties:
token:
type: string
description: Bearer authentication token
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
oneOf:
- type: object
required: [token]
properties:
token:
type: string
description: Bearer authentication token
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
- type: object
required: [2fa_required, 2fa_token]
properties:
2fa_required:
type: boolean
example: true
2fa_token:
type: string
description: Temporary 2FA verification token
example: "557a3e7b1a2b..."
'400':
$ref: '#/components/responses/BadRequest'
'401':
@@ -1083,11 +1107,22 @@ paths:
content:
application/json:
schema:
type: object
properties:
token:
type: string
description: Bearer authentication token
oneOf:
- type: object
required: [token]
properties:
token:
type: string
description: Bearer authentication token
- type: object
required: [2fa_required, 2fa_token]
properties:
2fa_required:
type: boolean
example: true
2fa_token:
type: string
description: Temporary 2FA verification token
'400':
$ref: '#/components/responses/BadRequest'
'401':
@@ -1127,7 +1162,129 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/User'
allOf:
- $ref: '#/components/schemas/User'
- type: object
properties:
two_factor_enabled:
type: boolean
description: Indicates if 2FA is enabled for this account
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
/auth/2fa/setup:
post:
tags:
- Authentication
summary: Generate 2FA secret
description: Generate a new TOTP secret for the authenticated user/subuser
operationId: setup2fa
responses:
'200':
description: 2FA secret generated successfully
content:
application/json:
schema:
type: object
properties:
secret:
type: string
description: The base32 encoded TOTP secret
qr_code_url:
type: string
description: An otpauth URL for generating a QR code
'401':
$ref: '#/components/responses/Unauthorized'
/auth/2fa/enable:
post:
tags:
- Authentication
summary: Enable 2FA
description: Verify a code and enable 2FA for the authenticated user/subuser
operationId: enable2fa
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [code]
properties:
code:
type: string
description: The 6-digit TOTP code
responses:
'200':
description: 2FA enabled successfully
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
/auth/2fa/disable:
post:
tags:
- Authentication
summary: Disable 2FA
description: Verify a code and disable 2FA for the authenticated user/subuser
operationId: disable2fa
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [code]
properties:
code:
type: string
description: The 6-digit TOTP code
responses:
'200':
description: 2FA disabled successfully
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
/auth/2fa/verify:
post:
tags:
- Authentication
summary: Verify 2FA code during login
description: Complete the login process by verifying the 2FA code
operationId: verify2fa
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [2fa_token, code]
properties:
2fa_token:
type: string
description: The temporary 2FA verification token
code:
type: string
description: The 6-digit TOTP code
responses:
'200':
description: Login successful
content:
application/json:
schema:
type: object
properties:
token:
type: string
description: Bearer authentication token (for users/employees)
session:
type: string
description: Session token (for subusers)
'400':
$ref: '#/components/responses/BadRequest'
'401':