auth: clarify invalid origin error toast guidance (#193)

* nix: fix flake module and runtime scripts

* auth: clarify invalid origin toast
This commit is contained in:
ARUNAVO RAY
2026-02-26 10:39:08 +05:30
committed by GitHub
parent 08da526ddd
commit 855906d990
3 changed files with 235 additions and 174 deletions

View File

@@ -7,14 +7,17 @@
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
forEachSystem = flake-utils.lib.eachDefaultSystem;
in
(forEachSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
# Build the application
gitea-mirror = pkgs.stdenv.mkDerivation {
pname = "gitea-mirror";
version = "3.8.11";
version = "3.9.4";
src = ./.;
@@ -53,7 +56,7 @@
# Create entrypoint script that matches Docker behavior
cat > $out/bin/gitea-mirror <<'EOF'
#!/usr/bin/env bash
#!${pkgs.bash}/bin/bash
set -e
# === DEFAULT CONFIGURATION ===
@@ -112,7 +115,7 @@ if [ -z "$ENCRYPTION_SECRET" ]; then
fi
# === DATABASE INITIALIZATION ===
DB_PATH=$(echo "$DATABASE_URL" | sed 's|^file:||')
DB_PATH=$(echo "$DATABASE_URL" | ${pkgs.gnused}/bin/sed 's|^file:||')
if [ ! -f "$DB_PATH" ]; then
echo "Database not found. It will be created and initialized via Drizzle migrations on first app startup..."
touch "$DB_PATH"
@@ -123,25 +126,25 @@ fi
# === STARTUP SCRIPTS ===
# Initialize configuration from environment variables
echo "Checking for environment configuration..."
if [ -f "dist/scripts/startup-env-config.js" ]; then
if [ -f "scripts/startup-env-config.ts" ]; then
echo "Loading configuration from environment variables..."
${pkgs.bun}/bin/bun dist/scripts/startup-env-config.js && \
${pkgs.bun}/bin/bun scripts/startup-env-config.ts && \
echo " Environment configuration loaded successfully" || \
echo " Environment configuration loading completed with warnings"
fi
# Run startup recovery
echo "Running startup recovery..."
if [ -f "dist/scripts/startup-recovery.js" ]; then
${pkgs.bun}/bin/bun dist/scripts/startup-recovery.js --timeout=30000 && \
if [ -f "scripts/startup-recovery.ts" ]; then
${pkgs.bun}/bin/bun scripts/startup-recovery.ts --timeout=30000 && \
echo " Startup recovery completed successfully" || \
echo " Startup recovery completed with warnings"
fi
# Run repository status repair
echo "Running repository status repair..."
if [ -f "dist/scripts/repair-mirrored-repos.js" ]; then
${pkgs.bun}/bin/bun dist/scripts/repair-mirrored-repos.js --startup && \
if [ -f "scripts/repair-mirrored-repos.ts" ]; then
${pkgs.bun}/bin/bun scripts/repair-mirrored-repos.ts --startup && \
echo " Repository status repair completed successfully" || \
echo " Repository status repair completed with warnings"
fi
@@ -170,7 +173,7 @@ EOF
# Create database management helper
cat > $out/bin/gitea-mirror-db <<'EOF'
#!/usr/bin/env bash
#!${pkgs.bash}/bin/bash
export DATA_DIR=''${DATA_DIR:-"$HOME/.local/share/gitea-mirror"}
mkdir -p "$DATA_DIR"
cd $out/lib/gitea-mirror
@@ -217,7 +220,8 @@ EOF
'';
};
# NixOS module
}
)) // {
nixosModules.default = { config, lib, pkgs, ... }:
with lib;
let
@@ -228,7 +232,7 @@ EOF
package = mkOption {
type = types.package;
default = self.packages.${system}.default;
default = self.packages.${pkgs.system}.default;
description = "The Gitea Mirror package to use";
};
@@ -350,18 +354,17 @@ EOF
ProtectHome = true;
ReadWritePaths = [ cfg.dataDir ];
# Load environment file if specified (optional)
EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
# Graceful shutdown
TimeoutStopSec = "30s";
KillMode = "mixed";
KillSignal = "SIGTERM";
} // optionalAttrs (cfg.environmentFile != null) {
EnvironmentFile = cfg.environmentFile;
};
};
# Health check timer (optional monitoring)
systemd.timers.gitea-mirror-healthcheck = mkIf cfg.enable {
systemd.timers.gitea-mirror-healthcheck = {
description = "Gitea Mirror health check timer";
wantedBy = [ "timers.target" ];
timerConfig = {
@@ -370,12 +373,12 @@ EOF
};
};
systemd.services.gitea-mirror-healthcheck = mkIf cfg.enable {
systemd.services.gitea-mirror-healthcheck = {
description = "Gitea Mirror health check";
after = [ "gitea-mirror.service" ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.curl}/bin/curl -f http://${cfg.host}:${toString cfg.port}/api/health || true";
ExecStart = "${pkgs.bash}/bin/bash -c '${pkgs.curl}/bin/curl -f http://127.0.0.1:${toString cfg.port}/api/health || true'";
User = "nobody";
};
};
@@ -385,8 +388,7 @@ EOF
};
};
};
}
) // {
# Overlay for adding to nixpkgs
overlays.default = final: prev: {
gitea-mirror = self.packages.${final.system}.default;

View File

@@ -169,4 +169,31 @@ describe("parseErrorMessage", () => {
expect(result.description).toBeUndefined();
expect(result.isStructured).toBe(false);
});
test("adds trusted origins guidance for invalid origin errors", () => {
const errorMessage = "Invalid Origin: https://mirror.example.com";
const result = parseErrorMessage(errorMessage);
expect(result.title).toBe("Invalid Origin");
expect(result.description).toContain("BETTER_AUTH_TRUSTED_ORIGINS");
expect(result.description).toContain("https://mirror.example.com");
expect(result.isStructured).toBe(true);
});
});
describe("showErrorToast", () => {
test("shows invalid origin guidance in toast description", () => {
const calls: any[] = [];
const toast = {
error: (...args: any[]) => calls.push(args),
};
showErrorToast("Invalid Origin: http://10.10.20.45:4321", toast);
expect(calls).toHaveLength(1);
expect(calls[0][0]).toBe("Invalid Origin");
expect(calls[0][1].description).toContain("BETTER_AUTH_TRUSTED_ORIGINS");
expect(calls[0][1].description).toContain("http://10.10.20.45:4321");
});
});

