mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-02-04 15:39:45 +00:00
Add initial REST endpoints
This commit is contained in:
@@ -63,6 +63,12 @@ func NewOidcController(group *gin.RouterGroup, authMiddleware *middleware.AuthMi
|
|||||||
|
|
||||||
group.GET("/oidc/users/me/clients", authMiddleware.WithAdminNotRequired().Add(), oc.listOwnAccessibleClientsHandler)
|
group.GET("/oidc/users/me/clients", authMiddleware.WithAdminNotRequired().Add(), oc.listOwnAccessibleClientsHandler)
|
||||||
|
|
||||||
|
// OIDC API (Resource Server) routes
|
||||||
|
group.POST("/oidc/apis", authMiddleware.Add(), oc.createAPIHandler)
|
||||||
|
group.GET("/oidc/apis", authMiddleware.Add(), oc.listAPIsHandler)
|
||||||
|
group.GET("/oidc/apis/:id", authMiddleware.Add(), oc.getAPIHandler)
|
||||||
|
group.POST("/oidc/apis/:id", authMiddleware.Add(), oc.updateAPIHandler)
|
||||||
|
group.DELETE("/oidc/apis/:id", authMiddleware.Add(), oc.deleteAPIHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
type OidcController struct {
|
type OidcController struct {
|
||||||
@@ -845,3 +851,151 @@ func (oc *OidcController) getClientPreviewHandler(c *gin.Context) {
|
|||||||
|
|
||||||
c.JSON(http.StatusOK, preview)
|
c.JSON(http.StatusOK, preview)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createAPIHandler godoc
|
||||||
|
// @Summary Create OIDC API
|
||||||
|
// @Description Create a new OIDC API (resource server)
|
||||||
|
// @Tags OIDC
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param api body dto.OidcAPICreateDto true "API information"
|
||||||
|
// @Success 201 {object} dto.OidcAPIDto "Created API"
|
||||||
|
// @Router /api/oidc/apis [post]
|
||||||
|
func (oc *OidcController) createAPIHandler(c *gin.Context) {
|
||||||
|
var input dto.OidcAPICreateDto
|
||||||
|
err := c.ShouldBindJSON(&input)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
api, err := oc.oidcService.CreateAPI(c.Request.Context(), input)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiDto dto.OidcAPIDto
|
||||||
|
err = dto.MapStruct(api, &apiDto)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, apiDto)
|
||||||
|
}
|
||||||
|
|
||||||
|
// listAPIsHandler godoc
|
||||||
|
// @Summary List OIDC APIs
|
||||||
|
// @Description Get a paginated list of OIDC APIs (resource servers)
|
||||||
|
// @Tags OIDC
|
||||||
|
// @Param search query string false "Search term to filter APIs by name"
|
||||||
|
// @Param pagination[page] query int false "Page number for pagination" default(1)
|
||||||
|
// @Param pagination[limit] query int false "Number of items per page" default(20)
|
||||||
|
// @Param sort[column] query string false "Column to sort by"
|
||||||
|
// @Param sort[direction] query string false "Sort direction (asc or desc)" default("asc")
|
||||||
|
// @Success 200 {object} dto.Paginated[dto.OidcAPIDto]
|
||||||
|
// @Router /api/oidc/apis [get]
|
||||||
|
func (oc *OidcController) listAPIsHandler(c *gin.Context) {
|
||||||
|
searchTerm := c.Query("search")
|
||||||
|
listRequestOptions := utils.ParseListRequestOptions(c)
|
||||||
|
|
||||||
|
apis, pagination, err := oc.oidcService.ListAPIs(c.Request.Context(), searchTerm, listRequestOptions)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apisDto := make([]dto.OidcAPIDto, len(apis))
|
||||||
|
for i, api := range apis {
|
||||||
|
var apiDto dto.OidcAPIDto
|
||||||
|
err = dto.MapStruct(api, &apiDto)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apisDto[i] = apiDto
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, dto.Paginated[dto.OidcAPIDto]{
|
||||||
|
Data: apisDto,
|
||||||
|
Pagination: pagination,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAPIHandler godoc
|
||||||
|
// @Summary Get OIDC API
|
||||||
|
// @Description Get detailed information about an OIDC API (resource server)
|
||||||
|
// @Tags OIDC
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "API ID"
|
||||||
|
// @Success 200 {object} dto.OidcAPIDto "API information"
|
||||||
|
// @Router /api/oidc/apis/{id} [get]
|
||||||
|
func (oc *OidcController) getAPIHandler(c *gin.Context) {
|
||||||
|
apiID := c.Param("id")
|
||||||
|
api, err := oc.oidcService.GetAPI(c.Request.Context(), apiID)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiDto dto.OidcAPIDto
|
||||||
|
err = dto.MapStruct(api, &apiDto)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, apiDto)
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateAPIHandler godoc
|
||||||
|
// @Summary Update OIDC API
|
||||||
|
// @Description Update an existing OIDC API (resource server)
|
||||||
|
// @Tags OIDC
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "API ID"
|
||||||
|
// @Param api body dto.OidcAPIUpdateDto true "API information"
|
||||||
|
// @Success 200 {object} dto.OidcAPIDto "Updated API"
|
||||||
|
// @Router /api/oidc/apis/{id} [post]
|
||||||
|
func (oc *OidcController) updateAPIHandler(c *gin.Context) {
|
||||||
|
var input dto.OidcAPIUpdateDto
|
||||||
|
err := c.ShouldBindJSON(&input)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
api, err := oc.oidcService.UpdateAPI(c.Request.Context(), c.Param("id"), input)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiDto dto.OidcAPIDto
|
||||||
|
err = dto.MapStruct(api, &apiDto)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, apiDto)
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteAPIHandler godoc
|
||||||
|
// @Summary Delete OIDC API
|
||||||
|
// @Description Delete an OIDC API (resource server) by ID
|
||||||
|
// @Tags OIDC
|
||||||
|
// @Param id path string true "API ID"
|
||||||
|
// @Success 204 "No Content"
|
||||||
|
// @Router /api/oidc/apis/{id} [delete]
|
||||||
|
func (oc *OidcController) deleteAPIHandler(c *gin.Context) {
|
||||||
|
err := oc.oidcService.DeleteAPI(c.Request.Context(), c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|||||||
@@ -178,3 +178,26 @@ type AccessibleOidcClientDto struct {
|
|||||||
OidcClientMetaDataDto
|
OidcClientMetaDataDto
|
||||||
LastUsedAt *datatype.DateTime `json:"lastUsedAt"`
|
LastUsedAt *datatype.DateTime `json:"lastUsedAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OidcAPIDto struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
|
Permissions []OidcAPIPermissionDto `json:"permissions"`
|
||||||
|
CreatedAt datatype.DateTime `json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OidcAPIPermissionDto struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OidcAPICreateDto struct {
|
||||||
|
Name string `json:"name" binding:"required,max=100" unorm:"nfc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OidcAPIUpdateDto struct {
|
||||||
|
Name string `json:"name" binding:"required,max=100" unorm:"nfc"`
|
||||||
|
Identifier string `json:"identifier" binding:"omitempty,url,max=255"`
|
||||||
|
Permissions []OidcAPIPermissionDto `json:"permissions" binding:"omitempty,dive"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -151,3 +151,33 @@ type OidcDeviceCode struct {
|
|||||||
ClientID string
|
ClientID string
|
||||||
Client OidcClient
|
Client OidcClient
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OidcAPI struct {
|
||||||
|
Base
|
||||||
|
|
||||||
|
Name string `sortable:"true"`
|
||||||
|
Identifier string `sortable:"true"`
|
||||||
|
Data OidcAPIData `gorm:"type:text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a OidcAPI) DefaultIdentifier() string {
|
||||||
|
return "api://" + a.Identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:recvcheck
|
||||||
|
type OidcAPIData struct {
|
||||||
|
Permissions []OidcAPIPermission `json:"permissions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OidcAPIData) Scan(value any) error {
|
||||||
|
return utils.UnmarshalJSONFromDatabase(p, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p OidcAPIData) Value() (driver.Value, error) {
|
||||||
|
return json.Marshal(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
type OidcAPIPermission struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -1433,7 +1433,6 @@ func (s *OidcService) GetAllowedGroupsCountOfClient(ctx context.Context, id stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *OidcService) ListAuthorizedClients(ctx context.Context, userID string, listRequestOptions utils.ListRequestOptions) ([]model.UserAuthorizedOidcClient, utils.PaginationResponse, error) {
|
func (s *OidcService) ListAuthorizedClients(ctx context.Context, userID string, listRequestOptions utils.ListRequestOptions) ([]model.UserAuthorizedOidcClient, utils.PaginationResponse, error) {
|
||||||
|
|
||||||
query := s.db.
|
query := s.db.
|
||||||
WithContext(ctx).
|
WithContext(ctx).
|
||||||
Model(&model.UserAuthorizedOidcClient{}).
|
Model(&model.UserAuthorizedOidcClient{}).
|
||||||
@@ -2123,3 +2122,106 @@ func (s *OidcService) updateClientLogoType(ctx context.Context, clientID string,
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *OidcService) CreateAPI(ctx context.Context, input dto.OidcAPICreateDto) (model.OidcAPI, error) {
|
||||||
|
api := model.OidcAPI{
|
||||||
|
Name: input.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.db.
|
||||||
|
WithContext(ctx).
|
||||||
|
Create(&api).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return model.OidcAPI{}, fmt.Errorf("failed to create API in database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return api, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OidcService) GetAPI(ctx context.Context, id string) (model.OidcAPI, error) {
|
||||||
|
var api model.OidcAPI
|
||||||
|
err := s.db.
|
||||||
|
WithContext(ctx).
|
||||||
|
First(&api, "id = ?", id).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return model.OidcAPI{}, fmt.Errorf("failed to get API from database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return api, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OidcService) ListAPIs(ctx context.Context, searchTerm string, listRequestOptions utils.ListRequestOptions) ([]model.OidcAPI, utils.PaginationResponse, error) {
|
||||||
|
var apis []model.OidcAPI
|
||||||
|
|
||||||
|
query := s.db.
|
||||||
|
WithContext(ctx).
|
||||||
|
Model(&model.OidcAPI{})
|
||||||
|
|
||||||
|
if searchTerm != "" {
|
||||||
|
query = query.Where("id = ? OR name = ? OR identifier = ?", searchTerm, searchTerm, searchTerm)
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := utils.PaginateFilterAndSort(listRequestOptions, query, &apis)
|
||||||
|
return apis, response, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OidcService) UpdateAPI(ctx context.Context, id string, input dto.OidcAPIUpdateDto) (model.OidcAPI, error) {
|
||||||
|
tx := s.db.Begin()
|
||||||
|
defer func() {
|
||||||
|
tx.Rollback()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var api model.OidcAPI
|
||||||
|
err := tx.
|
||||||
|
WithContext(ctx).
|
||||||
|
First(&api, "id = ?", id).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return model.OidcAPI{}, fmt.Errorf("failed to get API from database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
api.Name = input.Name
|
||||||
|
api.Identifier = input.Identifier
|
||||||
|
|
||||||
|
// Convert permissions from DTO to model
|
||||||
|
api.Data.Permissions = make([]model.OidcAPIPermission, len(input.Permissions))
|
||||||
|
for i, p := range input.Permissions {
|
||||||
|
api.Data.Permissions[i] = model.OidcAPIPermission{
|
||||||
|
Name: p.Name,
|
||||||
|
Description: p.Description,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.
|
||||||
|
WithContext(ctx).
|
||||||
|
Save(&api).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return model.OidcAPI{}, fmt.Errorf("failed to save API in database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Commit().Error
|
||||||
|
if err != nil {
|
||||||
|
return model.OidcAPI{}, fmt.Errorf("failed to commit transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return api, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OidcService) DeleteAPI(ctx context.Context, id string) error {
|
||||||
|
result := s.db.
|
||||||
|
WithContext(ctx).
|
||||||
|
Delete(&model.OidcAPI{}, "id = ?", id)
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE oidc_apis;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
CREATE TABLE oidc_apis
|
||||||
|
(
|
||||||
|
id UUID PRIMARY KEY NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
identifier TEXT NOT NULL,
|
||||||
|
data JSONB NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX idx_oidc_apis_identifier_key ON oidc_apis(identifier);
|
||||||
|
CREATE INDEX idx_oidc_apis_name_key ON oidc_apis(name);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE oidc_apis;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
CREATE TABLE oidc_apis
|
||||||
|
(
|
||||||
|
id UUID PRIMARY KEY NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
identifier TEXT NOT NULL,
|
||||||
|
data TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX idx_oidc_apis_identifier_key ON oidc_apis(identifier);
|
||||||
|
CREATE INDEX idx_oidc_apis_name_key ON oidc_apis(name);
|
||||||
Reference in New Issue
Block a user