mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-05-14 16:20:14 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b6df4b9f0a | |||
| c298d0b158 | |||
| b8b0e45ec4 | |||
| 35cd3b9ce5 | |||
| 90145dd774 | |||
| 9c0fe751cc | |||
| bc7c604a7d | |||
| 8b28e0c3e4 | |||
| 8fad270379 | |||
| 32595e351d | |||
| 25017a76c9 | |||
| 312671c795 | |||
| 3971710e87 | |||
| a5677d2558 |
@@ -38,6 +38,6 @@ jobs:
|
|||||||
retention-days: 5
|
retention-days: 5
|
||||||
|
|
||||||
- name: Upload to code-scanning
|
- name: Upload to code-scanning
|
||||||
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4
|
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
|||||||
@@ -48,6 +48,3 @@ __debug_*
|
|||||||
|
|
||||||
# testing config
|
# testing config
|
||||||
config.certify.yml
|
config.certify.yml
|
||||||
|
|
||||||
# deepsec
|
|
||||||
/.deepsec
|
|
||||||
|
|||||||
+2
-50
@@ -2,56 +2,8 @@
|
|||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
It is recommended to use the [latest](https://github.com/tinyauthapp/tinyauth/releases/latest) available version of Tinyauth. This is because it includes security fixes, new features and dependency updates. Older versions, especially major ones, are not supported and won't receive security or patch updates.
|
It is recommended to use the [latest](https://github.com/tinyauthapp/tinyauth/releases/latest) available version of tinyauth. This is because it includes security fixes, new features and dependency updates. Older versions, especially major ones, are not supported and won't receive security or patch updates.
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
Please **do not** report security vulnerabilities through public GitHub issues, discussions, or pull requests as I won't be able to patch them in time and they may get exploited by malicious actors.
|
Due to the nature of this app, it needs to be secure. If you discover any security issues or vulnerabilities in the app please contact me as soon as possible at <security@tinyauth.app>. Please do not use the issues section to report security issues as I won't be able to patch them in time and they may get exploited by malicious actors.
|
||||||
|
|
||||||
Instead, report them privately using [GitHub's Private Vulnerability Reporting](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability) via the **Security** tab of this repository.
|
|
||||||
|
|
||||||
Or send us an email at <security@tinyauth.app>.
|
|
||||||
|
|
||||||
### A note on AI-assisted reports
|
|
||||||
|
|
||||||
If AI tooling (LLMs, automated scanners, agentic assistants, etc.) helped you discover, analyse, or write up this issue, please say so in your report. This isn't a judgement - AI-assisted findings are welcome - but disclosing it up front helps maintainers calibrate how much additional verification a report needs, and tends to make the report itself clearer.
|
|
||||||
|
|
||||||
When submitting a report, please use the structure below so it can be triaged quickly.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 1. Summary
|
|
||||||
|
|
||||||
A short, one-paragraph description of the vulnerability and its impact (e.g. what an attacker can achieve, who is affected, and under what conditions).
|
|
||||||
|
|
||||||
### 2. Steps to Reproduce / Proof of Concept
|
|
||||||
|
|
||||||
Provide a minimal, reliable reproduction:
|
|
||||||
|
|
||||||
1. Step one
|
|
||||||
2. Step two
|
|
||||||
3. Step three
|
|
||||||
|
|
||||||
Include any required input, payloads, configuration, or code snippets. Attach a PoC script or screenshots where helpful.
|
|
||||||
|
|
||||||
### 3. Expected vs. Actual Behaviour
|
|
||||||
|
|
||||||
- **Expected:** what *should* happen
|
|
||||||
- **Actual:** what *does* happen, and why it's a security issue
|
|
||||||
|
|
||||||
### 4. Suggested Fix or Mitigation *(optional)*
|
|
||||||
|
|
||||||
If you have an idea for how to address the issue, describe it here. A private gist link is welcome but not required.
|
|
||||||
|
|
||||||
- **Have you tested this fix?** Yes / No
|
|
||||||
- **If yes,** briefly describe how it was tested and what was verified.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What to Expect
|
|
||||||
|
|
||||||
- **Acknowledgement** within a reasonable timeframe after receiving your report
|
|
||||||
- **Updates** as the issue is investigated and addressed
|
|
||||||
- **Public credit** in the resulting advisory, along with any **CVE assigned**, unless you'd prefer to stay anonymous
|
|
||||||
|
|
||||||
We follow a **90-day coordinated disclosure** window: please allow up to 90 days from the date of your report for the issue to be investigated and patched before publicly disclosing it. The publication date - whether earlier if a fix lands sooner, or later if more time is genuinely needed - will be agreed with you in advance.
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { Navigate } from "react-router";
|
|||||||
import { useUserContext } from "./context/user-context";
|
import { useUserContext } from "./context/user-context";
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
const { isLoggedIn } = useUserContext();
|
const { auth } = useUserContext();
|
||||||
|
|
||||||
if (isLoggedIn) {
|
if (auth.authenticated) {
|
||||||
return <Navigate to="/logout" replace />;
|
return <Navigate to="/logout" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ import { DomainWarning } from "../domain-warning/domain-warning";
|
|||||||
import { ThemeToggle } from "../theme-toggle/theme-toggle";
|
import { ThemeToggle } from "../theme-toggle/theme-toggle";
|
||||||
|
|
||||||
const BaseLayout = ({ children }: { children: React.ReactNode }) => {
|
const BaseLayout = ({ children }: { children: React.ReactNode }) => {
|
||||||
const { backgroundImage, title } = useAppContext();
|
const { ui } = useAppContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = title;
|
document.title = ui.title;
|
||||||
}, [title]);
|
}, [ui.title]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex flex-col justify-center items-center min-h-svh px-4"
|
className="flex flex-col justify-center items-center min-h-svh px-4"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(${backgroundImage})`,
|
backgroundImage: `url(${ui.backgroundImage})`,
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
}}
|
}}
|
||||||
@@ -31,7 +31,7 @@ const BaseLayout = ({ children }: { children: React.ReactNode }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Layout = () => {
|
export const Layout = () => {
|
||||||
const { appUrl, warningsEnabled } = useAppContext();
|
const { app, ui } = useAppContext();
|
||||||
const [ignoreDomainWarning, setIgnoreDomainWarning] = useState(() => {
|
const [ignoreDomainWarning, setIgnoreDomainWarning] = useState(() => {
|
||||||
return window.sessionStorage.getItem("ignoreDomainWarning") === "true";
|
return window.sessionStorage.getItem("ignoreDomainWarning") === "true";
|
||||||
});
|
});
|
||||||
@@ -42,11 +42,15 @@ export const Layout = () => {
|
|||||||
setIgnoreDomainWarning(true);
|
setIgnoreDomainWarning(true);
|
||||||
}, [setIgnoreDomainWarning]);
|
}, [setIgnoreDomainWarning]);
|
||||||
|
|
||||||
if (!ignoreDomainWarning && warningsEnabled && appUrl !== currentUrl) {
|
if (
|
||||||
|
!ignoreDomainWarning &&
|
||||||
|
ui.warningsEnabled &&
|
||||||
|
!app.trustedDomains.includes(currentUrl)
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
<DomainWarning
|
<DomainWarning
|
||||||
appUrl={appUrl}
|
appUrl={app.appUrl}
|
||||||
currentUrl={currentUrl}
|
currentUrl={currentUrl}
|
||||||
onClick={() => handleIgnore()}
|
onClick={() => handleIgnore()}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -80,5 +80,17 @@
|
|||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
"groupsScopeDescription": "Allows the app to access your group information.",
|
||||||
"backToLoginButton": "Back to login"
|
"backToLoginButton": "Back to login",
|
||||||
|
"phoneScopeName": "Phone",
|
||||||
|
"phoneScopeDescription": "Allows the app to access your phone number.",
|
||||||
|
"addressScopeName": "Address",
|
||||||
|
"addressScopeDescription": "Allows the app to access your address.",
|
||||||
|
"loginTailscaleTitle": "Continue with Tailscale",
|
||||||
|
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
||||||
|
"loginTailscaleDeviceName": "Device name:",
|
||||||
|
"loginTailscaleSubmit": "Continue with Tailscale",
|
||||||
|
"loginTailscaleOtherMethod": "Login with another method",
|
||||||
|
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
||||||
|
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
||||||
|
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,5 +84,13 @@
|
|||||||
"phoneScopeName": "Phone",
|
"phoneScopeName": "Phone",
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
"phoneScopeDescription": "Allows the app to access your phone number.",
|
||||||
"addressScopeName": "Address",
|
"addressScopeName": "Address",
|
||||||
"addressScopeDescription": "Allows the app to access your address."
|
"addressScopeDescription": "Allows the app to access your address.",
|
||||||
|
"loginTailscaleTitle": "Continue with Tailscale",
|
||||||
|
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
||||||
|
"loginTailscaleDeviceName": "Device name:",
|
||||||
|
"loginTailscaleSubmit": "Continue with Tailscale",
|
||||||
|
"loginTailscaleOtherMethod": "Login with another method",
|
||||||
|
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
||||||
|
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
||||||
|
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ const createScopeMap = (t: TFunction<"translation", undefined>): Scope[] => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AuthorizePage = () => {
|
export const AuthorizePage = () => {
|
||||||
const { isLoggedIn } = useUserContext();
|
const { auth } = useUserContext();
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -127,7 +127,7 @@ export const AuthorizePage = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
if (!auth.authenticated) {
|
||||||
return <Navigate to={`/login?${oidcParams.compiled}`} replace />;
|
return <Navigate to={`/login?${oidcParams.compiled}`} replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|||||||
import { useRedirectUri } from "@/lib/hooks/redirect-uri";
|
import { useRedirectUri } from "@/lib/hooks/redirect-uri";
|
||||||
|
|
||||||
export const ContinuePage = () => {
|
export const ContinuePage = () => {
|
||||||
const { cookieDomain, warningsEnabled } = useAppContext();
|
const { app, ui } = useAppContext();
|
||||||
const { isLoggedIn } = useUserContext();
|
const { auth } = useUserContext();
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -29,17 +29,18 @@ export const ContinuePage = () => {
|
|||||||
|
|
||||||
const { url, valid, trusted, allowedProto, httpsDowngrade } = useRedirectUri(
|
const { url, valid, trusted, allowedProto, httpsDowngrade } = useRedirectUri(
|
||||||
redirectUri,
|
redirectUri,
|
||||||
cookieDomain,
|
app.cookieDomain,
|
||||||
);
|
);
|
||||||
|
|
||||||
const urlHref = url?.href;
|
const urlHref = url?.href;
|
||||||
|
|
||||||
const hasValidRedirect = valid && allowedProto;
|
const hasValidRedirect = valid && allowedProto;
|
||||||
const showUntrustedWarning = hasValidRedirect && !trusted && warningsEnabled;
|
const showUntrustedWarning =
|
||||||
|
hasValidRedirect && !trusted && ui.warningsEnabled;
|
||||||
const showInsecureWarning =
|
const showInsecureWarning =
|
||||||
hasValidRedirect && httpsDowngrade && warningsEnabled;
|
hasValidRedirect && httpsDowngrade && ui.warningsEnabled;
|
||||||
const shouldAutoRedirect =
|
const shouldAutoRedirect =
|
||||||
isLoggedIn &&
|
auth.authenticated &&
|
||||||
hasValidRedirect &&
|
hasValidRedirect &&
|
||||||
!showUntrustedWarning &&
|
!showUntrustedWarning &&
|
||||||
!showInsecureWarning;
|
!showInsecureWarning;
|
||||||
@@ -77,7 +78,7 @@ export const ContinuePage = () => {
|
|||||||
};
|
};
|
||||||
}, [shouldAutoRedirect, redirectToTarget]);
|
}, [shouldAutoRedirect, redirectToTarget]);
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
if (!auth.authenticated) {
|
||||||
return (
|
return (
|
||||||
<Navigate
|
<Navigate
|
||||||
to={`/login${redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : ""}`}
|
to={`/login${redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : ""}`}
|
||||||
@@ -104,7 +105,7 @@ export const ContinuePage = () => {
|
|||||||
components={{
|
components={{
|
||||||
code: <code />,
|
code: <code />,
|
||||||
}}
|
}}
|
||||||
values={{ cookieDomain }}
|
values={{ cookieDomain: app.cookieDomain }}
|
||||||
shouldUnescape={true}
|
shouldUnescape={true}
|
||||||
/>
|
/>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import Markdown from "react-markdown";
|
|||||||
import { useLocation } from "react-router";
|
import { useLocation } from "react-router";
|
||||||
|
|
||||||
export const ForgotPasswordPage = () => {
|
export const ForgotPasswordPage = () => {
|
||||||
const { forgotPasswordMessage } = useAppContext();
|
const { ui } = useAppContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const searchParams = new URLSearchParams(search);
|
const searchParams = new URLSearchParams(search);
|
||||||
@@ -26,8 +26,8 @@ export const ForgotPasswordPage = () => {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
<Markdown>
|
<Markdown>
|
||||||
{forgotPasswordMessage !== ""
|
{ui.forgotPasswordMessage !== ""
|
||||||
? forgotPasswordMessage
|
? ui.forgotPasswordMessage
|
||||||
: t("forgotPasswordMessage")}
|
: t("forgotPasswordMessage")}
|
||||||
</Markdown>
|
</Markdown>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
|
|||||||
@@ -36,12 +36,17 @@ const iconMap: Record<string, React.ReactNode> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const LoginPage = () => {
|
export const LoginPage = () => {
|
||||||
const { isLoggedIn } = useUserContext();
|
const { auth, tailscale } = useUserContext();
|
||||||
const { providers, title, oauthAutoRedirect } = useAppContext();
|
const {
|
||||||
|
ui,
|
||||||
|
oauth,
|
||||||
|
auth: { providers },
|
||||||
|
} = useAppContext();
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [showRedirectButton, setShowRedirectButton] = useState(false);
|
const [showRedirectButton, setShowRedirectButton] = useState(false);
|
||||||
|
const [useTailscale, setUseTailscale] = useState(tailscale.nodeName !== "");
|
||||||
|
|
||||||
const hasAutoRedirectedRef = useRef(false);
|
const hasAutoRedirectedRef = useRef(false);
|
||||||
|
|
||||||
@@ -55,7 +60,7 @@ export const LoginPage = () => {
|
|||||||
const oidcParams = useOIDCParams(searchParams);
|
const oidcParams = useOIDCParams(searchParams);
|
||||||
|
|
||||||
const [isOauthAutoRedirect, setIsOauthAutoRedirect] = useState(
|
const [isOauthAutoRedirect, setIsOauthAutoRedirect] = useState(
|
||||||
providers.find((provider) => provider.id === oauthAutoRedirect) !==
|
providers.find((provider) => provider.id === oauth.autoRedirect) !==
|
||||||
undefined && redirectUri !== undefined,
|
undefined && redirectUri !== undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -148,21 +153,47 @@ export const LoginPage = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { mutate: tailscaleMutate, isPending: tailscaleIsPending } =
|
||||||
|
useMutation({
|
||||||
|
mutationFn: () => axios.post("/api/user/tailscale"),
|
||||||
|
mutationKey: ["tailscale"],
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(t("loginSuccessTitle"), {
|
||||||
|
description: t("loginTailscaleSuccess"),
|
||||||
|
});
|
||||||
|
|
||||||
|
redirectTimer.current = window.setTimeout(() => {
|
||||||
|
if (oidcParams.isOidc) {
|
||||||
|
window.location.replace(`/authorize?${oidcParams.compiled}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.location.replace(
|
||||||
|
`/continue${redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : ""}`,
|
||||||
|
);
|
||||||
|
}, 500);
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast.error(t("loginFailTitle"), {
|
||||||
|
description: t("loginTailscaleFail"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
!isLoggedIn &&
|
!auth.authenticated &&
|
||||||
isOauthAutoRedirect &&
|
isOauthAutoRedirect &&
|
||||||
!hasAutoRedirectedRef.current &&
|
!hasAutoRedirectedRef.current &&
|
||||||
redirectUri !== undefined
|
redirectUri !== undefined
|
||||||
) {
|
) {
|
||||||
hasAutoRedirectedRef.current = true;
|
hasAutoRedirectedRef.current = true;
|
||||||
oauthMutate(oauthAutoRedirect);
|
oauthMutate(oauth.autoRedirect);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
isLoggedIn,
|
auth.authenticated,
|
||||||
oauthMutate,
|
oauthMutate,
|
||||||
hasAutoRedirectedRef,
|
hasAutoRedirectedRef,
|
||||||
oauthAutoRedirect,
|
oauth.autoRedirect,
|
||||||
isOauthAutoRedirect,
|
isOauthAutoRedirect,
|
||||||
redirectUri,
|
redirectUri,
|
||||||
]);
|
]);
|
||||||
@@ -179,11 +210,11 @@ export const LoginPage = () => {
|
|||||||
};
|
};
|
||||||
}, [redirectTimer, redirectButtonTimer]);
|
}, [redirectTimer, redirectButtonTimer]);
|
||||||
|
|
||||||
if (isLoggedIn && oidcParams.isOidc) {
|
if (auth.authenticated && oidcParams.isOidc) {
|
||||||
return <Navigate to={`/authorize?${oidcParams.compiled}`} replace />;
|
return <Navigate to={`/authorize?${oidcParams.compiled}`} replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoggedIn && redirectUri !== undefined) {
|
if (auth.authenticated && redirectUri !== undefined) {
|
||||||
return (
|
return (
|
||||||
<Navigate
|
<Navigate
|
||||||
to={`/continue${redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : ""}`}
|
to={`/continue${redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : ""}`}
|
||||||
@@ -192,7 +223,7 @@ export const LoginPage = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoggedIn) {
|
if (auth.authenticated) {
|
||||||
return <Navigate to="/logout" replace />;
|
return <Navigate to="/logout" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,10 +259,49 @@ export const LoginPage = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (useTailscale) {
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="gap-3">
|
||||||
|
<TailscaleIcon className="mx-auto h-8 w-8" />
|
||||||
|
<CardTitle className="text-center text-xl">
|
||||||
|
{t("loginTailscaleTitle")}
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-4">
|
||||||
|
<div className="text-muted-foreground text-sm">
|
||||||
|
{t("loginTailscaleDescription")}
|
||||||
|
</div>
|
||||||
|
<div className="text-muted-foreground text-sm">
|
||||||
|
{t("loginTailscaleDeviceName")} <code>{tailscale.nodeName}</code>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="flex flex-col items-stretch gap-3">
|
||||||
|
<Button
|
||||||
|
className="w-full"
|
||||||
|
onClick={() => tailscaleMutate()}
|
||||||
|
loading={tailscaleIsPending}
|
||||||
|
>
|
||||||
|
{t("loginTailscaleSubmit")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="w-full"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setUseTailscale(false)}
|
||||||
|
disabled={tailscaleIsPending}
|
||||||
|
>
|
||||||
|
{t("loginTailscaleOtherMethod")}
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="gap-1.5">
|
<CardHeader className="gap-1.5">
|
||||||
<CardTitle className="text-center text-xl">{title}</CardTitle>
|
<CardTitle className="text-center text-xl">{ui.title}</CardTitle>
|
||||||
{providers.length > 0 && (
|
{providers.length > 0 && (
|
||||||
<CardDescription className="text-center">
|
<CardDescription className="text-center">
|
||||||
{oauthProviders.length !== 0
|
{oauthProviders.length !== 0
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ import { useEffect, useRef } from "react";
|
|||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { Navigate } from "react-router";
|
import { Navigate } from "react-router";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { type UseMutationResult } from "@tanstack/react-query";
|
||||||
|
import { type AxiosResponse } from "axios";
|
||||||
|
|
||||||
export const LogoutPage = () => {
|
export const LogoutPage = () => {
|
||||||
const { provider, username, isLoggedIn, email, oauthName } = useUserContext();
|
const { auth, oauth, tailscale } = useUserContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const redirectTimer = useRef<number | null>(null);
|
const redirectTimer = useRef<number | null>(null);
|
||||||
@@ -47,42 +49,82 @@ export const LogoutPage = () => {
|
|||||||
};
|
};
|
||||||
}, [redirectTimer]);
|
}, [redirectTimer]);
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
if (!auth.authenticated) {
|
||||||
return <Navigate to="/login" replace />;
|
return <Navigate to="/login" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oauth.active) {
|
||||||
|
return (
|
||||||
|
<LogoutLayout logoutMutation={logoutMutation}>
|
||||||
|
<Trans
|
||||||
|
i18nKey="logoutOauthSubtitle"
|
||||||
|
t={t}
|
||||||
|
components={{
|
||||||
|
code: <code />,
|
||||||
|
}}
|
||||||
|
values={{
|
||||||
|
username: auth.email,
|
||||||
|
provider: oauth.displayName,
|
||||||
|
}}
|
||||||
|
shouldUnescape={true}
|
||||||
|
/>
|
||||||
|
</LogoutLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auth.providerId === "tailscale") {
|
||||||
|
return (
|
||||||
|
<LogoutLayout logoutMutation={logoutMutation}>
|
||||||
|
<Trans
|
||||||
|
i18nKey="logoutTailscaleSubtitle"
|
||||||
|
t={t}
|
||||||
|
components={{
|
||||||
|
code: <code />,
|
||||||
|
}}
|
||||||
|
values={{
|
||||||
|
deviceName: tailscale.nodeName,
|
||||||
|
}}
|
||||||
|
shouldUnescape={true}
|
||||||
|
/>
|
||||||
|
</LogoutLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LogoutLayout logoutMutation={logoutMutation}>
|
||||||
|
<Trans
|
||||||
|
i18nKey="logoutUsernameSubtitle"
|
||||||
|
t={t}
|
||||||
|
components={{
|
||||||
|
code: <code />,
|
||||||
|
}}
|
||||||
|
values={{
|
||||||
|
username: auth.username,
|
||||||
|
}}
|
||||||
|
shouldUnescape={true}
|
||||||
|
/>
|
||||||
|
</LogoutLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface LogoutLayoutProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
logoutMutation: UseMutationResult<
|
||||||
|
//eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-empty-object-type
|
||||||
|
AxiosResponse<any, any, {}>,
|
||||||
|
Error,
|
||||||
|
void,
|
||||||
|
unknown
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function LogoutLayout({ children, logoutMutation }: LogoutLayoutProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="gap-1.5">
|
<CardHeader className="gap-1.5">
|
||||||
<CardTitle className="text-xl">{t("logoutTitle")}</CardTitle>
|
<CardTitle className="text-xl">{t("logoutTitle")}</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>{children}</CardDescription>
|
||||||
{provider !== "local" && provider !== "ldap" ? (
|
|
||||||
<Trans
|
|
||||||
i18nKey="logoutOauthSubtitle"
|
|
||||||
t={t}
|
|
||||||
components={{
|
|
||||||
code: <code />,
|
|
||||||
}}
|
|
||||||
values={{
|
|
||||||
username: email,
|
|
||||||
provider: oauthName,
|
|
||||||
}}
|
|
||||||
shouldUnescape={true}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Trans
|
|
||||||
i18nKey="logoutUsernameSubtitle"
|
|
||||||
t={t}
|
|
||||||
components={{
|
|
||||||
code: <code />,
|
|
||||||
}}
|
|
||||||
values={{
|
|
||||||
username,
|
|
||||||
}}
|
|
||||||
shouldUnescape={true}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardFooter>
|
<CardFooter>
|
||||||
<Button
|
<Button
|
||||||
@@ -96,4 +138,4 @@ export const LogoutPage = () => {
|
|||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { toast } from "sonner";
|
|||||||
import { useOIDCParams } from "@/lib/hooks/oidc";
|
import { useOIDCParams } from "@/lib/hooks/oidc";
|
||||||
|
|
||||||
export const TotpPage = () => {
|
export const TotpPage = () => {
|
||||||
const { totpPending } = useUserContext();
|
const { totp } = useUserContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const formId = useId();
|
const formId = useId();
|
||||||
@@ -64,7 +64,7 @@ export const TotpPage = () => {
|
|||||||
};
|
};
|
||||||
}, [redirectTimer]);
|
}, [redirectTimer]);
|
||||||
|
|
||||||
if (!totpPending) {
|
if (!totp.pending) {
|
||||||
return <Navigate to="/" replace />;
|
return <Navigate to="/" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,32 @@ export const providerSchema = z.object({
|
|||||||
oauth: z.boolean(),
|
oauth: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const appContextSchema = z.object({
|
const authSchema = z.object({
|
||||||
providers: z.array(providerSchema),
|
providers: z.array(providerSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
const oauthSchema = z.object({
|
||||||
|
autoRedirect: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const uiSchema = z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
appUrl: z.string(),
|
|
||||||
cookieDomain: z.string(),
|
|
||||||
forgotPasswordMessage: z.string(),
|
forgotPasswordMessage: z.string(),
|
||||||
backgroundImage: z.string(),
|
backgroundImage: z.string(),
|
||||||
oauthAutoRedirect: z.string(),
|
|
||||||
warningsEnabled: z.boolean(),
|
warningsEnabled: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const appSchema = z.object({
|
||||||
|
appUrl: z.string(),
|
||||||
|
cookieDomain: z.string(),
|
||||||
|
trustedDomains: z.array(z.string()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const appContextSchema = z.object({
|
||||||
|
auth: authSchema,
|
||||||
|
oauth: oauthSchema,
|
||||||
|
ui: uiSchema,
|
||||||
|
app: appSchema,
|
||||||
|
});
|
||||||
|
|
||||||
export type AppContextSchema = z.infer<typeof appContextSchema>;
|
export type AppContextSchema = z.infer<typeof appContextSchema>;
|
||||||
|
|||||||
@@ -1,14 +1,31 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const userContextSchema = z.object({
|
const authSchema = z.object({
|
||||||
isLoggedIn: z.boolean(),
|
authenticated: z.boolean(),
|
||||||
username: z.string(),
|
username: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
email: z.string(),
|
email: z.string(),
|
||||||
provider: z.string(),
|
providerId: z.string(),
|
||||||
oauth: z.boolean(),
|
});
|
||||||
totpPending: z.boolean(),
|
|
||||||
oauthName: z.string(),
|
const oauthSchema = z.object({
|
||||||
|
active: z.boolean(),
|
||||||
|
displayName: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const totpSchema = z.object({
|
||||||
|
pending: z.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const tailscaleSchema = z.object({
|
||||||
|
nodeName: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const userContextSchema = z.object({
|
||||||
|
auth: authSchema,
|
||||||
|
oauth: oauthSchema,
|
||||||
|
totp: totpSchema,
|
||||||
|
tailscale: tailscaleSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type UserContextSchema = z.infer<typeof userContextSchema>;
|
export type UserContextSchema = z.infer<typeof userContextSchema>;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/tinyauthapp/tinyauth
|
module github.com/tinyauthapp/tinyauth
|
||||||
|
|
||||||
go 1.26.0
|
go 1.26.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
charm.land/huh/v2 v2.0.3
|
charm.land/huh/v2 v2.0.3
|
||||||
@@ -18,11 +18,12 @@ require (
|
|||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298
|
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298
|
||||||
github.com/weppos/publicsuffix-go v0.50.3
|
github.com/weppos/publicsuffix-go v0.50.3
|
||||||
golang.org/x/crypto v0.51.0
|
golang.org/x/crypto v0.50.0
|
||||||
golang.org/x/oauth2 v0.36.0
|
golang.org/x/oauth2 v0.36.0
|
||||||
k8s.io/apimachinery v0.36.1
|
k8s.io/apimachinery v0.36.0
|
||||||
k8s.io/client-go v0.36.1
|
k8s.io/client-go v0.36.0
|
||||||
modernc.org/sqlite v1.50.1
|
modernc.org/sqlite v1.50.0
|
||||||
|
tailscale.com v1.96.5
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -30,13 +31,29 @@ require (
|
|||||||
charm.land/bubbletea/v2 v2.0.2 // indirect
|
charm.land/bubbletea/v2 v2.0.2 // indirect
|
||||||
charm.land/lipgloss/v2 v2.0.1 // indirect
|
charm.land/lipgloss/v2 v2.0.1 // indirect
|
||||||
dario.cat/mergo v1.0.1 // indirect
|
dario.cat/mergo v1.0.1 // indirect
|
||||||
|
filippo.io/edwards25519 v1.2.0 // indirect
|
||||||
github.com/Azure/go-ntlmssp v0.1.1 // indirect
|
github.com/Azure/go-ntlmssp v0.1.1 // indirect
|
||||||
github.com/BurntSushi/toml v1.6.0 // indirect
|
github.com/BurntSushi/toml v1.6.0 // indirect
|
||||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||||
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
|
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
|
github.com/akutz/memconn v0.1.0 // indirect
|
||||||
|
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e // indirect
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.41.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.29.5 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.58 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
|
||||||
|
github.com/aws/smithy-go v1.24.0 // indirect
|
||||||
github.com/boombuler/barcode v1.0.2 // indirect
|
github.com/boombuler/barcode v1.0.2 // indirect
|
||||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||||
github.com/bytedance/sonic v1.15.0 // indirect
|
github.com/bytedance/sonic v1.15.0 // indirect
|
||||||
@@ -54,10 +71,12 @@ require (
|
|||||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
|
github.com/coder/websocket v1.8.12 // indirect
|
||||||
github.com/containerd/errdefs v1.0.0 // indirect
|
github.com/containerd/errdefs v1.0.0 // indirect
|
||||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||||
github.com/containerd/log v0.1.0 // indirect
|
github.com/creachadair/msync v0.7.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
|
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
|
||||||
github.com/distribution/reference v0.6.0 // indirect
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
github.com/docker/go-connections v0.5.0 // indirect
|
github.com/docker/go-connections v0.5.0 // indirect
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
@@ -65,8 +84,10 @@ require (
|
|||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||||
|
github.com/gaissmai/bart v0.26.1 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||||
|
github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
@@ -74,8 +95,16 @@ require (
|
|||||||
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||||
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
|
github.com/google/btree v1.1.3 // indirect
|
||||||
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
|
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
|
||||||
github.com/huandu/xstrings v1.5.0 // indirect
|
github.com/huandu/xstrings v1.5.0 // indirect
|
||||||
|
github.com/huin/goupnp v1.3.0 // indirect
|
||||||
|
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/compress v1.18.2 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||||
@@ -83,35 +112,46 @@ require (
|
|||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.20 // indirect
|
github.com/mattn/go-runewidth v0.0.20 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.32 // indirect
|
github.com/mattn/go-sqlite3 v1.14.32 // indirect
|
||||||
|
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
|
||||||
|
github.com/mdlayher/socket v0.5.0 // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
|
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
||||||
github.com/moby/term v0.5.2 // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/pires/go-proxyproto v0.8.1 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
|
github.com/prometheus-community/pro-bing v0.4.0 // indirect
|
||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.59.0 // indirect
|
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
github.com/safchain/ethtool v0.3.0 // indirect
|
||||||
github.com/shopspring/decimal v1.4.0 // indirect
|
github.com/shopspring/decimal v1.4.0 // indirect
|
||||||
github.com/spf13/cast v1.10.0 // indirect
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
|
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
|
||||||
|
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
|
||||||
|
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
|
||||||
|
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect
|
||||||
|
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
|
||||||
|
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||||
@@ -119,22 +159,27 @@ require (
|
|||||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
|
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
|
||||||
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/arch v0.22.0 // indirect
|
golang.org/x/arch v0.22.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||||
golang.org/x/net v0.53.0 // indirect
|
golang.org/x/net v0.52.0 // indirect
|
||||||
golang.org/x/sync v0.20.0 // indirect
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
golang.org/x/sys v0.44.0 // indirect
|
golang.org/x/sys v0.43.0 // indirect
|
||||||
golang.org/x/term v0.43.0 // indirect
|
golang.org/x/term v0.42.0 // indirect
|
||||||
golang.org/x/text v0.37.0 // indirect
|
golang.org/x/text v0.36.0 // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
|
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
||||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
|
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gotest.tools/v3 v3.5.2 // indirect
|
gotest.tools/v3 v3.5.2 // indirect
|
||||||
|
gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 // indirect
|
||||||
k8s.io/klog/v2 v2.140.0 // indirect
|
k8s.io/klog/v2 v2.140.0 // indirect
|
||||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
|
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
|
||||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
|
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
|
||||||
modernc.org/libc v1.72.3 // indirect
|
modernc.org/libc v1.72.0 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
rsc.io/qr v0.2.0 // indirect
|
rsc.io/qr v0.2.0 // indirect
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f h1:1C7nZuxUMNz7eiQALRfiqNOm04+m3edWlRff/BYHf0Q=
|
||||||
|
9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f/go.mod h1:hHyrZRryGqVdqrknjq5OWDLGCTJ2NeEvtrpR96mjraM=
|
||||||
charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s=
|
charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s=
|
||||||
charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI=
|
charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI=
|
||||||
charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0=
|
charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0=
|
||||||
@@ -8,6 +10,10 @@ charm.land/lipgloss/v2 v2.0.1 h1:6Xzrn49+Py1Um5q/wZG1gWgER2+7dUyZ9XMEufqPSys=
|
|||||||
charm.land/lipgloss/v2 v2.0.1/go.mod h1:KjPle2Qd3YmvP1KL5OMHiHysGcNwq6u83MUjYkFvEkM=
|
charm.land/lipgloss/v2 v2.0.1/go.mod h1:KjPle2Qd3YmvP1KL5OMHiHysGcNwq6u83MUjYkFvEkM=
|
||||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
|
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||||
|
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||||
|
filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc=
|
||||||
|
filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
github.com/Azure/go-ntlmssp v0.1.1 h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw=
|
github.com/Azure/go-ntlmssp v0.1.1 h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw=
|
||||||
@@ -18,16 +24,50 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ
|
|||||||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
||||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
|
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||||
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||||
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
|
github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
|
||||||
|
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
|
||||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
|
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
|
||||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.29.5 h1:4lS2IB+wwkj5J43Tq/AwvnscBerBJtQQ6YS7puzCI1k=
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.29.5/go.mod h1:SNzldMlDVbN6nWxM7XsUiNXPSa1LWlqiXtvh/1PrJGg=
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.58 h1:/d7FUpAPU8Lf2KUdjniQvfNdlMID0Sd9pS23FJ3SS9Y=
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.58/go.mod h1:aVYW33Ow10CyMQGFgC0ptMRIqJWvJ4nxZb0sUiuQT/A=
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 h1:7lOW8NUwE9UZekS1DYoiPdVAqZ6A+LheHWb+mHbNOq8=
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27/go.mod h1:w1BASFIPOPUae7AgaH4SbjNbfdkxuggLyGfNFTn8ITY=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 h1:c5WJ3iHz7rLIgArznb3JCSQT3uUMiz9DLZhIX+1G8ok=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.24.14/go.mod h1:+JJQTxB6N4niArC14YNtxcQtwEqzS3o9Z32n7q33Rfs=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 h1:f1L/JtUkVODD+k1+IiSJUUv8A++2qVr+Xvb3xWXETMU=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13/go.mod h1:tvqlFoja8/s0o+UruA1Nrezo/df0PzdunMDDurUfg6U=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk=
|
||||||
|
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
|
||||||
|
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||||
|
github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02 h1:bXAPYSbdYbS5VTy92NIUbeDI1qyggi+JYh5op9IFlcQ=
|
||||||
|
github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c=
|
||||||
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
|
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
|
||||||
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
|
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
@@ -69,26 +109,46 @@ github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2
|
|||||||
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
|
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
|
||||||
github.com/charmbracelet/x/xpty v0.1.3 h1:eGSitii4suhzrISYH50ZfufV3v085BXQwIytcOdFSsw=
|
github.com/charmbracelet/x/xpty v0.1.3 h1:eGSitii4suhzrISYH50ZfufV3v085BXQwIytcOdFSsw=
|
||||||
github.com/charmbracelet/x/xpty v0.1.3/go.mod h1:poPYpWuLDBFCKmKLDnhBp51ATa0ooD8FhypRwEFtH3Y=
|
github.com/charmbracelet/x/xpty v0.1.3/go.mod h1:poPYpWuLDBFCKmKLDnhBp51ATa0ooD8FhypRwEFtH3Y=
|
||||||
|
github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok=
|
||||||
|
github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE=
|
||||||
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
||||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
|
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
|
||||||
|
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||||
|
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
|
||||||
|
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||||
|
github.com/creachadair/mds v0.25.9 h1:080Hr8laN2h+l3NeVCGMBpXtIPnl9mz8e4HLraGPqtA=
|
||||||
|
github.com/creachadair/mds v0.25.9/go.mod h1:4hatI3hRM+qhzuAmqPRFvaBM8mONkS7nsLxkcuTYUIs=
|
||||||
|
github.com/creachadair/msync v0.7.1 h1:SeZmuEBXQPe5GqV/C94ER7QIZPwtvFbeQiykzt/7uho=
|
||||||
|
github.com/creachadair/msync v0.7.1/go.mod h1:8CcFlLsSujfHE5wWm19uUBLHIPDAUr6LXDwneVMO008=
|
||||||
|
github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc=
|
||||||
|
github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g=
|
||||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk=
|
||||||
|
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=
|
||||||
|
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc h1:8WFBn63wegobsYAX0YjD+8suexZDga5CctH4CCTx2+8=
|
||||||
|
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||||
|
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
|
||||||
|
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
|
||||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
|
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||||
|
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||||
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||||
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
@@ -107,14 +167,20 @@ github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sa
|
|||||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
|
github.com/gaissmai/bart v0.26.1 h1:+w4rnLGNlA2GDVn382Tfe3jOsK5vOr5n4KmigJ9lbTo=
|
||||||
|
github.com/gaissmai/bart v0.26.1/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c=
|
||||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||||
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
||||||
|
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
|
||||||
|
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||||
|
github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced h1:Q311OHjMh/u5E2TITc++WlTP5We0xNseRMkHDyvhW7I=
|
||||||
|
github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ=
|
github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0=
|
github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
@@ -122,10 +188,12 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU=
|
||||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4=
|
||||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
@@ -136,12 +204,20 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
|||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||||
|
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo=
|
||||||
|
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737/go.mod h1:MIS0jDzbU/vuM9MC4YnBITCv+RYuTRq8dJzmCrFsK9g=
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
|
||||||
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
|
||||||
github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
|
github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
|
||||||
github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE=
|
github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE=
|
||||||
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||||
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||||
|
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||||
|
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
||||||
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
@@ -149,7 +225,11 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
|||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
|
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
|
||||||
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
|
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
|
||||||
|
github.com/google/go-tpm v0.9.4 h1:awZRf9FwOeTunQmHoDYSHJps3ie6f1UlhS1fOdPEt1I=
|
||||||
|
github.com/google/go-tpm v0.9.4/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI=
|
||||||
|
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
@@ -158,10 +238,19 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz
|
|||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
|
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
|
||||||
|
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
|
||||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
|
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
|
||||||
|
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
|
||||||
|
github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk=
|
||||||
|
github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U=
|
||||||
|
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
|
||||||
|
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
|
||||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||||
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
||||||
@@ -174,12 +263,24 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
|
|||||||
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
||||||
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
||||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||||
|
github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g=
|
||||||
|
github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
|
||||||
|
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||||
|
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=
|
||||||
|
github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||||
|
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
|
||||||
|
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
|
||||||
|
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||||
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@@ -200,10 +301,22 @@ github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjc
|
|||||||
github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
||||||
|
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
|
||||||
|
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg=
|
||||||
|
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o=
|
||||||
|
github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c=
|
||||||
|
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
|
||||||
|
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
|
||||||
|
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
|
||||||
github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4=
|
github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4=
|
||||||
github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU=
|
github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU=
|
||||||
|
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||||
|
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
|
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||||
|
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
@@ -230,19 +343,33 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
|||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||||
|
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||||
|
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
|
||||||
|
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||||
|
github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=
|
||||||
|
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
|
||||||
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
|
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||||
|
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||||
@@ -255,6 +382,8 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
|
|||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
|
github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
|
||||||
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||||
|
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
|
||||||
|
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
||||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
@@ -275,12 +404,40 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
|||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=
|
||||||
|
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
|
||||||
|
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
|
||||||
|
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
|
||||||
|
github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869 h1:SRL6irQkKGQKKLzvQP/ke/2ZuB7Py5+XuqtOgSj+iMM=
|
||||||
|
github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ=
|
||||||
|
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
|
||||||
|
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
|
||||||
|
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU=
|
||||||
|
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
|
||||||
|
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA=
|
||||||
|
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc=
|
||||||
|
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14=
|
||||||
|
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
|
||||||
|
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M=
|
||||||
|
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y=
|
||||||
|
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da h1:jVRUZPRs9sqyKlYHHzHjAqKN+6e/Vog6NpHYeNPJqOw=
|
||||||
|
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
|
||||||
|
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek=
|
||||||
|
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
|
||||||
|
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
|
||||||
|
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
|
||||||
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298 h1:EYSb5jv8ZL/0/NVFZtY7Ejplk0QG5+3lrdL3mSrjFZQ=
|
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298 h1:EYSb5jv8ZL/0/NVFZtY7Ejplk0QG5+3lrdL3mSrjFZQ=
|
||||||
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298/go.mod h1:TlUDoCF66hMqFZqoBym9bUdJ0bKAWYMir6hLJeYN5z0=
|
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298/go.mod h1:TlUDoCF66hMqFZqoBym9bUdJ0bKAWYMir6hLJeYN5z0=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/u-root/u-root v0.14.0 h1:Ka4T10EEML7dQ5XDvO9c3MBN8z4nuSnGjcd1jmU2ivg=
|
||||||
|
github.com/u-root/u-root v0.14.0/go.mod h1:hAyZorapJe4qzbLWlAkmSVCJGbfoU9Pu4jpJ1WMluqE=
|
||||||
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
||||||
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
||||||
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
|
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||||
|
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
github.com/weppos/publicsuffix-go v0.50.3 h1:eT5dcjHQcVDNc0igpFEsGHKIip30feuB2zuuI9eJxiE=
|
github.com/weppos/publicsuffix-go v0.50.3 h1:eT5dcjHQcVDNc0igpFEsGHKIip30feuB2zuuI9eJxiE=
|
||||||
github.com/weppos/publicsuffix-go v0.50.3/go.mod h1:/rOa781xBykZhHK/I3QeHo92qdDKVmKZKF7s8qAEM/4=
|
github.com/weppos/publicsuffix-go v0.50.3/go.mod h1:/rOa781xBykZhHK/I3QeHo92qdDKVmKZKF7s8qAEM/4=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
@@ -291,8 +448,8 @@ go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF
|
|||||||
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
|
||||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
||||||
@@ -315,31 +472,45 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
|||||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek=
|
||||||
|
go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
|
||||||
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
||||||
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8=
|
||||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
|
||||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
|
||||||
|
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||||
|
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||||
|
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||||
|
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
|
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
|
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||||
|
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
|
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
|
||||||
@@ -361,22 +532,28 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||||
k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY=
|
gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 h1:Zy8IV/+FMLxy6j6p87vk/vQGKcdnbprwjTxc8UiUtsA=
|
||||||
k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo=
|
gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
|
||||||
k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA=
|
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0 h1:5SXjd4ET5dYijLaf0O3aOenC0Z4ZafIWSpjUzsQaNho=
|
||||||
k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8=
|
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0/go.mod h1:EPDDhEZqVHhWuPI5zPAsjU0U7v9xNIWjoOVyZ5ZcniQ=
|
||||||
k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0=
|
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||||
k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU=
|
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||||
|
k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80=
|
||||||
|
k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34=
|
||||||
|
k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ=
|
||||||
|
k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc=
|
||||||
|
k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c=
|
||||||
|
k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y=
|
||||||
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
|
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
|
||||||
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
|
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
|
||||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
|
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
|
||||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
|
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
|
||||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
|
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
|
||||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||||
modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
|
modernc.org/cc/v4 v4.27.3 h1:uNCgn37E5U09mTv1XgskEVUJ8ADKpmFMPxzGJ0TSo+U=
|
||||||
modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
modernc.org/cc/v4 v4.27.3/go.mod h1:3YjcbCqhoTTHPycJDRl2WZKKFj0nwcOIPBfEZK0Hdk8=
|
||||||
modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ=
|
modernc.org/ccgo/v4 v4.32.4 h1:L5OB8rpEX4ZsXEQwGozRfJyJSFHbbNVOoQ59DU9/KuU=
|
||||||
modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A=
|
modernc.org/ccgo/v4 v4.32.4/go.mod h1:lY7f+fiTDHfcv6YlRgSkxYfhs+UvOEEzj49jAn2TOx0=
|
||||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
@@ -385,18 +562,18 @@ modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
|||||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU=
|
modernc.org/libc v1.72.0 h1:IEu559v9a0XWjw0DPoVKtXpO2qt5NVLAnFaBbjq+n8c=
|
||||||
modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs=
|
modernc.org/libc v1.72.0/go.mod h1:tTU8DL8A+XLVkEY3x5E/tO7s2Q/q42EtnNWda/L5QhQ=
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/sqlite v1.50.1 h1:l+cQvn0sd0zJJtfygGHuQJ5AjlrwXmWPw4KP3ZMwr9w=
|
modernc.org/sqlite v1.50.0 h1:eMowQSWLK0MeiQTdmz3lqoF5dqclujdlIKeJA11+7oM=
|
||||||
modernc.org/sqlite v1.50.1/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
|
modernc.org/sqlite v1.50.0/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew=
|
||||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
@@ -411,3 +588,7 @@ sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80
|
|||||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||||
|
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
|
||||||
|
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||||
|
tailscale.com v1.96.5 h1:gNkfA/KSZAl6jCH9cj8urq00HRWItDDTtGsyATI89jA=
|
||||||
|
tailscale.com v1.96.5/go.mod h1:/3lnZBYb2UEwnN0MNu2SDXUtT06AGd5k0s+OWx3WmcY=
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -34,19 +33,21 @@ type Services struct {
|
|||||||
ldapService *service.LdapService
|
ldapService *service.LdapService
|
||||||
oauthBrokerService *service.OAuthBrokerService
|
oauthBrokerService *service.OAuthBrokerService
|
||||||
oidcService *service.OIDCService
|
oidcService *service.OIDCService
|
||||||
|
tailscaleService *service.TailscaleService
|
||||||
}
|
}
|
||||||
|
|
||||||
type BootstrapApp struct {
|
type BootstrapApp struct {
|
||||||
config model.Config
|
config model.Config
|
||||||
runtime model.RuntimeConfig
|
runtime model.RuntimeConfig
|
||||||
services Services
|
services Services
|
||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
queries *repository.Queries
|
queries *repository.Queries
|
||||||
router *gin.Engine
|
router *gin.Engine
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
listeners []Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBootstrapApp(config model.Config) *BootstrapApp {
|
func NewBootstrapApp(config model.Config) *BootstrapApp {
|
||||||
@@ -66,6 +67,8 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
log.Init()
|
log.Init()
|
||||||
app.log = log
|
app.log = log
|
||||||
|
|
||||||
|
app.log.App.Info().Msgf("Starting Tinyauth version: %s", model.Version)
|
||||||
|
|
||||||
// get app url
|
// get app url
|
||||||
if app.config.AppURL == "" {
|
if app.config.AppURL == "" {
|
||||||
return errors.New("app url cannot be empty, perhaps config loading failed")
|
return errors.New("app url cannot be empty, perhaps config loading failed")
|
||||||
@@ -78,6 +81,7 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.runtime.AppURL = appUrl.Scheme + "://" + appUrl.Host
|
app.runtime.AppURL = appUrl.Scheme + "://" + appUrl.Host
|
||||||
|
app.runtime.TrustedDomains = append(app.runtime.TrustedDomains, app.runtime.AppURL)
|
||||||
|
|
||||||
// validate session config
|
// validate session config
|
||||||
if app.config.Auth.SessionMaxLifetime != 0 && app.config.Auth.SessionMaxLifetime < app.config.Auth.SessionExpiry {
|
if app.config.Auth.SessionMaxLifetime != 0 && app.config.Auth.SessionMaxLifetime < app.config.Auth.SessionExpiry {
|
||||||
@@ -228,6 +232,11 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
|
|
||||||
app.runtime.ConfiguredProviders = configuredProviders
|
app.runtime.ConfiguredProviders = configuredProviders
|
||||||
|
|
||||||
|
// throw in tailscale if it's configured just before setting up the controllers
|
||||||
|
if app.services.tailscaleService != nil {
|
||||||
|
app.runtime.TrustedDomains = append(app.runtime.TrustedDomains, "https://"+app.services.tailscaleService.GetHostname())
|
||||||
|
}
|
||||||
|
|
||||||
// setup router
|
// setup router
|
||||||
err = app.setupRouter()
|
err = app.setupRouter()
|
||||||
|
|
||||||
@@ -245,42 +254,18 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
app.wg.Go(app.heartbeatRoutine)
|
app.wg.Go(app.heartbeatRoutine)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create err channel to listen for server errors
|
// setup listeners
|
||||||
errChanLen := 0
|
app.listeners = app.calculateListenerPolicy()
|
||||||
|
|
||||||
runUnix := app.config.Server.SocketPath != ""
|
|
||||||
runHTTP := app.config.Server.SocketPath == "" || app.config.Server.ConcurrentListenersEnabled
|
|
||||||
|
|
||||||
if runUnix {
|
|
||||||
errChanLen++
|
|
||||||
}
|
|
||||||
|
|
||||||
if runHTTP {
|
|
||||||
errChanLen++
|
|
||||||
}
|
|
||||||
|
|
||||||
errChan := make(chan error, errChanLen)
|
|
||||||
|
|
||||||
if app.config.Server.ConcurrentListenersEnabled {
|
if app.config.Server.ConcurrentListenersEnabled {
|
||||||
app.log.App.Info().Msg("Concurrent listeners enabled, will run on all available listeners")
|
app.log.App.Info().Msg("Concurrent listeners enabled, will run on all available listeners")
|
||||||
}
|
}
|
||||||
|
|
||||||
// serve unix
|
// run listeners
|
||||||
if runUnix {
|
lec, err := app.runListeners()
|
||||||
app.wg.Go(func() {
|
|
||||||
if err := app.serveUnix(); err != nil {
|
|
||||||
errChan <- err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// serve to http
|
if err != nil {
|
||||||
if runHTTP {
|
return fmt.Errorf("failed to run listeners: %w", err)
|
||||||
app.wg.Go(func() {
|
|
||||||
if err := app.serveHTTP(); err != nil {
|
|
||||||
errChan <- err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// monitor cancellation and server errors
|
// monitor cancellation and server errors
|
||||||
@@ -289,89 +274,14 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
case <-app.ctx.Done():
|
case <-app.ctx.Done():
|
||||||
app.log.App.Info().Msg("Oh, it's time for me to go, bye!")
|
app.log.App.Info().Msg("Oh, it's time for me to go, bye!")
|
||||||
return nil
|
return nil
|
||||||
case err := <-errChan:
|
case err := <-lec:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("server error: %w", err)
|
return fmt.Errorf("listener error: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *BootstrapApp) serveHTTP() error {
|
|
||||||
address := fmt.Sprintf("%s:%d", app.config.Server.Address, app.config.Server.Port)
|
|
||||||
|
|
||||||
app.log.App.Info().Msgf("Starting server on %s", address)
|
|
||||||
|
|
||||||
server := &http.Server{
|
|
||||||
Addr: address,
|
|
||||||
Handler: app.router.Handler(),
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
<-app.ctx.Done()
|
|
||||||
app.log.App.Debug().Msg("Shutting down http listener")
|
|
||||||
server.Shutdown(app.ctx)
|
|
||||||
}()
|
|
||||||
|
|
||||||
err := server.ListenAndServe()
|
|
||||||
|
|
||||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
||||||
return fmt.Errorf("failed to start http listener: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (app *BootstrapApp) serveUnix() error {
|
|
||||||
if app.config.Server.SocketPath == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := os.Stat(app.config.Server.SocketPath)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
app.log.App.Info().Msgf("Removing existing socket file %s", app.config.Server.SocketPath)
|
|
||||||
err := os.Remove(app.config.Server.SocketPath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to remove existing socket file: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app.log.App.Info().Msgf("Starting server on unix socket %s", app.config.Server.SocketPath)
|
|
||||||
|
|
||||||
listener, err := net.Listen("unix", app.config.Server.SocketPath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create unix socket listener: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
server := &http.Server{
|
|
||||||
Handler: app.router.Handler(),
|
|
||||||
}
|
|
||||||
|
|
||||||
shutdown := func() {
|
|
||||||
server.Shutdown(app.ctx)
|
|
||||||
listener.Close()
|
|
||||||
os.Remove(app.config.Server.SocketPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
<-app.ctx.Done()
|
|
||||||
app.log.App.Debug().Msg("Shutting down unix socket listener")
|
|
||||||
shutdown()
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = server.Serve(listener)
|
|
||||||
|
|
||||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
||||||
shutdown()
|
|
||||||
return fmt.Errorf("failed to start unix socket listener: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (app *BootstrapApp) heartbeatRoutine() {
|
func (app *BootstrapApp) heartbeatRoutine() {
|
||||||
ticker := time.NewTicker(time.Duration(12) * time.Hour)
|
ticker := time.NewTicker(time.Duration(12) * time.Hour)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|||||||
@@ -1,14 +1,29 @@
|
|||||||
package bootstrap
|
package bootstrap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/middleware"
|
"github.com/tinyauthapp/tinyauth/internal/middleware"
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Listener int
|
||||||
|
|
||||||
|
const (
|
||||||
|
ListenerHTTP Listener = iota
|
||||||
|
ListenerUnix
|
||||||
|
ListenerTailscale
|
||||||
|
)
|
||||||
|
|
||||||
func (app *BootstrapApp) setupRouter() error {
|
func (app *BootstrapApp) setupRouter() error {
|
||||||
// we don't want gin debug mode
|
// we don't want gin debug mode
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
@@ -24,7 +39,7 @@ func (app *BootstrapApp) setupRouter() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
contextMiddleware := middleware.NewContextMiddleware(app.log, app.runtime, app.services.authService, app.services.oauthBrokerService)
|
contextMiddleware := middleware.NewContextMiddleware(app.log, app.runtime, app.services.authService, app.services.oauthBrokerService, app.services.tailscaleService)
|
||||||
engine.Use(contextMiddleware.Middleware())
|
engine.Use(contextMiddleware.Middleware())
|
||||||
|
|
||||||
uiMiddleware, err := middleware.NewUIMiddleware()
|
uiMiddleware, err := middleware.NewUIMiddleware()
|
||||||
@@ -53,3 +68,161 @@ func (app *BootstrapApp) setupRouter() error {
|
|||||||
app.router = engine
|
app.router = engine
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *BootstrapApp) runListeners() (chan error, error) {
|
||||||
|
// lec -> listener error channel
|
||||||
|
lec := make(chan error, len(app.listeners))
|
||||||
|
|
||||||
|
for _, listenerType := range app.listeners {
|
||||||
|
listenerFunc, err := app.listenerFromType(listenerType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get listener function: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.wg.Go(func() {
|
||||||
|
lec <- listenerFunc()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return lec, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The way we calculate listeners is as follows:
|
||||||
|
// If concurrent listeners are disabled, we pick the first available listener, so:
|
||||||
|
// 1. If tailscale is enabled, we use tailscale
|
||||||
|
// 2. If socket path is configured, we use unix socket
|
||||||
|
// 3. Finally if none is configured we use http
|
||||||
|
// If concurrent listeners are enabled, we add all available listeners in the following order
|
||||||
|
func (app *BootstrapApp) calculateListenerPolicy() []Listener {
|
||||||
|
l := []Listener{}
|
||||||
|
|
||||||
|
if !app.config.Server.ConcurrentListenersEnabled {
|
||||||
|
if app.config.Tailscale.Enabled {
|
||||||
|
l = append(l, ListenerTailscale)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
if app.config.Server.SocketPath != "" {
|
||||||
|
l = append(l, ListenerUnix)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
l = append(l, ListenerHTTP)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
if app.config.Server.SocketPath != "" {
|
||||||
|
l = append(l, ListenerUnix)
|
||||||
|
}
|
||||||
|
|
||||||
|
if app.config.Tailscale.Enabled {
|
||||||
|
l = append(l, ListenerTailscale)
|
||||||
|
}
|
||||||
|
|
||||||
|
l = append(l, ListenerHTTP)
|
||||||
|
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *BootstrapApp) listenerFromType(listenerType Listener) (func() error, error) {
|
||||||
|
switch listenerType {
|
||||||
|
case ListenerHTTP:
|
||||||
|
return app.serveHTTP, nil
|
||||||
|
case ListenerUnix:
|
||||||
|
return app.serveUnix, nil
|
||||||
|
case ListenerTailscale:
|
||||||
|
return app.serveTailscale, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid listener type: %d", listenerType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *BootstrapApp) serveHTTP() error {
|
||||||
|
address := fmt.Sprintf("%s:%d", app.config.Server.Address, app.config.Server.Port)
|
||||||
|
|
||||||
|
app.log.App.Info().Msgf("Starting server on %s", address)
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", address)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create tcp listener: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: address,
|
||||||
|
Handler: app.router.Handler(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.serve(listener, server, "http")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *BootstrapApp) serveUnix() error {
|
||||||
|
_, err := os.Stat(app.config.Server.SocketPath)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
app.log.App.Info().Msgf("Removing existing socket file %s", app.config.Server.SocketPath)
|
||||||
|
err := os.Remove(app.config.Server.SocketPath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to remove existing socket file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.log.App.Info().Msgf("Starting server on unix socket %s", app.config.Server.SocketPath)
|
||||||
|
|
||||||
|
listener, err := net.Listen("unix", app.config.Server.SocketPath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create unix socket listener: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server := &http.Server{
|
||||||
|
Handler: app.router.Handler(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.serve(listener, server, "unix socket")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *BootstrapApp) serveTailscale() error {
|
||||||
|
app.log.App.Info().Msgf("Starting Tailscale server on %s", fmt.Sprintf("https://%s", app.services.tailscaleService.GetHostname()))
|
||||||
|
|
||||||
|
listener, err := app.services.tailscaleService.CreateListener()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create tailscale listener: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server := &http.Server{
|
||||||
|
Handler: app.router.Handler(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.serve(listener, server, "tailscale")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *BootstrapApp) serve(listener net.Listener, server *http.Server, name string) error {
|
||||||
|
shutdown := func() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), model.GracefulShutdownTimeout*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
err := server.Shutdown(ctx)
|
||||||
|
if err != nil {
|
||||||
|
app.log.App.Error().Err(err).Msgf("Failed to shutdown %s listener gracefully", name)
|
||||||
|
}
|
||||||
|
listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-app.ctx.Done()
|
||||||
|
app.log.App.Debug().Msgf("Shutting down %s listener", name)
|
||||||
|
shutdown()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := server.Serve(listener)
|
||||||
|
|
||||||
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
shutdown()
|
||||||
|
return fmt.Errorf("failed to start %s listener: %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package bootstrap
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||||
@@ -45,13 +46,21 @@ func (app *BootstrapApp) setupServices() error {
|
|||||||
labelProvider = dockerService
|
labelProvider = dockerService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tailscaleService, err := service.NewTailscaleService(app.log, app.config, app.ctx, &app.wg)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
app.log.App.Warn().Err(err).Msg("Failed to initialize Tailscale connection, will continue without it")
|
||||||
|
} else {
|
||||||
|
app.services.tailscaleService = tailscaleService
|
||||||
|
}
|
||||||
|
|
||||||
accessControlsService := service.NewAccessControlsService(app.log, &labelProvider, app.config.Apps)
|
accessControlsService := service.NewAccessControlsService(app.log, &labelProvider, app.config.Apps)
|
||||||
app.services.accessControlService = accessControlsService
|
app.services.accessControlService = accessControlsService
|
||||||
|
|
||||||
oauthBrokerService := service.NewOAuthBrokerService(app.log, app.runtime.OAuthProviders, app.ctx)
|
oauthBrokerService := service.NewOAuthBrokerService(app.log, app.runtime.OAuthProviders, app.ctx)
|
||||||
app.services.oauthBrokerService = oauthBrokerService
|
app.services.oauthBrokerService = oauthBrokerService
|
||||||
|
|
||||||
authService := service.NewAuthService(app.log, app.config, app.runtime, app.ctx, &app.wg, app.services.ldapService, app.queries, app.services.oauthBrokerService)
|
authService := service.NewAuthService(app.log, app.config, app.runtime, app.ctx, &app.wg, app.services.ldapService, app.queries, app.services.oauthBrokerService, app.services.tailscaleService)
|
||||||
app.services.authService = authService
|
app.services.authService = authService
|
||||||
|
|
||||||
oidcService, err := service.NewOIDCService(app.log, app.config, app.runtime, app.queries, app.ctx, &app.wg)
|
oidcService, err := service.NewOIDCService(app.log, app.config, app.runtime, app.queries, app.ctx, &app.wg)
|
||||||
|
|||||||
@@ -1,39 +1,74 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// UCR -> User Context Response
|
||||||
|
|
||||||
|
type UCRAuth struct {
|
||||||
|
Authenticated bool `json:"authenticated"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
ProviderID string `json:"providerId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UCROAuth struct {
|
||||||
|
Active bool `json:"active"`
|
||||||
|
DisplayName string `json:"displayName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UCRTOTP struct {
|
||||||
|
Pending bool `json:"pending"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UCRTailscale struct {
|
||||||
|
NodeName string `json:"nodeName,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type UserContextResponse struct {
|
type UserContextResponse struct {
|
||||||
Status int `json:"status"`
|
Status int `json:"status"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
IsLoggedIn bool `json:"isLoggedIn"`
|
Auth UCRAuth `json:"auth"`
|
||||||
Username string `json:"username"`
|
OAuth UCROAuth `json:"oauth"`
|
||||||
Name string `json:"name"`
|
TOTP UCRTOTP `json:"totp"`
|
||||||
Email string `json:"email"`
|
Tailscale UCRTailscale `json:"tailscale"`
|
||||||
Provider string `json:"provider"`
|
}
|
||||||
OAuth bool `json:"oauth"`
|
|
||||||
TOTPPending bool `json:"totpPending"`
|
// ACR -> App Context Response
|
||||||
OAuthName string `json:"oauthName"`
|
|
||||||
|
type ACRAuth struct {
|
||||||
|
Providers []model.Provider `json:"providers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ACROAuth struct {
|
||||||
|
AutoRedirect string `json:"autoRedirect"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ACRUI struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
ForgotPasswordMessage string `json:"forgotPasswordMessage"`
|
||||||
|
BackgroundImage string `json:"backgroundImage"`
|
||||||
|
WarningsEnabled bool `json:"warningsEnabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ACRApp struct {
|
||||||
|
AppURL string `json:"appUrl"`
|
||||||
|
CookieDomain string `json:"cookieDomain"`
|
||||||
|
TrustedDomains []string `json:"trustedDomains"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppContextResponse struct {
|
type AppContextResponse struct {
|
||||||
Status int `json:"status"`
|
Status int `json:"status"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Providers []model.Provider `json:"providers"`
|
Auth ACRAuth `json:"auth"`
|
||||||
Title string `json:"title"`
|
OAuth ACROAuth `json:"oauth"`
|
||||||
AppURL string `json:"appUrl"`
|
UI ACRUI `json:"ui"`
|
||||||
CookieDomain string `json:"cookieDomain"`
|
App ACRApp `json:"app"`
|
||||||
ForgotPasswordMessage string `json:"forgotPasswordMessage"`
|
|
||||||
BackgroundImage string `json:"backgroundImage"`
|
|
||||||
OAuthAutoRedirect string `json:"oauthAutoRedirect"`
|
|
||||||
WarningsEnabled bool `json:"warningsEnabled"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContextController struct {
|
type ContextController struct {
|
||||||
@@ -71,51 +106,58 @@ func (controller *ContextController) userContextHandler(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to create user context from request")
|
controller.log.App.Error().Err(err).Msg("Failed to create user context from request")
|
||||||
c.JSON(200, UserContextResponse{
|
c.JSON(200, UserContextResponse{
|
||||||
Status: 401,
|
Status: 401,
|
||||||
Message: "Unauthorized",
|
Message: "Unauthorized",
|
||||||
IsLoggedIn: false,
|
Auth: UCRAuth{Authenticated: false},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userContext := UserContextResponse{
|
userContext := UserContextResponse{
|
||||||
Status: 200,
|
Status: 200,
|
||||||
Message: "Success",
|
Message: "Success",
|
||||||
IsLoggedIn: context.Authenticated,
|
Auth: UCRAuth{
|
||||||
Username: context.GetUsername(),
|
Authenticated: context.Authenticated,
|
||||||
Name: context.GetName(),
|
Username: context.GetUsername(),
|
||||||
Email: context.GetEmail(),
|
Name: context.GetName(),
|
||||||
Provider: context.GetProviderID(),
|
Email: context.GetEmail(),
|
||||||
OAuth: context.IsOAuth(),
|
ProviderID: context.GetProviderID(),
|
||||||
TOTPPending: context.TOTPPending(),
|
},
|
||||||
OAuthName: context.OAuthName(),
|
OAuth: UCROAuth{
|
||||||
|
Active: context.IsOAuth(),
|
||||||
|
DisplayName: context.OAuthName(),
|
||||||
|
},
|
||||||
|
TOTP: UCRTOTP{
|
||||||
|
Pending: context.TOTPPending(),
|
||||||
|
},
|
||||||
|
Tailscale: UCRTailscale{
|
||||||
|
NodeName: context.TailscaleNodeName(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(200, userContext)
|
c.JSON(200, userContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (controller *ContextController) appContextHandler(c *gin.Context) {
|
func (controller *ContextController) appContextHandler(c *gin.Context) {
|
||||||
appUrl, err := url.Parse(controller.runtime.AppURL)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to parse app URL")
|
|
||||||
c.JSON(500, gin.H{
|
|
||||||
"status": 500,
|
|
||||||
"message": "Internal Server Error",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(200, AppContextResponse{
|
c.JSON(200, AppContextResponse{
|
||||||
Status: 200,
|
Status: 200,
|
||||||
Message: "Success",
|
Message: "Success",
|
||||||
Providers: controller.runtime.ConfiguredProviders,
|
Auth: ACRAuth{
|
||||||
Title: controller.config.UI.Title,
|
Providers: controller.runtime.ConfiguredProviders,
|
||||||
AppURL: fmt.Sprintf("%s://%s", appUrl.Scheme, appUrl.Host),
|
},
|
||||||
CookieDomain: controller.runtime.CookieDomain,
|
OAuth: ACROAuth{
|
||||||
ForgotPasswordMessage: controller.config.UI.ForgotPasswordMessage,
|
AutoRedirect: controller.config.OAuth.AutoRedirect,
|
||||||
BackgroundImage: controller.config.UI.BackgroundImage,
|
},
|
||||||
OAuthAutoRedirect: controller.config.OAuth.AutoRedirect,
|
UI: ACRUI{
|
||||||
WarningsEnabled: controller.config.UI.WarningsEnabled,
|
Title: controller.config.UI.Title,
|
||||||
|
ForgotPasswordMessage: controller.config.UI.ForgotPasswordMessage,
|
||||||
|
BackgroundImage: controller.config.UI.BackgroundImage,
|
||||||
|
WarningsEnabled: controller.config.UI.WarningsEnabled,
|
||||||
|
},
|
||||||
|
App: ACRApp{
|
||||||
|
AppURL: controller.runtime.AppURL,
|
||||||
|
CookieDomain: controller.runtime.CookieDomain,
|
||||||
|
TrustedDomains: controller.runtime.TrustedDomains,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,16 +34,25 @@ func TestContextController(t *testing.T) {
|
|||||||
path: "/api/context/app",
|
path: "/api/context/app",
|
||||||
expected: func() string {
|
expected: func() string {
|
||||||
expectedAppContextResponse := controller.AppContextResponse{
|
expectedAppContextResponse := controller.AppContextResponse{
|
||||||
Status: 200,
|
Status: 200,
|
||||||
Message: "Success",
|
Message: "Success",
|
||||||
Providers: runtime.ConfiguredProviders,
|
Auth: controller.ACRAuth{
|
||||||
Title: cfg.UI.Title,
|
Providers: runtime.ConfiguredProviders,
|
||||||
AppURL: runtime.AppURL,
|
},
|
||||||
CookieDomain: runtime.CookieDomain,
|
OAuth: controller.ACROAuth{
|
||||||
ForgotPasswordMessage: cfg.UI.ForgotPasswordMessage,
|
AutoRedirect: cfg.OAuth.AutoRedirect,
|
||||||
BackgroundImage: cfg.UI.BackgroundImage,
|
},
|
||||||
OAuthAutoRedirect: cfg.OAuth.AutoRedirect,
|
UI: controller.ACRUI{
|
||||||
WarningsEnabled: cfg.UI.WarningsEnabled,
|
Title: cfg.UI.Title,
|
||||||
|
ForgotPasswordMessage: cfg.UI.ForgotPasswordMessage,
|
||||||
|
BackgroundImage: cfg.UI.BackgroundImage,
|
||||||
|
WarningsEnabled: cfg.UI.WarningsEnabled,
|
||||||
|
},
|
||||||
|
App: controller.ACRApp{
|
||||||
|
AppURL: runtime.AppURL,
|
||||||
|
CookieDomain: runtime.CookieDomain,
|
||||||
|
TrustedDomains: runtime.TrustedDomains,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
bytes, err := json.Marshal(expectedAppContextResponse)
|
bytes, err := json.Marshal(expectedAppContextResponse)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -84,13 +93,15 @@ func TestContextController(t *testing.T) {
|
|||||||
path: "/api/context/user",
|
path: "/api/context/user",
|
||||||
expected: func() string {
|
expected: func() string {
|
||||||
expectedUserContextResponse := controller.UserContextResponse{
|
expectedUserContextResponse := controller.UserContextResponse{
|
||||||
Status: 200,
|
Status: 200,
|
||||||
Message: "Success",
|
Message: "Success",
|
||||||
IsLoggedIn: true,
|
Auth: controller.UCRAuth{
|
||||||
Username: "johndoe",
|
Authenticated: true,
|
||||||
Name: "John Doe",
|
Username: "johndoe",
|
||||||
Email: utils.CompileUserEmail("johndoe", runtime.CookieDomain),
|
Name: "John Doe",
|
||||||
Provider: "local",
|
Email: utils.CompileUserEmail("johndoe", runtime.CookieDomain),
|
||||||
|
ProviderID: "local",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
bytes, err := json.Marshal(expectedUserContextResponse)
|
bytes, err := json.Marshal(expectedUserContextResponse)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ func TestProxyController(t *testing.T) {
|
|||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
|
||||||
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
|
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
|
||||||
authService := service.NewAuthService(log, cfg, runtime, ctx, wg, nil, queries, broker)
|
authService := service.NewAuthService(log, cfg, runtime, ctx, wg, nil, queries, broker, nil)
|
||||||
aclsService := service.NewAccessControlsService(log, nil, acls)
|
aclsService := service.NewAccessControlsService(log, nil, acls)
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ func NewUserController(
|
|||||||
userGroup.POST("/login", controller.loginHandler)
|
userGroup.POST("/login", controller.loginHandler)
|
||||||
userGroup.POST("/logout", controller.logoutHandler)
|
userGroup.POST("/logout", controller.logoutHandler)
|
||||||
userGroup.POST("/totp", controller.totpHandler)
|
userGroup.POST("/totp", controller.totpHandler)
|
||||||
|
userGroup.POST("/tailscale", controller.tailscaleHandler)
|
||||||
|
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
@@ -189,9 +190,6 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
|||||||
|
|
||||||
if search.Type == model.UserLDAP {
|
if search.Type == model.UserLDAP {
|
||||||
sessionCookie.Provider = "ldap"
|
sessionCookie.Provider = "ldap"
|
||||||
if search.Email != "" {
|
|
||||||
sessionCookie.Email = search.Email
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cookie, err := controller.auth.CreateSession(c, sessionCookie)
|
cookie, err := controller.auth.CreateSession(c, sessionCookie)
|
||||||
@@ -394,3 +392,53 @@ func (controller *UserController) totpHandler(c *gin.Context) {
|
|||||||
"message": "Login successful",
|
"message": "Login successful",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (controller *UserController) tailscaleHandler(c *gin.Context) {
|
||||||
|
context, err := new(model.UserContext).NewFromGin(c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
controller.log.App.Error().Err(err).Msg("Failed to create user context from request")
|
||||||
|
c.JSON(401, gin.H{
|
||||||
|
"status": 401,
|
||||||
|
"message": "Unauthorized",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if context.Tailscale == nil {
|
||||||
|
controller.log.App.Warn().Msg("Tailscale login attempt without Tailscale context")
|
||||||
|
c.JSON(401, gin.H{
|
||||||
|
"status": 401,
|
||||||
|
"message": "Unauthorized",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionCookie := repository.Session{
|
||||||
|
Username: context.Tailscale.Username,
|
||||||
|
Name: context.Tailscale.Name,
|
||||||
|
Email: context.Tailscale.Email,
|
||||||
|
Provider: "tailscale",
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie, err := controller.auth.CreateSession(c, sessionCookie)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
controller.log.App.Error().Err(err).Str("username", context.GetUsername()).Msg("Failed to create session cookie after successful Tailscale login")
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"status": 500,
|
||||||
|
"message": "Internal Server Error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.SetCookie(c.Writer, cookie)
|
||||||
|
|
||||||
|
controller.log.App.Info().Str("username", context.GetUsername()).Msg("Tailscale login successful, login complete")
|
||||||
|
controller.log.AuditLoginSuccess(context.GetUsername(), "tailscale", c.ClientIP())
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Login successful",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -420,7 +420,7 @@ func TestUserController(t *testing.T) {
|
|||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
|
|
||||||
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
|
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
|
||||||
authService := service.NewAuthService(log, cfg, runtime, ctx, wg, nil, queries, broker)
|
authService := service.NewAuthService(log, cfg, runtime, ctx, wg, nil, queries, broker, nil)
|
||||||
|
|
||||||
beforeEach := func() {
|
beforeEach := func() {
|
||||||
// Clear failed login attempts before each test
|
// Clear failed login attempts before each test
|
||||||
|
|||||||
@@ -36,10 +36,11 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ContextMiddleware struct {
|
type ContextMiddleware struct {
|
||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
runtime model.RuntimeConfig
|
runtime model.RuntimeConfig
|
||||||
auth *service.AuthService
|
auth *service.AuthService
|
||||||
broker *service.OAuthBrokerService
|
broker *service.OAuthBrokerService
|
||||||
|
tailscale *service.TailscaleService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContextMiddleware(
|
func NewContextMiddleware(
|
||||||
@@ -47,12 +48,14 @@ func NewContextMiddleware(
|
|||||||
runtime model.RuntimeConfig,
|
runtime model.RuntimeConfig,
|
||||||
auth *service.AuthService,
|
auth *service.AuthService,
|
||||||
broker *service.OAuthBrokerService,
|
broker *service.OAuthBrokerService,
|
||||||
|
tailscale *service.TailscaleService,
|
||||||
) *ContextMiddleware {
|
) *ContextMiddleware {
|
||||||
return &ContextMiddleware{
|
return &ContextMiddleware{
|
||||||
log: log,
|
log: log,
|
||||||
runtime: runtime,
|
runtime: runtime,
|
||||||
auth: auth,
|
auth: auth,
|
||||||
broker: broker,
|
broker: broker,
|
||||||
|
tailscale: tailscale,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +69,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
|||||||
uuid, err := c.Cookie(m.runtime.SessionCookieName)
|
uuid, err := c.Cookie(m.runtime.SessionCookieName)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
userContext, cookie, err := m.cookieAuth(c.Request.Context(), uuid)
|
userContext, cookie, err := m.cookieAuth(c.Request.Context(), uuid, c.RemoteIP())
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if cookie != nil {
|
if cookie != nil {
|
||||||
@@ -102,11 +105,28 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lastly check if we have a tailscale session to add
|
||||||
|
if m.tailscale != nil {
|
||||||
|
tailscaleContext, err := m.tailscaleWhois(c.Request.Context(), c.RemoteIP())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
m.log.App.Error().Err(err).Msgf("Error performing tailscale whois for IP %s: %v", c.RemoteIP(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tailscaleContext != nil {
|
||||||
|
c.Set("context", &model.UserContext{
|
||||||
|
Authenticated: false,
|
||||||
|
Provider: model.ProviderTailscale,
|
||||||
|
Tailscale: tailscaleContext,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ContextMiddleware) cookieAuth(ctx context.Context, uuid string) (*model.UserContext, *http.Cookie, error) {
|
func (m *ContextMiddleware) cookieAuth(ctx context.Context, uuid string, ip string) (*model.UserContext, *http.Cookie, error) {
|
||||||
session, err := m.auth.GetSession(ctx, uuid)
|
session, err := m.auth.GetSession(ctx, uuid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -141,6 +161,18 @@ func (m *ContextMiddleware) cookieAuth(ctx context.Context, uuid string) (*model
|
|||||||
if userContext.Local.Attributes.Email == "" {
|
if userContext.Local.Attributes.Email == "" {
|
||||||
userContext.Local.Attributes.Email = utils.CompileUserEmail(user.Username, m.runtime.CookieDomain)
|
userContext.Local.Attributes.Email = utils.CompileUserEmail(user.Username, m.runtime.CookieDomain)
|
||||||
}
|
}
|
||||||
|
case model.ProviderTailscale:
|
||||||
|
tailscaleContext, err := m.tailscaleWhois(ctx, ip)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("error performing tailscale whois: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tailscaleContext == nil {
|
||||||
|
return nil, nil, fmt.Errorf("tailscale whois returned no result for IP: %s", ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
userContext.Tailscale = tailscaleContext
|
||||||
case model.ProviderLDAP:
|
case model.ProviderLDAP:
|
||||||
search, err := m.auth.SearchUser(userContext.LDAP.Username)
|
search, err := m.auth.SearchUser(userContext.LDAP.Username)
|
||||||
|
|
||||||
@@ -160,12 +192,7 @@ func (m *ContextMiddleware) cookieAuth(ctx context.Context, uuid string) (*model
|
|||||||
|
|
||||||
userContext.LDAP.Groups = user.Groups
|
userContext.LDAP.Groups = user.Groups
|
||||||
userContext.LDAP.Name = utils.Capitalize(userContext.LDAP.Username)
|
userContext.LDAP.Name = utils.Capitalize(userContext.LDAP.Username)
|
||||||
|
|
||||||
userContext.LDAP.Email = utils.CompileUserEmail(userContext.LDAP.Username, m.runtime.CookieDomain)
|
userContext.LDAP.Email = utils.CompileUserEmail(userContext.LDAP.Username, m.runtime.CookieDomain)
|
||||||
if search.Email != "" {
|
|
||||||
userContext.LDAP.Email = search.Email
|
|
||||||
}
|
|
||||||
|
|
||||||
case model.ProviderOAuth:
|
case model.ProviderOAuth:
|
||||||
_, exists := m.broker.GetService(userContext.OAuth.ID)
|
_, exists := m.broker.GetService(userContext.OAuth.ID)
|
||||||
|
|
||||||
@@ -243,15 +270,11 @@ func (m *ContextMiddleware) basicAuth(username string, password string) (*model.
|
|||||||
BaseContext: model.BaseContext{
|
BaseContext: model.BaseContext{
|
||||||
Username: username,
|
Username: username,
|
||||||
Name: utils.Capitalize(username),
|
Name: utils.Capitalize(username),
|
||||||
|
Email: utils.CompileUserEmail(username, m.runtime.CookieDomain),
|
||||||
},
|
},
|
||||||
Groups: user.Groups,
|
Groups: user.Groups,
|
||||||
}
|
}
|
||||||
userContext.Provider = model.ProviderLDAP
|
userContext.Provider = model.ProviderLDAP
|
||||||
|
|
||||||
userContext.LDAP.Email = utils.CompileUserEmail(username, m.runtime.CookieDomain)
|
|
||||||
if search.Email != "" {
|
|
||||||
userContext.LDAP.Email = search.Email
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
userContext.Authenticated = true
|
userContext.Authenticated = true
|
||||||
@@ -266,3 +289,36 @@ func (m *ContextMiddleware) isIgnorePath(path string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *ContextMiddleware) tailscaleWhois(ctx context.Context, ip string) (*model.TailscaleContext, error) {
|
||||||
|
if m.tailscale == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
whois, err := m.tailscale.Whois(ctx, ip)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
m.log.App.Error().Err(err).Msgf("Error performing Tailscale whois for IP %s: %v", ip, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if whois == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
uctx := model.TailscaleContext{
|
||||||
|
BaseContext: model.BaseContext{
|
||||||
|
Username: whois.NodeName,
|
||||||
|
Email: whois.LoginName,
|
||||||
|
Name: whois.DisplayName,
|
||||||
|
},
|
||||||
|
UserID: whois.UserID,
|
||||||
|
Tags: whois.Tags,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.ContainsAny(uctx.Email, "@") {
|
||||||
|
uctx.Email = utils.CompileUserEmail(uctx.Email+"-tailscale", m.runtime.CookieDomain)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &uctx, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -260,9 +260,9 @@ func TestContextMiddleware(t *testing.T) {
|
|||||||
queries := repository.New(app.GetDB())
|
queries := repository.New(app.GetDB())
|
||||||
|
|
||||||
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
|
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
|
||||||
authService := service.NewAuthService(log, cfg, runtime, ctx, wg, nil, queries, broker)
|
authService := service.NewAuthService(log, cfg, runtime, ctx, wg, nil, queries, broker, nil)
|
||||||
|
|
||||||
contextMiddleware := middleware.NewContextMiddleware(log, runtime, authService, broker)
|
contextMiddleware := middleware.NewContextMiddleware(log, runtime, authService, broker, nil)
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
authService.ClearRateLimitsTestingOnly()
|
authService.ClearRateLimitsTestingOnly()
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ func NewDefaultConfiguration() *Config {
|
|||||||
Experimental: ExperimentalConfig{
|
Experimental: ExperimentalConfig{
|
||||||
ConfigFile: "",
|
ConfigFile: "",
|
||||||
},
|
},
|
||||||
|
Tailscale: TailscaleConfig{
|
||||||
|
Dir: "./tailscale_state",
|
||||||
|
},
|
||||||
LabelProvider: "auto",
|
LabelProvider: "auto",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,8 +81,9 @@ type Config struct {
|
|||||||
UI UIConfig `description:"UI customization." yaml:"ui"`
|
UI UIConfig `description:"UI customization." yaml:"ui"`
|
||||||
LDAP LDAPConfig `description:"LDAP configuration." yaml:"ldap"`
|
LDAP LDAPConfig `description:"LDAP configuration." yaml:"ldap"`
|
||||||
Experimental ExperimentalConfig `description:"Experimental features, use with caution." yaml:"experimental"`
|
Experimental ExperimentalConfig `description:"Experimental features, use with caution." yaml:"experimental"`
|
||||||
LabelProvider string `description:"Label provider to use for ACLs (auto, docker, or kubernetes). auto detects the environment." yaml:"labelProvider"`
|
|
||||||
Log LogConfig `description:"Logging configuration." yaml:"log"`
|
Log LogConfig `description:"Logging configuration." yaml:"log"`
|
||||||
|
Tailscale TailscaleConfig `description:"Tailscale configuration." yaml:"tailscale"`
|
||||||
|
LabelProvider string `description:"Label provider to use (docker, kubernetes, auto)." yaml:"labelProvider"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DatabaseConfig struct {
|
type DatabaseConfig struct {
|
||||||
@@ -201,6 +205,16 @@ type ExperimentalConfig struct {
|
|||||||
ConfigFile string `description:"Path to config file." yaml:"-"`
|
ConfigFile string `description:"Path to config file." yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TailscaleConfig struct {
|
||||||
|
Enabled bool `description:"Enable Tailscale integration." yaml:"enabled"`
|
||||||
|
Dir string `description:"Tailscale state directory." yaml:"dir"`
|
||||||
|
Hostname string `description:"Tailscale hostname." yaml:"hostname"`
|
||||||
|
AuthKey string `description:"Tailscale auth key." yaml:"authKey"`
|
||||||
|
Ephemeral bool `description:"Use ephemeral Tailscale node." yaml:"ephemeral"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OAuth/OIDC config
|
||||||
|
|
||||||
type OAuthServiceConfig struct {
|
type OAuthServiceConfig struct {
|
||||||
ClientID string `description:"OAuth client ID." yaml:"clientId"`
|
ClientID string `description:"OAuth client ID." yaml:"clientId"`
|
||||||
ClientSecret string `description:"OAuth client secret." yaml:"clientSecret"`
|
ClientSecret string `description:"OAuth client secret." yaml:"clientSecret"`
|
||||||
@@ -223,7 +237,13 @@ type OIDCClientConfig struct {
|
|||||||
Name string `description:"Client name in UI." yaml:"name"`
|
Name string `description:"Client name in UI." yaml:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACLs
|
type TailscaleWhoisResponse struct {
|
||||||
|
UserID string
|
||||||
|
LoginName string
|
||||||
|
DisplayName string
|
||||||
|
NodeName string
|
||||||
|
Tags []string
|
||||||
|
}
|
||||||
|
|
||||||
type Apps struct {
|
type Apps struct {
|
||||||
Apps map[string]App `description:"App ACLs configuration." yaml:"apps"`
|
Apps map[string]App `description:"App ACLs configuration." yaml:"apps"`
|
||||||
|
|||||||
@@ -21,3 +21,5 @@ const SessionCookieName = "tinyauth-session"
|
|||||||
const CSRFCookieName = "tinyauth-csrf"
|
const CSRFCookieName = "tinyauth-csrf"
|
||||||
const RedirectCookieName = "tinyauth-redirect"
|
const RedirectCookieName = "tinyauth-redirect"
|
||||||
const OAuthSessionCookieName = "tinyauth-oauth"
|
const OAuthSessionCookieName = "tinyauth-oauth"
|
||||||
|
|
||||||
|
const GracefulShutdownTimeout = 5 // seconds
|
||||||
|
|||||||
+59
-58
@@ -19,6 +19,7 @@ const (
|
|||||||
ProviderBasicAuth
|
ProviderBasicAuth
|
||||||
ProviderOAuth
|
ProviderOAuth
|
||||||
ProviderLDAP
|
ProviderLDAP
|
||||||
|
ProviderTailscale
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserContext struct {
|
type UserContext struct {
|
||||||
@@ -27,6 +28,7 @@ type UserContext struct {
|
|||||||
Local *LocalContext
|
Local *LocalContext
|
||||||
OAuth *OAuthContext
|
OAuth *OAuthContext
|
||||||
LDAP *LDAPContext
|
LDAP *LDAPContext
|
||||||
|
Tailscale *TailscaleContext
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseContext struct {
|
type BaseContext struct {
|
||||||
@@ -54,6 +56,13 @@ type LDAPContext struct {
|
|||||||
Groups []string
|
Groups []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TailscaleContext struct {
|
||||||
|
BaseContext
|
||||||
|
UserID string
|
||||||
|
// for future use
|
||||||
|
Tags []string
|
||||||
|
}
|
||||||
|
|
||||||
func (c *UserContext) IsAuthenticated() bool {
|
func (c *UserContext) IsAuthenticated() bool {
|
||||||
return c.Authenticated
|
return c.Authenticated
|
||||||
}
|
}
|
||||||
@@ -74,6 +83,10 @@ func (c *UserContext) IsBasicAuth() bool {
|
|||||||
return c.Provider == ProviderBasicAuth && c.Local != nil
|
return c.Provider == ProviderBasicAuth && c.Local != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *UserContext) IsTailscale() bool {
|
||||||
|
return c.Provider == ProviderTailscale && c.Tailscale != nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *UserContext) NewFromGin(ginctx *gin.Context) (*UserContext, error) {
|
func (c *UserContext) NewFromGin(ginctx *gin.Context) (*UserContext, error) {
|
||||||
userContextValue, exists := ginctx.Get("context")
|
userContextValue, exists := ginctx.Get("context")
|
||||||
|
|
||||||
@@ -87,7 +100,7 @@ func (c *UserContext) NewFromGin(ginctx *gin.Context) (*UserContext, error) {
|
|||||||
return nil, errors.New("invalid user context type")
|
return nil, errors.New("invalid user context type")
|
||||||
}
|
}
|
||||||
|
|
||||||
if userContext.LDAP == nil && userContext.Local == nil && userContext.OAuth == nil {
|
if userContext.LDAP == nil && userContext.Local == nil && userContext.OAuth == nil && userContext.Tailscale == nil {
|
||||||
return nil, errors.New("incomplete user context")
|
return nil, errors.New("incomplete user context")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +134,15 @@ func (c *UserContext) NewFromSession(session *repository.Session) (*UserContext,
|
|||||||
Email: session.Email,
|
Email: session.Email,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
case "tailscale":
|
||||||
|
c.Provider = ProviderTailscale
|
||||||
|
c.Tailscale = &TailscaleContext{
|
||||||
|
BaseContext: BaseContext{
|
||||||
|
Username: session.Username,
|
||||||
|
Name: session.Name,
|
||||||
|
Email: session.Email,
|
||||||
|
},
|
||||||
|
}
|
||||||
// By default we assume an unknown name which is oauth
|
// By default we assume an unknown name which is oauth
|
||||||
default:
|
default:
|
||||||
c.Provider = ProviderOAuth
|
c.Provider = ProviderOAuth
|
||||||
@@ -145,85 +167,55 @@ func (c *UserContext) NewFromSession(session *repository.Session) (*UserContext,
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserContext) GetUsername() string {
|
func (c *UserContext) getBaseContext() *BaseContext {
|
||||||
switch c.Provider {
|
switch c.Provider {
|
||||||
case ProviderLocal:
|
case ProviderLocal, ProviderBasicAuth:
|
||||||
if c.Local == nil {
|
if c.Local == nil {
|
||||||
return ""
|
return nil
|
||||||
}
|
}
|
||||||
return c.Local.Username
|
return &c.Local.BaseContext
|
||||||
case ProviderLDAP:
|
case ProviderLDAP:
|
||||||
if c.LDAP == nil {
|
if c.LDAP == nil {
|
||||||
return ""
|
return nil
|
||||||
}
|
}
|
||||||
return c.LDAP.Username
|
return &c.LDAP.BaseContext
|
||||||
case ProviderBasicAuth:
|
|
||||||
if c.Local == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return c.Local.Username
|
|
||||||
case ProviderOAuth:
|
case ProviderOAuth:
|
||||||
if c.OAuth == nil {
|
if c.OAuth == nil {
|
||||||
return ""
|
return nil
|
||||||
}
|
}
|
||||||
return c.OAuth.Username
|
return &c.OAuth.BaseContext
|
||||||
|
case ProviderTailscale:
|
||||||
|
if c.Tailscale == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &c.Tailscale.BaseContext
|
||||||
default:
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserContext) GetUsername() string {
|
||||||
|
base := c.getBaseContext()
|
||||||
|
if base == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
return base.Username
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserContext) GetEmail() string {
|
func (c *UserContext) GetEmail() string {
|
||||||
switch c.Provider {
|
base := c.getBaseContext()
|
||||||
case ProviderLocal:
|
if base == nil {
|
||||||
if c.Local == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return c.Local.Email
|
|
||||||
case ProviderLDAP:
|
|
||||||
if c.LDAP == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return c.LDAP.Email
|
|
||||||
case ProviderBasicAuth:
|
|
||||||
if c.Local == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return c.Local.Email
|
|
||||||
case ProviderOAuth:
|
|
||||||
if c.OAuth == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return c.OAuth.Email
|
|
||||||
default:
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
return base.Email
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserContext) GetName() string {
|
func (c *UserContext) GetName() string {
|
||||||
switch c.Provider {
|
base := c.getBaseContext()
|
||||||
case ProviderLocal:
|
if base == nil {
|
||||||
if c.Local == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return c.Local.Name
|
|
||||||
case ProviderLDAP:
|
|
||||||
if c.LDAP == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return c.LDAP.Name
|
|
||||||
case ProviderBasicAuth:
|
|
||||||
if c.Local == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return c.Local.Name
|
|
||||||
case ProviderOAuth:
|
|
||||||
if c.OAuth == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return c.OAuth.Name
|
|
||||||
default:
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
return base.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserContext) GetProviderID() string {
|
func (c *UserContext) GetProviderID() string {
|
||||||
@@ -234,6 +226,8 @@ func (c *UserContext) GetProviderID() string {
|
|||||||
return "ldap"
|
return "ldap"
|
||||||
case ProviderOAuth:
|
case ProviderOAuth:
|
||||||
return c.OAuth.ID
|
return c.OAuth.ID
|
||||||
|
case ProviderTailscale:
|
||||||
|
return "tailscale"
|
||||||
default:
|
default:
|
||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
@@ -252,3 +246,10 @@ func (c *UserContext) OAuthName() string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *UserContext) TailscaleNodeName() string {
|
||||||
|
if c.Tailscale != nil {
|
||||||
|
return c.Tailscale.Username
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ type RuntimeConfig struct {
|
|||||||
OAuthWhitelist []string
|
OAuthWhitelist []string
|
||||||
ConfiguredProviders []Provider
|
ConfiguredProviders []Provider
|
||||||
OIDCClients []OIDCClientConfig
|
OIDCClients []OIDCClientConfig
|
||||||
|
TrustedDomains []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
|
|||||||
@@ -21,6 +21,5 @@ type LocalUser struct {
|
|||||||
|
|
||||||
type UserSearch struct {
|
type UserSearch struct {
|
||||||
Username string
|
Username string
|
||||||
Email string // used for LDAP, we can't throw it to LDAPUser because it would need another cache or an LDAP lookup every time
|
|
||||||
Type UserSearchType
|
Type UserSearchType
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ type AuthService struct {
|
|||||||
ldap *LdapService
|
ldap *LdapService
|
||||||
queries *repository.Queries
|
queries *repository.Queries
|
||||||
oauthBroker *OAuthBrokerService
|
oauthBroker *OAuthBrokerService
|
||||||
|
tailscale *TailscaleService
|
||||||
|
|
||||||
loginAttempts map[string]*LoginAttempt
|
loginAttempts map[string]*LoginAttempt
|
||||||
ldapGroupsCache map[string]*LdapGroupsCache
|
ldapGroupsCache map[string]*LdapGroupsCache
|
||||||
@@ -102,6 +103,7 @@ func NewAuthService(
|
|||||||
ldap *LdapService,
|
ldap *LdapService,
|
||||||
queries *repository.Queries,
|
queries *repository.Queries,
|
||||||
oauthBroker *OAuthBrokerService,
|
oauthBroker *OAuthBrokerService,
|
||||||
|
tailscale *TailscaleService,
|
||||||
) *AuthService {
|
) *AuthService {
|
||||||
service := &AuthService{
|
service := &AuthService{
|
||||||
log: log,
|
log: log,
|
||||||
@@ -114,6 +116,7 @@ func NewAuthService(
|
|||||||
ldap: ldap,
|
ldap: ldap,
|
||||||
queries: queries,
|
queries: queries,
|
||||||
oauthBroker: oauthBroker,
|
oauthBroker: oauthBroker,
|
||||||
|
tailscale: tailscale,
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Go(service.CleanupOAuthSessionsRoutine)
|
wg.Go(service.CleanupOAuthSessionsRoutine)
|
||||||
@@ -130,7 +133,7 @@ func (auth *AuthService) SearchUser(username string) (*model.UserSearch, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if auth.ldap != nil {
|
if auth.ldap != nil {
|
||||||
userDN, email, err := auth.ldap.GetUserInfo(username)
|
userDN, err := auth.ldap.GetUserDN(username)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get ldap user: %w", err)
|
return nil, fmt.Errorf("failed to get ldap user: %w", err)
|
||||||
@@ -138,7 +141,6 @@ func (auth *AuthService) SearchUser(username string) (*model.UserSearch, error)
|
|||||||
|
|
||||||
return &model.UserSearch{
|
return &model.UserSearch{
|
||||||
Username: userDN,
|
Username: userDN,
|
||||||
Email: email,
|
|
||||||
Type: model.UserLDAP,
|
Type: model.UserLDAP,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -290,6 +292,10 @@ func (auth *AuthService) IsEmailWhitelisted(email string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (auth *AuthService) CreateSession(ctx context.Context, data repository.Session) (*http.Cookie, error) {
|
func (auth *AuthService) CreateSession(ctx context.Context, data repository.Session) (*http.Cookie, error) {
|
||||||
|
if data.Provider == "tailscale" && auth.tailscale == nil {
|
||||||
|
return nil, fmt.Errorf("tailscale service not configured, cannot create session for tailscale user")
|
||||||
|
}
|
||||||
|
|
||||||
uuid, err := uuid.NewRandom()
|
uuid, err := uuid.NewRandom()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -326,6 +332,28 @@ func (auth *AuthService) CreateSession(ctx context.Context, data repository.Sess
|
|||||||
return nil, fmt.Errorf("failed to create session entry: %w", err)
|
return nil, fmt.Errorf("failed to create session entry: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if data.Provider == "tailscale" {
|
||||||
|
auth.log.App.Trace().Str("url", fmt.Sprintf("https://%s", auth.tailscale.GetHostname())).Msg("Extracting root domain from Tailscale hostname")
|
||||||
|
|
||||||
|
tsCookieDomain, err := utils.GetCookieDomain(fmt.Sprintf("https://%s", auth.tailscale.GetHostname()))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get cookie domain for tailscale user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &http.Cookie{
|
||||||
|
Name: auth.runtime.SessionCookieName,
|
||||||
|
Value: session.UUID,
|
||||||
|
Path: "/",
|
||||||
|
Domain: fmt.Sprintf(".%s", tsCookieDomain),
|
||||||
|
Expires: expiresAt,
|
||||||
|
MaxAge: int(time.Until(expiresAt).Seconds()),
|
||||||
|
Secure: auth.config.Auth.SecureCookie,
|
||||||
|
HttpOnly: true,
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
return &http.Cookie{
|
return &http.Cookie{
|
||||||
Name: auth.runtime.SessionCookieName,
|
Name: auth.runtime.SessionCookieName,
|
||||||
Value: session.UUID,
|
Value: session.UUID,
|
||||||
|
|||||||
@@ -134,7 +134,8 @@ func (ldap *LdapService) connect() (*ldapgo.Conn, error) {
|
|||||||
return ldap.conn, nil
|
return ldap.conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ldap *LdapService) GetUserInfo(username string) (dn string, email string, err error) {
|
func (ldap *LdapService) GetUserDN(username string) (string, error) {
|
||||||
|
// Escape the username to prevent LDAP injection
|
||||||
escapedUsername := ldapgo.EscapeFilter(username)
|
escapedUsername := ldapgo.EscapeFilter(username)
|
||||||
filter := fmt.Sprintf(ldap.config.LDAP.SearchFilter, escapedUsername)
|
filter := fmt.Sprintf(ldap.config.LDAP.SearchFilter, escapedUsername)
|
||||||
|
|
||||||
@@ -142,7 +143,7 @@ func (ldap *LdapService) GetUserInfo(username string) (dn string, email string,
|
|||||||
ldap.config.LDAP.BaseDN,
|
ldap.config.LDAP.BaseDN,
|
||||||
ldapgo.ScopeWholeSubtree, ldapgo.NeverDerefAliases, 0, 0, false,
|
ldapgo.ScopeWholeSubtree, ldapgo.NeverDerefAliases, 0, 0, false,
|
||||||
filter,
|
filter,
|
||||||
[]string{"dn", "mail"},
|
[]string{"dn"},
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -151,15 +152,15 @@ func (ldap *LdapService) GetUserInfo(username string) (dn string, email string,
|
|||||||
|
|
||||||
searchResult, err := ldap.conn.Search(searchRequest)
|
searchResult, err := ldap.conn.Search(searchRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(searchResult.Entries) != 1 {
|
if len(searchResult.Entries) != 1 {
|
||||||
return "", "", fmt.Errorf("multiple or no entries found for user %s", username)
|
return "", fmt.Errorf("multiple or no entries found for user %s", username)
|
||||||
}
|
}
|
||||||
|
|
||||||
entry := searchResult.Entries[0]
|
userDN := searchResult.Entries[0].DN
|
||||||
return entry.DN, entry.GetAttributeValue("mail"), nil
|
return userDN, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ldap *LdapService) GetUserGroups(userDN string) ([]string, error) {
|
func (ldap *LdapService) GetUserGroups(userDN string) ([]string, error) {
|
||||||
|
|||||||
@@ -296,11 +296,6 @@ func (service *OIDCService) ValidateAuthorizeParams(req AuthorizeRequest) error
|
|||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("access_denied")
|
return errors.New("access_denied")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect URI to verify that it's trusted
|
|
||||||
if !slices.Contains(client.TrustedRedirectURIs, req.RedirectURI) {
|
|
||||||
return errors.New("invalid_request_uri")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scopes
|
// Scopes
|
||||||
scopes := strings.Split(req.Scope, " ")
|
scopes := strings.Split(req.Scope, " ")
|
||||||
@@ -323,6 +318,11 @@ func (service *OIDCService) ValidateAuthorizeParams(req AuthorizeRequest) error
|
|||||||
return errors.New("unsupported_response_type")
|
return errors.New("unsupported_response_type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Redirect URI
|
||||||
|
if !slices.Contains(client.TrustedRedirectURIs, req.RedirectURI) {
|
||||||
|
return errors.New("invalid_request_uri")
|
||||||
|
}
|
||||||
|
|
||||||
// PKCE code challenge method if set
|
// PKCE code challenge method if set
|
||||||
if req.CodeChallenge != "" && req.CodeChallengeMethod != "" {
|
if req.CodeChallenge != "" && req.CodeChallengeMethod != "" {
|
||||||
if req.CodeChallengeMethod != "S256" && req.CodeChallengeMethod != "plain" {
|
if req.CodeChallengeMethod != "S256" && req.CodeChallengeMethod != "plain" {
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||||
|
"tailscale.com/client/local"
|
||||||
|
"tailscale.com/tsnet"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TailscaleService struct {
|
||||||
|
log *logger.Logger
|
||||||
|
wg *sync.WaitGroup
|
||||||
|
config model.Config
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
srv *tsnet.Server
|
||||||
|
lc *local.Client
|
||||||
|
ln *net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTailscaleService(log *logger.Logger, config model.Config, ctx context.Context, wg *sync.WaitGroup) (*TailscaleService, error) {
|
||||||
|
if !config.Tailscale.Enabled {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := new(tsnet.Server)
|
||||||
|
|
||||||
|
// node options
|
||||||
|
srv.Dir = config.Tailscale.Dir
|
||||||
|
srv.Hostname = config.Tailscale.Hostname
|
||||||
|
srv.AuthKey = config.Tailscale.AuthKey
|
||||||
|
srv.Ephemeral = config.Tailscale.Ephemeral
|
||||||
|
|
||||||
|
// redirect logs to zerolog
|
||||||
|
srv.Logf = log.App.Printf
|
||||||
|
srv.UserLogf = log.App.Printf
|
||||||
|
|
||||||
|
err := srv.Start()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to start tailscale server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lc, err := srv.LocalClient()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get tailscale local client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
service := &TailscaleService{
|
||||||
|
log: log,
|
||||||
|
wg: wg,
|
||||||
|
config: config,
|
||||||
|
ctx: ctx,
|
||||||
|
srv: srv,
|
||||||
|
lc: lc,
|
||||||
|
}
|
||||||
|
|
||||||
|
connectCtx, cancel := context.WithTimeout(ctx, 2*time.Minute) // large enough timeout to allow for user to manually authenticate with link if needed
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err = service.waitForConn(connectCtx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to connect to tailscale network: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Go(service.watchAndClose)
|
||||||
|
|
||||||
|
return service, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TailscaleService) watchAndClose() {
|
||||||
|
<-ts.ctx.Done()
|
||||||
|
ts.log.App.Debug().Msg("Shutting down Tailscale service")
|
||||||
|
if ts.ln != nil {
|
||||||
|
(*ts.ln).Close()
|
||||||
|
}
|
||||||
|
if ts.srv != nil {
|
||||||
|
ts.srv.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TailscaleService) Whois(ctx context.Context, addr string) (*model.TailscaleWhoisResponse, error) {
|
||||||
|
who, err := ts.lc.WhoIs(ctx, addr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, local.ErrPeerNotFound) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to get client whois: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := model.TailscaleWhoisResponse{
|
||||||
|
UserID: who.UserProfile.ID.String(),
|
||||||
|
LoginName: who.UserProfile.LoginName,
|
||||||
|
DisplayName: who.UserProfile.DisplayName,
|
||||||
|
NodeName: strings.TrimSuffix(who.Node.Name, "."),
|
||||||
|
Tags: who.Node.Tags,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TailscaleService) CreateListener() (net.Listener, error) {
|
||||||
|
if ts.ln != nil {
|
||||||
|
return *ts.ln, nil
|
||||||
|
}
|
||||||
|
ln, err := ts.srv.ListenTLS("tcp", ":443")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ts.ln = &ln
|
||||||
|
return ln, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TailscaleService) GetHostname() string {
|
||||||
|
status, err := ts.lc.Status(ts.ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ts.log.App.Error().Err(err).Msg("Failed to get Tailscale status")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSuffix(status.Self.DNSName, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TailscaleService) waitForConn(ctx context.Context) error {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return fmt.Errorf("timed out waiting for tailscale connection")
|
||||||
|
default:
|
||||||
|
ip4, _ := ts.srv.TailscaleIPs()
|
||||||
|
if !ip4.IsValid() {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user