Skip to content

Commit 1a55e8e

Browse files
authored
Merge pull request #472 from talktosam2003/codes11
codes endpoints
2 parents 0645423 + bca2a11 commit 1a55e8e

11 files changed

Lines changed: 1022 additions & 0 deletions

File tree

docs/swagger.yaml

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1697,6 +1697,216 @@ paths:
16971697
application/json:
16981698
schema:
16991699
$ref: '#/components/schemas/ErrorResponse'
1700+
/api/auth/2fa/setup:
1701+
post:
1702+
summary: Initiate TOTP 2FA setup
1703+
description: |
1704+
Generates a TOTP secret for the authenticated user and returns an `otpauth://` URI
1705+
for QR-code rendering. 2FA is NOT yet active — the user must call
1706+
`POST /api/auth/2fa/verify` with a valid code to enable it.
1707+
tags:
1708+
- Auth
1709+
security:
1710+
- bearerAuth: []
1711+
responses:
1712+
'200':
1713+
description: 2FA setup initiated
1714+
content:
1715+
application/json:
1716+
schema:
1717+
type: object
1718+
properties:
1719+
success:
1720+
type: boolean
1721+
message:
1722+
type: string
1723+
data:
1724+
type: object
1725+
properties:
1726+
otpauthUrl:
1727+
type: string
1728+
description: otpauth:// URI to encode as QR code
1729+
secret:
1730+
type: string
1731+
description: Raw TOTP secret for manual entry
1732+
'401':
1733+
description: Missing or invalid auth token
1734+
content:
1735+
application/json:
1736+
schema:
1737+
$ref: '#/components/schemas/ErrorResponse'
1738+
'409':
1739+
description: 2FA is already enabled
1740+
content:
1741+
application/json:
1742+
schema:
1743+
$ref: '#/components/schemas/ErrorResponse'
1744+
/api/auth/2fa/verify:
1745+
post:
1746+
summary: Verify first TOTP code and enable 2FA
1747+
description: |
1748+
Validates the first TOTP code after setup. On success, enables 2FA and returns
1749+
10 single-use backup codes (plaintext, shown exactly once — store them safely).
1750+
tags:
1751+
- Auth
1752+
security:
1753+
- bearerAuth: []
1754+
requestBody:
1755+
required: true
1756+
content:
1757+
application/json:
1758+
schema:
1759+
type: object
1760+
required:
1761+
- code
1762+
properties:
1763+
code:
1764+
type: string
1765+
pattern: '^\d{6}$'
1766+
description: 6-digit TOTP code from the authenticator app
1767+
responses:
1768+
'200':
1769+
description: 2FA enabled successfully
1770+
content:
1771+
application/json:
1772+
schema:
1773+
type: object
1774+
properties:
1775+
success:
1776+
type: boolean
1777+
message:
1778+
type: string
1779+
data:
1780+
type: object
1781+
properties:
1782+
backupCodes:
1783+
type: array
1784+
items:
1785+
type: string
1786+
description: 10 single-use backup codes (shown once only)
1787+
'400':
1788+
description: Invalid or wrong TOTP code, or setup not initiated
1789+
content:
1790+
application/json:
1791+
schema:
1792+
$ref: '#/components/schemas/ErrorResponse'
1793+
'401':
1794+
description: Missing or invalid auth token
1795+
content:
1796+
application/json:
1797+
schema:
1798+
$ref: '#/components/schemas/ErrorResponse'
1799+
'409':
1800+
description: 2FA already enabled
1801+
content:
1802+
application/json:
1803+
schema:
1804+
$ref: '#/components/schemas/ErrorResponse'
1805+
'429':
1806+
description: Too many verification attempts (rate-limited)
1807+
content:
1808+
application/json:
1809+
schema:
1810+
type: object
1811+
properties:
1812+
success:
1813+
type: boolean
1814+
message:
1815+
type: string
1816+
retryAfter:
1817+
type: integer
1818+
data:
1819+
nullable: true
1820+
/api/auth/2fa:
1821+
delete:
1822+
summary: Disable 2FA
1823+
description: |
1824+
Disables 2FA on the account after verifying the user's current password.
1825+
Clears the TOTP secret and all backup codes.
1826+
tags:
1827+
- Auth
1828+
security:
1829+
- bearerAuth: []
1830+
requestBody:
1831+
required: true
1832+
content:
1833+
application/json:
1834+
schema:
1835+
type: object
1836+
required:
1837+
- password
1838+
properties:
1839+
password:
1840+
type: string
1841+
description: Current account password
1842+
responses:
1843+
'200':
1844+
description: 2FA disabled successfully
1845+
content:
1846+
application/json:
1847+
schema:
1848+
type: object
1849+
properties:
1850+
success:
1851+
type: boolean
1852+
message:
1853+
type: string
1854+
data:
1855+
nullable: true
1856+
'400':
1857+
description: 2FA is not enabled on this account
1858+
content:
1859+
application/json:
1860+
schema:
1861+
$ref: '#/components/schemas/ErrorResponse'
1862+
'401':
1863+
description: Incorrect password or missing/invalid auth token
1864+
content:
1865+
application/json:
1866+
schema:
1867+
$ref: '#/components/schemas/ErrorResponse'
1868+
/api/auth/2fa/backup-codes/regenerate:
1869+
post:
1870+
summary: Regenerate backup codes
1871+
description: |
1872+
Invalidates all existing backup codes and issues 10 new single-use codes.
1873+
Requires 2FA to be enabled.
1874+
tags:
1875+
- Auth
1876+
security:
1877+
- bearerAuth: []
1878+
responses:
1879+
'200':
1880+
description: New backup codes generated
1881+
content:
1882+
application/json:
1883+
schema:
1884+
type: object
1885+
properties:
1886+
success:
1887+
type: boolean
1888+
message:
1889+
type: string
1890+
data:
1891+
type: object
1892+
properties:
1893+
backupCodes:
1894+
type: array
1895+
items:
1896+
type: string
1897+
description: 10 new single-use backup codes (shown once only)
1898+
'400':
1899+
description: 2FA is not enabled on this account
1900+
content:
1901+
application/json:
1902+
schema:
1903+
$ref: '#/components/schemas/ErrorResponse'
1904+
'401':
1905+
description: Missing or invalid auth token
1906+
content:
1907+
application/json:
1908+
schema:
1909+
$ref: '#/components/schemas/ErrorResponse'
17001910
/api/auth/api-keys/{organizationId}:
17011911
get:
17021912
summary: List API keys for an organization

