mirror of
https://github.com/apernet/hysteria.git
synced 2025-04-03 04:27:39 +03:00
feat: HTTP auth
This commit is contained in:
parent
cbfb1998a5
commit
d3db1e4a1d
6 changed files with 172 additions and 3 deletions
|
@ -88,10 +88,16 @@ type serverConfigBandwidth struct {
|
|||
Down string `mapstructure:"down"`
|
||||
}
|
||||
|
||||
type serverConfigAuthHTTP struct {
|
||||
URL string `mapstructure:"url"`
|
||||
Insecure bool `mapstructure:"insecure"`
|
||||
}
|
||||
|
||||
type serverConfigAuth struct {
|
||||
Type string `mapstructure:"type"`
|
||||
Password string `mapstructure:"password"`
|
||||
UserPass map[string]string `mapstructure:"userpass"`
|
||||
Type string `mapstructure:"type"`
|
||||
Password string `mapstructure:"password"`
|
||||
UserPass map[string]string `mapstructure:"userpass"`
|
||||
HTTP serverConfigAuthHTTP `mapstructure:"http"`
|
||||
}
|
||||
|
||||
type serverConfigResolverTCP struct {
|
||||
|
@ -393,6 +399,12 @@ func (c *serverConfig) fillAuthenticator(hyConfig *server.Config) error {
|
|||
}
|
||||
hyConfig.Authenticator = &auth.UserPassAuthenticator{Users: c.Auth.UserPass}
|
||||
return nil
|
||||
case "http", "https":
|
||||
if c.Auth.HTTP.URL == "" {
|
||||
return configError{Field: "auth.http.url", Err: errors.New("empty auth http url")}
|
||||
}
|
||||
hyConfig.Authenticator = auth.NewHTTPAuthenticator(c.Auth.HTTP.URL, c.Auth.HTTP.Insecure)
|
||||
return nil
|
||||
default:
|
||||
return configError{Field: "auth.type", Err: errors.New("unsupported auth type")}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,10 @@ func TestServerConfig(t *testing.T) {
|
|||
"lol": "kek",
|
||||
"foo": "bar",
|
||||
},
|
||||
HTTP: serverConfigAuthHTTP{
|
||||
URL: "http://127.0.0.1:5000/auth",
|
||||
Insecure: true,
|
||||
},
|
||||
},
|
||||
Resolver: serverConfigResolver{
|
||||
Type: "udp",
|
||||
|
|
|
@ -46,6 +46,9 @@ auth:
|
|||
yolo: swag
|
||||
lol: kek
|
||||
foo: bar
|
||||
http:
|
||||
url: http://127.0.0.1:5000/auth
|
||||
insecure: true
|
||||
|
||||
resolver:
|
||||
type: udp
|
||||
|
|
90
extras/auth/http.go
Normal file
90
extras/auth/http.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/server"
|
||||
)
|
||||
|
||||
const (
|
||||
httpAuthTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
var _ server.Authenticator = &HTTPAuthenticator{}
|
||||
|
||||
var errInvalidStatusCode = errors.New("invalid status code")
|
||||
|
||||
type HTTPAuthenticator struct {
|
||||
Client *http.Client
|
||||
URL string
|
||||
}
|
||||
|
||||
func NewHTTPAuthenticator(url string, insecure bool) *HTTPAuthenticator {
|
||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||
tr.TLSClientConfig = &tls.Config{
|
||||
InsecureSkipVerify: insecure,
|
||||
}
|
||||
return &HTTPAuthenticator{
|
||||
Client: &http.Client{
|
||||
Transport: tr,
|
||||
Timeout: httpAuthTimeout,
|
||||
},
|
||||
URL: url,
|
||||
}
|
||||
}
|
||||
|
||||
type httpAuthRequest struct {
|
||||
Addr string `json:"addr"`
|
||||
Auth string `json:"auth"`
|
||||
Tx uint64 `json:"tx"`
|
||||
}
|
||||
|
||||
type httpAuthResponse struct {
|
||||
OK bool `json:"ok"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func (a *HTTPAuthenticator) post(req *httpAuthRequest) (*httpAuthResponse, error) {
|
||||
bs, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := a.Client.Post(a.URL, "application/json", bytes.NewReader(bs))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errInvalidStatusCode
|
||||
}
|
||||
respData, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var authResp httpAuthResponse
|
||||
err = json.Unmarshal(respData, &authResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &authResp, nil
|
||||
}
|
||||
|
||||
func (a *HTTPAuthenticator) Authenticate(addr net.Addr, auth string, tx uint64) (ok bool, id string) {
|
||||
req := &httpAuthRequest{
|
||||
Addr: addr.String(),
|
||||
Auth: auth,
|
||||
Tx: tx,
|
||||
}
|
||||
resp, err := a.post(req)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
return resp.OK, resp.ID
|
||||
}
|
36
extras/auth/http_test.go
Normal file
36
extras/auth/http_test.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHTTPAuthenticator(t *testing.T) {
|
||||
// Run the Python test auth server
|
||||
cmd := exec.Command("python", "http_test.py")
|
||||
err := cmd.Start()
|
||||
assert.NoError(t, err)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(1 * time.Second) // Wait for the server to start
|
||||
|
||||
auth := NewHTTPAuthenticator("http://127.0.0.1:5000/auth", false)
|
||||
|
||||
ok, id := auth.Authenticate(&net.UDPAddr{
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Port: 34567,
|
||||
}, "idk", 123)
|
||||
assert.False(t, ok)
|
||||
assert.Equal(t, "", id)
|
||||
|
||||
ok, id = auth.Authenticate(&net.UDPAddr{
|
||||
IP: net.ParseIP("123.123.123.123"),
|
||||
Port: 5566,
|
||||
}, "wahaha", 12345)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "some_unique_id", id)
|
||||
}
|
24
extras/auth/http_test.py
Normal file
24
extras/auth/http_test.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
from flask import Flask, request, jsonify
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route("/auth", methods=["POST"])
|
||||
def auth():
|
||||
data = request.json
|
||||
|
||||
if data is None:
|
||||
return jsonify({"ok": False, "id": ""}), 400
|
||||
|
||||
addr = data.get("addr", "")
|
||||
auth = data.get("auth", "")
|
||||
tx = data.get("tx", 0)
|
||||
|
||||
if addr == "123.123.123.123:5566" and auth == "wahaha" and tx == 12345:
|
||||
return jsonify({"ok": True, "id": "some_unique_id"})
|
||||
else:
|
||||
return jsonify({"ok": False, "id": ""})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
Loading…
Add table
Add a link
Reference in a new issue