github.com/greenpau/go-authcrunch@v1.1.4/pkg/authn/respond_json.go (about) 1 // Copyright 2022 Paul Greenberg greenpau@outlook.com 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package authn 16 17 import ( 18 "context" 19 "encoding/json" 20 "net/http" 21 "strings" 22 "time" 23 24 "github.com/greenpau/go-authcrunch/pkg/requests" 25 addrutil "github.com/greenpau/go-authcrunch/pkg/util/addr" 26 "go.uber.org/zap" 27 ) 28 29 // AccessDeniedResponse is the access denied response. 30 type AccessDeniedResponse struct { 31 Error bool `json:"error,omitempty" xml:"error,omitempty" yaml:"error,omitempty"` 32 Message string `json:"message,omitempty" xml:"message,omitempty" yaml:"message,omitempty"` 33 Timestamp string `json:"timestamp,omitempty" xml:"timestamp,omitempty" yaml:"timestamp,omitempty"` 34 } 35 36 func newAccessDeniedResponse(msg string) *AccessDeniedResponse { 37 return &AccessDeniedResponse{ 38 Error: true, 39 Message: msg, 40 Timestamp: time.Now().UTC().Format(time.RFC3339Nano), 41 } 42 } 43 44 func (p *Portal) handleJSONErrorWithLog(ctx context.Context, w http.ResponseWriter, _ *http.Request, rr *requests.Request, code int, msg string) error { 45 p.logger.Warn( 46 "Access denied", 47 zap.String("session_id", rr.Upstream.SessionID), 48 zap.String("request_id", rr.ID), 49 zap.String("error", msg), 50 ) 51 switch code { 52 case http.StatusBadRequest: 53 return p.handleJSONError(ctx, w, code, "Bad Request") 54 case http.StatusForbidden: 55 return p.handleJSONError(ctx, w, code, "Forbidden") 56 case http.StatusInternalServerError: 57 return p.handleJSONError(ctx, w, code, "Internal Server Error") 58 } 59 return p.handleJSONError(ctx, w, code, "Access denied") 60 } 61 62 func (p *Portal) handleJSONError(_ context.Context, w http.ResponseWriter, code int, msg string) error { 63 resp := newAccessDeniedResponse(msg) 64 respBytes, _ := json.Marshal(resp) 65 w.WriteHeader(code) 66 w.Write(respBytes) 67 return nil 68 } 69 70 func (p *Portal) handleJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request) error { 71 p.disableClientCache(w) 72 p.injectSessionID(ctx, w, r, rr) 73 w.Header().Set("Content-Type", "application/json") 74 p.logger.Debug( 75 "Received JSON API request", 76 zap.String("session_id", rr.Upstream.SessionID), 77 zap.String("request_id", rr.ID), 78 zap.String("url_path", r.URL.Path), 79 zap.String("source_address", addrutil.GetSourceAddress(r)), 80 ) 81 82 usr, err := p.authorizeRequest(ctx, w, r, rr) 83 if err != nil { 84 return p.handleJSONErrorWithLog(ctx, w, r, rr, http.StatusUnauthorized, err.Error()) 85 } 86 87 switch { 88 case strings.Contains(r.URL.Path, "/login"): 89 return p.handleJSONLogin(ctx, w, r, rr) 90 case strings.Contains(r.URL.Path, "/whoami"): 91 return p.handleJSONWhoami(ctx, w, r, rr, usr) 92 case strings.Contains(r.URL.Path, "/beacon"): 93 return p.handleJSONBeacon(ctx, w, r, rr, usr) 94 } 95 96 if usr != nil { 97 p.logger.Debug( 98 "No route found", 99 zap.String("session_id", rr.Upstream.SessionID), 100 zap.String("request_id", rr.ID), 101 zap.Any("user", usr.Claims), 102 ) 103 return p.handleJSONError(ctx, w, http.StatusBadRequest, "Bad Request") 104 } 105 return p.handleJSONError(ctx, w, http.StatusUnauthorized, "Access denied") 106 }