Add authentication system with Authelia integration

- Add /auth/user API endpoint to extract and return Authelia headers
- Create account.html test page for authentication verification
- Update documentation in CLAUDE.md with authentication setup details
- Add account page to main feature list in index.html

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Lucas 2025-06-14 01:56:00 +02:00
parent 94e6b88cbe
commit e95f47874a
4 changed files with 143 additions and 0 deletions

View file

@ -18,6 +18,30 @@ There is a reverse proxy rewrite rule in place. To call any API endpoint from th
- API endpoint URLs should be: `playground.shiny.space/api/yourendpointhere`
- Example: `/randomnumber` endpoint becomes `/api/randomnumber`
## Authentication
The setup uses Caddy with Authelia for authentication:
- Caddy acts as reverse proxy and checks with Authelia before allowing access to protected pages
- When authenticated, Authelia adds headers to requests that reach the Go API
- The Go API can read these headers to get user information
### Authentication Headers from Authelia
The following headers are available in authenticated requests:
- `Remote-User`: Username of the authenticated user
- `Remote-Name`: Display name of the user
- `Remote-Email`: Email address of the user
- `Remote-Groups`: User's group memberships
### Authentication API Endpoint
- `/api/auth/user` (GET) - Returns user information and all headers for debugging
- Protected pages can use JavaScript to fetch this endpoint to get current user info
- Returns JSON with username, name, email, groups, and complete headers object
### Authentication Test Page
- `account.html` - Test page that requires authentication to access
- Displays user information by calling `/api/auth/user`
- Shows both structured user info and raw headers for debugging
- Configure Caddy to require authentication for this page to test the setup
## Development
To rebuild the Go API after making changes:
- Run `go build -o api` in the project directory

77
account.html Normal file
View file

@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Account Information</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>Account Information</h1>
<p>Your authenticated user information:</p>
<div class="section">
<h3>User Details</h3>
<div id="user-info" class="loading">Loading user information...</div>
</div>
<div class="section">
<h3>All Headers (Debug)</h3>
<div id="headers-info" class="loading">Loading headers...</div>
</div>
<div class="back-link">
<a href="index.html" class="back-button">← Back to Feature List</a>
</div>
<script>
async function loadUserInfo() {
try {
const response = await fetch('/api/auth/user');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Display user information
const userInfoDiv = document.getElementById('user-info');
userInfoDiv.innerHTML = `
<p><strong>Username:</strong> ${data.username || 'Not provided'}</p>
<p><strong>Display Name:</strong> ${data.name || 'Not provided'}</p>
<p><strong>Email:</strong> ${data.email || 'Not provided'}</p>
<p><strong>Groups:</strong> ${data.groups || 'Not provided'}</p>
`;
userInfoDiv.className = 'response';
// Display all headers
const headersDiv = document.getElementById('headers-info');
let headersHtml = '<div style="font-family: monospace; font-size: 14px; background: #f8f8f8; padding: 15px; border-radius: 4px; overflow-x: auto;">';
if (data.headers && Object.keys(data.headers).length > 0) {
for (const [key, value] of Object.entries(data.headers)) {
headersHtml += `<div><strong>${key}:</strong> ${value}</div>`;
}
} else {
headersHtml += '<div>No headers available</div>';
}
headersHtml += '</div>';
headersDiv.innerHTML = headersHtml;
headersDiv.className = 'response';
} catch (error) {
console.error('Error loading user info:', error);
document.getElementById('user-info').innerHTML = `<p class="error">Error loading user information: ${error.message}</p>`;
document.getElementById('user-info').className = 'error';
document.getElementById('headers-info').innerHTML = `<p class="error">Error loading headers: ${error.message}</p>`;
document.getElementById('headers-info').className = 'error';
}
}
// Load user info when page loads
document.addEventListener('DOMContentLoaded', loadUserInfo);
</script>
</body>
</html>

View file

@ -27,6 +27,10 @@
<a href="items.html">Items Management</a>
<div class="description">Full CRUD demo with SQLite database - create, view, and delete items with persistent storage.</div>
</li>
<li>
<a href="account.html">Account Information</a>
<div class="description">Authentication test page - displays user info and headers from Authelia (requires authentication).</div>
</li>
</ul>
</body>
</html>

38
main.go
View file

@ -21,6 +21,14 @@ type Item struct {
Name string `json:"name"`
}
type UserInfo struct {
Username string `json:"username"`
Name string `json:"name"`
Email string `json:"email"`
Groups string `json:"groups"`
Headers map[string]string `json:"headers"`
}
var db *sql.DB
func initDB() {
@ -81,6 +89,9 @@ func main() {
// Items API endpoints
http.HandleFunc("/items", handleItems)
http.HandleFunc("/items/", handleItemDelete)
// Auth endpoints
http.HandleFunc("/auth/user", handleAuthUser)
log.Println("API running on :3847")
http.ListenAndServe("127.0.0.1:3847", nil)
@ -224,3 +235,30 @@ func handleItemDelete(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(map[string]string{"message": "Item deleted successfully"})
}
func handleAuthUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if r.Method != "GET" {
w.WriteHeader(http.StatusMethodNotAllowed)
json.NewEncoder(w).Encode(map[string]string{"error": "Method not allowed"})
return
}
// Collect all headers for debugging
headers := make(map[string]string)
for name, values := range r.Header {
headers[name] = strings.Join(values, ", ")
}
// Extract common Authelia headers
userInfo := UserInfo{
Username: r.Header.Get("Remote-User"),
Name: r.Header.Get("Remote-Name"),
Email: r.Header.Get("Remote-Email"),
Groups: r.Header.Get("Remote-Groups"),
Headers: headers,
}
json.NewEncoder(w).Encode(userInfo)
}