107 lines
3.8 KiB
Go
107 lines
3.8 KiB
Go
package public
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
customermodel "git.sendico.io/tech/sxchnge/product/customers/model"
|
|
sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model"
|
|
"net/http"
|
|
"time"
|
|
|
|
"git.sendico.io/tech/sxchnge/product/auth/http/v1/request"
|
|
"git.sendico.io/tech/sxchnge/product/auth/http/v1/response"
|
|
"git.sendico.io/tech/sxchnge/product/sessions/browser"
|
|
"git.sendico.io/tech/sxchnge/shared/errors"
|
|
"git.sendico.io/tech/sxchnge/shared/logging/fields"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
var errClientIPNotAllowed = merrors.Define("client_ip_not_allowed")
|
|
|
|
func isExpectedRefreshValidationError(err error) bool {
|
|
return errors.Is(err, merrors.ErrUnauthorized)
|
|
}
|
|
|
|
func validateToken(token string, rt *sessionmodel.RefreshToken) string {
|
|
if rt.AccountRef == nil {
|
|
return "missing account reference"
|
|
}
|
|
if token != rt.RefreshToken {
|
|
return "tokens do not match"
|
|
}
|
|
if rt.ExpiresAt.Before(time.Now()) {
|
|
return "token expired"
|
|
}
|
|
if rt.IsRevoked {
|
|
return "token has been revoked"
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func (pr *PublicRouter) validateRefreshToken(ctx context.Context, r *http.Request, req *srequest.TokenRefreshRotate) (*customermodel.Account, *sresponse.TokenData, *sessionmodel.RefreshToken, error) {
|
|
credential, err := browser.RefreshCredential(r, req.RefreshToken)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
req.RefreshToken = credential
|
|
client, err := pr.rtdb.GetClient(ctx, req.ClientID)
|
|
if errors.Is(err, merrors.ErrNoData) || client == nil {
|
|
pr.logger.Debug("Refresh token rejected: client not found", zap.String("client_id", req.ClientID))
|
|
return nil, nil, nil, merrors.Unauthorized("client not found")
|
|
}
|
|
if err != nil {
|
|
pr.logger.Warn("Failed to fetch client for refresh token validation", zap.Error(err), zap.String("client_id", req.ClientID))
|
|
return nil, nil, nil, err
|
|
}
|
|
if client.IsRevoked {
|
|
return nil, nil, nil, merrors.Unauthorized("client has been revoked")
|
|
}
|
|
if h := pr.validateClientIPPolicy(r, req.ClientID, client); h != nil {
|
|
return nil, nil, nil, errClientIPNotAllowed
|
|
}
|
|
|
|
rt, err := pr.rtdb.GetByCRT(ctx, req)
|
|
if errors.Is(err, merrors.ErrNoData) || errors.Is(err, merrors.ErrAccessDenied) {
|
|
pr.logger.Debug("Refresh session is inactive", zap.String("client_id", req.ClientID), zap.String("device_id", req.DeviceID))
|
|
return nil, nil, nil, merrors.Unauthorized("refresh session is inactive")
|
|
}
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
if rt.SessionVersion == "" {
|
|
pr.logger.Debug("Refresh token rejected: legacy session requires reauthentication",
|
|
zap.String("client_id", req.ClientID), zap.String("device_id", req.DeviceID))
|
|
return nil, nil, nil, merrors.Unauthorized("session reauthentication required")
|
|
}
|
|
|
|
if reason := validateToken(req.RefreshToken, rt); len(reason) > 0 {
|
|
pr.logger.Debug("Token validation failed", zap.String("reason", reason))
|
|
return nil, nil, nil, merrors.Unauthorized(reason)
|
|
}
|
|
|
|
var account customermodel.Account
|
|
if err := pr.db.Get(ctx, *rt.AccountRef, &account); errors.Is(err, merrors.ErrNoData) {
|
|
pr.logger.Debug("User not found while rotating refresh token", zap.Error(err), mzap.AccRef(*rt.AccountRef))
|
|
return nil, nil, nil, merrors.Unauthorized("user not found")
|
|
} else if err != nil {
|
|
pr.logger.Warn("Failed to fetch user while validating refresh token", zap.Error(err), mzap.AccRef(*rt.AccountRef))
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
if !account.IsActive() || account.IsArchived() {
|
|
return nil, nil, nil, merrors.Unauthorized("customer is inactive")
|
|
}
|
|
if client.AccountRef != nil && *client.AccountRef != account.ID {
|
|
return nil, nil, nil, merrors.Unauthorized("client account mismatch")
|
|
}
|
|
|
|
accessToken, err := pr.imp.CreateAccessTokenForSession(&account, rt.SessionIdentifier)
|
|
if err != nil {
|
|
pr.logger.Warn("Failed to generate access token", zap.Error(err))
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
return &account, &accessToken, rt, nil
|
|
}
|