-
Notifications
You must be signed in to change notification settings - Fork 672
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Dusan Borovcanin <[email protected]>
- Loading branch information
1 parent
edf8050
commit c4d17e3
Showing
165 changed files
with
791 additions
and
141 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// Copyright (c) Abstract Machines | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
// Package api contains commonly used constants and functions | ||
// for the HTTP and gRPC API. | ||
package api |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// Copyright (c) Abstract Machines | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package http | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
|
||
apiutil "github.com/absmach/supermq/api/http/util" | ||
smqauthn "github.com/absmach/supermq/pkg/authn" | ||
"github.com/go-chi/chi/v5" | ||
) | ||
|
||
type sessionKeyType string | ||
|
||
const SessionKey = sessionKeyType("session") | ||
|
||
func AuthenticateMiddleware(authn smqauthn.Authentication, domainCheck bool) func(http.Handler) http.Handler { | ||
return func(next http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
token := apiutil.ExtractBearerToken(r) | ||
if token == "" { | ||
EncodeError(r.Context(), apiutil.ErrBearerToken, w) | ||
return | ||
} | ||
|
||
resp, err := authn.Authenticate(r.Context(), token) | ||
if err != nil { | ||
EncodeError(r.Context(), err, w) | ||
return | ||
} | ||
|
||
if domainCheck { | ||
domain := chi.URLParam(r, "domainID") | ||
if domain == "" { | ||
EncodeError(r.Context(), apiutil.ErrMissingDomainID, w) | ||
return | ||
} | ||
resp.DomainID = domain | ||
resp.DomainUserID = domain + "_" + resp.UserID | ||
} | ||
|
||
ctx := context.WithValue(r.Context(), SessionKey, resp) | ||
|
||
next.ServeHTTP(w, r.WithContext(ctx)) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,249 @@ | ||
// Copyright (c) Abstract Machines | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package http | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"net/http" | ||
|
||
"github.com/absmach/supermq" | ||
apiutil "github.com/absmach/supermq/api/http/util" | ||
"github.com/absmach/supermq/bootstrap" | ||
"github.com/absmach/supermq/certs" | ||
"github.com/absmach/supermq/clients" | ||
"github.com/absmach/supermq/groups" | ||
"github.com/absmach/supermq/pkg/errors" | ||
svcerr "github.com/absmach/supermq/pkg/errors/service" | ||
"github.com/absmach/supermq/users" | ||
"github.com/gofrs/uuid/v5" | ||
) | ||
|
||
const ( | ||
MemberKindKey = "member_kind" | ||
PermissionKey = "permission" | ||
RelationKey = "relation" | ||
StatusKey = "status" | ||
OffsetKey = "offset" | ||
OrderKey = "order" | ||
LimitKey = "limit" | ||
MetadataKey = "metadata" | ||
ParentKey = "parent_id" | ||
OwnerKey = "owner_id" | ||
ClientKey = "client" | ||
UsernameKey = "username" | ||
NameKey = "name" | ||
GroupKey = "group" | ||
ActionKey = "action" | ||
ActionsKey = "actions" | ||
RoleIDKey = "role_id" | ||
RoleNameKey = "role_name" | ||
AccessTypeKey = "access_type" | ||
TagKey = "tag" | ||
FirstNameKey = "first_name" | ||
LastNameKey = "last_name" | ||
TotalKey = "total" | ||
SubjectKey = "subject" | ||
ObjectKey = "object" | ||
LevelKey = "level" | ||
StartLevelKey = "start_level" | ||
EndLevelKey = "end_level" | ||
TreeKey = "tree" | ||
DirKey = "dir" | ||
ListPerms = "list_perms" | ||
VisibilityKey = "visibility" | ||
EmailKey = "email" | ||
SharedByKey = "shared_by" | ||
TokenKey = "token" | ||
UserKey = "user" | ||
DomainKey = "domain" | ||
ChannelKey = "channel" | ||
DefPermission = "read_permission" | ||
DefTotal = uint64(100) | ||
DefOffset = 0 | ||
DefOrder = "updated_at" | ||
DefDir = "asc" | ||
DefLimit = 10 | ||
DefLevel = 0 | ||
DefStartLevel = 1 | ||
DefEndLevel = 0 | ||
DefStatus = "enabled" | ||
DefClientStatus = clients.Enabled | ||
DefUserStatus = users.Enabled | ||
DefGroupStatus = groups.Enabled | ||
DefListPerms = false | ||
SharedVisibility = "shared" | ||
MyVisibility = "mine" | ||
AllVisibility = "all" | ||
// ContentType represents JSON content type. | ||
ContentType = "application/json" | ||
|
||
// MaxNameSize limits name size to prevent making them too complex. | ||
MaxLimitSize = 100 | ||
MaxNameSize = 1024 | ||
MaxIDSize = 36 | ||
NameOrder = "name" | ||
IDOrder = "id" | ||
AscDir = "asc" | ||
DescDir = "desc" | ||
) | ||
|
||
// ValidateUUID validates UUID format. | ||
func ValidateUUID(extID string) (err error) { | ||
id, err := uuid.FromString(extID) | ||
if id.String() != extID || err != nil { | ||
return apiutil.ErrInvalidIDFormat | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// EncodeResponse encodes successful response. | ||
func EncodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { | ||
if ar, ok := response.(supermq.Response); ok { | ||
for k, v := range ar.Headers() { | ||
w.Header().Set(k, v) | ||
} | ||
w.Header().Set("Content-Type", ContentType) | ||
w.WriteHeader(ar.Code()) | ||
|
||
if ar.Empty() { | ||
return nil | ||
} | ||
} | ||
|
||
return json.NewEncoder(w).Encode(response) | ||
} | ||
|
||
// EncodeError encodes an error response. | ||
func EncodeError(_ context.Context, err error, w http.ResponseWriter) { | ||
var wrapper error | ||
if errors.Contains(err, apiutil.ErrValidation) { | ||
wrapper, err = errors.Unwrap(err) | ||
} | ||
|
||
w.Header().Set("Content-Type", ContentType) | ||
switch { | ||
case errors.Contains(err, svcerr.ErrAuthorization), | ||
errors.Contains(err, svcerr.ErrDomainAuthorization), | ||
errors.Contains(err, bootstrap.ErrExternalKey), | ||
errors.Contains(err, bootstrap.ErrExternalKeySecure): | ||
err = unwrap(err) | ||
w.WriteHeader(http.StatusForbidden) | ||
|
||
case errors.Contains(err, svcerr.ErrAuthentication), | ||
errors.Contains(err, apiutil.ErrBearerToken), | ||
errors.Contains(err, svcerr.ErrLogin): | ||
err = unwrap(err) | ||
w.WriteHeader(http.StatusUnauthorized) | ||
case errors.Contains(err, svcerr.ErrMalformedEntity), | ||
errors.Contains(err, apiutil.ErrMalformedPolicy), | ||
errors.Contains(err, apiutil.ErrMissingSecret), | ||
errors.Contains(err, errors.ErrMalformedEntity), | ||
errors.Contains(err, apiutil.ErrMissingID), | ||
errors.Contains(err, apiutil.ErrMissingName), | ||
errors.Contains(err, apiutil.ErrMissingAlias), | ||
errors.Contains(err, apiutil.ErrMissingEmail), | ||
errors.Contains(err, apiutil.ErrInvalidEmail), | ||
errors.Contains(err, apiutil.ErrMissingHost), | ||
errors.Contains(err, apiutil.ErrInvalidResetPass), | ||
errors.Contains(err, apiutil.ErrEmptyList), | ||
errors.Contains(err, apiutil.ErrMissingMemberKind), | ||
errors.Contains(err, apiutil.ErrMissingMemberType), | ||
errors.Contains(err, apiutil.ErrLimitSize), | ||
errors.Contains(err, apiutil.ErrBearerKey), | ||
errors.Contains(err, svcerr.ErrInvalidStatus), | ||
errors.Contains(err, apiutil.ErrNameSize), | ||
errors.Contains(err, apiutil.ErrInvalidIDFormat), | ||
errors.Contains(err, apiutil.ErrInvalidQueryParams), | ||
errors.Contains(err, apiutil.ErrMissingRelation), | ||
errors.Contains(err, apiutil.ErrValidation), | ||
errors.Contains(err, apiutil.ErrMissingPass), | ||
errors.Contains(err, apiutil.ErrMissingConfPass), | ||
errors.Contains(err, apiutil.ErrPasswordFormat), | ||
errors.Contains(err, svcerr.ErrInvalidRole), | ||
errors.Contains(err, svcerr.ErrInvalidPolicy), | ||
errors.Contains(err, apiutil.ErrInvitationState), | ||
errors.Contains(err, apiutil.ErrInvalidAPIKey), | ||
errors.Contains(err, svcerr.ErrViewEntity), | ||
errors.Contains(err, apiutil.ErrBootstrapState), | ||
errors.Contains(err, apiutil.ErrMissingCertData), | ||
errors.Contains(err, apiutil.ErrInvalidContact), | ||
errors.Contains(err, apiutil.ErrInvalidTopic), | ||
errors.Contains(err, bootstrap.ErrAddBootstrap), | ||
errors.Contains(err, apiutil.ErrInvalidCertData), | ||
errors.Contains(err, apiutil.ErrEmptyMessage), | ||
errors.Contains(err, apiutil.ErrInvalidLevel), | ||
errors.Contains(err, apiutil.ErrInvalidDirection), | ||
errors.Contains(err, apiutil.ErrInvalidEntityType), | ||
errors.Contains(err, apiutil.ErrMissingEntityType), | ||
errors.Contains(err, apiutil.ErrInvalidTimeFormat), | ||
errors.Contains(err, svcerr.ErrSearch), | ||
errors.Contains(err, apiutil.ErrEmptySearchQuery), | ||
errors.Contains(err, apiutil.ErrLenSearchQuery), | ||
errors.Contains(err, apiutil.ErrMissingDomainID), | ||
errors.Contains(err, certs.ErrFailedReadFromPKI), | ||
errors.Contains(err, apiutil.ErrMissingUsername), | ||
errors.Contains(err, apiutil.ErrMissingFirstName), | ||
errors.Contains(err, apiutil.ErrMissingLastName), | ||
errors.Contains(err, apiutil.ErrInvalidUsername), | ||
errors.Contains(err, apiutil.ErrMissingIdentity), | ||
errors.Contains(err, apiutil.ErrInvalidProfilePictureURL), | ||
errors.Contains(err, apiutil.ErrSelfParentingNotAllowed), | ||
errors.Contains(err, apiutil.ErrMissingChildrenGroupIDs), | ||
errors.Contains(err, apiutil.ErrMissingParentGroupID), | ||
errors.Contains(err, apiutil.ErrMissingConnectionType), | ||
errors.Contains(err, apiutil.ErrMissingRoleName), | ||
errors.Contains(err, apiutil.ErrMissingPolicyEntityType), | ||
errors.Contains(err, apiutil.ErrMissingRoleMembers): | ||
err = unwrap(err) | ||
w.WriteHeader(http.StatusBadRequest) | ||
|
||
case errors.Contains(err, svcerr.ErrCreateEntity), | ||
errors.Contains(err, svcerr.ErrUpdateEntity), | ||
errors.Contains(err, svcerr.ErrRemoveEntity), | ||
errors.Contains(err, svcerr.ErrEnableClient), | ||
errors.Contains(err, svcerr.ErrEnableUser), | ||
errors.Contains(err, svcerr.ErrDisableUser): | ||
err = unwrap(err) | ||
w.WriteHeader(http.StatusUnprocessableEntity) | ||
|
||
case errors.Contains(err, svcerr.ErrNotFound), | ||
errors.Contains(err, bootstrap.ErrBootstrap): | ||
err = unwrap(err) | ||
w.WriteHeader(http.StatusNotFound) | ||
|
||
case errors.Contains(err, errors.ErrStatusAlreadyAssigned), | ||
errors.Contains(err, svcerr.ErrInvitationAlreadyRejected), | ||
errors.Contains(err, svcerr.ErrInvitationAlreadyAccepted), | ||
errors.Contains(err, svcerr.ErrConflict): | ||
err = unwrap(err) | ||
w.WriteHeader(http.StatusConflict) | ||
|
||
case errors.Contains(err, apiutil.ErrUnsupportedContentType): | ||
err = unwrap(err) | ||
w.WriteHeader(http.StatusUnsupportedMediaType) | ||
|
||
default: | ||
w.WriteHeader(http.StatusInternalServerError) | ||
} | ||
|
||
if wrapper != nil { | ||
err = errors.Wrap(wrapper, err) | ||
} | ||
|
||
if errorVal, ok := err.(errors.Error); ok { | ||
if err := json.NewEncoder(w).Encode(errorVal); err != nil { | ||
w.WriteHeader(http.StatusInternalServerError) | ||
} | ||
} | ||
} | ||
|
||
func unwrap(err error) error { | ||
wrapper, err := errors.Unwrap(err) | ||
if wrapper != nil { | ||
return wrapper | ||
} | ||
return err | ||
} |
Oops, something went wrong.