music-assistant-server

8 KBHTML
login.html
8 KB238 lines • xml
1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>Login - Music Assistant</title>
7    <link rel="stylesheet" href="resources/common.css">
8    <style>
9        body {
10            min-height: 100vh;
11            display: flex;
12            align-items: center;
13            justify-content: center;
14            padding: 20px;
15        }
16
17        .login-container {
18            background: var(--panel);
19            border-radius: 16px;
20            box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12),
21                        0 0 0 1px var(--border);
22            width: 100%;
23            max-width: 400px;
24            padding: 48px 40px;
25        }
26
27        h1 {
28            text-align: center;
29            color: var(--fg);
30            font-size: 24px;
31            font-weight: 600;
32            letter-spacing: -0.5px;
33            margin-bottom: 8px;
34        }
35
36        .subtitle {
37            text-align: center;
38            color: var(--text-tertiary);
39            font-size: 14px;
40            margin-bottom: 32px;
41        }
42
43        .oauth-providers {
44            margin-top: 8px;
45        }
46    </style>
47</head>
48<body>
49    <div class="login-container">
50        <div class="logo">
51            <img src="logo.png" alt="Music Assistant">
52        </div>
53
54        <h1>Music Assistant</h1>
55        <p class="subtitle">External Client Authentication</p>
56
57        <div id="error" class="error"></div>
58
59        <form id="loginForm">
60            <div class="form-group">
61                <label for="username">Username</label>
62                <input type="text" id="username" name="username" required autofocus placeholder="Enter your username">
63            </div>
64
65            <div class="form-group">
66                <label for="password">Password</label>
67                <input type="password" id="password" name="password" required placeholder="Enter your password">
68            </div>
69
70            <button type="submit" class="btn btn-primary" id="loginBtn">
71                <span id="loginText">Sign In</span>
72                <span id="loginLoading" class="loading" style="display: none;"></span>
73            </button>
74        </form>
75
76        <div id="oauthProviders" class="oauth-providers">
77            <!-- OAuth providers will be inserted here -->
78        </div>
79    </div>
80
81    <script>
82        const API_BASE = window.location.origin;
83
84        // Get return_url and device_name from query string
85        const urlParams = new URLSearchParams(window.location.search);
86        const returnUrl = urlParams.get('return_url');
87        const deviceName = urlParams.get('device_name');
88
89        // Show error message
90        function showError(message) {
91            const errorEl = document.getElementById('error');
92            errorEl.textContent = message;
93            errorEl.classList.add('show');
94        }
95
96        // Hide error message
97        function hideError() {
98            document.getElementById('error').classList.remove('show');
99        }
100
101        // Set loading state
102        function setLoading(loading) {
103            const btn = document.getElementById('loginBtn');
104            const text = document.getElementById('loginText');
105            const loadingEl = document.getElementById('loginLoading');
106
107            btn.disabled = loading;
108            text.style.display = loading ? 'none' : 'inline';
109            loadingEl.style.display = loading ? 'inline-block' : 'none';
110        }
111
112        // Load OAuth providers
113        async function loadProviders() {
114            try {
115                const response = await fetch(`${API_BASE}/auth/providers`);
116                const providers = await response.json();
117
118                const oauthProviders = providers.filter(p => p.requires_redirect && p.provider_type !== 'builtin');
119
120                if (oauthProviders.length > 0) {
121                    const container = document.getElementById('oauthProviders');
122
123                    // Add divider
124                    const divider = document.createElement('div');
125                    divider.className = 'divider';
126                    divider.innerHTML = '<span>Or continue with</span>';
127                    container.appendChild(divider);
128
129                    // Add OAuth buttons
130                    oauthProviders.forEach(provider => {
131                        const btn = document.createElement('button');
132                        btn.className = 'btn btn-secondary';
133                        btn.type = 'button';
134
135                        let providerName = provider.provider_type;
136                        if (provider.provider_type === 'homeassistant') {
137                            providerName = 'Home Assistant';
138                        } else if (provider.provider_type === 'google') {
139                            providerName = 'Google';
140                        }
141
142                        btn.innerHTML = `<span>Sign in with ${providerName}</span>`;
143                        btn.onclick = () => initiateOAuth(provider.provider_id);
144
145                        container.appendChild(btn);
146                    });
147                }
148            } catch (error) {
149                console.error('Failed to load providers:', error);
150            }
151        }
152
153        // Handle form submission
154        document.getElementById('loginForm').addEventListener('submit', async (e) => {
155            e.preventDefault();
156            hideError();
157            setLoading(true);
158
159            const username = document.getElementById('username').value;
160            const password = document.getElementById('password').value;
161
162            try {
163                const requestBody = {
164                    provider_id: 'builtin',
165                    credentials: { username, password }
166                };
167
168                // Include device_name if provided via query parameter
169                if (deviceName) {
170                    requestBody.device_name = deviceName;
171                }
172
173                // Include return_url if present
174                if (returnUrl) {
175                    requestBody.return_url = returnUrl;
176                }
177
178                const response = await fetch(`${API_BASE}/auth/login`, {
179                    method: 'POST',
180                    headers: {
181                        'Content-Type': 'application/json'
182                    },
183                    body: JSON.stringify(requestBody)
184                });
185
186                const data = await response.json();
187
188                if (data.success) {
189                    if (data.redirect_to) {
190                        window.location.href = data.redirect_to;
191                    } else {
192                        window.location.href = `/?code=${encodeURIComponent(data.token)}`;
193                    }
194                } else {
195                    showError(data.error || 'Login failed');
196                }
197            } catch (error) {
198                showError('Network error. Please try again.');
199            } finally {
200                setLoading(false);
201            }
202        });
203
204        // Initiate OAuth flow
205        async function initiateOAuth(providerId) {
206            try {
207                let authorizeUrl = `${API_BASE}/auth/authorize?provider_id=${providerId}`;
208
209                // Pass return_url to authorize endpoint if present
210                if (returnUrl) {
211                    authorizeUrl += `&return_url=${encodeURIComponent(returnUrl)}`;
212                }
213
214                const response = await fetch(authorizeUrl);
215                const data = await response.json();
216
217                if (data.authorization_url) {
218                    // Redirect directly to OAuth provider
219                    window.location.href = data.authorization_url;
220                } else {
221                    showError('Failed to initiate OAuth flow');
222                }
223            } catch (error) {
224                showError('Network error. Please try again.');
225            }
226        }
227
228        // Clear error on input
229        document.querySelectorAll('input').forEach(input => {
230            input.addEventListener('input', hideError);
231        });
232
233        // Load providers on page load
234        loadProviders();
235    </script>
236</body>
237</html>
238