Files
tyto/backend/internal/auth/session.go
vikingowl 50c5811e22 feat: add authentication system with local and LDAP support
Auth Package (internal/auth/):
- Service: main auth orchestrator with multi-provider support
- LocalProvider: username/password auth with bcrypt hashing
- LDAPProvider: LDAP/Active Directory authentication with:
  - Service account bind for user search
  - User bind for password verification
  - Automatic user provisioning on first login
  - Group membership to role synchronization
- SessionManager: token-based session lifecycle
- Middleware: Gin middleware for route protection
- API: REST endpoints for login/logout/register

Security Features:
- bcrypt with cost factor 12 for password hashing
- Secure random 32-byte session tokens
- HTTP-only session cookies with SameSite=Lax
- Bearer token support for API clients
- Session expiration and cleanup
- Account disable with session invalidation

API Endpoints:
- POST /auth/login - Authenticate and get session
- POST /auth/logout - Invalidate current session
- POST /auth/logout/all - Invalidate all user sessions
- POST /auth/register - Create account (if enabled)
- GET /auth/me - Get current user info
- PUT /auth/me - Update profile
- PUT /auth/me/password - Change password

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 08:24:39 +01:00

95 lines
2.1 KiB
Go

package auth
import (
"context"
"time"
"tyto/internal/database"
)
// SessionManager handles session lifecycle.
type SessionManager struct {
db database.Database
duration time.Duration
}
// NewSessionManager creates a new session manager.
func NewSessionManager(db database.Database, duration time.Duration) *SessionManager {
if duration == 0 {
duration = 24 * time.Hour
}
return &SessionManager{
db: db,
duration: duration,
}
}
// Create creates a new session for a user.
func (m *SessionManager) Create(ctx context.Context, userID, ipAddress, userAgent string) (*database.Session, error) {
now := time.Now().UTC()
session := &database.Session{
Token: generateToken(),
UserID: userID,
CreatedAt: now,
ExpiresAt: now.Add(m.duration),
IPAddress: ipAddress,
UserAgent: userAgent,
}
if err := m.db.CreateSession(ctx, session); err != nil {
return nil, err
}
return session, nil
}
// Get retrieves a session by token, returning an error if expired or not found.
func (m *SessionManager) Get(ctx context.Context, token string) (*database.Session, error) {
session, err := m.db.GetSession(ctx, token)
if err != nil {
return nil, err
}
if session == nil {
return nil, ErrSessionNotFound
}
if time.Now().UTC().After(session.ExpiresAt) {
// Clean up expired session
m.db.DeleteSession(ctx, token)
return nil, ErrSessionExpired
}
return session, nil
}
// Delete removes a session.
func (m *SessionManager) Delete(ctx context.Context, token string) error {
return m.db.DeleteSession(ctx, token)
}
// Extend extends a session's expiration time.
func (m *SessionManager) Extend(ctx context.Context, token string) error {
session, err := m.Get(ctx, token)
if err != nil {
return err
}
// Update expiration
session.ExpiresAt = time.Now().UTC().Add(m.duration)
// Re-create session with new expiration
// (This is simpler than adding an Update method to Database interface)
if err := m.db.DeleteSession(ctx, token); err != nil {
return err
}
return m.db.CreateSession(ctx, session)
}
// Duration returns the session duration.
func (m *SessionManager) Duration() time.Duration {
return m.duration
}