Compare commits
2 Commits
cfccdf6ddb
...
55beb7f026
| Author | SHA1 | Date |
|---|---|---|
|
|
55beb7f026 | |
|
|
bac4f62bce |
|
|
@ -0,0 +1,77 @@
|
|||
# NODE.DC branded login RFC
|
||||
|
||||
## Цель
|
||||
|
||||
Пользователь должен видеть единое окно входа NODE.DC в визуальной логике текущего Plane login: темный фон, компактная карточка, NODE.DC logo, крупный заголовок, email/password, яркая CTA-кнопка.
|
||||
|
||||
При этом Authentik остается внутренним identity provider и продолжает владеть паролями, MFA, recovery, lockout, session, OAuth/OIDC и audit events.
|
||||
|
||||
## Жесткие запреты
|
||||
|
||||
Запрещенные варианты реализации:
|
||||
|
||||
- reverse proxy, который переписывает HTML Authentik на лету;
|
||||
- frontend-only форма в Launcher, которая принимает email/password;
|
||||
- BFF endpoint в Launcher, который принимает пароль пользователя и пересылает его в Authentik;
|
||||
- OAuth password grant / ROPC;
|
||||
- хранение паролей, refresh tokens или Authentik session cookies в frontend/localStorage;
|
||||
- обход Authentik CSRF, MFA, recovery, lockout, enrollment и audit flow.
|
||||
|
||||
Эти варианты расширяют trusted boundary платформы: Launcher становится обработчиком паролей, а это ломает разделение ответственности и усложняет аудит.
|
||||
|
||||
## Выбранный безопасный путь
|
||||
|
||||
Базовый production-safe путь: Authentik-native branded flow.
|
||||
|
||||
- UI кастомизируется через Brand/Flow внутри Authentik.
|
||||
- `branding_custom_css` задает NODE.DC look без HTML-прокси.
|
||||
- Password stage, Identification stage, User Login stage, MFA/recovery остаются штатными Authentik stages.
|
||||
- Identification stage связан со штатным Password stage, поэтому email и password показываются в одном Authentik challenge без передачи пароля в Launcher.
|
||||
- Launcher и Task Manager остаются OIDC clients и не получают пароль пользователя.
|
||||
- Direct links на Launcher/Task Manager продолжают идти через Authorization Code Flow + PKCE.
|
||||
- OAuth2 provider end-session использует штатный Authentik invalidation flow с UserLogoutStage, чтобы пользователь не попадал на Authentik application logout dashboard.
|
||||
|
||||
Если Brand/CSS не даст pixel-level соответствие Plane login, следующий допустимый уровень — кастомный Authentik template/flow executor внутри Authentik deployment. Это тоже остается IdP-side custom UI, но требует отдельной security review при каждом upgrade Authentik.
|
||||
|
||||
## Runtime prototype
|
||||
|
||||
Локальный bootstrap настраивает Brand для `auth.local.nodedc`:
|
||||
|
||||
- title: `NODE.DC`;
|
||||
- default authentication flow: `default-authentication-flow`;
|
||||
- flow title: `Работайте во всех измерениях.`;
|
||||
- default locale: `ru`;
|
||||
- theme: `dark`;
|
||||
- dark NODE.DC CSS из `/templates/branding/nodedc-login.css`;
|
||||
- без изменения password/MFA/recovery mechanics.
|
||||
|
||||
Файлы:
|
||||
|
||||
- `infra/authentik/bootstrap-dev.py` — idempotent Brand/Flow bootstrap;
|
||||
- `infra/authentik/custom-templates/branding/nodedc-login.css` — native Authentik Brand CSS.
|
||||
|
||||
## Security acceptance
|
||||
|
||||
Перед переводом в production нужно проверить:
|
||||
|
||||
- пароль не проходит через Launcher/BFF/network logs вне Authentik;
|
||||
- CSRF token и Authentik cookies остаются штатными;
|
||||
- MFA/passkeys/recovery/enrollment работают после стилизации;
|
||||
- failed login/lockout/audit events остаются в Authentik;
|
||||
- logout/SLO закрывает Launcher и app sessions;
|
||||
- прямой заход `task.local.nodedc` и `launcher.local.nodedc` возвращает пользователя после login;
|
||||
- UI не содержит слова `authentik` в пользовательском flow;
|
||||
- кастомизация переживает restart контейнеров и повторный bootstrap;
|
||||
- fallback admin URL к Authentik остается доступен только как служебный контур.
|
||||
|
||||
## Upgrade policy
|
||||
|
||||
Brand/CSS считается безопасным maintenance layer. Template override считается повышенным риском: перед обновлением Authentik нужно прогонять visual/security regression, потому что DOM web components может измениться.
|
||||
|
||||
## Источники
|
||||
|
||||
- Authentik Flows: https://docs.goauthentik.io/docs/add-secure-apps/flows-stages/flow/
|
||||
- Authentik Stages: https://docs.goauthentik.io/docs/flow/stages
|
||||
- Password stage: https://docs.goauthentik.io/add-secure-apps/flows-stages/stages/password/
|
||||
- Custom CSS: https://docs.goauthentik.io/brands/custom-css/
|
||||
- Single Logout: https://docs.goauthentik.io/add-secure-apps/providers/single-logout/
|
||||
|
|
@ -48,6 +48,8 @@ Production login должен быть NODE.DC-branded:
|
|||
|
||||
Запрещено делать быстрый frontend-only password form, который обходит IdP protections, хранит секреты в браузере или ломает MFA/recovery/audit.
|
||||
|
||||
Текущее безопасное решение зафиксировано в `docs/AUTH_BRANDED_LOGIN_RFC.md`: сначала используем Authentik-native Brand/CSS/Flow customization. Reverse proxy HTML-rewrite, password form в Launcher и пересылка пароля через BFF запрещены.
|
||||
|
||||
## Required claims
|
||||
|
||||
Минимальный normalized user object:
|
||||
|
|
|
|||
|
|
@ -33,3 +33,13 @@ Later phases should add reproducible configuration for:
|
|||
- groups and policies;
|
||||
- admin service token scope;
|
||||
- exports or blueprints for repeatable setup.
|
||||
|
||||
## NODE.DC branded login
|
||||
|
||||
`custom-templates/branding/nodedc-login.css` is mounted into Authentik at `/templates/branding/nodedc-login.css` and applied by `bootstrap-dev.py` through the native Authentik Brand `branding_custom_css` field.
|
||||
|
||||
`custom-templates/base/header_js.html` keeps Authentik's native config script and adds a minimal NODE.DC field enhancement for the email clear control and password placeholder only.
|
||||
|
||||
OAuth2 providers are assigned Authentik's `default-invalidation-flow` so application logout completes the IdP session and returns through the NODE.DC launcher route instead of showing the default Authentik application logout screen.
|
||||
|
||||
This is intentionally not an HTML-rewriting proxy. Passwords, MFA, recovery, sessions and audit remain inside Authentik; Launcher and Task Manager stay OIDC clients.
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
from os import environ
|
||||
from pathlib import Path
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
from authentik.brands.models import Brand
|
||||
from authentik.common.oauth.constants import SubModes
|
||||
from authentik.core.models import Application, Group, User
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.flows.models import Flow, FlowStageBinding
|
||||
from authentik.policies.models import PolicyBinding
|
||||
from authentik.providers.oauth2.models import (
|
||||
ClientTypes,
|
||||
|
|
@ -16,6 +18,10 @@ from authentik.providers.oauth2.models import (
|
|||
RedirectURIMatchingMode,
|
||||
ScopeMapping,
|
||||
)
|
||||
from authentik.stages.identification.models import IdentificationStage
|
||||
from authentik.stages.password.models import PasswordStage
|
||||
|
||||
BRANDING_CSS_PATH = Path("/templates/branding/nodedc-login.css")
|
||||
|
||||
GROUP_SPECS = [
|
||||
("nodedc:superadmin", False),
|
||||
|
|
@ -158,9 +164,67 @@ def default_scope_mappings():
|
|||
return mappings
|
||||
|
||||
|
||||
def read_branding_css():
|
||||
if BRANDING_CSS_PATH.exists():
|
||||
return BRANDING_CSS_PATH.read_text(encoding="utf-8")
|
||||
return ""
|
||||
|
||||
|
||||
def ensure_nodedc_brand():
|
||||
auth_domain = environ.get("AUTH_DOMAIN", "auth.local.nodedc").strip() or "auth.local.nodedc"
|
||||
authentication_flow = Flow.objects.get(slug="default-authentication-flow")
|
||||
invalidation_flow = Flow.objects.get(slug="default-invalidation-flow")
|
||||
|
||||
authentication_flow.name = "NODE.DC authentication"
|
||||
authentication_flow.title = "Работайте во всех измерениях."
|
||||
authentication_flow.layout = "stacked"
|
||||
authentication_flow.background = ""
|
||||
authentication_flow.save()
|
||||
|
||||
identification_stage = IdentificationStage.objects.get(
|
||||
name="default-authentication-identification"
|
||||
)
|
||||
password_stage = PasswordStage.objects.get(name="default-authentication-password")
|
||||
password_stage.allow_show_password = True
|
||||
password_stage.save()
|
||||
identification_stage.user_fields = ["email"]
|
||||
identification_stage.password_stage = password_stage
|
||||
identification_stage.show_matched_user = False
|
||||
identification_stage.enable_remember_me = False
|
||||
identification_stage.save()
|
||||
FlowStageBinding.objects.filter(target=authentication_flow, stage=password_stage).delete()
|
||||
|
||||
brand = Brand.objects.filter(domain=auth_domain).first()
|
||||
if brand is None:
|
||||
brand = Brand(domain=auth_domain)
|
||||
|
||||
Brand.objects.exclude(brand_uuid=brand.brand_uuid).update(default=False)
|
||||
brand.default = True
|
||||
brand.domain = auth_domain
|
||||
brand.branding_title = "NODE.DC"
|
||||
brand.branding_logo = ""
|
||||
brand.branding_favicon = ""
|
||||
brand.branding_custom_css = read_branding_css()
|
||||
brand.flow_authentication = authentication_flow
|
||||
brand.flow_invalidation = invalidation_flow
|
||||
brand.attributes = {
|
||||
**(brand.attributes or {}),
|
||||
"settings": {
|
||||
**((brand.attributes or {}).get("settings") or {}),
|
||||
"locale": "ru",
|
||||
"theme": {
|
||||
**(((brand.attributes or {}).get("settings") or {}).get("theme") or {}),
|
||||
"base": "dark",
|
||||
},
|
||||
},
|
||||
}
|
||||
brand.save()
|
||||
return brand
|
||||
|
||||
|
||||
def ensure_provider(spec, mappings):
|
||||
authorization_flow = Flow.objects.get(slug="default-provider-authorization-implicit-consent")
|
||||
invalidation_flow = Flow.objects.get(slug="default-provider-invalidation-flow")
|
||||
invalidation_flow = Flow.objects.get(slug="default-invalidation-flow")
|
||||
signing_key = (
|
||||
CertificateKeyPair.objects.filter(name="authentik Self-signed Certificate").first()
|
||||
or CertificateKeyPair.objects.first()
|
||||
|
|
@ -228,6 +292,7 @@ def ensure_application(spec, provider, groups):
|
|||
|
||||
@transaction.atomic
|
||||
def main():
|
||||
brand = ensure_nodedc_brand()
|
||||
groups = ensure_groups()
|
||||
user = ensure_user_groups(groups)
|
||||
mappings = default_scope_mappings()
|
||||
|
|
@ -243,6 +308,7 @@ def main():
|
|||
summary = {
|
||||
"groups": list(groups),
|
||||
"admin_user": user.email if user else None,
|
||||
"brand": brand.domain,
|
||||
"applications": [application.slug for application in applications],
|
||||
"providers": [provider.name for provider in providers],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,297 @@
|
|||
{% load i18n %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
|
||||
<script data-id="authentik-config">
|
||||
"use strict";
|
||||
|
||||
window.authentik = {
|
||||
locale: "ru",
|
||||
config: JSON.parse('{{ config_json|escapejs }}' || "{}"),
|
||||
brand: JSON.parse('{{ brand_json|escapejs }}' || "{}"),
|
||||
versionFamily: "{{ version_family }}",
|
||||
versionSubdomain: "{{ version_subdomain }}",
|
||||
build: "{{ build }}",
|
||||
api: {
|
||||
base: "{{ base_url }}",
|
||||
relBase: "{{ base_url_rel }}",
|
||||
},
|
||||
};
|
||||
window.addEventListener("DOMContentLoaded", function () {
|
||||
{% for message in messages %}
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("ak-message", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
level: "{{ message.tags|escapejs }}",
|
||||
message: "{{ message.message|escapejs }}",
|
||||
},
|
||||
}),
|
||||
);
|
||||
{% endfor %}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script data-id="nodedc-auth-field-enhancements">
|
||||
"use strict";
|
||||
|
||||
(function () {
|
||||
const logoSvg = `
|
||||
<svg id="nodedc-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220.82 54.55" aria-hidden="true">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1{fill:#e2e1e1;}
|
||||
.cls-2{fill:#dbdbdb;stroke:#dbdbdb;stroke-miterlimit:10;stroke-width:0.75px;}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="cls-1" d="M52.8,23.61,46.92,33.76,41.05,23.61H52.8m18-10.39H23.06L46.92,54.55Z"></path>
|
||||
<polygon class="cls-1" points="31.28 33.13 18.11 10.34 75.73 10.34 62.59 33.13 74.28 33.13 93.22 0 0 0 19.61 33.13 31.28 33.13"></polygon>
|
||||
<path class="cls-2" d="M116.35,18.49V1h1.27l10.34,15V1h1.33V18.49H128l-10.34-15v15Z"></path>
|
||||
<path class="cls-2" d="M140.43,18.64c-4.79,0-8.16-3.72-8.16-8.89S135.64.86,140.43.86s8.17,3.72,8.17,8.89S145.25,18.64,140.43,18.64Zm0-1.25c4,0,6.79-3.17,6.79-7.64s-2.77-7.64-6.79-7.64-6.77,3.17-6.77,7.64S136.44,17.39,140.43,17.39Z"></path>
|
||||
<path class="cls-2" d="M151.6,18.49V1h5.1c5.54,0,8.79,3.42,8.79,8.74s-3.25,8.74-8.79,8.74ZM153,17.24h3.75c4.77,0,7.42-2.92,7.42-7.49s-2.65-7.49-7.42-7.49H153Z"></path>
|
||||
<path class="cls-2" d="M168.49,1h10.77V2.26h-9.42V8.93h7.89v1.25h-7.89v7.06h9.74v1.25H168.49Z"></path>
|
||||
<path class="cls-2" d="M188.88,18.49V1H194c5.54,0,8.79,3.42,8.79,8.74s-3.25,8.74-8.79,8.74Zm1.35-1.25H194c4.77,0,7.41-2.92,7.41-7.49S198.75,2.26,194,2.26h-3.75Z"></path>
|
||||
<path class="cls-2" d="M205.15,9.75c0-5.24,3.19-8.89,8.11-8.89a6.8,6.8,0,0,1,7.1,5.52h-1.43a5.54,5.54,0,0,0-5.74-4.27c-4.05,0-6.64,3.17-6.64,7.64s2.54,7.64,6.59,7.64a5.46,5.46,0,0,0,5.74-4.29h1.43c-.75,3.52-3.4,5.54-7.15,5.54C208.27,18.64,205.15,15.05,205.15,9.75Z"></path>
|
||||
</svg>
|
||||
`;
|
||||
const clearIcon = `
|
||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2"></circle>
|
||||
<path d="M9 9l6 6M15 9l-6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
|
||||
</svg>
|
||||
`;
|
||||
const authTranslations = new Map([
|
||||
["Failed to authenticate.", "Не удалось выполнить вход."],
|
||||
["Invalid credentials.", "Неверная почта или пароль."],
|
||||
["Invalid password.", "Неверный пароль."],
|
||||
["Incorrect password.", "Неверный пароль."],
|
||||
["Authentication failed.", "Не удалось выполнить вход."],
|
||||
["This field is required.", "Заполните это поле."],
|
||||
["Please enter your password", "Введите пароль"],
|
||||
["Please enter your password.", "Введите пароль."],
|
||||
["Please enter a valid email address.", "Введите корректную электронную почту."],
|
||||
["Caps Lock is enabled.", "Включён Caps Lock."],
|
||||
["Forgot username or password?", "Забыли пароль?"],
|
||||
["Forgot password?", "Забыли пароль?"],
|
||||
["Remember me on this device", "Запомнить на этом устройстве"],
|
||||
["Email", "Эл. почта"],
|
||||
["Email or Username", "Эл. почта"],
|
||||
["Password", "Пароль"],
|
||||
["Log in", "Войти"],
|
||||
["Continue", "Продолжить"],
|
||||
["Show password", "Показать пароль"],
|
||||
["Hide password", "Скрыть пароль"],
|
||||
]);
|
||||
|
||||
function visitRoots(root, callback) {
|
||||
callback(root);
|
||||
root.querySelectorAll("*").forEach((element) => {
|
||||
if (element.shadowRoot) {
|
||||
visitRoots(element.shadowRoot, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function ensureLogo() {
|
||||
if (!document.body || document.querySelector("[data-nodedc-auth-logo='true']")) return;
|
||||
|
||||
document.body.classList.add("nodedc-auth-enhanced");
|
||||
const logo = document.createElement("div");
|
||||
logo.className = "nodedc-auth-logo";
|
||||
logo.dataset.nodedcAuthLogo = "true";
|
||||
logo.innerHTML = logoSvg;
|
||||
document.body.appendChild(logo);
|
||||
}
|
||||
|
||||
function translateTextValue(value) {
|
||||
if (!value) return value;
|
||||
|
||||
const trimmed = value.trim();
|
||||
const translated = authTranslations.get(trimmed);
|
||||
if (!translated) return value;
|
||||
|
||||
return value.replace(trimmed, translated);
|
||||
}
|
||||
|
||||
function translateAuthText(root) {
|
||||
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
||||
acceptNode(node) {
|
||||
const parent = node.parentElement;
|
||||
if (!parent || !node.nodeValue?.trim()) return NodeFilter.FILTER_REJECT;
|
||||
if (["SCRIPT", "STYLE", "SVG", "PATH", "POLYGON"].includes(parent.tagName)) {
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
return NodeFilter.FILTER_ACCEPT;
|
||||
},
|
||||
});
|
||||
|
||||
const textNodes = [];
|
||||
while (walker.nextNode()) {
|
||||
textNodes.push(walker.currentNode);
|
||||
}
|
||||
|
||||
textNodes.forEach((node) => {
|
||||
const translated = translateTextValue(node.nodeValue);
|
||||
if (translated !== node.nodeValue) {
|
||||
node.nodeValue = translated;
|
||||
}
|
||||
});
|
||||
|
||||
root.querySelectorAll("[aria-label], [title], [placeholder], input[value], button").forEach((element) => {
|
||||
["aria-label", "title", "placeholder"].forEach((attribute) => {
|
||||
const value = element.getAttribute(attribute);
|
||||
const translated = translateTextValue(value);
|
||||
if (translated !== value) element.setAttribute(attribute, translated);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function enhanceIdentifierField(root) {
|
||||
const input = root.querySelector("#ak-identifier-input");
|
||||
if (!input || input.dataset.nodedcClearBound === "true") return;
|
||||
|
||||
const fieldGroup = input.closest(".pf-c-form__group") || input.parentElement;
|
||||
if (!fieldGroup) return;
|
||||
|
||||
input.dataset.nodedcClearBound = "true";
|
||||
fieldGroup.style.position = "relative";
|
||||
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.tabIndex = -1;
|
||||
button.className = "nodedc-auth-clear-input";
|
||||
button.dataset.nodedcClearInput = "true";
|
||||
button.setAttribute("aria-label", "Очистить электронную почту");
|
||||
button.innerHTML = clearIcon;
|
||||
|
||||
const update = () => {
|
||||
button.dataset.visible = input.value ? "true" : "false";
|
||||
};
|
||||
|
||||
button.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
input.value = "";
|
||||
input.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
|
||||
input.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
|
||||
input.focus();
|
||||
update();
|
||||
});
|
||||
|
||||
input.addEventListener("input", update);
|
||||
input.addEventListener("change", update);
|
||||
fieldGroup.appendChild(button);
|
||||
update();
|
||||
}
|
||||
|
||||
function maskPasswordInput(input) {
|
||||
if (!input || input.type !== "text") return;
|
||||
|
||||
input.type = "password";
|
||||
const button = input.closest(".pf-c-input-group")?.querySelector(".pf-c-button.pf-m-control");
|
||||
const icon = button?.querySelector("i");
|
||||
|
||||
button?.setAttribute("aria-label", "Показать пароль");
|
||||
icon?.classList.remove("fa-eye-slash");
|
||||
icon?.classList.add("fa-eye");
|
||||
}
|
||||
|
||||
function maskVisiblePasswords(target) {
|
||||
visitRoots(document, (root) => {
|
||||
root.querySelectorAll("#ak-stage-identification-password, #ak-stage-password-input").forEach((input) => {
|
||||
const inputGroup = input.closest(".pf-c-input-group");
|
||||
if (inputGroup?.contains(target)) return;
|
||||
maskPasswordInput(input);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function enhancePasswordFields(root) {
|
||||
root.querySelectorAll("#ak-stage-identification-password, #ak-stage-password-input").forEach((input) => {
|
||||
if (input.placeholder !== "Введите пароль") {
|
||||
input.placeholder = "Введите пароль";
|
||||
}
|
||||
|
||||
if (input.dataset.nodedcBlurBound === "true") return;
|
||||
|
||||
input.dataset.nodedcBlurBound = "true";
|
||||
input.addEventListener("blur", () => {
|
||||
window.setTimeout(() => {
|
||||
const inputGroup = input.closest(".pf-c-input-group");
|
||||
if (inputGroup?.contains(document.activeElement)) return;
|
||||
maskPasswordInput(input);
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function normalizePasswordFields(root) {
|
||||
enhancePasswordFields(root);
|
||||
}
|
||||
|
||||
function enhanceSubmitHandoff(root) {
|
||||
root.querySelectorAll("form").forEach((form) => {
|
||||
if (form.dataset.nodedcSubmitBound === "true") return;
|
||||
|
||||
form.dataset.nodedcSubmitBound = "true";
|
||||
form.addEventListener("submit", () => {
|
||||
document.body?.classList.add("nodedc-auth-submitting");
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
|
||||
function hasAuthError(root) {
|
||||
const hasErrorElement = root.querySelector(
|
||||
".pf-c-alert, .pf-m-error, .pf-c-helper-text__item.pf-m-error, [aria-invalid='true']"
|
||||
);
|
||||
if (hasErrorElement) return true;
|
||||
|
||||
const text = root.textContent || "";
|
||||
return [
|
||||
"Failed to authenticate.",
|
||||
"Не удалось выполнить вход.",
|
||||
"Invalid credentials.",
|
||||
"Неверная почта или пароль.",
|
||||
"Invalid password.",
|
||||
"Неверный пароль.",
|
||||
].some((message) => text.includes(message));
|
||||
}
|
||||
|
||||
function restoreCardOnErrors(root) {
|
||||
if (hasAuthError(root)) {
|
||||
document.body?.classList.remove("nodedc-auth-submitting");
|
||||
}
|
||||
}
|
||||
|
||||
let scheduled = false;
|
||||
|
||||
function enhanceAuthFields() {
|
||||
scheduled = false;
|
||||
ensureLogo();
|
||||
visitRoots(document, (root) => {
|
||||
enhanceIdentifierField(root);
|
||||
normalizePasswordFields(root);
|
||||
enhanceSubmitHandoff(root);
|
||||
translateAuthText(root);
|
||||
restoreCardOnErrors(root);
|
||||
});
|
||||
}
|
||||
|
||||
function scheduleEnhancement() {
|
||||
if (scheduled) return;
|
||||
scheduled = true;
|
||||
window.requestAnimationFrame(enhanceAuthFields);
|
||||
}
|
||||
|
||||
window.addEventListener("DOMContentLoaded", scheduleEnhancement);
|
||||
window.addEventListener("load", scheduleEnhancement);
|
||||
document.addEventListener("pointerdown", (event) => {
|
||||
const target = event.composedPath?.()[0] || event.target;
|
||||
maskVisiblePasswords(target);
|
||||
}, true);
|
||||
window.addEventListener("blur", () => maskVisiblePasswords(null));
|
||||
new MutationObserver(scheduleEnhancement).observe(document.documentElement, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
|
@ -0,0 +1,553 @@
|
|||
:root,
|
||||
:host {
|
||||
--ak-global--primary: rgb(195, 255, 102);
|
||||
--ak-global--primary-hover: rgb(218, 255, 139);
|
||||
--ak-global--background: #0e0f10;
|
||||
--ak-global--background-image: none !important;
|
||||
--ak-c-login--MaxWidth: 28rem;
|
||||
--ak-c-login--spacer: 2.2rem;
|
||||
--ak-c-login__main--BackgroundColor: transparent;
|
||||
--ak-c-login__main--Color: #f3f3f3;
|
||||
--ak-c-login__main--BoxShadow: none;
|
||||
--ak-c-login__footer--PaddingBlock: 0;
|
||||
--pf-global--primary-color--100: rgb(195, 255, 102);
|
||||
--pf-global--primary-color--200: rgb(218, 255, 139);
|
||||
--pf-global--link--Color: rgb(195, 255, 102);
|
||||
--pf-global--link--Color--hover: rgb(218, 255, 139);
|
||||
--pf-global--BackgroundColor--100: #0e0f10;
|
||||
--pf-global--BackgroundColor--light-100: transparent;
|
||||
--pf-global--Color--100: #f3f3f3;
|
||||
--pf-global--Color--200: #9d9da3;
|
||||
--pf-global--BorderRadius--lg: 1.9rem;
|
||||
--pf-global--BorderRadius--sm: 1.15rem;
|
||||
--pf-v5-global--BorderRadius--lg: 1.9rem;
|
||||
--pf-v5-global--BorderRadius--sm: 1.15rem;
|
||||
--pf-v6-global--BorderRadius--large: 1.9rem;
|
||||
--pf-c-form-control--BackgroundColor: rgba(255, 255, 255, 0.04);
|
||||
--pf-c-form-control--BorderColor: transparent;
|
||||
--pf-c-form-control--BorderBottomColor: transparent;
|
||||
--pf-v5-c-form-control--BackgroundColor: rgba(255, 255, 255, 0.04);
|
||||
--pf-v5-c-form-control--BorderColor: transparent;
|
||||
--pf-v5-c-form-control--BorderBottomColor: transparent;
|
||||
--nodedc-auth-bg: #0e0f10;
|
||||
--nodedc-auth-card-bg: rgba(9, 9, 12, 0.84);
|
||||
--nodedc-auth-primary: rgb(195, 255, 102);
|
||||
--nodedc-auth-primary-hover: rgb(218, 255, 139);
|
||||
--nodedc-auth-on-primary: rgb(11, 17, 23);
|
||||
--nodedc-auth-text-primary: #e4e6e7;
|
||||
--nodedc-auth-text-secondary: #cacdce;
|
||||
--nodedc-auth-text-placeholder: #959a9d;
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
.pf-c-login {
|
||||
background: var(--nodedc-auth-bg) !important;
|
||||
color: var(--nodedc-auth-text-primary) !important;
|
||||
font-family: Inter, "Inter var", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
|
||||
font-synthesis: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: geometricPrecision;
|
||||
}
|
||||
|
||||
:host {
|
||||
background: transparent !important;
|
||||
color: inherit !important;
|
||||
font-family: Inter, "Inter var", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
|
||||
font-synthesis: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: geometricPrecision;
|
||||
}
|
||||
|
||||
:host(.pf-c-login) {
|
||||
display: flex !important;
|
||||
min-width: 100vw !important;
|
||||
min-height: 100vh !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
background: var(--nodedc-auth-bg) !important;
|
||||
color: var(--nodedc-auth-text-primary) !important;
|
||||
}
|
||||
|
||||
body::before,
|
||||
.pf-c-login::before {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
body:not(.nodedc-auth-enhanced)::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 1.6rem;
|
||||
left: 1.25rem;
|
||||
width: 7.25rem;
|
||||
height: 1.79rem;
|
||||
background: center / contain no-repeat url("data:image/svg+xml,%3Csvg id='nodedc-logo' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 220.82 54.55'%3E%3Cpath fill='%23e2e1e1' d='M52.8 23.61 46.92 33.76 41.05 23.61H52.8m18-10.39H23.06l23.86 41.33Z'/%3E%3Cpolygon fill='%23e2e1e1' points='31.28 33.13 18.11 10.34 75.73 10.34 62.59 33.13 74.28 33.13 93.22 0 0 0 19.61 33.13 31.28 33.13'/%3E%3Cpath fill='%23dbdbdb' d='M116.35 18.49V1h1.27l10.34 15V1h1.33v17.49H128l-10.34-15v15Zm24.08.15c-4.79 0-8.16-3.72-8.16-8.89S135.64.86 140.43.86s8.17 3.72 8.17 8.89-3.35 8.89-8.17 8.89Zm0-1.25c4 0 6.79-3.17 6.79-7.64s-2.77-7.64-6.79-7.64-6.77 3.17-6.77 7.64 2.78 7.64 6.77 7.64ZM151.6 18.49V1h5.1c5.54 0 8.79 3.42 8.79 8.74s-3.25 8.74-8.79 8.74Zm1.4-1.25h3.75c4.77 0 7.42-2.92 7.42-7.49s-2.65-7.49-7.42-7.49H153ZM168.49 1h10.77v1.26h-9.42v6.67h7.89v1.25h-7.89v7.06h9.74v1.25h-11.09Zm20.39 17.49V1H194c5.54 0 8.79 3.42 8.79 8.74s-3.25 8.74-8.79 8.74Zm1.35-1.25H194c4.77 0 7.41-2.92 7.41-7.49S198.75 2.26 194 2.26h-3.75Zm14.92-7.49c0-5.24 3.19-8.89 8.11-8.89a6.8 6.8 0 0 1 7.1 5.52h-1.43a5.54 5.54 0 0 0-5.74-4.27c-4.05 0-6.64 3.17-6.64 7.64s2.54 7.64 6.59 7.64a5.46 5.46 0 0 0 5.74-4.29h1.43c-.75 3.52-3.4 5.54-7.15 5.54-4.89 0-8.01-3.59-8.01-8.89Z'/%3E%3C/svg%3E");
|
||||
image-rendering: auto;
|
||||
z-index: 20;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.nodedc-auth-logo {
|
||||
position: fixed;
|
||||
top: 1.6rem;
|
||||
left: 1.25rem;
|
||||
width: 7.25rem;
|
||||
height: 1.79rem;
|
||||
z-index: 20;
|
||||
pointer-events: none;
|
||||
color: var(--nodedc-auth-text-primary);
|
||||
}
|
||||
|
||||
.nodedc-auth-logo svg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
shape-rendering: geometricPrecision;
|
||||
text-rendering: geometricPrecision;
|
||||
}
|
||||
|
||||
.pf-c-page__drawer,
|
||||
.pf-c-drawer,
|
||||
.pf-c-drawer__main,
|
||||
.pf-c-drawer__content,
|
||||
.pf-c-drawer__body {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.pf-c-drawer__body {
|
||||
display: flex !important;
|
||||
min-height: 100vh !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
ak-flow-executor.pf-c-login::before,
|
||||
:host(.pf-c-login)::before {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
ak-flow-executor.pf-c-login {
|
||||
display: flex !important;
|
||||
width: 100% !important;
|
||||
min-height: 100vh !important;
|
||||
position: relative !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
padding-right: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
ak-brand-links,
|
||||
ak-flow-inspector,
|
||||
ak-locale-context,
|
||||
ak-locale-switcher,
|
||||
ak-locale-select,
|
||||
ak-flow-executor [slot="footer"],
|
||||
.pf-c-login__footer,
|
||||
.pf-c-login__header,
|
||||
.pf-c-login__main-header.pf-c-brand,
|
||||
.pf-c-form > p:first-child {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
ak-flow-executor::part(locale-select),
|
||||
ak-flow-executor::part(footer),
|
||||
ak-flow-executor::part(branding) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
ak-flow-executor::part(main),
|
||||
.pf-c-login__main {
|
||||
position: fixed !important;
|
||||
top: 50% !important;
|
||||
left: 50% !important;
|
||||
right: auto !important;
|
||||
transform: translate(-50%, -50%) !important;
|
||||
width: min(100%, 28rem) !important;
|
||||
max-width: 28rem !important;
|
||||
min-height: 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 2.2rem !important;
|
||||
box-sizing: border-box !important;
|
||||
overflow: hidden !important;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 1.9rem !important;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.048) 0%, rgba(255, 255, 255, 0.015) 100%),
|
||||
var(--nodedc-auth-card-bg) !important;
|
||||
-webkit-backdrop-filter: blur(40px);
|
||||
backdrop-filter: blur(40px);
|
||||
justify-content: flex-start !important;
|
||||
}
|
||||
|
||||
ak-flow-card,
|
||||
ak-stage-identification,
|
||||
ak-stage-password {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.pf-c-login__main-body,
|
||||
.pf-c-form,
|
||||
.pf-c-form__group,
|
||||
.pf-c-form__group-control,
|
||||
.pf-c-input-group,
|
||||
.pf-c-input-group__item,
|
||||
.pf-c-card,
|
||||
.pf-c-card__body {
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.pf-c-login__main-header {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
padding: 0 0 1.75rem !important;
|
||||
}
|
||||
|
||||
.pf-c-login__main-header .pf-c-title,
|
||||
.pf-c-title.pf-m-3xl,
|
||||
h1.pf-c-title {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 22rem !important;
|
||||
margin: 0 !important;
|
||||
color: var(--nodedc-auth-text-primary) !important;
|
||||
font-size: 2rem !important;
|
||||
line-height: 2.5rem !important;
|
||||
font-weight: 650 !important;
|
||||
letter-spacing: -0.045em !important;
|
||||
text-wrap: balance !important;
|
||||
white-space: normal !important;
|
||||
word-break: normal !important;
|
||||
overflow-wrap: normal !important;
|
||||
hyphens: none !important;
|
||||
overflow: visible !important;
|
||||
line-clamp: unset !important;
|
||||
-webkit-line-clamp: unset !important;
|
||||
-webkit-box-orient: initial !important;
|
||||
text-transform: none !important;
|
||||
}
|
||||
|
||||
.pf-c-login__main-header::after {
|
||||
content: "С возвращением в NODE.DC.";
|
||||
display: block;
|
||||
max-width: 22rem;
|
||||
margin-top: 0.75rem;
|
||||
color: var(--nodedc-auth-text-placeholder);
|
||||
font-size: 1.9rem;
|
||||
line-height: 2.5rem;
|
||||
font-weight: 650;
|
||||
letter-spacing: -0.045em;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.pf-c-login__main-body {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.pf-c-form {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 1.05rem !important;
|
||||
}
|
||||
|
||||
.pf-c-form__group,
|
||||
ak-flow-input-password.pf-c-form__group {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.pf-c-form__label,
|
||||
.pf-c-form__label-text,
|
||||
label {
|
||||
color: var(--nodedc-auth-text-placeholder) !important;
|
||||
font-size: 0.75rem !important;
|
||||
line-height: 1rem !important;
|
||||
font-weight: 400 !important;
|
||||
letter-spacing: -0.01em !important;
|
||||
text-transform: none !important;
|
||||
}
|
||||
|
||||
.pf-c-form__label-required {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
label[for="ak-identifier-input"],
|
||||
label[for="ak-stage-identification-password"] {
|
||||
font-size: 0 !important;
|
||||
}
|
||||
|
||||
label[for="ak-identifier-input"] .pf-c-form__label-text,
|
||||
label[for="ak-identifier-input"] .pf-c-form__label-required,
|
||||
label[for="ak-stage-identification-password"] .pf-c-form__label-text,
|
||||
label[for="ak-stage-identification-password"] .pf-c-form__label-required {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
label[for="ak-identifier-input"]::after {
|
||||
content: "Эл. почта";
|
||||
font-size: 0.75rem;
|
||||
color: var(--nodedc-auth-text-placeholder);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
label[for="ak-stage-identification-password"]::after {
|
||||
content: "Пароль";
|
||||
font-size: 0.75rem;
|
||||
color: var(--nodedc-auth-text-placeholder);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.pf-c-form-control,
|
||||
.pf-c-input-group .pf-c-form-control,
|
||||
input.pf-c-form-control {
|
||||
width: 100% !important;
|
||||
min-height: 3rem !important;
|
||||
padding: 0 1rem !important;
|
||||
border: 0 !important;
|
||||
border-bottom: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 1.15rem !important;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.028) 0%, rgba(255, 255, 255, 0.012) 100%),
|
||||
rgba(255, 255, 255, 0.03) !important;
|
||||
-webkit-backdrop-filter: blur(18px);
|
||||
backdrop-filter: blur(18px);
|
||||
color: var(--nodedc-auth-text-primary) !important;
|
||||
font-size: 0.875rem !important;
|
||||
font-style: normal !important;
|
||||
font-weight: 400 !important;
|
||||
text-transform: none !important;
|
||||
caret-color: var(--nodedc-auth-primary) !important;
|
||||
}
|
||||
|
||||
.pf-c-form-control::placeholder,
|
||||
input.pf-c-form-control::placeholder {
|
||||
color: var(--nodedc-auth-text-placeholder) !important;
|
||||
font-style: normal !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.pf-c-form-control:focus,
|
||||
input.pf-c-form-control:focus {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.028) 0%, rgba(255, 255, 255, 0.012) 100%),
|
||||
rgba(255, 255, 255, 0.03) !important;
|
||||
border: 0 !important;
|
||||
border-bottom: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.pf-c-form-control:-webkit-autofill,
|
||||
.pf-c-form-control:-webkit-autofill:hover,
|
||||
.pf-c-form-control:-webkit-autofill:focus,
|
||||
input.pf-c-form-control:-webkit-autofill,
|
||||
input.pf-c-form-control:-webkit-autofill:hover,
|
||||
input.pf-c-form-control:-webkit-autofill:focus {
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
-webkit-text-fill-color: var(--nodedc-auth-text-primary) !important;
|
||||
caret-color: var(--nodedc-auth-primary) !important;
|
||||
transition: background-color 999999s ease-in-out 0s !important;
|
||||
box-shadow: inset 0 0 0 1000px rgba(255, 255, 255, 0.03) !important;
|
||||
-webkit-box-shadow: inset 0 0 0 1000px rgba(255, 255, 255, 0.03) !important;
|
||||
}
|
||||
|
||||
.pf-c-input-group {
|
||||
position: relative !important;
|
||||
display: block !important;
|
||||
border: 0 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.pf-c-form__group:has(#ak-identifier-input) {
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.pf-c-input-group .pf-c-button.pf-m-control {
|
||||
position: absolute !important;
|
||||
top: 0.5rem !important;
|
||||
right: 0.75rem !important;
|
||||
display: grid !important;
|
||||
width: 2rem !important;
|
||||
min-width: 2rem !important;
|
||||
height: 2rem !important;
|
||||
place-items: center !important;
|
||||
padding: 0 !important;
|
||||
border: 0 !important;
|
||||
border-radius: 999px !important;
|
||||
background: transparent !important;
|
||||
color: var(--nodedc-auth-text-placeholder) !important;
|
||||
box-shadow: none !important;
|
||||
text-decoration: none !important;
|
||||
appearance: none !important;
|
||||
-webkit-appearance: none !important;
|
||||
}
|
||||
|
||||
.pf-c-input-group .pf-c-button.pf-m-control::before,
|
||||
.pf-c-input-group .pf-c-button.pf-m-control::after,
|
||||
.pf-c-input-group .pf-c-button.pf-m-control:hover::before,
|
||||
.pf-c-input-group .pf-c-button.pf-m-control:hover::after,
|
||||
.pf-c-input-group .pf-c-button.pf-m-control:focus::before,
|
||||
.pf-c-input-group .pf-c-button.pf-m-control:focus::after {
|
||||
display: none !important;
|
||||
border: 0 !important;
|
||||
box-shadow: none !important;
|
||||
content: none !important;
|
||||
}
|
||||
|
||||
.pf-c-input-group .pf-c-button.pf-m-control:hover,
|
||||
.pf-c-input-group .pf-c-button.pf-m-control:focus,
|
||||
.pf-c-input-group .pf-c-button.pf-m-control:active {
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
background: transparent !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.pf-c-input-group .pf-c-button.pf-m-control i,
|
||||
.nodedc-auth-clear-input svg {
|
||||
width: 1.25rem !important;
|
||||
height: 1.25rem !important;
|
||||
color: var(--nodedc-auth-text-placeholder) !important;
|
||||
opacity: 0.9 !important;
|
||||
}
|
||||
|
||||
.pf-c-input-group input.pf-c-form-control {
|
||||
padding-right: 3rem !important;
|
||||
}
|
||||
|
||||
.nodedc-auth-clear-input {
|
||||
position: absolute !important;
|
||||
right: 0.75rem !important;
|
||||
bottom: 0.5rem !important;
|
||||
display: none !important;
|
||||
width: 2rem !important;
|
||||
height: 2rem !important;
|
||||
padding: 0 !important;
|
||||
place-items: center !important;
|
||||
border: 0 !important;
|
||||
border-radius: 999px !important;
|
||||
background: transparent !important;
|
||||
color: var(--nodedc-auth-text-placeholder) !important;
|
||||
box-shadow: none !important;
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
.nodedc-auth-clear-input[data-visible="true"] {
|
||||
display: grid !important;
|
||||
}
|
||||
|
||||
.pf-c-button.pf-m-primary,
|
||||
button.pf-m-primary,
|
||||
button[type="submit"],
|
||||
.pf-c-form .pf-c-button.pf-m-primary {
|
||||
width: 100% !important;
|
||||
min-height: 3.25rem !important;
|
||||
margin-top: 1.25rem !important;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 1.25rem !important;
|
||||
background: var(--nodedc-auth-primary) !important;
|
||||
color: var(--nodedc-auth-on-primary) !important;
|
||||
font-size: 0.95rem !important;
|
||||
font-weight: 600 !important;
|
||||
letter-spacing: -0.02em !important;
|
||||
text-transform: none !important;
|
||||
}
|
||||
|
||||
.pf-c-button.pf-m-primary:hover,
|
||||
button.pf-m-primary:hover,
|
||||
button[type="submit"]:hover {
|
||||
background: var(--nodedc-auth-primary-hover) !important;
|
||||
color: var(--nodedc-auth-on-primary) !important;
|
||||
}
|
||||
|
||||
.pf-c-alert,
|
||||
.pf-c-alert.pf-m-inline {
|
||||
border: 0 !important;
|
||||
border-radius: 1rem !important;
|
||||
margin: 0 0 1.15rem !important;
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
color: var(--nodedc-auth-primary) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.pf-c-alert__icon,
|
||||
.pf-c-alert__title,
|
||||
.pf-c-alert__description {
|
||||
color: var(--nodedc-auth-primary) !important;
|
||||
font-size: 0.75rem !important;
|
||||
line-height: 1rem !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
a,
|
||||
.pf-c-button.pf-m-link,
|
||||
button.pf-m-link {
|
||||
color: var(--nodedc-auth-primary) !important;
|
||||
font-size: 0.75rem !important;
|
||||
line-height: 1rem !important;
|
||||
font-weight: 600 !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
body.nodedc-auth-submitting ak-flow-executor::part(main),
|
||||
body.nodedc-auth-submitting .pf-c-login__main,
|
||||
body.nodedc-auth-submitting .pf-c-empty-state,
|
||||
body.nodedc-auth-submitting .pf-c-spinner,
|
||||
body.nodedc-auth-submitting [role="progressbar"] {
|
||||
opacity: 0 !important;
|
||||
visibility: hidden !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
ak-library-application,
|
||||
ak-application-wizard,
|
||||
ak-user-interface,
|
||||
.pf-c-page__sidebar,
|
||||
.pf-c-page__header,
|
||||
.pf-c-nav,
|
||||
.pf-c-brand {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
ak-flow-executor.pf-c-login::before,
|
||||
:host(.pf-c-login)::before {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body::after,
|
||||
.nodedc-auth-logo {
|
||||
top: 1.6rem;
|
||||
left: 1.25rem;
|
||||
width: 7.25rem;
|
||||
height: 1.79rem;
|
||||
}
|
||||
|
||||
ak-flow-executor::part(main),
|
||||
.pf-c-login__main {
|
||||
position: fixed !important;
|
||||
top: 50% !important;
|
||||
right: auto !important;
|
||||
left: 50% !important;
|
||||
transform: translate(-50%, -50%) !important;
|
||||
width: calc(100vw - 2rem) !important;
|
||||
padding: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,12 @@
|
|||
}
|
||||
|
||||
http://{$AUTH_DOMAIN:auth.local.nodedc} {
|
||||
@auth_root path /
|
||||
redir @auth_root http://{$LAUNCHER_DOMAIN:launcher.local.nodedc}/ 302
|
||||
|
||||
@auth_user_dashboard path /if/user /if/user/*
|
||||
redir @auth_user_dashboard http://{$LAUNCHER_DOMAIN:launcher.local.nodedc}/ 302
|
||||
|
||||
reverse_proxy authentik-server:9000 {
|
||||
header_up Host {host}
|
||||
header_up X-Forwarded-Proto {scheme}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ set +a
|
|||
|
||||
cd "$ROOT_DIR"
|
||||
docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" exec -T \
|
||||
-e AUTH_DOMAIN="${AUTH_DOMAIN:-auth.local.nodedc}" \
|
||||
-e NODEDC_BOOTSTRAP_ADMIN_EMAIL="${NODEDC_BOOTSTRAP_ADMIN_EMAIL:-}" \
|
||||
-e NODEDC_BOOTSTRAP_ADMIN_PASSWORD="${NODEDC_BOOTSTRAP_ADMIN_PASSWORD:-}" \
|
||||
-e LAUNCHER_OIDC_CLIENT_ID="$LAUNCHER_OIDC_CLIENT_ID" \
|
||||
|
|
|
|||
Loading…
Reference in New Issue