feat: real SCRAM-SHA-256 authentication (Option C)
Security: - New scram.nim module with full SCRAM-SHA-256 per RFC 7677 * PBKDF2-HMAC-SHA-256 key derivation * HMAC-SHA-256 with multiple overloads for bytes/strings * Secure nonce/salt generation via /dev/urandom * Client/server message parsing and proof verification AuthManager updates: - registerScramUser(): stores salted credentials (salt + iterations + storedKey + serverKey) - startScram(): initiates challenge-response handshake - finishScram(): verifies client proof and returns server signature - Legacy amSCRAMSHA256 path kept for backward compatibility HTTP endpoints: - POST /auth/scram/start — accepts client-first-message, returns server-first - POST /auth/scram/finish — accepts client-final-message, returns server-final Tests: - SCRAM-SHA-256 full handshake test (register → start → compute proof → finish) - SCRAM-SHA-256 invalid proof rejection test Build: 0 warnings, all tests pass
This commit is contained in:
@@ -18,6 +18,7 @@ import ../core/mvcc
|
||||
import ../protocol/wire
|
||||
import ../core/websocket
|
||||
import jwt as jwtlib
|
||||
import ../protocol/auth
|
||||
|
||||
type
|
||||
HttpServer* = ref object
|
||||
@@ -27,6 +28,7 @@ type
|
||||
ctx: ExecutionContext
|
||||
metrics*: Metrics
|
||||
secretKey*: string
|
||||
authManager*: AuthManager
|
||||
ws*: WsServer
|
||||
|
||||
Metrics* = ref object
|
||||
@@ -46,8 +48,10 @@ proc newHttpServer*(config: BaraConfig): HttpServer =
|
||||
ctx.onChange = proc(ev: ChangeEvent) =
|
||||
let msg = $ev.kind & " " & ev.table
|
||||
asyncCheck ws.broadcastToTable(ev.table, msg)
|
||||
let am = newAuthManager(secret)
|
||||
HttpServer(config: config, running: false, db: db, ctx: ctx,
|
||||
secretKey: secret,
|
||||
authManager: am,
|
||||
metrics: Metrics(), ws: ws)
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
@@ -211,6 +215,46 @@ proc authHandler(server: HttpServer): RequestHandler =
|
||||
"role": "user"
|
||||
})
|
||||
|
||||
proc scramStartHandler(server: HttpServer): RequestHandler =
|
||||
return proc(request: Request) {.gcsafe.} =
|
||||
{.cast(gcsafe).}:
|
||||
let ctx = newContext(request)
|
||||
let body = parseJson(request.body)
|
||||
if body == nil or "username" notin body or "clientFirstMessage" notin body:
|
||||
ctx.json(%*{"error": "Missing username or clientFirstMessage"}, 400)
|
||||
return
|
||||
let username = body["username"].getStr()
|
||||
let clientFirst = body["clientFirstMessage"].getStr()
|
||||
try:
|
||||
let serverFirst = server.authManager.startScram(clientFirst)
|
||||
ctx.json(%*{
|
||||
"serverFirstMessage": serverFirst,
|
||||
"status": "continue"
|
||||
})
|
||||
except CatchableError as e:
|
||||
ctx.json(%*{"error": e.msg}, 401)
|
||||
|
||||
proc scramFinishHandler(server: HttpServer): RequestHandler =
|
||||
return proc(request: Request) {.gcsafe.} =
|
||||
{.cast(gcsafe).}:
|
||||
let ctx = newContext(request)
|
||||
let body = parseJson(request.body)
|
||||
if body == nil or "clientFinalMessage" notin body:
|
||||
ctx.json(%*{"error": "Missing clientFinalMessage"}, 400)
|
||||
return
|
||||
let clientFinal = body["clientFinalMessage"].getStr()
|
||||
try:
|
||||
let (ok, serverFinal) = server.authManager.finishScram(clientFinal)
|
||||
if ok:
|
||||
ctx.json(%*{
|
||||
"serverFinalMessage": serverFinal,
|
||||
"status": "authenticated"
|
||||
})
|
||||
else:
|
||||
ctx.json(%*{"error": serverFinal}, 401)
|
||||
except CatchableError as e:
|
||||
ctx.json(%*{"error": e.msg}, 401)
|
||||
|
||||
proc openApiHandler(): RequestHandler =
|
||||
return proc(request: Request) {.gcsafe.} =
|
||||
let ctx = newContext(request)
|
||||
@@ -482,6 +526,8 @@ proc run*(server: HttpServer, port: int = 9470) =
|
||||
router.get("/health", healthHandler())
|
||||
router.get("/metrics", server.metricsHandler())
|
||||
router.post("/auth", server.authHandler())
|
||||
router.post("/auth/scram/start", server.scramStartHandler())
|
||||
router.post("/auth/scram/finish", server.scramFinishHandler())
|
||||
router.get("/tables", server.tablesHandler())
|
||||
router.get("/api", openApiHandler())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user