Merge branch 'pr-183'

# Conflicts:
#	js/md5.js
This commit is contained in:
Eduard Prigoana 2026-02-14 16:46:19 +00:00
commit a941faaa4e
7 changed files with 71 additions and 4 deletions

View file

@ -10,6 +10,9 @@ MONOCHROME_DEV_PORT=5173
AUTH_ENABLED=false
AUTH_SECRET=change-me-to-a-random-string
FIREBASE_PROJECT_ID=monochrome-database
# Optional: toggle login providers (defaults to true when unset)
# AUTH_GOOGLE_ENABLED=true
# AUTH_EMAIL_ENABLED=true
# Optional: override the Firebase config for the login page (JSON string)
# FIREBASE_CONFIG={"apiKey":"...","authDomain":"...","projectId":"...","storageBucket":"...","messagingSenderId":"...","appId":"..."}
# Optional: set PocketBase URL (hides the field in settings when set)

View file

@ -27,6 +27,8 @@ This document explains the optional server-side login gate and what it implies f
- `AUTH_ENABLED=true` enables the gate (default is false).
- `AUTH_SECRET` is required when the gate is enabled. It signs the session cookie.
- `AUTH_GOOGLE_ENABLED` toggles Google sign-in on `/login` (default true).
- `AUTH_EMAIL_ENABLED` toggles email/password sign-in on `/login` (default true).
- `FIREBASE_PROJECT_ID` sets the Firebase project used to verify tokens.
- `FIREBASE_CONFIG` (JSON) injects config into the login page.
- `POCKETBASE_URL` hides the custom DB setting field.

View file

@ -61,6 +61,23 @@ export class AuthManager {
return;
}
// Check for Neutralino mode
const isNeutralino =
window.NL_MODE ||
window.location.search.includes('mode=neutralino') ||
(window.Neutralino && typeof window.Neutralino === 'object');
if (isNeutralino) {
try {
await signInWithRedirect(auth, provider);
return;
} catch (error) {
console.error('Redirect Login failed:', error);
alert(`Login failed: ${error.message}`);
throw error;
}
}
try {
const result = await signInWithPopup(auth, provider);

View file

@ -265,7 +265,13 @@ document.addEventListener('DOMContentLoaded', async () => {
initTracker(player);
// Initialize desktop features if in Neutralino mode
if (typeof window !== 'undefined' && (window.NL_MODE || window.location.search.includes('mode=neutralino'))) {
if (
typeof window !== 'undefined' &&
(window.NL_MODE ||
window.location.search.includes('mode=neutralino') ||
(window.Neutralino && typeof window.Neutralino === 'object'))
) {
window.NL_MODE = true;
import('./desktop/desktop.js').then((m) => m.initDesktop(player));
}

View file

@ -5,6 +5,8 @@
"description": "[<img src=\"https://github.com/SamidyFR/monochrome/blob/main/assets/512.png?raw=true\" alt=\"Monochrome Logo\">](https://monochrome.samidy.com)",
"main": "sw.js",
"scripts": {
"preview": "vite preview",
"start": "vite preview",
"dev": "vite",
"dev:desktop": "start npm run dev & node scripts/dev-runner.js",
"build": "vite build --mode neutralino && bun x neu build",

View file

@ -183,7 +183,7 @@
<div id="error" class="error-msg"></div>
<button id="google-btn" class="btn" onclick="googleSignIn()">
<button id="google-btn" class="btn" onclick="googleSignIn()" style="display:none">
<svg class="google-icon" viewBox="0 0 24 24">
<path
fill="#4285F4"
@ -205,9 +205,9 @@
Sign in with Google
</button>
<div class="divider">or</div>
<div id="divider" class="divider" style="display:none">or</div>
<form id="email-form" onsubmit="emailAuth(event)">
<form id="email-form" onsubmit="emailAuth(event)" style="display:none">
<div class="form-group">
<input type="email" id="email" placeholder="Email" required autocomplete="email" />
</div>
@ -287,6 +287,30 @@
document.getElementById('error').style.display = 'none';
}
const googleBtn = document.getElementById('google-btn');
const emailForm = document.getElementById('email-form');
const divider = document.getElementById('divider');
const providerState = {
google: true,
password: true,
};
const providerConfig = window.__AUTH_PROVIDERS__ || {};
if (typeof providerConfig.google === 'boolean') providerState.google = providerConfig.google;
if (typeof providerConfig.password === 'boolean') providerState.password = providerConfig.password;
const NO_PROVIDER_MESSAGE = 'No sign-in providers are enabled for this Firebase project.';
function renderProviders() {
if (googleBtn) googleBtn.style.display = providerState.google ? '' : 'none';
if (emailForm) emailForm.style.display = providerState.password ? '' : 'none';
if (divider) divider.style.display = providerState.google && providerState.password ? '' : 'none';
if (!providerState.google && !providerState.password) {
showError(NO_PROVIDER_MESSAGE);
}
}
renderProviders();
function setLoading(loading) {
document.getElementById('google-btn').disabled = loading;
document.getElementById('email-btn').disabled = loading;

View file

@ -33,6 +33,8 @@ export default function authGatePlugin() {
const AUTH_ENABLED = (env.AUTH_ENABLED ?? 'false') !== 'false';
const FIREBASE_CONFIG = env.FIREBASE_CONFIG;
const POCKETBASE_URL = env.POCKETBASE_URL;
const AUTH_GOOGLE_ENABLED = env.AUTH_GOOGLE_ENABLED;
const AUTH_EMAIL_ENABLED = env.AUTH_EMAIL_ENABLED;
// Parse Firebase config once (used for injection + auth verification)
let parsedFirebaseConfig = null;
@ -51,6 +53,17 @@ export default function authGatePlugin() {
const flags = [];
if (AUTH_ENABLED) flags.push('window.__AUTH_GATE__=true');
const authProviderOverrides = {};
if (AUTH_GOOGLE_ENABLED !== undefined) {
authProviderOverrides.google = AUTH_GOOGLE_ENABLED !== 'false';
}
if (AUTH_EMAIL_ENABLED !== undefined) {
// Firebase calls it "password" provider; env uses "EMAIL" for clarity
authProviderOverrides.password = AUTH_EMAIL_ENABLED !== 'false';
}
if (Object.keys(authProviderOverrides).length > 0) {
flags.push(`window.__AUTH_PROVIDERS__=${JSON.stringify(authProviderOverrides)}`);
}
if (parsedFirebaseConfig) flags.push(`window.__FIREBASE_CONFIG__=${JSON.stringify(parsedFirebaseConfig)}`);
if (POCKETBASE_URL) flags.push(`window.__POCKETBASE_URL__=${JSON.stringify(POCKETBASE_URL)}`);
const configScript = flags.length > 0 ? `<script>${flags.join(';')};</script>` : null;