diff --git a/config/excluded-services.json b/config/excluded-services.json index c7028df065c96621745e74c83b2256d5beb3d238..b963165c9910b6805e86b32481f5b9882cd5e7ba 100644 --- a/config/excluded-services.json +++ b/config/excluded-services.json @@ -1,4 +1,5 @@ [ "evil-forgejo", - "evil-hedgedoc" + "evil-hedgedoc", + "keycloak" ] diff --git a/service/keycloak/theme/login/resources/css/styles.css b/service/keycloak/theme/login/resources/css/styles.css deleted file mode 100644 index b2f24b7690c8bb824e36271ee47d1dc3d71c7620..0000000000000000000000000000000000000000 --- a/service/keycloak/theme/login/resources/css/styles.css +++ /dev/null @@ -1,345 +0,0 @@ -@import "https://file.paperclover.net/.static/font.css"; - -/* reset */ -html, -body { - height: 100%; -} - -h1, -h2, -h3, -h4, -h5, -h6 { - font-size: unset; - font-weight: unset; -} - -:where( - html, - body, - p, - ol, - ul, - li, - dl, - dt, - dd, - blockquote, - figure, - fieldset, - legend, - textarea, - pre, - iframe, - hr, - h1, - h2, - h3, - h4, - h5, - h6 -) { - margin: 0; - padding: 0; -} - -*, :after, :before { - box-sizing: border-box; - font: unset; -} - -/* root */ -body { - color-scheme: light dark; - background-color: #f9feff; - background-image: url(../img/miku-light.png); - background-size: auto 100%; - background-position: right top; - background-repeat: no-repeat; - color: black; - --text: black; - - --header: light-dark(#1a46cd, #938cff); - --primary: light-dark(#00238f, #938cff); -} -@media (prefers-color-scheme: dark) { - body { - background-image: url(../img/miku-dark.png); - background-color: #2f4b67; - color: white; - --text: white; - } -} -@media (max-width: 1313px) { - body { - background-position: right calc(-100px + 50%) top; - } -} - -/* sidebar */ -.pf-v5-c-login__main { - max-width: 30rem; - padding: 2rem; - height: 100%; - display: flex; - flex-direction: column; - justify-content: center; - --bg: light-dark(rgba(30, 30, 30, 0.1), rgba(0, 0, 0, 0.3)); - background-color: var(--bg); - border-style: solid; - border-right-width: 4px; - border-color: var(--bg); - backdrop-filter: blur(4px); - overflow-y: auto; -} -.kc-logo-text { - font-size: 3rem; - text-align: center; - color: var(--header); -} -#kc-page-title, #kc-info-wrapper { - text-align: center; - color: rgb(from var(--text) r g b / 0.8); -} -#kc-page-title { - margin-bottom: 1rem; -} -#kc-info-wrapper { - margin-top: 1rem; -} -#kc-form-options { - margin-bottom: 1rem; -} -a { - color: var(--header); - text-decoration: dotted underline; -} -a:hover { - text-decoration: underline; - background-color: rgb(from var(--header) r g b / 0.2); -} - -/* branding */ -.kc-logo-text::before { - display: block; - content: " "; - width: 30%; - aspect-ratio: 1; - margin: auto; - background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9Ii0yIC0yIDI4IDI4IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMTAgMjBMOC43NSAxNy41TDYgMThNMTAgNEw4Ljc1IDYuNUw2IDZNMTQgMjBMMTUuMjUgMTcuNUwxOCAxOE0xNCA0TDE1LjI1IDYuNUwxOCA2TTE3IDIxTDE0IDE1TTE0IDE1SDEwTTE0IDE1TDE1LjUgMTJNMTAgMTVMNyAyMU0xMCAxNUw4LjUgMTJNMTcgM0wxNCA5TTE0IDlMMTUuNSAxMk0xNCA5SDEwTTE1LjUgMTJIMjJNMiAxMkg4LjVNOC41IDEyTDEwIDlNMTAgOUw3IDNNMjAgMTBMMTguNSAxMkwyMCAxNE00IDEwTDUuNSAxMkw0IDE0IiBzdHJva2U9InVybCgjcGFpbnQwX2xpbmVhcl81NDhfMTkpIiBzdHJva2Utd2lkdGg9IjMiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMTAgMjBMOC43NSAxNy41TDYgMThNMTAgNEw4Ljc1IDYuNUw2IDZNMTQgMjBMMTUuMjUgMTcuNUwxOCAxOE0xNCA0TDE1LjI1IDYuNUwxOCA2TTE3IDIxTDE0IDE1TTE0IDE1SDEwTTE0IDE1TDE1LjUgMTJNMTAgMTVMNyAyMU0xMCAxNUw4LjUgMTJNMTcgM0wxNCA5TTE0IDlMMTUuNSAxMk0xNCA5SDEwTTE1LjUgMTJIMjJNMiAxMkg4LjVNOC41IDEyTDEwIDlNMTAgOUw3IDNNMjAgMTBMMTguNSAxMkwyMCAxNE00IDEwTDUuNSAxMkw0IDE0IiBzdHJva2U9InVybCgjcGFpbnQxX2xpbmVhcl81NDhfMTkpIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzU0OF8xOSIgeDE9IjQiIHkxPSItMiIgeDI9IjE4LjUiIHkyPSIyNSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjMjIzRDk5Ii8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzE1NDM5MiIvPgo8L2xpbmVhckdyYWRpZW50Pgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MV9saW5lYXJfNTQ4XzE5IiB4MT0iMTAiIHkxPSItMyIgeDI9IjE4IiB5Mj0iMjciIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0YyRTNGRiIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGMkY4RkYiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K); - background-size: cover; -} -#kc-page-title, -#kc-info-wrapper, -.checkbox, -a, -#kc-form-buttons, -.pf-v5-c-helper-text__item-text, -.pf-v5-c-alert__title, -input[type="submit"], -input[type="button"], -button { - text-transform: lowercase; -} - -/* text input */ -.pf-v5-c-form__group { - margin-bottom: 1rem; -} -.pf-v5-c-form__group input:not([type="checkbox"]) { - width: 100%; -} -.pf-v5-c-form__group > div:first-child { - display: flex; -} -.pf-v5-c-form__label { - margin-bottom: 0.25rem; - text-transform: lowercase; - user-select: none; - display: block; -} -.pf-v5-c-input-group { - display: flex; - border-radius: 8px; -} -.pf-v5-c-form-control { - background-color: light-dark(rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.15)); - padding: 2px; - appearance: none; - border: 2px solid var(--text); - font-size: inherit; - color: var(--text); - text-indent: 8px; - height: 38px; - border-radius: 8px; - flex: 1; -} -.pf-v5-c-form-control:has(+ button) { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.pf-v5-c-form-control + button { - appearance: none; - border-top-right-radius: 8px; - border-bottom-right-radius: 8px; - width: 38px; - border: 2px solid var(--text); - border-left: none; - background-color: light-dark(rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.15)); - transition: background-color 0.1s linear; -} -.pf-v5-c-form-control + button:hover { - background-color: light-dark(rgba(0, 0, 0, 0.2), rgba(255, 255, 255, 0.2)); -} -.pf-v5-c-form__group:has(input:focus-visible) - > :is(input, .pf-v5-c-input-group) { - outline: 2px solid rgb(from var(--primary) r g b / 0.5); -} -.pf-v5-c-form__group:has(input:focus-visible) * { - border-color: var(--primary); - outline: none; -} -.pf-v5-c-form__group:has(input:focus-visible) .pf-v5-c-form__label { - color: var(--header); -} -.pf-v5-c-helper-text__item-text { - display: block; - margin-top: 0.5rem; -} -.pf-m-error { - color: light-dark(#c80000, #f56666); -} - -/* checkbox */ -.checkbox input { - display: none; -} -.checkbox label { - display: flex; - align-items: center; - user-select: none; - cursor: pointer; -} -.checkbox label:before { - content: " "; - display: block; - width: 24px; - height: 24px; - margin-right: 0.5rem; - margin-left: -2px; - background-color: var(--text); - - mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXNxdWFyZS1pY29uIGx1Y2lkZS1zcXVhcmUiPjxyZWN0IHdpZHRoPSIxOCIgaGVpZ2h0PSIxOCIgeD0iMyIgeT0iMyIgcng9IjIiLz48L3N2Zz4=); - mask-size: cover; -} -.checkbox label:has(:checked):before { - background-color: var(--primary); - mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXNxdWFyZS1jaGVjay1iaWctaWNvbiBsdWNpZGUtc3F1YXJlLWNoZWNrLWJpZyI+PHBhdGggZD0iTTIxIDEwLjY1NlYxOWEyIDIgMCAwIDEtMiAySDVhMiAyIDAgMCAxLTItMlY1YTIgMiAwIDAgMSAyLTJoMTIuMzQ0Ii8+PHBhdGggZD0ibTkgMTEgMyAzTDIyIDQiLz48L3N2Zz4=); -} -.checkbox label:has(:checked) { - color: var(--primary); -} - -/* buttons */ -.pf-v5-c-button.pf-m-primary { - background-color: var(--primary); - display: block; - border: none; - height: 30px; - border-radius: 8px; - color: white; -} -.pf-v5-c-button.pf-m-block { - display: block; - width: 100%; -} - -/* alert */ -.pf-v5-c-alert { - border-radius: 8px; - padding: 8px; - align-items: center; - margin-bottom: 1rem; - text-align: center; - text-wrap: balance; -} -.alert-error { - background-color: light-dark(#ff010182, #f56666a1); -} -.alert-warning, .alert-info { - background-color: rgb(from var(--primary) r g b / 0.5); -} - -/* icons */ -[data-password-toggle] { - display: grid; - align-items: center; - justify-content: center; -} -[data-password-toggle] i { - display: block; - width: 24px; - height: 24px; - background-color: var(--text); - mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWV5ZS1pY29uIGx1Y2lkZS1leWUiPjxwYXRoIGQ9Ik0yLjA2MiAxMi4zNDhhMSAxIDAgMCAxIDAtLjY5NiAxMC43NSAxMC43NSAwIDAgMSAxOS44NzYgMCAxIDEgMCAwIDEgMCAuNjk2IDEwLjc1IDEwLjc1IDAgMCAxLTE5Ljg3NiAwIi8+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMyIvPjwvc3ZnPg==); -} -input[type="text"] + [data-password-toggle] i { - mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWV5ZS1vZmYtaWNvbiBsdWNpZGUtZXllLW9mZiI+PHBhdGggZD0iTTEwLjczMyA1LjA3NmExMC43NDQgMTAuNzQ0IDAgMCAxIDExLjIwNSA2LjU3NSAxIDEgMCAwIDEgMCAuNjk2IDEwLjc0NyAxMC43NDcgMCAwIDEtMS40NDQgMi40OSIvPjxwYXRoIGQ9Ik0xNC4wODQgMTQuMTU4YTMgMyAwIDAgMS00LjI0Mi00LjI0MiIvPjxwYXRoIGQ9Ik0xNy40NzkgMTcuNDk5YTEwLjc1IDEwLjc1IDAgMCAxLTE1LjQxNy01LjE1MSAxIDEgMCAwIDEgMC0uNjk2IDEwLjc1IDEwLjc1IDAgMCAxIDQuNDQ2LTUuMTQzIi8+PHBhdGggZD0ibTIgMiAyMCAyMCIvPjwvc3ZnPg==); -} - -@media (max-width: 799px) { - body { - background-position: left 65% bottom 60%; - background-size: auto 150%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: flex-end; - } - body:before { - display: block; - content: ""; - flex: 1.5; - } - .pf-v5-c-login__main { - flex: 1 1 40%; - max-width: 25rem; - min-width: 80%; - border-width: 4px; - border-bottom-width: 0; - border-top-left-radius: 32px; - border-top-right-radius: 32px; - corner-shape: superellipse(0); - justify-content: space-between; - --bg: light-dark(rgba(255, 255, 255, 0.7), rgba(0.2, 0, 0.2, 0.6)); - /* the logo straddles the card's top edge; overflow-y: auto would clip - the half that sticks out above (#35) */ - overflow-y: visible; - } - .pf-v5-c-login__main:after { - display: block; - content: ""; - } - .kc-logo-text::before { - position: absolute; - left: 50%; - width: 100px; - transform: translate(-50%, calc(-50% - 35px)); - } -} - -#credit { - position: fixed; - bottom: 2px; - right: 2px; - font-size: 14px; -} -#credit a:not(:hover) { - opacity: 0.5; -} - -.subtitle:has(.subtitle .required) { - display: none; -} diff --git a/service/keycloak/theme/login/resources/css/styles.css b/service/keycloak/theme/login/resources/css/styles.css new file mode 120000 index 0000000000000000000000000000000000000000..d770636a4ef025e125c1a98b577ed77d2eeef25c --- /dev/null +++ b/service/keycloak/theme/login/resources/css/styles.css @@ -0,0 +1 @@ +../../../../../../dashboard/web/sso/css/styles.css \ No newline at end of file diff --git a/service/keycloak/theme/login/resources/img/license.txt b/service/keycloak/theme/login/resources/img/license.txt deleted file mode 100644 index e3dfb05a4dc66f6de256016ed332ea9d27d56d04..0000000000000000000000000000000000000000 --- a/service/keycloak/theme/login/resources/img/license.txt +++ /dev/null @@ -1,2 +0,0 @@ -https://safebooru.org/index.php?page=post&s=view&id=4405346 -dark mode by paper clover diff --git a/service/keycloak/theme/login/resources/img/license.txt b/service/keycloak/theme/login/resources/img/license.txt new file mode 120000 index 0000000000000000000000000000000000000000..a8df3a0a49d5efc3e25eb1eff8aa0a544eb386fe --- /dev/null +++ b/service/keycloak/theme/login/resources/img/license.txt @@ -0,0 +1 @@ +../../../../../../dashboard/web/sso/img/license.txt \ No newline at end of file diff --git a/service/keycloak/theme/login/resources/img/miku-dark.png b/service/keycloak/theme/login/resources/img/miku-dark.png deleted file mode 100644 index 635e025142cb94b89f97ac79c6b460ca2c8294f4..0000000000000000000000000000000000000000 Binary files a/service/keycloak/theme/login/resources/img/miku-dark.png and /dev/null differ diff --git a/service/keycloak/theme/login/resources/img/miku-dark.png b/service/keycloak/theme/login/resources/img/miku-dark.png new file mode 120000 index 0000000000000000000000000000000000000000..b847b152a6470d4a21fbe441d040bb9a08e98cf6 --- /dev/null +++ b/service/keycloak/theme/login/resources/img/miku-dark.png @@ -0,0 +1 @@ +../../../../../../dashboard/web/sso/img/miku-dark.png \ No newline at end of file diff --git a/service/keycloak/theme/login/resources/img/miku-light.png b/service/keycloak/theme/login/resources/img/miku-light.png deleted file mode 100644 index 5dc5ef1e1b250627c999b4fbe296af62cc2dea82..0000000000000000000000000000000000000000 Binary files a/service/keycloak/theme/login/resources/img/miku-light.png and /dev/null differ diff --git a/service/keycloak/theme/login/resources/img/miku-light.png b/service/keycloak/theme/login/resources/img/miku-light.png new file mode 120000 index 0000000000000000000000000000000000000000..ff83a9d81a3d43936b42b58f9a2f478ac46fdde8 --- /dev/null +++ b/service/keycloak/theme/login/resources/img/miku-light.png @@ -0,0 +1 @@ +../../../../../../dashboard/web/sso/img/miku-light.png \ No newline at end of file diff --git a/service/postgres/service.pkl b/service/postgres/service.pkl index 603f9bfb13e72dc19744ae419add49308b0b993e..5f121bd4fb480f6e7bc3a06f102cf6f23129b46d 100644 --- a/service/postgres/service.pkl +++ b/service/postgres/service.pkl @@ -26,7 +26,6 @@ container { tcp { name = "db" containerPort = 5432 - // The podman0 firewall permits this port for Keycloak's connection. hostPort = 15432 loopback = false } diff --git a/tools/router.py b/tools/router.py index 64436855afef933f41b8f9d2cc2216406c9d1e90..58959ed2ccf1188ffc245ef59ce591367e7c0f73 100644 --- a/tools/router.py +++ b/tools/router.py @@ -89,6 +89,7 @@ def render(token): if not re.fullmatch(r"[0-9a-fA-F]{64}", dashboard_proof): raise ValueError("invalid dashboard proxy token") auth_host = "auth." + os.environ["STUDIO_DOMAIN"] + legacy_host = "keycloak." + os.environ["STUDIO_DOMAIN"] origins = json.dumps({"origins": ["https://snowglobe." + os.environ["STUDIO_DOMAIN"]]}, separators=(",", ":")) webauthn = [" handle /.well-known/webauthn {", ' header Content-Type application/json', f" respond {json.dumps(origins)} 200", " }"] @@ -159,6 +160,8 @@ def render(token): lines = [] for (listener, host), route in sorted(routes.items()): if host: + if host in {auth_host, legacy_host}: + continue role = route["authRole"] if role and not auth_upstreams: continue @@ -172,8 +175,6 @@ def render(token): service = next(iter(route["services"])) if not re.fullmatch(r"[a-z][a-z0-9-]*", service): raise ValueError(f"invalid service name: {service}") - if service == "keycloak" and host == auth_host: - lines += webauthn if service == "shale" or re.fullmatch(r"shale-preview-[0-9a-f]{8}", service): # Keep the Origin guard before every static/proxy handle; # otherwise Caddy sorts those handles ahead of respond. @@ -291,18 +292,19 @@ def render(token): lines.insert(len(lines) - 1, " }") else: lines += [f":{listener} {{", *proxy(" ".join(sorted(route["upstreams"])), " "), "}"] - if (80, auth_host) not in routes: - if not HOST.fullmatch(auth_host): - raise ValueError("invalid passkey domain") - lines += [f"{auth_host} {{", *([" tls internal"] if auth_host.endswith(".test") else []), - *webauthn, " handle {", " respond 503", " }", "}"] + dashboard_host = "snowglobe." + os.environ["STUDIO_DOMAIN"] + if any(not HOST.fullmatch(host) for host in [auth_host, legacy_host, dashboard_host]): + raise ValueError("invalid login domain") + for host in [auth_host, legacy_host]: + lines += [f"{host} {{", *([" tls internal"] if host.endswith(".test") else []), + *(webauthn if host == auth_host else []), " handle {", + f" redir https://{dashboard_host}/ 308", " }", "}"] traces = next((route for route in routes.values() if "victoria-traces" in route["services"]), None) if traces: lines += ["http://127.0.0.1:10428 {", " bind 127.0.0.1", *proxy(" ".join(sorted(traces["upstreams"])), " "), "}"] - dashboard_host = "snowglobe." + os.environ["STUDIO_DOMAIN"] dashboard_port = int(os.environ["STUDIO_DASHBOARD_PORT"]) - if not HOST.fullmatch(dashboard_host) or not 1 <= dashboard_port <= 65535: + if not 1 <= dashboard_port <= 65535: raise ValueError("invalid dashboard route") if (80, dashboard_host) in routes: raise ValueError(f"dashboard route conflicts with a Nomad service: {dashboard_host}")