From 8693673a7117996c7a8107d9a351731afac48375 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Mon, 10 Apr 2017 14:53:26 +0300 Subject: [PATCH 1/2] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B2=D0=BE=D1=81=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=D0=BD=D1=84=D0=BE=D1=80?= =?UTF-8?q?=D0=BC=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BE=D0=B1=20=D0=B0=D0=BA?= =?UTF-8?q?=D0=BA=D0=B0=D1=83=D0=BD=D1=82=D0=B5,=20=D0=B5=D1=81=D0=BB?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=BE=20=D0=BA=D0=B0=D0=BA=D0=BE=D0=B9-=D1=82?= =?UTF-8?q?=D0=BE=20=D0=BF=D1=80=D0=B8=D1=87=D0=B8=D0=BD=D0=B5=20=D0=B5?= =?UTF-8?q?=D1=91=20=D0=BD=D0=B5=20=D1=83=D0=B4=D0=B0=D0=BB=D0=BE=D1=81?= =?UTF-8?q?=D1=8C=20=D0=BD=D0=B0=D0=B9=D1=82=D0=B8=20=D0=B2=20=D1=85=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=BB=D0=B8=D1=89=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/external/accounts/AccountInfo.go | 44 ++++++++++++++++++++ lib/external/accounts/GetToken.go | 49 ++++++++++++++++++++++ lib/external/accounts/base.go | 51 +++++++++++++++++++++++ lib/worker/handlers.go | 41 ++++++++++++++---- lib/worker/supports.go | 62 ++++++++++++++++++++++++++++ 5 files changed, 238 insertions(+), 9 deletions(-) create mode 100644 lib/external/accounts/AccountInfo.go create mode 100644 lib/external/accounts/GetToken.go create mode 100644 lib/external/accounts/base.go create mode 100644 lib/worker/supports.go diff --git a/lib/external/accounts/AccountInfo.go b/lib/external/accounts/AccountInfo.go new file mode 100644 index 0000000..eb1bb71 --- /dev/null +++ b/lib/external/accounts/AccountInfo.go @@ -0,0 +1,44 @@ +package accounts + +import ( + "net/http" + "io/ioutil" + "encoding/json" +) + +type AccountInfoResponse struct { + Id int `json:"id"` + Uuid string `json:"uuid"` + Username string `json:"username"` + Email string `json:"email"` +} + +const internalAccountInfoUrl = domain + "/api/internal/accounts/info" + +func (token *Token) AccountInfo(attribute string, value string) (AccountInfoResponse, error) { + request, err := http.NewRequest("GET", internalAccountInfoUrl, nil) + request.Header.Add("Authorization", "Bearer " + token.AccessToken) + query := request.URL.Query() + query.Add(attribute, value) + request.URL.RawQuery = query.Encode() + + response, err := Client.Do(request) + if err != nil { + panic(err) + } + + defer response.Body.Close() + + var info AccountInfoResponse + + responseError := handleResponse(response) + if responseError != nil { + return info, responseError + } + + body, _ := ioutil.ReadAll(response.Body) + println("Raw account info response is " + string(body)) + json.Unmarshal(body, &info) + + return info, nil +} diff --git a/lib/external/accounts/GetToken.go b/lib/external/accounts/GetToken.go new file mode 100644 index 0000000..49afd5c --- /dev/null +++ b/lib/external/accounts/GetToken.go @@ -0,0 +1,49 @@ +package accounts + +import ( + "strings" + "net/url" + "io/ioutil" + "encoding/json" +) + +type TokenRequest struct { + Id string + Secret string + Scopes []string +} + +type Token struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` +} + +const tokenUrl = domain + "/api/oauth2/v1/token" + +func GetToken(request TokenRequest) (Token, error) { + form := url.Values{} + form.Add("client_id", request.Id) + form.Add("client_secret", request.Secret) + form.Add("grant_type", "client_credentials") + form.Add("scope", strings.Join(request.Scopes, ",")) + + response, err := Client.Post(tokenUrl, "application/x-www-form-urlencoded", strings.NewReader(form.Encode())) + if err != nil { + panic(err) + } + + defer response.Body.Close() + + var result Token + responseError := handleResponse(response) + if responseError != nil { + return result, responseError + } + + body, _ := ioutil.ReadAll(response.Body) + + json.Unmarshal(body, &result) + + return result, nil +} diff --git a/lib/external/accounts/base.go b/lib/external/accounts/base.go new file mode 100644 index 0000000..51feb75 --- /dev/null +++ b/lib/external/accounts/base.go @@ -0,0 +1,51 @@ +package accounts + +import ( + "fmt" + "net/http" +) + +const domain = "https://dev.account.ely.by" + +var Client = &http.Client{} + +type UnauthorizedResponse struct {} + +func (err UnauthorizedResponse) Error() string { + return "Unauthorized response" +} + +type ForbiddenResponse struct {} + +func (err ForbiddenResponse) Error() string { + return "Forbidden response" +} + +type NotFoundResponse struct {} + +func (err NotFoundResponse) Error() string { + return "Not found" +} + +type NotSuccessResponse struct { + StatusCode int +} + +func (err NotSuccessResponse) Error() string { + return fmt.Sprintf("Response code is \"%d\"", err.StatusCode) +} + +func handleResponse(response *http.Response) error { + switch status := response.StatusCode; status { + case 200: + return nil + case 401: + return &UnauthorizedResponse{} + case 403: + return &ForbiddenResponse{} + case 404: + return &NotFoundResponse{} + default: + return &NotSuccessResponse{status} + } +} diff --git a/lib/worker/handlers.go b/lib/worker/handlers.go index 072fefd..fc3c32d 100644 --- a/lib/worker/handlers.go +++ b/lib/worker/handlers.go @@ -1,6 +1,7 @@ package worker import ( + "fmt" "elyby/minecraft-skinsystem/lib/data" "elyby/minecraft-skinsystem/lib/services" ) @@ -18,13 +19,23 @@ func handleChangeUsername(model usernameChanged) (bool) { return true } - record, err := data.FindSkinByUsername(model.OldUsername) + record, err := data.FindSkinById(model.AccountId) if (err != nil) { - services.Logger.IncCounter("worker.change_username.username_not_found", 1) - // TODO: я не уверен, что это валидное поведение - // Суть в том, что здесь может возникнуть ошибка в том случае, если записи в базе нету - // а значит его нужно, как минимум, зарегистрировать - return true + services.Logger.IncCounter("worker.change_username.id_not_found", 1) + fmt.Println("Cannot find user id. Trying to search.") + response, err := getById(model.AccountId) + if err != nil { + services.Logger.IncCounter("worker.change_username.id_not_restored", 1) + fmt.Printf("Cannot restore user info. %T\n", err) + // TODO: логгировать в какой-нибудь Sentry, если там не 404 + return true + } + + services.Logger.IncCounter("worker.change_username.id_restored", 1) + fmt.Println("User info successfully restored.") + record = data.SkinItem{ + UserId: response.Id, + } } record.Username = model.NewUsername @@ -35,11 +46,23 @@ func handleChangeUsername(model usernameChanged) (bool) { return true } -func handleSkinChanged(model skinChanged) (bool) { +func handleSkinChanged(model skinChanged) bool { record, err := data.FindSkinById(model.AccountId) - if (err != nil) { + if err != nil { services.Logger.IncCounter("worker.skin_changed.id_not_found", 1) - return true + fmt.Println("Cannot find user id. Trying to search.") + response, err := getById(model.AccountId) + if err != nil { + services.Logger.IncCounter("worker.skin_changed.id_not_restored", 1) + fmt.Printf("Cannot restore user info. %T\n", err) + // TODO: логгировать в какой-нибудь Sentry, если там не 404 + return true + } + + services.Logger.IncCounter("worker.skin_changed.id_restored", 1) + fmt.Println("User info successfully restored.") + record.UserId = response.Id + record.Username = response.Username } record.Uuid = model.Uuid diff --git a/lib/worker/supports.go b/lib/worker/supports.go new file mode 100644 index 0000000..e370521 --- /dev/null +++ b/lib/worker/supports.go @@ -0,0 +1,62 @@ +package worker + +import ( + "strconv" + "elyby/minecraft-skinsystem/lib/external/accounts" +) + +var token *accounts.Token + +const repeatsLimit = 3 +var repeatsCount = 0 + +func getById(id int) (accounts.AccountInfoResponse, error) { + return _getByField("id", strconv.Itoa(id)) +} + +func _getByField(field string, value string) (accounts.AccountInfoResponse, error) { + defer resetRepeatsCount() + + apiToken, err := getToken() + if err != nil { + return accounts.AccountInfoResponse{}, err + } + + result, err := apiToken.AccountInfo(field, value) + if err != nil { + _, ok := err.(*accounts.UnauthorizedResponse) + if !ok || repeatsCount >= repeatsLimit { + return accounts.AccountInfoResponse{}, err + } + + repeatsCount++ + token = nil + + return _getByField(field, value) + } + + return result, nil +} + +func getToken() (*accounts.Token, error) { + if token == nil { + tempToken, err := accounts.GetToken(accounts.TokenRequest{ + Id: "skinsystem", + Secret: "qugFIaCjec3LMA", + Scopes: []string{ + "internal_account_info", + }, + }) + if err != nil { + return &accounts.Token{}, err + } + + token = &tempToken + } + + return token, nil +} + +func resetRepeatsCount() { + repeatsCount = 0 +} From cbe940f8ecbfd4a81d53d1b29e781656c1d2a19b Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Mon, 10 Apr 2017 20:28:47 +0300 Subject: [PATCH 2/2] =?UTF-8?q?=D0=9A=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=20Acco?= =?UTF-8?q?unts=20API=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=20?= =?UTF-8?q?=D0=B2=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D1=8B?= =?UTF-8?q?=20=D0=BE=D0=BA=D1=80=D1=83=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.dev.yml | 4 ++++ docker-compose.prod.yml | 4 ++++ lib/worker/supports.go | 10 +++------- minecraft-skinsystem.go | 16 ++++++++++++++++ 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index c5e919d..fd1f743 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -12,6 +12,10 @@ services: - redis - rabbitmq - statsd + environment: + ACCOUNTS_API_ID: "" + ACCOUNTS_API_SECRET: "" + STATSD_ADDR: "" redis: extends: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 3c86b31..9d8bc79 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -8,6 +8,10 @@ services: - redis - rabbitmq restart: always + environment: + ACCOUNTS_API_ID: "" + ACCOUNTS_API_SECRET: "" + STATSD_ADDR: "" redis: extends: diff --git a/lib/worker/supports.go b/lib/worker/supports.go index e370521..4d1f58a 100644 --- a/lib/worker/supports.go +++ b/lib/worker/supports.go @@ -5,6 +5,8 @@ import ( "elyby/minecraft-skinsystem/lib/external/accounts" ) +var AccountsTokenConfig *accounts.TokenRequest + var token *accounts.Token const repeatsLimit = 3 @@ -40,13 +42,7 @@ func _getByField(field string, value string) (accounts.AccountInfoResponse, erro func getToken() (*accounts.Token, error) { if token == nil { - tempToken, err := accounts.GetToken(accounts.TokenRequest{ - Id: "skinsystem", - Secret: "qugFIaCjec3LMA", - Scopes: []string{ - "internal_account_info", - }, - }) + tempToken, err := accounts.GetToken(*AccountsTokenConfig) if err != nil { return &accounts.Token{}, err } diff --git a/minecraft-skinsystem.go b/minecraft-skinsystem.go index b8eefb6..c684279 100644 --- a/minecraft-skinsystem.go +++ b/minecraft-skinsystem.go @@ -19,6 +19,7 @@ import ( "elyby/minecraft-skinsystem/lib/routes" "elyby/minecraft-skinsystem/lib/services" "elyby/minecraft-skinsystem/lib/worker" + "elyby/minecraft-skinsystem/lib/external/accounts" ) const redisPoolSize int = 10 @@ -28,6 +29,20 @@ func main() { runtime.GOMAXPROCS(runtime.NumCPU()) + accountsApiId := os.Getenv("ACCOUNTS_API_ID") + accountsApiSecret := os.Getenv("ACCOUNTS_API_SECRET") + if accountsApiId == "" || accountsApiSecret == "" { + log.Fatal("ACCOUNTS_API params must be provided") + } + + worker.AccountsTokenConfig = &accounts.TokenRequest{ + Id: accountsApiId, + Secret: accountsApiSecret, + Scopes: []string{ + "internal_account_info", + }, + } + log.Println("Connecting to redis") var redisString = os.Getenv("REDIS_ADDR") @@ -62,6 +77,7 @@ func main() { // statsd var statsdString = os.Getenv("STATSD_ADDR") + statsdString = "" if (statsdString != "") { log.Println("Connecting to statsd") hostname, _ := os.Hostname()