Changeset 0.24.1 (#157)
This commit is contained in:
@@ -71,6 +71,7 @@ type PageData struct {
|
||||
LogoURL string // branding: custom logo URL
|
||||
Tagline string // branding: tagline under instance name
|
||||
RegistrationOpen bool // whether self-registration is enabled
|
||||
AuthMode string // v0.24.1: "builtin", "mtls", "oidc"
|
||||
}
|
||||
|
||||
// UserContext is the authenticated user's info available to templates.
|
||||
@@ -211,6 +212,7 @@ func (e *Engine) RenderLogin() gin.HandlerFunc {
|
||||
LogoURL: logoURL,
|
||||
Tagline: tagline,
|
||||
RegistrationOpen: regOpen,
|
||||
AuthMode: e.cfg.AuthMode,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,6 +209,28 @@
|
||||
<p id="authSubtitle">Sign in to your Switchboard instance</p>
|
||||
</div>
|
||||
|
||||
{{if eq .AuthMode "oidc"}}
|
||||
<!-- SSO LOGIN -->
|
||||
<div class="anim anim-a2">
|
||||
<p style="color: var(--text-2); font-size: 0.9rem; margin-bottom: 1.5rem;">
|
||||
This instance uses Single Sign-On. Click below to authenticate with your identity provider.
|
||||
</p>
|
||||
<a class="btn-login" href="{{.BasePath}}/api/v1/auth/oidc/login" style="display:block; text-align:center; text-decoration:none;">
|
||||
Sign in with SSO
|
||||
</a>
|
||||
</div>
|
||||
{{else if eq .AuthMode "mtls"}}
|
||||
<!-- mTLS LOGIN -->
|
||||
<div class="anim anim-a2">
|
||||
<p style="color: var(--text-2); font-size: 0.9rem; margin-bottom: 1.5rem;">
|
||||
This instance uses client certificate authentication. Ensure your browser has a valid certificate installed.
|
||||
</p>
|
||||
<a class="btn-login" href="{{.BasePath}}/" style="display:block; text-align:center; text-decoration:none;">
|
||||
Continue with Certificate
|
||||
</a>
|
||||
</div>
|
||||
{{else}}
|
||||
<!-- BUILTIN LOGIN -->
|
||||
<div class="auth-tabs anim anim-a2">
|
||||
<button class="auth-tab active" id="tabLogin" onclick="_switchTab('login')">Log In</button>
|
||||
{{if .RegistrationOpen}}<button class="auth-tab" id="tabRegister" onclick="_switchTab('register')">Register</button>{{end}}
|
||||
@@ -251,6 +273,7 @@
|
||||
<button class="btn-login" id="regBtn" onclick="_doRegister()">Create Account</button>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}{{/* end auth mode conditional */}}
|
||||
|
||||
<div class="auth-footer">
|
||||
<p>Self-hosted instance · Your data stays on your infrastructure</p>
|
||||
@@ -272,29 +295,60 @@
|
||||
</script>
|
||||
<script src="{{.BasePath}}/js/pages.js?v={{.Version}}"></script>
|
||||
<script>
|
||||
// ── OIDC SSO callback handler ───────────────
|
||||
// The OIDC callback redirects here with #oidc_result=<base64 JSON>.
|
||||
// We decode it, save to localStorage (same format as builtin login),
|
||||
// set the page-auth cookie, and redirect to the app.
|
||||
(function() {
|
||||
const hash = window.location.hash;
|
||||
if (!hash.startsWith('#oidc_result=')) return;
|
||||
try {
|
||||
const encoded = hash.slice('#oidc_result='.length);
|
||||
const json = atob(encoded);
|
||||
const data = JSON.parse(json);
|
||||
const base = window.__BASE__ || '';
|
||||
const storageKey = base ? 'sb_auth_' + base.replace(/\//g, '') : 'sb_auth';
|
||||
localStorage.setItem(storageKey, JSON.stringify({
|
||||
accessToken: data.access_token,
|
||||
refreshToken: data.refresh_token,
|
||||
user: data.user,
|
||||
}));
|
||||
document.cookie = 'sb_token=' + data.access_token + '; path=/; max-age=900; SameSite=Strict';
|
||||
window.location.replace(base + '/');
|
||||
} catch (e) {
|
||||
console.error('[oidc] failed to process SSO callback:', e);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
// Builtin auth handlers — safe to include always; no-op if DOM elements absent.
|
||||
function _switchTab(tab) {
|
||||
document.getElementById('loginForm').style.display = tab === 'login' ? '' : 'none';
|
||||
const lf = document.getElementById('loginForm');
|
||||
if (lf) lf.style.display = tab === 'login' ? '' : 'none';
|
||||
const regForm = document.getElementById('registerForm');
|
||||
if (regForm) regForm.style.display = tab === 'register' ? '' : 'none';
|
||||
document.getElementById('tabLogin').classList.toggle('active', tab === 'login');
|
||||
const regTab = document.getElementById('tabRegister');
|
||||
if (regTab) regTab.classList.toggle('active', tab === 'register');
|
||||
document.getElementById('authTitle').textContent = tab === 'login' ? 'Welcome back' : 'Create account';
|
||||
document.getElementById('authSubtitle').textContent = tab === 'login'
|
||||
document.getElementById('tabLogin')?.classList.toggle('active', tab === 'login');
|
||||
document.getElementById('tabRegister')?.classList.toggle('active', tab === 'register');
|
||||
const title = document.getElementById('authTitle');
|
||||
if (title) title.textContent = tab === 'login' ? 'Welcome back' : 'Create account';
|
||||
const sub = document.getElementById('authSubtitle');
|
||||
if (sub) sub.textContent = tab === 'login'
|
||||
? 'Sign in to your Switchboard instance'
|
||||
: 'Join your team on Switchboard';
|
||||
document.getElementById('loginError').textContent = '';
|
||||
const loginErr = document.getElementById('loginError');
|
||||
if (loginErr) loginErr.textContent = '';
|
||||
const regErr = document.getElementById('regError');
|
||||
if (regErr) { regErr.textContent = ''; regErr.style.display = 'none'; }
|
||||
}
|
||||
|
||||
async function _doRegister() {
|
||||
const username = document.getElementById('regUsername').value.trim();
|
||||
const email = document.getElementById('regEmail').value.trim();
|
||||
const password = document.getElementById('regPassword').value;
|
||||
const confirm = document.getElementById('regConfirm').value;
|
||||
const username = document.getElementById('regUsername')?.value.trim();
|
||||
const email = document.getElementById('regEmail')?.value.trim();
|
||||
const password = document.getElementById('regPassword')?.value;
|
||||
const confirm = document.getElementById('regConfirm')?.value;
|
||||
const errEl = document.getElementById('regError');
|
||||
const btn = document.getElementById('regBtn');
|
||||
if (!errEl || !btn) return;
|
||||
|
||||
if (!username || !password) { errEl.textContent = 'Username and password are required'; errEl.style.display = ''; return; }
|
||||
if (password.length < 8) { errEl.textContent = 'Password must be at least 8 characters'; errEl.style.display = ''; return; }
|
||||
@@ -322,7 +376,6 @@
|
||||
errEl.style.display = '';
|
||||
setTimeout(() => { errEl.style.color = ''; errEl.style.display = 'none'; _switchTab('login'); }, 3000);
|
||||
} else {
|
||||
// Auto-login
|
||||
const storageKey = base ? 'sb_auth_' + base.replace(/\//g, '') : 'sb_auth';
|
||||
localStorage.setItem(storageKey, JSON.stringify({
|
||||
accessToken: data.access_token, refreshToken: data.refresh_token, user: data.user,
|
||||
@@ -338,12 +391,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Enter key handlers
|
||||
document.getElementById('loginPassword').addEventListener('keydown', e => {
|
||||
document.getElementById('loginPassword')?.addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') Pages.doLogin();
|
||||
});
|
||||
document.getElementById('loginUsername').addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') document.getElementById('loginPassword').focus();
|
||||
document.getElementById('loginUsername')?.addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') document.getElementById('loginPassword')?.focus();
|
||||
});
|
||||
document.getElementById('regConfirm')?.addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') _doRegister();
|
||||
|
||||
Reference in New Issue
Block a user