9 Commits

Author SHA1 Message Date
Jan Eitzinger
c94f5918f3 Merge pull request #556 from ClusterCockpit/release/v1.5
Fix critical/severe issues in init, startup and shutdown
2026-06-07 07:31:02 +02:00
Jan Eitzinger
dd8c0e1b9f Merge pull request #555 from ClusterCockpit/release/v1.5
Update ReleaseNotes
2026-06-05 09:05:17 +02:00
Jan Eitzinger
879f730876 Merge pull request #554 from ClusterCockpit/release/v1.5
Fix bug that prevents WAL cleanup on shutdown
2026-06-05 08:42:38 +02:00
Jan Eitzinger
d74ae77c8e Merge pull request #553 from ClusterCockpit/release/v1.5
Release/v1.5
2026-06-04 20:33:22 +02:00
Jan Eitzinger
cdb4f06fea Merge pull request #552 from ClusterCockpit/fix/add-user-edit-api
Reintroduce user update api path
2026-06-04 08:27:23 +02:00
Christoph Kluge
1ebde74774 Adapt swagger definitions of user update endpoint 2026-06-02 17:58:15 +02:00
Christoph Kluge
40722d72f5 fix name in doc comment 2026-06-02 16:48:23 +02:00
Christoph Kluge
f4384668e5 fix name comment 2026-06-02 16:47:29 +02:00
Christoph Kluge
e06982db00 reintroduce user update api path 2026-06-02 16:34:46 +02:00
8 changed files with 232 additions and 131 deletions

View File

@@ -341,21 +341,6 @@ records, archives) at scale. All code changes must prioritize maximum throughput
and minimal latency. Avoid unnecessary allocations, prefer streaming over and minimal latency. Avoid unnecessary allocations, prefer streaming over
buffering, and be mindful of lock contention. When in doubt, benchmark. buffering, and be mindful of lock contention. When in doubt, benchmark.
### Commit Message Convention
Commits must use conventional commit prefixes so goreleaser can generate the
changelog automatically. Only commits with these prefixes appear in releases:
| Prefix | Changelog group |
|---------|------------------------|
| `feat:` | New Features |
| `fix:` | Bug fixes |
| `sec:` | Security updates |
| `docs:` | Documentation updates |
Scoped variants are also recognised, e.g. `feat(api):`, `fix(deps):`.
Commits without one of these prefixes are excluded from the changelog.
### Change Impact Analysis ### Change Impact Analysis
For any significant change, you MUST: For any significant change, you MUST:

View File

