diff --git a/docs/android-questionnaire-api.md b/docs/android-questionnaire-api.md index 0abb77f..67f3200 100644 --- a/docs/android-questionnaire-api.md +++ b/docs/android-questionnaire-api.md @@ -50,6 +50,49 @@ Request: Success: +```json +{ + "ok": true, + "data": { + "token": "64-character-hex-token", + "user": "coach1", + "role": "coach", + "clients": [ + { "clientCode": "CLIENT-001" }, + { "clientCode": "CLIENT-002" } + ] + } +} +``` + +For `coach` accounts, `clients` lists client codes currently assigned to that coach (`client.coachID`). Other roles omit this field or return an empty array. + +If the account must change its password first, the response contains `mustChangePassword: true` and a temporary token. That temporary token cannot access questionnaire endpoints until the password is changed. + +### Change password + +`POST /api/auth/change-password` + +Headers: + +```http +Authorization: Bearer +``` + +The token may be a normal session token or the temporary token returned when `mustChangePassword` is true. + +Request: + +```json +{ + "username": "coach1", + "old_password": "temporary-or-current-password", + "new_password": "new-secret" +} +``` + +Success: + ```json { "ok": true, @@ -61,7 +104,16 @@ Success: } ``` -If the account must change its password first, the response contains `mustChangePassword: true` and a temporary token. That temporary token cannot access questionnaire endpoints until the password is changed. +The server revokes the previous token and returns a new full session token. `new_password` must be at least 6 characters. + +Common failures: + +- `400 MISSING_FIELDS`: required fields missing. +- `400 PASSWORD_TOO_SHORT`: new password too short. +- `401 INVALID_CREDENTIALS`: old password incorrect. +- `403`: invalid, expired, or mismatched token. + +There is no self-service signup endpoint. Coach accounts are created on the web by administrators. ## Fetch Questionnaires diff --git a/handlers/auth.php b/handlers/auth.php index 8be9726..cff7db5 100644 --- a/handlers/auth.php +++ b/handlers/auth.php @@ -23,12 +23,26 @@ case 'auth/login': ); $stmt->execute([':u' => $username]); $user = $stmt->fetch(PDO::FETCH_ASSOC); - qdb_discard($tmpDb, $lockFp); if (!$user || !password_verify($password, $user['passwordHash'])) { + qdb_discard($tmpDb, $lockFp); json_error('INVALID_CREDENTIALS', 'Invalid username or password', 401); } + $assignedClients = []; + if (($user['role'] ?? '') === 'coach' && ($user['entityID'] ?? '') !== '') { + $cStmt = $pdo->prepare( + "SELECT clientCode FROM client WHERE coachID = :cid ORDER BY clientCode" + ); + $cStmt->execute([':cid' => $user['entityID']]); + $assignedClients = array_map( + fn($code) => ['clientCode' => $code], + $cStmt->fetchAll(PDO::FETCH_COLUMN) + ); + } + + qdb_discard($tmpDb, $lockFp); + if ((int)$user['mustChangePassword'] === 1) { $tempToken = bin2hex(random_bytes(32)); token_add($tempToken, 10 * 60, [ @@ -52,9 +66,10 @@ case 'auth/login': 'userID' => $user['userID'], ]); json_success([ - 'token' => $token, - 'user' => $username, - 'role' => $user['role'], + 'token' => $token, + 'user' => $username, + 'role' => $user['role'], + 'clients' => $assignedClients, ]); } catch (Throwable $e) { if (isset($tmpDb, $lockFp)) qdb_discard($tmpDb, $lockFp);