github.com/greenpau/go-authcrunch@v1.1.4/pkg/authn/handle_basic_login.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/base64" 20 "fmt" 21 "github.com/greenpau/go-authcrunch/pkg/requests" 22 "net/http" 23 "strings" 24 ) 25 26 func (p *Portal) handleHTTPBasicLogin(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request) error { 27 pathPrefix := "/basic/login/" 28 p.disableClientCache(w) 29 p.injectRedirectURL(ctx, w, r, rr) 30 // The GET request arrives to `/basic/login/<realm>`. 31 // Extract realm name and process as a regular login. 32 i := strings.Index(r.URL.Path, pathPrefix) 33 if i < 0 { 34 return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest) 35 } 36 realm := strings.TrimPrefix(r.URL.Path[i:], pathPrefix) 37 credentials, err := parseBasicAuthHeader(r) 38 w.Header().Set("Content-Type", "text/plain") 39 if err != nil { 40 w.WriteHeader(http.StatusBadRequest) 41 w.Write([]byte(http.StatusText(http.StatusBadRequest))) 42 return nil 43 } 44 if credentials == nil { 45 w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=\"%s\"", realm)) 46 w.WriteHeader(http.StatusUnauthorized) 47 w.Write([]byte("Authorization Required")) 48 return nil 49 } 50 if v, exists := credentials["realm"]; exists { 51 realm = v 52 } else { 53 credentials["realm"] = realm 54 } 55 if realm == "" { 56 w.WriteHeader(http.StatusBadRequest) 57 w.Write([]byte(http.StatusText(http.StatusBadRequest))) 58 return nil 59 } 60 if err := p.authenticateLoginRequest(ctx, w, r, rr, credentials); err != nil { 61 return p.handleJSONErrorWithLog(ctx, w, r, rr, rr.Response.Code, err.Error()) 62 } 63 if err := p.authorizeLoginRequest(ctx, w, r, rr); err != nil { 64 return p.handleJSONErrorWithLog(ctx, w, r, rr, rr.Response.Code, err.Error()) 65 } 66 w.WriteHeader(rr.Response.Code) 67 return nil 68 } 69 70 func parseBasicAuthHeader(r *http.Request) (map[string]string, error) { 71 kv := make(map[string]string) 72 headers := strings.Split(r.Header.Get("Authorization"), ",") 73 if len(headers) == 0 { 74 return nil, nil 75 } 76 for _, header := range headers { 77 header = strings.TrimSpace(header) 78 switch { 79 case strings.HasPrefix(header, "Basic") || strings.HasPrefix(header, "basic"): 80 arr := strings.SplitN(header, " ", 2) 81 if len(arr) != 2 { 82 return nil, fmt.Errorf("invalid authorization header") 83 } 84 arrDecoded, err := base64.StdEncoding.DecodeString(arr[1]) 85 if err != nil { 86 return nil, err 87 } 88 creds := strings.SplitN(string(arrDecoded), ":", 2) 89 kv["username"] = creds[0] 90 kv["password"] = creds[1] 91 case strings.HasPrefix(header, "Realm") || strings.HasPrefix(header, "realm"): 92 arr := strings.SplitN(header, "=", 2) 93 if len(arr) != 2 { 94 return nil, fmt.Errorf("invalid authorization header") 95 } 96 kv["realm"] = arr[1] 97 } 98 } 99 if _, exists := kv["username"]; !exists { 100 return nil, nil 101 } 102 return kv, nil 103 }