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>
95 lines
2.1 KiB
Go
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
|
|
}
|