From d217083059120171d5c555b09eefe6ba3c8a8d42 Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Wed, 4 Jun 2025 09:23:44 +0200 Subject: [PATCH] feat: add API endpoint for user authorized clients --- .../internal/controller/oidc_controller.go | 61 +++++++++++++++++++ backend/internal/dto/oidc_dto.go | 5 ++ backend/internal/service/oidc_service.go | 14 +++++ 3 files changed, 80 insertions(+) diff --git a/backend/internal/controller/oidc_controller.go b/backend/internal/controller/oidc_controller.go index 2489337e..d6cb3fa2 100644 --- a/backend/internal/controller/oidc_controller.go +++ b/backend/internal/controller/oidc_controller.go @@ -51,6 +51,9 @@ func NewOidcController(group *gin.RouterGroup, authMiddleware *middleware.AuthMi group.POST("/oidc/device/authorize", oc.deviceAuthorizationHandler) group.POST("/oidc/device/verify", authMiddleware.WithAdminNotRequired().Add(), oc.verifyDeviceCodeHandler) group.GET("/oidc/device/info", authMiddleware.WithAdminNotRequired().Add(), oc.getDeviceCodeInfoHandler) + + group.GET("/oidc/users/me/clients", authMiddleware.WithAdminNotRequired().Add(), oc.listOwnAuthorizedClientsHandler) + group.GET("/oidc/users/:id/clients", authMiddleware.Add(), oc.listAuthorizedClientsHandler) } type OidcController struct { @@ -637,6 +640,64 @@ func (oc *OidcController) deviceAuthorizationHandler(c *gin.Context) { c.JSON(http.StatusOK, response) } +// listOwnAuthorizedClientsHandler godoc +// @Summary List authorized clients for current user +// @Description Get a paginated list of OIDC clients that the current user has authorized +// @Tags OIDC +// @Param page query int false "Page number, starting from 1" default(1) +// @Param limit query int false "Number of items per page" default(10) +// @Param sort_column query string false "Column to sort by" default("name") +// @Param sort_direction query string false "Sort direction (asc or desc)" default("asc") +// @Success 200 {object} dto.Paginated[dto.AuthorizedOidcClientDto] +// @Security BearerAuth +// @Router /api/oidc/users/me/clients [get] +func (oc *OidcController) listOwnAuthorizedClientsHandler(c *gin.Context) { + userID := c.GetString("userID") + oc.listAuthorizedClients(c, userID) +} + +// listAuthorizedClientsHandler godoc +// @Summary List authorized clients for a user +// @Description Get a paginated list of OIDC clients that a specific user has authorized +// @Tags OIDC +// @Param id path string true "User ID" +// @Param page query int false "Page number, starting from 1" default(1) +// @Param limit query int false "Number of items per page" default(10) +// @Param sort_column query string false "Column to sort by" default("name") +// @Param sort_direction query string false "Sort direction (asc or desc)" default("asc") +// @Success 200 {object} dto.Paginated[dto.AuthorizedOidcClientDto] +// @Security BearerAuth +// @Router /api/oidc/users/{id}/clients [get] +func (oc *OidcController) listAuthorizedClientsHandler(c *gin.Context) { + userID := c.Param("id") + oc.listAuthorizedClients(c, userID) +} + +func (oc *OidcController) listAuthorizedClients(c *gin.Context, userID string) { + var sortedPaginationRequest utils.SortedPaginationRequest + if err := c.ShouldBindQuery(&sortedPaginationRequest); err != nil { + _ = c.Error(err) + return + } + authorizedClients, pagination, err := oc.oidcService.ListAuthorizedClients(c.Request.Context(), userID, sortedPaginationRequest) + if err != nil { + _ = c.Error(err) + return + } + + // Map the clients to DTOs + var authorizedClientsDto []dto.AuthorizedOidcClientDto + if err := dto.MapStructList(authorizedClients, &authorizedClientsDto); err != nil { + _ = c.Error(err) + return + } + + c.JSON(http.StatusOK, dto.Paginated[dto.AuthorizedOidcClientDto]{ + Data: authorizedClientsDto, + Pagination: pagination, + }) +} + func (oc *OidcController) verifyDeviceCodeHandler(c *gin.Context) { userCode := c.Query("code") if userCode == "" { diff --git a/backend/internal/dto/oidc_dto.go b/backend/internal/dto/oidc_dto.go index e9304d86..9c0b62c7 100644 --- a/backend/internal/dto/oidc_dto.go +++ b/backend/internal/dto/oidc_dto.go @@ -125,3 +125,8 @@ type DeviceCodeInfoDto struct { AuthorizationRequired bool `json:"authorizationRequired"` Client OidcClientMetaDataDto `json:"client"` } + +type AuthorizedOidcClientDto struct { + Scope string `json:"scope"` + Client OidcClientMetaDataDto `json:"client"` +} diff --git a/backend/internal/service/oidc_service.go b/backend/internal/service/oidc_service.go index 115520fd..4211eb25 100644 --- a/backend/internal/service/oidc_service.go +++ b/backend/internal/service/oidc_service.go @@ -1243,6 +1243,20 @@ func (s *OidcService) GetAllowedGroupsCountOfClient(ctx context.Context, id stri return count, nil } +func (s *OidcService) ListAuthorizedClients(ctx context.Context, userID string, sortedPaginationRequest utils.SortedPaginationRequest) ([]model.UserAuthorizedOidcClient, utils.PaginationResponse, error) { + + query := s.db. + WithContext(ctx). + Model(&model.UserAuthorizedOidcClient{}). + Preload("Client"). + Where("user_id = ?", userID) + + var authorizedClients []model.UserAuthorizedOidcClient + response, err := utils.PaginateAndSort(sortedPaginationRequest, query, &authorizedClients) + + return authorizedClients, response, err +} + func (s *OidcService) createRefreshToken(ctx context.Context, clientID string, userID string, scope string, tx *gorm.DB) (string, error) { refreshToken, err := utils.GenerateRandomAlphanumericString(40) if err != nil {