Fix(BE-304):add refresh token and adjustment permission

This commit is contained in:
ragilap
2025-12-23 08:57:41 +07:00
parent ff4b4afcca
commit 2d8f20b70e
6 changed files with 119 additions and 34 deletions
+13 -22
View File
@@ -2,9 +2,10 @@ package middleware
// project-flock // project-flock
const ( const (
P_ProjectFlockKandangsClosing = "lti.production.project_flock_kandangs.closing" P_ProjectFlockKandangsClosing = "lti.production.project_flock_kandangs.closing"
P_ProjectFlockKandangsGetAll = "lti.production.project_flock_kandangs.list" P_ProjectFlockKandangsCheckClosing = "lti.production.project_flock_kandangs.closing.detail"
P_ProjectFlockKandangsGetOne = "lti.production.project_flock_kandangs.detail" P_ProjectFlockKandangsGetAll = "lti.production.project_flock_kandangs.list"
P_ProjectFlockKandangsGetOne = "lti.production.project_flock_kandangs.detail"
P_ProjectFlockGetAll = "lti.production.project_flocks.list" P_ProjectFlockGetAll = "lti.production.project_flocks.list"
P_ProjectFlockCreate = "lti.production.project_flocks.create" P_ProjectFlockCreate = "lti.production.project_flocks.create"
@@ -52,18 +53,8 @@ const (
P_ProductWarehouseGetOne = "lti.inventory.product_warehouses.detail" P_ProductWarehouseGetOne = "lti.inventory.product_warehouses.detail"
) )
const ( const (
P_ClosingGetAll = "lti.closing.list" P_ClosingGetAll = "lti.closing.list"
P_ClosingPenjualan = "lti.closing.penjualan" P_ClosingDetail = "lti.closing.detail"
P_ClosingGetSummary = "lti.closing.getsummary"
P_ClosingGetOverhead = "lti.closing.getoverhead"
P_ClosingCountSapronakKandang = "lti.closing.getsapronakcount.kandang"
P_ClosingCountSapronak = "lti.closing.getsapronakcount"
P_ClosingSapronak = "lti.closing.getsapronak"
P_ClosingExpeditionHpp = "lti.closing.expedition"
P_ClosingExpeditionHppByKandang = "lti.closing.expedition.kandang"
P_ClosingDataProduction = "lti.closing.production.data"
P_ClosingKeuangan = "lti.closing.keuangan"
) )
const ( const (
@@ -73,13 +64,13 @@ const (
) )
const ( const (
P_TransferToLaying_GetAll = "lti.production.transfer_to_laying.list" P_TransferToLaying_GetAll = "lti.production.transfer_to_laying.list"
P_TransferToLaying_GetOne = "lti.production.transfer_to_laying.detail" P_TransferToLaying_GetOne = "lti.production.transfer_to_laying.detail"
P_TransferToLaying_CreateOne = "lti.production.transfer_to_laying.create" P_TransferToLaying_CreateOne = "lti.production.transfer_to_laying.create"
P_TransferToLaying_UpdateOne = "lti.production.transfer_to_laying.update" P_TransferToLaying_UpdateOne = "lti.production.transfer_to_laying.update"
P_TransferToLaying_DeleteOne = "lti.production.transfer_to_laying.delete" P_TransferToLaying_DeleteOne = "lti.production.transfer_to_laying.delete"
P_TransferToLaying_Approval = "lti.production.transfer_to_laying.approve" P_TransferToLaying_Approval = "lti.production.transfer_to_laying.approve"
P_TransferToLaying_GetAvailableQty = "lti.production.transfer_to_laying.getavailableqty" P_TransferToLaying_GetAvailableQty = "lti.production.transfer_to_laying.getavailableqty"
) )
const ( const (
+10 -10
View File
@@ -22,14 +22,14 @@ func ClosingRoutes(v1 fiber.Router, u user.UserService, s closing.ClosingService
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne) // route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
route.Get("/", m.RequirePermissions(m.P_ClosingGetAll), ctrl.GetAll) route.Get("/", m.RequirePermissions(m.P_ClosingGetAll), ctrl.GetAll)
route.Get("/:project_flock_id/penjualan", m.RequirePermissions(m.P_ClosingPenjualan), ctrl.GetPenjualan) route.Get("/:project_flock_id/penjualan", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetPenjualan)
route.Get("/:projectFlockId", m.RequirePermissions(m.P_ClosingGetSummary), ctrl.GetClosingSummary) route.Get("/:projectFlockId", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetClosingSummary)
route.Get("/:project_flock_id/overhead", m.RequirePermissions(m.P_ClosingGetOverhead), ctrl.GetOverhead) route.Get("/:project_flock_id/overhead", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetOverhead)
route.Get("/:project_flock_id/:project_flock_kandang_id/perhitungan_sapronak", m.RequirePermissions(m.P_ClosingCountSapronakKandang), ctrl.GetSapronakByKandang) route.Get("/:project_flock_id/:project_flock_kandang_id/perhitungan_sapronak", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetSapronakByKandang)
route.Get("/:project_flock_id/perhitungan_sapronak", m.RequirePermissions(m.P_ClosingCountSapronak), ctrl.GetSapronakByProject) route.Get("/:project_flock_id/perhitungan_sapronak", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetSapronakByProject)
route.Get("/:projectFlockId/sapronak", m.RequirePermissions(m.P_ClosingSapronak), ctrl.GetClosingSapronak) route.Get("/:projectFlockId/sapronak", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetClosingSapronak)
route.Get("/:project_flock_id/expedition-hpp", m.RequirePermissions(m.P_ClosingExpeditionHpp), ctrl.GetExpeditionHPP) route.Get("/:project_flock_id/expedition-hpp", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetExpeditionHPP)
route.Get("/:project_flock_id/:project_flock_kandang_id/expedition-hpp", m.RequirePermissions(m.P_ClosingExpeditionHppByKandang), ctrl.GetExpeditionHPPByKandang) route.Get("/:project_flock_id/:project_flock_kandang_id/expedition-hpp", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetExpeditionHPPByKandang)
route.Get("/:projectFlockId/data-produksi", m.RequirePermissions(m.P_ClosingDataProduction), ctrl.GetClosingDataProduksi) route.Get("/:projectFlockId/data-produksi", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetClosingDataProduksi)
route.Get("/:projectFlockId/keuangan", m.RequirePermissions(m.P_ClosingKeuangan), ctrl.GetClosingKeuangan) route.Get("/:projectFlockId/keuangan", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetClosingKeuangan)
} }
@@ -18,6 +18,6 @@ func ProjectFlockKandangRoutes(v1 fiber.Router, u user.UserService, s projectFlo
route.Get("/:id",m.RequirePermissions(m.P_ProjectFlockKandangsGetOne), ctrl.GetOne) route.Get("/:id",m.RequirePermissions(m.P_ProjectFlockKandangsGetOne), ctrl.GetOne)
// route.Post("/:id/closing", m.RequirePermissions(m.PermissionProjectFlockClosing), ctrl.Closing) // route.Post("/:id/closing", m.RequirePermissions(m.PermissionProjectFlockClosing), ctrl.Closing)
// route.Get("/:id/closing/check", m.RequirePermissions(m.PermissionProjectFlockClosing), ctrl.CheckClosing) // route.Get("/:id/closing/check", m.RequirePermissions(m.PermissionProjectFlockClosing), ctrl.CheckClosing)
route.Post("/:id/closing", ctrl.Closing) route.Post("/:id/closing",m.RequirePermissions(m.P_ProjectFlockKandangsClosing), ctrl.Closing)
route.Get("/:id/closing/check", ctrl.CheckClosing) route.Get("/:id/closing/check", m.RequirePermissions(m.P_ProjectFlockKandangsCheckClosing), ctrl.CheckClosing)
} }
@@ -0,0 +1,13 @@
package controllers
type refreshTokenResponse struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"`
IDToken string `json:"id_token"`
Error string `json:"error"`
Description string `json:"error_description"`
}
@@ -138,6 +138,86 @@ func (h *Controller) Start(c *fiber.Ctx) error {
return c.Redirect(authorizeURL.String(), fiber.StatusFound) return c.Redirect(authorizeURL.String(), fiber.StatusFound)
} }
// Refresh exchanges the current SSO refresh token for a new access/refresh pair
// without redirecting the browser to the SSO login page.
func (h *Controller) Refresh(c *fiber.Ctx) error {
refreshName := resolveSSOCookieName(config.SSORefreshCookieName, "refresh")
refreshToken := strings.TrimSpace(c.Cookies(refreshName))
if refreshToken == "" {
return fiber.NewError(fiber.StatusUnauthorized, "unauthenticated")
}
tokenEndpoint := strings.TrimSpace(config.SSOTokenURL)
if tokenEndpoint == "" {
return fiber.NewError(fiber.StatusInternalServerError, "token endpoint not configured")
}
form := url.Values{}
form.Set("grant_type", "refresh_token")
form.Set("refresh_token", refreshToken)
req, err := http.NewRequestWithContext(c.Context(), http.MethodPost, tokenEndpoint, strings.NewReader(form.Encode()))
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "failed to create refresh request")
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := h.httpClient.Do(req)
if err != nil {
utils.Log.Errorf("token refresh request failed: %v", err)
return fiber.NewError(fiber.StatusBadGateway, "failed to refresh access token")
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
utils.Log.Warnf("token refresh response status %d", resp.StatusCode)
return fiber.NewError(fiber.StatusUnauthorized, "unauthenticated")
}
var tokenResp refreshTokenResponse
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
return fiber.NewError(fiber.StatusBadGateway, "invalid token response")
}
if tokenResp.Error != "" {
return fiber.NewError(fiber.StatusBadGateway, tokenResp.Description)
}
if tokenResp.AccessToken == "" {
return fiber.NewError(fiber.StatusBadGateway, "missing access token")
}
verification, err := sso.VerifyAccessToken(tokenResp.AccessToken)
if err != nil {
utils.Log.Errorf("access token verification failed: %v", err)
return fiber.NewError(fiber.StatusUnauthorized, "invalid access token")
}
issueCookies(c, struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"`
IDToken string `json:"id_token"`
Error string `json:"error"`
Description string `json:"error_description"`
}{
AccessToken: tokenResp.AccessToken,
RefreshToken: tokenResp.RefreshToken,
TokenType: tokenResp.TokenType,
ExpiresIn: tokenResp.ExpiresIn,
Scope: tokenResp.Scope,
IDToken: tokenResp.IDToken,
Error: tokenResp.Error,
Description: tokenResp.Description,
}, verification)
utils.Log.WithFields(logrus.Fields{
"user_id": verification.UserID,
}).Info("sso refresh successful")
return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "ok"})
}
// Callback handles the redirect from SSO containing the authorization code. // Callback handles the redirect from SSO containing the authorization code.
func (h *Controller) Callback(c *fiber.Ctx) error { func (h *Controller) Callback(c *fiber.Ctx) error {
state := strings.TrimSpace(c.Query("state")) state := strings.TrimSpace(c.Query("state"))
+1
View File
@@ -31,6 +31,7 @@ func Routes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
group.Get("/start", middleware.NewLimiter(30, time.Minute), ctrl.Start) group.Get("/start", middleware.NewLimiter(30, time.Minute), ctrl.Start)
group.Get("/callback", ctrl.Callback) group.Get("/callback", ctrl.Callback)
group.Get("/userinfo", middleware.NewLimiter(60, time.Minute), ctrl.UserInfo) group.Get("/userinfo", middleware.NewLimiter(60, time.Minute), ctrl.UserInfo)
group.Post("/refresh", middleware.NewLimiter(60, time.Minute), ctrl.Refresh)
group.Post("/logout", middleware.NewLimiter(60, time.Minute), ctrl.Logout) group.Post("/logout", middleware.NewLimiter(60, time.Minute), ctrl.Logout)
group.Post("/users/sync", middleware.NewLimiter(30, time.Minute), syncCtrl.Sync) group.Post("/users/sync", middleware.NewLimiter(30, time.Minute), syncCtrl.Sync)
} }