diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 5f3c1e4effb..f733819f5e9 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -493,7 +493,9 @@ jobs:
--tag cmd-api-server \
--tag "ghcr.io/hyperledger/cactus-cmd-api-server:$(date +"%Y-%m-%dT%H-%M-%S" --utc)-dev-$(git rev-parse --short HEAD)"
- - if: ${{ env.RUN_TRIVY_SCAN == 'true' }}
+ - if: |
+ ${{ env.RUN_TRIVY_SCAN == 'true'
+ && (env.DAY_OF_WEEK == '4' || env.DAY_OF_WEEK == '7') }}
name: Run Trivy vulnerability scan for cmd-api-server
uses: aquasecurity/trivy-action@0.19.0
with:
diff --git a/packages/cactus-cmd-api-server/package.json b/packages/cactus-cmd-api-server/package.json
index bc904396d85..3b283060b9d 100644
--- a/packages/cactus-cmd-api-server/package.json
+++ b/packages/cactus-cmd-api-server/package.json
@@ -89,6 +89,7 @@
"fastify": "4.28.1",
"fs-extra": "11.2.0",
"google-protobuf": "3.21.4",
+ "http-status-codes": "2.1.3",
"jose": "4.15.5",
"json-stable-stringify": "1.0.2",
"lmify": "0.3.0",
@@ -134,7 +135,6 @@
"google-protobuf": "3.21.4",
"grpc-tools": "1.12.4",
"grpc_tools_node_protoc_ts": "5.3.3",
- "http-status-codes": "2.1.4",
"protobufjs": "7.4.0",
"tsx": "4.16.2"
},
diff --git a/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/.openapi-generator/FILES b/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/.openapi-generator/FILES
index 8c0f008a05f..3b1e0f7513b 100644
--- a/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/.openapi-generator/FILES
+++ b/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/.openapi-generator/FILES
@@ -6,6 +6,7 @@ client.go
configuration.go
go.mod
go.sum
+model_cmd_api_server_endpoint_error_response.go
model_health_check_response.go
model_memory_usage.go
model_watch_healthcheck_v1.go
diff --git a/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/README.md b/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/README.md
index 548fe6c56c8..2eff5f1a8e8 100644
--- a/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/README.md
+++ b/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/README.md
@@ -84,6 +84,7 @@ Class | Method | HTTP request | Description
## Documentation For Models
+ - [CmdApiServerEndpointErrorResponse](docs/CmdApiServerEndpointErrorResponse.md)
- [HealthCheckResponse](docs/HealthCheckResponse.md)
- [MemoryUsage](docs/MemoryUsage.md)
- [WatchHealthcheckV1](docs/WatchHealthcheckV1.md)
@@ -91,7 +92,18 @@ Class | Method | HTTP request | Description
## Documentation For Authorization
-Endpoints do not require authorization.
+
+Authentication schemes defined for the API:
+### bearerTokenAuth
+
+- **Type**: HTTP Bearer token authentication
+
+Example
+
+```golang
+auth := context.WithValue(context.Background(), sw.ContextAccessToken, "BEARER_TOKEN_STRING")
+r, err := client.Service.Operation(auth, args)
+```
## Documentation for Utility Methods
diff --git a/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/api/openapi.yaml b/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/api/openapi.yaml
index b25ac93cc3c..b3391e5b6c4 100644
--- a/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/api/openapi.yaml
+++ b/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/api/openapi.yaml
@@ -8,6 +8,11 @@ info:
version: 2.0.0
servers:
- url: /
+security:
+- bearerTokenAuth:
+ - read:health
+ - read:metrics
+ - read:spec
paths:
/api/v1/api-server/healthcheck:
get:
@@ -21,6 +26,21 @@ paths:
schema:
$ref: '#/components/schemas/HealthCheckResponse'
description: OK
+ "401":
+ content:
+ '*/*':
+ schema:
+ $ref: '#/components/schemas/CmdApiServerEndpointErrorResponse'
+ description: Unauthorized - Invalid token
+ "403":
+ content:
+ '*/*':
+ schema:
+ $ref: '#/components/schemas/CmdApiServerEndpointErrorResponse'
+ description: Forbidden - Valid token but missing correct scope
+ security:
+ - bearerTokenAuth:
+ - read:health
summary: Can be used to verify liveness of an API server instance
x-hyperledger-cacti:
http:
@@ -37,6 +57,21 @@ paths:
schema:
$ref: '#/components/schemas/PrometheusExporterMetricsResponse'
description: OK
+ "401":
+ content:
+ '*/*':
+ schema:
+ $ref: '#/components/schemas/CmdApiServerEndpointErrorResponse'
+ description: Unauthorized - Invalid token
+ "403":
+ content:
+ '*/*':
+ schema:
+ $ref: '#/components/schemas/CmdApiServerEndpointErrorResponse'
+ description: Forbidden - Valid token but missing correct scope
+ security:
+ - bearerTokenAuth:
+ - read:metrics
summary: Get the Prometheus Metrics
x-hyperledger-cacti:
http:
@@ -54,6 +89,21 @@ paths:
schema:
$ref: '#/components/schemas/GetOpenApiSpecV1EndpointResponse'
description: OK
+ "401":
+ content:
+ '*/*':
+ schema:
+ $ref: '#/components/schemas/CmdApiServerEndpointErrorResponse'
+ description: Unauthorized - Invalid token
+ "403":
+ content:
+ '*/*':
+ schema:
+ $ref: '#/components/schemas/CmdApiServerEndpointErrorResponse'
+ description: Forbidden - Valid token but missing correct scope
+ security:
+ - bearerTokenAuth:
+ - read:spec
x-hyperledger-cacti:
http:
verbLowerCase: get
@@ -127,3 +177,14 @@ components:
GetOpenApiSpecV1EndpointResponse:
nullable: false
type: string
+ CmdApiServerEndpointErrorResponse:
+ properties:
+ message:
+ example: |
+ Forbidden - Valid token but missing correct scope
+ type: string
+ securitySchemes:
+ bearerTokenAuth:
+ bearerFormat: JSON Web Tokens
+ scheme: bearer
+ type: http
diff --git a/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/api_default.go b/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/api_default.go
index 27da765c0c8..58c64eaeed9 100644
--- a/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/api_default.go
+++ b/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/api_default.go
@@ -77,7 +77,7 @@ func (a *DefaultApiService) GetHealthCheckV1Execute(r ApiGetHealthCheckV1Request
}
// to determine the Accept header
- localVarHTTPHeaderAccepts := []string{"application/json"}
+ localVarHTTPHeaderAccepts := []string{"application/json", "*/*"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
@@ -106,6 +106,27 @@ func (a *DefaultApiService) GetHealthCheckV1Execute(r ApiGetHealthCheckV1Request
body: localVarBody,
error: localVarHTTPResponse.Status,
}
+ if localVarHTTPResponse.StatusCode == 401 {
+ var v CmdApiServerEndpointErrorResponse
+ err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+ if err != nil {
+ newErr.error = err.Error()
+ return localVarReturnValue, localVarHTTPResponse, newErr
+ }
+ newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+ newErr.model = v
+ return localVarReturnValue, localVarHTTPResponse, newErr
+ }
+ if localVarHTTPResponse.StatusCode == 403 {
+ var v CmdApiServerEndpointErrorResponse
+ err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+ if err != nil {
+ newErr.error = err.Error()
+ return localVarReturnValue, localVarHTTPResponse, newErr
+ }
+ newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+ newErr.model = v
+ }
return localVarReturnValue, localVarHTTPResponse, newErr
}
@@ -176,7 +197,7 @@ func (a *DefaultApiService) GetOpenApiSpecV1Execute(r ApiGetOpenApiSpecV1Request
}
// to determine the Accept header
- localVarHTTPHeaderAccepts := []string{"application/json"}
+ localVarHTTPHeaderAccepts := []string{"application/json", "*/*"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
@@ -205,6 +226,27 @@ func (a *DefaultApiService) GetOpenApiSpecV1Execute(r ApiGetOpenApiSpecV1Request
body: localVarBody,
error: localVarHTTPResponse.Status,
}
+ if localVarHTTPResponse.StatusCode == 401 {
+ var v CmdApiServerEndpointErrorResponse
+ err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+ if err != nil {
+ newErr.error = err.Error()
+ return localVarReturnValue, localVarHTTPResponse, newErr
+ }
+ newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+ newErr.model = v
+ return localVarReturnValue, localVarHTTPResponse, newErr
+ }
+ if localVarHTTPResponse.StatusCode == 403 {
+ var v CmdApiServerEndpointErrorResponse
+ err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+ if err != nil {
+ newErr.error = err.Error()
+ return localVarReturnValue, localVarHTTPResponse, newErr
+ }
+ newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+ newErr.model = v
+ }
return localVarReturnValue, localVarHTTPResponse, newErr
}
@@ -273,7 +315,7 @@ func (a *DefaultApiService) GetPrometheusMetricsV1Execute(r ApiGetPrometheusMetr
}
// to determine the Accept header
- localVarHTTPHeaderAccepts := []string{"text/plain"}
+ localVarHTTPHeaderAccepts := []string{"text/plain", "*/*"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
@@ -302,6 +344,27 @@ func (a *DefaultApiService) GetPrometheusMetricsV1Execute(r ApiGetPrometheusMetr
body: localVarBody,
error: localVarHTTPResponse.Status,
}
+ if localVarHTTPResponse.StatusCode == 401 {
+ var v CmdApiServerEndpointErrorResponse
+ err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+ if err != nil {
+ newErr.error = err.Error()
+ return localVarReturnValue, localVarHTTPResponse, newErr
+ }
+ newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+ newErr.model = v
+ return localVarReturnValue, localVarHTTPResponse, newErr
+ }
+ if localVarHTTPResponse.StatusCode == 403 {
+ var v CmdApiServerEndpointErrorResponse
+ err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+ if err != nil {
+ newErr.error = err.Error()
+ return localVarReturnValue, localVarHTTPResponse, newErr
+ }
+ newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+ newErr.model = v
+ }
return localVarReturnValue, localVarHTTPResponse, newErr
}
diff --git a/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/client.go b/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/client.go
index a3b0f197443..b9e16de3b6a 100644
--- a/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/client.go
+++ b/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/client.go
@@ -410,6 +410,11 @@ func (c *APIClient) prepareRequest(
// Walk through any authentication.
+ // AccessToken Authentication
+ if auth, ok := ctx.Value(ContextAccessToken).(string); ok {
+ localVarRequest.Header.Add("Authorization", "Bearer "+auth)
+ }
+
}
for header, value := range c.cfg.DefaultHeader {
diff --git a/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/configuration.go b/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/configuration.go
index c69a1313a52..6242dfec4f9 100644
--- a/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/configuration.go
+++ b/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/configuration.go
@@ -28,6 +28,9 @@ func (c contextKey) String() string {
}
var (
+ // ContextAccessToken takes a string oauth2 access token as authentication for the request.
+ ContextAccessToken = contextKey("accesstoken")
+
// ContextServerIndex uses a server configuration from the index.
ContextServerIndex = contextKey("serverIndex")
diff --git a/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/model_cmd_api_server_endpoint_error_response.go b/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/model_cmd_api_server_endpoint_error_response.go
new file mode 100644
index 00000000000..d3fa00b9c9a
--- /dev/null
+++ b/packages/cactus-cmd-api-server/src/main/go/generated/openapi/go-client/model_cmd_api_server_endpoint_error_response.go
@@ -0,0 +1,126 @@
+/*
+Hyperledger Cactus API
+
+Interact with a Cactus deployment through HTTP.
+
+API version: 2.0.0
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package cactus-cmd-api-server
+
+import (
+ "encoding/json"
+)
+
+// checks if the CmdApiServerEndpointErrorResponse type satisfies the MappedNullable interface at compile time
+var _ MappedNullable = &CmdApiServerEndpointErrorResponse{}
+
+// CmdApiServerEndpointErrorResponse struct for CmdApiServerEndpointErrorResponse
+type CmdApiServerEndpointErrorResponse struct {
+ Message *string `json:"message,omitempty"`
+}
+
+// NewCmdApiServerEndpointErrorResponse instantiates a new CmdApiServerEndpointErrorResponse object
+// This constructor will assign default values to properties that have it defined,
+// and makes sure properties required by API are set, but the set of arguments
+// will change when the set of required properties is changed
+func NewCmdApiServerEndpointErrorResponse() *CmdApiServerEndpointErrorResponse {
+ this := CmdApiServerEndpointErrorResponse{}
+ return &this
+}
+
+// NewCmdApiServerEndpointErrorResponseWithDefaults instantiates a new CmdApiServerEndpointErrorResponse object
+// This constructor will only assign default values to properties that have it defined,
+// but it doesn't guarantee that properties required by API are set
+func NewCmdApiServerEndpointErrorResponseWithDefaults() *CmdApiServerEndpointErrorResponse {
+ this := CmdApiServerEndpointErrorResponse{}
+ return &this
+}
+
+// GetMessage returns the Message field value if set, zero value otherwise.
+func (o *CmdApiServerEndpointErrorResponse) GetMessage() string {
+ if o == nil || IsNil(o.Message) {
+ var ret string
+ return ret
+ }
+ return *o.Message
+}
+
+// GetMessageOk returns a tuple with the Message field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CmdApiServerEndpointErrorResponse) GetMessageOk() (*string, bool) {
+ if o == nil || IsNil(o.Message) {
+ return nil, false
+ }
+ return o.Message, true
+}
+
+// HasMessage returns a boolean if a field has been set.
+func (o *CmdApiServerEndpointErrorResponse) HasMessage() bool {
+ if o != nil && !IsNil(o.Message) {
+ return true
+ }
+
+ return false
+}
+
+// SetMessage gets a reference to the given string and assigns it to the Message field.
+func (o *CmdApiServerEndpointErrorResponse) SetMessage(v string) {
+ o.Message = &v
+}
+
+func (o CmdApiServerEndpointErrorResponse) MarshalJSON() ([]byte, error) {
+ toSerialize,err := o.ToMap()
+ if err != nil {
+ return []byte{}, err
+ }
+ return json.Marshal(toSerialize)
+}
+
+func (o CmdApiServerEndpointErrorResponse) ToMap() (map[string]interface{}, error) {
+ toSerialize := map[string]interface{}{}
+ if !IsNil(o.Message) {
+ toSerialize["message"] = o.Message
+ }
+ return toSerialize, nil
+}
+
+type NullableCmdApiServerEndpointErrorResponse struct {
+ value *CmdApiServerEndpointErrorResponse
+ isSet bool
+}
+
+func (v NullableCmdApiServerEndpointErrorResponse) Get() *CmdApiServerEndpointErrorResponse {
+ return v.value
+}
+
+func (v *NullableCmdApiServerEndpointErrorResponse) Set(val *CmdApiServerEndpointErrorResponse) {
+ v.value = val
+ v.isSet = true
+}
+
+func (v NullableCmdApiServerEndpointErrorResponse) IsSet() bool {
+ return v.isSet
+}
+
+func (v *NullableCmdApiServerEndpointErrorResponse) Unset() {
+ v.value = nil
+ v.isSet = false
+}
+
+func NewNullableCmdApiServerEndpointErrorResponse(val *CmdApiServerEndpointErrorResponse) *NullableCmdApiServerEndpointErrorResponse {
+ return &NullableCmdApiServerEndpointErrorResponse{value: val, isSet: true}
+}
+
+func (v NullableCmdApiServerEndpointErrorResponse) MarshalJSON() ([]byte, error) {
+ return json.Marshal(v.value)
+}
+
+func (v *NullableCmdApiServerEndpointErrorResponse) UnmarshalJSON(src []byte) error {
+ v.isSet = true
+ return json.Unmarshal(src, &v.value)
+}
+
+
diff --git a/packages/cactus-cmd-api-server/src/main/json/openapi.json b/packages/cactus-cmd-api-server/src/main/json/openapi.json
index a9c63cf9d26..d4ee7c7681b 100644
--- a/packages/cactus-cmd-api-server/src/main/json/openapi.json
+++ b/packages/cactus-cmd-api-server/src/main/json/openapi.json
@@ -75,9 +75,29 @@
"GetOpenApiSpecV1EndpointResponse": {
"type": "string",
"nullable": false
+ },
+ "CmdApiServerEndpointErrorResponse": {
+ "properties": {
+ "message": {
+ "type": "string",
+ "example": "Forbidden - Valid token but missing correct scope\n"
+ }
+ }
+ }
+ },
+ "securitySchemes": {
+ "bearerTokenAuth": {
+ "type": "http",
+ "scheme": "bearer",
+ "bearerFormat": "JSON Web Tokens"
}
}
},
+ "security": [
+ {
+ "bearerTokenAuth": ["read:health", "read:metrics", "read:spec"]
+ }
+ ],
"paths": {
"/api/v1/api-server/healthcheck": {
"get": {
@@ -101,8 +121,33 @@
}
}
}
+ },
+ "401": {
+ "description": "Unauthorized - Invalid token",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/CmdApiServerEndpointErrorResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden - Valid token but missing correct scope",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/CmdApiServerEndpointErrorResponse"
+ }
+ }
+ }
}
- }
+ },
+ "security": [
+ {
+ "bearerTokenAuth": ["read:health"]
+ }
+ ]
}
},
"/api/v1/api-server/get-prometheus-exporter-metrics": {
@@ -126,8 +171,33 @@
}
}
}
+ },
+ "401": {
+ "description": "Unauthorized - Invalid token",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/CmdApiServerEndpointErrorResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden - Valid token but missing correct scope",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/CmdApiServerEndpointErrorResponse"
+ }
+ }
+ }
}
- }
+ },
+ "security": [
+ {
+ "bearerTokenAuth": ["read:metrics"]
+ }
+ ]
}
},
"/api/v1/api-server/get-open-api-spec": {
@@ -151,8 +221,33 @@
}
}
}
+ },
+ "401": {
+ "description": "Unauthorized - Invalid token",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/CmdApiServerEndpointErrorResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden - Valid token but missing correct scope",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/CmdApiServerEndpointErrorResponse"
+ }
+ }
+ }
}
- }
+ },
+ "security": [
+ {
+ "bearerTokenAuth": ["read:spec"]
+ }
+ ]
}
}
}
diff --git a/packages/cactus-cmd-api-server/src/main/json/openapi.tpl.json b/packages/cactus-cmd-api-server/src/main/json/openapi.tpl.json
index a9c63cf9d26..d4ee7c7681b 100644
--- a/packages/cactus-cmd-api-server/src/main/json/openapi.tpl.json
+++ b/packages/cactus-cmd-api-server/src/main/json/openapi.tpl.json
@@ -75,9 +75,29 @@
"GetOpenApiSpecV1EndpointResponse": {
"type": "string",
"nullable": false
+ },
+ "CmdApiServerEndpointErrorResponse": {
+ "properties": {
+ "message": {
+ "type": "string",
+ "example": "Forbidden - Valid token but missing correct scope\n"
+ }
+ }
+ }
+ },
+ "securitySchemes": {
+ "bearerTokenAuth": {
+ "type": "http",
+ "scheme": "bearer",
+ "bearerFormat": "JSON Web Tokens"
}
}
},
+ "security": [
+ {
+ "bearerTokenAuth": ["read:health", "read:metrics", "read:spec"]
+ }
+ ],
"paths": {
"/api/v1/api-server/healthcheck": {
"get": {
@@ -101,8 +121,33 @@
}
}
}
+ },
+ "401": {
+ "description": "Unauthorized - Invalid token",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/CmdApiServerEndpointErrorResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden - Valid token but missing correct scope",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/CmdApiServerEndpointErrorResponse"
+ }
+ }
+ }
}
- }
+ },
+ "security": [
+ {
+ "bearerTokenAuth": ["read:health"]
+ }
+ ]
}
},
"/api/v1/api-server/get-prometheus-exporter-metrics": {
@@ -126,8 +171,33 @@
}
}
}
+ },
+ "401": {
+ "description": "Unauthorized - Invalid token",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/CmdApiServerEndpointErrorResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden - Valid token but missing correct scope",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/CmdApiServerEndpointErrorResponse"
+ }
+ }
+ }
}
- }
+ },
+ "security": [
+ {
+ "bearerTokenAuth": ["read:metrics"]
+ }
+ ]
}
},
"/api/v1/api-server/get-open-api-spec": {
@@ -151,8 +221,33 @@
}
}
}
+ },
+ "401": {
+ "description": "Unauthorized - Invalid token",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/CmdApiServerEndpointErrorResponse"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden - Valid token but missing correct scope",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/CmdApiServerEndpointErrorResponse"
+ }
+ }
+ }
}
- }
+ },
+ "security": [
+ {
+ "bearerTokenAuth": ["read:spec"]
+ }
+ ]
}
}
}
diff --git a/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/.openapi-generator/FILES b/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/.openapi-generator/FILES
index 9354990b616..e1a27d52906 100644
--- a/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/.openapi-generator/FILES
+++ b/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/.openapi-generator/FILES
@@ -21,6 +21,7 @@ src/main/kotlin/org/openapitools/client/infrastructure/ResponseExtensions.kt
src/main/kotlin/org/openapitools/client/infrastructure/Serializer.kt
src/main/kotlin/org/openapitools/client/infrastructure/URIAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/UUIDAdapter.kt
+src/main/kotlin/org/openapitools/client/models/CmdApiServerEndpointErrorResponse.kt
src/main/kotlin/org/openapitools/client/models/HealthCheckResponse.kt
src/main/kotlin/org/openapitools/client/models/MemoryUsage.kt
src/main/kotlin/org/openapitools/client/models/WatchHealthcheckV1.kt
diff --git a/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/README.md b/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/README.md
index a9770d9c2ef..dbb033f5b69 100644
--- a/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/README.md
+++ b/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/README.md
@@ -52,6 +52,7 @@ Class | Method | HTTP request | Description
## Documentation for Models
+ - [org.openapitools.client.models.CmdApiServerEndpointErrorResponse](docs/CmdApiServerEndpointErrorResponse.md)
- [org.openapitools.client.models.HealthCheckResponse](docs/HealthCheckResponse.md)
- [org.openapitools.client.models.MemoryUsage](docs/MemoryUsage.md)
- [org.openapitools.client.models.WatchHealthcheckV1](docs/WatchHealthcheckV1.md)
@@ -60,5 +61,10 @@ Class | Method | HTTP request | Description
## Documentation for Authorization
-Endpoints do not require authorization.
+
+Authentication schemes defined for the API:
+
+### bearerTokenAuth
+
+- **Type**: HTTP basic authentication
diff --git a/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/apis/DefaultApi.kt b/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/apis/DefaultApi.kt
index d05dc1394b8..9464bd4d053 100644
--- a/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/apis/DefaultApi.kt
+++ b/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/apis/DefaultApi.kt
@@ -19,6 +19,7 @@ import java.io.IOException
import okhttp3.OkHttpClient
import okhttp3.HttpUrl
+import org.openapitools.client.models.CmdApiServerEndpointErrorResponse
import org.openapitools.client.models.HealthCheckResponse
import com.squareup.moshi.Json
@@ -108,7 +109,7 @@ class DefaultApi(basePath: kotlin.String = defaultBasePath, client: OkHttpClient
path = "/api/v1/api-server/healthcheck",
query = localVariableQuery,
headers = localVariableHeaders,
- requiresAuthentication = false,
+ requiresAuthentication = true,
body = localVariableBody
)
}
@@ -176,7 +177,7 @@ class DefaultApi(basePath: kotlin.String = defaultBasePath, client: OkHttpClient
path = "/api/v1/api-server/get-open-api-spec",
query = localVariableQuery,
headers = localVariableHeaders,
- requiresAuthentication = false,
+ requiresAuthentication = true,
body = localVariableBody
)
}
@@ -243,7 +244,7 @@ class DefaultApi(basePath: kotlin.String = defaultBasePath, client: OkHttpClient
path = "/api/v1/api-server/get-prometheus-exporter-metrics",
query = localVariableQuery,
headers = localVariableHeaders,
- requiresAuthentication = false,
+ requiresAuthentication = true,
body = localVariableBody
)
}
diff --git a/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt b/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt
index ea4b7b65935..c83d4d4d575 100644
--- a/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt
+++ b/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt
@@ -143,10 +143,20 @@ open class ApiClient(val baseUrl: String, val client: OkHttpClient = defaultClie
}
}
+ protected fun updateAuthParams(requestConfig: RequestConfig) {
+ if (requestConfig.headers[Authorization].isNullOrEmpty()) {
+ accessToken?.let { accessToken ->
+ requestConfig.headers[Authorization] = "Bearer $accessToken"
+ }
+ }
+ }
protected inline fun request(requestConfig: RequestConfig): ApiResponse {
val httpUrl = baseUrl.toHttpUrlOrNull() ?: throw IllegalStateException("baseUrl is invalid.")
+ // take authMethod from operation
+ updateAuthParams(requestConfig)
+
val url = httpUrl.newBuilder()
.addEncodedPathSegments(requestConfig.path.trimStart('/'))
.apply {
diff --git a/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CmdApiServerEndpointErrorResponse.kt b/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CmdApiServerEndpointErrorResponse.kt
new file mode 100644
index 00000000000..a1502dda6a1
--- /dev/null
+++ b/packages/cactus-cmd-api-server/src/main/kotlin/generated/openapi/kotlin-client/src/main/kotlin/org/openapitools/client/models/CmdApiServerEndpointErrorResponse.kt
@@ -0,0 +1,35 @@
+/**
+ *
+ * Please note:
+ * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * Do not edit this file manually.
+ *
+ */
+
+@file:Suppress(
+ "ArrayInDataClass",
+ "EnumEntryName",
+ "RemoveRedundantQualifierName",
+ "UnusedImport"
+)
+
+package org.openapitools.client.models
+
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ *
+ *
+ * @param message
+ */
+
+
+data class CmdApiServerEndpointErrorResponse (
+
+ @Json(name = "message")
+ val message: kotlin.String? = null
+
+)
+
diff --git a/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/.openapi-generator/FILES b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/.openapi-generator/FILES
index 270cf21ca1a..779704fc76e 100644
--- a/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/.openapi-generator/FILES
+++ b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/.openapi-generator/FILES
@@ -1,4 +1,5 @@
README.md
+models/cmd_api_server_endpoint_error_response_pb.proto
models/health_check_response_pb.proto
models/memory_usage_pb.proto
models/watch_healthcheck_v1_pb.proto
diff --git a/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/models/cmd_api_server_endpoint_error_response_pb.proto b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/models/cmd_api_server_endpoint_error_response_pb.proto
new file mode 100644
index 00000000000..cf9db105765
--- /dev/null
+++ b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/models/cmd_api_server_endpoint_error_response_pb.proto
@@ -0,0 +1,19 @@
+/*
+ Hyperledger Cactus API
+
+ Interact with a Cactus deployment through HTTP.
+
+ The version of the OpenAPI document: 2.0.0
+
+ Generated by OpenAPI Generator: https://openapi-generator.tech
+*/
+
+syntax = "proto3";
+
+package org.hyperledger.cactus.cmd_api_server;
+
+
+message CmdApiServerEndpointErrorResponsePB {
+ string message = 418054152;
+
+}
diff --git a/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/services/default_service.proto b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/services/default_service.proto
index e1d50cfdede..5297324f112 100644
--- a/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/services/default_service.proto
+++ b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/services/default_service.proto
@@ -13,6 +13,7 @@ syntax = "proto3";
package org.hyperledger.cactus.cmd_api_server;
import "google/protobuf/empty.proto";
+import "models/cmd_api_server_endpoint_error_response_pb.proto";
import "models/health_check_response_pb.proto";
service DefaultService {
diff --git a/packages/cactus-cmd-api-server/src/main/typescript/api-server.ts b/packages/cactus-cmd-api-server/src/main/typescript/api-server.ts
index 79a7e263419..76b05573857 100644
--- a/packages/cactus-cmd-api-server/src/main/typescript/api-server.ts
+++ b/packages/cactus-cmd-api-server/src/main/typescript/api-server.ts
@@ -81,6 +81,10 @@ import {
GetOpenApiSpecV1Endpoint,
IGetOpenApiSpecV1EndpointOptions,
} from "./web-services/get-open-api-spec-v1-endpoint";
+import {
+ GetHealthcheckV1Endpoint,
+ IGetHealthcheckV1EndpointOptions,
+} from "./web-services/get-healthcheck-v1-endpoint";
export interface IApiServerConstructorOptions {
readonly pluginManagerOptions?: { pluginsPath: string };
@@ -640,6 +644,15 @@ export class ApiServer {
const { logLevel } = this.options.config;
const pluginRegistry = await this.getOrInitPluginRegistry();
+ {
+ const opts: IGetHealthcheckV1EndpointOptions = {
+ process: global.process,
+ logLevel,
+ };
+ const endpoint = new GetHealthcheckV1Endpoint(opts);
+ await registerWebServiceEndpoint(app, endpoint);
+ }
+
{
const oasPath = OAS.paths["/api/v1/api-server/get-open-api-spec"];
@@ -657,23 +670,6 @@ export class ApiServer {
await registerWebServiceEndpoint(app, endpoint);
}
- const healthcheckHandler = (req: Request, res: Response) => {
- res.json({
- success: true,
- createdAt: new Date(),
- memoryUsage: process.memoryUsage(),
- });
- };
-
- const { "/api/v1/api-server/healthcheck": oasPath } = OAS.paths;
- const { http } = oasPath.get["x-hyperledger-cacti"];
- const { path: httpPath, verbLowerCase: httpVerb } = http;
- if (!isExpressHttpVerbMethodName(httpVerb)) {
- const eMsg = `${fnTag} Invalid HTTP verb "${httpVerb}" in cmd-api-server OpenAPI specification for HTTP path: "${httpPath}"`;
- throw new RuntimeError(eMsg);
- }
- app[httpVerb](httpPath, healthcheckHandler);
-
this.wsApi.on("connection", (socket: SocketIoSocket) => {
const { id } = socket;
const transport = socket.conn.transport.name; // in most cases, "polling"
diff --git a/packages/cactus-cmd-api-server/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/cactus-cmd-api-server/src/main/typescript/generated/openapi/typescript-axios/api.ts
index 6610e56b536..67b89bca7fc 100644
--- a/packages/cactus-cmd-api-server/src/main/typescript/generated/openapi/typescript-axios/api.ts
+++ b/packages/cactus-cmd-api-server/src/main/typescript/generated/openapi/typescript-axios/api.ts
@@ -23,6 +23,19 @@ import type { RequestArgs } from './base';
// @ts-ignore
import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError } from './base';
+/**
+ *
+ * @export
+ * @interface CmdApiServerEndpointErrorResponse
+ */
+export interface CmdApiServerEndpointErrorResponse {
+ /**
+ *
+ * @type {string}
+ * @memberof CmdApiServerEndpointErrorResponse
+ */
+ 'message'?: string;
+}
/**
*
* @export
@@ -128,6 +141,10 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
+ // authentication bearerTokenAuth required
+ // http bearer authentication required
+ await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
setSearchParams(localVarUrlObj, localVarQueryParameter);
@@ -157,6 +174,10 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
+ // authentication bearerTokenAuth required
+ // http bearer authentication required
+ await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
setSearchParams(localVarUrlObj, localVarQueryParameter);
@@ -187,6 +208,10 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
+ // authentication bearerTokenAuth required
+ // http bearer authentication required
+ await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
setSearchParams(localVarUrlObj, localVarQueryParameter);
diff --git a/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/models/cmd_api_server_endpoint_error_response_pb.ts b/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/models/cmd_api_server_endpoint_error_response_pb.ts
new file mode 100644
index 00000000000..5c031ffef49
--- /dev/null
+++ b/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/models/cmd_api_server_endpoint_error_response_pb.ts
@@ -0,0 +1,75 @@
+/**
+ * Generated by the protoc-gen-ts. DO NOT EDIT!
+ * compiler version: 3.19.1
+ * source: models/cmd_api_server_endpoint_error_response_pb.proto
+ * git: https://github.com/thesayyn/protoc-gen-ts */
+import * as pb_1 from "google-protobuf";
+export namespace org.hyperledger.cactus.cmd_api_server {
+ export class CmdApiServerEndpointErrorResponsePB extends pb_1.Message {
+ #one_of_decls: number[][] = [];
+ constructor(data?: any[] | {
+ message?: string;
+ }) {
+ super();
+ pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
+ if (!Array.isArray(data) && typeof data == "object") {
+ if ("message" in data && data.message != undefined) {
+ this.message = data.message;
+ }
+ }
+ }
+ get message() {
+ return pb_1.Message.getFieldWithDefault(this, 418054152, "") as string;
+ }
+ set message(value: string) {
+ pb_1.Message.setField(this, 418054152, value);
+ }
+ static fromObject(data: {
+ message?: string;
+ }): CmdApiServerEndpointErrorResponsePB {
+ const message = new CmdApiServerEndpointErrorResponsePB({});
+ if (data.message != null) {
+ message.message = data.message;
+ }
+ return message;
+ }
+ toObject() {
+ const data: {
+ message?: string;
+ } = {};
+ if (this.message != null) {
+ data.message = this.message;
+ }
+ return data;
+ }
+ serialize(): Uint8Array;
+ serialize(w: pb_1.BinaryWriter): void;
+ serialize(w?: pb_1.BinaryWriter): Uint8Array | void {
+ const writer = w || new pb_1.BinaryWriter();
+ if (this.message.length)
+ writer.writeString(418054152, this.message);
+ if (!w)
+ return writer.getResultBuffer();
+ }
+ static deserialize(bytes: Uint8Array | pb_1.BinaryReader): CmdApiServerEndpointErrorResponsePB {
+ const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new CmdApiServerEndpointErrorResponsePB();
+ while (reader.nextField()) {
+ if (reader.isEndGroup())
+ break;
+ switch (reader.getFieldNumber()) {
+ case 418054152:
+ message.message = reader.readString();
+ break;
+ default: reader.skipField();
+ }
+ }
+ return message;
+ }
+ serializeBinary(): Uint8Array {
+ return this.serialize();
+ }
+ static deserializeBinary(bytes: Uint8Array): CmdApiServerEndpointErrorResponsePB {
+ return CmdApiServerEndpointErrorResponsePB.deserialize(bytes);
+ }
+ }
+}
diff --git a/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/services/default_service.ts b/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/services/default_service.ts
index 06d8bf0eb10..f4086197f86 100644
--- a/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/services/default_service.ts
+++ b/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/services/default_service.ts
@@ -4,7 +4,8 @@
* source: services/default_service.proto
* git: https://github.com/thesayyn/protoc-gen-ts */
import * as dependency_1 from "./../google/protobuf/empty";
-import * as dependency_2 from "./../models/health_check_response_pb";
+import * as dependency_2 from "./../models/cmd_api_server_endpoint_error_response_pb";
+import * as dependency_3 from "./../models/health_check_response_pb";
import * as pb_1 from "google-protobuf";
import * as grpc_1 from "@grpc/grpc-js";
export namespace org.hyperledger.cactus.cmd_api_server {
@@ -174,8 +175,8 @@ export namespace org.hyperledger.cactus.cmd_api_server {
responseStream: false,
requestSerialize: (message: dependency_1.google.protobuf.Empty) => Buffer.from(message.serialize()),
requestDeserialize: (bytes: Buffer) => dependency_1.google.protobuf.Empty.deserialize(new Uint8Array(bytes)),
- responseSerialize: (message: dependency_2.org.hyperledger.cactus.cmd_api_server.HealthCheckResponsePB) => Buffer.from(message.serialize()),
- responseDeserialize: (bytes: Buffer) => dependency_2.org.hyperledger.cactus.cmd_api_server.HealthCheckResponsePB.deserialize(new Uint8Array(bytes))
+ responseSerialize: (message: dependency_3.org.hyperledger.cactus.cmd_api_server.HealthCheckResponsePB) => Buffer.from(message.serialize()),
+ responseDeserialize: (bytes: Buffer) => dependency_3.org.hyperledger.cactus.cmd_api_server.HealthCheckResponsePB.deserialize(new Uint8Array(bytes))
},
GetOpenApiSpecV1: {
path: "/org.hyperledger.cactus.cmd_api_server.DefaultService/GetOpenApiSpecV1",
@@ -197,7 +198,7 @@ export namespace org.hyperledger.cactus.cmd_api_server {
}
};
[method: string]: grpc_1.UntypedHandleCall;
- abstract GetHealthCheckV1(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void;
+ abstract GetHealthCheckV1(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void;
abstract GetOpenApiSpecV1(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void;
abstract GetPrometheusMetricsV1(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void;
}
@@ -205,7 +206,7 @@ export namespace org.hyperledger.cactus.cmd_api_server {
constructor(address: string, credentials: grpc_1.ChannelCredentials, options?: Partial) {
super(address, credentials, options);
}
- GetHealthCheckV1: GrpcUnaryServiceInterface = (message: dependency_1.google.protobuf.Empty, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback, options?: grpc_1.CallOptions | grpc_1.requestCallback, callback?: grpc_1.requestCallback): grpc_1.ClientUnaryCall => {
+ GetHealthCheckV1: GrpcUnaryServiceInterface = (message: dependency_1.google.protobuf.Empty, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback, options?: grpc_1.CallOptions | grpc_1.requestCallback, callback?: grpc_1.requestCallback): grpc_1.ClientUnaryCall => {
return super.GetHealthCheckV1(message, metadata, options, callback);
};
GetOpenApiSpecV1: GrpcUnaryServiceInterface = (message: dependency_1.google.protobuf.Empty, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback, options?: grpc_1.CallOptions | grpc_1.requestCallback, callback?: grpc_1.requestCallback): grpc_1.ClientUnaryCall => {
diff --git a/packages/cactus-cmd-api-server/src/main/typescript/web-services/get-healthcheck-v1-endpoint.ts b/packages/cactus-cmd-api-server/src/main/typescript/web-services/get-healthcheck-v1-endpoint.ts
new file mode 100644
index 00000000000..f1ee542cdaf
--- /dev/null
+++ b/packages/cactus-cmd-api-server/src/main/typescript/web-services/get-healthcheck-v1-endpoint.ts
@@ -0,0 +1,117 @@
+import { StatusCodes } from "http-status-codes";
+import type { Express, Request, Response } from "express";
+
+import {
+ Checks,
+ IAsyncProvider,
+ Logger,
+ LoggerProvider,
+ LogLevelDesc,
+} from "@hyperledger/cactus-common";
+import {
+ IEndpointAuthzOptions,
+ IExpressRequestHandler,
+ IWebServiceEndpoint,
+} from "@hyperledger/cactus-core-api";
+import {
+ handleRestEndpointException,
+ IHandleRestEndpointExceptionOptions,
+ registerWebServiceEndpoint,
+} from "@hyperledger/cactus-core";
+
+import OAS from "../../json/openapi.json";
+
+export interface IGetHealthcheckV1EndpointOptions {
+ readonly logLevel?: LogLevelDesc;
+ readonly process: NodeJS.Process;
+}
+
+export class GetHealthcheckV1Endpoint implements IWebServiceEndpoint {
+ public static readonly CLASS_NAME = "GetHealthcheckV1Endpoint";
+
+ private readonly log: Logger;
+
+ private readonly process: NodeJS.Process;
+
+ public get className(): string {
+ return GetHealthcheckV1Endpoint.CLASS_NAME;
+ }
+
+ constructor(public readonly opts: IGetHealthcheckV1EndpointOptions) {
+ const fnTag = `${this.className}#constructor()`;
+ Checks.truthy(opts, `${fnTag} arg opts`);
+ Checks.truthy(opts.process, `${fnTag} arg opts.process`);
+
+ this.process = opts.process;
+
+ const level = this.opts.logLevel || "INFO";
+ const label = this.className;
+ this.log = LoggerProvider.getOrCreate({ level, label });
+ }
+
+ public getAuthorizationOptionsProvider(): IAsyncProvider {
+ return {
+ get: async () => ({
+ isProtected: true,
+ requiredRoles: this.oasPath.get.security[0].bearerTokenAuth,
+ }),
+ };
+ }
+
+ public getExpressRequestHandler(): IExpressRequestHandler {
+ return this.handleRequest.bind(this);
+ }
+
+ public get oasPath(): (typeof OAS.paths)["/api/v1/api-server/healthcheck"] {
+ return OAS.paths["/api/v1/api-server/healthcheck"];
+ }
+
+ public getPath(): string {
+ return this.oasPath.get["x-hyperledger-cacti"].http.path;
+ }
+
+ public getVerbLowerCase(): string {
+ return this.oasPath.get["x-hyperledger-cacti"].http.verbLowerCase;
+ }
+
+ public getOperationId(): string {
+ return this.oasPath.get.operationId;
+ }
+
+ public async registerExpress(
+ expressApp: Express,
+ ): Promise {
+ await registerWebServiceEndpoint(expressApp, this);
+ return this;
+ }
+
+ async handleRequest(_req: Request, res: Response): Promise {
+ const fnTag = `${this.className}#handleRequest()`;
+ const verbUpper = this.getVerbLowerCase().toUpperCase();
+ const reqTag = `${verbUpper} ${this.getPath()}`;
+ this.log.debug(reqTag);
+
+ try {
+ const memoryUsage = this.process.memoryUsage();
+ const createdAt = new Date();
+ const body = {
+ success: true,
+ createdAt,
+ memoryUsage,
+ };
+ res.json(body).status(StatusCodes.OK);
+ } catch (error) {
+ const { log } = this;
+ const errorMsg = `${fnTag} request handler fn crashed for: ${reqTag}`;
+
+ const ctx: Readonly = {
+ errorMsg,
+ log,
+ error,
+ res,
+ };
+
+ await handleRestEndpointException(ctx);
+ }
+ }
+}
diff --git a/packages/cactus-cmd-api-server/src/main/typescript/web-services/get-open-api-spec-v1-endpoint.ts b/packages/cactus-cmd-api-server/src/main/typescript/web-services/get-open-api-spec-v1-endpoint.ts
index 79b3e6d4b0c..c71993819b0 100644
--- a/packages/cactus-cmd-api-server/src/main/typescript/web-services/get-open-api-spec-v1-endpoint.ts
+++ b/packages/cactus-cmd-api-server/src/main/typescript/web-services/get-open-api-spec-v1-endpoint.ts
@@ -3,8 +3,15 @@ import {
IGetOpenApiSpecV1EndpointBaseOptions,
} from "@hyperledger/cactus-core";
-import { Checks, LogLevelDesc } from "@hyperledger/cactus-common";
-import { IWebServiceEndpoint } from "@hyperledger/cactus-core-api";
+import {
+ Checks,
+ IAsyncProvider,
+ LogLevelDesc,
+} from "@hyperledger/cactus-common";
+import {
+ IEndpointAuthzOptions,
+ IWebServiceEndpoint,
+} from "@hyperledger/cactus-core-api";
import OAS from "../../json/openapi.json";
@@ -34,4 +41,13 @@ export class GetOpenApiSpecV1Endpoint
const fnTag = `${this.className}#constructor()`;
Checks.truthy(options, `${fnTag} arg options`);
}
+
+ public getAuthorizationOptionsProvider(): IAsyncProvider {
+ return {
+ get: async () => ({
+ isProtected: true,
+ requiredRoles: this.opts.oasPath.get.security[0].bearerTokenAuth,
+ }),
+ };
+ }
}
diff --git a/packages/cactus-cmd-api-server/src/test/typescript/benchmark/run-cmd-api-server-benchmark.ts b/packages/cactus-cmd-api-server/src/test/typescript/benchmark/run-cmd-api-server-benchmark.ts
index 2babf8ff130..70bd10444d2 100644
--- a/packages/cactus-cmd-api-server/src/test/typescript/benchmark/run-cmd-api-server-benchmark.ts
+++ b/packages/cactus-cmd-api-server/src/test/typescript/benchmark/run-cmd-api-server-benchmark.ts
@@ -104,7 +104,11 @@ const createTestInfrastructure = async (opts: {
const grpcHost = `${addressInfoGrpc.address}:${addressInfoGrpc.port}`;
- const jwtPayload = { name: "Peter", location: "Albertirsa" };
+ const jwtPayload = {
+ name: "Peter",
+ location: "London",
+ scope: "read:spec",
+ };
const validJwt = await new SignJWT(jwtPayload)
.setProtectedHeader({ alg: "RS256" })
.setIssuer(expressJwtOptions.issuer)
diff --git a/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-endpoint-authorization.test.ts b/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-endpoint-authorization.test.ts
index 2e0d8c7e571..555520c3d66 100644
--- a/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-endpoint-authorization.test.ts
+++ b/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-endpoint-authorization.test.ts
@@ -87,7 +87,11 @@ describe(testCase, () => {
try {
expect(expressJwtOptions).toBeTruthy();
- const jwtPayload = { name: "Peter", location: "London" };
+ const jwtPayload = {
+ name: "Peter",
+ location: "London",
+ scope: "read:health",
+ };
const tokenGood = await new SignJWT(jwtPayload)
.setProtectedHeader({
alg: "RS256",
@@ -100,7 +104,6 @@ describe(testCase, () => {
const startResponse = apiServer.start();
await expect(startResponse).not.toReject;
expect(startResponse).toBeTruthy();
-
const addressInfoApi = (await startResponse).addressInfoApi;
const protocol = apiSrvOpts.apiTlsEnabled ? "https" : "http";
const { address, port } = addressInfoApi;
diff --git a/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-socketio-endpoint-authorization.test.ts b/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-socketio-endpoint-authorization.test.ts
index 38b636d7c42..2fb27ed2fe6 100644
--- a/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-socketio-endpoint-authorization.test.ts
+++ b/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-socketio-endpoint-authorization.test.ts
@@ -84,7 +84,11 @@ describe("cmd-api-server:ApiServer", () => {
const { address, port } = addressInfoApi;
apiHost = `${protocol}://${address}:${port}`;
- const jwtPayload = { name: "Peter", location: "Albertirsa" };
+ const jwtPayload = {
+ name: "Peter",
+ location: "London",
+ scope: "read:health",
+ };
const validJwt = await new SignJWT(jwtPayload)
.setProtectedHeader({ alg: "RS256" })
.setIssuer(expressJwtOptions.issuer)
diff --git a/packages/cactus-cmd-api-server/src/test/typescript/unit/get-open-api-spec-v1-endpoint.test.ts b/packages/cactus-cmd-api-server/src/test/typescript/unit/get-open-api-spec-v1-endpoint.test.ts
index 5b44fa205a0..70ae5e0a8d8 100644
--- a/packages/cactus-cmd-api-server/src/test/typescript/unit/get-open-api-spec-v1-endpoint.test.ts
+++ b/packages/cactus-cmd-api-server/src/test/typescript/unit/get-open-api-spec-v1-endpoint.test.ts
@@ -101,7 +101,11 @@ describe("cmd-api-server:getOpenApiSpecV1Endpoint", () => {
grpcHost = `${addressInfoGrpc.address}:${addressInfoGrpc.port}`;
- const jwtPayload = { name: "Peter", location: "Albertirsa" };
+ const jwtPayload = {
+ name: "Peter",
+ location: "London",
+ scope: "read:spec",
+ };
const validJwt = await new SignJWT(jwtPayload)
.setProtectedHeader({ alg: "RS256" })
.setIssuer(expressJwtOptions.issuer)
diff --git a/packages/cactus-cmd-api-server/src/test/typescript/unit/get-open-api-spec-v1-oauth2-scopes.test.ts b/packages/cactus-cmd-api-server/src/test/typescript/unit/get-open-api-spec-v1-oauth2-scopes.test.ts
new file mode 100644
index 00000000000..ec264c0b268
--- /dev/null
+++ b/packages/cactus-cmd-api-server/src/test/typescript/unit/get-open-api-spec-v1-oauth2-scopes.test.ts
@@ -0,0 +1,274 @@
+import {
+ ApiServer,
+ ApiServerApiClient,
+ ApiServerApiClientConfiguration,
+ AuthorizationProtocol,
+ ConfigService,
+ IAuthorizationConfig,
+} from "../../../main/typescript/public-api";
+import {
+ IJoseFittingJwtParams,
+ LogLevelDesc,
+} from "@hyperledger/cactus-common";
+import { PluginRegistry } from "@hyperledger/cactus-core";
+import { Constants } from "@hyperledger/cactus-core-api";
+import type { AuthorizeOptions as SocketIoJwtOptions } from "@thream/socketio-jwt";
+import type { Params as ExpressJwtOptions } from "express-jwt";
+import "jest-extended";
+import { SignJWT, exportSPKI, generateKeyPair } from "jose";
+import path from "path";
+import { v4 as uuidv4 } from "uuid";
+
+describe("cmd-api-server:getOpenApiSpecV1Endpoint", () => {
+ const logLevel: LogLevelDesc = "INFO";
+ let apiServer: ApiServer;
+ let apiClient: ApiServerApiClient;
+ let jwtKeyPair: { publicKey: CryptoKey; privateKey: CryptoKey };
+ let expressJwtOptions: ExpressJwtOptions & IJoseFittingJwtParams;
+
+ afterAll(async () => await apiServer.shutdown());
+
+ beforeAll(async () => {
+ jwtKeyPair = await generateKeyPair("RS256", { modulusLength: 4096 });
+ const jwtPublicKey = await exportSPKI(jwtKeyPair.publicKey);
+
+ expressJwtOptions = {
+ algorithms: ["RS256"],
+ secret: jwtPublicKey,
+ audience: uuidv4(),
+ issuer: uuidv4(),
+ };
+
+ const socketIoJwtOptions: SocketIoJwtOptions = {
+ secret: jwtPublicKey,
+ algorithms: ["RS256"],
+ };
+ expect(expressJwtOptions).toBeTruthy();
+
+ const authorizationConfig: IAuthorizationConfig = {
+ unprotectedEndpointExemptions: [],
+ expressJwtOptions,
+ socketIoJwtOptions,
+ socketIoPath: Constants.SocketIoConnectionPathV1,
+ };
+
+ const pluginsPath = path.join(
+ __dirname,
+ "../../../../../../", // walk back up to the project root
+ ".tmp/test/test-cmd-api-server/get-open-api-spec-v1-endpoint_test/", // the dir path from the root
+ uuidv4(), // then a random directory to ensure proper isolation
+ );
+ const pluginManagerOptionsJson = JSON.stringify({ pluginsPath });
+
+ const pluginRegistry = new PluginRegistry({ logLevel });
+
+ const configService = new ConfigService();
+
+ const apiSrvOpts = await configService.newExampleConfig();
+ apiSrvOpts.logLevel = logLevel;
+ apiSrvOpts.pluginManagerOptionsJson = pluginManagerOptionsJson;
+ apiSrvOpts.authorizationProtocol = AuthorizationProtocol.JSON_WEB_TOKEN;
+ apiSrvOpts.authorizationConfigJson = authorizationConfig;
+ apiSrvOpts.configFile = "";
+ apiSrvOpts.apiCorsDomainCsv = "*";
+ apiSrvOpts.apiPort = 0;
+ apiSrvOpts.cockpitPort = 0;
+ apiSrvOpts.grpcPort = 0;
+ apiSrvOpts.crpcPort = 0;
+ apiSrvOpts.apiTlsEnabled = false;
+ apiSrvOpts.grpcMtlsEnabled = false;
+ apiSrvOpts.plugins = [];
+
+ const config = await configService.newExampleConfigConvict(apiSrvOpts);
+
+ apiServer = new ApiServer({
+ config: config.getProperties(),
+ pluginRegistry,
+ });
+
+ apiServer.initPluginRegistry({ pluginRegistry });
+ const startResponsePromise = apiServer.start();
+ await expect(startResponsePromise).toResolve();
+ const startResponse = await startResponsePromise;
+ expect(startResponse).toBeTruthy();
+
+ const { addressInfoApi } = await startResponsePromise;
+ const protocol = apiSrvOpts.apiTlsEnabled ? "https" : "http";
+ const { address, port } = addressInfoApi;
+ const apiHost = `${protocol}://${address}:${port}`;
+
+ const jwtPayload = { name: "Peter", location: "Albertirsa" };
+ const validJwt = await new SignJWT(jwtPayload)
+ .setProtectedHeader({ alg: "RS256" })
+ .setIssuer(expressJwtOptions.issuer)
+ .setAudience(expressJwtOptions.audience)
+ .sign(jwtKeyPair.privateKey);
+ expect(validJwt).toBeTruthy();
+
+ const validBearerToken = `Bearer ${validJwt}`;
+ expect(validBearerToken).toBeTruthy();
+
+ apiClient = new ApiServerApiClient(
+ new ApiServerApiClientConfiguration({
+ basePath: apiHost,
+ baseOptions: { headers: { Authorization: validBearerToken } },
+ logLevel,
+ }),
+ );
+ });
+
+ it("HTTP - allows request execution with a valid JWT Token", async () => {
+ const jwtPayload = { scope: "read:spec" };
+ const validJwt = await new SignJWT(jwtPayload)
+ .setProtectedHeader({ alg: "RS256" })
+ .setIssuer(expressJwtOptions.issuer)
+ .setAudience(expressJwtOptions.audience)
+ .sign(jwtKeyPair.privateKey);
+
+ const validBearerToken = `Bearer ${validJwt}`;
+ expect(validBearerToken).toBeTruthy();
+
+ const res3Promise = apiClient.getOpenApiSpecV1({
+ headers: { Authorization: validBearerToken },
+ });
+
+ await expect(res3Promise).resolves.toHaveProperty("data.openapi");
+ const res3 = await res3Promise;
+ expect(res3.status).toEqual(200);
+ expect(res3.data).toBeTruthy();
+ });
+
+ it("HTTP - rejects request with an valid JWT but incorrect scope", async () => {
+ const jwtPayload = { scope: "red:specs" };
+ const validJwt = await new SignJWT(jwtPayload)
+ .setProtectedHeader({ alg: "RS256" })
+ .setIssuer(expressJwtOptions.issuer)
+ .setAudience(expressJwtOptions.audience)
+ .sign(jwtKeyPair.privateKey);
+
+ const validBearerToken = `Bearer ${validJwt}`;
+ expect(validBearerToken).toBeTruthy();
+
+ await expect(
+ apiClient.getOpenApiSpecV1({
+ headers: { Authorization: validBearerToken },
+ }),
+ ).rejects.toMatchObject({
+ response: {
+ status: 403,
+ statusText: expect.stringContaining("Forbidden"),
+ },
+ });
+ });
+
+ it("HTTP - rejects request with an invalid JWT", async () => {
+ const { privateKey: otherPrivateKey } = await generateKeyPair("RS256");
+ const invalidJwt = await new SignJWT({ scope: "invalid:scope" })
+ .setProtectedHeader({ alg: "RS256" })
+ .setIssuer("invalid-issuer")
+ .setAudience("invalid-audience")
+ .sign(otherPrivateKey);
+
+ const invalidBearerToken = `Bearer ${invalidJwt}`;
+ expect(invalidBearerToken).toBeTruthy();
+
+ await expect(
+ apiClient.getOpenApiSpecV1({
+ headers: { Authorization: invalidBearerToken },
+ }),
+ ).rejects.toMatchObject({
+ response: {
+ status: 401,
+ data: expect.stringContaining("Unauthorized"),
+ },
+ });
+ });
+
+ it("HTTP - allows health check execution with a valid JWT Token", async () => {
+ const jwtPayload = { scope: "read:health" };
+ const validJwt = await new SignJWT(jwtPayload)
+ .setProtectedHeader({ alg: "RS256" })
+ .setIssuer(expressJwtOptions.issuer)
+ .setAudience(expressJwtOptions.audience)
+ .sign(jwtKeyPair.privateKey);
+
+ const validBearerToken = `Bearer ${validJwt}`;
+ expect(validBearerToken).toBeTruthy();
+
+ const resPromise = apiClient.getHealthCheckV1({
+ headers: { Authorization: validBearerToken },
+ });
+
+ await expect(resPromise).resolves.toHaveProperty("data");
+ const res = await resPromise;
+ expect(res.status).toEqual(200);
+ expect(res.data).toBeTruthy();
+ });
+
+ it("HTTP - rejects health check execution with an invalid JWT", async () => {
+ const { privateKey: otherPrivateKey } = await generateKeyPair("RS256");
+ const invalidJwt = await new SignJWT({ scope: "invalid:scope" })
+ .setProtectedHeader({ alg: "RS256" })
+ .setIssuer("invalid-issuer")
+ .setAudience("invalid-audience")
+ .sign(otherPrivateKey);
+
+ const invalidBearerToken = `Bearer ${invalidJwt}`;
+ expect(invalidBearerToken).toBeTruthy();
+
+ await expect(
+ apiClient.getHealthCheckV1({
+ headers: { Authorization: invalidBearerToken },
+ }),
+ ).rejects.toMatchObject({
+ response: {
+ status: 401,
+ data: expect.stringContaining("Unauthorized"),
+ },
+ });
+ });
+
+ it("HTTP - allows Prometheus metrics execution with a valid JWT Token", async () => {
+ const jwtPayload = { scope: "read:metrics" };
+ const validJwt = await new SignJWT(jwtPayload)
+ .setProtectedHeader({ alg: "RS256" })
+ .setIssuer(expressJwtOptions.issuer)
+ .setAudience(expressJwtOptions.audience)
+ .sign(jwtKeyPair.privateKey);
+
+ const validBearerToken = `Bearer ${validJwt}`;
+ expect(validBearerToken).toBeTruthy();
+
+ const resPromise = apiClient.getPrometheusMetricsV1({
+ headers: { Authorization: validBearerToken },
+ });
+
+ await expect(resPromise).resolves.toHaveProperty("data");
+ const res = await resPromise;
+ expect(res.status).toEqual(200);
+ expect(res.data).toBeTruthy();
+ });
+
+ it("HTTP - rejects Prometheus metrics execution with an invalid JWT", async () => {
+ const { privateKey: otherPrivateKey } = await generateKeyPair("RS256");
+ const invalidJwt = await new SignJWT({ scope: "invalid:scope" })
+ .setProtectedHeader({ alg: "RS256" })
+ .setIssuer("invalid-issuer")
+ .setAudience("invalid-audience")
+ .sign(otherPrivateKey);
+
+ const invalidBearerToken = `Bearer ${invalidJwt}`;
+ expect(invalidBearerToken).toBeTruthy();
+
+ await expect(
+ apiClient.getPrometheusMetricsV1({
+ headers: { Authorization: invalidBearerToken },
+ }),
+ ).rejects.toMatchObject({
+ response: {
+ status: 401,
+ data: expect.stringContaining("Unauthorized"),
+ },
+ });
+ });
+});
diff --git a/yarn.lock b/yarn.lock
index 590decc8637..3e367b4193e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9521,7 +9521,7 @@ __metadata:
google-protobuf: "npm:3.21.4"
grpc-tools: "npm:1.12.4"
grpc_tools_node_protoc_ts: "npm:5.3.3"
- http-status-codes: "npm:2.1.4"
+ http-status-codes: "npm:2.1.3"
jose: "npm:4.15.5"
json-stable-stringify: "npm:1.0.2"
lmify: "npm:0.3.0"
@@ -32848,6 +32848,13 @@ __metadata:
languageName: node
linkType: hard
+"http-status-codes@npm:2.1.3":
+ version: 2.1.3
+ resolution: "http-status-codes@npm:2.1.3"
+ checksum: 10/d5025903b41d88d35c1039e6fa4fe4061cce95119f58672d56ed8837bf91cc10c1b3dc47b73288398b55990e7a2c2147ae32feddc896b6863f020b44ef58ee6e
+ languageName: node
+ linkType: hard
+
"http-status-codes@npm:2.1.4":
version: 2.1.4
resolution: "http-status-codes@npm:2.1.4"