Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 86 additions & 26 deletions backend/src/routes/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,44 @@ const verifySchema = z.object({
});

/**
* Generate a SEP-10 authentication challenge.
*
* @route POST /auth/sep10
* @param {string} public_key - The Stellar public key requesting a challenge
* @returns {Object} 200 - Challenge transaction and nonce
* @returns {string} 200.transaction - The unsigned SEP-10 challenge transaction
* @returns {string} 200.nonce - A unique nonce for this challenge (single-use)
* @returns {Object} 400 - Invalid public key format
* @returns {Object} 429 - Rate limit exceeded (max 10 per minute per IP)
* @throws {Error} 500 - Internal server error
* @swagger
* /auth/sep10:
* post:
* summary: Generate a SEP-10 authentication challenge
* tags:
* - Auth
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - public_key
* properties:
* public_key:
* type: string
* description: Stellar public key (starts with G) requesting a challenge
* responses:
* 200:
* description: Challenge generated successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* transaction:
* type: string
* description: Unsigned SEP-10 challenge transaction (XDR)
* nonce:
* type: string
* description: Single-use nonce tied to this challenge
* 400:
* description: Invalid Stellar public key format
* 429:
* description: Rate limit exceeded (max 10 requests per IP per minute)
* 500:
* description: Internal server error
*/
router.post('/sep10', sep10Limiter, validate(sep10Schema), async (req, res) => {
const { public_key } = req.body;
Expand All @@ -51,22 +79,54 @@ router.post('/sep10', sep10Limiter, validate(sep10Schema), async (req, res) => {
});

/**
* Verify a signed SEP-10 challenge and issue a JWT.
*
* The client must sign the challenge transaction returned from POST /auth/sep10
* with their Stellar wallet and submit it here. Upon successful verification,
* a JWT is issued that can be used to authenticate subsequent API requests.
*
* @route POST /auth/verify
* @param {string} transaction - The signed SEP-10 challenge transaction
* @param {string} nonce - The nonce from the challenge (must match)
* @returns {Object} 200 - Authentication successful
* @returns {string} 200.token - JWT token (expires in 1 hour)
* @returns {string} 200.publicKey - The authenticated Stellar public key
* @returns {string} 200.role - User role: 'issuer' (admin) or 'patient'
* @returns {Object} 400 - Missing or invalid parameters
* @returns {Object} 401 - Invalid signature or nonce mismatch
* @throws {Error} 500 - Internal server error
* @swagger
* /auth/verify:
* post:
* summary: Verify a signed SEP-10 challenge and issue a JWT
* description: >
* The client signs the challenge transaction from POST /auth/sep10 with
* their Stellar wallet and submits it here. On success a short-lived JWT
* (1 hour) is returned scoped to the caller's role.
* tags:
* - Auth
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - transaction
* - nonce
* properties:
* transaction:
* type: string
* description: Signed SEP-10 challenge transaction (XDR)
* nonce:
* type: string
* description: Nonce returned by POST /auth/sep10 (must match)
* responses:
* 200:
* description: Authentication successful
* content:
* application/json:
* schema:
* type: object
* properties:
* token:
* type: string
* description: JWT (expires in 1 hour)
* wallet:
* type: string
* description: Authenticated Stellar public key
* role:
* type: string
* enum: [admin, patient]
* description: Role derived from the authenticated key
* 400:
* description: Missing or malformed parameters
* 401:
* description: Invalid signature or nonce mismatch
*/
router.post('/verify', validate(verifySchema), bruteForceGuard, (req, res) => {
const { transaction, nonce } = req.body;
Expand Down