Skip to content

Commit 6d28f2e

Browse files
committed
add api key auth
1 parent c708254 commit 6d28f2e

4 files changed

Lines changed: 78 additions & 1 deletion

File tree

.github/workflows/deploy.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,6 @@ jobs:
2828
uses: docker/setup-buildx-action@v2
2929

3030
- name: Run Deployment Script
31-
run: ./deploy-working.sh
31+
run: ./deploy-working.sh
32+
env:
33+
API_KEY: ${{ secrets.SCREENSHOT_API_KEY }}

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,44 @@ aws apigatewayv2 create-api \
164164
The function works out of the box but you can configure:
165165

166166
- `RUST_LOG`: Set logging level (e.g., "info", "debug")
167+
- `API_KEY`: **Optional.** If set, every request must provide this value either in the `x-api-key` HTTP header **or** as the `key` query-string parameter. Omit the variable to disable authentication (useful for local testing).
168+
169+
### Setting `API_KEY`
170+
171+
Once the Lambda function exists you can set / change the key at any time:
172+
173+
```bash
174+
# one-off update (keeps existing image & config)
175+
aws lambda update-function-configuration \
176+
--function-name screenshotapi \
177+
--environment "Variables={API_KEY=your-secret-value}" \
178+
--region eu-central-1
179+
```
180+
181+
The key persists for the lifetime of the function. **However:** the sample `deploy-working.sh` script currently deletes and recreates the function on each deploy. If you use that script you have two options:
182+
183+
1. Add `--environment "Variables={API_KEY=your-secret-value}"` to the `aws lambda create-function` call inside the script so the key is applied on every deploy.
184+
2. Stop deleting the function (remove the `aws lambda delete-function` call). Updating an existing function keeps its environment variables intact.
185+
186+
### Calling the API with the key
187+
188+
```bash
189+
# Header style
190+
curl -H "x-api-key: your-secret-value" "${FUNCTION_URL}?url=https://example.com" | jq .
191+
192+
# Query-string style (handy for quick browser tests)
193+
curl "${FUNCTION_URL}?url=https://example.com&key=your-secret-value" | jq .
194+
```
195+
196+
Requests without the correct key receive:
197+
198+
```json
199+
HTTP/1.1 401 Unauthorized
200+
{
201+
"error": "UNAUTHORIZED",
202+
"message": "Invalid or missing API key"
203+
}
204+
```
167205

168206
### Lambda Configuration Recommendations
169207

deploy-working.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ aws lambda create-function \
8686
--region "${REGION}" \
8787
--memory-size 2048 \
8888
--timeout 90 \
89+
${API_KEY:+--environment "Variables={API_KEY=$API_KEY}"} \
8990
--architectures x86_64 > /dev/null
9091

9192
echo "Waiting for function to become active..."

src/http_handler.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use lambda_http::{Body, Error, Request, RequestExt, Response};
22
use serde::Serialize;
33
use crate::screenshot::ScreenshotService;
4+
use std::env;
45

56
#[derive(Serialize)]
67
struct ErrorResponse {
@@ -17,6 +18,41 @@ struct SuccessResponse {
1718

1819
/// Main function handler for the screenshot API
1920
pub(crate) async fn function_handler(event: Request) -> Result<Response<Body>, Error> {
21+
// --- Simple API key check ---
22+
// Expected key (set via Lambda environment variable `API_KEY`)
23+
let expected_key = env::var("API_KEY").unwrap_or_default();
24+
25+
if !expected_key.is_empty() {
26+
// Try to read from `x-api-key` header first
27+
let provided_key_header = event
28+
.headers()
29+
.get("x-api-key")
30+
.and_then(|v| v.to_str().ok());
31+
32+
// Fallback: query parameter `key`
33+
let provided_key_query = event
34+
.query_string_parameters()
35+
.first("key");
36+
37+
let provided_key = provided_key_header.or(provided_key_query);
38+
39+
if provided_key != Some(expected_key.as_str()) {
40+
// Unauthorized
41+
let error_response = ErrorResponse {
42+
error: "UNAUTHORIZED".to_string(),
43+
message: "Invalid or missing API key".to_string(),
44+
};
45+
46+
let resp = Response::builder()
47+
.status(401)
48+
.header("content-type", "application/json")
49+
.body(serde_json::to_string(&error_response)?.into())
50+
.map_err(Box::new)?;
51+
52+
return Ok(resp);
53+
}
54+
}
55+
2056
match handle_screenshot_request(event).await {
2157
Ok(response) => Ok(response),
2258
Err(e) => {

0 commit comments

Comments
 (0)