Skip to content

Commit bca2a11

Browse files
authored
Merge branch 'main' into codes11
2 parents b6013c4 + 0645423 commit bca2a11

40 files changed

Lines changed: 4564 additions & 75 deletions

.env.example

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ FRONTEND_URL=http://localhost:5173
1515
# JWT signing secret (required, min 32 characters)
1616
JWT_SECRET=replace-this-with-a-strong-32-char-minimum-jwt-secret
1717

18+
# TOTP 2FA encryption key — 64 hex characters (32 bytes, AES-256).
19+
# Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
20+
# Optional in development (falls back to JWT_SECRET hash); required in production.
21+
TOTP_ENCRYPTION_KEY=
22+
1823
# Pino log level (optional; not validated in env.ts) — debug|info|warn|error
1924
LOG_LEVEL=debug
2025

@@ -43,13 +48,26 @@ SMTP_PASS=smtp-password
4348
SMTP_PORT=587
4449
SMTP_USER=you@example.com
4550

46-
# --- S3 object storage (optional — TODO wire real adapter; see #379) ---
51+
# --- S3 object storage (optional — for AWS S3, MinIO, etc.) ---
4752
S3_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE
4853
S3_BUCKET=my-bucket
4954
S3_ENDPOINT=https://s3.amazonaws.com
5055
S3_REGION=us-east-1
5156
S3_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
5257

58+
# --- Storage provider selection ---
59+
# Options: mock (default), s3, r2, cloudinary
60+
# mock: Local development without cloud credentials
61+
# s3: AWS S3 or S3-compatible (use S3_* vars above)
62+
# r2: Cloudflare R2 (use S3_* vars, set S3_ENDPOINT to R2 URL)
63+
# cloudinary: Cloudinary CDN (image-optimized)
64+
STORAGE_PROVIDER=mock
65+
66+
# --- Cloudinary storage (optional — for image optimization) ---
67+
CLOUDINARY_CLOUD_NAME=my-cloud
68+
CLOUDINARY_API_KEY=my-api-key
69+
CLOUDINARY_API_SECRET=my-api-secret
70+
5371
# --- Soroban / escrow (optional — TODO implement client; see #358) ---
5472
ESCROW_CONTRACT_ID=CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2B
5573
SOROBAN_RPC_URL=https://soroban-rpc.example.com

