From 4424dad826d84b7d34ac7766222dab2db5c00c30 Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 6 Jun 2024 19:11:37 +0100 Subject: [PATCH 1/6] Add scripts to help with docker --- Dockerfile => .docker/Dockerfile | 5 +--- .docker/docker-build.ps1 | 18 +++++++++++++ .docker/docker-build.sh | 19 +++++++++++++ .docker/docker-create-network.ps1 | 10 +++++++ .docker/docker-create-network.sh | 11 ++++++++ .docker/docker-run.ps1 | 42 +++++++++++++++++++++++++++++ .docker/docker-run.sh | 44 +++++++++++++++++++++++++++++++ .gitignore | 2 ++ README.md | 6 +++-- 9 files changed, 151 insertions(+), 6 deletions(-) rename Dockerfile => .docker/Dockerfile (93%) create mode 100644 .docker/docker-build.ps1 create mode 100644 .docker/docker-build.sh create mode 100644 .docker/docker-create-network.ps1 create mode 100644 .docker/docker-create-network.sh create mode 100644 .docker/docker-run.ps1 create mode 100644 .docker/docker-run.sh diff --git a/Dockerfile b/.docker/Dockerfile similarity index 93% rename from Dockerfile rename to .docker/Dockerfile index c8f5e4d..5a3119e 100644 --- a/Dockerfile +++ b/.docker/Dockerfile @@ -22,10 +22,7 @@ ENV PATH $PATH:/usr/local/gcloud/google-cloud-sdk/bin WORKDIR /go/src/app # Copy the current directory contents into the container at /go/src/app -COPY . . - -# Remove Dockerfile to prevent issue with App Engine -RUN rm Dockerfile +COPY ../ . # Expose port 8080 to the outside world EXPOSE 8080 diff --git a/.docker/docker-build.ps1 b/.docker/docker-build.ps1 new file mode 100644 index 0000000..723b852 --- /dev/null +++ b/.docker/docker-build.ps1 @@ -0,0 +1,18 @@ +# Description: Build docker image from Dockerfile + +Write-Host "Building Docker image..." + +# Confirm that the script is being executed from the correct directory +if (-not (Test-Path ".docker/docker-run.ps1")) { + Write-Host "You must run this command from the parent directory of this script." + exit +} + +Write-Host "Confirmed that script is being executed from the correct directory." + +$dockerCommand = "docker build --tag 'diplicity' ./.docker" + +Write-Host "Running Docker command: $dockerCommand" + +# Execute the Docker command +Invoke-Expression $dockerCommand \ No newline at end of file diff --git a/.docker/docker-build.sh b/.docker/docker-build.sh new file mode 100644 index 0000000..ce2a4f1 --- /dev/null +++ b/.docker/docker-build.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Description: Build docker image from Dockerfile +echo "Building Docker image..." + +# Confirm that the script is being executed from the correct directory +if [ ! -f ".docker/docker-run.sh" ]; then + echo "You must run this command from the parent directory of this script." + exit 1 +fi + +echo "Confirmed that script is being executed from the correct directory." + +dockerCommand="docker build --tag 'diplicity' ./.docker" + +echo "Running Docker command: $dockerCommand" + +# Execute the Docker command +eval $dockerCommand \ No newline at end of file diff --git a/.docker/docker-create-network.ps1 b/.docker/docker-create-network.ps1 new file mode 100644 index 0000000..e5d477c --- /dev/null +++ b/.docker/docker-create-network.ps1 @@ -0,0 +1,10 @@ +# Description: Create a new docker network called my-net + +Write-Host "Creating Docker network..." + +$dockerCommand = "docker network create -d bridge my-net" + +Write-Host "Running Docker command: $dockerCommand" + +# Execute the Docker command +Invoke-Expression $dockerCommand \ No newline at end of file diff --git a/.docker/docker-create-network.sh b/.docker/docker-create-network.sh new file mode 100644 index 0000000..26975c6 --- /dev/null +++ b/.docker/docker-create-network.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Description: Create a new docker network called my-net +echo "Creating Docker network..." + +dockerCommand="docker network create -d bridge my-net" + +echo "Running Docker command: $dockerCommand" + +# Execute the Docker command +eval $dockerCommand \ No newline at end of file diff --git a/.docker/docker-run.ps1 b/.docker/docker-run.ps1 new file mode 100644 index 0000000..2e6e0da --- /dev/null +++ b/.docker/docker-run.ps1 @@ -0,0 +1,42 @@ +# Ensure that this command is being called from the parent directory +# of the Dockerfile. This is necessary for the volume mapping to work +# correctly. + +Write-Host "Running Docker container..." + +# Confirm that the script is being executed from the correct directory +if (-not (Test-Path ".docker/docker-run.ps1")) { + Write-Host "You must run this command from the parent directory of this script." + exit +} + +Write-Host "Confirmed that script is being executed from the correct directory." + +# Base docker run command +$dockerCommand = "docker run" + +# Mount a volume from the host machine to the container. This means +# that the container will have access to the files in the host machine +# and that changes made in the host machine will be reflected in the +# container. +$dockerCommand += " -v .:/go/src/app:ro" # Add volume mapping + +# Specify the network that the container should be connected to. This +# is necessary for the Discord bot to be able to work correctly. +$dockerCommand += " --network my-net" # Specify the network + +# Specify that the 8080 port should be exposed to the host machine. This +# is the port that the API application listens on. +$dockerCommand += " -p 8080:8080" # Map port 8080 + +# Specify that the 8000 port should be exposed to the host machine. This +# is the port that the admin application listens on. +$dockerCommand += " -p 8000:8000" # Map port 8000 + +# Specify the image that should be used to create the container. This +$dockerCommand += " diplicity" + +Write-Host "Running Docker command: $dockerCommand" + +# Execute the Docker command +Invoke-Expression $dockerCommand \ No newline at end of file diff --git a/.docker/docker-run.sh b/.docker/docker-run.sh new file mode 100644 index 0000000..187c3c4 --- /dev/null +++ b/.docker/docker-run.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# Ensure that this command is being called from the parent directory +# of the Dockerfile. This is necessary for the volume mapping to work +# correctly. + +echo "Running Docker container..." + +# Confirm that the script is being executed from the correct directory +if [ ! -f ".docker/docker-run.sh" ]; then + echo "You must run this command from the parent directory of this script." + exit 1 +fi + +echo "Confirmed that script is being executed from the correct directory." + +# Base docker run command +dockerCommand="docker run" + +# Mount a volume from the host machine to the container. This means +# that the container will have access to the files in the host machine +# and that changes made in the host machine will be reflected in the +# container. +dockerCommand+=" -v .:/go/src/app:ro" # Add volume mapping + +# Specify the network that the container should be connected to. This +# is necessary for the Discord bot to be able to work correctly. +dockerCommand+=" --network my-net" # Specify the network + +# Specify that the 8080 port should be exposed to the host machine. This +# is the port that the API application listens on. +dockerCommand+=" -p 8080:8080" # Map port 8080 + +# Specify that the 8000 port should be exposed to the host machine. This +# is the port that the admin application listens on. +dockerCommand+=" -p 8000:8000" # Map port 8000 + +# Specify the image that should be used to create the container. +dockerCommand+=" diplicity" + +echo "Running Docker command: $dockerCommand" + +# Execute the Docker command +eval $dockerCommand \ No newline at end of file diff --git a/.gitignore b/.gitignore index 80ae255..9e47138 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ game/game.debug # Diff tool files *.orig + +.env \ No newline at end of file diff --git a/README.md b/README.md index b93274d..b721772 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,10 @@ To enable debugging the JSON output in a browser, adding the query parameter `ac - Download Docker - Navigate to the root directory of this project -- Run `docker build --tag 'diplicity' .` -- Run `docker run -p 8080:8080 -p 8000:8000 diplicity` +- Use `ps1` files for Windows and `sh` files for UNIX +- Run `.\.docker\docker-build.ps1` **or** `.\.docker\docker-build.sh` (only required once) +- Run `.\.docker\docker-network.ps1` **or** `.\.docker\docker-network.sh` (only required once) +- Run `.\.docker\docker-run.ps1` **or** `.\.docker\docker-run.sh` - The API is now available on your machine at `localhost:8080` - The Admin server is now available on your machine at `localhost:8000` From 49c02aeb747b1a2397e1676ca98ef1e183b9e28a Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 6 Jun 2024 19:48:32 +0100 Subject: [PATCH 2/6] Create TokenForDiscordUser endpoint --- auth/auth.go | 93 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 84 insertions(+), 9 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 0b52ad4..79d32b2 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -33,15 +33,16 @@ var ( ) const ( - LoginRoute = "Login" - LogoutRoute = "Logout" - RedirectRoute = "Redirect" - OAuth2CallbackRoute = "OAuth2Callback" - UnsubscribeRoute = "Unsubscribe" - ApproveRedirectRoute = "ApproveRedirect" - ListRedirectURLsRoute = "ListRedirectURLs" - ReplaceFCMRoute = "ReplaceFCM" - TestUpdateUserRoute = "TestUpdateUser" + LoginRoute = "Login" + LogoutRoute = "Logout" + TokenForDiscordUserRoute = "TokenForDiscordUser" + RedirectRoute = "Redirect" + OAuth2CallbackRoute = "OAuth2Callback" + UnsubscribeRoute = "Unsubscribe" + ApproveRedirectRoute = "ApproveRedirect" + ListRedirectURLsRoute = "ListRedirectURLs" + ReplaceFCMRoute = "ReplaceFCM" + TestUpdateUserRoute = "TestUpdateUser" ) const ( @@ -417,6 +418,79 @@ func getOAuth2Config(ctx context.Context, r *http.Request) (*oauth2.Config, erro }, nil } +type TokenForDiscordUserResponse struct { + token string +} + +func handleGetTokenForDiscordUser(w ResponseWriter, r Request) (*TokenForDiscordUserResponse, error) { + ctx := appengine.NewContext(r.Req()) + + user, ok := r.Values()["user"].(*User) + if !ok { + return nil, HTTPErr{ + Body: "Unauthenticated", + Status: http.StatusUnauthorized, + } + } + + if !appengine.IsDevAppServer() { + + superusers, err := GetSuperusers(ctx) + if err != nil { + return nil, HTTPErr{ + Body: "Unable to load superusers", + Status: http.StatusInternalServerError, + } + } + + if !superusers.Includes(user.Id) { + return nil, HTTPErr{ + Body: "Unauthorized", + Status: http.StatusForbidden, + } + } + } + + discordUserId := r.Vars()["user_id"] + if discordUserId == "" { + return nil, HTTPErr{ + Body: "Must provide discord user id", + Status: http.StatusBadRequest, + } + } + + discordUser := createUserFromDiscordUserId(discordUserId) + + if _, err := datastore.Put(ctx, UserID(ctx, discordUser.Id), discordUser); err != nil { + return nil, HTTPErr{ + Body: "Unable to store user", + Status: http.StatusInternalServerError, + } + } + + token, err := encodeUserToToken(ctx, discordUser) + if err != nil { + return nil, HTTPErr{ + Body: "Unable to encode user to token", + Status: http.StatusInternalServerError, + } + } + + return &TokenForDiscordUserResponse{token: token}, nil +} + +func createUserFromDiscordUserId(discordUserId string) *User { + return &User{ + Email: "discord-user@discord-user.fake", + FamilyName: "Discord User", + GivenName: "Discord User", + Id: discordUserId, + Name: "Discord User", + VerifiedEmail: true, + ValidUntil: time.Now().Add(time.Hour * 24 * 365 * 10), + } +} + func handleLogin(w ResponseWriter, r Request) error { ctx := appengine.NewContext(r.Req()) @@ -1096,6 +1170,7 @@ func SetupRouter(r *mux.Router) { Handle(router, "/Auth/ApproveRedirect", []string{"POST"}, ApproveRedirectRoute, handleApproveRedirect) Handle(router, "/User/{user_id}/Unsubscribe", []string{"GET"}, UnsubscribeRoute, unsubscribe) Handle(router, "/User/{user_id}/FCMToken/{replace_token}/Replace", []string{"PUT"}, ReplaceFCMRoute, replaceFCM) + Handle(router, "/User/{user_id}/TokenForDiscordUser", []string{"GET"}, TokenForDiscordUserRoute, handleGetTokenForDiscordUser) AddFilter(decorateAPILevel) AddFilter(tokenFilter) AddFilter(logHeaders) From 6ff6b0646370339207967cfda108aad065f027e3 Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 7 Jun 2024 13:11:04 +0100 Subject: [PATCH 3/6] docker-compose --- .docker/Dockerfile | 5 +- .docker/docker-build.ps1 | 18 ------ .docker/docker-build.sh | 19 ------ .docker/docker-create-network.ps1 | 10 --- .docker/docker-create-network.sh | 11 ---- .docker/docker-run.ps1 | 42 ------------ .docker/docker-run.sh | 44 ------------- auth/auth.go | 103 ++++++++++++++++++++++++++---- docker-compose.yml | 29 +++++++++ 9 files changed, 122 insertions(+), 159 deletions(-) delete mode 100644 .docker/docker-build.ps1 delete mode 100644 .docker/docker-build.sh delete mode 100644 .docker/docker-create-network.ps1 delete mode 100644 .docker/docker-create-network.sh delete mode 100644 .docker/docker-run.ps1 delete mode 100644 .docker/docker-run.sh create mode 100644 docker-compose.yml diff --git a/.docker/Dockerfile b/.docker/Dockerfile index 5a3119e..2d20f95 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -27,9 +27,12 @@ COPY ../ . # Expose port 8080 to the outside world EXPOSE 8080 +# Expose port 80 for http traffic +EXPOSE 80 + # Expose port 8000 to the outside world EXPOSE 8000 # Start the process -CMD ["python3", "../../../usr/local/gcloud/google-cloud-sdk/bin/dev_appserver.py", "--host=0.0.0.0", "--admin_host=0.0.0.0", "."] +CMD ["python3", "../../../usr/local/gcloud/google-cloud-sdk/bin/dev_appserver.py", "--host=0.0.0.0", "--admin_host=0.0.0.0", "--enable_host_checking=False", "."] diff --git a/.docker/docker-build.ps1 b/.docker/docker-build.ps1 deleted file mode 100644 index 723b852..0000000 --- a/.docker/docker-build.ps1 +++ /dev/null @@ -1,18 +0,0 @@ -# Description: Build docker image from Dockerfile - -Write-Host "Building Docker image..." - -# Confirm that the script is being executed from the correct directory -if (-not (Test-Path ".docker/docker-run.ps1")) { - Write-Host "You must run this command from the parent directory of this script." - exit -} - -Write-Host "Confirmed that script is being executed from the correct directory." - -$dockerCommand = "docker build --tag 'diplicity' ./.docker" - -Write-Host "Running Docker command: $dockerCommand" - -# Execute the Docker command -Invoke-Expression $dockerCommand \ No newline at end of file diff --git a/.docker/docker-build.sh b/.docker/docker-build.sh deleted file mode 100644 index ce2a4f1..0000000 --- a/.docker/docker-build.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -# Description: Build docker image from Dockerfile -echo "Building Docker image..." - -# Confirm that the script is being executed from the correct directory -if [ ! -f ".docker/docker-run.sh" ]; then - echo "You must run this command from the parent directory of this script." - exit 1 -fi - -echo "Confirmed that script is being executed from the correct directory." - -dockerCommand="docker build --tag 'diplicity' ./.docker" - -echo "Running Docker command: $dockerCommand" - -# Execute the Docker command -eval $dockerCommand \ No newline at end of file diff --git a/.docker/docker-create-network.ps1 b/.docker/docker-create-network.ps1 deleted file mode 100644 index e5d477c..0000000 --- a/.docker/docker-create-network.ps1 +++ /dev/null @@ -1,10 +0,0 @@ -# Description: Create a new docker network called my-net - -Write-Host "Creating Docker network..." - -$dockerCommand = "docker network create -d bridge my-net" - -Write-Host "Running Docker command: $dockerCommand" - -# Execute the Docker command -Invoke-Expression $dockerCommand \ No newline at end of file diff --git a/.docker/docker-create-network.sh b/.docker/docker-create-network.sh deleted file mode 100644 index 26975c6..0000000 --- a/.docker/docker-create-network.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -# Description: Create a new docker network called my-net -echo "Creating Docker network..." - -dockerCommand="docker network create -d bridge my-net" - -echo "Running Docker command: $dockerCommand" - -# Execute the Docker command -eval $dockerCommand \ No newline at end of file diff --git a/.docker/docker-run.ps1 b/.docker/docker-run.ps1 deleted file mode 100644 index 2e6e0da..0000000 --- a/.docker/docker-run.ps1 +++ /dev/null @@ -1,42 +0,0 @@ -# Ensure that this command is being called from the parent directory -# of the Dockerfile. This is necessary for the volume mapping to work -# correctly. - -Write-Host "Running Docker container..." - -# Confirm that the script is being executed from the correct directory -if (-not (Test-Path ".docker/docker-run.ps1")) { - Write-Host "You must run this command from the parent directory of this script." - exit -} - -Write-Host "Confirmed that script is being executed from the correct directory." - -# Base docker run command -$dockerCommand = "docker run" - -# Mount a volume from the host machine to the container. This means -# that the container will have access to the files in the host machine -# and that changes made in the host machine will be reflected in the -# container. -$dockerCommand += " -v .:/go/src/app:ro" # Add volume mapping - -# Specify the network that the container should be connected to. This -# is necessary for the Discord bot to be able to work correctly. -$dockerCommand += " --network my-net" # Specify the network - -# Specify that the 8080 port should be exposed to the host machine. This -# is the port that the API application listens on. -$dockerCommand += " -p 8080:8080" # Map port 8080 - -# Specify that the 8000 port should be exposed to the host machine. This -# is the port that the admin application listens on. -$dockerCommand += " -p 8000:8000" # Map port 8000 - -# Specify the image that should be used to create the container. This -$dockerCommand += " diplicity" - -Write-Host "Running Docker command: $dockerCommand" - -# Execute the Docker command -Invoke-Expression $dockerCommand \ No newline at end of file diff --git a/.docker/docker-run.sh b/.docker/docker-run.sh deleted file mode 100644 index 187c3c4..0000000 --- a/.docker/docker-run.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash - -# Ensure that this command is being called from the parent directory -# of the Dockerfile. This is necessary for the volume mapping to work -# correctly. - -echo "Running Docker container..." - -# Confirm that the script is being executed from the correct directory -if [ ! -f ".docker/docker-run.sh" ]; then - echo "You must run this command from the parent directory of this script." - exit 1 -fi - -echo "Confirmed that script is being executed from the correct directory." - -# Base docker run command -dockerCommand="docker run" - -# Mount a volume from the host machine to the container. This means -# that the container will have access to the files in the host machine -# and that changes made in the host machine will be reflected in the -# container. -dockerCommand+=" -v .:/go/src/app:ro" # Add volume mapping - -# Specify the network that the container should be connected to. This -# is necessary for the Discord bot to be able to work correctly. -dockerCommand+=" --network my-net" # Specify the network - -# Specify that the 8080 port should be exposed to the host machine. This -# is the port that the API application listens on. -dockerCommand+=" -p 8080:8080" # Map port 8080 - -# Specify that the 8000 port should be exposed to the host machine. This -# is the port that the admin application listens on. -dockerCommand+=" -p 8000:8000" # Map port 8000 - -# Specify the image that should be used to create the container. -dockerCommand+=" diplicity" - -echo "Running Docker command: $dockerCommand" - -# Execute the Docker command -eval $dockerCommand \ No newline at end of file diff --git a/auth/auth.go b/auth/auth.go index 79d32b2..a612633 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -36,6 +36,7 @@ const ( LoginRoute = "Login" LogoutRoute = "Logout" TokenForDiscordUserRoute = "TokenForDiscordUser" + DiscordBotLoginRoute = "DiscordBotLogin" RedirectRoute = "Redirect" OAuth2CallbackRoute = "OAuth2Callback" UnsubscribeRoute = "Unsubscribe" @@ -418,16 +419,12 @@ func getOAuth2Config(ctx context.Context, r *http.Request) (*oauth2.Config, erro }, nil } -type TokenForDiscordUserResponse struct { - token string -} - -func handleGetTokenForDiscordUser(w ResponseWriter, r Request) (*TokenForDiscordUserResponse, error) { +func handleGetTokenForDiscordUser(w ResponseWriter, r Request) error { ctx := appengine.NewContext(r.Req()) user, ok := r.Values()["user"].(*User) if !ok { - return nil, HTTPErr{ + return HTTPErr{ Body: "Unauthenticated", Status: http.StatusUnauthorized, } @@ -437,14 +434,14 @@ func handleGetTokenForDiscordUser(w ResponseWriter, r Request) (*TokenForDiscord superusers, err := GetSuperusers(ctx) if err != nil { - return nil, HTTPErr{ + return HTTPErr{ Body: "Unable to load superusers", Status: http.StatusInternalServerError, } } if !superusers.Includes(user.Id) { - return nil, HTTPErr{ + return HTTPErr{ Body: "Unauthorized", Status: http.StatusForbidden, } @@ -453,7 +450,7 @@ func handleGetTokenForDiscordUser(w ResponseWriter, r Request) (*TokenForDiscord discordUserId := r.Vars()["user_id"] if discordUserId == "" { - return nil, HTTPErr{ + return HTTPErr{ Body: "Must provide discord user id", Status: http.StatusBadRequest, } @@ -462,7 +459,7 @@ func handleGetTokenForDiscordUser(w ResponseWriter, r Request) (*TokenForDiscord discordUser := createUserFromDiscordUserId(discordUserId) if _, err := datastore.Put(ctx, UserID(ctx, discordUser.Id), discordUser); err != nil { - return nil, HTTPErr{ + return HTTPErr{ Body: "Unable to store user", Status: http.StatusInternalServerError, } @@ -470,13 +467,14 @@ func handleGetTokenForDiscordUser(w ResponseWriter, r Request) (*TokenForDiscord token, err := encodeUserToToken(ctx, discordUser) if err != nil { - return nil, HTTPErr{ + return HTTPErr{ Body: "Unable to encode user to token", Status: http.StatusInternalServerError, } } - return &TokenForDiscordUserResponse{token: token}, nil + w.SetContent(NewItem(token).SetName("token")) + return nil } func createUserFromDiscordUserId(discordUserId string) *User { @@ -491,6 +489,18 @@ func createUserFromDiscordUserId(discordUserId string) *User { } } +func createDiscordBotUser() *User { + return &User{ + Email: "discord-bot@discord-bot.fake", + FamilyName: "Discord Bot", + GivenName: "Discord Bot", + Id: "discord-bot-user-id", + Name: "Discord Bot", + VerifiedEmail: true, + ValidUntil: time.Now().Add(time.Hour * 24 * 365 * 10), + } +} + func handleLogin(w ResponseWriter, r Request) error { ctx := appengine.NewContext(r.Req()) @@ -522,6 +532,70 @@ func handleLogin(w ResponseWriter, r Request) error { return nil } +var USERNAME = "username" +var PASSWORD = "password" + +func handleDiscordBotLogin(w ResponseWriter, r Request) error { + ctx := appengine.NewContext(r.Req()) + + authHeader := r.Req().Header.Get("Authorization") + if !strings.HasPrefix(authHeader, "Basic ") { + // return HTTPErr{ + // Body: "Authorization header must be Basic", + // Status: http.StatusBadRequest, + // } + return HTTPErr{"Authorization header must be Basic", http.StatusBadRequest} + } + + decoded, err := base64.StdEncoding.DecodeString(authHeader[6:]) + if err != nil { + // return HTTPErr{ + // Body: "Unable to decode authorization header", + // Status: http.StatusBadRequest, + // } + return HTTPErr{"Unable to decode authorization header", http.StatusBadRequest} + } + + parts := strings.Split(string(decoded), ":") + if len(parts) != 2 { + // return HTTPErr{ + // Body: "Authorization header format not username:password", + // Status: http.StatusBadRequest, + // } + return HTTPErr{"Authorization header format not username:password", http.StatusBadRequest} + } + + if parts[0] != USERNAME || parts[1] != PASSWORD { + // return HTTPErr{ + // Body: "Unauthorized", + // Status: http.StatusUnauthorized, + // } + return HTTPErr{"Unauthorized", http.StatusUnauthorized} + } + + discordBotUser := createDiscordBotUser() + + if _, err := datastore.Put(ctx, UserID(ctx, discordBotUser.Id), discordBotUser); err != nil { + // return HTTPErr{ + // Body: "Unable to store user", + // Status: http.StatusInternalServerError, + // } + return HTTPErr{"Unable to store user", http.StatusInternalServerError} + } + + token, err := encodeUserToToken(ctx, discordBotUser) + if err != nil { + // return HTTPErr{ + // Body: "Unable to encode user to token", + // Status: http.StatusInternalServerError, + // } + return HTTPErr{"Unable to encode user to token", http.StatusInternalServerError} + } + + w.SetContent(NewItem(token).SetName("token")) + return nil +} + func EncodeString(ctx context.Context, s string) (string, error) { b, err := EncodeBytes(ctx, []byte(s)) if err != nil { @@ -864,7 +938,7 @@ func tokenFilter(w ResponseWriter, r Request) (bool, error) { token := r.Req().URL.Query().Get("token") if token == "" { queryToken = false - if authHeader := r.Req().Header.Get("Authorization"); authHeader != "" { + if authHeader := r.Req().Header.Get("Authorization"); authHeader != "" && !strings.HasPrefix(authHeader, "Basic") { parts := strings.Split(authHeader, " ") if len(parts) != 2 { return false, HTTPErr{"Authorization header not two parts joined by space", http.StatusBadRequest} @@ -1164,13 +1238,14 @@ func SetupRouter(r *mux.Router) { HandleResource(router, RedirectURLResource) Handle(router, "/_test_update_user", []string{"PUT"}, TestUpdateUserRoute, handleTestUpdateUser) Handle(router, "/Auth/Login", []string{"GET"}, LoginRoute, handleLogin) + Handle(router, "/Auth/DiscordBotLogin", []string{"GET"}, DiscordBotLoginRoute, handleDiscordBotLogin) + Handle(router, "/Auth/{user_id}/TokenForDiscordUser", []string{"GET"}, TokenForDiscordUserRoute, handleGetTokenForDiscordUser) Handle(router, "/Auth/Logout", []string{"GET"}, LogoutRoute, handleLogout) // Don't use `Handle` here, because we don't want CORS support for this particular route. router.Path("/Auth/OAuth2Callback").Methods("GET").Name(OAuth2CallbackRoute).HandlerFunc(handleOAuth2Callback) Handle(router, "/Auth/ApproveRedirect", []string{"POST"}, ApproveRedirectRoute, handleApproveRedirect) Handle(router, "/User/{user_id}/Unsubscribe", []string{"GET"}, UnsubscribeRoute, unsubscribe) Handle(router, "/User/{user_id}/FCMToken/{replace_token}/Replace", []string{"PUT"}, ReplaceFCMRoute, replaceFCM) - Handle(router, "/User/{user_id}/TokenForDiscordUser", []string{"GET"}, TokenForDiscordUserRoute, handleGetTokenForDiscordUser) AddFilter(decorateAPILevel) AddFilter(tokenFilter) AddFilter(logHeaders) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..45df468 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,29 @@ +version: "3.9" +services: + diplicity-application: + build: + context: . + dockerfile: .docker/Dockerfile + container_name: diplicity-application + entrypoint: + [ + "python3", + "../../../usr/local/gcloud/google-cloud-sdk/bin/dev_appserver.py", + "--host=0.0.0.0", + "--admin_host=0.0.0.0", + "--enable_host_checking=False", + ".", + ] + volumes: + - .:/go/src/app + networks: + - diplicity-net + env_file: + - .env + ports: + - "8080:8080" + - "8000:8000" +networks: + diplicity-net: + name: diplicity-net + driver: bridge From 9a1b6de7f4c4659effd7a8a4042fa0ede5a2d8e2 Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 7 Jun 2024 14:40:12 +0100 Subject: [PATCH 4/6] Working --- .docker/Dockerfile | 5 +-- auth/auth.go | 102 +++++++++++++++++++++++++++------------------ game/handler.go | 19 +++++++-- 3 files changed, 78 insertions(+), 48 deletions(-) diff --git a/.docker/Dockerfile b/.docker/Dockerfile index 2d20f95..5846ae9 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -15,9 +15,6 @@ RUN /usr/local/gcloud/google-cloud-sdk/bin/gcloud components install app-engine- # Add the gcloud command-line tool to your path. ENV PATH $PATH:/usr/local/gcloud/google-cloud-sdk/bin -# # Run dev_appserver.py . from inside the repo -# RUN dev_appserver.py . - # Set the working directory in the container WORKDIR /go/src/app @@ -30,6 +27,8 @@ EXPOSE 8080 # Expose port 80 for http traffic EXPOSE 80 +RUN chmod +x set-discord-bot-credentials.sh + # Expose port 8000 to the outside world EXPOSE 8000 diff --git a/auth/auth.go b/auth/auth.go index a612633..eb067f9 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -53,12 +53,13 @@ const ( ) const ( - UserKind = "User" - naClKind = "NaCl" - oAuthKind = "OAuth" - redirectURLKind = "RedirectURL" - superusersKind = "Superusers" - prodKey = "prod" + UserKind = "User" + naClKind = "NaCl" + oAuthKind = "OAuth" + redirectURLKind = "RedirectURL" + superusersKind = "Superusers" + discordBotCredentialsKind = "DiscordBotCredentials" + prodKey = "prod" ) const ( @@ -66,17 +67,58 @@ const ( ) var ( - prodOAuth *OAuth - prodOAuthLock = sync.RWMutex{} - prodNaCl *naCl - prodNaClLock = sync.RWMutex{} - prodSuperusers *Superusers - prodSuperusersLock = sync.RWMutex{} - router *mux.Router + prodOAuth *OAuth + prodOAuthLock = sync.RWMutex{} + prodNaCl *naCl + prodNaClLock = sync.RWMutex{} + prodSuperusers *Superusers + prodSuperusersLock = sync.RWMutex{} + prodDiscordBotCredentials *DiscordBotCredentials + prodDiscordBotCredentialsLock = sync.RWMutex{} + router *mux.Router RedirectURLResource *Resource ) +type DiscordBotCredentials struct { + Username string + Password string +} + +func getDiscordBotCredentialsKey(ctx context.Context) *datastore.Key { + return datastore.NewKey(ctx, discordBotCredentialsKind, prodKey, 0, nil) +} + +func SetDiscordBotCredentials(ctx context.Context, discordBotCredentials *DiscordBotCredentials) error { + return datastore.RunInTransaction(ctx, func(ctx context.Context) error { + currentDiscordBotCredentials := &DiscordBotCredentials{} + if err := datastore.Get(ctx, getDiscordBotCredentialsKey(ctx), currentDiscordBotCredentials); err == nil { + return HTTPErr{"DiscordBotCredentials already configured", http.StatusBadRequest} + } + if _, err := datastore.Put(ctx, getDiscordBotCredentialsKey(ctx), discordBotCredentials); err != nil { + return err + } + return nil + }, &datastore.TransactionOptions{XG: false}) +} + +func getDiscordBotCredentials(ctx context.Context) (*DiscordBotCredentials, error) { + prodDiscordBotCredentialsLock.RLock() + if prodDiscordBotCredentials != nil { + defer prodDiscordBotCredentialsLock.RUnlock() + return prodDiscordBotCredentials, nil + } + prodDiscordBotCredentialsLock.RUnlock() + prodDiscordBotCredentialsLock.Lock() + defer prodDiscordBotCredentialsLock.Unlock() + foundDiscordBotCredentials := &DiscordBotCredentials{} + if err := datastore.Get(ctx, getDiscordBotCredentialsKey(ctx), foundDiscordBotCredentials); err != nil { + return nil, err + } + prodDiscordBotCredentials = foundDiscordBotCredentials + return prodDiscordBotCredentials, nil +} + func init() { RedirectURLResource = &Resource{ Delete: deleteRedirectURL, @@ -532,63 +574,41 @@ func handleLogin(w ResponseWriter, r Request) error { return nil } -var USERNAME = "username" -var PASSWORD = "password" - func handleDiscordBotLogin(w ResponseWriter, r Request) error { ctx := appengine.NewContext(r.Req()) + discordBotCredentials, err := getDiscordBotCredentials(ctx) + if err != nil { + return HTTPErr{"Unable to load discord bot credentials", http.StatusInternalServerError} + } + authHeader := r.Req().Header.Get("Authorization") if !strings.HasPrefix(authHeader, "Basic ") { - // return HTTPErr{ - // Body: "Authorization header must be Basic", - // Status: http.StatusBadRequest, - // } return HTTPErr{"Authorization header must be Basic", http.StatusBadRequest} } decoded, err := base64.StdEncoding.DecodeString(authHeader[6:]) if err != nil { - // return HTTPErr{ - // Body: "Unable to decode authorization header", - // Status: http.StatusBadRequest, - // } return HTTPErr{"Unable to decode authorization header", http.StatusBadRequest} } parts := strings.Split(string(decoded), ":") if len(parts) != 2 { - // return HTTPErr{ - // Body: "Authorization header format not username:password", - // Status: http.StatusBadRequest, - // } return HTTPErr{"Authorization header format not username:password", http.StatusBadRequest} } - if parts[0] != USERNAME || parts[1] != PASSWORD { - // return HTTPErr{ - // Body: "Unauthorized", - // Status: http.StatusUnauthorized, - // } + if parts[0] != discordBotCredentials.Username || parts[1] != discordBotCredentials.Password { return HTTPErr{"Unauthorized", http.StatusUnauthorized} } discordBotUser := createDiscordBotUser() if _, err := datastore.Put(ctx, UserID(ctx, discordBotUser.Id), discordBotUser); err != nil { - // return HTTPErr{ - // Body: "Unable to store user", - // Status: http.StatusInternalServerError, - // } return HTTPErr{"Unable to store user", http.StatusInternalServerError} } token, err := encodeUserToToken(ctx, discordBotUser) if err != nil { - // return HTTPErr{ - // Body: "Unable to encode user to token", - // Status: http.StatusInternalServerError, - // } return HTTPErr{"Unable to encode user to token", http.StatusInternalServerError} } diff --git a/game/handler.go b/game/handler.go index 888289b..8440107 100644 --- a/game/handler.go +++ b/game/handler.go @@ -655,16 +655,20 @@ func createAllocation(w ResponseWriter, r Request) (*Allocation, error) { } type configuration struct { - OAuth *auth.OAuth - FCMConf *FCMConf - SendGrid *auth.SendGrid - Superusers *auth.Superusers + OAuth *auth.OAuth + FCMConf *FCMConf + SendGrid *auth.SendGrid + Superusers *auth.Superusers + DiscordBotCredentials *auth.DiscordBotCredentials } func handleConfigure(w ResponseWriter, r Request) error { ctx := appengine.NewContext(r.Req()) + log.Infof(ctx, "handleConfigure called") + fmt.Printf("handleConfigure called") conf := &configuration{} + log.Infof(ctx, "handleConfigure called with %+v", conf) if err := json.NewDecoder(r.Req().Body).Decode(conf); err != nil { return err } @@ -688,6 +692,13 @@ func handleConfigure(w ResponseWriter, r Request) error { return err } } + fmt.Printf("DiscordBotCredentials: %+v", conf.DiscordBotCredentials) + log.Infof(ctx, "DiscordBotCredentials: %+v", conf.DiscordBotCredentials) + if conf.DiscordBotCredentials != nil { + if err := auth.SetDiscordBotCredentials(ctx, conf.DiscordBotCredentials); err != nil { + return err + } + } return nil } From bf13cb96532d648229d83abd442a281694a17346 Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 7 Jun 2024 14:43:44 +0100 Subject: [PATCH 5/6] Update readme --- .docker/Dockerfile | 2 -- README.md | 7 +++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.docker/Dockerfile b/.docker/Dockerfile index 5846ae9..8189cc3 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -27,8 +27,6 @@ EXPOSE 8080 # Expose port 80 for http traffic EXPOSE 80 -RUN chmod +x set-discord-bot-credentials.sh - # Expose port 8000 to the outside world EXPOSE 8000 diff --git a/README.md b/README.md index b721772..9ebdd2a 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,11 @@ To enable debugging the JSON output in a browser, adding the query parameter `ac - Download Docker - Navigate to the root directory of this project -- Use `ps1` files for Windows and `sh` files for UNIX -- Run `.\.docker\docker-build.ps1` **or** `.\.docker\docker-build.sh` (only required once) -- Run `.\.docker\docker-network.ps1` **or** `.\.docker\docker-network.sh` (only required once) -- Run `.\.docker\docker-run.ps1` **or** `.\.docker\docker-run.sh` +- Run `docker-compose up` - The API is now available on your machine at `localhost:8080` - The Admin server is now available on your machine at `localhost:8000` +- **Note** to get the Discord bot auth to work, you need to send a curl request + after the service is initialized: `curl -XPOST http://localhost:8080/_configure -d '{"DiscordBotCredentials": {"Username": "", "Password": ""}}'` ## Running locally From 6873824f6750e91bd48ae6d5a01a2b402a1dac68 Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 7 Jun 2024 14:44:57 +0100 Subject: [PATCH 6/6] Remove logs --- game/handler.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/game/handler.go b/game/handler.go index 8440107..6e60618 100644 --- a/game/handler.go +++ b/game/handler.go @@ -664,11 +664,8 @@ type configuration struct { func handleConfigure(w ResponseWriter, r Request) error { ctx := appengine.NewContext(r.Req()) - log.Infof(ctx, "handleConfigure called") - fmt.Printf("handleConfigure called") conf := &configuration{} - log.Infof(ctx, "handleConfigure called with %+v", conf) if err := json.NewDecoder(r.Req().Body).Decode(conf); err != nil { return err } @@ -692,8 +689,6 @@ func handleConfigure(w ResponseWriter, r Request) error { return err } } - fmt.Printf("DiscordBotCredentials: %+v", conf.DiscordBotCredentials) - log.Infof(ctx, "DiscordBotCredentials: %+v", conf.DiscordBotCredentials) if conf.DiscordBotCredentials != nil { if err := auth.SetDiscordBotCredentials(ctx, conf.DiscordBotCredentials); err != nil { return err