@@ -1,9 +1,10 @@
# NOTE # NOTE
While we do our best to keep the main branch in a usable state, there is no While we do our best to keep the master branch in a usable state, there is no guarantee the master branch works.
guarantee the main branch works. Please do not use it for production! Please do not use it for production!
Please have a look at the [Release Notes](https://github.com/ClusterCockpit/cc-backend/blob/main/ReleaseNotes.md) Please have a look at the [Release
Notes](https://github.com/ClusterCockpit/cc-backend/blob/master/ReleaseNotes.md)
for breaking changes! for breaking changes!
# ClusterCockpit REST and GraphQL API backend # ClusterCockpit REST and GraphQL API backend
@@ -40,7 +41,7 @@ For real-time integration with HPC systems, the backend can subscribe to
state updates, providing an alternative to REST API polling. state updates, providing an alternative to REST API polling.
Completed batch jobs are stored in a file-based job archive according to Completed batch jobs are stored in a file-based job archive according to
[this specification](https://github.com/ClusterCockpit/cc-specifications/tree/main/job-archive). [this specification](https://github.com/ClusterCockpit/cc-specifications/tree/master/job-archive).
The backend supports authentication via local accounts, an external LDAP The backend supports authentication via local accounts, an external LDAP
directory, and JWT tokens. Authorization for APIs is implemented with directory, and JWT tokens. Authorization for APIs is implemented with
[JWT](https://jwt.io/) tokens created with public/private key encryption. [JWT](https://jwt.io/) tokens created with public/private key encryption.
@@ -242,73 +243,73 @@ The effective configuration is logged at startup for verification.
## Project file structure ## Project file structure
- [`.github/`](https://github.com/ClusterCockpit/cc-backend/tree/main/.github) - [`.github/`](https://github.com/ClusterCockpit/cc-backend/tree/master/.github)
GitHub Actions workflows and dependabot configuration for CI/CD. GitHub Actions workflows and dependabot configuration for CI/CD.
- [`api/`](https://github.com/ClusterCockpit/cc-backend/tree/main/api) - [`api/`](https://github.com/ClusterCockpit/cc-backend/tree/master/api)
contains the API schema files for the REST and GraphQL APIs. The REST API is contains the API schema files for the REST and GraphQL APIs. The REST API is
documented in the OpenAPI 3.0 format in documented in the OpenAPI 3.0 format in
[./api/swagger.yaml](./api/swagger.yaml). The GraphQL schema is in [./api/swagger.yaml](./api/swagger.yaml). The GraphQL schema is in
[./api/schema.graphqls](./api/schema.graphqls). [./api/schema.graphqls](./api/schema.graphqls).
- [`cmd/cc-backend`](https://github.com/ClusterCockpit/cc-backend/tree/main/cmd/cc-backend) - [`cmd/cc-backend`](https://github.com/ClusterCockpit/cc-backend/tree/master/cmd/cc-backend)
contains the main application entry point and CLI implementation. contains the main application entry point and CLI implementation.
- [`configs/`](https://github.com/ClusterCockpit/cc-backend/tree/main/configs) - [`configs/`](https://github.com/ClusterCockpit/cc-backend/tree/master/configs)
contains documentation about configuration and command line options and required contains documentation about configuration and command line options and required
environment variables. Sample configuration files are provided. environment variables. Sample configuration files are provided.
- [`init/`](https://github.com/ClusterCockpit/cc-backend/tree/main/init) - [`init/`](https://github.com/ClusterCockpit/cc-backend/tree/master/init)
contains an example of setting up systemd for production use. contains an example of setting up systemd for production use.
- [`internal/`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal) - [`internal/`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal)
contains library source code that is not intended for use by others. contains library source code that is not intended for use by others.
- [`api`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/api) - [`api`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/api)
REST API handlers and NATS integration REST API handlers and NATS integration
- [`archiver`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/archiver) - [`archiver`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/archiver)
Job archiving functionality Job archiving functionality
- [`auth`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/auth) - [`auth`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/auth)
Authentication (local, LDAP, OIDC) and JWT token handling Authentication (local, LDAP, OIDC) and JWT token handling
- [`config`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/config) - [`config`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/config)
Configuration management and validation Configuration management and validation
- [`graph`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/graph) - [`graph`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/graph)
GraphQL schema and resolvers GraphQL schema and resolvers
- [`importer`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/importer) - [`importer`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/importer)
Job data import and database initialization Job data import and database initialization
- [`metricdispatch`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/metricdispatch) - [`metricdispatch`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/metricdispatch)
Dispatches metric data loading to appropriate backends Dispatches metric data loading to appropriate backends
- [`repository`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/repository) - [`repository`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/repository)
Database repository layer for jobs and metadata Database repository layer for jobs and metadata
- [`routerConfig`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/routerConfig) - [`routerConfig`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/routerConfig)
HTTP router configuration and middleware HTTP router configuration and middleware
- [`tagger`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/tagger) - [`tagger`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/tagger)
Job classification and application detection Job classification and application detection
- [`taskmanager`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/taskmanager) - [`taskmanager`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/taskmanager)
Background task management and scheduled jobs Background task management and scheduled jobs
- [`metricstoreclient`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/metricstoreclient) - [`metricstoreclient`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/metricstoreclient)
Client for cc-metric-store queries Client for cc-metric-store queries
- [`pkg/`](https://github.com/ClusterCockpit/cc-backend/tree/main/pkg) - [`pkg/`](https://github.com/ClusterCockpit/cc-backend/tree/master/pkg)
contains Go packages that can be used by other projects. contains Go packages that can be used by other projects.
- [`archive`](https://github.com/ClusterCockpit/cc-backend/tree/main/pkg/archive) - [`archive`](https://github.com/ClusterCockpit/cc-backend/tree/master/pkg/archive)
Job archive backend implementations (filesystem, S3, SQLite) Job archive backend implementations (filesystem, S3, SQLite)
- [`metricstore`](https://github.com/ClusterCockpit/cc-backend/tree/main/pkg/metricstore) - [`metricstore`](https://github.com/ClusterCockpit/cc-backend/tree/master/pkg/metricstore)
In-memory metric data store with checkpointing and metric loading In-memory metric data store with checkpointing and metric loading
- [`tools/`](https://github.com/ClusterCockpit/cc-backend/tree/main/tools) - [`tools/`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools)
Additional command line helper tools. Additional command line helper tools.
- [`archive-manager`](https://github.com/ClusterCockpit/cc-backend/tree/main/tools/archive-manager) - [`archive-manager`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/archive-manager)
Commands for getting infos about an existing job archive, importing jobs Commands for getting infos about an existing job archive, importing jobs
between archive backends, and converting archives between JSON and Parquet formats. between archive backends, and converting archives between JSON and Parquet formats.
- [`archive-migration`](https://github.com/ClusterCockpit/cc-backend/tree/main/tools/archive-migration) - [`archive-migration`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/archive-migration)
Tool for migrating job archives between formats. Tool for migrating job archives between formats.
- [`convert-pem-pubkey`](https://github.com/ClusterCockpit/cc-backend/tree/main/tools/convert-pem-pubkey) - [`convert-pem-pubkey`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/convert-pem-pubkey)
Tool to convert external pubkey for use in `cc-backend`. Tool to convert external pubkey for use in `cc-backend`.
- [`gen-keypair`](https://github.com/ClusterCockpit/cc-backend/tree/main/tools/gen-keypair) - [`gen-keypair`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/gen-keypair)
contains a small application to generate a compatible JWT keypair. You find contains a small application to generate a compatible JWT keypair. You find
documentation on how to use it documentation on how to use it
[here](https://github.com/ClusterCockpit/cc-backend/blob/main/docs/JWT-Handling.md). [here](https://github.com/ClusterCockpit/cc-backend/blob/master/docs/JWT-Handling.md).
- [`web/`](https://github.com/ClusterCockpit/cc-backend/tree/main/web) - [`web/`](https://github.com/ClusterCockpit/cc-backend/tree/master/web)
Server-side templates and frontend-related files: Server-side templates and frontend-related files:
- [`frontend`](https://github.com/ClusterCockpit/cc-backend/tree/main/web/frontend) - [`frontend`](https://github.com/ClusterCockpit/cc-backend/tree/master/web/frontend)
Svelte components and static assets for the frontend UI Svelte components and static assets for the frontend UI
- [`templates`](https://github.com/ClusterCockpit/cc-backend/tree/main/web/templates) - [`templates`](https://github.com/ClusterCockpit/cc-backend/tree/master/web/templates)
Server-side Go templates, including monitoring views Server-side Go templates, including monitoring views
- [`gqlgen.yml`](https://github.com/ClusterCockpit/cc-backend/blob/main/gqlgen.yml) - [`gqlgen.yml`](https://github.com/ClusterCockpit/cc-backend/blob/master/gqlgen.yml)
Configures the behaviour and generation of Configures the behaviour and generation of
[gqlgen](https://github.com/99designs/gqlgen). [gqlgen](https://github.com/99designs/gqlgen).
- [`startDemo.sh`](https://github.com/ClusterCockpit/cc-backend/blob/main/startDemo.sh) - [`startDemo.sh`](https://github.com/ClusterCockpit/cc-backend/blob/master/startDemo.sh)
is a shell script that sets up demo data, and builds and starts `cc-backend`. is a shell script that sets up demo data, and builds and starts `cc-backend`.

View File

@@ -1016,8 +1016,11 @@
"/api/user/{id}": { "/api/user/{id}": {
"post": { "post": {
"description": "Allows admins to add/remove roles and projects for a user", "description": "Allows admins to add/remove roles and projects for a user",
"consumes": [
"application/json"
],
"produces": [ "produces": [
"text/plain" "application/json"
], ],
"tags": [ "tags": [
"User" "User"
@@ -1032,35 +1035,26 @@
"required": true "required": true
}, },
{ {
"type": "string", "description": "Single Field Changes",
"description": "Role to add", "name": "request",
"name": "add-role", "in": "body",
"in": "formData" "required": true,
}, "schema": {
{ "$ref": "#/definitions/api.UpdateUserAPIRequest"
"type": "string", }
"description": "Role to remove",
"name": "remove-role",
"in": "formData"
},
{
"type": "string",
"description": "Project to add",
"name": "add-project",
"in": "formData"
},
{
"type": "string",
"description": "Project to remove",
"name": "remove-project",
"in": "formData"
} }
], ],
"responses": { "responses": {
"200": { "200": {
"description": "Success message", "description": "OK",
"schema": { "schema": {
"type": "string" "$ref": "#/definitions/api.DefaultAPIResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.ErrorResponse"
} }
}, },
"403": { "403": {
@@ -1934,6 +1928,31 @@
} }
} }
}, },
"api.UpdateUserAPIRequest": {
"type": "object",
"properties": {
"add-role": {
"description": "Role to add to user $ID",
"type": "string",
"example": "user"
},
"remove-role": {
"description": "Role to remove from user $ID",
"type": "string",
"example": "user"
},
"add-project": {
"description": "Project to add to user $ID managed array",
"type": "string",
"example": "abcd100"
},
"remove-project": {
"description": "Project to remove from user $ID managed array",
"type": "string",
"example": "abcd100"
}
}
},
"api.DefaultAPIResponse": { "api.DefaultAPIResponse": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -31,6 +31,25 @@ definitions:
example: Debug example: Debug
type: string type: string
type: object type: object
api.UpdateUserAPIRequest:
properties:
add-project:
description: Project to add to user $ID managed array
example: abcd100
type: string
add-role:
description: Role to add to user $ID
example: user
type: string
remove-project:
description: Project to remove from user $ID managed array
example: abcd100
type: string
remove-role:
description: Role to remove from user $ID
example: user
type: string
type: object
api.DefaultAPIResponse: api.DefaultAPIResponse:
properties: properties:
msg: msg:
@@ -1430,6 +1449,8 @@ paths:
- Nodestates - Nodestates
/api/user/{id}: /api/user/{id}:
post: post:
consumes:
- application/json
description: Allows admins to add/remove roles and projects for a user description: Allows admins to add/remove roles and projects for a user
parameters: parameters:
- description: Username - description: Username
@@ -1437,29 +1458,23 @@ paths:
name: id name: id
required: true required: true
type: string type: string
- description: Role to add - description: Single Field Changes
in: formData in: body
name: add-role name: request
type: string required: true
- description: Role to remove schema:
in: formData $ref: '#/definitions/api.UpdateUserAPIRequest'
name: remove-role
type: string
- description: Project to add
in: formData
name: add-project
type: string
- description: Project to remove
in: formData
name: remove-project
type: string
produces: produces:
- text/plain - application/json
responses: responses:
"200": "200":
description: Success message description: OK
schema: schema:
type: string $ref: '#/definitions/api.DefaultAPIResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/api.ErrorResponse'
"403": "403":
description: Forbidden description: Forbidden
schema: schema:

View File

@@ -1023,8 +1023,11 @@ const docTemplate = `{
"/api/user/{id}": { "/api/user/{id}": {
"post": { "post": {
"description": "Allows admins to add/remove roles and projects for a user", "description": "Allows admins to add/remove roles and projects for a user",
"consumes": [
"application/json"
],
"produces": [ "produces": [
"text/plain" "application/json"
], ],
"tags": [ "tags": [
"User" "User"
@@ -1039,35 +1042,26 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "description": "Single Field Changes",
"description": "Role to add", "name": "request",
"name": "add-role", "in": "body",
"in": "formData" "required": true,
}, "schema": {
{ "$ref": "#/definitions/api.UpdateUserAPIRequest"
"type": "string", }
"description": "Role to remove",
"name": "remove-role",
"in": "formData"
},
{
"type": "string",
"description": "Project to add",
"name": "add-project",
"in": "formData"
},
{
"type": "string",
"description": "Project to remove",
"name": "remove-project",
"in": "formData"
} }
], ],
"responses": { "responses": {
"200": { "200": {
"description": "Success message", "description": "OK",
"schema": { "schema": {
"type": "string" "$ref": "#/definitions/api.DefaultAPIResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.ErrorResponse"
} }
}, },
"403": { "403": {
@@ -1941,6 +1935,31 @@ const docTemplate = `{
} }
} }
}, },
"api.UpdateUserAPIRequest": {
"type": "object",
"properties": {
"add-role": {
"description": "Role to add to user $ID",
"type": "string",
"example": "user"
},
"remove-role": {
"description": "Role to remove from user $ID",
"type": "string",
"example": "user"
},
"add-project": {
"description": "Project to add to user $ID managed array",
"type": "string",
"example": "abcd100"
},
"remove-project": {
"description": "Project to remove from user $ID managed array",
"type": "string",
"example": "abcd100"
}
}
},
"api.DefaultAPIResponse": { "api.DefaultAPIResponse": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -79,6 +79,8 @@ func (api *RestAPI) MountAPIRoutes(r chi.Router) {
// REST API Uses TokenAuth // REST API Uses TokenAuth
// User List // User List
r.Get("/users/", api.getUsers) r.Get("/users/", api.getUsers)
// User Edit
r.Post("/user/{id}", api.updateUserByRequest)
// Cluster List // Cluster List
r.Get("/clusters/", api.getClusters) r.Get("/clusters/", api.getClusters)
// Slurm node state // Slurm node state
@@ -152,7 +154,7 @@ func (api *RestAPI) MountConfigAPIRoutes(r chi.Router) {
r.Put("/config/users/", api.createUser) r.Put("/config/users/", api.createUser)
r.Get("/config/users/", api.getUsers) r.Get("/config/users/", api.getUsers)
r.Delete("/config/users/", api.deleteUser) r.Delete("/config/users/", api.deleteUser)
r.Post("/config/user/{id}", api.updateUser) r.Post("/config/user/{id}", api.updateUserByForm)
r.Post("/config/notice/", api.editNotice) r.Post("/config/notice/", api.editNotice)
r.Get("/config/taggers/", api.getTaggers) r.Get("/config/taggers/", api.getTaggers)
r.Post("/config/taggers/run/", api.runTagger) r.Post("/config/taggers/run/", api.runTagger)

View File

@@ -24,6 +24,14 @@ type APIReturnedUser struct {
Projects []string `json:"projects"` Projects []string `json:"projects"`
} }
// UpdateUserAPIRequest model
type UpdateUserAPIRequest struct {
NewRole string `json:"add-role" example:"user"` // Role to add to user $ID
DelRole string `json:"remove-role" example:"user"` // Role to remove from user $ID
NewProj string `json:"add-project" example:"abcd100"` // Project to add to user $ID managed array
DelProj string `json:"remove-project" example:"abcd100"` // Project to remove from user $ID managed array
}
// getUsers godoc // getUsers godoc
// @summary Returns a list of users // @summary Returns a list of users
// @tags User // @tags User
@@ -58,22 +66,74 @@ func (api *RestAPI) getUsers(rw http.ResponseWriter, r *http.Request) {
} }
} }
// updateUser godoc // updateUserByRequest godoc
// @summary Update user roles and projects // @summary Update user roles and projects
// @tags User // @tags User
// @description Allows admins to add/remove roles and projects for a user // @description Allows admins to add/remove roles and projects for a user
// @produce plain // @accept json
// @param id path string true "Username" // @produce json
// @param add-role formData string false "Role to add" // @param id path string true "Username"
// @param remove-role formData string false "Role to remove" // @param request body api.UpdateUserAPIRequest true "Single Field Changes"
// @param add-project formData string false "Project to add"
// @param remove-project formData string false "Project to remove"
// @success 200 {string} string "Success message" // @success 200 {string} string "Success message"
// @failure 403 {object} api.ErrorResponse "Forbidden" // @failure 403 {object} api.ErrorResponse "Forbidden"
// @failure 422 {object} api.ErrorResponse "Unprocessable Entity" // @failure 422 {object} api.ErrorResponse "Unprocessable Entity"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /api/user/{id} [post] // @router /api/user/{id} [post]
func (api *RestAPI) updateUser(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) updateUserByRequest(rw http.ResponseWriter, r *http.Request) {
if user := repository.GetUserFromContext(r.Context()); !user.HasRole(schema.RoleAdmin) {
handleError(fmt.Errorf("only admins are allowed to update a user"), http.StatusForbidden, rw)
return
}
// Get Values
var req UpdateUserAPIRequest
if err := decode(r.Body, &req); err != nil {
handleError(fmt.Errorf("decoding request failed: %w", err), http.StatusBadRequest, rw)
return
}
rw.Header().Set("Content-Type", "application/json")
// Handle role updates
if req.NewRole != "" {
if err := repository.GetUserRepository().AddRole(r.Context(), chi.URLParam(r, "id"), req.NewRole); err != nil {
handleError(fmt.Errorf("adding role failed: %w", err), http.StatusUnprocessableEntity, rw)
return
}
if err := json.NewEncoder(rw).Encode(DefaultAPIResponse{Message: fmt.Sprintf("Add Role Success for user %s", chi.URLParam(r, "id"))}); err != nil {
cclog.Errorf("Failed to encode response: %v", err)
}
} else if req.DelRole != "" {
if err := repository.GetUserRepository().RemoveRole(r.Context(), chi.URLParam(r, "id"), req.DelRole); err != nil {
handleError(fmt.Errorf("removing role failed: %w", err), http.StatusUnprocessableEntity, rw)
return
}
if err := json.NewEncoder(rw).Encode(DefaultAPIResponse{Message: fmt.Sprintf("Remove Role Success for user %s", chi.URLParam(r, "id"))}); err != nil {
cclog.Errorf("Failed to encode response: %v", err)
}
} else if req.NewProj != "" {
if err := repository.GetUserRepository().AddProject(r.Context(), chi.URLParam(r, "id"), req.NewProj); err != nil {
handleError(fmt.Errorf("adding project failed: %w", err), http.StatusUnprocessableEntity, rw)
return
}
if err := json.NewEncoder(rw).Encode(DefaultAPIResponse{Message: fmt.Sprintf("Add Project Success for user %s", chi.URLParam(r, "id"))}); err != nil {
cclog.Errorf("Failed to encode response: %v", err)
}
} else if req.DelProj != "" {
if err := repository.GetUserRepository().RemoveProject(r.Context(), chi.URLParam(r, "id"), req.DelProj); err != nil {
handleError(fmt.Errorf("removing project failed: %w", err), http.StatusUnprocessableEntity, rw)
return
}
if err := json.NewEncoder(rw).Encode(DefaultAPIResponse{Message: fmt.Sprintf("Remove Project Success for user %s", chi.URLParam(r, "id"))}); err != nil {
cclog.Errorf("Failed to encode response: %v", err)
}
} else {
handleError(fmt.Errorf("no operation specified: must provide add-role, remove-role, add-project, or remove-project"), http.StatusBadRequest, rw)
}
}
func (api *RestAPI) updateUserByForm(rw http.ResponseWriter, r *http.Request) {
// SecuredCheck() only worked with TokenAuth: Removed // SecuredCheck() only worked with TokenAuth: Removed
if user := repository.GetUserFromContext(r.Context()); !user.HasRole(schema.RoleAdmin) { if user := repository.GetUserFromContext(r.Context()); !user.HasRole(schema.RoleAdmin) {

View File

@@ -650,7 +650,7 @@ func securedCheck(user *schema.User, r *http.Request) error {
} }
// If SplitHostPort fails, IPAddress is already just a host (no port) // If SplitHostPort fails, IPAddress is already just a host (no port)
// If nothing declared in config: Continue // If nothing declared in config: Continue // FIXME: Allow All If Not Declared?
if len(config.Keys.APIAllowedIPs) == 0 { if len(config.Keys.APIAllowedIPs) == 0 {
return nil return nil
} }