feat: Improved bypass
parent
217c90d0cf
commit
a33b79712f
|
@ -9,6 +9,7 @@ RUN CGO_ENABLED=0 go build -ldflags "-s -w"
|
|||
|
||||
FROM scratch
|
||||
|
||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY --from=build /src/login /login
|
||||
COPY --from=build /src/.env /.env
|
||||
|
||||
|
|
487
main.go
487
main.go
|
@ -1,29 +1,28 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/rsa"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/go-redis/redis"
|
||||
"github.com/ttacon/libphonenumber"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/go-redis/redis"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/ttacon/libphonenumber"
|
||||
)
|
||||
|
||||
var listen string
|
||||
|
@ -44,63 +43,63 @@ var db *sql.DB
|
|||
var redisClient *redis.Client
|
||||
|
||||
func main() {
|
||||
// Load .env
|
||||
err := godotenv.Load()
|
||||
if err != nil {
|
||||
log.Fatal("Error loading .env file")
|
||||
}
|
||||
listen = os.Getenv("LISTEN")
|
||||
postgres = os.Getenv("POSTGRES")
|
||||
redisHost = os.Getenv("REDIS")
|
||||
// Load .env
|
||||
err := godotenv.Load()
|
||||
if err != nil {
|
||||
log.Fatal("Error loading .env file")
|
||||
}
|
||||
listen = os.Getenv("LISTEN")
|
||||
postgres = os.Getenv("POSTGRES")
|
||||
redisHost = os.Getenv("REDIS")
|
||||
|
||||
ttl, err = time.ParseDuration(os.Getenv("TTL"))
|
||||
if err != nil {
|
||||
log.Fatal("Error parsing ttl")
|
||||
}
|
||||
ttl, err = time.ParseDuration(os.Getenv("TTL"))
|
||||
if err != nil {
|
||||
log.Fatal("Error parsing ttl")
|
||||
}
|
||||
|
||||
messagingSID = os.Getenv("MESSAGING_SID")
|
||||
twilioSID = os.Getenv("TWILIO_SID")
|
||||
twilioToken = os.Getenv("TWILIO_TOKEN")
|
||||
messagingSID = os.Getenv("MESSAGING_SID")
|
||||
twilioSID = os.Getenv("TWILIO_SID")
|
||||
twilioToken = os.Getenv("TWILIO_TOKEN")
|
||||
|
||||
dummyToken = "{\"userid\":\"dummy\",\"clientid\":\"dummy\"}"
|
||||
coreURL = os.Getenv("CORE_URL")
|
||||
dummyToken = "{\"userid\":\"dummy\",\"clientid\":\"dummy\"}"
|
||||
coreURL = os.Getenv("CORE_URL")
|
||||
|
||||
// Load RSA private key
|
||||
privateKeyBytes, err := ioutil.ReadFile("key")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Load RSA private key
|
||||
privateKeyBytes, err := ioutil.ReadFile("key")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
privateKey, err = jwt.ParseRSAPrivateKeyFromPEM(privateKeyBytes)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
privateKey, err = jwt.ParseRSAPrivateKeyFromPEM(privateKeyBytes)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Postgres
|
||||
log.Printf("connecting to postgres %s", postgres)
|
||||
db, err = sql.Open("postgres", postgres)
|
||||
// Postgres
|
||||
log.Printf("connecting to postgres %s", postgres)
|
||||
db, err = sql.Open("postgres", postgres)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Redis
|
||||
redisClient = redis.NewClient(&redis.Options{
|
||||
Addr: redisHost,
|
||||
Password: "",
|
||||
DB: 1,
|
||||
})
|
||||
// Redis
|
||||
redisClient = redis.NewClient(&redis.Options{
|
||||
Addr: redisHost,
|
||||
Password: "",
|
||||
DB: 1,
|
||||
})
|
||||
|
||||
// Routes
|
||||
// Routes
|
||||
router := httprouter.New()
|
||||
|
||||
router.POST("/login", Login);
|
||||
router.POST("/init", InitRequest)
|
||||
router.POST("/verify", VerifyCode)
|
||||
router.POST("/register/:code/:nonce", CreateUser)
|
||||
router.POST("/init", InitRequest)
|
||||
router.POST("/init/bypass", InitRequestBypass)
|
||||
router.POST("/verify", VerifyCode)
|
||||
router.POST("/register/:code/:nonce", CreateUser)
|
||||
|
||||
// Start server
|
||||
log.Printf("starting server on %s", listen)
|
||||
// Start server
|
||||
log.Printf("starting server on %s", listen)
|
||||
log.Fatal(http.ListenAndServe(listen, router))
|
||||
}
|
||||
|
||||
|
@ -119,249 +118,255 @@ func RandomHex() (string, error) {
|
|||
}
|
||||
|
||||
type InitRequestBody struct {
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
}
|
||||
|
||||
func InitRequest(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
// Get request body
|
||||
req := InitRequestBody{}
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
// Get request body
|
||||
req := InitRequestBody{}
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&req)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure phone number is legitimate
|
||||
phone, err := ParsePhone(req.PhoneNumber)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate OTP code
|
||||
c, err := rand.Int(rand.Reader, big.NewInt(1000000))
|
||||
code := fmt.Sprintf("%06d", c)
|
||||
|
||||
// Generate nonce
|
||||
b := make([]byte, 16)
|
||||
_, err = rand.Read(b)
|
||||
// Make sure phone number is legitimate
|
||||
phone, err := ParsePhone(req.PhoneNumber)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
bytes := hex.EncodeToString(b)
|
||||
|
||||
// Set code-nonce pair in redis first
|
||||
redisClient.Set(code + "nonce", bytes, ttl)
|
||||
// Set code-phone_number pair
|
||||
redisClient.Set(code + "phone", phone, ttl)
|
||||
// Generate OTP code
|
||||
c, err := rand.Int(rand.Reader, big.NewInt(1000000))
|
||||
code := fmt.Sprintf("%06d", c)
|
||||
|
||||
// Send SMS via Twilio
|
||||
data := url.Values {}
|
||||
data.Set("MessagingServiceSid", messagingSID)
|
||||
data.Set("To", phone)
|
||||
data.Set("Body", fmt.Sprintf("Your OTP for Beep is %s", code))
|
||||
|
||||
url := fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json", twilioSID)
|
||||
twilioReq, err := http.NewRequest("POST", url, strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
// Generate nonce
|
||||
b := make([]byte, 16)
|
||||
_, err = rand.Read(b)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
twilioReq.SetBasicAuth(twilioSID, twilioToken)
|
||||
twilioReq.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
}
|
||||
bytes := hex.EncodeToString(b)
|
||||
|
||||
// Twilio uses self-signed certs
|
||||
transport := &http.Transport {
|
||||
TLSClientConfig: &tls.Config{ InsecureSkipVerify: true },
|
||||
}
|
||||
client := &http.Client{ Transport: transport }
|
||||
resp, err := client.Do(twilioReq)
|
||||
// Set code-nonce pair in redis first
|
||||
redisClient.Set(code+"nonce", bytes, ttl)
|
||||
// Set code-phone_number pair
|
||||
redisClient.Set(code+"phone", phone, ttl)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
// Send SMS via Twilio
|
||||
data := url.Values{}
|
||||
data.Set("MessagingServiceSid", messagingSID)
|
||||
data.Set("To", phone)
|
||||
data.Set("Body", fmt.Sprintf("Your OTP for Beep is %s", code))
|
||||
|
||||
url := fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json", twilioSID)
|
||||
twilioReq, err := http.NewRequest("POST", url, strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
twilioReq.SetBasicAuth(twilioSID, twilioToken)
|
||||
twilioReq.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
// Return nonce
|
||||
w.Write([]byte(bytes))
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(twilioReq)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Return nonce
|
||||
w.Write([]byte(bytes))
|
||||
}
|
||||
|
||||
func InitRequestBypass(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
// Get request body
|
||||
req := InitRequestBody{}
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&req)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure phone number is legitimate
|
||||
phone, err := ParsePhone(req.PhoneNumber)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate nonce
|
||||
b := make([]byte, 16)
|
||||
_, err = rand.Read(b)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
bytes := hex.EncodeToString(b)
|
||||
|
||||
code := "000000"
|
||||
// Set code-nonce pair in redis first
|
||||
redisClient.Set(code+"nonce", bytes, ttl)
|
||||
// Set code-phone_number pair
|
||||
redisClient.Set(code+"phone", phone, ttl)
|
||||
|
||||
// Return nonce
|
||||
w.Write([]byte(bytes))
|
||||
}
|
||||
|
||||
type VerifyRequestBody struct {
|
||||
Code string `json:"code"`
|
||||
Nonce string `json:"nonce"`
|
||||
ClientId string `json:"clientid"`
|
||||
Code string `json:"code"`
|
||||
Nonce string `json:"nonce"`
|
||||
ClientId string `json:"clientid"`
|
||||
}
|
||||
|
||||
func VerifyCode(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
// Get request body
|
||||
req := VerifyRequestBody{}
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
// Get request body
|
||||
req := VerifyRequestBody{}
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&req)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Get nonce
|
||||
storedNonce, err := redisClient.Get(req.Code + "nonce").Result()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
// Get nonce
|
||||
storedNonce, err := redisClient.Get(req.Code + "nonce").Result()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Delete nonce
|
||||
_, err = redisClient.Del(req.Code + "nonce").Result()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
// Delete nonce
|
||||
_, err = redisClient.Del(req.Code + "nonce").Result()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check nonce
|
||||
if req.Nonce != storedNonce {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
// Check nonce
|
||||
if req.Nonce != storedNonce {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get stored phone number
|
||||
phoneNumber, err := redisClient.Get(req.Code + "phone").Result()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
// Get stored phone number
|
||||
phoneNumber, err := redisClient.Get(req.Code + "phone").Result()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Delete stored phone number
|
||||
_, err = redisClient.Del(req.Code + "phone").Result()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
// Delete stored phone number
|
||||
_, err = redisClient.Del(req.Code + "phone").Result()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Generate (potential) User ID
|
||||
userHex, err := RandomHex()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
// Generate (potential) User ID
|
||||
userHex, err := RandomHex()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
userIDPotential := "u-" + userHex
|
||||
}
|
||||
userIDPotential := "u-" + userHex
|
||||
|
||||
// Check for existing user
|
||||
var userID string
|
||||
err = db.QueryRow(`
|
||||
// Check for existing user
|
||||
var userID string
|
||||
err = db.QueryRow(`
|
||||
INSERT INTO "user" (id, first_name, last_name, phone_number)
|
||||
VALUES ($1, '', '', $2)
|
||||
ON CONFLICT(phone_number)
|
||||
DO UPDATE SET phone_number=EXCLUDED.phone_number
|
||||
RETURNING id
|
||||
`, userIDPotential, phoneNumber).Scan(&userID)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate JWT
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims {
|
||||
"userid": userID,
|
||||
"clientid": req.ClientId,
|
||||
})
|
||||
|
||||
tokenString, err := token.SignedString(privateKey)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write([]byte(tokenString))
|
||||
}
|
||||
|
||||
type LoginData struct {
|
||||
ID string `json:"userid"`
|
||||
Client string `json:"clientid"`
|
||||
}
|
||||
|
||||
func Login(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
login := LoginData {}
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&login)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims {
|
||||
"userid": login.ID,
|
||||
"clientid": login.Client,
|
||||
})
|
||||
// Generate JWT
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
|
||||
"userid": userID,
|
||||
"clientid": req.ClientId,
|
||||
})
|
||||
|
||||
tokenString, err := token.SignedString(privateKey)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
tokenString, err := token.SignedString(privateKey)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write([]byte(tokenString))
|
||||
w.Write([]byte(tokenString))
|
||||
}
|
||||
|
||||
func CreateUser(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
code := p.ByName("code")
|
||||
nonce := p.ByName("nonce")
|
||||
code := p.ByName("code")
|
||||
nonce := p.ByName("nonce")
|
||||
|
||||
// Get nonce
|
||||
storedNonce, err := redisClient.Get(code + "nonce").Result()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
// Get nonce
|
||||
storedNonce, err := redisClient.Get(code + "nonce").Result()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Delete nonce
|
||||
_, err = redisClient.Del(code + "nonce").Result()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
// Delete nonce
|
||||
_, err = redisClient.Del(code + "nonce").Result()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check nonce
|
||||
if nonce != storedNonce {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
// Check nonce
|
||||
if nonce != storedNonce {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Delete phone number
|
||||
_, err = redisClient.Del(code + "phone").Result()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
// Delete phone number
|
||||
_, err = redisClient.Del(code + "phone").Result()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
proxyReq, err := http.NewRequest(r.Method, coreURL, r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
proxyReq, err := http.NewRequest(r.Method, coreURL, r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
proxyReq.Header.Set("X-User-Claim", dummyToken)
|
||||
for header, values := range r.Header {
|
||||
for _, value := range values {
|
||||
proxyReq.Header.Add(header, value)
|
||||
}
|
||||
}
|
||||
proxyReq.Header.Set("X-User-Claim", dummyToken)
|
||||
for header, values := range r.Header {
|
||||
for _, value := range values {
|
||||
proxyReq.Header.Add(header, value)
|
||||
}
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
proxyRes, err := client.Do(proxyReq)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
client := &http.Client{}
|
||||
proxyRes, err := client.Do(proxyReq)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
for header, values := range proxyRes.Header {
|
||||
for _, value := range values {
|
||||
w.Header().Add(header, value)
|
||||
}
|
||||
}
|
||||
io.Copy(w, proxyRes.Body)
|
||||
proxyRes.Body.Close()
|
||||
for header, values := range proxyRes.Header {
|
||||
for _, value := range values {
|
||||
w.Header().Add(header, value)
|
||||
}
|
||||
}
|
||||
io.Copy(w, proxyRes.Body)
|
||||
proxyRes.Body.Close()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue