package service import ( "errors" "fmt" "net/url" "golang.org/x/crypto/bcrypt" "devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/models" "devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/auth" ) var ( ErrInvalidCredentials = errors.New("invalid credentials") ErrUserNotFound = errors.New("user not found") ) // AuthService handles authentication operations type AuthService struct { jwtManager *auth.JWTManager // When you implement database connection, add a db client here // db *sqlx.DB } // NewAuthService creates a new authentication service func NewAuthService(jwtManager *auth.JWTManager) *AuthService { return &AuthService{ jwtManager: jwtManager, } } // Login authenticates a user and generates tokens func (s *AuthService) Login(email, password string) (*models.LoginResponse, error) { // Find user in mock data user := models.FindUserByCredentials(email, password) if user == nil { return nil, ErrInvalidCredentials } // Create token claims based on user role additionalClaims := make(map[string]interface{}) var redirectURL string switch user.Role { case "patient": // Get patient data patientData := models.FindPatientDataByUserID(user.ID) if patientData == nil { return nil, ErrUserNotFound } // Set patient-specific claims additionalClaims["patient_id"] = patientData.PatientID additionalClaims["patient_name"] = patientData.PatientName additionalClaims["study_iuids"] = patientData.StudyIUIDs additionalClaims["accession_numbers"] = patientData.AccessionNumbers // For redirectURL and home_url, use first study for simplicity if len(patientData.StudyIUIDs) > 0 { additionalClaims["home_url"] = fmt.Sprintf("viewer?StudyInstanceUIDs=%s", patientData.StudyIUIDs[0]) redirectURL = fmt.Sprintf("/viewer?StudyInstanceUIDs=%s", patientData.StudyIUIDs[0]) } else { // Fallback for empty studies array (shouldn't happen) additionalClaims["home_url"] = "/" redirectURL = "/" } additionalClaims["study_list"] = "disabled" case "ref_doctor": // Set referring doctor claims encodedName := url.QueryEscape(user.Name) filterURL := fmt.Sprintf("studies?limit=101&offset=0&fuzzymatching=false&includefield=00081030,00080060,00080090&00080090=%s", encodedName) additionalClaims["home_url"] = "/" additionalClaims["study_list"] = "enabled" additionalClaims["filter_url"] = filterURL redirectURL = "/" case "expertise_doctor": // Expertise doctors have full access additionalClaims["home_url"] = "/" additionalClaims["study_list"] = "enabled" redirectURL = "/" } // Generate tokens accessToken, err := s.jwtManager.GenerateAccessToken(user.ID, user.Email, user.Role, user.Name, additionalClaims) if err != nil { return nil, err } refreshToken, err := s.jwtManager.GenerateRefreshToken(user.ID, user.Email, user.Role, user.Name, additionalClaims) if err != nil { return nil, err } return &models.LoginResponse{ AccessToken: accessToken, RefreshToken: refreshToken, User: user, RedirectURL: redirectURL, }, nil } // RefreshToken generates a new access token using a refresh token func (s *AuthService) RefreshToken(refreshToken string) (string, error) { // Validate the refresh token claims, err := s.jwtManager.ValidateToken(refreshToken) if err != nil { return "", err } // Check if token is a refresh token if claims.TokenType != "refresh" { return "", errors.New("invalid token type") } // Build additionalClaims from the refresh token additionalClaims := make(map[string]interface{}) // Add string claims if claims.PatientID != "" { additionalClaims["patient_id"] = claims.PatientID } if claims.PatientName != "" { additionalClaims["patient_name"] = claims.PatientName } if claims.HomeURL != "" { additionalClaims["home_url"] = claims.HomeURL } if claims.StudyList != "" { additionalClaims["study_list"] = claims.StudyList } if claims.FilterURL != "" { additionalClaims["filter_url"] = claims.FilterURL } // Add array claims if len(claims.StudyIUIDs) > 0 { additionalClaims["study_iuids"] = claims.StudyIUIDs } if len(claims.AccessionNumbers) > 0 { additionalClaims["accession_numbers"] = claims.AccessionNumbers } // Generate a new access token with the same claims accessToken, err := s.jwtManager.GenerateAccessToken(claims.UserID, claims.Email, claims.Role, claims.UserName, additionalClaims) if err != nil { return "", err } return accessToken, nil } // ValidateToken validates a token and returns the claims func (s *AuthService) ValidateToken(token string) (*auth.CustomClaims, error) { return s.jwtManager.ValidateToken(token) } // HashPassword hashes a password using bcrypt func HashPassword(password string) (string, error) { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return "", err } return string(hashedPassword), nil } // CheckPassword compares a password with a hash func CheckPassword(password, hash string) error { return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) } // Below functions would be implemented when connecting to a real database // storeRefreshToken stores a refresh token in the database func (s *AuthService) storeRefreshToken(userID, token string) error { // TODO: In a real implementation, this would insert a record in the database return nil } // revokeRefreshToken marks a refresh token as revoked func (s *AuthService) revokeRefreshToken(token string) error { // TODO: In a real implementation, this would update a record in the database return nil }