mirror of
https://github.com/wahyd4/passkey-auth.git
synced 2026-08-08 20:15:44 +10:00
Add logic to share session across configured domains
This commit is contained in:
@@ -45,6 +45,14 @@ auth:
|
||||
# Set to false to allow automatic approval for trusted environments
|
||||
require_approval: true
|
||||
|
||||
# Cookie domain for session cookies
|
||||
# Leave empty for single domain (cookies only work on current domain)
|
||||
# Set to ".yourdomain.com" to share cookies across all subdomains
|
||||
# Examples:
|
||||
# - "" (empty) - cookies only work on the exact domain
|
||||
# - ".example.com" - cookies work on example.com and all subdomains
|
||||
cookie_domain: ""
|
||||
|
||||
# Email allowlist - list of email addresses allowed to register
|
||||
# Leave empty to allow any email address (not recommended for production)
|
||||
allowed_emails:
|
||||
@@ -65,3 +73,4 @@ auth:
|
||||
# - SESSION_SECRET: Session encryption secret
|
||||
# - ALLOWED_EMAILS: Comma-separated list of allowed emails
|
||||
# - ADMIN_EMAIL: Admin email address
|
||||
# - COOKIE_DOMAIN: Cookie domain for session cookies
|
||||
|
||||
@@ -41,6 +41,7 @@ type AuthConfig struct {
|
||||
RequireApproval bool `yaml:"require_approval"`
|
||||
AllowedEmails []string `yaml:"allowed_emails"`
|
||||
AdminEmail string `yaml:"admin_email"`
|
||||
CookieDomain string `yaml:"cookie_domain"`
|
||||
}
|
||||
|
||||
func Load() (*Config, error) {
|
||||
@@ -75,6 +76,7 @@ func Load() (*Config, error) {
|
||||
SessionSecret: "change-me-in-production",
|
||||
RequireApproval: true,
|
||||
AllowedEmails: []string{}, // Empty means no email restrictions
|
||||
CookieDomain: "", // Empty means no domain restriction (current domain only)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -117,6 +119,9 @@ func Load() (*Config, error) {
|
||||
if adminEmail := os.Getenv("ADMIN_EMAIL"); adminEmail != "" {
|
||||
config.Auth.AdminEmail = adminEmail
|
||||
}
|
||||
if cookieDomain := os.Getenv("COOKIE_DOMAIN"); cookieDomain != "" {
|
||||
config.Auth.CookieDomain = cookieDomain
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ func New(db *database.DB, webAuthn *auth.WebAuthn, config *config.Config) *Handl
|
||||
HttpOnly: true,
|
||||
Secure: false, // Set to true in production with HTTPS
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Domain: config.Auth.CookieDomain, // Share cookies across subdomains if configured
|
||||
}
|
||||
|
||||
return &Handlers{
|
||||
@@ -393,10 +394,24 @@ func (h *Handlers) Logout(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// AuthCheck implements the nginx auth_request protocol
|
||||
func (h *Handlers) AuthCheck(w http.ResponseWriter, r *http.Request) {
|
||||
session, _ := h.store.Get(r, "auth-session")
|
||||
// Debug logging
|
||||
logrus.Debugf("AuthCheck request from %s", r.RemoteAddr)
|
||||
logrus.Debugf("AuthCheck headers: %+v", r.Header)
|
||||
logrus.Debugf("AuthCheck cookies: %+v", r.Cookies())
|
||||
|
||||
session, err := h.store.Get(r, "auth-session")
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to get auth session: %v", err)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
authenticated, ok := session.Values["authenticated"].(bool)
|
||||
logrus.Debugf("Session authenticated: %v, ok: %v", authenticated, ok)
|
||||
logrus.Debugf("Session values: %+v", session.Values)
|
||||
|
||||
if !ok || !authenticated {
|
||||
logrus.Debugf("User not authenticated, returning 401")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
@@ -409,6 +424,7 @@ func (h *Handlers) AuthCheck(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Auth-User", userEmail)
|
||||
}
|
||||
|
||||
logrus.Debugf("User authenticated, returning 200")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -263,13 +263,13 @@
|
||||
// Prefer 'redirect' over 'rd' if both are present
|
||||
const redirectParam = urlParams.get('redirect');
|
||||
const rdParam = urlParams.get('rd');
|
||||
|
||||
|
||||
console.log('URL params:', {
|
||||
redirect: redirectParam,
|
||||
rd: rdParam,
|
||||
search: window.location.search
|
||||
});
|
||||
|
||||
|
||||
return redirectParam || rdParam;
|
||||
}
|
||||
|
||||
@@ -281,14 +281,14 @@
|
||||
// Decode the URL if it's encoded
|
||||
const decodedUrl = decodeURIComponent(redirectUrl);
|
||||
console.log('Decoded redirect URL:', decodedUrl);
|
||||
|
||||
|
||||
// Validate the URL
|
||||
const url = new URL(decodedUrl);
|
||||
console.log('Parsed URL:', url.href);
|
||||
|
||||
|
||||
// Show a brief message before redirecting
|
||||
showAlert(`Redirecting to ${url.hostname}...`, 'success');
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
console.log('Executing redirect to:', decodedUrl);
|
||||
window.location.href = decodedUrl;
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Passkey Authentication</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
max-width: 400px;
|
||||
padding: 2rem;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #007bff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 1rem;
|
||||
}
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="spinner"></div>
|
||||
<h2>🔐 Passkey Authentication</h2>
|
||||
<p id="status">Checking authentication status...</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function updateStatus(message) {
|
||||
document.getElementById('status').textContent = message;
|
||||
}
|
||||
|
||||
function getRedirectUrl() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
// Check for both 'redirect' and 'rd' parameters (nginx uses 'rd' by default)
|
||||
// Prefer 'redirect' over 'rd' if both are present
|
||||
const redirectParam = urlParams.get('redirect');
|
||||
const rdParam = urlParams.get('rd');
|
||||
|
||||
console.log('URL params:', {
|
||||
redirect: redirectParam,
|
||||
rd: rdParam,
|
||||
search: window.location.search
|
||||
});
|
||||
|
||||
return redirectParam || rdParam;
|
||||
}
|
||||
|
||||
function redirectToTarget(url) {
|
||||
try {
|
||||
const decodedUrl = decodeURIComponent(url);
|
||||
updateStatus(`Redirecting to ${new URL(decodedUrl).hostname}...`);
|
||||
console.log('Redirecting to:', decodedUrl);
|
||||
// Small delay to show the message
|
||||
setTimeout(() => {
|
||||
window.location.href = decodedUrl;
|
||||
}, 1000);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error processing redirect URL:', error);
|
||||
updateStatus('Invalid redirect URL');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function redirectToLogin() {
|
||||
const redirectUrl = getRedirectUrl();
|
||||
if (redirectUrl) {
|
||||
// Preserve the redirect parameter when going to login
|
||||
const loginUrl = `/login.html?redirect=${encodeURIComponent(redirectUrl)}`;
|
||||
updateStatus('Redirecting to login...');
|
||||
setTimeout(() => {
|
||||
window.location.href = loginUrl;
|
||||
}, 1000);
|
||||
} else {
|
||||
// No redirect parameter, just go to login
|
||||
window.location.href = '/login.html';
|
||||
}
|
||||
}
|
||||
|
||||
async function checkAuthAndRedirect() {
|
||||
const redirectUrl = getRedirectUrl();
|
||||
|
||||
if (!redirectUrl) {
|
||||
updateStatus('No redirect URL provided');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login.html';
|
||||
}, 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
updateStatus('Checking authentication...');
|
||||
|
||||
const response = await fetch('/api/auth/status', {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const userData = await response.json();
|
||||
if (userData.authenticated) {
|
||||
updateStatus(`Welcome back, ${userData.user.display_name}!`);
|
||||
redirectToTarget(redirectUrl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Not authenticated, redirect to login with the target URL
|
||||
updateStatus('Authentication required...');
|
||||
redirectToLogin();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Auth check failed:', error);
|
||||
updateStatus('Authentication check failed...');
|
||||
redirectToLogin();
|
||||
}
|
||||
}
|
||||
|
||||
// Start the process when page loads
|
||||
document.addEventListener('DOMContentLoaded', checkAuthAndRedirect);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user