Files
bookorbit/server/test/e2e/authorization-matrix/route-inventory/routes-07.json
T
neon 6769bf4b0f fix(auth): make account lockout recoverable and visible
The lockout was checked before the password was verified, so a correct password on a locked
account returned the same generic invalid-credentials error as a wrong one. Users had no way to
tell the two apart, and the natural response was to keep retrying a password that was already
right.

Completing a password reset did not clear the lock either, so the one recovery path the UI
advertises appeared to fail for up to fifteen minutes after a successful reset.

Verify the password first and report the lockout, with the remaining wait, only when the supplied
password was correct. Someone guessing wrong passwords still gets the generic failure, so account
existence is not leaked. Clear the lock whenever a password is reset or changed, and give admins
an explicit unlock action for the case where waiting is not acceptable.
2026-08-09 05:12:57 +00:00

411 lines
12 KiB
JSON

[
{
"file": "server/src/modules/user/user.controller.ts",
"className": "UserController",
"methodName": "setLibraries",
"httpMethod": "PUT",
"path": "/users/:id/libraries",
"permissions": ["Permission.ManageUsers"],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user/user.controller.ts",
"className": "UserController",
"methodName": "adminResetPassword",
"httpMethod": "POST",
"path": "/users/:id/reset-password",
"permissions": ["Permission.ManageUsers"],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user/user.controller.ts",
"className": "UserController",
"methodName": "unlockUser",
"httpMethod": "POST",
"path": "/users/:id/unlock",
"permissions": ["Permission.ManageUsers"],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getSummary",
"httpMethod": "GET",
"path": "/user-statistics/summary",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getDailyReading",
"httpMethod": "GET",
"path": "/user-statistics/daily-reading",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getReadingHeatmap",
"httpMethod": "GET",
"path": "/user-statistics/reading-heatmap",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getPeakHours",
"httpMethod": "GET",
"path": "/user-statistics/peak-hours",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getFavoriteDays",
"httpMethod": "GET",
"path": "/user-statistics/favorite-days",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getSessionTimeline",
"httpMethod": "GET",
"path": "/user-statistics/session-timeline",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "updateSessionTimelineSession",
"httpMethod": "PATCH",
"path": "/user-statistics/session-timeline/:sessionId",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getCompletionTimeline",
"httpMethod": "GET",
"path": "/user-statistics/completion-timeline",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getGoalTrajectory",
"httpMethod": "GET",
"path": "/user-statistics/goal-trajectory",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getProgressFunnel",
"httpMethod": "GET",
"path": "/user-statistics/progress-funnel",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getCompletionLatency",
"httpMethod": "GET",
"path": "/user-statistics/completion-latency",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getGenreReadingTime",
"httpMethod": "GET",
"path": "/user-statistics/genre-reading-time",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getReadingPace",
"httpMethod": "GET",
"path": "/user-statistics/reading-pace",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getReadingSurvival",
"httpMethod": "GET",
"path": "/user-statistics/reading-survival",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getCompletionRace",
"httpMethod": "GET",
"path": "/user-statistics/completion-race",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getSessionArchetypes",
"httpMethod": "GET",
"path": "/user-statistics/session-archetypes",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getAuthorGenreChord",
"httpMethod": "GET",
"path": "/user-statistics/author-genre-chord",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/koreader/koreader.controller.ts",
"className": "KoreaderController",
"methodName": "createCredentials",
"httpMethod": "POST",
"path": "/koreader/credentials",
"permissions": ["Permission.KoreaderSync"],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/koreader/koreader.controller.ts",
"className": "KoreaderController",
"methodName": "updateCredentials",
"httpMethod": "PATCH",
"path": "/koreader/credentials",
"permissions": ["Permission.KoreaderSync"],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/koreader/koreader.controller.ts",
"className": "KoreaderController",
"methodName": "deleteCredentials",
"httpMethod": "DELETE",
"path": "/koreader/credentials",
"permissions": ["Permission.KoreaderSync"],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/koreader/koreader.controller.ts",
"className": "KoreaderController",
"methodName": "getCredentials",
"httpMethod": "GET",
"path": "/koreader/credentials",
"permissions": ["Permission.KoreaderSync"],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/koreader/koreader.controller.ts",
"className": "KoreaderController",
"methodName": "getSyncStatus",
"httpMethod": "GET",
"path": "/koreader/sync-status",
"permissions": ["Permission.KoreaderSync"],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/koreader/koreader.controller.ts",
"className": "KoreaderController",
"methodName": "getDevices",
"httpMethod": "GET",
"path": "/koreader/devices",
"permissions": ["Permission.KoreaderSync"],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/koreader/koreader.controller.ts",
"className": "KoreaderController",
"methodName": "getBookProgress",
"httpMethod": "GET",
"path": "/koreader/books/:bookId/progress",
"permissions": ["Permission.KoreaderSync"],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/koreader/koreader.controller.ts",
"className": "KoreaderController",
"methodName": "testConnection",
"httpMethod": "POST",
"path": "/koreader/test-connection",
"permissions": ["Permission.KoreaderSync"],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/notification/notification.controller.ts",
"className": "NotificationController",
"methodName": "list",
"httpMethod": "GET",
"path": "/notifications",
"permissions": ["Permission.NotificationAccess", "Permission.DemoRestricted"],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/notification/notification.controller.ts",
"className": "NotificationController",
"methodName": "getUnreadCount",
"httpMethod": "GET",
"path": "/notifications/unread-count",
"permissions": ["Permission.NotificationAccess", "Permission.DemoRestricted"],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/notification/notification.controller.ts",
"className": "NotificationController",
"methodName": "markAllAsRead",
"httpMethod": "PATCH",
"path": "/notifications/read-all",
"permissions": ["Permission.NotificationAccess", "Permission.DemoRestricted"],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/notification/notification.controller.ts",
"className": "NotificationController",
"methodName": "markAsRead",
"httpMethod": "PATCH",
"path": "/notifications/:id/read",
"permissions": ["Permission.NotificationAccess", "Permission.DemoRestricted"],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/notification/notification.controller.ts",
"className": "NotificationController",
"methodName": "dismiss",
"httpMethod": "DELETE",
"path": "/notifications/:id",
"permissions": ["Permission.NotificationAccess", "Permission.DemoRestricted"],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/notification/notification.controller.ts",
"className": "NotificationController",
"methodName": "clearAll",
"httpMethod": "DELETE",
"path": "/notifications",
"permissions": ["Permission.NotificationAccess", "Permission.DemoRestricted"],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
}
]