Skip to content

Commit

Permalink
Frontend API boilerplate implementation (#34)
Browse files Browse the repository at this point in the history
* Frontend API boilerplate implementation

Auth service with env api keys

Implemented JWT and updated auth handler

* Updated to GIN, new Dockerfile and Makefile

* Updated loggin as per recommendation

* Removed leftover panics from the launcher

* Updated readme

---------

Co-authored-by: Nikolai Danylchyk <[email protected]>
Co-authored-by: Mark Mandel <[email protected]>
  • Loading branch information
3 people authored Feb 28, 2023
1 parent 1378377 commit 89a4efd
Show file tree
Hide file tree
Showing 13 changed files with 868 additions and 46 deletions.
4 changes: 2 additions & 2 deletions game/launcher/app.ini
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

frontend_api = http://localhost:8080
callback_listen_port = 8081
frontend_api = http://localhost:8081
callback_listen_port = 8082

[windows]
binary = DroidShooterClient.exe
Expand Down
80 changes: 36 additions & 44 deletions game/launcher/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,43 +34,27 @@ import (
"gopkg.in/ini.v1"
)

type GoogleOauthToken struct {
AccessToken string
RefreshToken string
Expiry string
TokenType string
IdToken string
}

type UserInfo struct {
Id string `json:"id"`
Sub string `json:"sub"`
Name string `json:"name"`
Email string `json:"email"`
}

var (
myToken string
myRefreshToken string
myApp fyne.App
myWindow fyne.Window
iniCfg *ini.File
myToken string
myApp fyne.App
myWindow fyne.Window
iniCfg *ini.File
)

func main() {
// Load configs
var err error
iniCfg, err = ini.Load("app.ini")
if err != nil {
fmt.Printf("Fail to read file: %v", err)
log.Fatalf("Fail to read file: %v", err)
os.Exit(1)
}

// Callback handling from the frontend api
http.HandleFunc("/callback", handleGoogleCallback)
go func() {
fmt.Println("Google for Games Launcher is listening for callbacks on :" + iniCfg.Section("").Key("callback_listen_port").String())
fmt.Println(http.ListenAndServe(":"+iniCfg.Section("").Key("callback_listen_port").String(), nil))
log.Printf("Google for Games Launcher is listening for callbacks on :%s", iniCfg.Section("").Key("callback_listen_port").String())
log.Println(http.ListenAndServe(":"+iniCfg.Section("").Key("callback_listen_port").String(), nil))
}()

// UI
Expand Down Expand Up @@ -105,30 +89,25 @@ func handleGoogleCallback(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusInternalServerError)

fmt.Fprintf(rw, "{\"error\": \"%s\"}", err)
log.Println("panic occurred:", err)
log.Printf("panic occurred: %s", err)
}
}()

// Save my token
myToken = req.FormValue("access_token")
myToken = req.FormValue("token")
if len(myToken) == 0 {
panic("No token received!")
}
myRefreshToken = req.FormValue("refresh_token")
if len(myRefreshToken) == 0 {
panic("No refresh received!")
log.Fatal("No token received!")
}

// Update UI with profile info and launch game button
myProfile := getProfileInfo()
fmt.Printf("My name is " + myProfile.Name)
playerName := getPlayerName()

image := canvas.NewImageFromFile("assets/header.png")
image.FillMode = canvas.ImageFillContain

label1 := widget.NewLabel(fmt.Sprintf("Welcome %s!", myProfile.Name))
label1 := widget.NewLabel(fmt.Sprintf("Welcome %s!", playerName))
label1.Alignment = fyne.TextAlignCenter
label2 := widget.NewLabel(fmt.Sprintf("Are you ready to play again?!"))
label2 := widget.NewLabel("Are you ready to play again?!")
label2.Alignment = fyne.TextAlignCenter

