mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 22:02:17 +01:00
feat!: the token on every route, Secure Config Only goes (step 4b)
Under Secured every HTTP route but the public ones needs the token (docs/adr/0006-two-security-modes-eleven-http-routes.md): - isPublicRoute, isWebAsset and originMatchesHost in src/HttpAccess.h, with native tests. - One middleware checks the token. It runs once the body is in, so /restore, /updateFirmware and /updateApplication check it on their first chunk and drop a refused upload. - Static serving answers the web UI's files only: shades.cfg and shades.tmp are no longer served. - The wildcard CORS headers go; WEB_CORS turns them back on for UI work from file://. - The socket refuses a handshake whose Origin doesn't name its Host. - "Secure Config Only" goes: a device set to it is Secured, and /loginContext drops permissions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
13ec38823b
commit
ecb0260f61
16 changed files with 392 additions and 54 deletions
49
src/HttpAccess.h
Normal file
49
src/HttpAccess.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#ifndef httpaccess_h
|
||||
#define httpaccess_h
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
// Who may reach what on port 80 under Secured
|
||||
// (docs/adr/0006-two-security-modes-eleven-http-routes.md). Paths are the
|
||||
// request's path, without the query.
|
||||
|
||||
// The files the web UI loads. Static serving answers these alone, so the
|
||||
// rolling codes in shades.cfg leave the device only through /backup.
|
||||
inline bool isWebAsset(const char *path) {
|
||||
if(!path || path[0] != '/' || strstr(path, "..")) return false;
|
||||
if(strcmp(path, "/") == 0 || strcmp(path, "/appversion") == 0) return true;
|
||||
static const char *const types[] = {".html", ".js", ".css", ".png", ".svg", ".ico"};
|
||||
const size_t len = strlen(path);
|
||||
for(const char *type : types) {
|
||||
const size_t n = strlen(type);
|
||||
if(len > n + 1 && strcmp(path + len - n, type) == 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// True for what anyone may reach without a token: the web UI's files, what
|
||||
// logging in needs, SSDP's description and preflight requests. /ws is here
|
||||
// because the socket checks its own token in the handshake, from the query.
|
||||
inline bool isPublicRoute(const char *path, bool preflight) {
|
||||
if(preflight) return true;
|
||||
if(!path) return false;
|
||||
static const char *const routes[] = {"/loginContext", "/login", "/upnp.xml", "/ws"};
|
||||
for(const char *route : routes)
|
||||
if(strcmp(path, route) == 0) return true;
|
||||
return isWebAsset(path);
|
||||
}
|
||||
|
||||
// True when a socket handshake comes from a page the device served. A
|
||||
// browser always sends Origin with a handshake, so another site's page open
|
||||
// on the LAN can't use a token the browser holds. Clients that aren't
|
||||
// browsers send none.
|
||||
inline bool originMatchesHost(const char *origin, const char *host) {
|
||||
if(!origin) return true;
|
||||
static const char *const schemes[] = {"http://", "https://"};
|
||||
for(const char *scheme : schemes) {
|
||||
const size_t n = strlen(scheme);
|
||||
if(strncasecmp(origin, scheme, n) == 0) return host && host[0] && strcasecmp(origin + n, host) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue