(req: http.IncomingMessage)
| 1134 | } |
| 1135 | |
| 1136 | private authenticate(req: http.IncomingMessage) { |
| 1137 | const header = req.headers.authorization; |
| 1138 | if (!header || !header.startsWith("Bearer ")) return null; |
| 1139 | const token = header.slice("Bearer ".length); |
| 1140 | const payload = verifyUserToken(token, this.authSecret); |
| 1141 | if (!payload) return null; |
| 1142 | const user = this.opts.store.getHubUser(payload.userId); |
| 1143 | if (!user || user.status !== "active") return null; |
| 1144 | const hash = createHash("sha256").update(token).digest("hex"); |
| 1145 | if (user.tokenHash !== hash) return null; |
| 1146 | const clientIp = (req.headers["x-client-ip"] as string)?.trim() |
| 1147 | || (req.headers["x-forwarded-for"] as string)?.split(",")[0]?.trim() |
| 1148 | || req.socket.remoteAddress || ""; |
| 1149 | try { this.opts.store.updateHubUserActivity(user.id, clientIp); } catch { /* best-effort */ } |
| 1150 | return { |
| 1151 | userId: user.id, |
| 1152 | username: user.username, |
| 1153 | role: user.role, |
| 1154 | status: user.status, |
| 1155 | }; |
| 1156 | } |
| 1157 | |
| 1158 | private static readonly MAX_BODY_BYTES = 10 * 1024 * 1024; // 10 MB |
| 1159 |
no test coverage detected