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