diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..3e121d0 --- /dev/null +++ b/.env.template @@ -0,0 +1,26 @@ +DATABASE_URL=# This is for the .env to get the Docker container URL +HOST= +POSTGRES_DB= +POSTGRES_USER= +POSTGRES_PASSWORD= +DATABASE_PORT= +SERVER_PORT= +DATABASE_URL_LOCAL=# This is for the .env to get the local URL + +# The URL of our Auth0 Tenant Domain. +# If you're using a Custom Domain, be sure to set this to that value instead. +AUTH0_DOMAIN= + +# Our Auth0 application's Client ID. +AUTH0_CLIENT_ID= + +# Our Auth0 application's Client Secret. +AUTH0_CLIENT_SECRET= + +# The Callback URL of our application. Customizable. +AUTH0_CALLBACK_URL= +AUTH0_AUDIENCE= + +# These two are different from the previous ones, from these ones we're getting the Management API JWT +AUTH0_TEST_CLIENT_ID= +AUTH0_TEST_CLIENT_SECRET= \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 013cb97..e3fa8d6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.23 AS builder +FROM golang:1.23-alpine AS builder WORKDIR /app @@ -15,4 +15,6 @@ WORKDIR /root/ COPY --from=builder /app/rpl-service . +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ + CMD ["./rpl-service"] \ No newline at end of file diff --git a/config/databaseConfig.go b/config/databaseConfig.go new file mode 100644 index 0000000..cdd17bd --- /dev/null +++ b/config/databaseConfig.go @@ -0,0 +1,61 @@ +package config + +import ( + "fmt" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "log" + "os" + "rpl-service/constants" + "rpl-service/models" +) + +func StartDatabase() *gorm.DB { + // Uncomment these lines when running in local + // // Retrieve environment variables + // err := godotenv.Load(".env") + // if err != nil { + // log.Fatalf("Error loading .env file: %v", err) + // return nil + //} + + host := os.Getenv("HOST") + user := os.Getenv("POSTGRES_USER") + password := os.Getenv("POSTGRES_PASSWORD") + dbname := os.Getenv("POSTGRES_DB") + port := os.Getenv("DATABASE_PORT") + + envVariables := []string{host, user, password, dbname, port} + + for _, envVar := range envVariables { + if envVar == constants.EmptyString { + log.Fatal("One or more database environment variables are not set") + } + } + + // Database connection string + dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable", host, user, password, dbname, port) + + // Open the database connection + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + if err != nil { + log.Fatalf("failed to connect to the database: %v", err) + } + + // Enable uuid-ossp extension + err = db.Exec("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"").Error + if err != nil { + log.Fatalf("failed to enable uuid-ossp extension: %v", err) + } + + migrateSchemas(db) + + return db +} + +func migrateSchemas(db *gorm.DB) { + err := db.AutoMigrate(&models.Course{}, &models.Exercise{}, &models.Test{}, &models.IsEnrolled{}) + if err != nil { + log.Fatalf("failed to migrate database: %v", err) + } +} diff --git a/config/routerConfig.go b/config/routerConfig.go new file mode 100644 index 0000000..714820a --- /dev/null +++ b/config/routerConfig.go @@ -0,0 +1,50 @@ +package config + +import ( + "fmt" + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "net/http" + "rpl-service/controllers/course" + "rpl-service/models" + "rpl-service/platform/middleware" +) + +func InitializeRoutes(router *gin.Engine, db *gorm.DB) { + for _, endpoint := range course.Endpoints { + mapToGinRoute(router, endpoint, db) + } +} + +func mapToGinRoute(router *gin.Engine, endpoint models.Endpoint, db *gorm.DB) { + methods := map[string]func(string, ...gin.HandlerFunc) gin.IRoutes{ + "GET": router.GET, + "POST": router.POST, + "PUT": router.PUT, + "DELETE": router.DELETE, + } + + // Validate method exists + method, exists := methods[endpoint.Method] + if !exists { + panic(fmt.Sprintf("Unsupported method: %s", endpoint.Method)) + } + + // Create the base handler + handler := func(w http.ResponseWriter, r *http.Request) { + endpoint.HandlerFunction(w, r, db) + } + + // Apply middleware if the route is protected + if endpoint.IsProtected { + method(endpoint.Path, gin.WrapH(middleware.EnsureValidToken()( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler(w, r) + })))) + } else { + // Unprotected route + method(endpoint.Path, gin.WrapF(func(w http.ResponseWriter, r *http.Request) { + handler(w, r) + })) + } +} diff --git a/constants/constants.go b/constants/constants.go index a7d0f0a..654442e 100644 --- a/constants/constants.go +++ b/constants/constants.go @@ -1,3 +1,6 @@ package constants +import "time" + const EmptyString = "" +const ProviderDuration = 5 * time.Minute diff --git a/controllers/courseController.go b/controllers/course/courseController.go similarity index 73% rename from controllers/courseController.go rename to controllers/course/courseController.go index 8e2294b..f80fca1 100644 --- a/controllers/courseController.go +++ b/controllers/course/courseController.go @@ -1,4 +1,4 @@ -package controllers +package course import ( "encoding/json" @@ -10,9 +10,10 @@ import ( "rpl-service/services/users" ) -const BaseURL = "/courses" +// Controllers should have all the functions and logic, routers should expose the endpoints. +// This is the controller for the course entity. -func CourseExists(w http.ResponseWriter, r *http.Request, db *gorm.DB) { +func Exists(w http.ResponseWriter, r *http.Request, db *gorm.DB) { courseID := r.PathValue("id") // Get the course ID from the URL if courseID == constants.EmptyString { http.Error(w, "Course ID is required", http.StatusBadRequest) @@ -24,20 +25,20 @@ func CourseExists(w http.ResponseWriter, r *http.Request, db *gorm.DB) { return } - // Should return whether a user with that ID exists + // Should return whether a user with that ID Exists if !users.CourseExists(db, courseUUID) { // TODO: change package name http.Error(w, "Course not found", http.StatusNotFound) return } w.WriteHeader(http.StatusOK) - _, err = w.Write([]byte("Course exists")) + _, err = w.Write([]byte("Course Exists")) if err != nil { return } } -func CreateCourse(w http.ResponseWriter, r *http.Request, db *gorm.DB) { - // Should create a new course +func Create(w http.ResponseWriter, r *http.Request, db *gorm.DB) { + // Should Create a new course var body models.Course err := json.NewDecoder(r.Body).Decode(&body) if err != nil { @@ -48,7 +49,7 @@ func CreateCourse(w http.ResponseWriter, r *http.Request, db *gorm.DB) { userID := uuid.New() // TODO: get the actual userID currentCourse, creatingCourseErr := users.CreateCourse(db, userID, body.Name, body.Description) if creatingCourseErr != nil { - http.Error(w, "Failed to create course", http.StatusInternalServerError) + http.Error(w, "Failed to Create course", http.StatusInternalServerError) return } @@ -67,7 +68,7 @@ func CreateCourse(w http.ResponseWriter, r *http.Request, db *gorm.DB) { } } -// TODO: Test this function after implementing auth0. +// EnrollToCourse TODO: Test this function after implementing auth0. func EnrollToCourse(w http.ResponseWriter, r *http.Request, db *gorm.DB) { var enrollmentRequest struct { UserID uuid.UUID `json:"UserID"` @@ -93,7 +94,7 @@ func EnrollToCourse(w http.ResponseWriter, r *http.Request, db *gorm.DB) { } } -// TODO: Test this function after implementing auth0. +// StudentExists TODO: Test this function after implementing auth0. func StudentExists(w http.ResponseWriter, r *http.Request, db *gorm.DB) { var enrollmentRequest struct { UserID uuid.UUID `json:"UserID"` @@ -118,7 +119,7 @@ func StudentExists(w http.ResponseWriter, r *http.Request, db *gorm.DB) { } } -// TODO: Test this function after implementing auth0. +// DeleteStudent TODO: Test this function after implementing auth0. func DeleteStudent(w http.ResponseWriter, r *http.Request, db *gorm.DB) { var deleteRequest struct { UserID uuid.UUID `json:"UserID"` @@ -144,33 +145,3 @@ func DeleteStudent(w http.ResponseWriter, r *http.Request, db *gorm.DB) { return } } - -var CourseExistsEndpoint = models.Endpoint{ - Method: models.GET, - Path: BaseURL + "/course/exists/{id}", - HandlerFunction: CourseExists, -} - -var CreateCourseEndpoint = models.Endpoint{ - Method: models.POST, - Path: BaseURL + "/course", - HandlerFunction: CreateCourse, -} - -var EnrollToCourseEndpoint = models.Endpoint{ - Method: models.POST, - Path: BaseURL + "/enroll", - HandlerFunction: EnrollToCourse, -} - -var StudentExistsEndPoint = models.Endpoint{ - Method: models.POST, - Path: BaseURL + "/check-enrollment", - HandlerFunction: StudentExists, -} - -var DeleteStudentEndpoint = models.Endpoint{ - Method: models.DELETE, - Path: BaseURL + "/delete-student", - HandlerFunction: DeleteStudent, -} diff --git a/controllers/course/courseController_test.go b/controllers/course/courseController_test.go new file mode 100644 index 0000000..ef98fbf --- /dev/null +++ b/controllers/course/courseController_test.go @@ -0,0 +1 @@ +package course_test diff --git a/controllers/course/courseRouter.go b/controllers/course/courseRouter.go new file mode 100644 index 0000000..5571964 --- /dev/null +++ b/controllers/course/courseRouter.go @@ -0,0 +1,50 @@ +package course + +import ( + "rpl-service/models" +) + +const BaseURL = "/courses" + +var ExistsEndpoint = models.Endpoint{ + Method: models.GET, + Path: BaseURL + "/course/{id}", + HandlerFunction: Exists, + IsProtected: true, +} + +var CreateCourseEndpoint = models.Endpoint{ + Method: models.POST, + Path: BaseURL + "/course", + HandlerFunction: Create, + IsProtected: true, +} + +var EnrollToCourseEndpoint = models.Endpoint{ + Method: models.POST, + Path: BaseURL + "/enroll", + HandlerFunction: EnrollToCourse, + IsProtected: true, +} + +var StudentExistsEndPoint = models.Endpoint{ + Method: models.GET, + Path: BaseURL + "/course/{id}/is-enrolled", + HandlerFunction: StudentExists, + IsProtected: true, +} + +var DeleteStudentEndpoint = models.Endpoint{ + Method: models.DELETE, + Path: BaseURL + "/course/{id}/student", + HandlerFunction: DeleteStudent, + IsProtected: true, +} + +var Endpoints = []models.Endpoint{ + ExistsEndpoint, + CreateCourseEndpoint, + EnrollToCourseEndpoint, + StudentExistsEndPoint, + DeleteStudentEndpoint, +} diff --git a/controllers/courseController_test.go b/controllers/courseController_test.go deleted file mode 100644 index 3e934e6..0000000 --- a/controllers/courseController_test.go +++ /dev/null @@ -1 +0,0 @@ -package controllers_test diff --git a/docker-compose.yml b/docker-compose.yml index 1c96d08..7f4095f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: context: . dockerfile: Dockerfile ports: - - "8080:8080" + - ${SERVER_PORT}:${SERVER_PORT} env_file: - .env depends_on: @@ -19,7 +19,7 @@ services: env_file: - .env ports: - - "5433:5432" + - ${DATABASE_PORT}:${DATABASE_PORT} volumes: - db_data:/var/lib/postgresql/data diff --git a/go.mod b/go.mod index 0554211..7e4da64 100644 --- a/go.mod +++ b/go.mod @@ -4,19 +4,51 @@ go 1.23 require ( github.com/google/uuid v1.6.0 - github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.10.9 gorm.io/driver/postgres v1.5.11 gorm.io/gorm v1.25.12 ) require ( + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/gorilla/context v1.1.2 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/gorilla/sessions v1.2.2 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.7.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - golang.org/x/crypto v0.31.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( @@ -30,3 +62,12 @@ require ( //golang.org/x/sync v0.1.0 // indirect golang.org/x/text v0.21.0 // indirect ) + +require ( + github.com/auth0/go-jwt-middleware/v2 v2.2.2 + github.com/coreos/go-oidc/v3 v3.8.0 + github.com/gin-contrib/sessions v1.0.2 + github.com/gin-gonic/gin v1.10.0 + github.com/joho/godotenv v1.5.1 + golang.org/x/oauth2 v0.15.0 +) diff --git a/go.sum b/go.sum index ef9d99c..6809b0e 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,58 @@ +github.com/auth0/go-jwt-middleware/v2 v2.2.2 h1:vrvkFZf72r3Qbt45KLjBG3/6Xq2r3NTixWKu2e8de9I= +github.com/auth0/go-jwt-middleware/v2 v2.2.2/go.mod h1:4vwxpVtu/Kl4c4HskT+gFLjq0dra8F1joxzamrje6J0= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/coreos/go-oidc/v3 v3.8.0 h1:s3e30r6VEl3/M7DTSCEuImmrfu1/1WBgA0cXkdzkrAY= +github.com/coreos/go-oidc/v3 v3.8.0/go.mod h1:yQzSCqBnK3e6Fs5l+f5i0F8Kwf0zpH9bPEsbY00KanM= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sessions v1.0.2 h1:UaIjUvTH1cMeOdj3in6dl+Xb6It8RiKRF9Z1anbUyCA= +github.com/gin-contrib/sessions v1.0.2/go.mod h1:KxKxWqWP5LJVDCInulOl4WbLzK2KSPlLesfZ66wRvMs= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= +github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= +github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= +github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -17,22 +67,106 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= +gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -40,3 +174,5 @@ gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/main/main.go b/main/main.go index f78314e..ff68d7e 100644 --- a/main/main.go +++ b/main/main.go @@ -3,14 +3,12 @@ package main import ( "database/sql" "fmt" - "gorm.io/driver/postgres" - "gorm.io/gorm" "log" - "net/http" "os" + "rpl-service/config" "rpl-service/constants" - "rpl-service/controllers" - "rpl-service/models" + "rpl-service/platform/authenticator" + "rpl-service/platform/router" ) // Should run the main web application @@ -21,7 +19,7 @@ func main() { } func startServer() { - db := startDatabase() + db := config.StartDatabase() if db == nil { fmt.Println("Error starting the database") return @@ -40,85 +38,25 @@ func startServer() { } }(s) - // Here should go the functions for each endpoint - - http.HandleFunc(controllers.CourseExistsEndpoint.Path, func(writer http.ResponseWriter, request *http.Request) { - controllers.CourseExistsEndpoint.HandlerFunction(writer, request, db) - }) - - http.HandleFunc(controllers.CreateCourseEndpoint.Path, func(writer http.ResponseWriter, request *http.Request) { - controllers.CreateCourseEndpoint.HandlerFunction(writer, request, db) - }) - - http.HandleFunc(controllers.EnrollToCourseEndpoint.Path, func(writer http.ResponseWriter, request *http.Request) { - controllers.CreateCourseEndpoint.HandlerFunction(writer, request, db) - }) - - http.HandleFunc(controllers.StudentExistsEndPoint.Path, func(writer http.ResponseWriter, request *http.Request) { - controllers.StudentExistsEndPoint.HandlerFunction(writer, request, db) - }) + // Initialize authenticator + auth, err := authenticator.New() + if err != nil { + log.Panicf("Failed to start authenticator: %v", err) + return + } - http.HandleFunc(controllers.DeleteStudentEndpoint.Path, func(writer http.ResponseWriter, request *http.Request) { - controllers.DeleteStudentEndpoint.HandlerFunction(writer, request, db) - }) + // Initialize the ginRouter + ginRouter := router.New(auth, db) serverPort := os.Getenv("SERVER_PORT") if serverPort == constants.EmptyString { - log.Panic("serverPort environment variable is not set") + log.Panic("SERVER_PORT environment variable is not set") } - serverError = http.ListenAndServe(":"+serverPort, nil) - if serverError != nil { + routerError := ginRouter.Run(":" + serverPort) + if routerError != nil { + fmt.Println("Failed to start server") return } } - -func startDatabase() *gorm.DB { - // Retrieve environment variables - // err := godotenv.Load(".env") - // if err != nil { - // log.Fatalf("Error loading .env file: %v", err) - // return nil - //} - - host := os.Getenv("HOST") - user := os.Getenv("POSTGRES_USER") - password := os.Getenv("POSTGRES_PASSWORD") - dbname := os.Getenv("POSTGRES_DB") - port := os.Getenv("DATABASE_PORT") - - envVariables := []string{host, user, password, dbname, port} - - for _, envVar := range envVariables { - if envVar == constants.EmptyString { - log.Fatal("One or more database environment variables are not set") - } - } - - // Database connection string - dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable", host, user, password, dbname, port) - - // Open the database connection - db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) - if err != nil { - log.Fatalf("failed to connect to the database: %v", err) - } - - // Enable uuid-ossp extension - err = db.Exec("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"").Error - if err != nil { - log.Fatalf("failed to enable uuid-ossp extension: %v", err) - } - - migrateSchemas(db) - - return db -} - -func migrateSchemas(db *gorm.DB) { - err := db.AutoMigrate(&models.Course{}, &models.Exercise{}, &models.Test{}, &models.IsEnrolled{}) - if err != nil { - log.Fatalf("failed to migrate database: %v", err) - } -} diff --git a/models/controllerModel.go b/models/controllerModel.go index 4b1ad40..6b3fff5 100644 --- a/models/controllerModel.go +++ b/models/controllerModel.go @@ -16,4 +16,5 @@ type Endpoint struct { Method string Path string HandlerFunction func(w http.ResponseWriter, r *http.Request, db *gorm.DB) + IsProtected bool } diff --git a/platform/authenticator/auth.go b/platform/authenticator/auth.go new file mode 100644 index 0000000..0d51d00 --- /dev/null +++ b/platform/authenticator/auth.go @@ -0,0 +1,54 @@ +package authenticator + +import ( + "context" + "errors" + "os" + + "github.com/coreos/go-oidc/v3/oidc" + "golang.org/x/oauth2" +) + +// Authenticator is used to authenticate our users. +type Authenticator struct { + *oidc.Provider + oauth2.Config +} + +// New instantiates the *Authenticator. +func New() (*Authenticator, error) { + provider, err := oidc.NewProvider( + context.Background(), + "https://"+os.Getenv("AUTH0_DOMAIN")+"/", + ) + if err != nil { + return nil, err + } + + conf := oauth2.Config{ + ClientID: os.Getenv("AUTH0_TEST_CLIENT_ID"), + ClientSecret: os.Getenv("AUTH0_TEST_CLIENT_SECRET"), + RedirectURL: os.Getenv("AUTH0_CALLBACK_URL"), + Endpoint: provider.Endpoint(), + Scopes: []string{oidc.ScopeOpenID, "profile"}, + } + + return &Authenticator{ + Provider: provider, + Config: conf, + }, nil +} + +// VerifyIDToken verifies that an *oauth2.Token is a valid *oidc.IDToken. +func (a *Authenticator) VerifyIDToken(ctx context.Context, token *oauth2.Token) (*oidc.IDToken, error) { + rawIDToken, ok := token.Extra("id_token").(string) + if !ok { + return nil, errors.New("no id_token field in oauth2 token") + } + + oidcConfig := &oidc.Config{ + ClientID: a.ClientID, + } + + return a.Verifier(oidcConfig).Verify(ctx, rawIDToken) +} diff --git a/platform/middleware/jwt.go b/platform/middleware/jwt.go new file mode 100644 index 0000000..dffd1c9 --- /dev/null +++ b/platform/middleware/jwt.go @@ -0,0 +1,72 @@ +package middleware + +import ( + "context" + "log" + "net/http" + "net/url" + "os" + "rpl-service/constants" + "time" + + jwtmiddleware "github.com/auth0/go-jwt-middleware/v2" + "github.com/auth0/go-jwt-middleware/v2/jwks" + "github.com/auth0/go-jwt-middleware/v2/validator" +) + +// CustomClaims contains custom data we want from the token. +type CustomClaims struct { + Scope string `json:"scope"` +} + +// Validate does nothing for this example, but we need +// it to satisfy validator.CustomClaims interface. +func (c CustomClaims) Validate(_ context.Context) error { + return nil +} + +// EnsureValidToken is a middleware that will check the validity of our JWT. +func EnsureValidToken() func(next http.Handler) http.Handler { + issuerURL, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/") + if err != nil { + log.Fatalf("Failed to parse the issuer url: %v", err) + } + + provider := jwks.NewCachingProvider(issuerURL, constants.ProviderDuration) + + jwtValidator, err := validator.New( + provider.KeyFunc, + validator.RS256, + issuerURL.String(), + []string{os.Getenv("AUTH0_AUDIENCE")}, + validator.WithCustomClaims( + func() validator.CustomClaims { + return &CustomClaims{} + }, + ), + validator.WithAllowedClockSkew(time.Minute), + ) + if err != nil { + log.Fatalf("Failed to set up the jwt validator") + } + + errorHandler := func(w http.ResponseWriter, _ *http.Request, err error) { + log.Printf("Encountered error while validating JWT: %v", err) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusUnauthorized) + _, writingError := w.Write([]byte(`{"message":"Failed to validate JWT."}`)) + if writingError != nil { + return + } + } + + middleware := jwtmiddleware.New( + jwtValidator.ValidateToken, + jwtmiddleware.WithErrorHandler(errorHandler), + ) + + return func(next http.Handler) http.Handler { + return middleware.CheckJWT(next) + } +} diff --git a/platform/router/router.go b/platform/router/router.go new file mode 100644 index 0000000..7f1313b --- /dev/null +++ b/platform/router/router.go @@ -0,0 +1,27 @@ +package router + +import ( + "encoding/gob" + "github.com/gin-contrib/sessions" + "github.com/gin-contrib/sessions/cookie" + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "rpl-service/config" + "rpl-service/platform/authenticator" +) + +// New registers the routes and returns the router. +func New(_ *authenticator.Authenticator, db *gorm.DB) *gin.Engine { + router := gin.Default() + + // To store custom types in our cookies, + // we must first register them using gob.Register + gob.Register(map[string]interface{}{}) + + store := cookie.NewStore([]byte("secret")) + router.Use(sessions.Sessions("auth-session", store)) + + config.InitializeRoutes(router, db) + + return router +}