feat: webbasierte Azure-Authentifizierung

Device-Code-Flow direkt ueber das Web-Interface starten statt
ueber die Kommandozeile. Button "Mit Azure verbinden" in den
Einstellungen zeigt Code und Link an, pollt den Status und
laedt die Seite nach Erfolg neu.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 10:03:04 +02:00
co-authored by Claude Opus 4.6
parent d5d5160257
commit bae07e78e7
2 changed files with 161 additions and 8 deletions
+86 -7
View File
@@ -43,19 +43,53 @@
placeholder="User.Read User.Read.All Presence.Read Presence.Read.All">
<small class="text-muted">Standard: User.Read User.Read.All Presence.Read Presence.Read.All</small>
</div>
<div class="border rounded p-3 bg-light">
<div id="auth-status" class="border rounded p-3 bg-light">
{% if authenticated %}
<i class="bi bi-check-circle text-success"></i>
<strong class="text-success">Authentifiziert</strong>
<strong class="text-success">Verbunden</strong>
<div class="mt-2">
<button type="button" class="btn btn-sm btn-outline-warning" onclick="startAuth()">
<i class="bi bi-arrow-repeat"></i> Neu verbinden
</button>
</div>
{% else %}
<i class="bi bi-x-circle text-danger"></i>
<strong class="text-danger">Nicht authentifiziert</strong>
<p class="small text-muted mt-2 mb-0">
Fuehre auf dem Server aus:<br>
<code>python app.py --auth</code>
</p>
<strong class="text-danger">Nicht verbunden</strong>
<div class="mt-2">
<button type="button" class="btn btn-sm btn-primary" onclick="startAuth()">
<i class="bi bi-microsoft"></i> Mit Azure verbinden
</button>
</div>
{% endif %}
</div>
<div id="auth-flow" class="border rounded p-3 bg-light mt-2" style="display:none;">
<div id="auth-waiting" style="display:none;">
<p class="mb-2"><strong>Schritt 1:</strong> Oeffne den folgenden Link:</p>
<p><a id="auth-url" href="#" target="_blank" class="btn btn-sm btn-outline-primary">
<i class="bi bi-box-arrow-up-right"></i> Bei Microsoft anmelden
</a></p>
<p class="mb-2"><strong>Schritt 2:</strong> Gib diesen Code ein:</p>
<p class="text-center">
<span id="auth-code" class="badge bg-dark fs-4 font-monospace p-3"></span>
</p>
<p class="small text-muted mb-0">
<i class="bi bi-hourglass-split"></i> Warte auf Anmeldung...
</p>
</div>
<div id="auth-starting" style="display:none;">
<i class="bi bi-hourglass-split"></i> Verbindung wird hergestellt...
</div>
<div id="auth-success" style="display:none;">
<i class="bi bi-check-circle text-success"></i>
<strong class="text-success">Erfolgreich verbunden!</strong>
<p class="small text-muted mt-1 mb-0">Seite wird neu geladen...</p>
</div>
<div id="auth-error" style="display:none;">
<i class="bi bi-exclamation-triangle text-danger"></i>
<strong class="text-danger">Fehler:</strong>
<span id="auth-error-msg"></span>
</div>
</div>
</div>
</div>
</div>
@@ -132,3 +166,48 @@
</div>
</form>
{% endblock %}
{% block scripts %}
<script>
let authPollInterval = null;
function startAuth() {
document.getElementById('auth-flow').style.display = 'block';
document.getElementById('auth-starting').style.display = 'block';
document.getElementById('auth-waiting').style.display = 'none';
document.getElementById('auth-success').style.display = 'none';
document.getElementById('auth-error').style.display = 'none';
fetch('/api/azure/auth', {method: 'POST'})
.then(() => {
authPollInterval = setInterval(pollAuthStatus, 1000);
});
}
function pollAuthStatus() {
fetch('/api/azure/auth/status')
.then(r => r.json())
.then(data => {
if (data.status === 'waiting') {
document.getElementById('auth-starting').style.display = 'none';
document.getElementById('auth-waiting').style.display = 'block';
document.getElementById('auth-code').textContent = data.user_code;
const url = document.getElementById('auth-url');
url.href = data.verification_uri;
} else if (data.status === 'success') {
clearInterval(authPollInterval);
document.getElementById('auth-waiting').style.display = 'none';
document.getElementById('auth-starting').style.display = 'none';
document.getElementById('auth-success').style.display = 'block';
setTimeout(() => location.reload(), 2000);
} else if (data.status === 'error') {
clearInterval(authPollInterval);
document.getElementById('auth-waiting').style.display = 'none';
document.getElementById('auth-starting').style.display = 'none';
document.getElementById('auth-error').style.display = 'block';
document.getElementById('auth-error-msg').textContent = data.error;
}
});
}
</script>
{% endblock %}