From feec7505737c65b0661014715bdb3f95287eb07d Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 30 Jul 2023 08:12:18 +0800 Subject: [PATCH] Support Bearer token authentication and test. --- .github/workflows/test.yml | 38 ++- .gitignore | 1 + DEVELOPER.md | 2 +- Dockerfile | 1 + Dockerfile.script | 2 +- Makefile | 9 +- README.md | 7 +- mgmt/bootstrap | 1 + platform/dvr-local-disk.go | 77 ++--- platform/dvr-tencent-cos.go | 61 ++-- platform/dvr-tencent-vod.go | 53 +--- platform/forward.go | 38 +-- platform/main.go | 5 +- platform/service.go | 158 +++------- platform/srs-hooks.go | 85 ++---- platform/utils.go | 61 ++++ platform/virtual-live-stream.go | 60 ++-- scripts/setup-ubuntu/install.sh | 5 +- test/Makefile | 11 + test/api_test.go | 41 +++ test/go.mod | 8 + test/go.sum | 4 + test/main_test.go | 248 ++++++++++++++++ .../github.com/joho/godotenv/.gitignore | 1 + test/vendor/github.com/joho/godotenv/LICENCE | 23 ++ .../vendor/github.com/joho/godotenv/README.md | 202 +++++++++++++ test/vendor/github.com/joho/godotenv/go.mod | 3 + .../github.com/joho/godotenv/godotenv.go | 228 +++++++++++++++ .../vendor/github.com/joho/godotenv/parser.go | 271 ++++++++++++++++++ .../github.com/ossrs/go-oryx-lib/LICENSE | 21 ++ .../ossrs/go-oryx-lib/errors/LICENSE | 23 ++ .../ossrs/go-oryx-lib/errors/README.md | 52 ++++ .../ossrs/go-oryx-lib/errors/errors.go | 270 +++++++++++++++++ .../ossrs/go-oryx-lib/errors/stack.go | 187 ++++++++++++ .../ossrs/go-oryx-lib/logger/go17.go | 86 ++++++ .../ossrs/go-oryx-lib/logger/logger.go | 239 +++++++++++++++ .../ossrs/go-oryx-lib/logger/pre_go17.go | 34 +++ test/vendor/modules.txt | 7 + ui/src/pages/Settings.js | 87 ++---- ui/src/resources/locale.json | 26 +- 40 files changed, 2237 insertions(+), 499 deletions(-) create mode 100644 test/Makefile create mode 100644 test/api_test.go create mode 100644 test/go.mod create mode 100644 test/go.sum create mode 100644 test/main_test.go create mode 100644 test/vendor/github.com/joho/godotenv/.gitignore create mode 100644 test/vendor/github.com/joho/godotenv/LICENCE create mode 100644 test/vendor/github.com/joho/godotenv/README.md create mode 100644 test/vendor/github.com/joho/godotenv/go.mod create mode 100644 test/vendor/github.com/joho/godotenv/godotenv.go create mode 100644 test/vendor/github.com/joho/godotenv/parser.go create mode 100644 test/vendor/github.com/ossrs/go-oryx-lib/LICENSE create mode 100644 test/vendor/github.com/ossrs/go-oryx-lib/errors/LICENSE create mode 100644 test/vendor/github.com/ossrs/go-oryx-lib/errors/README.md create mode 100644 test/vendor/github.com/ossrs/go-oryx-lib/errors/errors.go create mode 100644 test/vendor/github.com/ossrs/go-oryx-lib/errors/stack.go create mode 100644 test/vendor/github.com/ossrs/go-oryx-lib/logger/go17.go create mode 100644 test/vendor/github.com/ossrs/go-oryx-lib/logger/logger.go create mode 100644 test/vendor/github.com/ossrs/go-oryx-lib/logger/pre_go17.go create mode 100644 test/vendor/modules.txt diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7697d86d..8b18f9b6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -68,18 +68,48 @@ jobs: docker tag platform:latest ossrs/srs-cloud:$SRS_TAG && docker tag platform:latest registry.cn-hangzhou.aliyuncs.com/ossrs/srs-cloud:$SRS_TAG && docker images - - name: Test build install package + - name: Build package run: | bash scripts/setup-ubuntu/build.sh --language zh --version $SRS_TAG \ - --output build --extract + --output build --extract du -sh build/* - - name: Test install package + - name: Install package run: | sudo bash build/srs-cloud/scripts/setup-ubuntu/install.sh --verbose && du -sh /usr/local/srs-cloud/* - - name: Check installed package + - name: Check service run: | + # Wait for service ready. + make -j -C test + ./test/srs-cloud.test -test.v -srs-log -endpoint http://localhost:2022 \ + -wait-ready -check-api-secret=false \ + -test.run TestApi_Empty + + echo "Record log of services." + docker ps -a systemctl status srs-cloud + journalctl -u srs-cloud -f >journalctl.log 2>&1 & pid_journalctl=$! + docker logs -f srs-cloud >docker.log 2>&1 & pid_docker=$! + - name: Test service + run: | + # We will handle the error by ourselves. + set +e + + SRS_PLATFORM_SECRET=$(docker exec srs-cloud redis-cli hget SRS_PLATFORM_SECRET token) + ./test/srs-cloud.test -test.v -wait-ready -srs-log -endpoint http://localhost:2022 \ + -wait-ready -check-api-secret=true -api-secret=$SRS_PLATFORM_SECRET \ + -init-password + ret=$?; echo "Test with ${SRS_PLATFORM_SECRET} result: $ret" + + echo "Stop service" + sudo systemctl stop srs-cloud + kill $pid_journalctl 2>/dev/null + kill $pid_docker 2>/dev/null + + echo "Log of journalctl.log" && cat journalctl.log + echo "Log of docker.log" && cat docker.log + + exit $ret final: name: Final diff --git a/.gitignore b/.gitignore index 8e393e8f..7678d655 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,4 @@ t.md /build /*.tar /srs-cloud2 +/test/srs-cloud.test diff --git a/DEVELOPER.md b/DEVELOPER.md index fd40c46a..20a03d22 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -221,7 +221,6 @@ Platform: * `/terraform/v1/mgmt/beian/query` Query the beian information. * `/terraform/v1/mgmt/beian/update` Update the beian information. * `/terraform/v1/mgmt/secret/query` Query the api secret for OpenAPI. -* `/terraform/v1/mgmt/secret/token` Create token for OpenAPI. * `/terraform/v1/mgmt/nginx/hls` Update NGINX config, to enable HLS delivery. * `/terraform/v1/host/versions` Public version api. * `/terraform/v1/releases` Version management for all components. @@ -282,6 +281,7 @@ Also provided by platform for static Files: * `/terraform/v1/mgmt/upgrade` Upgrade the mgmt to latest version. * `/terraform/v1/mgmt/containers` Query SRS container. * `/terraform/v1/host/exec` Exec command sync, response the stdout and stderr. +* `/terraform/v1/mgmt/secret/token` Create token for OpenAPI. ## Depends diff --git a/Dockerfile b/Dockerfile index 9a47a638..e82aadcc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,7 @@ ADD mgmt /g/mgmt ADD platform /g/platform ADD ui /g/ui ADD usr /g/usr +ADD test /g/test ADD Makefile /g/Makefile # Note that we only build the platform without ui, because already build ui for all OS. diff --git a/Dockerfile.script b/Dockerfile.script index 4162c0ca..636e86ee 100644 --- a/Dockerfile.script +++ b/Dockerfile.script @@ -22,7 +22,7 @@ FROM ${ARCH}jrei/systemd-ubuntu:focal AS dist ENV DEBIAN_FRONTEND=noninteractive # See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#apt-get -RUN apt update -y && apt-get install -y docker.io +RUN apt update -y && apt-get install -y docker.io make # Copy nodejs for ui build. COPY --from=node /usr/local/bin /usr/local/bin diff --git a/Makefile b/Makefile index b94126a0..26d503c7 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ __REAL_INSTALL = $(DESTDIR)$(PREFIX) default: build help: - @echo "Usage: make build|install|test" + @echo "Usage: make build|install|utest" @echo " default Show help and quit" @echo " build Build the project, npm install and build the ui" @echo " install Copy files for installer" @@ -15,11 +15,13 @@ help: build: make -C platform make -C ui + make -C test make -C releases clean: make -C platform clean make -C ui clean + make -C test clean make -C releases clean install: @@ -48,6 +50,7 @@ else endif test: - cd platform && go test ./... - cd releases && go test ./... + cd platform && go test -v ./... + cd releases && go test -v ./... + cd test && go test -v -check-api-secret=false -test.run TestApi_Empty ./... cd ui && npm run test diff --git a/README.md b/README.md index d9b6c0cc..81a963ae 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,9 @@ You have the option to modify the volumes for srs-cloud and direct them to diffe * `/data` The global data directory. * `redis` The redis data directory, the publish secret and record configuration. - * `srs-cloud` The data directory for srs-cloud - * `config` The mgmt password and cloud configuration. - * `record` The record storage directory, save record files. - * `vlive` The storage directory for virtual live, save video files. + * `config` The mgmt password and cloud configuration. + * `record` The record storage directory, save record files. + * `vlive` The storage directory for virtual live, save video files. You can use environment variables to modify the settings. diff --git a/mgmt/bootstrap b/mgmt/bootstrap index 01f94ce4..5e5a7570 100755 --- a/mgmt/bootstrap +++ b/mgmt/bootstrap @@ -30,6 +30,7 @@ echo "Start platform container" CMD="docker run -v ${DATA_HOME}:/data ${EXTRA_PARAMS} -p ${MGMT_PORT}:2022 -p ${RTMP_PORT}:1935/tcp -p ${API_PORT}:1985/tcp -p ${HTTP_PORT}:8080/tcp -p ${RTC_PORT}:8000/udp -p ${SRT_PORT}:10080/udp + --log-driver=json-file --log-opt=max-size=1g --log-opt=max-file=3 --restart no --rm -it --name srs-cloud --detach ${IMAGE}" echo $CMD && $CMD diff --git a/platform/dvr-local-disk.go b/platform/dvr-local-disk.go index c21e03d7..798c8493 100644 --- a/platform/dvr-local-disk.go +++ b/platform/dvr-local-disk.go @@ -10,7 +10,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net/http" "os" "os/exec" @@ -25,7 +24,6 @@ import ( // Use v8 because we use Go 1.16+, while v9 requires Go 1.18+ "github.com/go-redis/redis/v8" - "github.com/golang-jwt/jwt/v4" "github.com/google/uuid" ) @@ -52,27 +50,18 @@ func (v *RecordWorker) Handle(ctx context.Context, handler *http.ServeMux) error logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token string - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` }{ Token: &token, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } if all, err := rdb.HGet(ctx, SRS_RECORD_PATTERNS, "all").Result(); err != nil && err != redis.Nil { @@ -97,29 +86,20 @@ func (v *RecordWorker) Handle(ctx context.Context, handler *http.ServeMux) error logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token string var all bool - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` All *bool `json:"all"` }{ Token: &token, All: &all, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } if all, err := rdb.HSet(ctx, SRS_RECORD_PATTERNS, "all", fmt.Sprintf("%v", all)).Result(); err != nil && err != redis.Nil { @@ -138,31 +118,23 @@ func (v *RecordWorker) Handle(ctx context.Context, handler *http.ServeMux) error logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token, uuid string - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` UUID *string `json:"uuid"` }{ Token: &token, UUID: &uuid, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) - } - if uuid == "" { - return errors.New("no uuid") + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") + } + + if uuid == "" { + return errors.New("no uuid") } var metadata M3u8VoDArtifact @@ -216,27 +188,18 @@ func (v *RecordWorker) Handle(ctx context.Context, handler *http.ServeMux) error logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token string - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` }{ Token: &token, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } keys, cursor, err := rdb.HScan(ctx, SRS_RECORD_M3U8_ARTIFACT, 0, "*", 100).Result() diff --git a/platform/dvr-tencent-cos.go b/platform/dvr-tencent-cos.go index 6a6a92a4..d20d8d7f 100644 --- a/platform/dvr-tencent-cos.go +++ b/platform/dvr-tencent-cos.go @@ -9,7 +9,6 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "net/url" "os" @@ -25,7 +24,6 @@ import ( // Use v8 because we use Go 1.16+, while v9 requires Go 1.18+ "github.com/go-redis/redis/v8" - "github.com/golang-jwt/jwt/v4" "github.com/google/uuid" "github.com/tencentyun/cos-go-sdk-v5" ) @@ -63,27 +61,18 @@ func (v *DvrWorker) Handle(ctx context.Context, handler *http.ServeMux) error { logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token string - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` }{ Token: &token, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } all, err := rdb.HGet(ctx, SRS_DVR_PATTERNS, "all").Result() @@ -114,29 +103,20 @@ func (v *DvrWorker) Handle(ctx context.Context, handler *http.ServeMux) error { logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token string var all bool - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` All *bool `json:"all"` }{ Token: &token, All: &all, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } if all, err := rdb.HSet(ctx, SRS_DVR_PATTERNS, "all", fmt.Sprintf("%v", all)).Result(); err != nil && err != redis.Nil { @@ -155,27 +135,18 @@ func (v *DvrWorker) Handle(ctx context.Context, handler *http.ServeMux) error { logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token string - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` }{ Token: &token, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } keys, cursor, err := rdb.HScan(ctx, SRS_DVR_M3U8_ARTIFACT, 0, "*", 100).Result() @@ -208,8 +179,8 @@ func (v *DvrWorker) Handle(ctx context.Context, handler *http.ServeMux) error { "duration": duration, "size": size, // For DVR only. - "bucket": metadata.Bucket, - "region": metadata.Region, + "bucket": metadata.Bucket, + "region": metadata.Region, }) } @@ -753,7 +724,7 @@ func (v *DvrM3u8Stream) serveMessage(ctx context.Context, msg *SrsOnHlsObject) e // See https://cloud.tencent.com/document/product/436/64980 opt := cos.ObjectPutOptions{ ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{ - ContentType: "video/MP2T", + ContentType: "video/MP2T", ContentLength: int64(msg.TsFile.Size), }, } @@ -784,7 +755,7 @@ func (v *DvrM3u8Stream) finishM3u8(ctx context.Context) error { // See https://cloud.tencent.com/document/product/436/64980 opt := cos.ObjectPutOptions{ ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{ - ContentType: contentType, + ContentType: contentType, ContentLength: int64(len(m3u8Body)), }, } diff --git a/platform/dvr-tencent-vod.go b/platform/dvr-tencent-vod.go index dc4467a9..6a015cd4 100644 --- a/platform/dvr-tencent-vod.go +++ b/platform/dvr-tencent-vod.go @@ -9,7 +9,6 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "net/url" "os" @@ -26,7 +25,6 @@ import ( // Use v8 because we use Go 1.16+, while v9 requires Go 1.18+ "github.com/go-redis/redis/v8" - "github.com/golang-jwt/jwt/v4" "github.com/google/uuid" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" @@ -67,27 +65,18 @@ func (v *VodWorker) Handle(ctx context.Context, handler *http.ServeMux) error { logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token string - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` }{ Token: &token, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } all, err := rdb.HGet(ctx, SRS_VOD_PATTERNS, "all").Result() @@ -127,29 +116,20 @@ func (v *VodWorker) Handle(ctx context.Context, handler *http.ServeMux) error { logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token string var all bool - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` All *bool `json:"all"` }{ Token: &token, All: &all, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } if all, err := rdb.HSet(ctx, SRS_VOD_PATTERNS, "all", fmt.Sprintf("%v", all)).Result(); err != nil && err != redis.Nil { @@ -168,27 +148,18 @@ func (v *VodWorker) Handle(ctx context.Context, handler *http.ServeMux) error { logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token string - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` }{ Token: &token, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } keys, cursor, err := rdb.HScan(ctx, SRS_VOD_M3U8_ARTIFACT, 0, "*", 100).Result() diff --git a/platform/forward.go b/platform/forward.go index 4d942270..f19d7c5f 100644 --- a/platform/forward.go +++ b/platform/forward.go @@ -9,7 +9,6 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "os" "os/exec" @@ -25,7 +24,6 @@ import ( // Use v8 because we use Go 1.16+, while v9 requires Go 1.18+ "github.com/go-redis/redis/v8" - "github.com/golang-jwt/jwt/v4" "github.com/google/uuid" ) @@ -55,30 +53,21 @@ func (v *ForwardWorker) Handle(ctx context.Context, handler *http.ServeMux) erro logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token, action string var userConf ForwardConfigure - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` Action *string `json:"action"` *ForwardConfigure }{ Token: &token, Action: &action, ForwardConfigure: &userConf, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } allowedActions := []string{"update"} @@ -159,27 +148,18 @@ func (v *ForwardWorker) Handle(ctx context.Context, handler *http.ServeMux) erro logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token string - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` }{ Token: &token, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } res := make([]map[string]interface{}, 0) @@ -628,7 +608,7 @@ func (v *ForwardTask) doForward(ctx context.Context, input *SrsStream) error { // If we got a PID, sleep for a while, to avoid too fast restart. if v.PID > 0 { select { - case <- ctx.Done(): + case <-ctx.Done(): case <-time.After(1 * time.Second): } } diff --git a/platform/main.go b/platform/main.go index e6a4f6d5..0e6bbf30 100644 --- a/platform/main.go +++ b/platform/main.go @@ -420,7 +420,7 @@ func initPlatform(ctx context.Context) error { if token, err := rdb.HGet(ctx, SRS_PLATFORM_SECRET, "token").Result(); err != nil && err != redis.Nil { return errors.Wrapf(err, "hget %v token", SRS_PLATFORM_SECRET) } else if token == "" { - token = fmt.Sprintf("srs-v1-%v", strings.ReplaceAll(uuid.NewString(), "-", "")) + token = fmt.Sprintf("srs-v2-%v", strings.ReplaceAll(uuid.NewString(), "-", "")) if err = rdb.HSet(ctx, SRS_PLATFORM_SECRET, "token", token).Err(); err != nil { return errors.Wrapf(err, "hset %v token %v", SRS_PLATFORM_SECRET, token) } @@ -429,7 +429,8 @@ func initPlatform(ctx context.Context) error { if err = rdb.HSet(ctx, SRS_PLATFORM_SECRET, "update", update).Err(); err != nil { return errors.Wrapf(err, "hset %v update %v", SRS_PLATFORM_SECRET, update) } - logger.Tf(ctx, "Platform api secret update, token=%vB, update=%v", len(token), update) + os.Setenv("SRS_PLATFORM_SECRET", token) + logger.Tf(ctx, "Platform update SRS_PLATFORM_SECRET, token=%vB, at=%v", len(token), update) } return nil diff --git a/platform/service.go b/platform/service.go index 03386d02..f059bd90 100644 --- a/platform/service.go +++ b/platform/service.go @@ -9,7 +9,6 @@ import ( "context" "encoding/json" "fmt" - "github.com/golang-jwt/jwt/v4" "github.com/joho/godotenv" "io/ioutil" "net/http" @@ -300,27 +299,18 @@ func handleDockerHTTPService(ctx context.Context, handler *http.ServeMux) error logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token string - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` }{ Token: &token, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } expireAt, createAt, token, err := createToken(ctx, os.Getenv("SRS_PLATFORM_SECRET")) @@ -342,47 +332,6 @@ func handleDockerHTTPService(ctx context.Context, handler *http.ServeMux) error } }) - ep = "/terraform/v1/mgmt/secret/token" - logger.Tf(ctx, "Handle %v", ep) - handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { - if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - - var apiSecret string - if err := json.Unmarshal(b, &struct { - ApiSecret *string `json:"apiSecret"` - }{ - ApiSecret: &apiSecret, - }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) - } - - if apiSecret != os.Getenv("SRS_PLATFORM_SECRET") { - return errors.New("apiSecret verify failed") - } - - expireAt, createAt, token, err := createToken(ctx, apiSecret) - if err != nil { - return errors.Wrapf(err, "build token") - } - - ohttp.WriteData(ctx, w, r, &struct { - Token string `json:"token"` - CreateAt string `json:"createAt"` - ExpireAt string `json:"expireAt"` - }{ - Token: token, CreateAt: createAt.Format(time.RFC3339), ExpireAt: expireAt.Format(time.RFC3339), - }) - logger.Tf(ctx, "create token by apiSecret ok, create=%v, expire=%v, token=%vB", createAt, expireAt, len(token)) - return nil - }(); err != nil { - ohttp.WriteError(ctx, w, r, err) - } - }) - ep = "/terraform/v1/mgmt/login" logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { @@ -437,27 +386,18 @@ func handleDockerHTTPService(ctx context.Context, handler *http.ServeMux) error logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token string - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` }{ Token: &token, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } upgrading, err := rdb.HGet(ctx, SRS_UPGRADING, "upgrading").Result() @@ -487,31 +427,23 @@ func handleDockerHTTPService(ctx context.Context, handler *http.ServeMux) error logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token, bvid string - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` BVID *string `json:"bvid"` }{ Token: &token, BVID: &bvid, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) - } - if bvid == "" { - return errors.New("no bvid") + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") + } + + if bvid == "" { + return errors.New("no bvid") } bilibiliObj := struct { @@ -603,27 +535,18 @@ func handleDockerHTTPService(ctx context.Context, handler *http.ServeMux) error logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token string - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` }{ Token: &token, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } ohttp.WriteData(ctx, w, r, apiSecret) @@ -638,21 +561,22 @@ func handleDockerHTTPService(ctx context.Context, handler *http.ServeMux) error logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token, beian, text string - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` Beian *string `json:"beian"` Text *string `json:"text"` }{ Token: &token, Beian: &beian, Text: &text, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") + } + + apiSecret := os.Getenv("SRS_PLATFORM_SECRET") + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } + if beian == "" { return errors.New("no beian") } @@ -660,15 +584,6 @@ func handleDockerHTTPService(ctx context.Context, handler *http.ServeMux) error return errors.New("no text") } - apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) - } - if err := rdb.HSet(ctx, SRS_BEIAN, beian, text).Err(); err != nil && err != redis.Nil { return errors.Wrapf(err, "hset %v %v %v", SRS_BEIAN, beian, text) } @@ -685,29 +600,20 @@ func handleDockerHTTPService(ctx context.Context, handler *http.ServeMux) error logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token string var enabled bool - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` Enabled *bool `json:"enabled"` }{ Token: &token, Enabled: &enabled, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } if err := nginxHlsDelivery(ctx, enabled); err != nil { diff --git a/platform/srs-hooks.go b/platform/srs-hooks.go index e0e70f6d..f7b60b06 100644 --- a/platform/srs-hooks.go +++ b/platform/srs-hooks.go @@ -24,7 +24,6 @@ import ( // Use v8 because we use Go 1.16+, while v9 requires Go 1.18+ "github.com/go-redis/redis/v8" - "github.com/golang-jwt/jwt/v4" cam "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam/v20190116" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" @@ -71,7 +70,7 @@ func handleDockerHooksService(ctx context.Context, handler *http.ServeMux) error var action string var streamObj SrsStream if err := json.Unmarshal(b, &struct { - Action *string `json:"action"` + Action *string `json:"action"` *SrsStream }{ Action: &action, SrsStream: &streamObj, @@ -153,27 +152,18 @@ func handleDockerHooksService(ctx context.Context, handler *http.ServeMux) error secretQueryHandler := func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token string - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` }{ Token: &token, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } publish, err := rdb.HGet(ctx, SRS_AUTH_SECRET, "pubSecret").Result() @@ -208,31 +198,23 @@ func handleDockerHooksService(ctx context.Context, handler *http.ServeMux) error logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token, secret string - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` Secret *string `json:"secret"` }{ Token: &token, Secret: &secret, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) - } - if secret == "" { - return errors.New("no secret") + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") + } + + if secret == "" { + return errors.New("no secret") } if err := rdb.HSet(ctx, SRS_AUTH_SECRET, "pubSecret", secret).Err(); err != nil { @@ -254,29 +236,20 @@ func handleDockerHooksService(ctx context.Context, handler *http.ServeMux) error logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token string var pubNoAuth bool - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` PubNoAuth *bool `json:"pubNoAuth"` }{ Token: &token, PubNoAuth: &pubNoAuth, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } if err := rdb.HSet(ctx, SRS_AUTH_SECRET, "pubNoAuth", fmt.Sprintf("%v", pubNoAuth)).Err(); err != nil { @@ -296,21 +269,22 @@ func handleDockerHooksService(ctx context.Context, handler *http.ServeMux) error logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token, secretId, secretKey string - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` SecretID *string `json:"secretId"` SecretKey *string `json:"secretKey"` }{ Token: &token, SecretID: &secretId, SecretKey: &secretKey, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") + } + + apiSecret := os.Getenv("SRS_PLATFORM_SECRET") + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } + if secretId == "" { return errors.New("no secretId") } @@ -318,15 +292,6 @@ func handleDockerHooksService(ctx context.Context, handler *http.ServeMux) error return errors.New("no secretKey") } - apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) - } - // Query, verify and setup the secret ID and key. var appID, ownerUIN string if true { diff --git a/platform/utils.go b/platform/utils.go index eaab53bd..2dfe11b7 100644 --- a/platform/utils.go +++ b/platform/utils.go @@ -1091,3 +1091,64 @@ func (v *SrsStream) IsSRT() bool { func (v *SrsStream) IsRTC() bool { return strings.Contains(v.Param, "upstream=rtc") } + +// ParseBody read the body from r, and unmarshal JSON to v. +func ParseBody(ctx context.Context, r io.ReadCloser, v interface{}) error { + b, err := ioutil.ReadAll(r) + if err != nil { + return errors.Wrapf(err, "read body") + } + defer r.Close() + + if len(b) == 0 { + return nil + } + + if err := json.Unmarshal(b, v); err != nil { + return errors.Wrapf(err, "json unmarshal %v", string(b)) + } + + return nil +} + +// Authenticate check by Bearer or token. +// If use bearer secret, there is the header Authorization: Bearer {apiSecret}. +// If use token, there is a JWT token which is signed by apiSecret. +func Authenticate(ctx context.Context, apiSecret, token string, header http.Header) error { + // Check system api secret. + if apiSecret == "" { + return errors.New("no api secret") + } + + // Verify bearer secret first. + if authorization := header.Get("Authorization"); authorization != "" { + parseBearerToken := func(authorization string) (string, error) { + authParts := strings.Split(authorization, " ") + if len(authParts) != 2 || strings.ToLower(authParts[0]) != "bearer" { + return "", errors.New("Invalid Authorization format") + } + + return authParts[1], nil + } + + authSecret, err := parseBearerToken(authorization) + if err != nil { + return errors.Wrapf(err, "parse bearer token") + } + + if authSecret != apiSecret { + return errors.New("invalid bearer token") + } + return nil + } + + // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes + // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac + if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { + return []byte(apiSecret), nil + }); err != nil { + return errors.Wrapf(err, "verify token %v", token) + } + + return nil +} diff --git a/platform/virtual-live-stream.go b/platform/virtual-live-stream.go index 4884cd9d..7132852c 100644 --- a/platform/virtual-live-stream.go +++ b/platform/virtual-live-stream.go @@ -10,7 +10,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net/http" "os" "os/exec" @@ -27,7 +26,6 @@ import ( // Use v8 because we use Go 1.16+, while v9 requires Go 1.18+ "github.com/go-redis/redis/v8" - "github.com/golang-jwt/jwt/v4" "github.com/google/uuid" ) @@ -57,30 +55,21 @@ func (v *VLiveWorker) Handle(ctx context.Context, handler *http.ServeMux) error logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token, action string var userConf VLiveConfigure - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` Action *string `json:"action"` *VLiveConfigure }{ Token: &token, Action: &action, VLiveConfigure: &userConf, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } allowedActions := []string{"update"} @@ -164,27 +153,18 @@ func (v *VLiveWorker) Handle(ctx context.Context, handler *http.ServeMux) error logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - var token string - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` }{ Token: &token, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") } res := make([]map[string]interface{}, 0) @@ -301,11 +281,6 @@ func (v *VLiveWorker) Handle(ctx context.Context, handler *http.ServeMux) error logger.Tf(ctx, "Handle %v", ep) handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) { if err := func() error { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrapf(err, "read body") - } - type VLiveTempFile struct { Name string `json"name"` Size int64 `json:"size"` @@ -315,15 +290,21 @@ func (v *VLiveWorker) Handle(ctx context.Context, handler *http.ServeMux) error var token, platform string var files []*VLiveTempFile - if err := json.Unmarshal(b, &struct { + if err := ParseBody(ctx, r.Body, &struct { Token *string `json:"token"` Platform *string `json:"platform"` Files *[]*VLiveTempFile `json:"files"` }{ Token: &token, Platform: &platform, Files: &files, }); err != nil { - return errors.Wrapf(err, "json unmarshal %v", string(b)) + return errors.Wrapf(err, "parse body") } + + apiSecret := os.Getenv("SRS_PLATFORM_SECRET") + if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil { + return errors.Wrapf(err, "authenticate") + } + if len(files) == 0 { return errors.New("no files") } @@ -342,15 +323,6 @@ func (v *VLiveWorker) Handle(ctx context.Context, handler *http.ServeMux) error } }() - apiSecret := os.Getenv("SRS_PLATFORM_SECRET") - // Verify token first, @see https://www.npmjs.com/package/jsonwebtoken#errors--codes - // See https://pkg.go.dev/github.com/golang-jwt/jwt/v4#example-Parse-Hmac - if _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(apiSecret), nil - }); err != nil { - return errors.Wrapf(err, "verify token %v", token) - } - // Check files. for _, f := range files { if f.Target == "" { diff --git a/scripts/setup-ubuntu/install.sh b/scripts/setup-ubuntu/install.sh index ea4f6628..8ce2f5b8 100755 --- a/scripts/setup-ubuntu/install.sh +++ b/scripts/setup-ubuntu/install.sh @@ -137,8 +137,7 @@ if [[ ${VERBOSE} == yes ]]; then fi echo "Start to create data and config files" -mkdir -p ${DATA_HOME}/config && touch ${DATA_HOME}/config/.env && -rm -rf ~/credentials.txt && ln -sf ${DATA_HOME}/config/.env ~/credentials.txt +mkdir -p ${DATA_HOME}/config && touch ${DATA_HOME}/config/.env if [[ $? -ne 0 ]]; then echo "Create /data/config/.env failed"; exit 1; fi echo "Create data and config files ok" @@ -206,7 +205,7 @@ cp ${SCRIPT_DIR}/init.d.sh /etc/init.d/srs_cloud && chmod +x /etc/init.d/srs_cloud if [[ $? -ne 0 ]]; then echo "Setup init.d script failed"; exit 1; fi -# Create srs-cloud service, and the credential file. +# Create srs-cloud service. # Remark: Never start the service, because the IP will change for new machine created. cd ${SRS_HOME} && cp -f usr/lib/systemd/system/srs-cloud.service /usr/lib/systemd/system/srs-cloud.service && diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 00000000..49f23591 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,11 @@ +.PHONY: default clean alpine build + +default: build + +build: ./srs-cloud.test + +clean: + rm -f srs-cloud.test + +./srs-cloud.test: *.go Makefile go.mod + go test -mod=vendor -c -o srs-cloud.test . diff --git a/test/api_test.go b/test/api_test.go new file mode 100644 index 00000000..0088d986 --- /dev/null +++ b/test/api_test.go @@ -0,0 +1,41 @@ +package main + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/ossrs/go-oryx-lib/logger" +) + +func TestApi_Empty(t *testing.T) { + ctx := logger.WithContext(context.Background()) + logger.Tf(ctx, "test done") +} + +func TestApi_QuerySecret(t *testing.T) { + ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond) + logger.Tf(ctx, "TestApi_QuerySecret with %v", options()) + + var r0 error + defer func(ctx context.Context) { + if err := filterTestError(ctx.Err(), r0); err != nil { + t.Errorf("Fail for err %+v", err) + } else { + logger.Tf(ctx, "test done with err %+v", err) + } + }(ctx) + defer cancel() + + var publishSecret string + if err := apiRequest(ctx, "/terraform/v1/hooks/srs/secret/query", nil, &struct { + Publish *string `json:"publish"` + }{ + Publish: &publishSecret, + }); err != nil { + r0 = err + } else if publishSecret == "" { + r0 = errors.New("empty publish secret") + } +} diff --git a/test/go.mod b/test/go.mod new file mode 100644 index 00000000..2a502004 --- /dev/null +++ b/test/go.mod @@ -0,0 +1,8 @@ +module test + +go 1.16 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/ossrs/go-oryx-lib v0.0.9 +) diff --git a/test/go.sum b/test/go.sum new file mode 100644 index 00000000..73c79a69 --- /dev/null +++ b/test/go.sum @@ -0,0 +1,4 @@ +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/ossrs/go-oryx-lib v0.0.9 h1:piZkzit/1hqAcXP31/mvDEDpHVjCmBMmvzF3hN8hUuQ= +github.com/ossrs/go-oryx-lib v0.0.9/go.mod h1:i2tH4TZBzAw5h+HwGrNOKvP/nmZgSQz0OEnLLdzcT/8= diff --git a/test/main_test.go b/test/main_test.go new file mode 100644 index 00000000..02fd2218 --- /dev/null +++ b/test/main_test.go @@ -0,0 +1,248 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "flag" + "fmt" + "io" + "io/ioutil" + "math/rand" + "net/http" + "net/url" + "os" + "strings" + "testing" + "time" + + "github.com/joho/godotenv" + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" +) + +var srsLog *bool +var srsTimeout *int +var endpoint *string +var apiSecret *string +var checkApiSecret *bool +var waitReady *bool +var apiReadyimeout *int +var initPassword *bool + +func options() string { + return fmt.Sprintf("log=%v, timeout=%vms, secret=%vB, checkApiSecret=%v, endpoint=%v, waitReady=%v, initPassword=%v", + *srsLog, *srsTimeout, len(*apiSecret), *checkApiSecret, *endpoint, *waitReady, *initPassword) +} + +func prepareTest(ctx context.Context) (err error) { + envFile := "../platform/containers/data/config/.env" + if _, err := os.Stat(envFile); err == nil { + if err := godotenv.Load(envFile); err != nil { + return errors.Wrapf(err, "load %v", envFile) + } + } + + srsLog = flag.Bool("srs-log", false, "Whether enable the detail log") + srsTimeout = flag.Int("srs-timeout", 5000, "For each case, the timeout in ms") + apiSecret = flag.String("api-secret", os.Getenv("SRS_PLATFORM_SECRET"), "The secret for api") + checkApiSecret = flag.Bool("check-api-secret", true, "Whether check the api secret") + endpoint = flag.String("endpoint", "http://localhost:2022", "The endpoint for api") + waitReady = flag.Bool("wait-ready", false, "Whether wait for the service ready") + apiReadyimeout = flag.Int("api-ready-timeout", 30000, "Check when startup, the timeout in ms") + initPassword = flag.Bool("init-password", false, "Whether init the system and set password") + + // Should parse it first. + flag.Parse() + + if *checkApiSecret && *apiSecret == "" { + return errors.Errorf("empty api secret") + } + + return nil +} + +func waitForServiceReady(ctx context.Context) error { + ctx, cancel := context.WithTimeout(ctx, time.Duration(*apiReadyimeout)*time.Millisecond) + logger.Tf(ctx, "Wait for API ready with %v, apiReadyimeout=%vms", options(), *apiReadyimeout) + defer cancel() + + for { + if ctx.Err() != nil { + logger.Ef(ctx, "Wait for API ready timeout, err %v", ctx.Err()) + return ctx.Err() + } + + err := apiRequest(ctx, "/terraform/v1/host/versions", nil, nil) + if err == nil { + logger.T(ctx, "API ready") + break + } + + logger.Tf(ctx, "Wait for API ready, err %v", err) + time.Sleep(1 * time.Second) + } + + return nil +} + +func initSystemPassword(ctx context.Context) error { + ctx, cancel := context.WithTimeout(ctx, time.Duration(*srsTimeout)*time.Millisecond) + logger.Tf(ctx, "Wait for API ready with %v", options()) + defer cancel() + + // Initialize the system by password. + password := fmt.Sprintf("%x", rand.Uint64()) + var token string + if err := apiRequest(ctx, "/terraform/v1/mgmt/init", &struct { + Password string `json:"password"` + }{ + Password: password, + }, &struct { + Token *string `json:"token"` + }{ + Token: &token, + }); err != nil { + return errors.Wrapf(err, "init system") + } + if token == "" { + return errors.Errorf("invalid token") + } + + // Login the system by password. + var token2 string + if err := apiRequest(ctx, "/terraform/v1/mgmt/login", &struct { + Password string `json:"password"` + }{ + Password: password, + }, &struct { + Token *string `json:"token"` + }{ + Token: &token2, + }); err != nil { + return errors.Wrapf(err, "login system") + } + if token2 == "" { + return errors.Errorf("invalid token") + } + + return nil +} + +func TestMain(m *testing.M) { + ctx := logger.WithContext(context.Background()) + + if err := prepareTest(ctx); err != nil { + logger.Ef(ctx, "Prepare test fail, err %+v", err) + os.Exit(-1) + } + + // Disable the logger during all tests. + if *srsLog == false { + olw := logger.Switch(ioutil.Discard) + defer func() { + logger.Switch(olw) + }() + } + + // Init rand seed. + rand.Seed(time.Now().UnixNano()) + + // Wait for the service ready. + if *waitReady { + if err := waitForServiceReady(ctx); err != nil { + os.Exit(-1) + } + } + + if *initPassword { + if err := initSystemPassword(ctx); err != nil { + logger.Ef(ctx, "Init system fail, err %+v", err) + os.Exit(-1) + } + } + + os.Exit(m.Run()) +} + +// Filter the test error, ignore context.Canceled +func filterTestError(errs ...error) error { + var filteredErrors []error + + for _, err := range errs { + if err == nil || errors.Cause(err) == context.Canceled { + continue + } + + // If url error, server maybe error, do not print the detail log. + if r0 := errors.Cause(err); r0 != nil { + if r1, ok := r0.(*url.Error); ok { + err = r1 + } + } + + filteredErrors = append(filteredErrors, err) + } + + if len(filteredErrors) == 0 { + return nil + } + if len(filteredErrors) == 1 { + return filteredErrors[0] + } + + var descs []string + for i, err := range filteredErrors[1:] { + descs = append(descs, fmt.Sprintf("err #%d, %+v", i, err)) + } + return errors.Wrapf(filteredErrors[0], "with %v", strings.Join(descs, ",")) +} + +func apiRequest(ctx context.Context, apiPath string, data interface{}, response interface{}) (err error) { + var body io.Reader + if data != nil { + if b, err := json.Marshal(data); err != nil { + return errors.Wrapf(err, "marshal data") + } else { + body = bytes.NewReader(b) + } + } + + u := fmt.Sprintf("%s%s", *endpoint, apiPath) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, body) + if err != nil { + return errors.Wrapf(err, "new request") + } + + req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", *apiSecret)) + resp, err := http.DefaultClient.Do(req) + if err != nil { + return errors.Wrapf(err, "do request") + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return errors.Errorf("invalid status code %v", resp.StatusCode) + } + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return errors.Wrapf(err, "read body") + } + + obj := &struct { + Code int `json:"code"` + Data interface{} `json:"data"` + }{ + Data: response, + } + if err = json.Unmarshal(b, obj); err != nil { + return errors.Wrapf(err, "unmarshal %s", b) + } + + if obj.Code != 0 { + return errors.Errorf("invalid code %v of %s", obj.Code, b) + } + + return nil +} diff --git a/test/vendor/github.com/joho/godotenv/.gitignore b/test/vendor/github.com/joho/godotenv/.gitignore new file mode 100644 index 00000000..e43b0f98 --- /dev/null +++ b/test/vendor/github.com/joho/godotenv/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/test/vendor/github.com/joho/godotenv/LICENCE b/test/vendor/github.com/joho/godotenv/LICENCE new file mode 100644 index 00000000..e7ddd51b --- /dev/null +++ b/test/vendor/github.com/joho/godotenv/LICENCE @@ -0,0 +1,23 @@ +Copyright (c) 2013 John Barton + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/test/vendor/github.com/joho/godotenv/README.md b/test/vendor/github.com/joho/godotenv/README.md new file mode 100644 index 00000000..bfbe66a0 --- /dev/null +++ b/test/vendor/github.com/joho/godotenv/README.md @@ -0,0 +1,202 @@ +# GoDotEnv ![CI](https://github.com/joho/godotenv/workflows/CI/badge.svg) [![Go Report Card](https://goreportcard.com/badge/github.com/joho/godotenv)](https://goreportcard.com/report/github.com/joho/godotenv) + +A Go (golang) port of the Ruby [dotenv](https://github.com/bkeepers/dotenv) project (which loads env vars from a .env file). + +From the original Library: + +> Storing configuration in the environment is one of the tenets of a twelve-factor app. Anything that is likely to change between deployment environments–such as resource handles for databases or credentials for external services–should be extracted from the code into environment variables. +> +> But it is not always practical to set environment variables on development machines or continuous integration servers where multiple projects are run. Dotenv load variables from a .env file into ENV when the environment is bootstrapped. + +It can be used as a library (for loading in env for your own daemons etc.) or as a bin command. + +There is test coverage and CI for both linuxish and Windows environments, but I make no guarantees about the bin version working on Windows. + +## Installation + +As a library + +```shell +go get github.com/joho/godotenv +``` + +or if you want to use it as a bin command + +go >= 1.17 +```shell +go install github.com/joho/godotenv/cmd/godotenv@latest +``` + +go < 1.17 +```shell +go get github.com/joho/godotenv/cmd/godotenv +``` + +## Usage + +Add your application configuration to your `.env` file in the root of your project: + +```shell +S3_BUCKET=YOURS3BUCKET +SECRET_KEY=YOURSECRETKEYGOESHERE +``` + +Then in your Go app you can do something like + +```go +package main + +import ( + "log" + "os" + + "github.com/joho/godotenv" +) + +func main() { + err := godotenv.Load() + if err != nil { + log.Fatal("Error loading .env file") + } + + s3Bucket := os.Getenv("S3_BUCKET") + secretKey := os.Getenv("SECRET_KEY") + + // now do something with s3 or whatever +} +``` + +If you're even lazier than that, you can just take advantage of the autoload package which will read in `.env` on import + +```go +import _ "github.com/joho/godotenv/autoload" +``` + +While `.env` in the project root is the default, you don't have to be constrained, both examples below are 100% legit + +```go +godotenv.Load("somerandomfile") +godotenv.Load("filenumberone.env", "filenumbertwo.env") +``` + +If you want to be really fancy with your env file you can do comments and exports (below is a valid env file) + +```shell +# I am a comment and that is OK +SOME_VAR=someval +FOO=BAR # comments at line end are OK too +export BAR=BAZ +``` + +Or finally you can do YAML(ish) style + +```yaml +FOO: bar +BAR: baz +``` + +as a final aside, if you don't want godotenv munging your env you can just get a map back instead + +```go +var myEnv map[string]string +myEnv, err := godotenv.Read() + +s3Bucket := myEnv["S3_BUCKET"] +``` + +... or from an `io.Reader` instead of a local file + +```go +reader := getRemoteFile() +myEnv, err := godotenv.Parse(reader) +``` + +... or from a `string` if you so desire + +```go +content := getRemoteFileContent() +myEnv, err := godotenv.Unmarshal(content) +``` + +### Precedence & Conventions + +Existing envs take precedence of envs that are loaded later. + +The [convention](https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use) +for managing multiple environments (i.e. development, test, production) +is to create an env named `{YOURAPP}_ENV` and load envs in this order: + +```go +env := os.Getenv("FOO_ENV") +if "" == env { + env = "development" +} + +godotenv.Load(".env." + env + ".local") +if "test" != env { + godotenv.Load(".env.local") +} +godotenv.Load(".env." + env) +godotenv.Load() // The Original .env +``` + +If you need to, you can also use `godotenv.Overload()` to defy this convention +and overwrite existing envs instead of only supplanting them. Use with caution. + +### Command Mode + +Assuming you've installed the command as above and you've got `$GOPATH/bin` in your `$PATH` + +``` +godotenv -f /some/path/to/.env some_command with some args +``` + +If you don't specify `-f` it will fall back on the default of loading `.env` in `PWD` + +By default, it won't override existing environment variables; you can do that with the `-o` flag. + +### Writing Env Files + +Godotenv can also write a map representing the environment to a correctly-formatted and escaped file + +```go +env, err := godotenv.Unmarshal("KEY=value") +err := godotenv.Write(env, "./.env") +``` + +... or to a string + +```go +env, err := godotenv.Unmarshal("KEY=value") +content, err := godotenv.Marshal(env) +``` + +## Contributing + +Contributions are welcome, but with some caveats. + +This library has been declared feature complete (see [#182](https://github.com/joho/godotenv/issues/182) for background) and will not be accepting issues or pull requests adding new functionality or breaking the library API. + +Contributions would be gladly accepted that: + +* bring this library's parsing into closer compatibility with the mainline dotenv implementations, in particular [Ruby's dotenv](https://github.com/bkeepers/dotenv) and [Node.js' dotenv](https://github.com/motdotla/dotenv) +* keep the library up to date with the go ecosystem (ie CI bumps, documentation changes, changes in the core libraries) +* bug fixes for use cases that pertain to the library's purpose of easing development of codebases deployed into twelve factor environments + +*code changes without tests and references to peer dotenv implementations will not be accepted* + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Added some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request + +## Releases + +Releases should follow [Semver](http://semver.org/) though the first couple of releases are `v1` and `v1.1`. + +Use [annotated tags for all releases](https://github.com/joho/godotenv/issues/30). Example `git tag -a v1.2.1` + +## Who? + +The original library [dotenv](https://github.com/bkeepers/dotenv) was written by [Brandon Keepers](http://opensoul.org/), and this port was done by [John Barton](https://johnbarton.co/) based off the tests/fixtures in the original library. diff --git a/test/vendor/github.com/joho/godotenv/go.mod b/test/vendor/github.com/joho/godotenv/go.mod new file mode 100644 index 00000000..126e61d8 --- /dev/null +++ b/test/vendor/github.com/joho/godotenv/go.mod @@ -0,0 +1,3 @@ +module github.com/joho/godotenv + +go 1.12 diff --git a/test/vendor/github.com/joho/godotenv/godotenv.go b/test/vendor/github.com/joho/godotenv/godotenv.go new file mode 100644 index 00000000..61b0ebba --- /dev/null +++ b/test/vendor/github.com/joho/godotenv/godotenv.go @@ -0,0 +1,228 @@ +// Package godotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv) +// +// Examples/readme can be found on the GitHub page at https://github.com/joho/godotenv +// +// The TL;DR is that you make a .env file that looks something like +// +// SOME_ENV_VAR=somevalue +// +// and then in your go code you can call +// +// godotenv.Load() +// +// and all the env vars declared in .env will be available through os.Getenv("SOME_ENV_VAR") +package godotenv + +import ( + "bytes" + "fmt" + "io" + "os" + "os/exec" + "sort" + "strconv" + "strings" +) + +const doubleQuoteSpecialChars = "\\\n\r\"!$`" + +// Parse reads an env file from io.Reader, returning a map of keys and values. +func Parse(r io.Reader) (map[string]string, error) { + var buf bytes.Buffer + _, err := io.Copy(&buf, r) + if err != nil { + return nil, err + } + + return UnmarshalBytes(buf.Bytes()) +} + +// Load will read your env file(s) and load them into ENV for this process. +// +// Call this function as close as possible to the start of your program (ideally in main). +// +// If you call Load without any args it will default to loading .env in the current path. +// +// You can otherwise tell it which files to load (there can be more than one) like: +// +// godotenv.Load("fileone", "filetwo") +// +// It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults. +func Load(filenames ...string) (err error) { + filenames = filenamesOrDefault(filenames) + + for _, filename := range filenames { + err = loadFile(filename, false) + if err != nil { + return // return early on a spazout + } + } + return +} + +// Overload will read your env file(s) and load them into ENV for this process. +// +// Call this function as close as possible to the start of your program (ideally in main). +// +// If you call Overload without any args it will default to loading .env in the current path. +// +// You can otherwise tell it which files to load (there can be more than one) like: +// +// godotenv.Overload("fileone", "filetwo") +// +// It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefully set all vars. +func Overload(filenames ...string) (err error) { + filenames = filenamesOrDefault(filenames) + + for _, filename := range filenames { + err = loadFile(filename, true) + if err != nil { + return // return early on a spazout + } + } + return +} + +// Read all env (with same file loading semantics as Load) but return values as +// a map rather than automatically writing values into env +func Read(filenames ...string) (envMap map[string]string, err error) { + filenames = filenamesOrDefault(filenames) + envMap = make(map[string]string) + + for _, filename := range filenames { + individualEnvMap, individualErr := readFile(filename) + + if individualErr != nil { + err = individualErr + return // return early on a spazout + } + + for key, value := range individualEnvMap { + envMap[key] = value + } + } + + return +} + +// Unmarshal reads an env file from a string, returning a map of keys and values. +func Unmarshal(str string) (envMap map[string]string, err error) { + return UnmarshalBytes([]byte(str)) +} + +// UnmarshalBytes parses env file from byte slice of chars, returning a map of keys and values. +func UnmarshalBytes(src []byte) (map[string]string, error) { + out := make(map[string]string) + err := parseBytes(src, out) + + return out, err +} + +// Exec loads env vars from the specified filenames (empty map falls back to default) +// then executes the cmd specified. +// +// Simply hooks up os.Stdin/err/out to the command and calls Run(). +// +// If you want more fine grained control over your command it's recommended +// that you use `Load()`, `Overload()` or `Read()` and the `os/exec` package yourself. +func Exec(filenames []string, cmd string, cmdArgs []string, overload bool) error { + op := Load + if overload { + op = Overload + } + if err := op(filenames...); err != nil { + return err + } + + command := exec.Command(cmd, cmdArgs...) + command.Stdin = os.Stdin + command.Stdout = os.Stdout + command.Stderr = os.Stderr + return command.Run() +} + +// Write serializes the given environment and writes it to a file. +func Write(envMap map[string]string, filename string) error { + content, err := Marshal(envMap) + if err != nil { + return err + } + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + _, err = file.WriteString(content + "\n") + if err != nil { + return err + } + return file.Sync() +} + +// Marshal outputs the given environment as a dotenv-formatted environment file. +// Each line is in the format: KEY="VALUE" where VALUE is backslash-escaped. +func Marshal(envMap map[string]string) (string, error) { + lines := make([]string, 0, len(envMap)) + for k, v := range envMap { + if d, err := strconv.Atoi(v); err == nil { + lines = append(lines, fmt.Sprintf(`%s=%d`, k, d)) + } else { + lines = append(lines, fmt.Sprintf(`%s="%s"`, k, doubleQuoteEscape(v))) + } + } + sort.Strings(lines) + return strings.Join(lines, "\n"), nil +} + +func filenamesOrDefault(filenames []string) []string { + if len(filenames) == 0 { + return []string{".env"} + } + return filenames +} + +func loadFile(filename string, overload bool) error { + envMap, err := readFile(filename) + if err != nil { + return err + } + + currentEnv := map[string]bool{} + rawEnv := os.Environ() + for _, rawEnvLine := range rawEnv { + key := strings.Split(rawEnvLine, "=")[0] + currentEnv[key] = true + } + + for key, value := range envMap { + if !currentEnv[key] || overload { + _ = os.Setenv(key, value) + } + } + + return nil +} + +func readFile(filename string) (envMap map[string]string, err error) { + file, err := os.Open(filename) + if err != nil { + return + } + defer file.Close() + + return Parse(file) +} + +func doubleQuoteEscape(line string) string { + for _, c := range doubleQuoteSpecialChars { + toReplace := "\\" + string(c) + if c == '\n' { + toReplace = `\n` + } + if c == '\r' { + toReplace = `\r` + } + line = strings.Replace(line, string(c), toReplace, -1) + } + return line +} diff --git a/test/vendor/github.com/joho/godotenv/parser.go b/test/vendor/github.com/joho/godotenv/parser.go new file mode 100644 index 00000000..cc709af8 --- /dev/null +++ b/test/vendor/github.com/joho/godotenv/parser.go @@ -0,0 +1,271 @@ +package godotenv + +import ( + "bytes" + "errors" + "fmt" + "regexp" + "strings" + "unicode" +) + +const ( + charComment = '#' + prefixSingleQuote = '\'' + prefixDoubleQuote = '"' + + exportPrefix = "export" +) + +func parseBytes(src []byte, out map[string]string) error { + src = bytes.Replace(src, []byte("\r\n"), []byte("\n"), -1) + cutset := src + for { + cutset = getStatementStart(cutset) + if cutset == nil { + // reached end of file + break + } + + key, left, err := locateKeyName(cutset) + if err != nil { + return err + } + + value, left, err := extractVarValue(left, out) + if err != nil { + return err + } + + out[key] = value + cutset = left + } + + return nil +} + +// getStatementPosition returns position of statement begin. +// +// It skips any comment line or non-whitespace character. +func getStatementStart(src []byte) []byte { + pos := indexOfNonSpaceChar(src) + if pos == -1 { + return nil + } + + src = src[pos:] + if src[0] != charComment { + return src + } + + // skip comment section + pos = bytes.IndexFunc(src, isCharFunc('\n')) + if pos == -1 { + return nil + } + + return getStatementStart(src[pos:]) +} + +// locateKeyName locates and parses key name and returns rest of slice +func locateKeyName(src []byte) (key string, cutset []byte, err error) { + // trim "export" and space at beginning + src = bytes.TrimLeftFunc(src, isSpace) + if bytes.HasPrefix(src, []byte(exportPrefix)) { + trimmed := bytes.TrimPrefix(src, []byte(exportPrefix)) + if bytes.IndexFunc(trimmed, isSpace) == 0 { + src = bytes.TrimLeftFunc(trimmed, isSpace) + } + } + + // locate key name end and validate it in single loop + offset := 0 +loop: + for i, char := range src { + rchar := rune(char) + if isSpace(rchar) { + continue + } + + switch char { + case '=', ':': + // library also supports yaml-style value declaration + key = string(src[0:i]) + offset = i + 1 + break loop + case '_': + default: + // variable name should match [A-Za-z0-9_.] + if unicode.IsLetter(rchar) || unicode.IsNumber(rchar) || rchar == '.' { + continue + } + + return "", nil, fmt.Errorf( + `unexpected character %q in variable name near %q`, + string(char), string(src)) + } + } + + if len(src) == 0 { + return "", nil, errors.New("zero length string") + } + + // trim whitespace + key = strings.TrimRightFunc(key, unicode.IsSpace) + cutset = bytes.TrimLeftFunc(src[offset:], isSpace) + return key, cutset, nil +} + +// extractVarValue extracts variable value and returns rest of slice +func extractVarValue(src []byte, vars map[string]string) (value string, rest []byte, err error) { + quote, hasPrefix := hasQuotePrefix(src) + if !hasPrefix { + // unquoted value - read until end of line + endOfLine := bytes.IndexFunc(src, isLineEnd) + + // Hit EOF without a trailing newline + if endOfLine == -1 { + endOfLine = len(src) + + if endOfLine == 0 { + return "", nil, nil + } + } + + // Convert line to rune away to do accurate countback of runes + line := []rune(string(src[0:endOfLine])) + + // Assume end of line is end of var + endOfVar := len(line) + if endOfVar == 0 { + return "", src[endOfLine:], nil + } + + // Work backwards to check if the line ends in whitespace then + // a comment (ie asdasd # some comment) + for i := endOfVar - 1; i >= 0; i-- { + if line[i] == charComment && i > 0 { + if isSpace(line[i-1]) { + endOfVar = i + break + } + } + } + + trimmed := strings.TrimFunc(string(line[0:endOfVar]), isSpace) + + return expandVariables(trimmed, vars), src[endOfLine:], nil + } + + // lookup quoted string terminator + for i := 1; i < len(src); i++ { + if char := src[i]; char != quote { + continue + } + + // skip escaped quote symbol (\" or \', depends on quote) + if prevChar := src[i-1]; prevChar == '\\' { + continue + } + + // trim quotes + trimFunc := isCharFunc(rune(quote)) + value = string(bytes.TrimLeftFunc(bytes.TrimRightFunc(src[0:i], trimFunc), trimFunc)) + if quote == prefixDoubleQuote { + // unescape newlines for double quote (this is compat feature) + // and expand environment variables + value = expandVariables(expandEscapes(value), vars) + } + + return value, src[i+1:], nil + } + + // return formatted error if quoted string is not terminated + valEndIndex := bytes.IndexFunc(src, isCharFunc('\n')) + if valEndIndex == -1 { + valEndIndex = len(src) + } + + return "", nil, fmt.Errorf("unterminated quoted value %s", src[:valEndIndex]) +} + +func expandEscapes(str string) string { + out := escapeRegex.ReplaceAllStringFunc(str, func(match string) string { + c := strings.TrimPrefix(match, `\`) + switch c { + case "n": + return "\n" + case "r": + return "\r" + default: + return match + } + }) + return unescapeCharsRegex.ReplaceAllString(out, "$1") +} + +func indexOfNonSpaceChar(src []byte) int { + return bytes.IndexFunc(src, func(r rune) bool { + return !unicode.IsSpace(r) + }) +} + +// hasQuotePrefix reports whether charset starts with single or double quote and returns quote character +func hasQuotePrefix(src []byte) (prefix byte, isQuored bool) { + if len(src) == 0 { + return 0, false + } + + switch prefix := src[0]; prefix { + case prefixDoubleQuote, prefixSingleQuote: + return prefix, true + default: + return 0, false + } +} + +func isCharFunc(char rune) func(rune) bool { + return func(v rune) bool { + return v == char + } +} + +// isSpace reports whether the rune is a space character but not line break character +// +// this differs from unicode.IsSpace, which also applies line break as space +func isSpace(r rune) bool { + switch r { + case '\t', '\v', '\f', '\r', ' ', 0x85, 0xA0: + return true + } + return false +} + +func isLineEnd(r rune) bool { + if r == '\n' || r == '\r' { + return true + } + return false +} + +var ( + escapeRegex = regexp.MustCompile(`\\.`) + expandVarRegex = regexp.MustCompile(`(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?`) + unescapeCharsRegex = regexp.MustCompile(`\\([^$])`) +) + +func expandVariables(v string, m map[string]string) string { + return expandVarRegex.ReplaceAllStringFunc(v, func(s string) string { + submatch := expandVarRegex.FindStringSubmatch(s) + + if submatch == nil { + return s + } + if submatch[1] == "\\" || submatch[2] == "(" { + return submatch[0][1:] + } else if submatch[4] != "" { + return m[submatch[4]] + } + return s + }) +} diff --git a/test/vendor/github.com/ossrs/go-oryx-lib/LICENSE b/test/vendor/github.com/ossrs/go-oryx-lib/LICENSE new file mode 100644 index 00000000..6615e30a --- /dev/null +++ b/test/vendor/github.com/ossrs/go-oryx-lib/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2017 winlin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/test/vendor/github.com/ossrs/go-oryx-lib/errors/LICENSE b/test/vendor/github.com/ossrs/go-oryx-lib/errors/LICENSE new file mode 100644 index 00000000..835ba3e7 --- /dev/null +++ b/test/vendor/github.com/ossrs/go-oryx-lib/errors/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/test/vendor/github.com/ossrs/go-oryx-lib/errors/README.md b/test/vendor/github.com/ossrs/go-oryx-lib/errors/README.md new file mode 100644 index 00000000..273db3c9 --- /dev/null +++ b/test/vendor/github.com/ossrs/go-oryx-lib/errors/README.md @@ -0,0 +1,52 @@ +# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) + +Package errors provides simple error handling primitives. + +`go get github.com/pkg/errors` + +The traditional error handling idiom in Go is roughly akin to +```go +if err != nil { + return err +} +``` +which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. + +## Adding context to an error + +The errors.Wrap function returns a new error that adds context to the original error. For example +```go +_, err := ioutil.ReadAll(r) +if err != nil { + return errors.Wrap(err, "read failed") +} +``` +## Retrieving the cause of an error + +Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. +```go +type causer interface { + Cause() error +} +``` +`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: +```go +switch err := errors.Cause(err).(type) { +case *MyError: + // handle specifically +default: + // unknown error +} +``` + +[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). + +## Contributing + +We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. + +Before proposing a change, please discuss your change by raising an issue. + +## Licence + +BSD-2-Clause diff --git a/test/vendor/github.com/ossrs/go-oryx-lib/errors/errors.go b/test/vendor/github.com/ossrs/go-oryx-lib/errors/errors.go new file mode 100644 index 00000000..257bc3cc --- /dev/null +++ b/test/vendor/github.com/ossrs/go-oryx-lib/errors/errors.go @@ -0,0 +1,270 @@ +// Package errors provides simple error handling primitives. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Wrap function returns a new error that adds context to the +// original error by recording a stack trace at the point Wrap is called, +// and the supplied message. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } +// +// If additional control is required the errors.WithStack and errors.WithMessage +// functions destructure errors.Wrap into its component operations of annotating +// an error with a stack trace and an a message, respectively. +// +// Retrieving the cause of an error +// +// Using errors.Wrap constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Wrap to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error which does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +// +// causer interface is not exported by this package, but is considered a part +// of stable public API. +// +// Formatted printing of errors +// +// All error values returned from this package implement fmt.Formatter and can +// be formatted by the fmt package. The following verbs are supported +// +// %s print the error. If the error has a Cause it will be +// printed recursively +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. +// +// Retrieving the stack trace of an error or wrapper +// +// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are +// invoked. This information can be retrieved with the following interface. +// +// type stackTracer interface { +// StackTrace() errors.StackTrace +// } +// +// Where errors.StackTrace is defined as +// +// type StackTrace []Frame +// +// The Frame type represents a call site in the stack trace. Frame supports +// the fmt.Formatter interface that can be used for printing information about +// the stack trace of this error. For example: +// +// if err, ok := err.(stackTracer); ok { +// for _, f := range err.StackTrace() { +// fmt.Printf("%+s:%d", f) +// } +// } +// +// stackTracer interface is not exported by this package, but is considered a part +// of stable public API. +// +// See the documentation for Frame.Format for more details. +// Fork from https://github.com/pkg/errors +package errors + +import ( + "fmt" + "io" +) + +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return &fundamental{ + msg: message, + stack: callers(), + } +} + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +// Errorf also records the stack trace at the point it was called. +func Errorf(format string, args ...interface{}) error { + return &fundamental{ + msg: fmt.Sprintf(format, args...), + stack: callers(), + } +} + +// fundamental is an error that has a message and a stack, but no caller. +type fundamental struct { + msg string + *stack +} + +func (f *fundamental) Error() string { return f.msg } + +func (f *fundamental) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + io.WriteString(s, f.msg) + f.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, f.msg) + case 'q': + fmt.Fprintf(s, "%q", f.msg) + } +} + +// WithStack annotates err with a stack trace at the point WithStack was called. +// If err is nil, WithStack returns nil. +func WithStack(err error) error { + if err == nil { + return nil + } + return &withStack{ + err, + callers(), + } +} + +type withStack struct { + error + *stack +} + +func (w *withStack) Cause() error { return w.error } + +func (w *withStack) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v", w.Cause()) + w.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, w.Error()) + case 'q': + fmt.Fprintf(s, "%q", w.Error()) + } +} + +// Wrap returns an error annotating err with a stack trace +// at the point Wrap is called, and the supplied message. +// If err is nil, Wrap returns nil. +func Wrap(err error, message string) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: message, + } + return &withStack{ + err, + callers(), + } +} + +// Wrapf returns an error annotating err with a stack trace +// at the point Wrapf is call, and the format specifier. +// If err is nil, Wrapf returns nil. +func Wrapf(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } + return &withStack{ + err, + callers(), + } +} + +// WithMessage annotates err with a new message. +// If err is nil, WithMessage returns nil. +func WithMessage(err error, message string) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: message, + } +} + +type withMessage struct { + cause error + msg string +} + +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } + +func (w *withMessage) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", w.Cause()) + io.WriteString(s, w.msg) + return + } + fallthrough + case 's', 'q': + io.WriteString(s, w.Error()) + } +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} diff --git a/test/vendor/github.com/ossrs/go-oryx-lib/errors/stack.go b/test/vendor/github.com/ossrs/go-oryx-lib/errors/stack.go new file mode 100644 index 00000000..6c42db5a --- /dev/null +++ b/test/vendor/github.com/ossrs/go-oryx-lib/errors/stack.go @@ -0,0 +1,187 @@ +// Fork from https://github.com/pkg/errors +package errors + +import ( + "fmt" + "io" + "path" + "runtime" + "strings" +) + +// Frame represents a program counter inside a stack frame. +type Frame uintptr + +// pc returns the program counter for this frame; +// multiple frames may have the same PC value. +func (f Frame) pc() uintptr { return uintptr(f) - 1 } + +// file returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) file() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + file, _ := fn.FileLine(f.pc()) + return file +} + +// line returns the line number of source code of the +// function for this Frame's pc. +func (f Frame) line() int { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return 0 + } + _, line := fn.FileLine(f.pc()) + return line +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+s path of source file relative to the compile time GOPATH +// %+v equivalent to %+s:%d +func (f Frame) Format(s fmt.State, verb rune) { + switch verb { + case 's': + switch { + case s.Flag('+'): + pc := f.pc() + fn := runtime.FuncForPC(pc) + if fn == nil { + io.WriteString(s, "unknown") + } else { + file, _ := fn.FileLine(pc) + fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) + } + default: + io.WriteString(s, path.Base(f.file())) + } + case 'd': + fmt.Fprintf(s, "%d", f.line()) + case 'n': + name := runtime.FuncForPC(f.pc()).Name() + io.WriteString(s, funcname(name)) + case 'v': + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') + } +} + +// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). +type StackTrace []Frame + +// Format formats the stack of Frames according to the fmt.Formatter interface. +// +// %s lists source files for each Frame in the stack +// %v lists the source file and line number for each Frame in the stack +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+v Prints filename, function, and line number for each Frame in the stack. +func (st StackTrace) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case s.Flag('+'): + for _, f := range st { + fmt.Fprintf(s, "\n%+v", f) + } + case s.Flag('#'): + fmt.Fprintf(s, "%#v", []Frame(st)) + default: + fmt.Fprintf(s, "%v", []Frame(st)) + } + case 's': + fmt.Fprintf(s, "%s", []Frame(st)) + } +} + +// stack represents a stack of program counters. +type stack []uintptr + +func (s *stack) Format(st fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case st.Flag('+'): + for _, pc := range *s { + f := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) + } + } + } +} + +func (s *stack) StackTrace() StackTrace { + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) + } + return f +} + +func callers() *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(3, pcs[:]) + var st stack = pcs[0:n] + return &st +} + +// funcname removes the path prefix component of a function's name reported by func.Name(). +func funcname(name string) string { + i := strings.LastIndex(name, "/") + name = name[i+1:] + i = strings.Index(name, ".") + return name[i+1:] +} + +func trimGOPATH(name, file string) string { + // Here we want to get the source file path relative to the compile time + // GOPATH. As of Go 1.6.x there is no direct way to know the compiled + // GOPATH at runtime, but we can infer the number of path segments in the + // GOPATH. We note that fn.Name() returns the function name qualified by + // the import path, which does not include the GOPATH. Thus we can trim + // segments from the beginning of the file path until the number of path + // separators remaining is one more than the number of path separators in + // the function name. For example, given: + // + // GOPATH /home/user + // file /home/user/src/pkg/sub/file.go + // fn.Name() pkg/sub.Type.Method + // + // We want to produce: + // + // pkg/sub/file.go + // + // From this we can easily see that fn.Name() has one less path separator + // than our desired output. We count separators from the end of the file + // path until it finds two more than in the function name and then move + // one character forward to preserve the initial path segment without a + // leading separator. + const sep = "/" + goal := strings.Count(name, sep) + 2 + i := len(file) + for n := 0; n < goal; n++ { + i = strings.LastIndex(file[:i], sep) + if i == -1 { + // not enough separators found, set i so that the slice expression + // below leaves file unmodified + i = -len(sep) + break + } + } + // get back to 0 or trim the leading separator + file = file[i+len(sep):] + return file +} diff --git a/test/vendor/github.com/ossrs/go-oryx-lib/logger/go17.go b/test/vendor/github.com/ossrs/go-oryx-lib/logger/go17.go new file mode 100644 index 00000000..65bdeb76 --- /dev/null +++ b/test/vendor/github.com/ossrs/go-oryx-lib/logger/go17.go @@ -0,0 +1,86 @@ +// The MIT License (MIT) +// +// Copyright (c) 2013-2017 Oryx(ossrs) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// +build go1.7 + +package logger + +import ( + "context" + "fmt" + "os" +) + +func (v *loggerPlus) Println(ctx Context, a ...interface{}) { + args := v.contextFormat(ctx, a...) + v.doPrintln(args...) +} + +func (v *loggerPlus) Printf(ctx Context, format string, a ...interface{}) { + format, args := v.contextFormatf(ctx, format, a...) + v.doPrintf(format, args...) +} + +func (v *loggerPlus) contextFormat(ctx Context, a ...interface{}) []interface{} { + if ctx, ok := ctx.(context.Context); ok { + if cid, ok := ctx.Value(cidKey).(int); ok { + return append([]interface{}{fmt.Sprintf("[%v][%v]", os.Getpid(), cid)}, a...) + } + } else { + return v.format(ctx, a...) + } + return a +} + +func (v *loggerPlus) contextFormatf(ctx Context, format string, a ...interface{}) (string, []interface{}) { + if ctx, ok := ctx.(context.Context); ok { + if cid, ok := ctx.Value(cidKey).(int); ok { + return "[%v][%v] " + format, append([]interface{}{os.Getpid(), cid}, a...) + } + } else { + return v.formatf(ctx, format, a...) + } + return format, a +} + +// User should use context with value to pass the cid. +type key string + +var cidKey key = "cid.logger.ossrs.org" + +var gCid int = 999 + +// Create context with value. +func WithContext(ctx context.Context) context.Context { + gCid += 1 + return context.WithValue(ctx, cidKey, gCid) +} + +// Create context with value from parent, copy the cid from source context. +// @remark Create new cid if source has no cid represent. +func AliasContext(parent context.Context, source context.Context) context.Context { + if source != nil { + if cid, ok := source.Value(cidKey).(int); ok { + return context.WithValue(parent, cidKey, cid) + } + } + return WithContext(parent) +} diff --git a/test/vendor/github.com/ossrs/go-oryx-lib/logger/logger.go b/test/vendor/github.com/ossrs/go-oryx-lib/logger/logger.go new file mode 100644 index 00000000..61cea362 --- /dev/null +++ b/test/vendor/github.com/ossrs/go-oryx-lib/logger/logger.go @@ -0,0 +1,239 @@ +// The MIT License (MIT) +// +// Copyright (c) 2013-2017 Oryx(ossrs) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// The oryx logger package provides connection-oriented log service. +// logger.I(ctx, ...) +// logger.T(ctx, ...) +// logger.W(ctx, ...) +// logger.E(ctx, ...) +// Or use format: +// logger.If(ctx, format, ...) +// logger.Tf(ctx, format, ...) +// logger.Wf(ctx, format, ...) +// logger.Ef(ctx, format, ...) +// @remark the Context is optional thus can be nil. +// @remark From 1.7+, the ctx could be context.Context, wrap by logger.WithContext, +// please read ExampleLogger_ContextGO17(). +package logger + +import ( + "fmt" + "io" + "io/ioutil" + "log" + "os" +) + +// default level for logger. +const ( + logInfoLabel = "[info] " + logTraceLabel = "[trace] " + logWarnLabel = "[warn] " + logErrorLabel = "[error] " +) + +// The context for current goroutine. +// It maybe a cidContext or context.Context from GO1.7. +// @remark Use logger.WithContext(ctx) to wrap the context. +type Context interface{} + +// The context to get current coroutine cid. +type cidContext interface { + Cid() int +} + +// the LOG+ which provides connection-based log. +type loggerPlus struct { + logger *log.Logger +} + +func NewLoggerPlus(l *log.Logger) Logger { + return &loggerPlus{logger: l} +} + +func (v *loggerPlus) format(ctx Context, a ...interface{}) []interface{} { + if ctx == nil { + return append([]interface{}{fmt.Sprintf("[%v] ", os.Getpid())}, a...) + } else if ctx, ok := ctx.(cidContext); ok { + return append([]interface{}{fmt.Sprintf("[%v][%v] ", os.Getpid(), ctx.Cid())}, a...) + } + return a +} + +func (v *loggerPlus) formatf(ctx Context, format string, a ...interface{}) (string, []interface{}) { + if ctx == nil { + return "[%v] " + format, append([]interface{}{os.Getpid()}, a...) + } else if ctx, ok := ctx.(cidContext); ok { + return "[%v][%v] " + format, append([]interface{}{os.Getpid(), ctx.Cid()}, a...) + } + return format, a +} + +var colorYellow = "\033[33m" +var colorRed = "\033[31m" +var colorBlack = "\033[0m" + +func (v *loggerPlus) doPrintln(args ...interface{}) { + if previousCloser == nil { + if v == Error { + fmt.Fprintf(os.Stdout, colorRed) + v.logger.Println(args...) + fmt.Fprintf(os.Stdout, colorBlack) + } else if v == Warn { + fmt.Fprintf(os.Stdout, colorYellow) + v.logger.Println(args...) + fmt.Fprintf(os.Stdout, colorBlack) + } else { + v.logger.Println(args...) + } + } else { + v.logger.Println(args...) + } +} + +func (v *loggerPlus) doPrintf(format string, args ...interface{}) { + if previousCloser == nil { + if v == Error { + fmt.Fprintf(os.Stdout, colorRed) + v.logger.Printf(format, args...) + fmt.Fprintf(os.Stdout, colorBlack) + } else if v == Warn { + fmt.Fprintf(os.Stdout, colorYellow) + v.logger.Printf(format, args...) + fmt.Fprintf(os.Stdout, colorBlack) + } else { + v.logger.Printf(format, args...) + } + } else { + v.logger.Printf(format, args...) + } +} + +// Info, the verbose info level, very detail log, the lowest level, to discard. +var Info Logger + +// Alias for Info level println. +func I(ctx Context, a ...interface{}) { + Info.Println(ctx, a...) +} + +// Printf for Info level log. +func If(ctx Context, format string, a ...interface{}) { + Info.Printf(ctx, format, a...) +} + +// Trace, the trace level, something important, the default log level, to stdout. +var Trace Logger + +// Alias for Trace level println. +func T(ctx Context, a ...interface{}) { + Trace.Println(ctx, a...) +} + +// Printf for Trace level log. +func Tf(ctx Context, format string, a ...interface{}) { + Trace.Printf(ctx, format, a...) +} + +// Warn, the warning level, dangerous information, to Stdout. +var Warn Logger + +// Alias for Warn level println. +func W(ctx Context, a ...interface{}) { + Warn.Println(ctx, a...) +} + +// Printf for Warn level log. +func Wf(ctx Context, format string, a ...interface{}) { + Warn.Printf(ctx, format, a...) +} + +// Error, the error level, fatal error things, ot Stdout. +var Error Logger + +// Alias for Error level println. +func E(ctx Context, a ...interface{}) { + Error.Println(ctx, a...) +} + +// Printf for Error level log. +func Ef(ctx Context, format string, a ...interface{}) { + Error.Printf(ctx, format, a...) +} + +// The logger for oryx. +type Logger interface { + // Println for logger plus, + // @param ctx the connection-oriented context, + // or context.Context from GO1.7, or nil to ignore. + Println(ctx Context, a ...interface{}) + Printf(ctx Context, format string, a ...interface{}) +} + +func init() { + Info = NewLoggerPlus(log.New(ioutil.Discard, logInfoLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) + Trace = NewLoggerPlus(log.New(os.Stdout, logTraceLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) + Warn = NewLoggerPlus(log.New(os.Stderr, logWarnLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) + Error = NewLoggerPlus(log.New(os.Stderr, logErrorLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) + + // init writer and closer. + previousWriter = os.Stdout + previousCloser = nil +} + +// Switch the underlayer io. +// @remark user must close previous io for logger never close it. +func Switch(w io.Writer) io.Writer { + // TODO: support level, default to trace here. + Info = NewLoggerPlus(log.New(ioutil.Discard, logInfoLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) + Trace = NewLoggerPlus(log.New(w, logTraceLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) + Warn = NewLoggerPlus(log.New(w, logWarnLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) + Error = NewLoggerPlus(log.New(w, logErrorLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) + + ow := previousWriter + previousWriter = w + + if c, ok := w.(io.Closer); ok { + previousCloser = c + } + + return ow +} + +// The previous underlayer io for logger. +var previousCloser io.Closer +var previousWriter io.Writer + +// The interface io.Closer +// Cleanup the logger, discard any log util switch to fresh writer. +func Close() (err error) { + Info = NewLoggerPlus(log.New(ioutil.Discard, logInfoLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) + Trace = NewLoggerPlus(log.New(ioutil.Discard, logTraceLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) + Warn = NewLoggerPlus(log.New(ioutil.Discard, logWarnLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) + Error = NewLoggerPlus(log.New(ioutil.Discard, logErrorLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) + + if previousCloser != nil { + err = previousCloser.Close() + previousCloser = nil + } + + return +} diff --git a/test/vendor/github.com/ossrs/go-oryx-lib/logger/pre_go17.go b/test/vendor/github.com/ossrs/go-oryx-lib/logger/pre_go17.go new file mode 100644 index 00000000..24041dc8 --- /dev/null +++ b/test/vendor/github.com/ossrs/go-oryx-lib/logger/pre_go17.go @@ -0,0 +1,34 @@ +// The MIT License (MIT) +// +// Copyright (c) 2013-2017 Oryx(ossrs) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// +build !go1.7 + +package logger + +func (v *loggerPlus) Println(ctx Context, a ...interface{}) { + args := v.format(ctx, a...) + v.doPrintln(args...) +} + +func (v *loggerPlus) Printf(ctx Context, format string, a ...interface{}) { + format, args := v.formatf(ctx, format, a...) + v.doPrintf(format, args...) +} diff --git a/test/vendor/modules.txt b/test/vendor/modules.txt new file mode 100644 index 00000000..53e06884 --- /dev/null +++ b/test/vendor/modules.txt @@ -0,0 +1,7 @@ +# github.com/joho/godotenv v1.5.1 +## explicit +github.com/joho/godotenv +# github.com/ossrs/go-oryx-lib v0.0.9 +## explicit +github.com/ossrs/go-oryx-lib/errors +github.com/ossrs/go-oryx-lib/logger diff --git a/ui/src/pages/Settings.js b/ui/src/pages/Settings.js index 8374e5d0..1aba9059 100644 --- a/ui/src/pages/Settings.js +++ b/ui/src/pages/Settings.js @@ -125,9 +125,7 @@ function SettingNginx() { } function SettingOpenApi({copyToClipboard}) { - const [apiSecretCopied, setAPISecretCopied] = React.useState(); const [apiSecret, setAPISecret] = React.useState(); - const [apiToken, setApiToken] = React.useState(); const handleError = useErrorHandler(); const {t} = useTranslation(); @@ -141,21 +139,9 @@ function SettingOpenApi({copyToClipboard}) { }).catch(handleError); }, [handleError]); - const createApiToken = React.useCallback((e) => { - e.preventDefault(); - - axios.post('/terraform/v1/mgmt/secret/token', { - apiSecret - }).then(res => { - setApiToken(res.data); - console.log(`OpenAPI Example: Get access_token ok, data=${JSON.stringify(res.data.data)}`); - }).catch(handleError); - }, [handleError, apiSecret]); - const copyApiSecret = React.useCallback((e, apiSecret) => { copyToClipboard(e, apiSecret); - setAPISecretCopied(true); - }, [copyToClipboard, setAPISecretCopied]); + }, [copyToClipboard]); return ( @@ -170,7 +156,6 @@ function SettingOpenApi({copyToClipboard}) {
  • {t('openapi.usage1')}
  • {t('openapi.usage2')}
  • -
  • {t('openapi.usage3')}
@@ -181,7 +166,7 @@ function SettingOpenApi({copyToClipboard}) { ApiSecret * {t('openapi.secretTip')} - +   - - } - - {t('openapi.apiPublishSecret')} - +
@@ -392,17 +344,17 @@ function SettingHttpsDisabled() { function RunOpenAPI(props) { const [showResult, setShowResult] = React.useState(); const {t} = useTranslation(); - const {token, api} = props; + const {apiSecret, api, data} = props; const onClick = React.useCallback((e) => { e.preventDefault(); setShowResult(!showResult); }, [showResult]); - if (!token) { + if (!apiSecret) { return (
- {t('openapi.tokenEmpty')}{t('openapi.token')} + {t('openapi.secretEmpty')}{t('openapi.secret')}
); } @@ -410,15 +362,22 @@ function RunOpenAPI(props) { return (
- API + URL - Body -
-          {JSON.stringify({token: `${token || ''}`}, null, 2)}
-        
+ Headers +
+ {data && + + Body + +
+
+          
+
+ } { showResult && } @@ -429,18 +388,20 @@ function RunOpenAPI(props) { ); } -function OpenAPIResult({token, api}) { +function OpenAPIResult({apiSecret, api, data}) { const [openAPIRes, setOpenAPIRes] = React.useState(); const handleError = useErrorHandler(); React.useEffect(() => { - axios.post(api, { - token: token + axios.post(api, data, { + headers: { + 'Authorization': `Bearer ${apiSecret}`, + } }).then(res => { setOpenAPIRes(res.data); console.log(`OpenAPI: Run api=${api} ok, data=${JSON.stringify(res.data.data)}`); }).catch(handleError); - }, [handleError, token, api]); + }, [handleError, apiSecret, api, data]); return ( <> diff --git a/ui/src/resources/locale.json b/ui/src/resources/locale.json index c5e4c7c6..02d955ac 100644 --- a/ui/src/resources/locale.json +++ b/ui/src/resources/locale.json @@ -9,7 +9,7 @@ }, "login": { "passwordLabel": "请输入密码", - "passwordTip": "忘记密码?可登录机器查看文件 /usr/local/srs-cloud/platform/.env", + "passwordTip": "忘记密码?可登录机器查看文件 /data/config/.env", "labelShow": "显示密码", "labelLogin": "登录" }, @@ -157,15 +157,12 @@ "openapi": { "title": "使用场景", "summary": "向开发者提供 OpenAPI, 方便开发者对接", - "usage1": "Token是OpenAPI接口调用凭据, 调用各接口时都需要使用Token", - "usage2": "使用ApiSecret,调用接口可创建Token, 默认有效期为1年", - "usage3": "请按照下面的步骤,依次点击,可以看到调用的步骤", - "secret": "第1步:拷贝ApiSecret", + "usage1": "ApiSecret是OpenAPI接口调用凭据, 调用各接口时都需要使用ApiSecret", + "usage2": "请按照下面的步骤,依次点击,可以看到调用的步骤", + "secret": "拷贝ApiSecret", "secretTip": "使用ApiSecret生成Token,用于访问OpenAPI", "secretCopy": "复制ApiSecret", - "token": "第2步:[API] 创建Token", - "apiPublishSecret": "第3步:[API] 获取推流密钥", - "tokenEmpty": "没有Token,请先运行", + "apiPublishSecret": "[API] 获取推流密钥", "secretEmpty": "没有ApiSecret,请先运行" }, "tutorials": { @@ -208,7 +205,7 @@ }, "login": { "passwordLabel": "Password", - "passwordTip": "The password is store at /usr/local/srs-cloud/platform/.env", + "passwordTip": "The password is store at /data/config/.env", "labelShow": "Show Password", "labelLogin": "Submit" }, @@ -356,15 +353,12 @@ "openapi": { "title": "Introduction", "summary": "For developer to call OpenAPI of SRS Cloud", - "usage1": "Token is used for authentication of OpenAPI", - "usage2": "Use ApiSecret to create Token, expired in 1 year", - "usage3": "Please click the bellow steps", - "secret": "Step 1: Copy ApiSecret", + "usage1": "ApiSecret is used for authentication of OpenAPI", + "usage2": "Please click the bellow steps", + "secret": "Copy ApiSecret", "secretTip": "Use ApiSecret to create Token", "secretCopy": "Copy ApiSecret", - "token": "Step 2: [API] Create Token", - "apiPublishSecret": "Step 3: [API] Query Publish Secret", - "tokenEmpty": "No Token, please run ", + "apiPublishSecret": "[API] Query Publish Secret", "secretEmpty": "No ApiSecret, please run " }, "tutorials": {