fix(t235): validate ERP user session by id

This commit is contained in:
QiuSW
2026-07-29 11:42:41 +08:00
parent dcc9ce34e1
commit 81f75cc372
9 changed files with 197 additions and 43 deletions
@@ -78,12 +78,18 @@ type SessionManager struct {
diagnosticLog DiagnosticLogger
diagnosticsOn bool
authenticated bool
identity sessionIdentity
captchaTicket string
captchaContent []byte
captchaType string
captchaExpires time.Time
}
type sessionIdentity struct {
ID string
Username string
}
func NewSessionManager(config SessionConfig) (*SessionManager, error) {
baseURL := strings.TrimRight(strings.TrimSpace(config.BaseURL), "/")
headers, err := RequestHeaders(baseURL)
@@ -259,7 +265,7 @@ func (manager *SessionManager) Login(
return manager.statusLocked(), ErrCaptchaTicketInvalid
}
defer manager.clearCaptchaLocked()
manager.authenticated = false
manager.clearAuthenticatedLocked()
payload, err := json.Marshal(map[string]string{
"username": manager.username,
"password": manager.password,
@@ -274,17 +280,18 @@ func (manager *SessionManager) Login(
LoginPath,
payload,
true,
false,
)
if err != nil {
return manager.statusLocked(), err
}
if !hasUser(data) {
identity, err := sessionIdentityFrom(data)
if err != nil {
return manager.statusLocked(), domain.ErrFreightSourceProtocol
}
if _, err := manager.validateLocked(ctx); err != nil {
if _, err := manager.validateIdentityLocked(ctx, identity); err != nil {
return manager.statusLocked(), err
}
manager.identity = identity
manager.authenticated = true
manager.clearCaptchaLocked()
return manager.statusLocked(), nil
@@ -308,18 +315,35 @@ func (manager *SessionManager) Validate(
}
func (manager *SessionManager) validateLocked(ctx context.Context) (any, error) {
if manager.identity.ID == "" || manager.identity.Username == "" {
manager.clearAuthenticatedLocked()
return nil, domain.ErrFreightSourceProtocol
}
return manager.validateIdentityLocked(ctx, manager.identity)
}
func (manager *SessionManager) validateIdentityLocked(
ctx context.Context,
expected sessionIdentity,
) (any, error) {
query := url.Values{}
query.Set("id", expected.ID)
data, err := manager.requestJSONLocked(
ctx,
http.MethodGet,
UserPath,
UserPath+"?"+query.Encode(),
nil,
false,
true,
)
if err != nil {
if errors.Is(err, domain.ErrFreightSourceProtocol) {
manager.clearAuthenticatedLocked()
}
return nil, err
}
if !hasUser(data) {
actual, err := sessionIdentityFrom(data)
if err != nil || actual != expected {
manager.clearAuthenticatedLocked()
return nil, domain.ErrFreightSourceProtocol
}
return data, nil
@@ -330,7 +354,6 @@ func (manager *SessionManager) requestJSONLocked(
method, path string,
body []byte,
loginRequest bool,
requireSession bool,
) (any, error) {
var content io.Reader
if body != nil {
@@ -387,8 +410,8 @@ func (manager *SessionManager) requestJSONLocked(
if loginRequest {
return nil, ErrLoginRejected
}
if requireSession || unauthenticatedCode(envelope.Code) {
manager.authenticated = false
if unauthenticatedCode(envelope.Code) {
manager.clearAuthenticatedLocked()
return nil, domain.ErrFreightSourceSessionNeeded
}
return nil, domain.ErrFreightSourceProtocol
@@ -430,12 +453,17 @@ func (manager *SessionManager) applyHeaders(request *http.Request) {
func (manager *SessionManager) responseErrorLocked(status int) error {
if status == http.StatusUnauthorized || status == http.StatusForbidden {
manager.authenticated = false
manager.clearAuthenticatedLocked()
return domain.ErrFreightSourceSessionNeeded
}
return domain.ErrFreightSourceUnavailable
}
func (manager *SessionManager) clearAuthenticatedLocked() {
manager.authenticated = false
manager.identity = sessionIdentity{}
}
func (manager *SessionManager) configuredLocked() bool {
return manager.username != "" && manager.password != ""
}
@@ -693,16 +721,28 @@ func redactDiagnosticString(value string) string {
return string(characters)
}
func hasUser(value any) bool {
func sessionIdentityFrom(value any) (sessionIdentity, error) {
data, ok := value.(map[string]any)
if !ok {
return false
return sessionIdentity{}, errInvalidProtocolInput
}
if user, exists := data["user"]; exists {
_, ok := user.(map[string]any)
return ok
data, ok = user.(map[string]any)
if !ok {
return sessionIdentity{}, errInvalidProtocolInput
}
}
return data["id"] != nil || data["username"] != nil
id, err := externalID(data["id"])
if err != nil {
return sessionIdentity{}, errInvalidProtocolInput
}
username, ok := data["username"].(string)
username = strings.TrimSpace(username)
if !ok || username == "" || len([]byte(username)) > 512 ||
!utf8.ValidString(username) || hasControl(username) {
return sessionIdentity{}, errInvalidProtocolInput
}
return sessionIdentity{ID: id, Username: username}, nil
}
func validCaptchaCode(value string) bool {