package-lock.json

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ export function buildApp() {
6565
app.use('/api/auth/signup', strictLimiter);
6666
app.use('/api/auth/forgot-password', otpLimiter);
6767
app.use('/api/auth/reset-password', otpLimiter);
68+
app.use('/api/auth/2fa/verify', otpLimiter);
69+
app.use('/api/auth/2fa/backup-codes/regenerate', strictLimiter);
6870

6971
app.use('/api/health', healthRouter);
7072
app.use('/api/auth', authRouter);

src/modules/auth/auth.routes.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
ResetPasswordBodySchema,
1212
RefreshBodySchema,
1313
RegisterCompanyBodySchema,
14+
Verify2faBodySchema,
15+
Disable2faBodySchema,
1416
} from './auth.validation.js';
1517
import {
1618
signupController,
@@ -32,6 +34,12 @@ import {
3234
CreateApiKeyBodySchema,
3335
OrganizationIdParamSchema,
3436
} from './apiKey.validation.js';
37+
import {
38+
setup2faController,
39+
verify2faController,
40+
disable2faController,
41+
regenerateBackupCodesController,
42+
} from './twoFactor.controller.js';
3543
import { listSessionsController, revokeSessionController } from './session.controller.js';
3644
import { SessionJtiParamSchema } from './session.validation.js';
3745

@@ -98,6 +106,32 @@ authRouter.delete(
98106
asyncHandler(revokeApiKeyController)
99107
);
100108

109+
// ── TOTP 2FA routes (all require JWT auth) ────────────────────────────────────
110+
111+
// Initiate 2FA setup: generates TOTP secret + otpauth URI
112+
authRouter.post('/2fa/setup', asyncHandler(requireAuth), asyncHandler(setup2faController));
113+
114+
// Verify first TOTP code, enable 2FA, return backup codes (rate-limited in app.ts)
115+
authRouter.post(
116+
'/2fa/verify',
117+
asyncHandler(requireAuth),
118+
validateRequest({ body: Verify2faBodySchema }),
119+
asyncHandler(verify2faController)
120+
);
121+
122+
// Disable 2FA — requires current password confirmation
123+
authRouter.delete(
124+
'/2fa',
125+
asyncHandler(requireAuth),
126+
validateRequest({ body: Disable2faBodySchema }),
127+
asyncHandler(disable2faController)
128+
);
129+
130+
// Regenerate backup codes — invalidates old ones
131+
authRouter.post(
132+
'/2fa/backup-codes/regenerate',
133+
asyncHandler(requireAuth),
134+
asyncHandler(regenerateBackupCodesController)
101135
// Session management routes (protected by JWT auth)
102136
authRouter.get('/sessions', asyncHandler(requireAuth), asyncHandler(listSessionsController));
103137
authRouter.delete(

0 commit comments

Comments
 (0)