View File

@@ -86,6 +86,30 @@ export interface ParsedErrorMessage {
isStructured: boolean;
}
function getInvalidOriginGuidance(title: string, description?: string): ParsedErrorMessage | null {
const fullMessage = `${title} ${description ?? ""}`.trim();
if (!/invalid origin/i.test(fullMessage)) {
return null;
}
const urlMatch = fullMessage.match(/https?:\/\/[^\s'")]+/i);
let originHint = "this URL";
if (urlMatch) {
try {
originHint = new URL(urlMatch[0]).origin;
} catch {
originHint = urlMatch[0];
}
}
return {
title: "Invalid Origin",
description: `Add ${originHint} to BETTER_AUTH_TRUSTED_ORIGINS and restart the app.`,
isStructured: true,
};
}
export function parseErrorMessage(error: unknown): ParsedErrorMessage {
// Handle Error objects
if (error instanceof Error) {
@@ -102,29 +126,32 @@ export function parseErrorMessage(error: unknown): ParsedErrorMessage {
if (typeof parsed === "object" && parsed !== null) {
// Format 1: { error: "message", errorType: "type", troubleshooting: "info" }
if (parsed.error) {
return {
const formatted = {
title: parsed.error,
description: parsed.troubleshooting || parsed.errorType || undefined,
isStructured: true,
};
return getInvalidOriginGuidance(formatted.title, formatted.description) || formatted;
}
// Format 2: { title: "title", description: "desc" }
if (parsed.title) {
return {
const formatted = {
title: parsed.title,
description: parsed.description || undefined,
isStructured: true,
};
return getInvalidOriginGuidance(formatted.title, formatted.description) || formatted;
}
// Format 3: { message: "msg", details: "details" }
if (parsed.message) {
return {
const formatted = {
title: parsed.message,
description: parsed.details || undefined,
isStructured: true,
};
return getInvalidOriginGuidance(formatted.title, formatted.description) || formatted;
}
}
} catch {
@@ -132,11 +159,12 @@ export function parseErrorMessage(error: unknown): ParsedErrorMessage {
}
// Plain string message
return {
const formatted = {
title: error,
description: undefined,
isStructured: false,
};
return getInvalidOriginGuidance(formatted.title, formatted.description) || formatted;
}
// Handle objects directly
@@ -144,36 +172,40 @@ export function parseErrorMessage(error: unknown): ParsedErrorMessage {
const errorObj = error as any;
if (errorObj.error) {
return {
const formatted = {
title: errorObj.error,
description: errorObj.troubleshooting || errorObj.errorType || undefined,
isStructured: true,
};
return getInvalidOriginGuidance(formatted.title, formatted.description) || formatted;
}
if (errorObj.title) {
return {
const formatted = {
title: errorObj.title,
description: errorObj.description || undefined,
isStructured: true,
};
return getInvalidOriginGuidance(formatted.title, formatted.description) || formatted;
}
if (errorObj.message) {
return {
const formatted = {
title: errorObj.message,
description: errorObj.details || undefined,
isStructured: true,
};
return getInvalidOriginGuidance(formatted.title, formatted.description) || formatted;
}
}
// Fallback for unknown types
return {
const fallback = {
title: String(error),
description: undefined,
isStructured: false,
};
return getInvalidOriginGuidance(fallback.title, fallback.description) || fallback;
}
// Enhanced toast helper that parses structured error messages