diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9ad94a5 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +HOST=:8080 +SECRET=secret +AUTHPDS_TOKEN=123 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30c3525 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ + +.env +bin/ diff --git a/Dockerfile b/Dockerfile index f78cdd8..a9f0f7f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:latest +FROM golang:alpine WORKDIR /app @@ -6,6 +6,6 @@ COPY go.mod go.sum ./ RUN go mod download && go mod verify COPY . . -RUN go build -v -o ./bin/drone-example . +RUN go build -v -o ./bin/go-maths-api . -CMD ["./bin/drone-example"] +CMD ["./bin/go-maths-api"] diff --git a/README.md b/README.md index 928b4e1..d6d3728 100644 --- a/README.md +++ b/README.md @@ -3,29 +3,55 @@ A simple Go API whose only purpose is to check whether an user is a Maths student or not. ## Prerequisites -- An environment variable `AUTHPDS_TOKEN` containing the authentication token for the external API. -- An environment variable named `SECRET` containing the secret token for authorization. -## Docker +- An environment variable `AUTHPDS_TOKEN` containing the authentication token for the external API. + +- An environment variable named `SECRET` containing the secret token for authorization. + +## Development + +### Development + +```bash shell +# Create a ".env" file from the given template and update as needed +$ cp .env.example .env + +# Start the server +$ go run -v . + +# Build to binary +$ go build -v -o ./bin/go-maths-api . +``` + +### Deploy + To build and run the image from the provided `Dockerfile`: ```bash shell +# Create a ".env" file from the given template and update as needed +$ cp .env.example .env + $ docker build -t go-maths-api . -$ docker run -d --restart always -p 127.235.203.11:1099 --env 'AUTHPDS_TOKEN' --env 'SECRET' --name go-maths-api go-maths-api +$ docker run -d --restart always -p 8080:8080 --name go-maths-api go-maths-api ``` -## Client Usage +### Client Usage An example request to the local Docker image (with `SECRET` defined in your environment): -``` -curl -X GET "http://asdf:8080/check-maths-user?user=f.minnocci" -H "Authorization: Bearer $SECRET" + +```bash shell +$ curl "localhost:8080/check-maths-user?user=f.minnocci" -H "Authorization: Bearer $SECRET" ``` Successful JSON response: -``` +```json { "result": true } ``` + +## Notes + +- TODO: Add a small 1-day cache diff --git a/go.mod b/go.mod index 9649b32..c799131 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module git.phc.dm.unipi.it/phc/go-maths-api go 1.21.1 + +require github.com/joho/godotenv v1.5.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d61b19e --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= diff --git a/main.go b/main.go index 28a8316..1307e37 100644 --- a/main.go +++ b/main.go @@ -3,78 +3,114 @@ package main import ( "encoding/json" "fmt" + "io" + "log" "net/http" "os" "strings" + + "github.com/joho/godotenv" +) + +var ( + Host string + AuthPDSToken string + Secret string ) func main() { - http.HandleFunc("/check-maths-user", CheckMathsUserHandler) - port := ":8080" + godotenv.Load() + + if v, present := os.LookupEnv("HOST"); present { + Host = v + } else { + Host = ":8080" + } + + if v, present := os.LookupEnv("AUTHPDS_TOKEN"); present { + AuthPDSToken = v + } else { + log.Fatal(`missing AUTHPDS_TOKEN`) + } - fmt.Printf("Listening on port %s...\n", port) - http.ListenAndServe(port, nil) + if v, present := os.LookupEnv("SECRET"); present { + Secret = v + } else { + log.Fatal(`missing SECRET`) + } + + r := http.NewServeMux() + r.HandleFunc("/check-maths-user", checkMathsUserHandler) + + log.Printf("Starting server on %s...\n", Host) + http.ListenAndServe(Host, r) } -func CheckMathsUserHandler(w http.ResponseWriter, r *http.Request) { +func checkMathsUserHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + // Get the username from the query parameters userName := r.URL.Query().Get("user") - // Get the AUTHORIZATION header value, which should include the secret token - authHeader := r.Header.Get("Authorization") - expectedAuthHeader := "Bearer " + os.Getenv("SECRET") - // Check if the provided authorization header matches the expected secret token - if authHeader != expectedAuthHeader { + if r.Header.Get("Authorization") != "Bearer "+Secret { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } - // Get the AUTHPDS_TOKEN from an environment variable - authPdsToken := os.Getenv("AUTHPDS_TOKEN") + userApiUrl := fmt.Sprintf("https://api.unipi.it/authPds/api/Carriera/studente/uid/%s/", userName) - // Make a GET request to the external API - apiURL := fmt.Sprintf("https://api.unipi.it/authPds/api/Carriera/studente/uid/%s/", userName) - req, err := http.NewRequest("GET", apiURL, nil) + // Create a GET request to the external API + ateneoRequest, err := http.NewRequest("GET", userApiUrl, nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - req.Header.Add("accept", "*/*") - req.Header.Add("Authorization", "Bearer "+authPdsToken) + ateneoRequest.Header.Add("Accept", "*/*") + ateneoRequest.Header.Add("Authorization", "Bearer "+AuthPDSToken) - // Execute the request - client := &http.Client{} - resp, err := client.Do(req) + // Make the request to the external API + ateneoResponse, err := http.DefaultClient.Do(ateneoRequest) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - defer resp.Body.Close() + defer ateneoResponse.Body.Close() - // Check if the response contains the desired keywords - body := make([]byte, 0) - _, err = resp.Body.Read(body) + isMatematica, err := checkUtenteDiMatematica(ateneoResponse) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - containsKeywords := false + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode( + map[string]any{ + "result": isMatematica, + }, + ); err != nil { + log.Printf(`encode error: %v`, err) + return + } +} + +func checkUtenteDiMatematica(r *http.Response) (bool, error) { + // Check if the response contains the desired keywords + body, err := io.ReadAll(r.Body) + if err != nil { + return false, err + } + keywords := []string{"MATEMATICA", "Mobilit", "Transizione"} for _, keyword := range keywords { if strings.Contains(string(body), keyword) { - containsKeywords = true - break + return true, nil } } - // Create a JSON response - response := map[string]bool{ - "result": containsKeywords, - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + return false, nil }