mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-26 15:46:35 +00:00
feat(firmware): allow env import token
This commit is contained in:
parent
d4abc86999
commit
444bb4c116
3 changed files with 26 additions and 3 deletions
|
|
@ -38,6 +38,10 @@ services:
|
|||
- BF_SELF_URL=http://server:18080
|
||||
# Optional: paste Ed25519 PEM private key here for firmware signing.
|
||||
# - BF_FIRMWARE_SIGNING_KEY=
|
||||
# Optional: single-purpose Bearer token for GitHub Actions firmware import.
|
||||
# Set GitHub's BF_AUTOIMPORT_API_KEY secret to the same value.
|
||||
# Generate with: openssl rand -base64 32
|
||||
# - BF_FIRMWARE_IMPORT_API_KEY=
|
||||
# Optional MQTT telemetry bridge (ThingsBoard / HA / Influx / etc).
|
||||
# - BF_MQTT_URL=mqtt://broker:1883
|
||||
# - BF_MQTT_USERNAME=
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
* record so downstream handlers (which always read `event.context.user`)
|
||||
* keep working unchanged.
|
||||
*/
|
||||
import { createHash, timingSafeEqual } from "node:crypto";
|
||||
import { type H3, getCookie, getRequestPath } from "h3";
|
||||
import type { AdminDeps } from "./index.js";
|
||||
import type { User, Session } from "../../shared/types.js";
|
||||
|
|
@ -36,6 +37,14 @@ function syntheticApiKeyUser(keyPrefix: string): User {
|
|||
};
|
||||
}
|
||||
|
||||
function tokenMatchesEnv(token: string, envName: string): boolean {
|
||||
const expected = process.env[envName]?.trim();
|
||||
if (!expected || expected.length < 32 || token.length < 32) return false;
|
||||
const a = createHash("sha256").update(token).digest();
|
||||
const b = createHash("sha256").update(expected).digest();
|
||||
return timingSafeEqual(a, b);
|
||||
}
|
||||
|
||||
export function registerMiddleware(app: H3, deps: AdminDeps): void {
|
||||
app.use(async (event) => {
|
||||
const path = getRequestPath(event);
|
||||
|
|
@ -70,6 +79,15 @@ export function registerMiddleware(app: H3, deps: AdminDeps): void {
|
|||
const authz = event.req.headers.get("authorization");
|
||||
if (authz && authz.startsWith("Bearer ")) {
|
||||
const token = authz.slice(7);
|
||||
if (
|
||||
path === "/api/admin/firmware/import" &&
|
||||
tokenMatchesEnv(token, "BF_FIRMWARE_IMPORT_API_KEY")
|
||||
) {
|
||||
event.context.user = syntheticApiKeyUser("fw-import");
|
||||
event.context.apiKeyPrefix = "fw-import";
|
||||
return;
|
||||
}
|
||||
|
||||
const key = await deps.auth.verifyApiKey(token, event.req.headers.get("x-real-ip"));
|
||||
if (!key || !key.scopes.includes("admin")) {
|
||||
return new Response(null, { status: 401 });
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@
|
|||
*
|
||||
* Upload path supports:
|
||||
* - browser multipart form ("upload from your machine")
|
||||
* - CI auto-import via API key (header X-BetterFrame-API-Key: bf-…)
|
||||
* - CI auto-import via Authorization: Bearer <token>. The token may be a
|
||||
* DB-backed admin API key or the single-purpose BF_FIRMWARE_IMPORT_API_KEY.
|
||||
* POST /api/admin/firmware/import with JSON {version, channel, arch,
|
||||
* signature, sha256, release_notes, content_b64} so GitHub Actions can
|
||||
* publish releases without a session.
|
||||
* release_notes, content_b64} so GitHub Actions can publish releases
|
||||
* without a session.
|
||||
*/
|
||||
import { type H3, getRouterParam, readBody, createError } from "h3";
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
|
|
|||
Loading…
Reference in a new issue