Changeset 0.24.3 (#159)
This commit is contained in:
96
server/auth/session.go
Normal file
96
server/auth/session.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/config"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/models"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/store"
|
||||
)
|
||||
|
||||
const sessionCookieName = "sb_session"
|
||||
|
||||
// CreateOrResumeSession looks up or creates a session participant for
|
||||
// an anonymous visitor to a workflow channel.
|
||||
//
|
||||
// Two entry paths:
|
||||
// - Cookie-based (default): random token stored in sb_session cookie
|
||||
// - mTLS-based: cert fingerprint used as stable session identity
|
||||
func CreateOrResumeSession(c *gin.Context, stores store.Stores, channelID string, cfg *config.Config) (*models.SessionParticipant, error) {
|
||||
// Check for existing session cookie
|
||||
token, _ := c.Cookie(sessionCookieName)
|
||||
|
||||
// mTLS mode: use cert fingerprint as stable token
|
||||
if cfg.AuthMode == "mtls" {
|
||||
fp := c.GetHeader("X-SSL-Client-Fingerprint")
|
||||
if fp != "" {
|
||||
token = "mtls:" + fp
|
||||
}
|
||||
}
|
||||
|
||||
// Resume existing session if token matches this channel
|
||||
if token != "" {
|
||||
session, err := stores.Sessions.GetByToken(c.Request.Context(), token)
|
||||
if err == nil && session.ChannelID == channelID {
|
||||
return session, nil
|
||||
}
|
||||
// Token exists but for different channel — fall through to create
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
log.Printf("[auth/session] warn: GetByToken error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create new session
|
||||
token = "sess:" + uuid.New().String()
|
||||
displayName, err := generateVisitorName(c.Request.Context(), stores, channelID)
|
||||
if err != nil {
|
||||
displayName = "Visitor"
|
||||
}
|
||||
|
||||
session := &models.SessionParticipant{
|
||||
SessionToken: token,
|
||||
ChannelID: channelID,
|
||||
DisplayName: displayName,
|
||||
}
|
||||
|
||||
// mTLS mode: store fingerprint for team member visibility
|
||||
if cfg.AuthMode == "mtls" {
|
||||
fp := c.GetHeader("X-SSL-Client-Fingerprint")
|
||||
if fp != "" {
|
||||
session.Fingerprint = fp
|
||||
}
|
||||
}
|
||||
|
||||
if err := stores.Sessions.Create(c.Request.Context(), session); err != nil {
|
||||
return nil, fmt.Errorf("create session: %w", err)
|
||||
}
|
||||
|
||||
// Add as channel participant
|
||||
_ = stores.Channels.AddParticipant(c.Request.Context(), &models.ChannelParticipant{
|
||||
ChannelID: channelID,
|
||||
ParticipantType: "session",
|
||||
ParticipantID: session.ID,
|
||||
Role: "visitor",
|
||||
})
|
||||
|
||||
// Set cookie (httponly, secure, 30 day expiry)
|
||||
c.SetCookie(sessionCookieName, token, 60*60*24*30, "/", "", true, true)
|
||||
|
||||
log.Printf("[auth/session] created session %s for channel %s (%s)", session.ID, channelID, displayName)
|
||||
return session, nil
|
||||
}
|
||||
|
||||
// generateVisitorName produces "Visitor #N" based on existing session count.
|
||||
func generateVisitorName(ctx context.Context, stores store.Stores, channelID string) (string, error) {
|
||||
count, err := stores.Sessions.CountForChannel(ctx, channelID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("Visitor #%d", count+1), nil
|
||||
}
|
||||
Reference in New Issue
Block a user