Merge pull request 'feat: Webhook use auth token' (#10) from feat/webhook-auth into main

Reviewed-on: #10
This commit is contained in:
Hendrik Schutter 2025-01-06 20:08:59 +01:00
commit 6d9626eaa2
6 changed files with 75 additions and 15 deletions

View File

@ -14,6 +14,12 @@ TODO
- Create new user for database: `GRANT ALL PRIVILEGES ON dev_locationhub.* TO 'dbuser'@'localhost' IDENTIFIED BY '1234';`
- Import tables: `/usr/bin/mariadb -u dbuser -p1234 dev_locationhub < server/sql/tables.sql`
### TTN Integration
Create new Webhook for application. Set base url and enable "Uplink message" to api `/api/ttn/webhook`.
Add a addidtional header:
- Type: `authorization`
- Value: `Bearer your-very-secure-token`
### Testing Webhook
- To test the webhook use the python script `ttn-webhook-dummy.py` to send prerecorded TTN Uplinks.
- To test the script you can use `while true; do echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nSuccess"; nc -l -p 8080 -q 1; done`

View File

@ -4,6 +4,7 @@ DB_PASSWORD=""
DB_HOST=""
DB_DIALECT=""
DB_PORT=""
WEBHOOK_TOKEN="" #Token that is placed a the TTN Webhook auth
WIGLE_TOKEN="" # Go to account and generate token "Encoded for use"
WIGLE_BASE_URL="https://api.wigle.net"
WIGLE_NETWORK_SEARCH="/api/v2/network/search"

View File

@ -9,11 +9,16 @@ import json
import argparse
import random
def send_post_request(uri, data):
def send_post_request(uri, data, token):
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
try:
requests.post(uri, json=data, timeout=1)
response = requests.post(uri, json=data, timeout=1, headers=headers)
print("Return code: " + str(response.status_code))
except requests.exceptions.RequestException as e:
pass
print(e)
def main():
parser = argparse.ArgumentParser(
@ -24,6 +29,11 @@ def main():
type=str,
help="The URI to send POST requests to (e.g., http://127.0.0.1:8080/api)",
)
parser.add_argument(
"token",
type=str,
help="Bearer authorization token)",
)
parser.add_argument(
"directory",
type=str,
@ -46,7 +56,7 @@ def main():
try:
data = json.load(file)
print(f"Sending {args.directory} to {args.uri}")
send_post_request(args.uri, data)
send_post_request(args.uri, data, args.token)
except json.JSONDecodeError as e:
print(f"Error reading {args.directory}: {e}")
return
@ -67,7 +77,7 @@ def main():
try:
data = json.load(file)
print(f"Sending {filename} to {args.uri}")
send_post_request(args.uri, data)
send_post_request(args.uri, data, args.token)
except json.JSONDecodeError as e:
print(f"Error reading {filename}: {e}")
@ -78,7 +88,7 @@ def main():
try:
data = json.load(file)
print(f"Sending {filename} to {args.uri}")
send_post_request(args.uri, data)
send_post_request(args.uri, data, args.token)
input("Press Enter to send the next file...")
except json.JSONDecodeError as e:
print(f"Error reading {filename}: {e}")
@ -91,11 +101,10 @@ def main():
try:
data = json.load(file)
print(f"Sending {filename} to {args.uri}")
send_post_request(args.uri, data)
send_post_request(args.uri, data, args.token)
input("Press Enter to send another random file...")
except json.JSONDecodeError as e:
print(f"Error reading {filename}: {e}")
if __name__ == "__main__":
main()

View File

@ -7,6 +7,8 @@ import { LpTtnEndDeviceUplinksService } from "../services/lpTtnEndDeviceUplinksS
import { TtnGatewayReceptionService } from "../services/ttnGatewayReceptionService";
import { WifiScanService } from "../services/wifiScanService";
import { ttnMessageValidator } from "../validation/ttn/ttnMessageValidation";
import { authenticateHeader } from "../middleware/authentificationMiddleware";
import { StatusCodes } from "http-status-codes";
const lpTtnEndDeviceUplinksService = container.resolve(
LpTtnEndDeviceUplinksService
@ -22,7 +24,7 @@ const router = express.Router();
router.post(
"/webhook",
validateData(ttnMessageValidator),
[authenticateHeader, validateData(ttnMessageValidator)],
async (req: Request, res: Response) => {
try {
const message = req.body as TtnMessage;
@ -96,17 +98,17 @@ router.post(
gnss:
gnnsLocation.latitude && gnnsLocation.longitude
? {
latitude: gnnsLocation.latitude,
longitude: gnnsLocation.longitude,
}
latitude: gnnsLocation.latitude,
longitude: gnnsLocation.longitude,
}
: undefined,
});
};
createDatabaseEntries().then();
res.status(200);
res.status(StatusCodes.OK).send();
} catch (error) {
console.log(error);
res.status(500).json({ error: "Error creating uplink" });
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: "Error creating uplink" });
}
}
);

View File

@ -0,0 +1,42 @@
import { NextFunction, Request, Response } from "express";
import { StatusCodes } from "http-status-codes";
const validateBearerToken = (authorizationHeader: string | undefined): boolean => {
if (!authorizationHeader) {
console.log("Authorization header is missing!");
return false;
}
const token = authorizationHeader.split(' ')[1]; // Extract token after 'Bearer'
if (!token) {
console.log("Bearer token is missing!");
return false;
}
if (token !== process.env.WEBHOOK_TOKEN) {
console.log("Bearer token is incorrect!");
return false;
}
return true;
};
export function authenticateHeader(req: Request, res: Response, next: NextFunction) {
try {
const authorizationHeader = req.headers['authorization'];
if (!validateBearerToken(authorizationHeader as string)) {
res.status(StatusCodes.UNAUTHORIZED).json({ error: "Authentication failed" });
return;
}
console.log("Bearer token is correct!");
next();
} catch (error) {
res.status(StatusCodes.INTERNAL_SERVER_ERROR)
.json({ error: "Internal Server Error" });
}
};

View File

@ -22,4 +22,4 @@ export function validateData(schema: z.ZodObject<any, any>) {
}
}
};
}
}