docs/swagger.yaml

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,95 @@ paths:
572572
application/json:
573573
schema:
574574
$ref: '#/components/schemas/ErrorResponse'
575+
/api/auth/sessions:
576+
get:
577+
summary: List active sessions
578+
description: |
579+
Returns all active session records for the authenticated user (one per device/login).
580+
Sessions are ordered newest-first.
581+
tags:
582+
- Auth
583+
security:
584+
- bearerAuth: []
585+
responses:
586+
'200':
587+
description: Session list retrieved successfully
588+
content:
589+
application/json:
590+
schema:
591+
type: object
592+
properties:
593+
success:
594+
type: boolean
595+
example: true
596+
message:
597+
type: string
598+
example: Sessions retrieved
599+
data:
600+
type: array
601+
items:
602+
$ref: '#/components/schemas/Session'
603+
'401':
604+
description: Missing or invalid authorization token
605+
content:
606+
application/json:
607+
schema:
608+
$ref: '#/components/schemas/ErrorResponse'
609+
/api/auth/sessions/{jti}:
610+
delete:
611+
summary: Revoke a session
612+
description: |
613+
Blocklists the token identified by `jti` in Redis and removes the session record.
614+
The token will be rejected immediately by `requireAuth` on subsequent requests.
615+
616+
- Returns **403** if the session belongs to a different user (cross-user revocation).
617+
- Returns **404** if no session with that `jti` exists.
618+
tags:
619+
- Auth
620+
security:
621+
- bearerAuth: []
622+
parameters:
623+
- in: path
624+
name: jti
625+
required: true
626+
schema:
627+
type: string
628+
description: The JWT ID (`jti` claim) of the session to revoke
629+
responses:
630+
'200':
631+
description: Session revoked successfully
632+
content:
633+
application/json:
634+
schema:
635+
type: object
636+
properties:
637+
success:
638+
type: boolean
639+
example: true
640+
message:
641+
type: string
642+
example: Session revoked successfully
643+
data:
644+
nullable: true
645+
example: null
646+
'401':
647+
description: Missing or invalid authorization token
648+
content:
649+
application/json:
650+
schema:
651+
$ref: '#/components/schemas/ErrorResponse'
652+
'403':
653+
description: Forbidden — cannot revoke another user's session
654+
content:
655+
application/json:
656+
schema:
657+
$ref: '#/components/schemas/ErrorResponse'
658+
'404':
659+
description: Session not found
660+
content:
661+
application/json:
662+
schema:
663+
$ref: '#/components/schemas/ErrorResponse'
575664
/api/auth/signup:
576665
post:
577666
summary: Register a new user
@@ -1523,6 +1612,50 @@ paths:
15231612
application/json:
15241613
schema:
15251614
$ref: '#/components/schemas/ErrorResponse'
1615+
/api/auth/2fa/setup:
1616+
post:
1617+
summary: Initiate TOTP 2FA setup
1618+
description: >-
1619+
Generates a new TOTP secret for the authenticated user, encrypts it at
1620+
rest (AES-256-GCM), and returns an `otpauth://` URI rendered as a base64
1621+
PNG QR code data URL. The user should scan this with an authenticator app
1622+
(e.g. Google Authenticator, Authy). **2FA is not yet enabled** after this
1623+
call — the user must verify a valid TOTP code via the upcoming verify
1624+
endpoint to activate protection.
1625+
tags:
1626+
- Auth
1627+
security:
1628+
- bearerAuth: []
1629+
responses:
1630+
'200':
1631+
description: QR code data URL generated successfully
1632+
content:
1633+
application/json:
1634+
schema:
1635+
type: object
1636+
properties:
1637+
success:
1638+
type: boolean
1639+
example: true
1640+
message:
1641+
type: string
1642+
example: '2FA setup initiated. Scan the QR code with your authenticator app.'
1643+
data:
1644+
type: object
1645+
properties:
1646+
qrCodeUrl:
1647+
type: string
1648+
description: >-
1649+
Base64 PNG data URL (`data:image/png;base64,...`) to
1650+
be displayed as a QR code for the user to scan.
1651+
example: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...'
1652+
'401':
1653+
description: Missing or invalid auth token
1654+
content:
1655+
application/json:
1656+
schema:
1657+
$ref: '#/components/schemas/ErrorResponse'
1658+
15261659
/api/auth/forgot-password:
15271660
post:
15281661
summary: Request a password reset email
@@ -4424,6 +4557,33 @@ paths:
44244557

44254558
components:
44264559
schemas:
4560+
Session:
4561+
type: object
4562+
description: A per-device session record created on login or signup.
4563+
properties:
4564+
_id:
4565+
type: string
4566+
description: Session MongoDB ObjectId
4567+
userId:
4568+
type: string
4569+
description: Owner user ObjectId
4570+
jti:
4571+
type: string
4572+
description: JWT ID (UUID v4) — use this as the path param for DELETE /api/auth/sessions/:jti
4573+
ip:
4574+
type: string
4575+
description: Client IP address at time of login (optional)
4576+
userAgent:
4577+
type: string
4578+
description: HTTP User-Agent string at time of login (optional)
4579+
createdAt:
4580+
type: string
4581+
format: date-time
4582+
description: When the session was created (ISO 8601)
4583+
lastUsedAt:
4584+
type: string
4585+
format: date-time
4586+
description: When the session was last active (ISO 8601)
44274587
TelemetryUpdateEvent:
44284588
type: object
44294589
description: Real-time telemetry reading from an IoT sensor.

0 commit comments

Comments
 (0)