feat: integrate Authelia SSO and update LLDAP routing
Deploy Dashboard / deploy (push) Successful in 16m20s
Deploy Dashboard / deploy (push) Successful in 16m20s
This commit is contained in:
@@ -11,3 +11,4 @@ asyncssh==2.17.0
|
|||||||
slowapi==0.1.9
|
slowapi==0.1.9
|
||||||
webauthn==2.7.1
|
webauthn==2.7.1
|
||||||
cryptography>=44.0.2
|
cryptography>=44.0.2
|
||||||
|
aiosqlite
|
||||||
|
|||||||
@@ -109,6 +109,30 @@ async def login(creds: LoginRequest, request: Request):
|
|||||||
"has_2fa": bool(totp_secret)
|
"has_2fa": bool(totp_secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@router.get("/proxy")
|
||||||
|
async def proxy_auth(request: Request):
|
||||||
|
"""Auto-login when Authelia has already authenticated the user via forward-auth.
|
||||||
|
Caddy sets Remote-User header after successful Authelia 2FA verification."""
|
||||||
|
remote_user = request.headers.get("Remote-User")
|
||||||
|
if not remote_user:
|
||||||
|
raise HTTPException(status_code=401, detail="No proxy auth header")
|
||||||
|
# Map the LDAP user to the dashboard admin user
|
||||||
|
# Only allow known users (the admin)
|
||||||
|
if remote_user != config.ADMIN_USER:
|
||||||
|
raise HTTPException(status_code=403, detail="User not authorized for dashboard")
|
||||||
|
access_token = auth.create_access_token(
|
||||||
|
data={"sub": remote_user},
|
||||||
|
expires_delta=timedelta(minutes=config.ACCESS_TOKEN_EXPIRE_MINUTES),
|
||||||
|
)
|
||||||
|
refresh_token = auth.create_refresh_token(data={"sub": remote_user})
|
||||||
|
return {
|
||||||
|
"access_token": access_token,
|
||||||
|
"refresh_token": refresh_token,
|
||||||
|
"token_type": "bearer",
|
||||||
|
"user": remote_user,
|
||||||
|
"has_2fa": True,
|
||||||
|
}
|
||||||
|
|
||||||
@router.get("/me")
|
@router.get("/me")
|
||||||
async def read_users_me(current_user: str = Depends(auth.get_current_user)):
|
async def read_users_me(current_user: str = Depends(auth.get_current_user)):
|
||||||
totp_secret = auth.load_totp_secret()
|
totp_secret = auth.load_totp_secret()
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ services:
|
|||||||
- VPS_SSH_HOST=158.101.140.85
|
- VPS_SSH_HOST=158.101.140.85
|
||||||
- VPS_SSH_USER=jimmyg
|
- VPS_SSH_USER=jimmyg
|
||||||
- SECRET_KEY=${SECRET_KEY}
|
- SECRET_KEY=${SECRET_KEY}
|
||||||
|
- ADMIN_USER=jimmy
|
||||||
- ADMIN_PASSWORD_HASH=${ADMIN_PASSWORD_HASH}
|
- ADMIN_PASSWORD_HASH=${ADMIN_PASSWORD_HASH}
|
||||||
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-}
|
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-}
|
||||||
- TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID:-}
|
- TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID:-}
|
||||||
|
|||||||
@@ -28,7 +28,22 @@
|
|||||||
document.documentElement.classList.remove('dark');
|
document.documentElement.classList.remove('dark');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth check
|
// Try proxy auth first (Authelia forward-auth sets Remote-User header)
|
||||||
|
try {
|
||||||
|
const proxyRes = await fetch("/api/auth/proxy");
|
||||||
|
if (proxyRes.ok) {
|
||||||
|
const data = await proxyRes.json();
|
||||||
|
setToken(data.access_token);
|
||||||
|
setRefreshToken(data.refresh_token);
|
||||||
|
authorized = true;
|
||||||
|
loading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Proxy auth not available, fall through to regular auth
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular token auth check
|
||||||
const token = getToken();
|
const token = getToken();
|
||||||
if (!token) {
|
if (!token) {
|
||||||
loading = false;
|
loading = false;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
{ label: "Gitea Web", port: 3300, icon: "git", external: true },
|
{ label: "Gitea Web", port: 3300, icon: "git", external: true },
|
||||||
{ label: "Stirling PDF", port: 8030, icon: "pdf", external: true },
|
{ label: "Stirling PDF", port: 8030, icon: "pdf", external: true },
|
||||||
{ label: "Vaultwarden", port: 8222, icon: "lock", external: true },
|
{ label: "Vaultwarden", port: 8222, icon: "lock", external: true },
|
||||||
|
{ label: "User Management", remoteHref: "https://ldap.jimmygan.com:8443", icon: "users", external: true },
|
||||||
{ label: "n8n", remoteHref: "https://n8n.jimmygan.com:8443", icon: "n8n", external: true },
|
{ label: "n8n", remoteHref: "https://n8n.jimmygan.com:8443", icon: "n8n", external: true },
|
||||||
{ label: "Speedtest", port: 8080, icon: "speedtest", external: true },
|
{ label: "Speedtest", port: 8080, icon: "speedtest", external: true },
|
||||||
];
|
];
|
||||||
@@ -144,6 +145,8 @@
|
|||||||
<svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg>
|
<svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg>
|
||||||
{:else if item.icon === "speedtest"}
|
{:else if item.icon === "speedtest"}
|
||||||
<svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
|
<svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
|
||||||
|
{:else if item.icon === "users"}
|
||||||
|
<svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" /></svg>
|
||||||
{:else if item.icon === "n8n"}
|
{:else if item.icon === "n8n"}
|
||||||
<svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16" /><circle cx="8" cy="6" r="1.5" fill="currentColor"/><circle cx="16" cy="12" r="1.5" fill="currentColor"/><circle cx="10" cy="18" r="1.5" fill="currentColor"/></svg>
|
<svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16" /><circle cx="8" cy="6" r="1.5" fill="currentColor"/><circle cx="16" cy="12" r="1.5" fill="currentColor"/><circle cx="10" cy="18" r="1.5" fill="currentColor"/></svg>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -55,6 +55,20 @@
|
|||||||
triggering = false;
|
triggering = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function markdownToHtml(md) {
|
||||||
|
if (!md) return "";
|
||||||
|
return md
|
||||||
|
.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
||||||
|
.replace(/^### (.+)$/gm, '<h3 class="text-lg font-semibold mt-4 mb-2">$1</h3>')
|
||||||
|
.replace(/^## (.+)$/gm, '<h2 class="text-xl font-bold mt-5 mb-2">$1</h2>')
|
||||||
|
.replace(/^# (.+)$/gm, '<h1 class="text-2xl font-bold mt-6 mb-3">$1</h1>')
|
||||||
|
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
||||||
|
.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
||||||
|
.replace(/^- (.+)$/gm, '<li class="ml-4">$1</li>')
|
||||||
|
.replace(/\n{2,}/g, '<br><br>')
|
||||||
|
.replace(/\n/g, '<br>');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -107,19 +121,3 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
function markdownToHtml(md) {
|
|
||||||
if (!md) return "";
|
|
||||||
return md
|
|
||||||
.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
|
||||||
.replace(/^### (.+)$/gm, '<h3 class="text-lg font-semibold mt-4 mb-2">$1</h3>')
|
|
||||||
.replace(/^## (.+)$/gm, '<h2 class="text-xl font-bold mt-5 mb-2">$1</h2>')
|
|
||||||
.replace(/^# (.+)$/gm, '<h1 class="text-2xl font-bold mt-6 mb-3">$1</h1>')
|
|
||||||
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
|
||||||
.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
|
||||||
.replace(/^- (.+)$/gm, '<li class="ml-4">$1</li>')
|
|
||||||
.replace(/\n{2,}/g, '<br><br>')
|
|
||||||
.replace(/\n/g, '<br>');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
services:
|
||||||
|
authelia:
|
||||||
|
image: authelia/authelia:latest
|
||||||
|
container_name: authelia
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "9092:9091"
|
||||||
|
volumes:
|
||||||
|
- /volume1/docker/authelia/config:/config
|
||||||
|
environment:
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
theme: auto
|
||||||
|
default_2fa_method: totp
|
||||||
|
|
||||||
|
server:
|
||||||
|
address: 'tcp://0.0.0.0:9092'
|
||||||
|
|
||||||
|
log:
|
||||||
|
level: info
|
||||||
|
|
||||||
|
totp:
|
||||||
|
issuer: nas.jimmygan.com
|
||||||
|
period: 30
|
||||||
|
skew: 1
|
||||||
|
|
||||||
|
webauthn:
|
||||||
|
disable: false
|
||||||
|
display_name: NAS Auth
|
||||||
|
attestation_conveyance_preference: indirect
|
||||||
|
user_verification: preferred
|
||||||
|
timeout: 60s
|
||||||
|
|
||||||
|
authentication_backend:
|
||||||
|
ldap:
|
||||||
|
implementation: custom
|
||||||
|
address: 'ldap://localhost:3890'
|
||||||
|
timeout: 5s
|
||||||
|
start_tls: false
|
||||||
|
base_dn: 'dc=jimmygan,dc=com'
|
||||||
|
additional_users_dn: 'ou=people'
|
||||||
|
users_filter: '(&({username_attribute}={input})(objectClass=person))'
|
||||||
|
additional_groups_dn: 'ou=groups'
|
||||||
|
groups_filter: '(member={dn})'
|
||||||
|
user: 'uid=admin,ou=people,dc=jimmygan,dc=com'
|
||||||
|
password: '5jb6+s69t+r+OmZbizEWJA=='
|
||||||
|
attributes:
|
||||||
|
display_name: displayName
|
||||||
|
mail: mail
|
||||||
|
member_of: memberOf
|
||||||
|
group_name: cn
|
||||||
|
|
||||||
|
access_control:
|
||||||
|
default_policy: two_factor
|
||||||
|
rules:
|
||||||
|
- domain: ldap.jimmygan.com
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
session:
|
||||||
|
secret: jcpCs/bUYjC85jBDLa3jwgZfC4WyIyjS87LsvO2l3jY=
|
||||||
|
expiration: 1h
|
||||||
|
inactivity: 5m
|
||||||
|
remember_me: 1M
|
||||||
|
cookies:
|
||||||
|
- domain: jimmygan.com
|
||||||
|
authelia_url: https://auth.jimmygan.com:8443
|
||||||
|
|
||||||
|
storage:
|
||||||
|
encryption_key: RPXltPeEWip9kJRoDRQOcUF3egeVCluOjma1TpeYbeo=
|
||||||
|
local:
|
||||||
|
path: /config/db.sqlite3
|
||||||
|
|
||||||
|
notifier:
|
||||||
|
smtp:
|
||||||
|
address: 'smtp://smtp.gmail.com:587'
|
||||||
|
username: 'zjgump@gmail.com'
|
||||||
|
password: 'lkykzvfrubsgvqfv'
|
||||||
|
sender: 'NAS Auth <zjgump@gmail.com>'
|
||||||
|
subject: '[NAS Auth] {title}'
|
||||||
|
|
||||||
|
identity_validation:
|
||||||
|
reset_password:
|
||||||
|
jwt_secret: HDty02r/yVwbWavqM+F0ChCgvgWE34XM1mzNFPzOrWY=
|
||||||
Reference in New Issue
Block a user