api-auth.go (6642B)
1 // This file is part of taler-cashless2ecash. 2 // Copyright (C) 2024 Joel Häberli 3 // 4 // taler-cashless2ecash is free software: you can redistribute it and/or modify it 5 // under the terms of the GNU Affero General Public License as published 6 // by the Free Software Foundation, either version 3 of the License, 7 // or (at your option) any later version. 8 // 9 // taler-cashless2ecash is distributed in the hope that it will be useful, but 10 // WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 // Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 // 17 // SPDX-License-Identifier: AGPL3.0-or-later 18 19 package internal_api 20 21 import ( 22 internal_utils "c2ec/internal/utils" 23 "c2ec/pkg/config" 24 "c2ec/pkg/db" 25 "encoding/base64" 26 "errors" 27 "fmt" 28 "net/http" 29 "strconv" 30 "strings" 31 ) 32 33 const AUTHORIZATION_HEADER = "Authorization" 34 const BASIC_AUTH_PREFIX = "Basic " 35 36 // Authenticates the Exchange against C2EC 37 // returns true if authentication was successful, otherwise false 38 // when not successful, the api shall return immediately 39 // The exchange is specified to use basic auth 40 func AuthenticateExchange(req *http.Request) bool { 41 42 auth := req.Header.Get(AUTHORIZATION_HEADER) 43 if basicAuth, found := strings.CutPrefix(auth, BASIC_AUTH_PREFIX); found { 44 45 ba := fmt.Sprintf("%s:%s", config.CONFIG.Server.WireGateway.Username, config.CONFIG.Server.WireGateway.Password) 46 encoded := base64.StdEncoding.EncodeToString([]byte(ba)) 47 return encoded == basicAuth 48 } 49 return false 50 } 51 52 // Authenticates a terminal against C2EC 53 // returns true if authentication was successful, otherwise false 54 // when not successful, the api shall return immediately 55 // 56 // Terminals are authenticated using basic auth. 57 // The basic authorization header MUST be base64 encoded. 58 // The username part is the name of the provider (case sensitive) a '-' sign, followed 59 // by the id of the terminal, which is a number. 60 func AuthenticateTerminal(req *http.Request) bool { 61 62 auth := req.Header.Get(AUTHORIZATION_HEADER) 63 if basicAuth, found := strings.CutPrefix(auth, BASIC_AUTH_PREFIX); found { 64 65 decoded, err := base64.StdEncoding.DecodeString(basicAuth) 66 if err != nil { 67 internal_utils.LogWarn("auth", "failed decoding basic auth header from base64") 68 return false 69 } 70 71 username, password, err := parseBasicAuth(string(decoded)) 72 if err != nil { 73 internal_utils.LogWarn("auth", "failed parsing username password from basic auth") 74 return false 75 } 76 77 provider, terminalId, err := parseTerminalUser(username) 78 if err != nil { 79 internal_utils.LogWarn("auth", "failed parsing terminal from username in basic auth") 80 return false 81 } 82 internal_utils.LogInfo("auth", fmt.Sprintf("req=%s by terminal with id=%d, provider=%s", req.RequestURI, terminalId, provider)) 83 84 terminal, err := db.DB.GetTerminalById(terminalId) 85 if err != nil { 86 return false 87 } 88 89 if !terminal.Active { 90 internal_utils.LogWarn("auth", fmt.Sprintf("request from inactive terminal. id=%d", terminalId)) 91 return false 92 } 93 94 prvdr, err := db.DB.GetTerminalProviderByName(provider) 95 if err != nil { 96 internal_utils.LogWarn("auth", fmt.Sprintf("failed requesting provider by name %s", err.Error())) 97 return false 98 } 99 100 if terminal.ProviderId != prvdr.ProviderId { 101 internal_utils.LogWarn("auth", "terminal's provider id did not match provider id of supplied provider") 102 return false 103 } 104 105 return internal_utils.ValidPassword(password, terminal.AccessToken) 106 } 107 internal_utils.LogWarn("auth", "basic auth prefix did not match") 108 return false 109 } 110 111 func AuthenticateWirewatcher(req *http.Request) bool { 112 113 auth := req.Header.Get(AUTHORIZATION_HEADER) 114 if basicAuth, found := strings.CutPrefix(auth, BASIC_AUTH_PREFIX); found { 115 116 decoded, err := base64.StdEncoding.DecodeString(basicAuth) 117 if err != nil { 118 internal_utils.LogWarn("auth", "failed decoding basic auth header from base64") 119 return false 120 } 121 122 username, password, err := parseBasicAuth(string(decoded)) 123 if err != nil { 124 internal_utils.LogWarn("auth", "failed parsing username password from basic auth") 125 return false 126 } 127 128 if strings.EqualFold(username, config.CONFIG.Server.WireGateway.Username) && 129 strings.EqualFold(password, config.CONFIG.Server.WireGateway.Password) { 130 131 return true 132 } 133 } else { 134 internal_utils.LogWarn("auth", "expecting exact 'Basic' prefix!") 135 } 136 internal_utils.LogWarn("auth", "basic auth prefix did not match") 137 return false 138 } 139 140 func parseBasicAuth(basicAuth string) (string, string, error) { 141 142 parts := strings.Split(basicAuth, ":") 143 if len(parts) != 2 { 144 return "", "", errors.New("malformed basic auth") 145 } 146 return parts[0], parts[1], nil 147 } 148 149 // parses the username of the basic auth param of the terminal. 150 // the username has following format: 151 // 152 // [PROVIDER_NAME]-[TERMINAL_ID] 153 func parseTerminalUser(username string) (string, int, error) { 154 155 parts := strings.Split(username, "-") 156 if len(parts) != 2 { 157 return "", -1, errors.New("malformed basic auth username") 158 } 159 160 providerName := parts[0] 161 terminalId, err := strconv.Atoi(parts[1]) 162 if err != nil { 163 return "", -1, errors.New("malformed basic auth username") 164 } 165 166 return providerName, terminalId, nil 167 } 168 169 // Parses the terminal id from the token. 170 // This function is used to determine the terminal 171 // which orchestrates the withdrawal. 172 func parseTerminalId(req *http.Request) int { 173 auth := req.Header.Get(AUTHORIZATION_HEADER) 174 if basicAuth, found := strings.CutPrefix(auth, BASIC_AUTH_PREFIX); found { 175 176 decoded, err := base64.StdEncoding.DecodeString(basicAuth) 177 if err != nil { 178 return -1 179 } 180 181 username, _, err := parseBasicAuth(string(decoded)) 182 if err != nil { 183 return -1 184 } 185 186 _, terminalId, err := parseTerminalUser(username) 187 if err != nil { 188 return -1 189 } 190 191 return terminalId 192 } 193 194 return -1 195 } 196 197 func parseProvider(req *http.Request) (*db.Provider, error) { 198 199 auth := req.Header.Get(AUTHORIZATION_HEADER) 200 if basicAuth, found := strings.CutPrefix(auth, BASIC_AUTH_PREFIX); found { 201 202 decoded, err := base64.StdEncoding.DecodeString(basicAuth) 203 if err != nil { 204 return nil, err 205 } 206 207 username, _, err := parseBasicAuth(string(decoded)) 208 if err != nil { 209 return nil, err 210 } 211 212 providerName, _, err := parseTerminalUser(username) 213 if err != nil { 214 return nil, err 215 } 216 217 p, err := db.DB.GetTerminalProviderByName(providerName) 218 if err != nil { 219 return nil, err 220 } 221 222 return p, nil 223 } 224 225 return nil, errors.New("authorization header did not match expectations") 226 }