changed the route name

This commit is contained in:
cpu
2025-03-28 17:28:24 +01:00
parent fda9178264
commit 5a3b4974c4
4 changed files with 191 additions and 88 deletions

View File

@@ -14,7 +14,9 @@ const vapidPublicKey = process.env.VAPID_PUBLIC_KEY;
const vapidPrivateKey = process.env.VAPID_PRIVATE_KEY;
const vapidSubject = process.env.VAPID_SUBJECT; // mailto: or https:
const subscriptionsFilePath = process.env.SUBSCRIPTIONS_FILE || path.join(__dirname, 'subscriptions.json');
const flicSecret = process.env.FLIC_SECRET; // Optional Bearer token secret for Flic webhook
// Basic Authentication Credentials
const basicAuthUsername = process.env.BASIC_AUTH_USERNAME;
const basicAuthPassword = process.env.BASIC_AUTH_PASSWORD;
// Note: We are NOT adding specific authentication for the /subscribe endpoint in this version.
// Consider adding API key or other auth if exposing this publicly.
const allowedOrigins = (process.env.ALLOWED_ORIGINS || "").split(',').map(origin => origin.trim()).filter(origin => origin);
@@ -182,37 +184,55 @@ const corsOptions = {
};
app.use(cors(corsOptions));
// Enable pre-flight requests for all relevant routes
app.options('/flic-webhook', cors(corsOptions));
app.options('/webhook', cors(corsOptions));
app.options('/subscribe', cors(corsOptions));
// --- Body Parsing Middleware ---
app.use(express.json());
// --- Authentication Middleware (For Flic Webhook Only) ---
const authenticateFlicRequest = (req, res, next) => {
// Only apply auth if flicSecret is configured
if (!flicSecret) {
// --- Basic Authentication Middleware ---
const authenticateBasic = (req, res, next) => {
// Skip authentication for OPTIONS requests (CORS preflight)
if (req.method === 'OPTIONS') {
return next();
}
// Skip authentication if username or password are not set in environment
if (!basicAuthUsername || !basicAuthPassword) {
logger.warn('Auth: Basic Auth username or password not configured. Skipping authentication.');
return next();
}
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
logger.warn('Auth (Flic): Missing or malformed Authorization header');
return res.status(401).json({ message: 'Unauthorized: Missing or malformed Bearer token' });
if (!authHeader || !authHeader.startsWith('Basic ')) {
logger.warn('Auth: Missing or malformed Basic Authorization header');
res.setHeader('WWW-Authenticate', 'Basic realm="Restricted Area"');
return res.status(401).json({ message: 'Unauthorized: Basic Authentication required' });
}
const token = authHeader.split(' ')[1];
if (token !== flicSecret) {
logger.warn('Auth (Flic): Invalid Bearer token received');
return res.status(401).json({ message: 'Unauthorized: Invalid token' });
}
const credentials = authHeader.split(' ')[1];
const decodedCredentials = Buffer.from(credentials, 'base64').toString('utf8');
const [username, password] = decodedCredentials.split(':');
logger.debug('Auth (Flic): Request authenticated successfully.');
next();
// Note: Use constant-time comparison for production environments if possible,
// but for this scope, direct comparison is acceptable.
if (username === basicAuthUsername && password === basicAuthPassword) {
logger.debug('Auth: Basic Authentication successful.');
return next();
} else {
logger.warn('Auth: Invalid Basic Authentication credentials received.');
res.setHeader('WWW-Authenticate', 'Basic realm="Restricted Area"');
return res.status(401).json({ message: 'Unauthorized: Invalid credentials' });
}
};
app.post('/subscribe', async (req, res) => {
// --- Routes ---
// Subscribe endpoint: Add a new button->subscription mapping
// Apply Basic Authentication
app.post('/subscribe', authenticateBasic, async (req, res) => {
const { button_id, subscription } = req.body;
logger.debug('All headers received:');
@@ -250,13 +270,17 @@ app.post('/subscribe', async (req, res) => {
});
// --- Flic Webhook Endpoint (GET only) ---
// Apply Flic-specific authentication to this route
app.get('/flic-webhook', authenticateFlicRequest, async (req, res) => {
// Apply Basic Authentication
app.get('/webhook/:click_type', authenticateBasic, async (req, res) => {
// Get buttonName from Header 'Button-Name' and timestamp from Header 'Timestamp'
const buttonName = req.headers['button-name'];
const timestamp = req.headers['timestamp'];
// Get click_type from query parameter instead of request body
const click_type = req.query.click_type;
// Get click_type from URL path
const click_type = req.params.click_type;
// Get battery level from Header 'Button-Battery-Level'
const batteryLevelHeader = req.headers['button-battery-level'];
// Use 'N/A' if header is missing or empty, otherwise parse as integer (or keep as string if parsing fails)
const batteryLevel = batteryLevelHeader ? parseInt(batteryLevelHeader, 10) || batteryLevelHeader : 'N/A';
// Log all headers received from Flic
logger.debug('All headers received:');
@@ -289,7 +313,8 @@ app.get('/flic-webhook', authenticateFlicRequest, async (req, res) => {
data: {
action: click_type,
button: normalizedButtonName, // Send normalized button name
timestamp: timestamp || new Date().toISOString()
timestamp: timestamp || new Date().toISOString(),
batteryLevel: batteryLevel // Use the extracted value
}
// icon: '/path/to/icon.png'
});
@@ -324,8 +349,13 @@ server.listen(port, () => {
logger.info(`Allowed Origins: ${allowedOrigins.length > 0 ? allowedOrigins.join(', ') : '(Any)'}`);
logger.info(`Allowed Methods: ${allowedMethods.join(', ')}`);
logger.info(`Allowed Headers: ${allowedHeaders.join(', ')}`);
logger.info(`Flic Webhook Auth: ${flicSecret ? 'Enabled (Bearer Token)' : 'Disabled'}`);
logger.info(`Subscription Endpoint Auth: Disabled`);
// Log Basic Auth status instead of Flic Secret
if (basicAuthUsername && basicAuthPassword) {
logger.info('Authentication: Basic Auth Enabled');
} else {
logger.info('Authentication: Basic Auth Disabled (username/password not set)');
}
logger.info(`Subscription Endpoint Auth: ${basicAuthUsername && basicAuthPassword ? 'Enabled (Basic)' : 'Disabled'}`);
logger.info(`Subscriptions File: ${subscriptionsFilePath}`);
logger.info(`Push Notification Retry Config: ${maxRetries} retries, ${initialRetryDelay}ms initial delay`);
logger.info(`DNS Config: IPv4 first, timeout ${dnsTimeout}ms`);