├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .whitesource ├── Dockerfile ├── LICENSE ├── README.md ├── auth └── authutils.go ├── conf ├── ssl │ ├── localhost.crt │ └── localhost.key └── test.json ├── crypt └── basiccrypt.go ├── database └── redis │ ├── basicredis.go │ └── basicredis_test.go ├── datastructures └── datastructures.go ├── docker-compose.yml ├── go.mod ├── go.sum ├── main.go └── utils ├── common └── commonutils.go └── http └── httputils.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report about: Create a report to help us improve title: '' 3 | labels: '' 4 | assignees: '' 5 | 6 | --- 7 | 8 | **Describe the bug** 9 | A clear and concise description of what the bug is. 10 | 11 | **To Reproduce** 12 | Steps to reproduce the behavior: 13 | 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Desktop (please complete the following information):** 26 | 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | 33 | - Device: [e.g. iPhone6] 34 | - OS: [e.g. iOS8.1] 35 | - Browser [e.g. stock browser, safari] 36 | - Version [e.g. 22] 37 | 38 | **Additional context** 39 | Add any other context about the problem here. 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request about: Suggest an idea for this project title: '' 3 | labels: '' 4 | assignees: '' 5 | 6 | --- 7 | 8 | **Is your feature request related to a problem? Please describe.** 9 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 10 | 11 | **Describe the solution you'd like** 12 | A clear and concise description of what you want to happen. 13 | 14 | **Describe alternatives you've considered** 15 | A clear and concise description of any alternative solutions or features you've considered. 16 | 17 | **Additional context** 18 | Add any other context or screenshots about the feature request here. 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | StreamingServer* 3 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "scanSettings": { 3 | "baseBranches": [] 4 | }, 5 | "checkRunSettings": { 6 | "vulnerableCheckRunConclusionLevel": "failure", 7 | "displayMode": "diff" 8 | }, 9 | "issueSettings": { 10 | "minSeverityLevel": "LOW" 11 | } 12 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine 2 | 3 | #RUN apt-get update && apt-get install -y --no-install-recommends gcc g++ make ; apt clean ; rm -rf /var/lib/apt/lists/* 4 | 5 | LABEL maintainer="Alessio Savi " 6 | 7 | # Set the Current Working Directory inside the container 8 | WORKDIR /app 9 | 10 | # Copy go mod and sum files 11 | COPY go.mod go.sum /app/ 12 | 13 | # Download dependencies 14 | RUN go mod download 15 | 16 | # Copy the source from the current directory to the Working Directory inside the container 17 | COPY . /app/ 18 | 19 | RUN go clean 20 | 21 | # Build the Go app 22 | RUN go build -o StreamingServer . 23 | 24 | # Expose port 11001 to the outside world 25 | EXPOSE 11001 26 | 27 | # Run the executable 28 | CMD ["./StreamingServer"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 alessiosavi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StreamingServer 2 | 3 | A simple video streaming services with authentication using redis 4 | 5 | [![License](https://img.shields.io/github/license/alessiosavi/StreamingServer)](https://img.shields.io/github/license/alessiosavi/StreamingServer) 6 | [![Version](https://img.shields.io/github/v/tag/alessiosavi/StreamingServer)](https://img.shields.io/github/v/tag/alessiosavi/StreamingServer) 7 | [![Code size](https://img.shields.io/github/languages/code-size/alessiosavi/StreamingServer)](https://img.shields.io/github/languages/code-size/alessiosavi/StreamingServer) 8 | [![Repo size](https://img.shields.io/github/repo-size/alessiosavi/StreamingServer)](https://img.shields.io/github/repo-size/alessiosavi/StreamingServer) 9 | [![Issue open](https://img.shields.io/github/issues/alessiosavi/StreamingServer)](https://img.shields.io/github/issues/alessiosavi/StreamingServer) 10 | [![Issue closed](https://img.shields.io/github/issues-closed/alessiosavi/StreamingServer)](https://img.shields.io/github/issues-closed/alessiosavi/StreamingServer) 11 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/9c5dc127effe4048b33ed1718190c299)](https://app.codacy.com/manual/alessiosavi/StreamingServer?utm_source=github.com&utm_medium=referral&utm_content=alessiosavi/StreamingServer&utm_campaign=Badge_Grade_Dashboard) 12 | [![Go Report Card](https://goreportcard.com/badge/github.com/alessiosavi/StreamingServer)](https://goreportcard.com/report/github.com/alessiosavi/StreamingServer) 13 | [![GoDoc](https://godoc.org/github.com/alessiosavi/GoGPUtils?status.svg)](https://godoc.org/github.com/alessiosavi/StreamingServer) 14 | 15 | ## Introduction 16 | 17 | This project is developed for have a plug-and-play video streaming server delegated to expose all the films downloaded 18 | from you main computer. With this tool, you can save all of you preterits films, song, videos into your PC. Then, you 19 | can view these media from anywhere using an internet connection. 20 | 21 | The server have a basic authentication system. One endpoint is delegated to register a user, another one is delegated 22 | to manage the log-in phase. 23 | 24 | Another endpoint is delegated to verify the account, so before that an account is able to stream your resources, you 25 | have to verify that the account is related to someone that you know 26 | 27 | ## Requirements 28 | 29 | - [GoGPUtils](https://github.com/alessiosavi/GoGPUtils/string) Enhance productivity and avoid to reinvent the wheel 30 | every time that you start a Go project 31 | - [redis](https://github.com/go-redis/redis) Type-safe Redis client for Golang 32 | - [fasthttp](https://github.com/valyala/fasthttp) Fast HTTP package for Go. Tuned for high performance. Zero memory 33 | allocations in hot paths. Up to 10x faster than net/http 34 | - [logrus](https://github.com/sirupsen/logrus) Structured, pluggable logging for Go. 35 | - [filename](https://github.com/onrik/logrus/) Hooks for logrus logging 36 | 37 | ## Table Of Contents 38 | 39 | - [StreamingServer](#StreamingServer) 40 | - [Introduction](#introduction) 41 | - [Requirements](#requirements) 42 | - [Table Of Contents](#table-of-contents) 43 | - [Prerequisites](#prerequisites) 44 | - [Usage](#usage) 45 | - [In Details](#in-details) 46 | - [Example response](#example-response) 47 | - [Contributing](#contributing) 48 | - [Versioning](#versioning) 49 | - [Authors](#authors) 50 | - [License](#license) 51 | - [Acknowledgments](#acknowledgments) 52 | 53 | ## Prerequisites 54 | 55 | The software is coded in `golang`, into the `go.mod` file are saved the necessary dependencies. In order to download all 56 | the dependencies, you can type the following string from your terminal 57 | 58 | ```bash 59 | go get -v -u all 60 | ``` 61 | 62 | ## Usage 63 | 64 | ## In Details 65 | 66 | ```bash 67 | tree 68 | . 69 | ├── auth 70 | │ └── authutils.go 71 | ├── conf // Folder that contains the configuration files 72 | │ ├── ssl // Folder that contains the certificate for the SSL connection 73 | │ │ ├── localhost.crt 74 | │ │ └── localhost.key 75 | │ └── test.json // File that contain the configuration related to the tool 76 | ├── crypt 77 | │ └── basiccrypt.go // basiccrypt contain the necessary method to encrypt/decrypt data 78 | ├── database 79 | │ └── redis 80 | │ └── basicredis.go // basicredis contain the necessary method to deal with save/load/update data from/to redis 81 | ├── datastructures 82 | │ └── datastructures.go // datastructures contain the necessary datastructure used among all the project 83 | ├── docker-compose.yml 84 | ├── Dockerfile 85 | ├── go.mod 86 | ├── go.sum 87 | ├── log 88 | ├── main.go 89 | ├── README.md 90 | └── utils 91 | ├── common 92 | │ └── commonutils.go // commonutils contain a bunch of method used as utils 93 | └── http 94 | └── httputils.go // httputils contain the core method related to the HTTP functionalities 95 | ``` 96 | 97 | ## Example response 98 | 99 | TODO 100 | 101 | ## Contributing 102 | 103 | - Feel free to open issue in order to __*require new functionality*__; 104 | - Feel free to open issue __*if you discover a bug*__; 105 | - New idea/request/concept are very appreciated!; 106 | 107 | ## Test 108 | 109 | Test are work in progress, is a good first issue for contribute to the project 110 | 111 | ## Versioning 112 | 113 | We use [SemVer](http://semver.org/) for versioning. 114 | 115 | ## Authors 116 | 117 | - **Alessio Savi** - *Initial work & Concept* - [Linkedin](https://www.linkedin.com/in/alessio-savi-2136b2188/) 118 | - [Github](https://github.com/alessiosavi) 119 | 120 | ## Contributors 121 | 122 | - **Alessio Savi** 123 | 124 | ## License 125 | 126 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 127 | 128 | ## Acknowledgments 129 | 130 | Security, in this phase of the development, is not my first concern. Please, fill an issue if you find something that 131 | can be enhanced from a security POV -------------------------------------------------------------------------------- /auth/authutils.go: -------------------------------------------------------------------------------- 1 | package authutils 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "errors" 7 | "regexp" 8 | "strings" 9 | 10 | basiccrypt "github.com/alessiosavi/StreamingServer/crypt" 11 | 12 | basicredis "github.com/alessiosavi/StreamingServer/database/redis" 13 | "github.com/alessiosavi/StreamingServer/datastructures" 14 | 15 | stringutils "github.com/alessiosavi/GoGPUtils/string" 16 | "github.com/go-redis/redis" 17 | "github.com/valyala/fasthttp" 18 | 19 | log "github.com/sirupsen/logrus" 20 | ) 21 | 22 | // ====== HTTP CORE METHODS ====== 23 | 24 | // LoginUserHTTPCore is delegated to manage the "core process" of authentication. It uses the username in input for retrieve the customer 25 | // data from MongoDB. If the data is found, then the password in input will be compared with the one retrieved from the database 26 | func LoginUserHTTPCore(username, password string, redisClient *redis.Client) error { 27 | var err error 28 | log.Debug("LoginUserHTTPCore | Verify if user [", username, "] is registered ...") 29 | log.Info("LoginUserHTTPCore | Getting value from DB ...") 30 | var User datastructures.User // Allocate a Person for store the DB result of next instruction 31 | if err = basicredis.GetValueFromDB(redisClient, username, &User); err == nil { 32 | log.Debug("LoginUserHTTPCore | Comparing password ...") 33 | if basiccrypt.VerifyPlainPasswords(password, User.Password, username+":"+password) { // Comparing password of the user from DB with the one in input 34 | log.Warn("LoginUserHTTPCore | Client credential authorizated !!! | User: ", User) 35 | return nil 36 | } 37 | log.Error("LoginUserHTTPCore | Passwords does not match!!") 38 | return errors.New("INVALID_PASSWORD") 39 | } 40 | log.Error("LoginUserHTTPCore | User is not registered [", username, "] | Error: ", err) 41 | return errors.New("USER [" + username + "] IS NOT REGISTERED!") 42 | } 43 | 44 | // RegisterUserHTTPCore is delegated to register the credential of the user into the Redis database. 45 | // It establishes the connection to MongoDB with a specialized function, then it create a user with the input data. 46 | // After that, it asks a delegated function to insert the data into Redis. 47 | func RegisterUserHTTPCore(username, password string, redisClient *redis.Client) error { 48 | // User := datastructures.User{Username: username, Password: basiccrypt.Encrypt([]byte(password), username+":"+password)} // Create the user 49 | log.Debug("RegisterUserHTTPCore | Registering [", username, ":", password, "]") 50 | if redisClient == nil { 51 | log.Error("RegisterUserHTTPCore | Impossible to connect to DB | ", redisClient) 52 | return errors.New("DB_UNAVAILABLE") 53 | } 54 | log.Debug("RegisterUserHTTPCore | Verifying if connection is available ...") 55 | if err := redisClient.Ping().Err(); err != nil { 56 | log.Error("RegisterUserHTTPCore | Redis ping: ", err) 57 | return err 58 | } 59 | log.Debug("RegisterUserHTTPCore | Connection established! Inserting data ...") 60 | // Store the password encrypting with the 'username:password' as key :/ 61 | // TODO: Increase security 62 | var User datastructures.User 63 | if err := basicredis.GetValueFromDB(redisClient, username, &User); err == redis.Nil { 64 | log.Debug("RegisterUserHTTPCore | User [", username, "] does not exists, inserting into DB") 65 | User := datastructures.User{ // Create the user 66 | Username: username, 67 | Password: basiccrypt.Encrypt([]byte(password), username+":"+password), 68 | Email: "", 69 | Active: false, 70 | } 71 | return basicredis.InsertValueIntoDB(redisClient, User.Username, User) 72 | } 73 | return errors.New("ALREADY_EXIST") 74 | 75 | } 76 | 77 | // VerifyCookieFromRedisHTTPCore is delegated to verify if the cookie of the customer is present on the DB (aka is logged). 78 | // This method have only to verify if the token provided by the customer that use the API is present on RedisDB. 79 | // In first instance it try to validate the input data. Then will continue connecting to Redis in order to retrieve the token of 80 | // the customer. If the token is found, the customer is authorized to continue. 81 | func VerifyCookieFromRedisHTTPCore(user, token string, redisClient *redis.Client) error { 82 | var err error 83 | log.Debug("VerifyCookieFromRedisHTTPCore | START | User: ", user, " | Token: ", token) 84 | if validateUsername(user) { // Verify that the credentials respect the rules 85 | if validateToken(token) { // Verify that the token respect the rules 86 | log.Debug("VerifyCookieFromRedisHTTPCore | Credential validated, retrieving token value from Redis ...") 87 | if dbToken, err := basicredis.GetTokenFromDB(redisClient, user); err == nil { 88 | log.Trace("VerifyCookieFromRedisHTTPCore | Data retrieved!") 89 | if strings.Compare(dbToken, token) == 0 { 90 | log.Info("VerifyCookieFromRedisHTTPCore | Token MATCH!! User is logged! | " + user + " | " + token) 91 | log.Debug("VerifyCookieFromRedisHTTPCore | Verifying if the user is active") 92 | var User datastructures.User // Allocate a Person for store the DB result of next instruction 93 | if err = basicredis.GetValueFromDB(redisClient, user, &User); err == nil { 94 | if User.Active { 95 | log.Debug("VerifyCookieFromRedisHTTPCore | User [" + user + "] is active") 96 | return nil 97 | } 98 | log.Warning("VerifyCookieFromRedisHTTPCore | User [" + user + "] is not active yet") 99 | return errors.New("user is registered and logged, but the account is not active yet. " + 100 | "Contact the admin for activate the user [" + User.Username + "]") 101 | } 102 | log.Warning("VerifyCookieFromRedisHTTPCore | Token is valid but the User is not in DB -.- ??") 103 | return err 104 | } 105 | log.Error("VerifyCookieFromRedisHTTPCore | Token MISMATCH!! User is NOT logged! | ", user, " | TK: ", token, " | DB: ", dbToken) 106 | return errors.New("NOT_AUTHORIZED") 107 | } 108 | log.Error("VerifyCookieFromRedisHTTPCore | Token not present in DB: ", err) 109 | return errors.New("USER_NOT_LOGGED") 110 | } 111 | log.Error("VerifyCookieFromRedisHTTPCore | Token not valid :/ | Token: ", token) 112 | return errors.New("COOKIE_NOT_VALID") 113 | } 114 | log.Error("VerifyCookieFromRedisHTTPCore | Username is not valid!") 115 | return errors.New("USERNAME_NOT_VALID") 116 | } 117 | 118 | // DeleteUserHTTPCore is delegated to remove the given username from the DB 119 | func DeleteUserHTTPCore(user, password, token string, redisClient *redis.Client) error { 120 | log.Info("DeleteCustomerHTTPCore | Removing -> User: ", user, " | Psw: ", password, " | Token: ", token) 121 | log.Debug("DeleteCustomerHTTPCore | Validating username and password ...") 122 | if ValidateCredentials(user, password) { 123 | log.Debug("DeleteCustomerHTTPCore | Validating token ...") 124 | if validateToken(token) { 125 | log.Debug("DeleteCustomerHTTPCore | Input validated! | Retrieving data from DB ...") 126 | var User datastructures.User // Allocate a Person for store the DB result of next instruction 127 | if err := basicredis.GetValueFromDB(redisClient, user, &User); err == nil { // User found... Let's now compare the password .. 128 | log.Debug("DeleteCustomerHTTPCore | Comparing password ...") 129 | if strings.Compare(User.Password, password) == 0 { // Comparing password of the user from DB with the one in input 130 | log.Warn("DeleteCustomerHTTPCore | Password match !! | Retrieving token from Redis ...") 131 | var dbToken string 132 | if err := basicredis.GetValueFromDB(redisClient, user, &dbToken); err == nil { 133 | log.Debug("DeleteCustomerHTTPCore | Data retrieved [", dbToken, "]! | Comparing token ...") 134 | if strings.Compare(token, dbToken) == 0 { 135 | log.Info("DeleteCustomerHTTPCore | Token match!! | Deleting customer [", user, "] from MongoDB ..") 136 | if err = basicredis.RemoveValueFromDB(redisClient, user); err != nil { 137 | log.Error("DeleteCustomerHTTPCore | Error during delete of user :( | User: ", user, " | Session: ", redisClient, " | Error: ", err) 138 | return errors.New("KO_DELETE_REDIS") 139 | } 140 | log.Info("DeleteCustomerHTTPCore | Customer [", user, "] deleted!! | Removing token") 141 | if err = basicredis.RemoveValueFromDB(redisClient, user); err == nil { 142 | log.Info("DeleteCustomerHTTPCore | Token removed from Redis | Bye bye [", User, "]") 143 | return nil 144 | } 145 | } 146 | log.Error("DeleteCustomerHTTPCore | User [", user, "] have tried to delete the account with a valid password but with an invalid token!! | ERR: ", err) 147 | log.Error("DeleteCustomerHTTPCore | TokenDB: ", token, " | Customer: ", User) 148 | return errors.New("TOKEN_MANIPULATED") 149 | } 150 | log.Error("DeleteCustomerHTTPCore | User [", user, "] not logged in!! ERR: ", err) 151 | return errors.New("NOT_LOGGED") 152 | } 153 | log.Error("DeleteCustomerHTTPCore | Passwords does not match!!") 154 | return errors.New("PSW") 155 | } 156 | log.Error("DeleteCustomerHTTPCore | User [", user, "] is not registered yet!!") 157 | return errors.New("NOT_REGISTER") 158 | } 159 | log.Error("DeleteCustomerHTTPCore | Token [", token, "] is not valid!") 160 | return errors.New("TOKEN") 161 | } 162 | log.Error("DeleteCustomerHTTPCore | Credentials [Usr: ", user, " | Psw: ", password, "] not valid!!") 163 | return errors.New("NOT_REGISTER") 164 | } 165 | 166 | // ====== HTTP UTILS METHODS ====== 167 | 168 | // ParseAuthCredentialFromHeaders is delegated to extract the username and the password from the BasicAuth header provided by the request 169 | // In case of error will return two empty string; in case of success will return (username,password) 170 | func ParseAuthCredentialFromHeaders(auth []byte) (string, string) { 171 | basicAuthPrefix := []byte("Basic ") 172 | if len(auth) <= len(basicAuthPrefix) { 173 | log.Debug("parseAuthCredentialFromHeaders | Headers does not contains no auth encoded") 174 | return "", "" 175 | } 176 | payload, err := base64.StdEncoding.DecodeString(string(auth[len(basicAuthPrefix):])) // Extract only the string after the "Basic " 177 | log.Info("parseAuthCredentialFromHeaders | Payload extracted: ", string(payload)) 178 | if err != nil { 179 | log.Error("parseAuthCredentialFromHeaders | STOP | KO | ", err) 180 | return "", "" // error cause 181 | } 182 | pair := bytes.SplitN(payload, []byte(":"), 2) // Extract the username [0] and password [1] separated by the ':' 183 | if len(pair) == 2 { // Only "username:password" admitted! 184 | log.Info("parseAuthCredentialFromHeaders | Payload split: ", string(pair[0]), " | ", string(pair[1])) 185 | return string(pair[0]), string(pair[1]) 186 | } 187 | log.Error("parseAuthCredentialFromHeaders | Impossible to split the payload :/ | Payload: ", payload, " | Basic: ", string(auth)) 188 | return "", "" // error cause 189 | } 190 | 191 | // ValidateCredentials is wrapper for the multiple method for validate the input parameters 192 | func ValidateCredentials(user string, pass string) bool { 193 | if validateUsername(user) && passwordValidation(pass) { 194 | return true 195 | } 196 | return false 197 | } 198 | 199 | // passwordValidation execute few check on the password in input 200 | func passwordValidation(password string) bool { 201 | if stringutils.IsBlank(password) { 202 | log.Warn("PasswordValidation | Password is empty :/") 203 | return false 204 | } 205 | if len(password) < 4 || len(password) > 32 { 206 | log.Warn("PasswordValidation | Password len not valid") 207 | return false 208 | } 209 | myReg := regexp.MustCompile("^[a-zA-Z0-9'\"+-.><=,;{}!@#$%^&_*()]{4,32}$") // Only letter + number 210 | if !myReg.MatchString(password) { // If the input don't match the regexp 211 | log.Warn("PasswordValidation | Password have strange character :/ [", password, "]") 212 | return false 213 | } 214 | log.Info("PasswordValidation | Password [", password, "] VALIDATED!") 215 | return true 216 | } 217 | 218 | // validateUsername execute few check on the username in input 219 | func validateUsername(username string) bool { 220 | if stringutils.IsBlank(username) { 221 | log.Warn("ValidateUsername | Username is empty :/") 222 | return false 223 | } 224 | if len(username) < 4 || len(username) > 32 { 225 | log.Warn("ValidateUsername | Username len not valid") 226 | return false 227 | } 228 | myReg := regexp.MustCompile("^[a-zA-Z0-9-_@]{4,32}$") // The string have to contain ONLY (letter OR number) 229 | if !myReg.MatchString(username) { // the input doesn't match the regexp 230 | log.Warn("ValidateUsername | Username have strange character :/ [", username, "]") 231 | return false 232 | } 233 | log.Debug("ValidateUsername | Username [", username, "] VALIDATED!") 234 | return true 235 | } 236 | 237 | // validateToken execute few check on the token in input 238 | func validateToken(token string) bool { 239 | log.Debug("ValidateToken | Validating [", token, "] ...") 240 | if stringutils.IsBlank(token) { 241 | log.Warn("ValidateToken | Token is empty :/") 242 | return false 243 | } 244 | if !(len(token) > 0 && len(token) < 100) { 245 | log.Warn("ValidateToken | Token len not in 0 ", decrypted1, "]") 74 | decrypted2 := decrypt(token2, password) 75 | logrus.Debug("VerifyTokens | First token decrypted [", token2, " -> ", decrypted2, "]") 76 | return strings.Compare(decrypted1, decrypted2) == 0 77 | } 78 | 79 | // VerifyPlainPasswords is delegated to verify if the incoming password is equals to the one stored in the DB 80 | func VerifyPlainPasswords(plainPswUser, chiperPswDb, key string) bool { 81 | log.Debug("VerifyPlainPasswords | Verifying if ["+plainPswUser+"] belong to [", chiperPswDb, "]") 82 | plainDb := decrypt(chiperPswDb, key) 83 | log.Debug("VerifyPlainPasswords | Plain DB: ", plainDb) 84 | return strings.Compare(plainPswUser, plainDb) == 0 85 | } 86 | -------------------------------------------------------------------------------- /database/redis/basicredis.go: -------------------------------------------------------------------------------- 1 | package basicredis 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | stringutils "github.com/alessiosavi/GoGPUtils/string" 7 | "github.com/alessiosavi/StreamingServer/datastructures" 8 | "strings" 9 | "time" 10 | 11 | "github.com/go-redis/redis" 12 | log "github.com/sirupsen/logrus" 13 | ) 14 | 15 | // ConnectToDb use empty string for hardcoded port 16 | func ConnectToDb(addr string, port string, db int) (*redis.Client, error) { 17 | // Empty addr and port for default connection 18 | if strings.Compare(addr, port) == 0 { 19 | addr = "localhost" 20 | port = "6379" 21 | } 22 | client := redis.NewClient(&redis.Options{ 23 | Addr: addr + ":" + port, 24 | Password: "", // no password set 25 | DB: db, 26 | }) 27 | log.Info("Connecting to -> ", client) 28 | err := client.Ping().Err() 29 | if err != nil { 30 | log.Errorf("Impossibile to connecto to DB ...| CLIENT: [%+v] | Addr: [%s] | Port: [%s] | ERR: [%s]", client, addr, port, err.Error()) 31 | return nil, err 32 | } 33 | log.Infof("Succesfully connected to -> [%+v]", client) 34 | return client, nil 35 | } 36 | 37 | // GetValueFromDB is delegated to check if a key is already inserted and return the value in the dest variable in signature 38 | func GetValueFromDB(client *redis.Client, key string, dest interface{}) error { 39 | tmp, err := client.Get(key).Result() 40 | if err == nil { 41 | if err = json.Unmarshal([]byte(tmp), dest); err != nil { 42 | log.Error("GetValueFromDB | Unable to unmarshal data from Redis: ", err) 43 | return err 44 | } 45 | log.Debugf("GetValueFromDB | SUCCESS | Key: "+key+" | Value: %+v", dest) 46 | return nil 47 | } else if err == redis.Nil { 48 | log.Warn("GetValueFromDB | Key -> " + key + " does not exist") 49 | return err 50 | } 51 | log.Errorf("GetValueFromDB | Fatal exception during retrieving of data [%s] | Redis: [%+v]", key, client) 52 | log.Error(err) 53 | return err 54 | } 55 | 56 | // RemoveValueFromDB is delegated to remove the given key from redis 57 | func RemoveValueFromDB(client *redis.Client, key string) error { 58 | err := client.Del(key).Err() 59 | if err == nil { 60 | log.Debugf("RemoveValueFromDB | SUCCESS | Key: [%s] | Removed", key) 61 | return nil 62 | } else if err == redis.Nil { 63 | log.Warnf("RemoveValueFromDB | Key -> [%s] does not exist", key) 64 | return err 65 | } 66 | log.Error("RemoveValueFromDB | Fatal exception during retrieving of data [", key, "] | Redis: ", client) 67 | log.Error(err) 68 | return err 69 | } 70 | 71 | // InsertTokenIntoDB set the two value into the Databased pointed from the client 72 | func InsertTokenIntoDB(client *redis.Client, key string, value string, expire time.Duration) error { 73 | key = key + "_token" 74 | log.Infof("InsertTokenIntoDB | Inserting -> (%s:%s)", key, value) 75 | err := client.Set(key, value, expire).Err() // Inserting the values into the DB 76 | if err != nil { 77 | log.Error(err) 78 | return err 79 | } 80 | //log.Debug("InsertTokenIntoDB | Setting ", expire, " seconds as expire time") 81 | //err1 := client.Expire(key, expire) 82 | //if err1.Err() != nil { 83 | // log.Error("Unable to set expiration time ... | Err: ", err1) 84 | // return err 85 | //} 86 | log.Infof("InsertTokenIntoDB | INSERTED SUCCESFULLY!! | (%s:%s)", key, value) 87 | return nil 88 | } 89 | 90 | // GetTokenFromDB is delegated to retrieve the token from Redis 91 | func GetTokenFromDB(client *redis.Client, key string) (string, error) { 92 | var err error 93 | var token string 94 | key = key + "_token" 95 | log.Info("GetTokenFromDB | Retrieving -> (", key, ")") 96 | if token, err = client.Get(key).Result(); err != nil { 97 | log.Error("GetTokenFromDB | Unable to retrieve the token for the key: [", key, "] | Err:", err) 98 | return "", err 99 | } 100 | log.Debugf("GetTokenFromDB | Token [%s] retrieved for the key [%s]", token, key) 101 | return token, nil 102 | 103 | } 104 | 105 | // InsertValueIntoDB is delegated to save a general structure into redis 106 | func InsertValueIntoDB(client *redis.Client, key string, value interface{}) error { 107 | var data []byte 108 | var err error 109 | if data, err = json.Marshal(value); err != nil { 110 | log.Errorf("InsertValueIntoDB | Unable to marshall user [%+v] | Err: %s", value, err.Error()) 111 | return err 112 | } 113 | return client.Set(key, data, 0).Err() 114 | } 115 | 116 | // insertUserIntoDB is delegated to save a general structure into redis 117 | func insertUserIntoDB(client *redis.Client, key string, user datastructures.User) error { 118 | var data []byte 119 | var err error 120 | if stringutils.IsBlank(user.Username) { 121 | err = errors.New("username is empty") 122 | log.Error("InsertUserIntoDB | ", err) 123 | return err 124 | } 125 | if stringutils.IsBlank(user.Password) { 126 | err = errors.New("password is empty") 127 | log.Error("InsertUserIntoDB | ", err) 128 | return err 129 | } 130 | if data, err = json.Marshal(user); err != nil { 131 | log.Errorf("InsertValueIntoDB | Unable to marshall user [%+v] | Err: %s", user, err.Error()) 132 | return err 133 | } 134 | return client.Set(key, data, 0).Err() 135 | } 136 | -------------------------------------------------------------------------------- /database/redis/basicredis_test.go: -------------------------------------------------------------------------------- 1 | package basicredis 2 | 3 | import ( 4 | "github.com/alessiosavi/StreamingServer/datastructures" 5 | "github.com/alicebob/miniredis/v2" 6 | "github.com/go-redis/redis" 7 | "os" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | var redisServer *miniredis.Miniredis 13 | var client *redis.Client 14 | 15 | //func TestConnectToDb(t *testing.T) { 16 | // var err error 17 | // var client *redis.Client 18 | // if client, err = ConnectToDb("localhost", "6379", 0); err != nil { 19 | // t.Error("Unable to connect to the localhost instance") 20 | // } 21 | // if client != nil { 22 | // client.Close() 23 | // } 24 | // if client, err = ConnectToDb("localhost", "6378", 0); err == nil { 25 | // t.Error("Expected an error!") 26 | // } 27 | // if client != nil { 28 | // client.Close() 29 | // } 30 | //} 31 | 32 | func TestGetTokenFromDB(t *testing.T) { 33 | var err error 34 | if err = client.Set("key_test1_token", "value_test1", 0).Err(); err != nil { 35 | t.Error("Unable to insert dummy value into Redis") 36 | return 37 | } 38 | 39 | type args struct { 40 | client *redis.Client 41 | key string 42 | } 43 | tests := []struct { 44 | name string 45 | args args 46 | want string 47 | wantErr bool 48 | }{ 49 | { 50 | name: "ok1", 51 | args: args{ 52 | client: client, 53 | key: "key_test1", 54 | }, 55 | want: "value_test1", 56 | wantErr: false, 57 | }, 58 | { 59 | name: "ko1", 60 | args: args{ 61 | client: client, 62 | key: "key_test2", 63 | }, 64 | want: "", 65 | wantErr: true, 66 | }, 67 | } 68 | for _, tt := range tests { 69 | t.Run(tt.name, func(t *testing.T) { 70 | got, err := GetTokenFromDB(tt.args.client, tt.args.key) 71 | if (err != nil) != tt.wantErr { 72 | t.Errorf("GetTokenFromDB() error = %v, wantErr %v", err, tt.wantErr) 73 | return 74 | } 75 | if got != tt.want { 76 | t.Errorf("GetTokenFromDB() got = %v, want %v", got, tt.want) 77 | } 78 | }) 79 | } 80 | } 81 | 82 | func TestGetValueFromDB(t *testing.T) { 83 | var err error 84 | user := datastructures.User{ 85 | Username: "username_test", 86 | Password: "password_test", 87 | Email: "email_test", 88 | Active: false, 89 | } 90 | 91 | if err = InsertValueIntoDB(client, user.Username, user); err != nil { 92 | t.Error("Unable to insert a dummy user | Err: " + err.Error()) 93 | return 94 | } 95 | user = datastructures.User{} 96 | type args struct { 97 | client *redis.Client 98 | key string 99 | dest interface{} 100 | } 101 | tests := []struct { 102 | name string 103 | args args 104 | wantErr bool 105 | }{ 106 | { 107 | name: "ok1", 108 | args: args{ 109 | client: client, 110 | key: "username_test", 111 | dest: &user, 112 | }, 113 | wantErr: false, 114 | }, 115 | { 116 | name: "test_ko", 117 | args: args{ 118 | client: client, 119 | key: "not_exists", 120 | dest: nil, 121 | }, 122 | wantErr: true, 123 | }, 124 | } 125 | for _, tt := range tests { 126 | t.Run(tt.name, func(t *testing.T) { 127 | if err := GetValueFromDB(tt.args.client, tt.args.key, tt.args.dest); (err != nil) != tt.wantErr { 128 | t.Errorf("GetValueFromDB() error = %v, wantErr %v", err, tt.wantErr) 129 | } 130 | }) 131 | } 132 | } 133 | 134 | func TestInsertTokenIntoDB(t *testing.T) { 135 | type args struct { 136 | client *redis.Client 137 | key string 138 | value string 139 | expire time.Duration 140 | } 141 | tests := []struct { 142 | name string 143 | args args 144 | wantErr bool 145 | }{ 146 | { 147 | name: "test_ok1", 148 | args: args{ 149 | client: client, 150 | key: "test", 151 | value: "test_value", 152 | expire: 0, 153 | }, 154 | wantErr: false, 155 | }, 156 | } 157 | 158 | for _, tt := range tests { 159 | t.Run(tt.name, func(t *testing.T) { 160 | if err := InsertTokenIntoDB(tt.args.client, tt.args.key, tt.args.value, tt.args.expire); (err != nil) != tt.wantErr { 161 | t.Errorf("InsertTokenIntoDB() error = %v, wantErr %v", err, tt.wantErr) 162 | } else { 163 | k := tt.args.key + "_token" 164 | if result, err := client.Get(k).Result(); err == nil { 165 | if result != tt.args.value { 166 | t.Errorf("InsertTokenIntoDB() value = %s, wantValue %s", result, tt.args.value) 167 | } 168 | } else { 169 | t.Error("Unable to retrieve the data for the key: " + k) 170 | } 171 | } 172 | }) 173 | } 174 | } 175 | 176 | func TestInsertValueIntoDB(t *testing.T) { 177 | type args struct { 178 | client *redis.Client 179 | key string 180 | value datastructures.User 181 | } 182 | var user_ok = datastructures.User{ 183 | Username: "username", 184 | Password: "password", 185 | Email: "email", 186 | Active: false, 187 | } 188 | 189 | var user_ko_username = datastructures.User{ 190 | Username: "", 191 | Password: "password", 192 | Email: "email", 193 | Active: false, 194 | } 195 | 196 | var user_ko_password = datastructures.User{ 197 | Username: "username", 198 | Password: "", 199 | Email: "email", 200 | Active: false, 201 | } 202 | 203 | tests := []struct { 204 | name string 205 | args args 206 | wantErr bool 207 | }{ 208 | // TODO: Add test cases. 209 | { 210 | name: "test_ok_1", 211 | args: args{ 212 | client: client, 213 | key: "key_test", 214 | value: user_ok, 215 | }, 216 | wantErr: false, 217 | }, 218 | { 219 | name: "test_ko_2", 220 | args: args{ 221 | client: client, 222 | key: "key_test1", 223 | value: datastructures.User{}, 224 | }, 225 | wantErr: true, 226 | }, 227 | { 228 | name: "test_ko_3", 229 | args: args{ 230 | client: client, 231 | key: "key_ko_test1", 232 | value: user_ko_username, 233 | }, 234 | wantErr: true, 235 | }, 236 | { 237 | name: "test_ko_4", 238 | args: args{ 239 | client: client, 240 | key: "key_ko_test2", 241 | value: user_ko_password, 242 | }, 243 | wantErr: true, 244 | }, 245 | } 246 | for _, tt := range tests { 247 | t.Run(tt.name, func(t *testing.T) { 248 | if err := insertUserIntoDB(tt.args.client, tt.args.key, tt.args.value); (err != nil) != tt.wantErr { 249 | t.Errorf("InsertValueIntoDB() error = %v, wantErr %v", err, tt.wantErr) 250 | } 251 | }) 252 | } 253 | } 254 | 255 | // 256 | //func TestRemoveValueFromDB(t *testing.T) { 257 | // type args struct { 258 | // client *redis.Client 259 | // key string 260 | // } 261 | // tests := []struct { 262 | // name string 263 | // args args 264 | // wantErr bool 265 | // }{ 266 | // // TODO: Add test cases. 267 | // } 268 | // for _, tt := range tests { 269 | // t.Run(tt.name, func(t *testing.T) { 270 | // if err := RemoveValueFromDB(tt.args.client, tt.args.key); (err != nil) != tt.wantErr { 271 | // t.Errorf("RemoveValueFromDB() error = %v, wantErr %v", err, tt.wantErr) 272 | // } 273 | // }) 274 | // } 275 | //} 276 | 277 | func mockRedis() *miniredis.Miniredis { 278 | if s, err := miniredis.Run(); err != nil { 279 | panic(err) 280 | } else { 281 | return s 282 | } 283 | } 284 | func TestMain(m *testing.M) { 285 | redisServer = mockRedis() 286 | var err error 287 | if client, err = ConnectToDb(redisServer.Host(), redisServer.Port(), 0); err != nil { 288 | panic(err.Error()) 289 | } 290 | exitVal := m.Run() 291 | redisServer.Close() 292 | client.Close() 293 | os.Exit(exitVal) 294 | } 295 | -------------------------------------------------------------------------------- /datastructures/datastructures.go: -------------------------------------------------------------------------------- 1 | package datastructures 2 | 3 | // User struct is delegated to save the information related to the user 4 | type User struct { 5 | Username string `json:"username"` 6 | Password string `json:"password"` 7 | Email string `json:"email"` 8 | Active bool `json:"active"` 9 | } 10 | 11 | // Response structure used for populate the json response for the RESTfull HTTP API 12 | type Response struct { 13 | Status bool `json:"Status"` // Status of response [true,false] OK, KO 14 | ErrorCode string `json:"ErrorCode"` // Code linked to the error (KO) 15 | Description string `json:"Description"` // Description linked to the error (KO) 16 | Data interface{} `json:"Data"` // Generic data to return in the response 17 | } 18 | 19 | // Configuration is the structure for handle the configuration data 20 | type Configuration struct { 21 | Host string // Hostname to bind the service 22 | Port int // Port to bind the service 23 | Version string 24 | SSL struct { 25 | Path string 26 | Cert string 27 | Key string 28 | Enabled bool 29 | } 30 | Redis struct { 31 | Host string 32 | Port string 33 | Token struct { 34 | Expire int 35 | DB int 36 | } 37 | } 38 | Log struct { 39 | Level string 40 | Path string 41 | Name string 42 | } 43 | Video struct { 44 | Path string 45 | Secret string 46 | ActivateSecret bool 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.3" 2 | services: 3 | streamingserver: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | ports: 8 | - "0.0.0.0:11001:11001" 9 | expose: 10 | - "11001" 11 | restart: always 12 | depends_on: 13 | - redisdb 14 | volumes: 15 | - go-modules:/go/pkg/mod 16 | network_mode: "host" 17 | 18 | redisdb: 19 | container_name: redis 20 | image: redis 21 | restart: always 22 | ports: 23 | - "0.0.0.0:6379:6379" 24 | expose: 25 | - "6379" 26 | volumes: 27 | - redis-db:/var/lib/redis 28 | entrypoint: redis-server --appendonly yes 29 | network_mode: "host" 30 | 31 | volumes: 32 | redis-db: 33 | go-modules: -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alessiosavi/StreamingServer 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/alessiosavi/GoGPUtils v0.0.92 7 | github.com/alicebob/miniredis/v2 v2.23.0 8 | github.com/go-redis/redis v6.15.9+incompatible 9 | github.com/onrik/logrus v0.9.0 10 | github.com/sirupsen/logrus v1.9.0 11 | github.com/valyala/fasthttp v1.41.0 12 | ) 13 | 14 | require ( 15 | github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect 16 | github.com/andybalholm/brotli v1.0.4 // indirect 17 | github.com/klauspost/compress v1.15.12 // indirect 18 | github.com/onsi/ginkgo v1.14.0 // indirect 19 | github.com/onsi/gomega v1.10.1 // indirect 20 | github.com/tidwall/gjson v1.6.0 // indirect 21 | github.com/tidwall/pretty v1.0.1 // indirect 22 | github.com/valyala/bytebufferpool v1.0.0 // indirect 23 | github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect 24 | golang.org/x/sys v0.1.0 // indirect 25 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 26 | ) 27 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alessiosavi/GoGPUtils v0.0.92 h1:EGdIQaGvNNm3T3fzgcWDUS2tUY0WRiLaPNEkGLkA9+U= 2 | github.com/alessiosavi/GoGPUtils v0.0.92/go.mod h1:OIZ7jXRNbV+rCHg/CJlgibowdb9hJV13CwtlQKbdulY= 3 | github.com/alessiosavi/ahocorasick v0.0.3/go.mod h1:GlX7JXZTgFoEccsMg5JZVpjoe+NW7Nk+3b5ldtGP5oU= 4 | github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= 5 | github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= 6 | github.com/alicebob/miniredis/v2 v2.23.0 h1:+lwAJYjvvdIVg6doFHuotFjueJ/7KY10xo/vm3X3Scw= 7 | github.com/alicebob/miniredis/v2 v2.23.0/go.mod h1:XNqvJdQJv5mSuVMc0ynneafpnL/zv52acZ6kqeS0t88= 8 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 9 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 10 | github.com/aws/aws-sdk-go-v2 v1.16.16/go.mod h1:SwiyXi/1zTUZ6KIAmLK5V5ll8SiURNUYOqTerZPaF9k= 11 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8/go.mod h1:JTnlBSot91steJeti4ryyu/tLd4Sk84O5W22L7O2EQU= 12 | github.com/aws/aws-sdk-go-v2/config v1.17.8/go.mod h1:UkCI3kb0sCdvtjiXYiU4Zx5h07BOpgBTtkPu/49r+kA= 13 | github.com/aws/aws-sdk-go-v2/credentials v1.12.21/go.mod h1:O+4XyAt4e+oBAoIwNUYkRg3CVMscaIJdmZBOcPgJ8D8= 14 | github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.10.0/go.mod h1:+CBJZMhsb1pTUcB/NTdS505bDX10xS4xnPMqDZj2Ptw= 15 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.17/go.mod h1:yIkQcCDYNsZfXpd5UX2Cy+sWA1jPgIhGTw9cOBzfVnQ= 16 | github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.34/go.mod h1:+Six+CXNHYllXam32j+YW8ixk82+am345ei89kEz8p4= 17 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23/go.mod h1:2DFxAQ9pfIRy0imBCJv+vZ2X6RKxves6fbnEuSry6b4= 18 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17/go.mod h1:pRwaTYCJemADaqCbUAxltMoHKata7hmB5PjEXeu0kfg= 19 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.24/go.mod h1:jULHjqqjDlbyTa7pfM7WICATnOv+iOhjletM3N0Xbu8= 20 | github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14/go.mod h1:AyGgqiKv9ECM6IZeNQtdT8NnMvUb3/2wokeq2Fgryto= 21 | github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.15.20/go.mod h1:p2i2jyYZzFBJeOOQ5ji2k/Yc6IvlQsG/CuHRwEi8whs= 22 | github.com/aws/aws-sdk-go-v2/service/dynamodb v1.17.1/go.mod h1:BZhn/C3z13ULTSstVi2Kymc62bgjFh/JwLO9Tm2OFYI= 23 | github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.13.20/go.mod h1:7qWU48SMzlrfOlNhHpazW3psFWlOIWrq4SmOr2/ESmk= 24 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.63.1/go.mod h1:0+6fPoY0SglgzQUs2yml7X/fup12cMlVumJufh5npRQ= 25 | github.com/aws/aws-sdk-go-v2/service/ecr v1.17.18/go.mod h1:DQtDYmexqR+z+B6HBCvY7zK/tuXKv6Zy/IwOXOK3eow= 26 | github.com/aws/aws-sdk-go-v2/service/glue v1.33.0/go.mod h1:aupHsCJmK66t1MQ542c6qBSuJYEA2IwKmwi4M3jdT1M= 27 | github.com/aws/aws-sdk-go-v2/service/iam v1.18.20/go.mod h1:pDBRPE4AibneAh4P6fZuU3eUkAgYirM88o2M2MxIXlg= 28 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9/go.mod h1:a9j48l6yL5XINLHLcOKInjdvknN+vWqPBxqeIDw7ktw= 29 | github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18/go.mod h1:NS55eQ4YixUJPTC+INxi2/jCqe1y2Uw3rnh9wEOVJxY= 30 | github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.17/go.mod h1:WJD9FbkwzM2a1bZ36ntH6+5Jc+x41Q4K2AcLeHDLAS8= 31 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.17/go.mod h1:4nYOrY41Lrbk2170/BGkcJKBhws9Pfn8MG3aGqjjeFI= 32 | github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17/go.mod h1:YqMdV+gEKCQ59NrB7rzrJdALeBIsYiVi8Inj3+KcqHI= 33 | github.com/aws/aws-sdk-go-v2/service/lambda v1.24.6/go.mod h1:oTJIIluTaJCRT6xP1AZpuU3JwRHBC0Q5O4Hg+SUxFHw= 34 | github.com/aws/aws-sdk-go-v2/service/rds v1.26.1/go.mod h1:d8jJiNpy2cyl52sw5msQQ12ajEbPAK+twYPR7J35slw= 35 | github.com/aws/aws-sdk-go-v2/service/redshift v1.26.10/go.mod h1:Sy+CUk5vCp1B9P5MhQQEigdm3AnlxCmx6wXS7KQD/mM= 36 | github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11/go.mod h1:fmgDANqTUCxciViKl9hb/zD5LFbvPINFRgWhDbR+vZo= 37 | github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.2/go.mod h1:HEBBc70BYi5eUvxBqC3xXjU/04NO96X/XNUe5qhC7Bc= 38 | github.com/aws/aws-sdk-go-v2/service/sesv2 v1.13.18/go.mod h1:y+PVz3TeQYhlvP7NbmYDXuwflXBbK1s5I2O7SVTiWyw= 39 | github.com/aws/aws-sdk-go-v2/service/sqs v1.19.10/go.mod h1:65Z/rmGw/6usiOFI0Tk4ddNUmPbjjPER1WLZwnFqxFM= 40 | github.com/aws/aws-sdk-go-v2/service/ssm v1.31.0/go.mod h1:JtkQSJFGEovwP6s+guH5Ap7iUemh3nMqHtg5liCv9ok= 41 | github.com/aws/aws-sdk-go-v2/service/sso v1.11.23/go.mod h1:/w0eg9IhFGjGyyncHIQrXtU8wvNsTJOP0R6PPj0wf80= 42 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.6/go.mod h1:csZuQY65DAdFBt1oIjO5hhBR49kQqop4+lcuCjf2arA= 43 | github.com/aws/aws-sdk-go-v2/service/sts v1.16.19/go.mod h1:h4J3oPZQbxLhzGnk+j9dfYHi5qIOVJ5kczZd658/ydM= 44 | github.com/aws/aws-sdk-go-v2/service/workspaces v1.23.0/go.mod h1:vPam8+zGthTXeaFWgl3Uqbzo/0QEoXF22jpuMZ97hSk= 45 | github.com/aws/smithy-go v1.13.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= 46 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 47 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 48 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 49 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 50 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 51 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 52 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 53 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 54 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 55 | github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= 56 | github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 57 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 58 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 59 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 60 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 61 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 62 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 63 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 64 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 65 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 66 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 67 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 68 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 69 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 70 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 71 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 72 | github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= 73 | github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= 74 | github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= 75 | github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= 76 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 77 | github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 78 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 79 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 80 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= 81 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 82 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 83 | github.com/onrik/logrus v0.9.0 h1:oT7VstCUxWBoX7fswYK61fi9bzRBSpROq5CR2b7wxQo= 84 | github.com/onrik/logrus v0.9.0/go.mod h1:qfe9NeZVAJfIxviw3cYkZo3kvBtLoPRJriAO8zl7qTk= 85 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 86 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 87 | github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= 88 | github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 89 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 90 | github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= 91 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 92 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 93 | github.com/pkg/sftp v1.13.5/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg= 94 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 95 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 96 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 97 | github.com/schollz/progressbar/v3 v3.11.0/go.mod h1:R2djRgv58sn00AGysc4fN0ip4piOGd3z88K+zVBjczs= 98 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 99 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 100 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 101 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 102 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 103 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 104 | github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc= 105 | github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= 106 | github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= 107 | github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= 108 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 109 | github.com/tidwall/pretty v1.0.1 h1:WE4RBSZ1x6McVVC8S/Md+Qse8YUv6HRObAx6ke00NY8= 110 | github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 111 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 112 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 113 | github.com/valyala/fasthttp v1.41.0 h1:zeR0Z1my1wDHTRiamBCXVglQdbUwgb9uWG3k1HQz6jY= 114 | github.com/valyala/fasthttp v1.41.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= 115 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 116 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 117 | github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= 118 | github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= 119 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 120 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 121 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 122 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 123 | golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 124 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 125 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 126 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 127 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 128 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 129 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 130 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 131 | golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 132 | golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU= 133 | golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 134 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 135 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 136 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 137 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 138 | golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 139 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 140 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 141 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 142 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 143 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 144 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 145 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 146 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 147 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 148 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 149 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 150 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 151 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 152 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 153 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 154 | golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 155 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 156 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 157 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 158 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 159 | golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 160 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 161 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 162 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 163 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 164 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 165 | golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= 166 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 167 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 168 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 169 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 170 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 171 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 172 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 173 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 174 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 175 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 176 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 177 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 178 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 179 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 180 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 181 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 182 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 183 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 184 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 185 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 186 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 187 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 188 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 189 | gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo= 190 | gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 191 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | stringutils "github.com/alessiosavi/GoGPUtils/string" 9 | "path" 10 | "strings" 11 | "time" 12 | 13 | authutils "github.com/alessiosavi/StreamingServer/auth" 14 | basiccrypt "github.com/alessiosavi/StreamingServer/crypt" 15 | 16 | commonutils "github.com/alessiosavi/StreamingServer/utils/common" 17 | httputils "github.com/alessiosavi/StreamingServer/utils/http" 18 | 19 | fileutils "github.com/alessiosavi/GoGPUtils/files" 20 | 21 | basicredis "github.com/alessiosavi/StreamingServer/database/redis" 22 | "github.com/alessiosavi/StreamingServer/datastructures" 23 | "github.com/go-redis/redis" 24 | "github.com/onrik/logrus/filename" 25 | "github.com/valyala/fasthttp" 26 | "github.com/valyala/fasthttp/expvarhandler" 27 | 28 | // Very nice log library 29 | log "github.com/sirupsen/logrus" 30 | ) 31 | 32 | func main() { 33 | // ==== SET LOGGING 34 | Formatter := new(log.TextFormatter) 35 | Formatter.TimestampFormat = "Jan _2 15:04:05.000000000" 36 | Formatter.FullTimestamp = true 37 | Formatter.ForceColors = true 38 | log.AddHook(filename.NewHook()) // Print filename + line at every log 39 | log.SetFormatter(Formatter) 40 | 41 | log.Debugln("Test") 42 | cfg := commonutils.VerifyCommandLineInput() 43 | log.SetLevel(commonutils.SetDebugLevel(cfg.Log.Level)) 44 | 45 | // ==== CONNECT TO REDIS ==== 46 | redisClient, err := basicredis.ConnectToDb(cfg.Redis.Host, cfg.Redis.Port, cfg.Redis.Token.DB) 47 | if err != nil { 48 | log.Fatal("Unable to connect to redis! | Err: " + err.Error()) 49 | return 50 | } 51 | defer redisClient.Close() 52 | handleRequests(cfg, redisClient) 53 | } 54 | 55 | // handleRequests Is delegated to map (BIND) the API methods to the HTTP URL 56 | // It uses a gzip handler that is usefully for reduce bandwidth usage while interacting with the middleware function 57 | func handleRequests(cfg datastructures.Configuration, redisClient *redis.Client) { 58 | m := func(ctx *fasthttp.RequestCtx) { 59 | if cfg.SSL.Enabled { 60 | log.Debug("handleRequests | SSL is enabled!") 61 | } 62 | httputils.SecureRequest(ctx, cfg.SSL.Enabled) 63 | ctx.Response.Header.Set("StreamingServer", "$v0.0.3") 64 | 65 | // Avoid to print stats for the expvar handler 66 | if strings.Compare(string(ctx.Path()), "/stats") != 0 { 67 | log.Info("\n|REQUEST --> ", ctx, " \n|Headers: ", ctx.Request.Header.String(), "| Body: ", string(ctx.PostBody())) 68 | } 69 | var err error 70 | switch string(ctx.Path()) { 71 | case "/auth/login": 72 | err = authLoginWrapper(ctx, redisClient, cfg) // Login functionality [Test purpose] 73 | case "/auth/register": 74 | err = authRegisterWrapper(ctx, redisClient) // Register user into the DB [Test purpose] 75 | case "/auth/delete": 76 | err = deleteCustomerHTTP(ctx, redisClient) 77 | case "/auth/verify": 78 | err = verifyCookieFromRedisHTTP(ctx, redisClient) // Verify if user is authorized to use the service 79 | case "/stream": 80 | streamVideos(ctx, cfg) 81 | case "/stats": 82 | expvarhandler.ExpvarHandler(ctx) 83 | case "/play": 84 | err = playVideo(ctx, cfg, redisClient) 85 | case "/activate": 86 | err = activateUser(ctx, cfg, redisClient) 87 | default: 88 | ctx.WriteString("The url " + string(ctx.URI().RequestURI()) + string(ctx.QueryArgs().QueryString()) + " does not exist :(\n") 89 | ctx.Response.SetStatusCode(404) 90 | } 91 | log.Println(err) 92 | ctx.WriteString(err.Error()) 93 | } 94 | // ==== GZIP HANDLER ==== 95 | // The gzipHandler will serve a compress request only if the client request it with headers (Content-Type: gzip, deflate) 96 | gzipHandler := fasthttp.CompressHandlerLevel(m, fasthttp.CompressBestCompression) // Compress data before sending (if requested by the client) 97 | ssl := "" 98 | if cfg.SSL.Enabled { 99 | ssl = "s" 100 | } 101 | log.Infof("HandleRequests | Binding services to @[http%s://"+cfg.Host+":%d]", ssl, cfg.Port) 102 | 103 | // ==== SSL HANDLER + GZIP if requested ==== 104 | if cfg.SSL.Enabled { 105 | httputils.ListAndServerSSL(cfg.Host, cfg.SSL.Path, cfg.SSL.Cert, cfg.SSL.Key, cfg.Port, gzipHandler) 106 | } 107 | // ==== Simple GZIP HANDLER ==== 108 | httputils.ListAndServerGZIP(cfg.Host, cfg.Port, gzipHandler) 109 | log.Trace("HandleRequests | STOP") 110 | } 111 | 112 | func activateUser(ctx *fasthttp.RequestCtx, cfg datastructures.Configuration, redisClient *redis.Client) error { 113 | var err error 114 | ctx.Response.Header.SetContentType("application/json; charset=utf-8") 115 | if !cfg.Video.ActivateSecret { 116 | err = errors.New("ACTIVATE_DISABLED") 117 | json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "Activation functionality is disabled", ErrorCode: err.Error(), Data: nil}) 118 | return err 119 | } 120 | 121 | user, pass := authutils.ParseAuthCredentialsFromRequestBody(ctx) 122 | if stringutils.IsBlank(user) { 123 | log.Warning("ActivateUser | User parameter is empty!") 124 | err = errors.New("USER_PARM_NOT_PROVIDED") 125 | json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "User parameter not provided", ErrorCode: err.Error(), Data: nil}) 126 | return err 127 | } 128 | if stringutils.IsBlank(pass) { 129 | log.Warning("ActivateUser | Pass parameter is empty!") 130 | err = errors.New("PASS_PARM_NOT_PROVIDED") 131 | json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "Pass parameter not provided", ErrorCode: err.Error(), Data: nil}) 132 | return err 133 | } 134 | 135 | if strings.Compare(cfg.Video.Secret, stringutils.Trim(pass)) != 0 { 136 | log.Warningf("ActivateUser | Password provided [%s] does not match the secret [%s]", pass, cfg.Video.Secret) 137 | err = errors.New("PASS_NOT_MATCH_SECRET") 138 | json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "Password does not match the secret token in configuration", ErrorCode: err.Error(), Data: nil}) 139 | return err 140 | } 141 | 142 | var User datastructures.User // Allocate a Person for store the DB result of next instruction 143 | if err = basicredis.GetValueFromDB(redisClient, user, &User); err == nil { 144 | if User.Active { 145 | log.Warningf("ActivateUser | User [%s] is already activated...", user) 146 | err = errors.New("USER_ALREADY_ACTIVE") 147 | json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "User [" + user + "] is already activated", ErrorCode: err.Error(), Data: nil}) 148 | return err 149 | } 150 | User.Active = true 151 | if err = basicredis.InsertValueIntoDB(redisClient, user, User); err == nil { 152 | log.Infof("ActivateUser | User [%s] was activated correctly!", user) 153 | json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "User [" + user + "] is activated", ErrorCode: "nil", Data: nil}) 154 | return nil 155 | } 156 | } 157 | return err 158 | } 159 | 160 | // playVideo is delegated to play the videos in input 161 | func playVideo(ctx *fasthttp.RequestCtx, cfg datastructures.Configuration, redisClient *redis.Client) error { 162 | if err := verifyCookieFromRedisHTTP(ctx, redisClient); err == nil { 163 | video := string(ctx.FormValue("video")) 164 | if stringutils.IsBlank(video) { 165 | ctx.Response.Header.SetContentType("application/json; charset=utf-8") 166 | return json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "video parameter is empty", ErrorCode: "EMPTY_VIDEO_PARM", Data: nil}) 167 | 168 | } 169 | f := path.Join(cfg.Video.Path, video) 170 | if !fileutils.FileExists(f) { 171 | ctx.Response.Header.SetContentType("application/json; charset=utf-8") 172 | return json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "video " + video + " does not exists", ErrorCode: "VIDEO_NOT_FOUND", Data: nil}) 173 | 174 | } 175 | ctx.SendFile(f) 176 | } else { 177 | ctx.Response.Header.SetContentType("application/json; charset=utf-8") 178 | return json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: err.Error(), ErrorCode: "NOT_LOGGED", Data: nil}) 179 | } 180 | return nil 181 | } 182 | 183 | // streamVideos is delegated to verify if the user is logged in and expose the video to stream 184 | func streamVideos(ctx *fasthttp.RequestCtx, cfg datastructures.Configuration) { 185 | ctx.Response.Header.SetContentType("text/html; charset=utf-8") 186 | files, err := fileutils.ListFiles(cfg.Video.Path) 187 | if err != nil { 188 | panic(err) 189 | } 190 | var s strings.Builder 191 | var ssl string 192 | if cfg.SSL.Enabled { 193 | ssl = "s" 194 | } 195 | s.WriteString("
    \n") 196 | for _, f := range files { 197 | f = strings.Replace(f, cfg.Video.Path, "", 1) 198 | url := fmt.Sprintf(`
  1. %s
  2. `, ssl, string(ctx.Request.Host()), f, f) 199 | //s.WriteString(`
  3. ` + f + "
  4. " + "\n") 200 | s.WriteString(url) 201 | } 202 | s.WriteString("
") 203 | ctx.WriteString(s.String() + "\n") 204 | } 205 | 206 | // authRegisterWrapper is the authentication wrapper for register the client into the service. 207 | // It has to parse the credentials of the customers and register the username and the password into the DB. 208 | func authRegisterWrapper(ctx *fasthttp.RequestCtx, redisClient *redis.Client) error { 209 | log.Debug("AuthRegisterWrapper | Starting register functionalities! | Parsing username and password ...") 210 | ctx.Response.Header.SetContentType("application/json; charset=utf-8") 211 | ctx.Request.Header.Set("WWW-Authenticate", `Basic realm="Restricted"`) 212 | username, password := parseAuthenticationCoreHTTP(ctx) // Retrieve the username and password encoded in the request 213 | if authutils.ValidateCredentials(username, password) { 214 | log.Debug("AuthRegisterWrapper | Input validated | User: ", username, " | Pass: ", password, " | Calling core functionalities ...") 215 | if err := authutils.RegisterUserHTTPCore(username, password, redisClient); err == nil { 216 | log.Warn("AuthRegisterWrapper | Customer insert with success! | ", username, ":", password) 217 | return json.NewEncoder(ctx).Encode(datastructures.Response{Status: true, Description: "User inserted!", ErrorCode: username + ":" + password, Data: nil}) 218 | } else { 219 | return commonutils.AuthRegisterErrorHelper(ctx, err.Error(), username, password) 220 | } 221 | } 222 | log.Info("AuthRegisterWrapper | Error parsing credential!! | ", username, ":", password) 223 | return json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "Error parsing credential", ErrorCode: "Wrong input or fatal error", Data: nil}) 224 | 225 | } 226 | 227 | // deleteCustomerHTTP wrapper for verify if the user is logged 228 | func deleteCustomerHTTP(ctx *fasthttp.RequestCtx, redisClient *redis.Client) error { 229 | ctx.Response.Header.SetContentType("application/json; charset=utf-8") 230 | log.Debug("DeleteCustomerHTTP | Retrieving username ...") 231 | user, psw := parseAuthenticationCoreHTTP(ctx) 232 | log.Debug("DeleteCustomerHTTP | Retrieving token ...") 233 | token := parseTokenFromRequest(ctx) 234 | log.Debug("DeleteCustomerHTTP | Retrieving cookie from redis ...") 235 | if err := authutils.DeleteUserHTTPCore(user, psw, token, redisClient); err == nil { 236 | return json.NewEncoder(ctx).Encode(datastructures.Response{Status: true, Description: "User " + user + " removed!", ErrorCode: "", Data: nil}) 237 | } else { 238 | return json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "User " + user + " NOT removed!", ErrorCode: err.Error(), Data: nil}) 239 | } 240 | } 241 | 242 | // authLoginWrapper is the authentication wrapper for login functionality. It allows the customers that have completed the registration phase to log in and receive the mandatory 243 | // token for interact with the services 244 | // In order to be compliant with as many protocol as possible, the method try to find the two parameter needed (user,pass) sequentially from: 245 | // BasicAuth headers; query args; GET args; POST args. It manages few error cause just for debug purpose 246 | // The login functionality can be accomplished using different methods: 247 | // BasicAuth headers: example ->from browser username:password@$URL/auth/login| curl -vL --user "username:password $URL/auth/login" 248 | // GET Request: example -> from browser $URL/auth/login?user=username&pass=password | curl -vL $URL/auth/login?user=username&pass=password 249 | // POST Request: example -> curl -vL $URL/auth/login -d 'user=username&pass=password' 250 | func authLoginWrapper(ctx *fasthttp.RequestCtx, redisClient *redis.Client, cfg datastructures.Configuration) error { 251 | log.Info("AuthLoginWrapper | Starting authentication | Parsing authentication credentials") 252 | ctx.Response.Header.SetContentType("application/json; charset=utf-8") 253 | username, password := parseAuthenticationCoreHTTP(ctx) // Retrieve the username and password encoded in the request from BasicAuth headers, GET & POST 254 | if authutils.ValidateCredentials(username, password) { // Verify if the input parameter respect the rules ... 255 | log.Debug("AuthLoginWrapper | Input validated | User: ", username, " | Pass: ", password, " | Calling core functionalities ...") 256 | if err := authutils.LoginUserHTTPCore(username, password, redisClient); err == nil { // Login phase 257 | log.Debug("AuthLoginWrapper | Login successfully! Generating token!") 258 | token := basiccrypt.GenerateToken(username, password) // Generate a simple md5 hashed token 259 | log.Info("AuthLoginWrapper | Inserting token into Redis ", token) 260 | if err = basicredis.InsertTokenIntoDB(redisClient, username, token, time.Second*time.Duration(cfg.Redis.Token.Expire)); err != nil { 261 | return err 262 | } 263 | // insert the token into the DB 264 | log.Info("AuthLoginWrapper | Token inserted! All operation finished correctly! | Setting token into response") 265 | authcookie := authutils.CreateCookie("GoLog-Token", token, cfg.Redis.Token.Expire) 266 | usernameCookie := authutils.CreateCookie("username", username, cfg.Redis.Token.Expire) 267 | if cfg.SSL.Enabled { 268 | authcookie.SetSecure(true) 269 | usernameCookie.SetSecure(true) 270 | } 271 | ctx.Response.Header.SetCookie(authcookie) // Set the token into the cookie headers 272 | ctx.Response.Header.SetCookie(usernameCookie) // Set the token into the cookie headers 273 | ctx.Response.Header.Set("GoLog-Token", token) // Set the token into a custom headers for future security improvements 274 | log.Warn("AuthLoginWrapper | Client logged in successfully!! | ", username, ":", password, " | Token: ", token) 275 | return json.NewEncoder(ctx).Encode(datastructures.Response{Status: true, Description: "User logged in!", ErrorCode: username + ":" + password, Data: token}) 276 | } else { 277 | return commonutils.AuthLoginWrapperErrorHelper(ctx, err.Error(), username, password) 278 | } 279 | } else { // error parsing credential 280 | log.Info("AuthLoginWrapper | Error parsing credential!! |", username+":"+password) 281 | ctx.Response.Header.DelCookie("GoLog-Token") 282 | ctx.Error(fasthttp.StatusMessage(fasthttp.StatusUnauthorized), fasthttp.StatusUnauthorized) 283 | ctx.Response.Header.Set("WWW-Authenticate", "Basic realm=Restricted") 284 | //err := json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "Error parsing credential", ErrorCode: "Missing or manipulated input", Data: nil}) 285 | //commonutils.Check(err, "AuthLoginWrapper") 286 | } 287 | return nil 288 | } 289 | 290 | // parseAuthenticationCoreHTTP The purpose of this method is to decode the username and the password encoded in the request. 291 | // It has to recognize if the parameters are sent in the body of the request OR in the payload of the BasicAuth Header. 292 | // In first instance he tries if the prefix of the BasicAuth is present in the headers. If found will delegate to extract the data to 293 | // another function specialized to extract the data from the BasicAuth header. 294 | // If the BasicAuth header is not provided, then the method will delegate the request to a function specialized for parse the data 295 | // from the body of the request 296 | func parseAuthenticationCoreHTTP(ctx *fasthttp.RequestCtx) (string, string) { 297 | basicAuthPrefix := []byte("Basic ") // BasicAuth template prefix 298 | auth := ctx.Request.Header.Peek("Authorization") // Get the Basic Authentication credentials from headers 299 | log.Info("ParseAuthenticationHTTP | Auth Headers: [", string(auth), "]") 300 | if bytes.HasPrefix(auth, basicAuthPrefix) { // Check if the login is executed using the BasicAuth headers 301 | log.Debug("ParseAuthenticationHTTP | Logging-in from BasicAuth headers ...") 302 | return authutils.ParseAuthCredentialFromHeaders(auth) // Call the delegated method for extract the credentials from the Header 303 | } // In other case call the delegated method for extract the credentials from the body of the Request 304 | log.Info("ParseAuthenticationCoreHTTP | Credentials not in Headers, retrieving from body ...") 305 | user, pass := authutils.ParseAuthCredentialsFromRequestBody(ctx) // Used for extract user and password from the request 306 | if stringutils.IsBlank(user) { 307 | log.Info("ParseAuthenticationCoreHTTP | Username not in body, retrieving from cookie ...") 308 | user = string(ctx.Request.Header.Cookie("username")) 309 | } 310 | return user, pass 311 | } 312 | 313 | // verifyCookieFromRedisHTTP wrapper for verify if the user is logged 314 | func verifyCookieFromRedisHTTP(ctx *fasthttp.RequestCtx, redisClient *redis.Client) error { 315 | log.Debug("VerifyCookieFromRedisHTTP | Retrieving username ...") 316 | user, _ := parseAuthenticationCoreHTTP(ctx) 317 | log.Debug("VerifyCookieFromRedisHTTP | Retrieving token ...") 318 | token := parseTokenFromRequest(ctx) 319 | log.Debug("VerifyCookieFromRedisHTTP | Retrieving cookie from redis ...") 320 | //if err = authutils.VerifyCookieFromRedisHTTPCore(user, token, redisClient); err != nil { // Verify if a user is authorized to use the service 321 | //ctx.Response.Header.SetContentType("application/json; charset=utf-8") 322 | //json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "Not logged in!", ErrorCode: err.Error(), Data: nil}) 323 | //} 324 | //return err 325 | return authutils.VerifyCookieFromRedisHTTPCore(user, token, redisClient) 326 | } 327 | 328 | // parseTokenFromRequest is delegated to retrieve the token encoded in the request. The token can be sent in two different way. 329 | // In first instance the method will try to find the token in the cookie. If the cookie is not provided in the cookie, 330 | // then the research will continue analyzing the body of the request (URL ARGS,GET,POST). 331 | // In case of token not found, an empty string will be returned 332 | func parseTokenFromRequest(ctx *fasthttp.RequestCtx) string { 333 | token := string(ctx.Request.Header.Cookie("GoLog-Token")) // GoLog-Token is the hardcoded name of the cookie 334 | log.Info("ParseTokenFromRequest | Checking if token is in the cookie ...") 335 | if strings.Compare(token, "") == 0 { // No cookie provided :/ Checking in the request 336 | log.Warn("ParseTokenFromRequest | Token is not in the cookie, retrieving from the request ...") 337 | token = string(ctx.FormValue("token")) // Extracting the token from the request (ARGS,GET,POST) 338 | if strings.Compare(token, "") == 0 { // No token provided in the request 339 | log.Warn("ParseTokenFromRequest | Can not find the token! ...") 340 | return "" // "COOKIE_NOT_PRESENT" 341 | } 342 | } 343 | log.Info("ParseTokenFromRequest | Token found:", token) 344 | return token 345 | } 346 | -------------------------------------------------------------------------------- /utils/common/commonutils.go: -------------------------------------------------------------------------------- 1 | package commonutils 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "math/rand" 7 | "os" 8 | "strings" 9 | "time" 10 | 11 | "github.com/alessiosavi/StreamingServer/datastructures" 12 | 13 | log "github.com/sirupsen/logrus" 14 | "github.com/valyala/fasthttp" 15 | ) 16 | 17 | // VerifyCommandLineInput is delegated to manage the input parameter provide with the input flag from command line 18 | func VerifyCommandLineInput() datastructures.Configuration { 19 | log.Debug("VerifyCommandLineInput | Init a new configuration from the conf file") 20 | c := flag.String("config", "./conf/test.json", "Specify the configuration file.") 21 | flag.Parse() 22 | if strings.Compare(*c, "") == 0 { 23 | log.Fatal("VerifyCommandLineInput | Call the tool using --config conf/config.json") 24 | } 25 | file, err := os.Open(*c) 26 | if err != nil { 27 | log.Fatal("VerifyCommandLineInput | can't open config file: ", err) 28 | } 29 | defer file.Close() 30 | decoder := json.NewDecoder(file) 31 | cfg := datastructures.Configuration{} 32 | err = decoder.Decode(&cfg) 33 | if err != nil { 34 | log.Fatal("VerifyCommandLineInput | can't decode config JSON: ", err) 35 | } 36 | log.Debug("VerifyCommandLineInput | Conf loaded -> ", cfg) 37 | 38 | return cfg 39 | } 40 | 41 | func AuthLoginWrapperErrorHelper(ctx *fasthttp.RequestCtx, err, username, password string) error { 42 | if strings.Compare(err, "NOT_VALID") == 0 { // Input does not match with rules 43 | log.Error("AuthLoginWrapper | Input does not respect the rules :/! | ", username, ":", password) 44 | ctx.Response.Header.DelCookie("GoLog-Token") 45 | return json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "Wrong input!", ErrorCode: username, Data: nil}) 46 | } else if strings.Compare(err, "USR") == 0 { //User does not exist in DB 47 | log.Error("AuthLoginWrapper | Client does not exists! | ", username, ":", password) 48 | ctx.Response.Header.DelCookie("GoLog-Token") 49 | return json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "User does not exists!", ErrorCode: "USER_NOT_REGISTERED", Data: nil}) 50 | 51 | } else if strings.Compare(err, "PSW") == 0 { //Password mismatch 52 | log.Error("AuthLoginWrapper | Password does not match! | ", username, ":", password) 53 | ctx.Response.Header.DelCookie("GoLog-Token") 54 | return json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "Password don't match!", ErrorCode: username, Data: nil}) 55 | } // General error cause 56 | return json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "", ErrorCode: err, Data: nil}) 57 | } 58 | 59 | func AuthRegisterErrorHelper(ctx *fasthttp.RequestCtx, check, username, password string) error { 60 | if strings.Compare(check, "NOT_VALID") == 0 { // Input don't match with rules 61 | log.Error("AuthRegisterWrapper | Input does not respect the rules :/! | ", username, ":", password) 62 | ctx.Response.Header.DelCookie("GoLog-Token") 63 | return json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "Wrong input!", ErrorCode: username, Data: nil}) 64 | 65 | } else if strings.Compare(check, "ALREADY_EXIST") == 0 { //User already present in DB 66 | log.Error("AuthRegisterWrapper | User already exists! | ", username, ":", password) 67 | return json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "User [" + username + "] already exists!", ErrorCode: "USER_ALREADY_EXIST", Data: nil}) 68 | } else { // General error cause 69 | return json.NewEncoder(ctx).Encode(datastructures.Response{Status: false, Description: "Unable to connect to DB", ErrorCode: check, Data: nil}) 70 | } 71 | } 72 | 73 | // SetDebugLevel return the LogRus object by the given string 74 | func SetDebugLevel(level string) log.Level { 75 | if strings.Compare(strings.ToLower(level), "debug") == 0 { 76 | return log.DebugLevel 77 | } else if strings.Compare(strings.ToLower(level), "trace") == 0 { 78 | return log.TraceLevel 79 | } else if strings.Compare(strings.ToLower(level), "info") == 0 { 80 | return log.InfoLevel 81 | } else if strings.Compare(strings.ToLower(level), "error") == 0 { 82 | return log.ErrorLevel 83 | } else if strings.Compare(strings.ToLower(level), "fatal") == 0 { 84 | return log.FatalLevel 85 | } else if strings.Compare(strings.ToLower(level), "panic") == 0 { 86 | return log.PanicLevel 87 | } else if strings.Contains(strings.ToLower(level), "warn") { 88 | return log.WarnLevel 89 | } 90 | return log.DebugLevel 91 | } 92 | 93 | // Random initialize a new seed using the UNIX Nano time and return an integer between the 2 input value 94 | func Random(min int, max int) int { 95 | rand.Seed(time.Now().UnixNano()) 96 | return rand.Intn(max-min) + min 97 | } 98 | -------------------------------------------------------------------------------- /utils/http/httputils.go: -------------------------------------------------------------------------------- 1 | package httputils 2 | 3 | import ( 4 | "path" 5 | "strconv" 6 | 7 | commonutils "github.com/alessiosavi/StreamingServer/utils/common" 8 | 9 | fileutils "github.com/alessiosavi/GoGPUtils/files" 10 | 11 | log "github.com/sirupsen/logrus" 12 | "github.com/valyala/fasthttp" 13 | ) 14 | 15 | func ListAndServerGZIP(host string, _port int, gzipHandler fasthttp.RequestHandler) { 16 | port := strconv.Itoa(_port) 17 | log.Infof("ListAndServerGZIP | Trying estabilishing connection @[http://%s:%s]", host, port) 18 | err := fasthttp.ListenAndServe(host+":"+port, gzipHandler) // Try to start the server with input "host:port" received in input 19 | if err != nil { // No luck, connection not successfully. Probably port used ... 20 | log.Warn("ListAndServerGZIP | Port [", port, "] seems used :/") 21 | for i := 0; i < 10; i++ { 22 | port := strconv.Itoa(commonutils.Random(8081, 8090)) // Generate a new port to use 23 | log.Info("ListAndServerGZIP | Round ", strconv.Itoa(i), "] No luck! Connecting to another random port [@", port, "] ...") 24 | err := fasthttp.ListenAndServe(host+":"+port, gzipHandler) // Trying with the random port generate few step above 25 | if err == nil { // Connection established! Not reached 26 | log.Infof("ListAndServerGZIP | Connection estabilished @[http://%s:%s]", host, port) 27 | break 28 | } 29 | } 30 | } 31 | } 32 | 33 | func ListAndServerSSL(host, _path, pub, priv string, _port int, gzipHandler fasthttp.RequestHandler) { 34 | pub = path.Join(_path, pub) 35 | priv = path.Join(_path, priv) 36 | if fileutils.FileExists(pub) && fileutils.FileExists(priv) { 37 | port := strconv.Itoa(_port) 38 | log.Infof("ListAndServerSSL | Trying estabilishing connection @[https://%s:%s]", host, port) 39 | err := fasthttp.ListenAndServeTLS(host+":"+port, pub, priv, gzipHandler) // Try to start the server with input "host:port" received in input 40 | if err != nil { // No luck, connection not successfully. Probably port used ... 41 | log.Warn("ListAndServerSSL | Port [", port, "] seems used :/") 42 | for i := 0; i < 10; i++ { 43 | port := strconv.Itoa(commonutils.Random(8081, 8090)) // Generate a new port to use 44 | log.Info("ListAndServerSSL | Round ", i, "] No luck! Connecting to another random port [@"+port+"] ...") 45 | err := fasthttp.ListenAndServeTLS(host+":"+port, pub, priv, gzipHandler) // Trying with the random port generate few step above 46 | if err == nil { // Connection established! Not reached 47 | log.Infof("ListAndServerSSL | Connection estabilished @[https://%s:%s]", host, port) 48 | break 49 | } 50 | } 51 | } 52 | } 53 | log.Error("ListAndServerSSL | Unable to find certificates: pub[" + pub + "] | priv[" + priv + "]") 54 | } 55 | 56 | // SecureRequest Enhance the security with additional sec header 57 | func SecureRequest(ctx *fasthttp.RequestCtx, ssl bool) { 58 | ctx.Response.Header.Set("Feature-Policy", "geolocation 'none'; microphone 'none'; camera 'self'") 59 | ctx.Response.Header.Set("Referrer-Policy", "no-referrer") 60 | ctx.Response.Header.Set("x-frame-options", "SAMEORIGIN") 61 | ctx.Response.Header.Set("X-Content-Type-Options", "nosniff") 62 | ctx.Response.Header.Set("X-Permitted-Cross-Domain-Policies", "none") 63 | ctx.Response.Header.Set("X-XSS-Protection", "1; mode=block") 64 | ctx.Response.Header.Set("Access-Control-Allow-Origin", "*") 65 | if ssl { 66 | ctx.Response.Header.Set("Content-Security-Policy", "upgrade-insecure-requests") 67 | ctx.Response.Header.Set("Strict-Transport-Security", "max-age=60; includeSubDomains; preload") 68 | ctx.Response.Header.Set("expect-ct", "max-age=60, enforce") 69 | } 70 | } 71 | --------------------------------------------------------------------------------