|
@@ -0,0 +1,429 @@
|
|
|
|
|
+<!DOCTYPE html>
|
|
|
|
|
+<html lang="en">
|
|
|
|
|
+<head>
|
|
|
|
|
+ <meta charset="UTF-8">
|
|
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
+ <title>Auth Service</title>
|
|
|
|
|
+ <style>
|
|
|
|
|
+ * {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ body {
|
|
|
|
|
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .container {
|
|
|
|
|
+ background: white;
|
|
|
|
|
+ border-radius: 15px;
|
|
|
|
|
+ box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ max-width: 400px;
|
|
|
|
|
+ margin: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .header {
|
|
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ padding: 30px 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .header h1 {
|
|
|
|
|
+ font-size: 1.8rem;
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .header p {
|
|
|
|
|
+ opacity: 0.9;
|
|
|
|
|
+ font-size: 0.9rem;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .tab-container {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ background: #f8f9fa;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .tab {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ padding: 15px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ border-bottom: 3px solid transparent;
|
|
|
|
|
+ transition: all 0.3s ease;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .tab.active {
|
|
|
|
|
+ color: #667eea;
|
|
|
|
|
+ border-bottom-color: #667eea;
|
|
|
|
|
+ background: white;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .form-container {
|
|
|
|
|
+ padding: 40px 30px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .form {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .form.active {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .form-group {
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .form-group label {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .form-group input {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 12px 15px;
|
|
|
|
|
+ border: 2px solid #e1e5e9;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ font-size: 1rem;
|
|
|
|
|
+ transition: all 0.3s ease;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .form-group input:focus {
|
|
|
|
|
+ outline: none;
|
|
|
|
|
+ border-color: #667eea;
|
|
|
|
|
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .submit-btn {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 12px;
|
|
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ font-size: 1rem;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.3s ease;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .submit-btn:hover {
|
|
|
|
|
+ transform: translateY(-2px);
|
|
|
|
|
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .submit-btn:disabled {
|
|
|
|
|
+ background: #ccc;
|
|
|
|
|
+ cursor: not-allowed;
|
|
|
|
|
+ transform: none;
|
|
|
|
|
+ box-shadow: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .message {
|
|
|
|
|
+ margin-top: 15px;
|
|
|
|
|
+ padding: 10px;
|
|
|
|
|
+ border-radius: 5px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .message.success {
|
|
|
|
|
+ background: #d4edda;
|
|
|
|
|
+ color: #155724;
|
|
|
|
|
+ border: 1px solid #c3e6cb;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .message.error {
|
|
|
|
|
+ background: #f8d7da;
|
|
|
|
|
+ color: #721c24;
|
|
|
|
|
+ border: 1px solid #f5c6cb;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .user-info {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ background: #f8f9fa;
|
|
|
|
|
+ border-top: 1px solid #e1e5e9;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .user-info.active {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .user-details {
|
|
|
|
|
+ background: white;
|
|
|
|
|
+ padding: 15px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ margin-bottom: 15px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .user-details h3 {
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .user-details p {
|
|
|
|
|
+ margin: 5px 0;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .logout-btn {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 10px;
|
|
|
|
|
+ background: #dc3545;
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ font-size: 0.9rem;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.3s ease;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .logout-btn:hover {
|
|
|
|
|
+ background: #c82333;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .loading {
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+ width: 16px;
|
|
|
|
|
+ height: 16px;
|
|
|
|
|
+ border: 2px solid #ffffff;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ border-top-color: transparent;
|
|
|
|
|
+ animation: spin 1s ease-in-out infinite;
|
|
|
|
|
+ margin-right: 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @keyframes spin {
|
|
|
|
|
+ to { transform: rotate(360deg); }
|
|
|
|
|
+ }
|
|
|
|
|
+ </style>
|
|
|
|
|
+</head>
|
|
|
|
|
+<body>
|
|
|
|
|
+ <div class="container">
|
|
|
|
|
+ <div class="header">
|
|
|
|
|
+ <h1>Auth Service</h1>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div id="authSection">
|
|
|
|
|
+ <div class="tab-container">
|
|
|
|
|
+ <div class="tab active" onclick="switchTab('login')">Login</div>
|
|
|
|
|
+ <div class="tab" onclick="switchTab('register')">Register</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="form-container">
|
|
|
|
|
+ <form class="form active" id="loginForm">
|
|
|
|
|
+ <div class="form-group">
|
|
|
|
|
+ <label for="loginUsername">Username</label>
|
|
|
|
|
+ <input type="text" id="loginUsername" name="username" required>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="form-group">
|
|
|
|
|
+ <label for="loginPassword">Password</label>
|
|
|
|
|
+ <input type="password" id="loginPassword" name="password" required>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <button type="submit" class="submit-btn" id="loginBtn">Sign In</button>
|
|
|
|
|
+ <div id="loginMessage" class="message" style="display: none;"></div>
|
|
|
|
|
+ </form>
|
|
|
|
|
+
|
|
|
|
|
+ <form class="form" id="registerForm">
|
|
|
|
|
+ <div class="form-group">
|
|
|
|
|
+ <label for="registerUsername">Username</label>
|
|
|
|
|
+ <input type="text" id="registerUsername" name="username" required>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="form-group">
|
|
|
|
|
+ <label for="registerPassword">Password</label>
|
|
|
|
|
+ <input type="password" id="registerPassword" name="password" required>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <button type="submit" class="submit-btn" id="registerBtn">Create Account</button>
|
|
|
|
|
+ <div id="registerMessage" class="message" style="display: none;"></div>
|
|
|
|
|
+ </form>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div id="userSection" class="user-info">
|
|
|
|
|
+ <div class="user-details">
|
|
|
|
|
+ <h3>Welcome!</h3>
|
|
|
|
|
+ <p><strong>Username:</strong> <span id="currentUsername"></span></p>
|
|
|
|
|
+ <p><strong>User ID:</strong> <span id="currentUserId"></span></p>
|
|
|
|
|
+ <p><strong>Roles:</strong> <span id="currentRoles"></span></p>
|
|
|
|
|
+ <p><strong>Token Expires:</strong> <span id="tokenExpiration"></span></p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <button class="logout-btn" onclick="logout()">Logout</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <script>
|
|
|
|
|
+ const API_BASE_URL = '/api';
|
|
|
|
|
+ let currentUser = null;
|
|
|
|
|
+
|
|
|
|
|
+ document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
|
+ const token = localStorage.getItem('authToken');
|
|
|
|
|
+ const userData = localStorage.getItem('userData');
|
|
|
|
|
+
|
|
|
|
|
+ if (token && userData) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ currentUser = JSON.parse(userData);
|
|
|
|
|
+ showUserSection();
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('Invalid user data in localStorage', e);
|
|
|
|
|
+ localStorage.removeItem('authToken');
|
|
|
|
|
+ localStorage.removeItem('userData');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ function switchTab(tab) {
|
|
|
|
|
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
|
|
|
+ document.querySelector(`.tab:nth-child(${tab === 'login' ? '1' : '2'})`).classList.add('active');
|
|
|
|
|
+
|
|
|
|
|
+ document.querySelectorAll('.form').forEach(f => f.classList.remove('active'));
|
|
|
|
|
+ document.getElementById(tab + 'Form').classList.add('active');
|
|
|
|
|
+
|
|
|
|
|
+ clearMessages();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function clearMessages() {
|
|
|
|
|
+ document.querySelectorAll('.message').forEach(msg => {
|
|
|
|
|
+ msg.style.display = 'none';
|
|
|
|
|
+ msg.textContent = '';
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function showMessage(elementId, message, type) {
|
|
|
|
|
+ const element = document.getElementById(elementId);
|
|
|
|
|
+ element.textContent = message;
|
|
|
|
|
+ element.className = `message ${type}`;
|
|
|
|
|
+ element.style.display = 'block';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function setButtonLoading(buttonId, loading) {
|
|
|
|
|
+ const button = document.getElementById(buttonId);
|
|
|
|
|
+ if (loading) {
|
|
|
|
|
+ button.innerHTML = '<span class="loading"></span>Processing...';
|
|
|
|
|
+ button.disabled = true;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ button.innerHTML = buttonId.includes('login') ? 'Sign In' : 'Create Account';
|
|
|
|
|
+ button.disabled = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ document.getElementById('loginForm').addEventListener('submit', async function(e) {
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+
|
|
|
|
|
+ const username = document.getElementById('loginUsername').value;
|
|
|
|
|
+ const password = document.getElementById('loginPassword').value;
|
|
|
|
|
+
|
|
|
|
|
+ setButtonLoading('loginBtn', true);
|
|
|
|
|
+ clearMessages();
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(`${API_BASE_URL}/authenticate`, {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
|
|
+ },
|
|
|
|
|
+ body: JSON.stringify({ username, password })
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ const data = await response.json();
|
|
|
|
|
+
|
|
|
|
|
+ localStorage.setItem('authToken', data.token);
|
|
|
|
|
+ localStorage.setItem('userData', JSON.stringify(data));
|
|
|
|
|
+
|
|
|
|
|
+ currentUser = data;
|
|
|
|
|
+ setButtonLoading('loginBtn', false);
|
|
|
|
|
+ showUserSection();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const errorText = await response.text();
|
|
|
|
|
+ showMessage('loginMessage', errorText || 'Login failed. Please check your credentials.', 'error');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Login error:', error);
|
|
|
|
|
+ showMessage('loginMessage', 'Network error. Please check if the auth service is running.', 'error');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setButtonLoading('loginBtn', false);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ document.getElementById('registerForm').addEventListener('submit', async function(e) {
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+
|
|
|
|
|
+ const username = document.getElementById('registerUsername').value;
|
|
|
|
|
+ const password = document.getElementById('registerPassword').value;
|
|
|
|
|
+
|
|
|
|
|
+ setButtonLoading('registerBtn', true);
|
|
|
|
|
+ clearMessages();
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(`${API_BASE_URL}/register`, {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
|
|
+ },
|
|
|
|
|
+ body: JSON.stringify({ username, password })
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ const data = await response.json();
|
|
|
|
|
+
|
|
|
|
|
+ localStorage.setItem('authToken', data.token);
|
|
|
|
|
+ localStorage.setItem('userData', JSON.stringify(data));
|
|
|
|
|
+
|
|
|
|
|
+ currentUser = data;
|
|
|
|
|
+ setButtonLoading('registerBtn', false);
|
|
|
|
|
+ showUserSection();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const errorText = await response.text();
|
|
|
|
|
+ showMessage('registerMessage', errorText || 'Registration failed. Please try again.', 'error');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Registration error:', error);
|
|
|
|
|
+ showMessage('registerMessage', 'Network error. Please check if the auth service is running.', 'error');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setButtonLoading('registerBtn', false);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ function showUserSection() {
|
|
|
|
|
+ document.getElementById('authSection').style.display = 'none';
|
|
|
|
|
+ document.getElementById('userSection').classList.add('active');
|
|
|
|
|
+
|
|
|
|
|
+ document.getElementById('currentUsername').textContent = currentUser.username;
|
|
|
|
|
+ document.getElementById('currentUserId').textContent = currentUser.id;
|
|
|
|
|
+ document.getElementById('currentRoles').textContent = currentUser.roles.join(', ');
|
|
|
|
|
+
|
|
|
|
|
+ const expirationDate = new Date(currentUser.expirationDate);
|
|
|
|
|
+ document.getElementById('tokenExpiration').textContent = expirationDate.toLocaleString();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function logout() {
|
|
|
|
|
+ localStorage.removeItem('authToken');
|
|
|
|
|
+ localStorage.removeItem('userData');
|
|
|
|
|
+ currentUser = null;
|
|
|
|
|
+
|
|
|
|
|
+ document.getElementById('userSection').classList.remove('active');
|
|
|
|
|
+ document.getElementById('authSection').style.display = 'block';
|
|
|
|
|
+
|
|
|
|
|
+ document.getElementById('loginForm').reset();
|
|
|
|
|
+ document.getElementById('registerForm').reset();
|
|
|
|
|
+ clearMessages();
|
|
|
|
|
+
|
|
|
|
|
+ switchTab('login');
|
|
|
|
|
+ }
|
|
|
|
|
+ </script>
|
|
|
|
|
+</body>
|
|
|
|
|
+</html>
|