From b65b066c36c577b768ac9ca6f6eed0abbdc81550 Mon Sep 17 00:00:00 2001 From: dimgigov Date: Thu, 7 May 2026 00:21:00 +0300 Subject: [PATCH] Fix auth wiring: HTTP + TCP wire protocol authentication - httpserver.nim: - Use config.jwtSecret instead of hardcoded default - Enforce auth on /query, /tables, /metrics when authEnabled=true - /auth endpoint now validates password against jwtSecret - server.nim: - Add TCP wire protocol auth (mkAuth message) - Reject queries with 401 until client authenticates with valid JWT - wire.nim: Add makeAuthOkMessage, makeAuthChallengeMessage, parseAuthMessage helpers --- src/barabadb/core/httpserver.nim | 39 +++++++++++++++++++++++++++++--- src/barabadb/core/server.nim | 31 +++++++++++++++++++++++++ src/barabadb/protocol/wire.nim | 20 ++++++++++++++++ 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/src/barabadb/core/httpserver.nim b/src/barabadb/core/httpserver.nim index f2681df..a51823e 100644 --- a/src/barabadb/core/httpserver.nim +++ b/src/barabadb/core/httpserver.nim @@ -46,8 +46,9 @@ proc newHttpServer*(config: BaraConfig): HttpServer = ctx.onChange = proc(ev: ChangeEvent) = let msg = $ev.kind & " " & ev.table asyncCheck ws.broadcastToTable(ev.table, msg) + let secret = if config.jwtSecret.len > 0: config.jwtSecret else: "baradb-default-secret-change-in-production!" HttpServer(config: config, running: false, db: db, ctx: ctx, - secretKey: "baradb-default-secret-change-in-production!", + secretKey: secret, metrics: Metrics(), ws: ws) # ---------------------------------------------------------------------- @@ -76,6 +77,24 @@ proc verifyToken*(server: HttpServer, tokenStr: string): (bool, string, string) except: return (false, "", "") +# ---------------------------------------------------------------------- +# Auth helper +# ---------------------------------------------------------------------- + +proc checkAuth(server: HttpServer, request: Request, ctx: Context): bool = + if not server.config.authEnabled: + return true + let authHeader = request.headers["Authorization"] + if authHeader.len == 0 or not authHeader.startsWith("Bearer "): + ctx.json(%*{"error": "Unauthorized"}, 401) + return false + let tokenStr = authHeader[7..^1] + let (valid, userId, role) = server.verifyToken(tokenStr) + if not valid: + ctx.json(%*{"error": "Unauthorized"}, 401) + return false + return true + # ---------------------------------------------------------------------- # Handlers # ---------------------------------------------------------------------- @@ -87,8 +106,11 @@ proc queryHandler(server: HttpServer): RequestHandler = server.metrics.queriesTotal += 1 # Auth check - let authHeader = request.headers["Authorization"] - if authHeader.len > 0 and authHeader.startsWith("Bearer "): + if server.config.authEnabled: + let authHeader = request.headers["Authorization"] + if authHeader.len == 0 or not authHeader.startsWith("Bearer "): + ctx.json(%*{"error": "Unauthorized"}, 401) + return let tokenStr = authHeader[7..^1] let (valid, userId, role) = server.verifyToken(tokenStr) if not valid: @@ -157,6 +179,9 @@ proc healthHandler(): RequestHandler = proc metricsHandler(server: HttpServer): RequestHandler = return proc(request: Request) {.gcsafe.} = + let ctx = newContext(request) + if not server.checkAuth(request, ctx): + return let prometheus = "baradb_queries_total " & $server.metrics.queriesTotal & "\n" & "baradb_query_errors_total " & $server.metrics.queryErrors & "\n" & "baradb_inserts_total " & $server.metrics.insertCount & "\n" & @@ -174,6 +199,12 @@ proc authHandler(server: HttpServer): RequestHandler = return let username = body["username"].getStr() + let password = body["password"].getStr() + # Simple password check: must match jwtSecret when auth is enabled + if server.config.authEnabled and password != server.config.jwtSecret: + ctx.json(%*{"error": "Invalid credentials"}, 401) + return + let token = server.createToken(username, "user") ctx.json(%*{ "token": token, @@ -214,6 +245,8 @@ proc tablesHandler(server: HttpServer): RequestHandler = return proc(request: Request) {.gcsafe.} = {.cast(gcsafe).}: let ctx = newContext(request) + if not server.checkAuth(request, ctx): + return var tables = newJArray() for name, tbl in server.ctx.tables: var cols = newJArray() diff --git a/src/barabadb/core/server.nim b/src/barabadb/core/server.nim index 197ba05..d50dacf 100644 --- a/src/barabadb/core/server.nim +++ b/src/barabadb/core/server.nim @@ -16,6 +16,7 @@ import ../query/ast import ../query/executor import ../storage/lsm import ../core/mvcc +import jwt as jwtlib type Server* = ref object @@ -216,6 +217,17 @@ proc slowQueryLog(logPath: string, query: string, durationMs: int, clientId: int f.write(line) except: discard +proc verifyToken(secret, tokenStr: string): (bool, string, string) = + try: + let token = tokenStr.toJWT() + if not token.verify(secret, HS256): + return (false, "", "") + let userId = token.claims["sub"].node.str + let role = if "role" in token.claims: token.claims["role"].node.str else: "user" + return (true, userId, role) + except: + return (false, "", "") + proc handleClient(server: Server, client: AsyncSocket, clientId: int) {.async.} = info("Client " & $clientId & " connected") var connCtx = cloneForConnection(server.ctx) @@ -223,6 +235,8 @@ proc handleClient(server: Server, client: AsyncSocket, clientId: int) {.async.} let queryTimeout = server.config.queryTimeoutMs let slowThreshold = server.config.slowQueryThresholdMs let slowLog = server.config.slowQueryLogPath + var authenticated = not server.config.authEnabled + let secret = if server.config.jwtSecret.len > 0: server.config.jwtSecret else: "baradb-default-secret-change-in-production!" try: while true: @@ -240,7 +254,24 @@ proc handleClient(server: Server, client: AsyncSocket, clientId: int) {.async.} if payload.len < int(header.length): break + if not authenticated and header.kind != mkAuth: + let err = makeErrorMessage(header.requestId, 401, "Authentication required") + await client.send(cast[string](err)) + continue + case header.kind + of mkAuth: + let tokenStr = parseAuthMessage(cast[seq[byte]](payload)) + let (valid, userId, role) = verifyToken(secret, tokenStr) + if valid: + authenticated = true + let okMsg = makeAuthOkMessage(header.requestId) + await client.send(cast[string](okMsg)) + info("Client " & $clientId & " authenticated as " & userId) + else: + let err = makeErrorMessage(header.requestId, 403, "Invalid token") + await client.send(cast[string](err)) + of mkQuery: var pos = 0 let queryStr = readString(cast[seq[byte]](payload), pos) diff --git a/src/barabadb/protocol/wire.nim b/src/barabadb/protocol/wire.nim index d05c2fb..3154a2d 100644 --- a/src/barabadb/protocol/wire.nim +++ b/src/barabadb/protocol/wire.nim @@ -312,3 +312,23 @@ proc makeCompleteMessage*(requestId: uint32, affectedRows: int): seq[byte] = payload: payload, ) return serializeMessage(msg) + +proc makeAuthOkMessage*(requestId: uint32): seq[byte] = + var msg = WireMessage( + header: MessageHeader(kind: mkAuthOk, length: 0, requestId: requestId), + payload: @[], + ) + return serializeMessage(msg) + +proc makeAuthChallengeMessage*(requestId: uint32, challenge: string): seq[byte] = + var payload: seq[byte] = @[] + payload.writeString(challenge) + var msg = WireMessage( + header: MessageHeader(kind: mkAuthChallenge, length: uint32(payload.len), requestId: requestId), + payload: payload, + ) + return serializeMessage(msg) + +proc parseAuthMessage*(payload: openArray[byte]): string = + var pos = 0 + return readString(payload, pos) \ No newline at end of file