- 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>
296 lines
8 KiB
HTML
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 %}
|