mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
feat(BE-281): adjustment sso redirect,adjustment response closing,adjustment uniformity
This commit is contained in:
@@ -54,6 +54,7 @@ var (
|
|||||||
SSOAuthorizeURL string
|
SSOAuthorizeURL string
|
||||||
SSOTokenURL string
|
SSOTokenURL string
|
||||||
SSOGetMeURL string
|
SSOGetMeURL string
|
||||||
|
SSOPortalURL string
|
||||||
SSOClients map[string]SSOClientConfig
|
SSOClients map[string]SSOClientConfig
|
||||||
SSOAccessCookieName string
|
SSOAccessCookieName string
|
||||||
SSORefreshCookieName string
|
SSORefreshCookieName string
|
||||||
@@ -131,6 +132,7 @@ func init() {
|
|||||||
SSOAuthorizeURL = viper.GetString("SSO_AUTHORIZE_URL")
|
SSOAuthorizeURL = viper.GetString("SSO_AUTHORIZE_URL")
|
||||||
SSOTokenURL = viper.GetString("SSO_TOKEN_URL")
|
SSOTokenURL = viper.GetString("SSO_TOKEN_URL")
|
||||||
SSOGetMeURL = viper.GetString("SSO_GETME_URL")
|
SSOGetMeURL = viper.GetString("SSO_GETME_URL")
|
||||||
|
SSOPortalURL = strings.TrimSpace(viper.GetString("SSO_PORTAL_URL"))
|
||||||
SSOAccessCookieName = defaultString(viper.GetString("SSO_ACCESS_COOKIE_NAME"), "sso_access")
|
SSOAccessCookieName = defaultString(viper.GetString("SSO_ACCESS_COOKIE_NAME"), "sso_access")
|
||||||
SSORefreshCookieName = defaultString(viper.GetString("SSO_REFRESH_COOKIE_NAME"), "sso_refresh")
|
SSORefreshCookieName = defaultString(viper.GetString("SSO_REFRESH_COOKIE_NAME"), "sso_refresh")
|
||||||
SSOCookieDomain = viper.GetString("SSO_COOKIE_DOMAIN")
|
SSOCookieDomain = viper.GetString("SSO_COOKIE_DOMAIN")
|
||||||
|
|||||||
@@ -134,7 +134,14 @@ func ToSapronakProjectAggregatedFromReport(report *SapronakReportDTO, flag strin
|
|||||||
report = &SapronakReportDTO{}
|
report = &SapronakReportDTO{}
|
||||||
}
|
}
|
||||||
|
|
||||||
filter := strings.ToUpper(strings.TrimSpace(flag))
|
normalizeFlag := func(raw string) string {
|
||||||
|
normalized := strings.ToUpper(strings.TrimSpace(raw))
|
||||||
|
if normalized == "PULLET" {
|
||||||
|
return "DOC"
|
||||||
|
}
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
filter := normalizeFlag(flag)
|
||||||
|
|
||||||
byFlag := map[string]**SapronakCategoryDTO{}
|
byFlag := map[string]**SapronakCategoryDTO{}
|
||||||
if filter == "" || filter == "DOC" {
|
if filter == "" || filter == "DOC" {
|
||||||
@@ -149,10 +156,6 @@ func ToSapronakProjectAggregatedFromReport(report *SapronakReportDTO, flag strin
|
|||||||
result.Pakan = &SapronakCategoryDTO{Rows: make([]SapronakCategoryRowDTO, 0)}
|
result.Pakan = &SapronakCategoryDTO{Rows: make([]SapronakCategoryRowDTO, 0)}
|
||||||
byFlag["PAKAN"] = &result.Pakan
|
byFlag["PAKAN"] = &result.Pakan
|
||||||
}
|
}
|
||||||
if filter == "" || filter == "PULLET" {
|
|
||||||
result.Pullet = &SapronakCategoryDTO{Rows: make([]SapronakCategoryRowDTO, 0)}
|
|
||||||
byFlag["PULLET"] = &result.Pullet
|
|
||||||
}
|
|
||||||
|
|
||||||
formatDate := func(t *time.Time) string {
|
formatDate := func(t *time.Time) string {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
@@ -162,7 +165,7 @@ func ToSapronakProjectAggregatedFromReport(report *SapronakReportDTO, flag strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, group := range report.Groups {
|
for _, group := range report.Groups {
|
||||||
flagKey := strings.ToUpper(group.Flag)
|
flagKey := normalizeFlag(group.Flag)
|
||||||
ptr := byFlag[flagKey]
|
ptr := byFlag[flagKey]
|
||||||
if ptr == nil || *ptr == nil {
|
if ptr == nil || *ptr == nil {
|
||||||
continue
|
continue
|
||||||
@@ -182,7 +185,7 @@ func ToSapronakProjectAggregatedFromReport(report *SapronakReportDTO, flag strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
for idx, item := range group.Items {
|
for idx, item := range group.Items {
|
||||||
productKey := strings.ToUpper(group.Flag + "|" + item.ProductName)
|
productKey := strings.ToUpper(flagKey + "|" + item.ProductName)
|
||||||
baseRow := SapronakCategoryRowDTO{
|
baseRow := SapronakCategoryRowDTO{
|
||||||
ID: idx + 1,
|
ID: idx + 1,
|
||||||
Date: formatDate(item.Tanggal),
|
Date: formatDate(item.Tanggal),
|
||||||
@@ -246,7 +249,5 @@ func ToSapronakProjectAggregatedFromReport(report *SapronakReportDTO, flag strin
|
|||||||
buildTotals(result.Doc, "TOTAL DOC")
|
buildTotals(result.Doc, "TOTAL DOC")
|
||||||
buildTotals(result.Ovk, "TOTAL OVK")
|
buildTotals(result.Ovk, "TOTAL OVK")
|
||||||
buildTotals(result.Pakan, "TOTAL PAKAN")
|
buildTotals(result.Pakan, "TOTAL PAKAN")
|
||||||
buildTotals(result.Pullet, "TOTAL PULLET")
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -359,7 +359,11 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
|
|||||||
if filterFlag == "" {
|
if filterFlag == "" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return strings.ToUpper(f) == filterFlag
|
candidate := strings.ToUpper(f)
|
||||||
|
if filterFlag == "DOC" || filterFlag == "PULLET" {
|
||||||
|
return candidate == "DOC" || candidate == "PULLET"
|
||||||
|
}
|
||||||
|
return candidate == filterFlag
|
||||||
}
|
}
|
||||||
|
|
||||||
// For project flocks with category GROWING, pullet usage from chickin
|
// For project flocks with category GROWING, pullet usage from chickin
|
||||||
|
|||||||
@@ -517,27 +517,6 @@ func (s projectflockService) GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID u
|
|||||||
return total, nil
|
return total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getProjectFlockClosingDate mengembalikan tanggal closing Project Flock jika sudah mencapai step SELESAI (Approved).
|
|
||||||
// func (s projectflockService) getProjectFlockClosingDate(ctx context.Context, projectFlockID uint) (*time.Time, error) {
|
|
||||||
// if projectFlockID == 0 || s.ApprovalSvc == nil {
|
|
||||||
// return nil, nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// latest, err := s.ApprovalSvc.LatestByTarget(ctx, utils.ApprovalWorkflowProjectFlock, projectFlockID, nil)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// if latest == nil || latest.Action == nil || *latest.Action != entity.ApprovalActionApproved {
|
|
||||||
// return nil, nil
|
|
||||||
// }
|
|
||||||
// if latest.StepNumber != uint16(utils.ProjectFlockStepSelesai) {
|
|
||||||
// return nil, nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// t := latest.ActionAt
|
|
||||||
// return &t, nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (s projectflockService) GetProjectPeriods(c *fiber.Ctx, projectIDs []uint) (map[uint]int, error) {
|
func (s projectflockService) GetProjectPeriods(c *fiber.Ctx, projectIDs []uint) (map[uint]int, error) {
|
||||||
if len(projectIDs) == 0 {
|
if len(projectIDs) == 0 {
|
||||||
return map[uint]int{}, nil
|
return map[uint]int{}, nil
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ func (u *UniformityController) GetAll(c *fiber.Ctx) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
documents, err := u.UniformityService.MapDocuments(c, result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).
|
return c.Status(fiber.StatusOK).
|
||||||
JSON(response.SuccessWithPaginate[dto.UniformityListDTO]{
|
JSON(response.SuccessWithPaginate[dto.UniformityListDTO]{
|
||||||
@@ -53,7 +57,7 @@ func (u *UniformityController) GetAll(c *fiber.Ctx) error {
|
|||||||
"status": "Pengajuan",
|
"status": "Pengajuan",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: dto.ToUniformityListDTOsWithStandard(result, standards),
|
Data: dto.ToUniformityListDTOsWithStandard(result, standards, documents),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ type UniformityDetailDTO struct {
|
|||||||
Sampling UniformitySamplingDTO `json:"sampling"`
|
Sampling UniformitySamplingDTO `json:"sampling"`
|
||||||
Result UniformityResultDTO `json:"result"`
|
Result UniformityResultDTO `json:"result"`
|
||||||
Standard *UniformityStandardDTO `json:"standard"`
|
Standard *UniformityStandardDTO `json:"standard"`
|
||||||
|
LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval"`
|
||||||
UniformityDetails []UniformityDetailItemDTO `json:"uniformity_details"`
|
UniformityDetails []UniformityDetailItemDTO `json:"uniformity_details"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +64,7 @@ type UniformityListDTO struct {
|
|||||||
LocationName string `json:"location_name"`
|
LocationName string `json:"location_name"`
|
||||||
FlockName string `json:"flock_name"`
|
FlockName string `json:"flock_name"`
|
||||||
KandangName string `json:"kandang_name"`
|
KandangName string `json:"kandang_name"`
|
||||||
|
FileName string `json:"file_name"`
|
||||||
AppliedAt *time.Time `json:"applied_at"`
|
AppliedAt *time.Time `json:"applied_at"`
|
||||||
Week int `json:"week"`
|
Week int `json:"week"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
@@ -115,12 +117,19 @@ func ToUniformityDetailDTO(
|
|||||||
info.FileURL = documentURL
|
info.FileURL = documentURL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var latestApproval *approvalDTO.ApprovalRelationDTO
|
||||||
|
if entityData.LatestApproval != nil {
|
||||||
|
mapped := approvalDTO.ToApprovalDTO(*entityData.LatestApproval)
|
||||||
|
latestApproval = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
return UniformityDetailDTO{
|
return UniformityDetailDTO{
|
||||||
Id: entityData.Id,
|
Id: entityData.Id,
|
||||||
InfoUmum: info,
|
InfoUmum: info,
|
||||||
Sampling: toUniformitySamplingDTO(calc),
|
Sampling: toUniformitySamplingDTO(calc),
|
||||||
Result: toUniformityResultDTO(calc),
|
Result: toUniformityResultDTO(calc),
|
||||||
Standard: standard,
|
Standard: standard,
|
||||||
|
LatestApproval: latestApproval,
|
||||||
UniformityDetails: toUniformityDetailItemsDTO(calc),
|
UniformityDetails: toUniformityDetailItemsDTO(calc),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,9 +172,15 @@ func ToUniformityListDTOs(items []entity.ProjectFlockKandangUniformity) []Unifor
|
|||||||
func ToUniformityListDTOsWithStandard(
|
func ToUniformityListDTOsWithStandard(
|
||||||
items []entity.ProjectFlockKandangUniformity,
|
items []entity.ProjectFlockKandangUniformity,
|
||||||
standards map[uint]service.UniformityStandard,
|
standards map[uint]service.UniformityStandard,
|
||||||
|
documentNames map[uint]string,
|
||||||
) []UniformityListDTO {
|
) []UniformityListDTO {
|
||||||
result := ToUniformityListDTOs(items)
|
result := ToUniformityListDTOs(items)
|
||||||
if len(result) == 0 || len(standards) == 0 {
|
if len(result) == 0 || len(standards) == 0 {
|
||||||
|
for i := range result {
|
||||||
|
if name, ok := documentNames[result[i].Id]; ok {
|
||||||
|
result[i].FileName = name
|
||||||
|
}
|
||||||
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +189,9 @@ func ToUniformityListDTOsWithStandard(
|
|||||||
result[i].StandardMeanWeight = std.MeanWeight
|
result[i].StandardMeanWeight = std.MeanWeight
|
||||||
result[i].StandardUniformity = std.Uniformity
|
result[i].StandardUniformity = std.Uniformity
|
||||||
}
|
}
|
||||||
|
if name, ok := documentNames[result[i].Id]; ok {
|
||||||
|
result[i].FileName = name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ type UniformityService interface {
|
|||||||
GetSummary(ctx *fiber.Ctx, id uint) (*entity.ProjectFlockKandangUniformity, error)
|
GetSummary(ctx *fiber.Ctx, id uint) (*entity.ProjectFlockKandangUniformity, error)
|
||||||
GetStandard(ctx *fiber.Ctx, uniformity *entity.ProjectFlockKandangUniformity) (*UniformityStandard, error)
|
GetStandard(ctx *fiber.Ctx, uniformity *entity.ProjectFlockKandangUniformity) (*UniformityStandard, error)
|
||||||
MapStandards(ctx *fiber.Ctx, items []entity.ProjectFlockKandangUniformity) (map[uint]UniformityStandard, error)
|
MapStandards(ctx *fiber.Ctx, items []entity.ProjectFlockKandangUniformity) (map[uint]UniformityStandard, error)
|
||||||
|
MapDocuments(ctx *fiber.Ctx, items []entity.ProjectFlockKandangUniformity) (map[uint]string, error)
|
||||||
CreateOne(ctx *fiber.Ctx, req *validation.Create, file *multipart.FileHeader, rows []BodyWeightExcelRow) (*entity.ProjectFlockKandangUniformity, error)
|
CreateOne(ctx *fiber.Ctx, req *validation.Create, file *multipart.FileHeader, rows []BodyWeightExcelRow) (*entity.ProjectFlockKandangUniformity, error)
|
||||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint, file *multipart.FileHeader, rows []BodyWeightExcelRow) (*entity.ProjectFlockKandangUniformity, error)
|
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint, file *multipart.FileHeader, rows []BodyWeightExcelRow) (*entity.ProjectFlockKandangUniformity, error)
|
||||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||||
@@ -189,6 +190,29 @@ func (s uniformityService) MapStandards(c *fiber.Ctx, items []entity.ProjectFloc
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s uniformityService) MapDocuments(c *fiber.Ctx, items []entity.ProjectFlockKandangUniformity) (map[uint]string, error) {
|
||||||
|
if s.DocumentSvc == nil || len(items) == 0 {
|
||||||
|
return map[uint]string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[uint]string, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
if item.Id == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
documents, err := s.DocumentSvc.ListByTarget(c.Context(), "UNIFORMITY", uint64(item.Id))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(documents) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result[item.Id] = documents[len(documents)-1].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *uniformityService) CreateOne(c *fiber.Ctx, req *validation.Create, file *multipart.FileHeader, rows []BodyWeightExcelRow) (*entity.ProjectFlockKandangUniformity, error) {
|
func (s *uniformityService) CreateOne(c *fiber.Ctx, req *validation.Create, file *multipart.FileHeader, rows []BodyWeightExcelRow) (*entity.ProjectFlockKandangUniformity, error) {
|
||||||
if err := s.Validate.Struct(req); err != nil {
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -649,7 +673,7 @@ func (s uniformityService) fetchUniformityDocument(ctx context.Context, uniformi
|
|||||||
return nil, "", fiber.NewError(fiber.StatusNotFound, "Uniformity document not found")
|
return nil, "", fiber.NewError(fiber.StatusNotFound, "Uniformity document not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
document := documents[0]
|
document := documents[len(documents)-1]
|
||||||
url, err := s.DocumentSvc.PresignURL(ctx, document, 15*time.Minute)
|
url, err := s.DocumentSvc.PresignURL(ctx, document, 15*time.Minute)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
|
|||||||
@@ -144,6 +144,9 @@ func (h *Controller) Refresh(c *fiber.Ctx) error {
|
|||||||
refreshName := resolveSSOCookieName(config.SSORefreshCookieName, "refresh")
|
refreshName := resolveSSOCookieName(config.SSORefreshCookieName, "refresh")
|
||||||
refreshToken := strings.TrimSpace(c.Cookies(refreshName))
|
refreshToken := strings.TrimSpace(c.Cookies(refreshName))
|
||||||
if refreshToken == "" {
|
if refreshToken == "" {
|
||||||
|
if target := buildStartRedirect(defaultSSOClientAlias()); target != "" {
|
||||||
|
return c.Redirect(target, fiber.StatusFound)
|
||||||
|
}
|
||||||
return fiber.NewError(fiber.StatusUnauthorized, "unauthenticated")
|
return fiber.NewError(fiber.StatusUnauthorized, "unauthenticated")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +177,9 @@ func (h *Controller) Refresh(c *fiber.Ctx) error {
|
|||||||
if resp.StatusCode == fiber.StatusTooManyRequests {
|
if resp.StatusCode == fiber.StatusTooManyRequests {
|
||||||
return fiber.NewError(fiber.StatusTooManyRequests, "Too many attempts, please slow down")
|
return fiber.NewError(fiber.StatusTooManyRequests, "Too many attempts, please slow down")
|
||||||
}
|
}
|
||||||
|
if target := buildStartRedirect(defaultSSOClientAlias()); target != "" {
|
||||||
|
return c.Redirect(target, fiber.StatusFound)
|
||||||
|
}
|
||||||
return fiber.NewError(fiber.StatusUnauthorized, "unauthenticated")
|
return fiber.NewError(fiber.StatusUnauthorized, "unauthenticated")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,6 +431,7 @@ func (h *Controller) Logout(c *fiber.Ctx) error {
|
|||||||
refreshName := resolveSSOCookieName(config.SSORefreshCookieName, "refresh")
|
refreshName := resolveSSOCookieName(config.SSORefreshCookieName, "refresh")
|
||||||
|
|
||||||
var accessToken, refreshToken string
|
var accessToken, refreshToken string
|
||||||
|
var verification *sso.VerificationResult
|
||||||
if accessName != "" {
|
if accessName != "" {
|
||||||
accessToken = strings.TrimSpace(c.Cookies(accessName))
|
accessToken = strings.TrimSpace(c.Cookies(accessName))
|
||||||
}
|
}
|
||||||
@@ -446,9 +453,10 @@ func (h *Controller) Logout(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if hadAccessCookie {
|
if hadAccessCookie {
|
||||||
if verification, err := sso.VerifyAccessToken(accessToken); err != nil {
|
if v, err := sso.VerifyAccessToken(accessToken); err != nil {
|
||||||
utils.Log.WithError(err).Warn("failed to verify access token during logout")
|
utils.Log.WithError(err).Warn("failed to verify access token during logout")
|
||||||
} else {
|
} else {
|
||||||
|
verification = v
|
||||||
if revoker := session.GetRevocationStore(); revoker != nil {
|
if revoker := session.GetRevocationStore(); revoker != nil {
|
||||||
if err := revoker.MarkUserLogout(c.Context(), verification.UserID, time.Now().UTC()); err != nil {
|
if err := revoker.MarkUserLogout(c.Context(), verification.UserID, time.Now().UTC()); err != nil {
|
||||||
utils.Log.WithError(err).Warn("failed to mark user logout")
|
utils.Log.WithError(err).Warn("failed to mark user logout")
|
||||||
@@ -475,6 +483,28 @@ func (h *Controller) Logout(c *fiber.Ctx) error {
|
|||||||
} else if rawReturn != "" {
|
} else if rawReturn != "" {
|
||||||
utils.Log.WithError(err).Warn("invalid return_to during logout")
|
utils.Log.WithError(err).Warn("invalid return_to during logout")
|
||||||
}
|
}
|
||||||
|
} else if rawReturn == "" && config.SSOPortalURL != "" {
|
||||||
|
if alias, singleCfg, ok := singleClientFromToken(verification); ok {
|
||||||
|
if normalized, err := normalizeReturnTarget(singleCfg.DefaultReturnURI, singleCfg); err == nil && normalized != "" {
|
||||||
|
redirectTarget = normalized
|
||||||
|
alias, cfg, hasClientInfo = alias, singleCfg, true
|
||||||
|
} else {
|
||||||
|
redirectTarget = config.SSOPortalURL
|
||||||
|
}
|
||||||
|
} else if accessToken != "" {
|
||||||
|
if alias, singleCfg, ok := h.singleClientFromSSO(c.Context(), accessToken); ok {
|
||||||
|
if normalized, err := normalizeReturnTarget(singleCfg.DefaultReturnURI, singleCfg); err == nil && normalized != "" {
|
||||||
|
redirectTarget = normalized
|
||||||
|
alias, cfg, hasClientInfo = alias, singleCfg, true
|
||||||
|
} else {
|
||||||
|
redirectTarget = config.SSOPortalURL
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
redirectTarget = config.SSOPortalURL
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
redirectTarget = config.SSOPortalURL
|
||||||
|
}
|
||||||
} else if rawReturn != "" {
|
} else if rawReturn != "" {
|
||||||
if strings.HasPrefix(rawReturn, "/") && !strings.HasPrefix(rawReturn, "//") {
|
if strings.HasPrefix(rawReturn, "/") && !strings.HasPrefix(rawReturn, "//") {
|
||||||
redirectTarget = rawReturn
|
redirectTarget = rawReturn
|
||||||
@@ -494,6 +524,177 @@ func (h *Controller) Logout(c *fiber.Ctx) error {
|
|||||||
return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "signed out"})
|
return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "signed out"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func singleSSOClient() (string, config.SSOClientConfig, bool) {
|
||||||
|
if len(config.SSOClients) != 1 {
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
for alias, cfg := range config.SSOClients {
|
||||||
|
if strings.TrimSpace(alias) == "" || strings.TrimSpace(cfg.PublicID) == "" {
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
return alias, cfg, true
|
||||||
|
}
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func singleClientFromToken(verification *sso.VerificationResult) (string, config.SSOClientConfig, bool) {
|
||||||
|
if verification == nil || verification.Claims == nil {
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
return singleClientFromScopes(verification.Claims.Scopes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Controller) singleClientFromSSO(ctx context.Context, accessToken string) (string, config.SSOClientConfig, bool) {
|
||||||
|
accessToken = strings.TrimSpace(accessToken)
|
||||||
|
if accessToken == "" {
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
meURL := strings.TrimSpace(config.SSOGetMeURL)
|
||||||
|
if meURL == "" {
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, meURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
utils.Log.WithError(err).Warn("failed to build SSO getme request")
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||||
|
|
||||||
|
resp, err := h.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
utils.Log.WithError(err).Warn("SSO getme request failed")
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||||
|
utils.Log.WithField("status", resp.StatusCode).Warn("SSO getme responded with error")
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload struct {
|
||||||
|
Data struct {
|
||||||
|
Roles []struct {
|
||||||
|
Client *struct {
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
} `json:"client"`
|
||||||
|
} `json:"roles"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
||||||
|
utils.Log.WithError(err).Warn("failed to decode SSO getme response")
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
aliases := make(map[string]struct{})
|
||||||
|
for _, role := range payload.Data.Roles {
|
||||||
|
if role.Client == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
alias := strings.ToLower(strings.TrimSpace(role.Client.Alias))
|
||||||
|
if alias != "" {
|
||||||
|
aliases[alias] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(aliases) != 1 {
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
for alias := range aliases {
|
||||||
|
if normalized, cfg, ok := findClientAlias(alias); ok {
|
||||||
|
return normalized, cfg, true
|
||||||
|
}
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func singleClientFromScopes(scopes []string) (string, config.SSOClientConfig, bool) {
|
||||||
|
if len(scopes) == 0 {
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
seen := make(map[string]struct{})
|
||||||
|
for _, scope := range scopes {
|
||||||
|
if alias, ok := matchClientAliasFromScope(scope); ok {
|
||||||
|
seen[alias] = struct{}{}
|
||||||
|
}
|
||||||
|
if len(seen) > 1 {
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(seen) != 1 {
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
for alias := range seen {
|
||||||
|
if normalized, cfg, ok := findClientAlias(alias); ok {
|
||||||
|
return normalized, cfg, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchClientAliasFromScope(scope string) (string, bool) {
|
||||||
|
scope = strings.ToLower(strings.TrimSpace(scope))
|
||||||
|
if scope == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
prefix := scope
|
||||||
|
if idx := strings.IndexAny(prefix, ".:"); idx > 0 {
|
||||||
|
prefix = prefix[:idx]
|
||||||
|
}
|
||||||
|
if prefix == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
if alias, _, ok := findClientAlias(prefix); ok {
|
||||||
|
return alias, true
|
||||||
|
}
|
||||||
|
if prefix == "user-management" {
|
||||||
|
if alias, _, ok := findClientAlias("umgmt"); ok {
|
||||||
|
return alias, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if prefix == "umgmt" {
|
||||||
|
if alias, _, ok := findClientAlias("user-management"); ok {
|
||||||
|
return alias, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func findClientAlias(alias string) (string, config.SSOClientConfig, bool) {
|
||||||
|
alias = strings.TrimSpace(alias)
|
||||||
|
if alias == "" {
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
if cfg, ok := config.SSOClients[alias]; ok && strings.TrimSpace(cfg.PublicID) != "" {
|
||||||
|
return alias, cfg, true
|
||||||
|
}
|
||||||
|
for key, cfg := range config.SSOClients {
|
||||||
|
if strings.EqualFold(key, alias) && strings.TrimSpace(cfg.PublicID) != "" {
|
||||||
|
return key, cfg, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", config.SSOClientConfig{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultSSOClientAlias() string {
|
||||||
|
for alias := range config.SSOClients {
|
||||||
|
if strings.TrimSpace(alias) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return alias
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildStartRedirect(alias string) string {
|
||||||
|
alias = strings.TrimSpace(alias)
|
||||||
|
if alias == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return "/api/sso/start?client=" + url.QueryEscape(alias)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Controller) revokeToken(ctx context.Context, token string, verification *sso.VerificationResult) {
|
func (h *Controller) revokeToken(ctx context.Context, token string, verification *sso.VerificationResult) {
|
||||||
if h.revoker == nil || verification == nil || verification.Claims == nil {
|
if h.revoker == nil || verification == nil || verification.Claims == nil {
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user