buttonPlay := widget.NewButtonWithIcon("Open Droidshooter", theme.MediaPlayIcon(), func() {
Expand Down Expand Up @@ -157,40 +136,53 @@ func handleGoogleCallback(rw http.ResponseWriter, req *http.Request) {
}

func handlePlay() {
params := fmt.Sprintf("-token=%s -refresh_token=%s", myToken, myRefreshToken)
params := fmt.Sprintf("-token=%s", myToken)

// Get the binary file from the ini
cmd := exec.Command(iniCfg.Section(runtime.GOOS).Key("binary").String(), params)
fmt.Printf("Launching: %s %s\n", iniCfg.Section(runtime.GOOS).Key("binary").String(), params)
log.Printf("Launching: %s %s", iniCfg.Section(runtime.GOOS).Key("binary").String(), params)

_, err := cmd.CombinedOutput()
if err != nil {
log.Printf("Error: %s", err)
}
}

func getProfileInfo() UserInfo {
fmt.Printf("Getting profile info\n")
func getPlayerName() string {
log.Printf("Getting player info")

req, err := http.NewRequest("GET", iniCfg.Section("").Key("frontend_api").String()+"/profile", nil)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", myToken))

if err != nil {
log.Fatal("Unable to initiate request to game api. Connection issues?")
}

client := &http.Client{}
response, err := client.Do(req)

if err != nil {
log.Fatal(err)
}

response, err := http.Get("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + myToken)
if response.StatusCode != 200 {
panic("Unable to fetch user information. Expired token?")
log.Fatal("Unable to fetch user information. Expired token?")
}

defer response.Body.Close()
// Use response.Body to get user information.

data, err := ioutil.ReadAll(response.Body)
if err != nil {
panic(err)
log.Fatal(err)
}

var result UserInfo
var result map[string]interface{}
if err := json.Unmarshal(data, &result); err != nil {
panic(err)
log.Fatalf("Unable to decode json: %s", err)
}

return result
return result["player_name"].(string)
}

func openBrowser(url string) {
Expand Down
32 changes: 32 additions & 0 deletions services/frontend-api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# Building the application.
FROM golang:1.19 as build

WORKDIR /go/src/app
COPY . .
# In case of local .env being present
RUN rm -rf .env

RUN go mod download
RUN go vet -v
RUN go test -v
RUN CGO_ENABLED=0 go build -o /go/bin/app

# Copy bin into our base image.
FROM gcr.io/distroless/static-debian11
COPY --from=build /go/bin/app /
CMD ["/app"]
29 changes: 29 additions & 0 deletions services/frontend-api/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright 2023 Google LLC All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

BUILD_DIR=$(PWD)/build
build:
echo "Building frontend api service"
mkdir -p ${BUILD_DIR} && GOOS=linux GOARCH=386 go build -o ${BUILD_DIR}/frontend-api main.go

build-docker:
echo "Building docker container"
docker build . -t frontend-api

clean:
echo "Running cleanup"
rm -rf ${BUILD_DIR}/
docker rmi -f frontend-api

.PHONY: build build-docker clean
45 changes: 45 additions & 0 deletions services/frontend-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Application API keys

CLIENT_ID and SECRET_ID need to be generated and fetched from https://console.cloud.google.com/apis/credentials (OAuth 2.0 Client IDs)

# For Local development

Please create a .env file in the same with the following variables:

```bash
CLIENT_ID=<CLIENT_ID>.apps.googleusercontent.com
CLIENT_SECRET=<CLIENT_SECRET>
LISTEN_PORT=8081
CLIENT_LAUNCHER_PORT=8082
PROFILE_SERVICE=http://localhost:8080
PING_SERVICE=http://localhost:8083
JWT_KEY=<JWT_KEY>
```

# Building locally

`make build`

Binary will be generated in the `build/` folder

# Building container

`make build-docker`

To use container locally:

`docker run --env-file .env -p 8081:8081 frontend-api`

# Running in production

Make sure the following environment variables are set

```bash
CLIENT_ID=<CLIENT_ID>.apps.googleusercontent.com
CLIENT_SECRET=<CLIENT_SECRET>
LISTEN_PORT=8081
CLIENT_LAUNCHER_PORT=8082
PROFILE_SERVICE=<PROFILE_SERVICE_ENDPOINT>
PING_SERVICE=<PING_SERVICE_ENDPOINT>
JWT_KEY=<JWT_KEY>
```
40 changes: 40 additions & 0 deletions services/frontend-api/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module github.com/googleforgames/global-multiplayer-demo/services/frontend-api

go 1.19

require (
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/joho/godotenv v1.5.1
golang.org/x/oauth2 v0.4.0
)

require (
cloud.google.com/go/compute v1.14.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/bytedance/sonic v1.8.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.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.11.2 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.17 // 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.0.6 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.10 // indirect
golang.org/x/arch v0.2.0 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading

0 comments on commit 89a4efd

Please sign in to comment.