Skip to content

Commit 5657ce3

Browse files
authored
Lemonade API key (lemonade-sdk#774)
* add support for API key * document LEMONADE_API_KEY * do not require auth on HTTP OPTIONS method
1 parent 443cc7d commit 5657ce3

5 files changed

Lines changed: 43 additions & 8 deletions

File tree

docs/server/lemonade-server-cli.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ Additionally, you can provide your own `llama-server` binary by giving the full
5555

5656
The same can also be done for the `whisper-server` binary. The environment variable to set in this case is `LEMONADE_WHISPERCPP_BIN`.
5757

58+
If you expose your server over a network you can use the `LEMONADE_API_KEY` to set an API key (use a random long string) that will be required to execute any request. The API key will be expected as HTTP Bearer authentication, which is compatible with the OpenAI API.
59+
60+
**IMPORTANT**: If you need to access `lemonade-server` over the internet, do not expose it directly! You will also need to setup an HTTPS reverse proxy (such as nginx) and expose that instead, otherwise all communication will be in plaintext!
61+
5862
## `pull` Command Options
5963

6064
The `pull` command downloads and installs models. For models already in the [Lemonade Server registry](./server_models.md), only the model name is required. To register and install custom models from Hugging Face, use the registration options below:

src/cpp/include/lemon/server.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ class Server {
4747
void setup_routes(httplib::Server &web_server);
4848
void setup_static_files(httplib::Server &web_server);
4949
void setup_cors(httplib::Server &web_server);
50-
void setup_http_logger(httplib::Server &web_server) ;
51-
50+
void setup_http_logger(httplib::Server &web_server);
51+
void log_request(const httplib::Request& req);
52+
httplib::Server::HandlerResponse authenticate_request(const httplib::Request& req, httplib::Response& res);
53+
5254
// Endpoint handlers
5355
void handle_health(const httplib::Request& req, httplib::Response& res);
5456
void handle_models(const httplib::Request& req, httplib::Response& res);
@@ -102,6 +104,8 @@ class Server {
102104
std::unique_ptr<ModelManager> model_manager_;
103105

104106
bool running_;
107+
108+
std::string api_key_;
105109
};
106110

107111
} // namespace lemon

src/cpp/include/lemon_tray/server_manager.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ class ServerManager {
110110
std::string llamacpp_args_;
111111
std::string extra_models_dir_;
112112
std::string host_;
113+
std::string api_key_;
113114
int port_;
114115
int ctx_size_;
115116
int max_llm_models_;

src/cpp/server/server.cpp

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ Server::Server(int port, const std::string& host, const std::string& log_level,
8282
if (log_level_ == "debug" || log_level_ == "trace") {
8383
std::cout << "[Server] Debug logging enabled - subprocess output will be visible" << std::endl;
8484
}
85+
86+
const char* api_key_env = std::getenv("LEMONADE_API_KEY");
87+
api_key_ = api_key_env ? std::string(api_key_env) : "";
8588

8689
setup_routes(*http_server_);
8790
setup_routes(*http_server_v6_);
@@ -91,15 +94,31 @@ Server::~Server() {
9194
stop();
9295
}
9396

97+
void Server::log_request(const httplib::Request& req) {
98+
if (req.path != "/api/v0/health" && req.path != "/api/v1/health") {
99+
std::cout << "[Server PRE-ROUTE] " << req.method << " " << req.path << std::endl;
100+
std::cout.flush();
101+
}
102+
}
103+
104+
httplib::Server::HandlerResponse Server::authenticate_request(const httplib::Request& req, httplib::Response& res) {
105+
if ((api_key_ != "") && (req.method != "OPTIONS")) {
106+
if (api_key_ != httplib::get_bearer_token_auth(req)) {
107+
res.status = 401;
108+
res.set_content("{\"error\": \"Invalid or missing API key\"}", "application/json");
109+
return httplib::Server::HandlerResponse::Handled;
110+
}
111+
}
112+
113+
return httplib::Server::HandlerResponse::Unhandled;
114+
}
115+
116+
94117
void Server::setup_routes(httplib::Server &web_server) {
95118
// Add pre-routing handler to log ALL incoming requests (except health checks)
96119
web_server.set_pre_routing_handler([this](const httplib::Request& req, httplib::Response& res) {
97-
// Skip logging health checks to reduce log noise
98-
if (req.path != "/api/v0/health" && req.path != "/api/v1/health") {
99-
std::cout << "[Server PRE-ROUTE] " << req.method << " " << req.path << std::endl;
100-
std::cout.flush();
101-
}
102-
return httplib::Server::HandlerResponse::Unhandled;
120+
this->log_request(req);
121+
return authenticate_request(req, res);
103122
});
104123

105124
// Setup CORS for all routes

src/cpp/tray/server_manager.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ bool ServerManager::start_server(
106106
llamacpp_args_ = llamacpp_args;
107107
extra_models_dir_ = extra_models_dir;
108108
host_ = host;
109+
110+
const char* api_key_env = std::getenv("LEMONADE_API_KEY");
111+
api_key_ = api_key_env ? std::string(api_key_env) : "";
109112

110113
if (!spawn_process()) {
111114
std::cerr << "Failed to spawn server process" << std::endl;
@@ -821,6 +824,10 @@ std::string ServerManager::make_http_request(
821824
httplib::Client cli(connect_host, port_);
822825
cli.set_connection_timeout(10, 0); // 10 second connection timeout
823826
cli.set_read_timeout(timeout_seconds, 0); // Configurable read timeout
827+
828+
if (api_key_ != "") {
829+
cli.set_bearer_token_auth(api_key_);
830+
}
824831

825832
httplib::Result res;
826833

0 commit comments

Comments
 (0)