agent_tracker/templates/login.html
michael 48db28b8fb Add local user login alongside Microsoft SSO
- Show both Microsoft SSO and local login options on login page
- Add admin user management: create local users with password
- Add admin password reset for local users only
- Add self-service password change on profile page for local users
- Display auth provider (Local/SSO) in admin user table
- New API endpoints: POST /api/admin/users, POST /api/admin/users/{email}/reset-password, POST /api/users/change-password
- SSO and local auth remain separate (no dual-auth)
- Minimum 8 character password requirement

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 06:58:45 -06:00

296 lines
8 KiB
HTML

{% extends "base.html" %}
{% block title %}Login - AgentHub{% endblock %}
{% block content %}
<div class="container my-5">
<div class="row justify-content-center">
<div class="col-md-6 col-lg-5">
<div class="card shadow-lg border-0">
<div class="card-body p-5">
<div class="text-center mb-4">
<div class="login-icon mb-3">
<i class="fas fa-sign-in-alt"></i>
</div>
<h2 class="card-title mb-2">Welcome Back</h2>
<p class="text-muted">Sign in to your account</p>
</div>
{% if error %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="fas fa-exclamation-circle me-2"></i>{{ error }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
{% if success %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="fas fa-check-circle me-2"></i>{{ success }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
{% if msal_enabled %}
<!-- Microsoft Sign-in Button -->
<div class="d-grid mb-4">
<button type="button" id="microsoftLoginBtn" class="btn btn-microsoft btn-lg">
<svg width="20" height="20" viewBox="0 0 24 24" class="me-2">
<path fill="currentColor" d="M11.4 24H0V12.6h11.4V24zM24 24H12.6V12.6H24V24zM11.4 11.4H0V0h11.4v11.4zM24 11.4H12.6V0H24v11.4z"/>
</svg>
Sign in with Microsoft
</button>
</div>
<!-- Divider -->
<div class="auth-divider mb-4">
<span class="auth-divider-text">or sign in with email</span>
</div>
{% endif %}
<!-- Local Authentication Section -->
<div class="local-auth-section">
{% if msal_enabled %}
<p class="text-muted small mb-3 text-center">
<i class="fas fa-user me-1"></i>
For partner organizations without Microsoft SSO
</p>
{% endif %}
<form method="POST" id="loginForm">
<div class="mb-4">
<label for="userEmail" class="form-label">
<i class="fas fa-envelope me-2"></i>Email Address
</label>
<input type="email"
name="email"
class="form-control form-control-lg"
id="userEmail"
placeholder="Enter your email"
required>
</div>
<div class="mb-4">
<label for="userPassword" class="form-label">
<i class="fas fa-lock me-2"></i>Password
</label>
<div class="password-input-group">
<input type="password"
name="password"
class="form-control form-control-lg"
id="userPassword"
placeholder="Enter your password"
required>
<button type="button" class="password-toggle" onclick="togglePassword('userPassword')">
<i class="fas fa-eye" id="passwordIcon"></i>
</button>
</div>
</div>
<div class="mb-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="rememberMe">
<label class="form-check-label" for="rememberMe">
Remember me
</label>
</div>
</div>
<div class="d-grid mb-4">
<button type="submit" class="btn btn-primary btn-lg">
<i class="fas fa-sign-in-alt me-2"></i>Sign In
</button>
</div>
</form>
</div> <!-- End local-auth-section -->
</div>
</div>
</div>
</div>
</div>
<style>
.login-icon {
width: 80px;
height: 80px;
border-radius: 50%;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto;
font-size: 2rem;
color: white;
}
.password-input-group {
position: relative;
}
.password-toggle {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: var(--text-light);
cursor: pointer;
padding: 0;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.password-toggle:hover {
color: var(--primary-color);
}
.form-control-lg {
padding: 12px 16px;
font-size: 1rem;
}
.card {
border-radius: 20px;
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.95);
}
.form-check-input:checked {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.auth-divider {
display: flex;
align-items: center;
text-align: center;
}
.auth-divider::before,
.auth-divider::after {
content: '';
flex: 1;
border-bottom: 1px solid #dee2e6;
}
.auth-divider-text {
padding: 0 1rem;
color: #6c757d;
font-size: 0.875rem;
}
.btn-microsoft {
background-color: #fff;
border: 1px solid #dee2e6;
color: #333;
display: flex;
align-items: center;
justify-content: center;
}
.btn-microsoft:hover {
background-color: #f8f9fa;
border-color: #c1c1c1;
}
@media (max-width: 576px) {
.card-body {
padding: 2rem 1.5rem !important;
}
.login-icon {
width: 60px;
height: 60px;
font-size: 1.5rem;
}
}
</style>
{% if msal_enabled %}
<!-- MSAL Browser Library -->
<script src="https://alcdn.msauth.net/browser/2.38.1/js/msal-browser.min.js"></script>
{% endif %}
<script>
function togglePassword(inputId) {
const passwordInput = document.getElementById(inputId);
const icon = document.getElementById('passwordIcon');
if (passwordInput.type === 'password') {
passwordInput.type = 'text';
icon.className = 'fas fa-eye-slash';
} else {
passwordInput.type = 'password';
icon.className = 'fas fa-eye';
}
}
{% if msal_enabled %}
// MSAL configuration following specification
const msalConfig = {
auth: {
clientId: "{{ client_id }}",
authority: "{{ authority }}",
redirectUri: "{{ redirect_uri }}"
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: true,
}
};
const loginRequest = {
scopes: ["openid", "profile", "email"],
prompt: "select_account"
};
// Initialize MSAL instance
const myMSALObj = new msal.PublicClientApplication(msalConfig);
// Microsoft login handler
document.addEventListener('DOMContentLoaded', function() {
const microsoftBtn = document.getElementById('microsoftLoginBtn');
if (microsoftBtn) {
microsoftBtn.addEventListener('click', async function() {
try {
// Use popup-based login as per specification
const response = await myMSALObj.loginPopup(loginRequest);
// Send tokens to server for validation and session creation
const serverResponse = await fetch('{{ base_path }}/api/auth/azure/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
access_token: response.accessToken,
id_token: response.idToken
})
});
if (!serverResponse.ok) {
throw new Error('Failed to authenticate with server');
}
const result = await serverResponse.json();
// Redirect based on user role
if (result.is_admin) {
window.location.href = '{{ base_path }}/admin';
} else {
window.location.href = '{{ base_path }}/agent-management';
}
} catch (error) {
console.error('Authentication error:', error);
alert('Authentication failed: ' + error.message);
}
});
}
});
{% endif %}
</script>
{% endblock %}