patient see their multiple studies
This commit is contained in:
@@ -61,10 +61,66 @@ func (h *DicomHandler) ForwardRequest(w http.ResponseWriter, r *http.Request) {
|
|||||||
if claims != nil {
|
if claims != nil {
|
||||||
switch claims.Role {
|
switch claims.Role {
|
||||||
case "patient":
|
case "patient":
|
||||||
// For patients requesting study list, filter to only show their study
|
// For patients requesting study list, filter to only show their studies
|
||||||
if strings.HasPrefix(urlPath, "/studies") && !strings.Contains(urlPath, "/") {
|
if strings.HasPrefix(urlPath, "/studies") && !strings.Contains(urlPath, "/") {
|
||||||
// Ensure only the patient's study is shown
|
// Check if multiple studies are available in the claim
|
||||||
queryParams.Set("StudyInstanceUID", claims.StudyIUID)
|
if len(claims.StudyIUIDs) > 0 {
|
||||||
|
// Remove existing StudyInstanceUID param if it exists
|
||||||
|
queryParams.Del("StudyInstanceUID")
|
||||||
|
|
||||||
|
// For DICOMweb, we can use comma-separated UIDs
|
||||||
|
queryParams.Set("StudyInstanceUID", strings.Join(claims.StudyIUIDs, ","))
|
||||||
|
|
||||||
|
h.logger.Debug("Filtering by multiple studies",
|
||||||
|
zap.Strings("studies", claims.StudyIUIDs),
|
||||||
|
)
|
||||||
|
} else if claims.StudyIUID != "" {
|
||||||
|
// Fallback for backward compatibility - ensure only the patient's study is shown
|
||||||
|
queryParams.Set("StudyInstanceUID", claims.StudyIUID)
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(urlPath, "/studies/") {
|
||||||
|
// This is a request for a specific study - check if the patient is authorized
|
||||||
|
|
||||||
|
// Extract the study ID from the path
|
||||||
|
// Format: /studies/{studyID}/...
|
||||||
|
pathParts := strings.Split(strings.TrimPrefix(urlPath, "/"), "/")
|
||||||
|
if len(pathParts) >= 2 {
|
||||||
|
studyID := pathParts[1]
|
||||||
|
|
||||||
|
// Check if this study is in the patient's authorized studies
|
||||||
|
authorized := false
|
||||||
|
|
||||||
|
// First check StudyIUIDs array
|
||||||
|
if len(claims.StudyIUIDs) > 0 {
|
||||||
|
for _, id := range claims.StudyIUIDs {
|
||||||
|
if id == studyID {
|
||||||
|
authorized = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then check single StudyIUID for backward compatibility
|
||||||
|
if !authorized && claims.StudyIUID == studyID {
|
||||||
|
authorized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not authorized, return 403 Forbidden
|
||||||
|
if !authorized {
|
||||||
|
h.logger.Warn("Unauthorized study access attempt",
|
||||||
|
zap.String("studyID", studyID),
|
||||||
|
zap.String("patientID", claims.PatientID),
|
||||||
|
zap.Strings("authorizedStudies", claims.StudyIUIDs),
|
||||||
|
)
|
||||||
|
http.Error(w, "Forbidden: You are not authorized to access this study", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Debug("Authorized access to specific study",
|
||||||
|
zap.String("studyID", studyID),
|
||||||
|
zap.String("patientID", claims.PatientID),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "ref_doctor":
|
case "ref_doctor":
|
||||||
// For ref_doctor requesting study list, apply filter from token
|
// For ref_doctor requesting study list, apply filter from token
|
||||||
|
|||||||
@@ -187,21 +187,49 @@ func PatientViewRestriction(logger *zap.Logger) func(http.Handler) http.Handler
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If a study is being requested, verify patient has access
|
// If a study is being requested, verify patient has access
|
||||||
if requestedStudyUID != "" && requestedStudyUID != claims.StudyIUID {
|
if requestedStudyUID != "" {
|
||||||
logger.Warn("Patient attempted to access unauthorized study",
|
// Check if the requested study is authorized
|
||||||
zap.String("userID", claims.UserID),
|
isAuthorized := false
|
||||||
zap.String("role", claims.Role),
|
|
||||||
zap.String("authorizedStudy", claims.StudyIUID),
|
|
||||||
zap.String("requestedStudy", requestedStudyUID))
|
|
||||||
|
|
||||||
// Return 403 Forbidden with a clear message
|
// First check the array of studies if available
|
||||||
w.Header().Set("Content-Type", "application/json")
|
if len(claims.StudyIUIDs) > 0 {
|
||||||
w.WriteHeader(http.StatusForbidden)
|
for _, studyUID := range claims.StudyIUIDs {
|
||||||
json.NewEncoder(w).Encode(map[string]string{
|
if studyUID == requestedStudyUID {
|
||||||
"error": "Access denied: You do not have permission to view this study",
|
isAuthorized = true
|
||||||
"code": "forbidden_study_access",
|
logger.Debug("Patient authorized to access study from StudyIUIDs array",
|
||||||
})
|
zap.String("userID", claims.UserID),
|
||||||
return
|
zap.String("requestedStudy", requestedStudyUID))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not found in the array, check the single StudyIUID for backward compatibility
|
||||||
|
if !isAuthorized && claims.StudyIUID == requestedStudyUID {
|
||||||
|
isAuthorized = true
|
||||||
|
logger.Debug("Patient authorized to access study from StudyIUID",
|
||||||
|
zap.String("userID", claims.UserID),
|
||||||
|
zap.String("requestedStudy", requestedStudyUID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// If still not authorized, return 403 Forbidden
|
||||||
|
if !isAuthorized {
|
||||||
|
logger.Warn("Patient attempted to access unauthorized study",
|
||||||
|
zap.String("userID", claims.UserID),
|
||||||
|
zap.String("role", claims.Role),
|
||||||
|
zap.String("requestedStudy", requestedStudyUID),
|
||||||
|
zap.Strings("authorizedStudies", claims.StudyIUIDs),
|
||||||
|
zap.String("authorizedStudy", claims.StudyIUID))
|
||||||
|
|
||||||
|
// Return 403 Forbidden with a clear message
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"error": "Access denied: You do not have permission to view this study",
|
||||||
|
"code": "forbidden_study_access",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patient has access or is requesting a list (which will be filtered)
|
// Patient has access or is requesting a list (which will be filtered)
|
||||||
|
|||||||
@@ -38,12 +38,14 @@ var MockUsers = []User{
|
|||||||
|
|
||||||
// PatientData represents additional data for patients
|
// PatientData represents additional data for patients
|
||||||
type PatientData struct {
|
type PatientData struct {
|
||||||
PatientID string `json:"patient_id"`
|
PatientID string `json:"patient_id"`
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
StudyIUID string `json:"study_iuid"`
|
StudyIUID string `json:"study_iuid,omitempty"` // For backward compatibility
|
||||||
AccessionNumber string `json:"accession_number"`
|
StudyIUIDs []string `json:"study_iuids,omitempty"` // Multiple study IDs
|
||||||
PatientName string `json:"patient_name"`
|
AccessionNumber string `json:"accession_number,omitempty"` // For backward compatibility
|
||||||
ReferringPhysician string `json:"referring_physician"`
|
AccessionNumbers []string `json:"accession_numbers,omitempty"` // Multiple accession numbers
|
||||||
|
PatientName string `json:"patient_name"`
|
||||||
|
ReferringPhysician string `json:"referring_physician"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockPatients represents a mock database of patient data
|
// MockPatients represents a mock database of patient data
|
||||||
@@ -51,8 +53,8 @@ var MockPatients = []PatientData{
|
|||||||
{
|
{
|
||||||
PatientID: "00211622",
|
PatientID: "00211622",
|
||||||
UserID: "2",
|
UserID: "2",
|
||||||
StudyIUID: "1.2.826.0.1.3680043.9.7307.1.20180713036",
|
StudyIUIDs: []string{"1.2.826.0.1.3680043.9.7307.1.20180530066", "1.2.826.0.1.3680043.9.7307.1.20180713036"},
|
||||||
AccessionNumber: "CR.180713.036",
|
AccessionNumbers: []string{"CR.180530.066", "CR.180713.036"},
|
||||||
PatientName: "DIDIT SUYATNA^R.10049.18",
|
PatientName: "DIDIT SUYATNA^R.10049.18",
|
||||||
ReferringPhysician: "DR. HERWINDO RIDWAN, SP.OT",
|
ReferringPhysician: "DR. HERWINDO RIDWAN, SP.OT",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ type RefreshToken struct {
|
|||||||
|
|
||||||
// PatientDetails contains patient-specific data
|
// PatientDetails contains patient-specific data
|
||||||
type PatientDetails struct {
|
type PatientDetails struct {
|
||||||
PatientID string `json:"patient_id"`
|
PatientID string `json:"patient_id"`
|
||||||
PatientName string `json:"patient_name"`
|
PatientName string `json:"patient_name"`
|
||||||
AccessionNumber string `json:"accession_number"`
|
AccessionNumber string `json:"accession_number,omitempty"` // For backward compatibility
|
||||||
StudyInstanceUID string `json:"study_instance_uid"`
|
AccessionNumbers []string `json:"accession_numbers,omitempty"` // Multiple accession numbers
|
||||||
|
StudyInstanceUID string `json:"study_instance_uid,omitempty"` // For backward compatibility
|
||||||
|
StudyInstanceUIDs []string `json:"study_instance_uids,omitempty"` // Multiple study IDs
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoctorDetails contains doctor-specific data
|
// DoctorDetails contains doctor-specific data
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ func SetupRouter(cfg *config.Config, logger *zap.Logger) http.Handler {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add this to your routes setup - in the public routes group
|
// * DEBUG PURPOSE ONLY *
|
||||||
r.Get("/debug/token", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/debug/token", func(w http.ResponseWriter, r *http.Request) {
|
||||||
authHeader := r.Header.Get("Authorization")
|
authHeader := r.Header.Get("Authorization")
|
||||||
if authHeader == "" {
|
if authHeader == "" {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func (s *AuthService) Login(email, password string) (*models.LoginResponse, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create token claims based on user role
|
// Create token claims based on user role
|
||||||
additionalClaims := make(map[string]string)
|
additionalClaims := make(map[string]interface{})
|
||||||
var redirectURL string
|
var redirectURL string
|
||||||
|
|
||||||
switch user.Role {
|
switch user.Role {
|
||||||
@@ -53,12 +53,29 @@ func (s *AuthService) Login(email, password string) (*models.LoginResponse, erro
|
|||||||
// Set patient-specific claims
|
// Set patient-specific claims
|
||||||
additionalClaims["patient_id"] = patientData.PatientID
|
additionalClaims["patient_id"] = patientData.PatientID
|
||||||
additionalClaims["patient_name"] = patientData.PatientName
|
additionalClaims["patient_name"] = patientData.PatientName
|
||||||
additionalClaims["accession_number"] = patientData.AccessionNumber
|
|
||||||
additionalClaims["study_iuid"] = patientData.StudyIUID
|
|
||||||
additionalClaims["home_url"] = fmt.Sprintf("viewer?StudyInstanceUIDs=%s", patientData.StudyIUID)
|
|
||||||
additionalClaims["study_list"] = "disabled"
|
|
||||||
|
|
||||||
redirectURL = fmt.Sprintf("/viewer?StudyInstanceUIDs=%s", patientData.StudyIUID)
|
// Handle multiple studies if available
|
||||||
|
if len(patientData.StudyIUIDs) > 0 {
|
||||||
|
// Store all studies in the token for DICOMWeb access
|
||||||
|
additionalClaims["study_iuids"] = patientData.StudyIUIDs
|
||||||
|
additionalClaims["accession_numbers"] = patientData.AccessionNumbers
|
||||||
|
|
||||||
|
// Set the first study as default for display and backward compatibility
|
||||||
|
additionalClaims["study_iuid"] = patientData.StudyIUIDs[0]
|
||||||
|
additionalClaims["accession_number"] = patientData.AccessionNumbers[0]
|
||||||
|
|
||||||
|
// Only use the first study in the redirect URL
|
||||||
|
additionalClaims["home_url"] = fmt.Sprintf("viewer?StudyInstanceUIDs=%s", patientData.StudyIUIDs[0])
|
||||||
|
redirectURL = fmt.Sprintf("/viewer?StudyInstanceUIDs=%s", patientData.StudyIUIDs[0])
|
||||||
|
} else {
|
||||||
|
// Fall back to single study for backward compatibility
|
||||||
|
additionalClaims["study_iuid"] = patientData.StudyIUID
|
||||||
|
additionalClaims["accession_number"] = patientData.AccessionNumber
|
||||||
|
additionalClaims["home_url"] = fmt.Sprintf("viewer?StudyInstanceUIDs=%s", patientData.StudyIUID)
|
||||||
|
redirectURL = fmt.Sprintf("/viewer?StudyInstanceUIDs=%s", patientData.StudyIUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
additionalClaims["study_list"] = "disabled"
|
||||||
|
|
||||||
case "ref_doctor":
|
case "ref_doctor":
|
||||||
// Set referring doctor claims
|
// Set referring doctor claims
|
||||||
@@ -112,7 +129,9 @@ func (s *AuthService) RefreshToken(refreshToken string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build additionalClaims from the refresh token
|
// Build additionalClaims from the refresh token
|
||||||
additionalClaims := make(map[string]string)
|
additionalClaims := make(map[string]interface{})
|
||||||
|
|
||||||
|
// Add string claims
|
||||||
if claims.PatientID != "" {
|
if claims.PatientID != "" {
|
||||||
additionalClaims["patient_id"] = claims.PatientID
|
additionalClaims["patient_id"] = claims.PatientID
|
||||||
}
|
}
|
||||||
@@ -135,6 +154,14 @@ func (s *AuthService) RefreshToken(refreshToken string) (string, error) {
|
|||||||
additionalClaims["filter_url"] = 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
|
// Generate a new access token with the same claims
|
||||||
accessToken, err := s.jwtManager.GenerateAccessToken(claims.UserID, claims.Email, claims.Role, claims.UserName, additionalClaims)
|
accessToken, err := s.jwtManager.GenerateAccessToken(claims.UserID, claims.Email, claims.Role, claims.UserName, additionalClaims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -38,10 +38,12 @@ type CustomClaims struct {
|
|||||||
TokenType string `json:"token_type"` // access or refresh
|
TokenType string `json:"token_type"` // access or refresh
|
||||||
|
|
||||||
// Patient-specific fields
|
// Patient-specific fields
|
||||||
PatientID string `json:"patient_id,omitempty"`
|
PatientID string `json:"patient_id,omitempty"`
|
||||||
PatientName string `json:"patient_name,omitempty"`
|
PatientName string `json:"patient_name,omitempty"`
|
||||||
AccessionNumber string `json:"accession_number,omitempty"`
|
AccessionNumber string `json:"accession_number,omitempty"` // For backward compatibility
|
||||||
StudyIUID string `json:"study_iuid,omitempty"`
|
AccessionNumbers []string `json:"accession_numbers,omitempty"` // Multiple accession numbers
|
||||||
|
StudyIUID string `json:"study_iuid,omitempty"` // For backward compatibility
|
||||||
|
StudyIUIDs []string `json:"study_iuids,omitempty"` // Multiple study IUIDs
|
||||||
|
|
||||||
// Navigation and permissions
|
// Navigation and permissions
|
||||||
HomeURL string `json:"home_url,omitempty"`
|
HomeURL string `json:"home_url,omitempty"`
|
||||||
@@ -52,7 +54,7 @@ type CustomClaims struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GenerateAccessToken creates a new access token with role-specific claims
|
// GenerateAccessToken creates a new access token with role-specific claims
|
||||||
func (m *JWTManager) GenerateAccessToken(userID, email, role, userName string, additionalClaims map[string]string) (string, error) {
|
func (m *JWTManager) GenerateAccessToken(userID, email, role, userName string, additionalClaims map[string]interface{}) (string, error) {
|
||||||
claims := CustomClaims{
|
claims := CustomClaims{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Email: email,
|
Email: email,
|
||||||
@@ -67,27 +69,36 @@ func (m *JWTManager) GenerateAccessToken(userID, email, role, userName string, a
|
|||||||
|
|
||||||
// Add role-specific additional claims
|
// Add role-specific additional claims
|
||||||
if additionalClaims != nil {
|
if additionalClaims != nil {
|
||||||
if val, ok := additionalClaims["patient_id"]; ok {
|
// Handle string claims
|
||||||
|
if val, ok := additionalClaims["patient_id"].(string); ok {
|
||||||
claims.PatientID = val
|
claims.PatientID = val
|
||||||
}
|
}
|
||||||
if val, ok := additionalClaims["patient_name"]; ok {
|
if val, ok := additionalClaims["patient_name"].(string); ok {
|
||||||
claims.PatientName = val
|
claims.PatientName = val
|
||||||
}
|
}
|
||||||
if val, ok := additionalClaims["accession_number"]; ok {
|
if val, ok := additionalClaims["accession_number"].(string); ok {
|
||||||
claims.AccessionNumber = val
|
claims.AccessionNumber = val
|
||||||
}
|
}
|
||||||
if val, ok := additionalClaims["study_iuid"]; ok {
|
if val, ok := additionalClaims["study_iuid"].(string); ok {
|
||||||
claims.StudyIUID = val
|
claims.StudyIUID = val
|
||||||
}
|
}
|
||||||
if val, ok := additionalClaims["home_url"]; ok {
|
if val, ok := additionalClaims["home_url"].(string); ok {
|
||||||
claims.HomeURL = val
|
claims.HomeURL = val
|
||||||
}
|
}
|
||||||
if val, ok := additionalClaims["study_list"]; ok {
|
if val, ok := additionalClaims["study_list"].(string); ok {
|
||||||
claims.StudyList = val
|
claims.StudyList = val
|
||||||
}
|
}
|
||||||
if val, ok := additionalClaims["filter_url"]; ok {
|
if val, ok := additionalClaims["filter_url"].(string); ok {
|
||||||
claims.FilterURL = val
|
claims.FilterURL = val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle array claims
|
||||||
|
if val, ok := additionalClaims["study_iuids"].([]string); ok {
|
||||||
|
claims.StudyIUIDs = val
|
||||||
|
}
|
||||||
|
if val, ok := additionalClaims["accession_numbers"].([]string); ok {
|
||||||
|
claims.AccessionNumbers = val
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
@@ -95,7 +106,7 @@ func (m *JWTManager) GenerateAccessToken(userID, email, role, userName string, a
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GenerateRefreshToken creates a new refresh token with the same claims as access token
|
// GenerateRefreshToken creates a new refresh token with the same claims as access token
|
||||||
func (m *JWTManager) GenerateRefreshToken(userID, email, role, userName string, additionalClaims map[string]string) (string, error) {
|
func (m *JWTManager) GenerateRefreshToken(userID, email, role, userName string, additionalClaims map[string]interface{}) (string, error) {
|
||||||
claims := CustomClaims{
|
claims := CustomClaims{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Email: email,
|
Email: email,
|
||||||
@@ -108,29 +119,38 @@ func (m *JWTManager) GenerateRefreshToken(userID, email, role, userName string,
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add role-specific additional claims (same as access token)
|
// Add role-specific additional claims
|
||||||
if additionalClaims != nil {
|
if additionalClaims != nil {
|
||||||
if val, ok := additionalClaims["patient_id"]; ok {
|
// Handle string claims
|
||||||
|
if val, ok := additionalClaims["patient_id"].(string); ok {
|
||||||
claims.PatientID = val
|
claims.PatientID = val
|
||||||
}
|
}
|
||||||
if val, ok := additionalClaims["patient_name"]; ok {
|
if val, ok := additionalClaims["patient_name"].(string); ok {
|
||||||
claims.PatientName = val
|
claims.PatientName = val
|
||||||
}
|
}
|
||||||
if val, ok := additionalClaims["accession_number"]; ok {
|
if val, ok := additionalClaims["accession_number"].(string); ok {
|
||||||
claims.AccessionNumber = val
|
claims.AccessionNumber = val
|
||||||
}
|
}
|
||||||
if val, ok := additionalClaims["study_iuid"]; ok {
|
if val, ok := additionalClaims["study_iuid"].(string); ok {
|
||||||
claims.StudyIUID = val
|
claims.StudyIUID = val
|
||||||
}
|
}
|
||||||
if val, ok := additionalClaims["home_url"]; ok {
|
if val, ok := additionalClaims["home_url"].(string); ok {
|
||||||
claims.HomeURL = val
|
claims.HomeURL = val
|
||||||
}
|
}
|
||||||
if val, ok := additionalClaims["study_list"]; ok {
|
if val, ok := additionalClaims["study_list"].(string); ok {
|
||||||
claims.StudyList = val
|
claims.StudyList = val
|
||||||
}
|
}
|
||||||
if val, ok := additionalClaims["filter_url"]; ok {
|
if val, ok := additionalClaims["filter_url"].(string); ok {
|
||||||
claims.FilterURL = val
|
claims.FilterURL = val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle array claims
|
||||||
|
if val, ok := additionalClaims["study_iuids"].([]string); ok {
|
||||||
|
claims.StudyIUIDs = val
|
||||||
|
}
|
||||||
|
if val, ok := additionalClaims["accession_numbers"].([]string); ok {
|
||||||
|
claims.AccessionNumbers = val
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
|||||||
Reference in New Issue
Block a user