add: login & token validation tapi belum connect ke DB
This commit is contained in:
217
internal/api/service/auth_service.go
Normal file
217
internal/api/service/auth_service.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"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) {
|
||||
// For now, use hardcoded credentials
|
||||
// TODO: In a real implementation, you would query the database
|
||||
if email == "admin" && password == "admin" {
|
||||
// Create a dummy user
|
||||
user := &models.User{
|
||||
ID: "1",
|
||||
Email: "admin",
|
||||
Role: "expertise_doctor",
|
||||
Name: "Admin User",
|
||||
CreatedAt: time.Now().Format(time.RFC3339),
|
||||
UpdatedAt: time.Now().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
// Generate tokens
|
||||
accessToken, err := s.jwtManager.GenerateAccessToken(user.ID, user.Email, user.Role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
refreshToken, err := s.jwtManager.GenerateRefreshToken(user.ID, user.Email, user.Role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: In a real implementation, you would store the refresh token in the database
|
||||
// For example:
|
||||
// s.storeRefreshToken(user.ID, refreshToken)
|
||||
|
||||
// Determine redirect URL based on role
|
||||
redirectURL := "/viewer"
|
||||
if user.Role == "ref_doctor" || user.Role == "expertise_doctor" {
|
||||
redirectURL = "/studylist"
|
||||
}
|
||||
|
||||
return &models.LoginResponse{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
User: user,
|
||||
RedirectURL: redirectURL,
|
||||
}, nil
|
||||
} else if email == "patient" && password == "patient" {
|
||||
// Create a patient user
|
||||
user := &models.User{
|
||||
ID: "2",
|
||||
Email: "patient",
|
||||
Role: "patient",
|
||||
Name: "Patient User",
|
||||
CreatedAt: time.Now().Format(time.RFC3339),
|
||||
UpdatedAt: time.Now().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
// Generate tokens with patient-specific claims
|
||||
accessToken, err := s.jwtManager.GenerateAccessToken(user.ID, user.Email, user.Role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
refreshToken, err := s.jwtManager.GenerateRefreshToken(user.ID, user.Email, user.Role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &models.LoginResponse{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
User: user,
|
||||
RedirectURL: "/viewer",
|
||||
}, nil
|
||||
} else if email == "doctor" && password == "doctor" {
|
||||
// Create a referring doctor user
|
||||
user := &models.User{
|
||||
ID: "3",
|
||||
Email: "doctor",
|
||||
Role: "ref_doctor",
|
||||
Name: "Doctor User",
|
||||
CreatedAt: time.Now().Format(time.RFC3339),
|
||||
UpdatedAt: time.Now().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
// Generate tokens
|
||||
accessToken, err := s.jwtManager.GenerateAccessToken(user.ID, user.Email, user.Role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
refreshToken, err := s.jwtManager.GenerateRefreshToken(user.ID, user.Email, user.Role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &models.LoginResponse{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
User: user,
|
||||
RedirectURL: "/studylist",
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, ErrInvalidCredentials
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
// TODO: In a real implementation, you would check if the token is in the database and not revoked
|
||||
// Here we just generate a new access token
|
||||
accessToken, err := s.jwtManager.GenerateAccessToken(claims.UserID, claims.Email, claims.Role)
|
||||
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
|
||||
// For example:
|
||||
/*
|
||||
refreshToken := &models.RefreshToken{
|
||||
ID: uuid.New().String(),
|
||||
UserID: userID,
|
||||
Token: token,
|
||||
ExpiresAt: time.Now().Add(7 * 24 * time.Hour).Format(time.RFC3339),
|
||||
IsRevoked: false,
|
||||
CreatedAt: time.Now().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
_, err := s.db.NamedExec(
|
||||
`INSERT INTO refresh_tokens (id, user_id, token, expires_at, is_revoked, created_at)
|
||||
VALUES (:id, :user_id, :token, :expires_at, :is_revoked, :created_at)`,
|
||||
refreshToken,
|
||||
)
|
||||
return err
|
||||
*/
|
||||
|
||||
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
|
||||
// For example:
|
||||
/*
|
||||
_, err := s.db.Exec(
|
||||
"UPDATE refresh_tokens SET is_revoked = true WHERE token = ?",
|
||||
token,
|
||||
)
|
||||
return err
|
||||
*/
|
||||
|
||||
return nil
|
||||
}
|
||||
130
internal/api/service/db_repository.go
Normal file
130
internal/api/service/db_repository.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/models"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql" // Hanya menjalankan side-effectsnya saja dahulu
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// Repository provides an interface to the database
|
||||
type Repository struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
// NewRepository creates a new database repository
|
||||
func NewRepository(dsn string) (*Repository, error) {
|
||||
db, err := sqlx.Connect("mysql", dsn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to database: %w", err)
|
||||
}
|
||||
|
||||
// Set connection pool settings
|
||||
db.SetMaxOpenConns(10)
|
||||
db.SetMaxIdleConns(5)
|
||||
db.SetConnMaxLifetime(time.Hour)
|
||||
|
||||
return &Repository{
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetUserByEmail retrieves a user by email
|
||||
func (r *Repository) GetUserByEmail(email string) (*models.User, error) {
|
||||
var user models.User
|
||||
err := r.db.Get(&user, "SELECT * FROM users WHERE email = ?", email)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetUserByID retrieves a user by ID
|
||||
func (r *Repository) GetUserByID(id string) (*models.User, error) {
|
||||
var user models.User
|
||||
err := r.db.Get(&user, "SELECT * FROM users WHERE id = ?", id)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// StoreRefreshToken saves a refresh token to the database
|
||||
func (r *Repository) StoreRefreshToken(userID, token string, expiresAt time.Time) error {
|
||||
refreshToken := models.RefreshToken{
|
||||
ID: uuid.New().String(),
|
||||
UserID: userID,
|
||||
Token: token,
|
||||
ExpiresAt: expiresAt.Format(time.RFC3339),
|
||||
IsRevoked: false,
|
||||
CreatedAt: time.Now().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
_, err := r.db.NamedExec(
|
||||
`INSERT INTO refresh_tokens (id, user_id, token, expires_at, is_revoked, created_at)
|
||||
VALUES (:id, :user_id, :token, :expires_at, :is_revoked, :created_at)`,
|
||||
refreshToken,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetRefreshToken retrieves a refresh token from the database
|
||||
func (r *Repository) GetRefreshToken(token string) (*models.RefreshToken, error) {
|
||||
var refreshToken models.RefreshToken
|
||||
err := r.db.Get(&refreshToken, "SELECT * FROM refresh_tokens WHERE token = ?", token)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &refreshToken, nil
|
||||
}
|
||||
|
||||
// RevokeRefreshToken marks a refresh token as revoked
|
||||
func (r *Repository) RevokeRefreshToken(token string) error {
|
||||
_, err := r.db.Exec("UPDATE refresh_tokens SET is_revoked = true WHERE token = ?", token)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetPatientDetails retrieves patient details for a user
|
||||
func (r *Repository) GetPatientDetails(userID string) (*models.PatientDetails, error) {
|
||||
var patientDetails models.PatientDetails
|
||||
err := r.db.Get(&patientDetails, "SELECT * FROM patient_details WHERE user_id = ?", userID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &patientDetails, nil
|
||||
}
|
||||
|
||||
// GetDoctorDetails retrieves doctor details for a user
|
||||
func (r *Repository) GetDoctorDetails(userID string) (*models.DoctorDetails, error) {
|
||||
var doctorDetails models.DoctorDetails
|
||||
err := r.db.Get(&doctorDetails, "SELECT * FROM doctor_details WHERE user_id = ?", userID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &doctorDetails, nil
|
||||
}
|
||||
|
||||
// Close closes the database connection
|
||||
func (r *Repository) Close() error {
|
||||
return r.db.Close()
|
||||
}
|
||||
Reference in New Issue
Block a user