mirror of
https://github.com/elyby/chrly.git
synced 2024-11-16 18:22:58 +05:30
Добавлена логика автоматического рефреша API токена при его истечении
This commit is contained in:
parent
eec6b384b7
commit
ec461efe34
56
api/accounts/auto-refresh-token.go
Normal file
56
api/accounts/auto-refresh-token.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package accounts
|
||||||
|
|
||||||
|
type AutoRefresh struct {
|
||||||
|
token *Token
|
||||||
|
config *Config
|
||||||
|
repeatsCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
const repeatsLimit = 3
|
||||||
|
|
||||||
|
func (config *Config) GetTokenWithAutoRefresh() *AutoRefresh {
|
||||||
|
return &AutoRefresh{
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (refresher *AutoRefresh) AccountInfo(attribute string, value string) (*AccountInfoResponse, error) {
|
||||||
|
defer refresher.resetRepeatsCount()
|
||||||
|
|
||||||
|
apiToken, err := refresher.getToken()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := apiToken.AccountInfo(attribute, value)
|
||||||
|
if err != nil {
|
||||||
|
_, isTokenExpire := err.(*UnauthorizedResponse)
|
||||||
|
if !isTokenExpire || refresher.repeatsCount >= repeatsLimit - 1 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
refresher.repeatsCount++
|
||||||
|
refresher.token = nil
|
||||||
|
|
||||||
|
return refresher.AccountInfo(attribute, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (refresher *AutoRefresh) getToken() (*Token, error) {
|
||||||
|
if refresher.token == nil {
|
||||||
|
newToken, err := refresher.config.GetToken()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
refresher.token = newToken
|
||||||
|
}
|
||||||
|
|
||||||
|
return refresher.token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (refresher *AutoRefresh) resetRepeatsCount() {
|
||||||
|
refresher.repeatsCount = 0
|
||||||
|
}
|
242
api/accounts/auto-refresh-token_test.go
Normal file
242
api/accounts/auto-refresh-token_test.go
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
package accounts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
testify "github.com/stretchr/testify/assert"
|
||||||
|
"gopkg.in/h2non/gock.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var config = &Config{
|
||||||
|
Addr: "https://account.ely.by",
|
||||||
|
Id: "mock-id",
|
||||||
|
Secret: "mock-secret",
|
||||||
|
Scopes: []string{"scope1", "scope2"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_GetTokenWithAutoRefresh(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
testConfig := &Config{}
|
||||||
|
*testConfig = *config
|
||||||
|
|
||||||
|
result := testConfig.GetTokenWithAutoRefresh()
|
||||||
|
assert.Equal(testConfig, result.config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAutoRefresh_AccountInfo(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
defer gock.Off()
|
||||||
|
gock.New("https://account.ely.by").
|
||||||
|
Post("/api/oauth2/v1/token").
|
||||||
|
Body(strings.NewReader("client_id=mock-id&client_secret=mock-secret&grant_type=client_credentials&scope=scope1%2Cscope2")).
|
||||||
|
Reply(200).
|
||||||
|
JSON(map[string]interface{}{
|
||||||
|
"access_token": "mocked-token",
|
||||||
|
"token_type": "Bearer",
|
||||||
|
"expires_in": 86400,
|
||||||
|
})
|
||||||
|
|
||||||
|
gock.New("https://account.ely.by").
|
||||||
|
Get("/api/internal/accounts/info").
|
||||||
|
Times(2).
|
||||||
|
MatchParam("id", "1").
|
||||||
|
MatchHeader("Authorization", "Bearer mocked-token").
|
||||||
|
Reply(200).
|
||||||
|
JSON(map[string]interface{}{
|
||||||
|
"id": 1,
|
||||||
|
"uuid": "0f657aa8-bfbe-415d-b700-5750090d3af3",
|
||||||
|
"username": "dummy",
|
||||||
|
"email": "dummy@ely.by",
|
||||||
|
})
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
gock.InterceptClient(client)
|
||||||
|
|
||||||
|
testConfig := &Config{}
|
||||||
|
*testConfig = *config
|
||||||
|
testConfig.Client = client
|
||||||
|
|
||||||
|
autoRefresher := testConfig.GetTokenWithAutoRefresh()
|
||||||
|
result, err := autoRefresher.AccountInfo("id", "1")
|
||||||
|
if assert.NoError(err) {
|
||||||
|
assert.Equal(1, result.Id)
|
||||||
|
assert.Equal("0f657aa8-bfbe-415d-b700-5750090d3af3", result.Uuid)
|
||||||
|
assert.Equal("dummy", result.Username)
|
||||||
|
assert.Equal("dummy@ely.by", result.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
result2, err2 := autoRefresher.AccountInfo("id", "1")
|
||||||
|
if assert.NoError(err2) {
|
||||||
|
assert.Equal(result, result2, "Results should still be same without token refreshing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAutoRefresh_AccountInfo2(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
defer gock.Off()
|
||||||
|
gock.New("https://account.ely.by").
|
||||||
|
Post("/api/oauth2/v1/token").
|
||||||
|
Body(strings.NewReader("client_id=mock-id&client_secret=mock-secret&grant_type=client_credentials&scope=scope1%2Cscope2")).
|
||||||
|
Reply(200).
|
||||||
|
JSON(map[string]interface{}{
|
||||||
|
"access_token": "mocked-token-1",
|
||||||
|
"token_type": "Bearer",
|
||||||
|
"expires_in": 86400,
|
||||||
|
})
|
||||||
|
|
||||||
|
gock.New("https://account.ely.by").
|
||||||
|
Get("/api/internal/accounts/info").
|
||||||
|
MatchParam("id", "1").
|
||||||
|
MatchHeader("Authorization", "Bearer mocked-token-1").
|
||||||
|
Reply(200).
|
||||||
|
JSON(map[string]interface{}{
|
||||||
|
"id": 1,
|
||||||
|
"uuid": "0f657aa8-bfbe-415d-b700-5750090d3af3",
|
||||||
|
"username": "dummy",
|
||||||
|
"email": "dummy@ely.by",
|
||||||
|
})
|
||||||
|
|
||||||
|
gock.New("https://account.ely.by").
|
||||||
|
Get("/api/internal/accounts/info").
|
||||||
|
MatchParam("id", "1").
|
||||||
|
MatchHeader("Authorization", "Bearer mocked-token-1").
|
||||||
|
Reply(401).
|
||||||
|
JSON(map[string]interface{}{
|
||||||
|
"name": "Unauthorized",
|
||||||
|
"message": "Incorrect token",
|
||||||
|
"code": 0,
|
||||||
|
"status": 401,
|
||||||
|
})
|
||||||
|
|
||||||
|
gock.New("https://account.ely.by").
|
||||||
|
Post("/api/oauth2/v1/token").
|
||||||
|
Body(strings.NewReader("client_id=mock-id&client_secret=mock-secret&grant_type=client_credentials&scope=scope1%2Cscope2")).
|
||||||
|
Reply(200).
|
||||||
|
JSON(map[string]interface{}{
|
||||||
|
"access_token": "mocked-token-2",
|
||||||
|
"token_type": "Bearer",
|
||||||
|
"expires_in": 86400,
|
||||||
|
})
|
||||||
|
|
||||||
|
gock.New("https://account.ely.by").
|
||||||
|
Get("/api/internal/accounts/info").
|
||||||
|
MatchParam("id", "1").
|
||||||
|
MatchHeader("Authorization", "Bearer mocked-token-2").
|
||||||
|
Reply(200).
|
||||||
|
JSON(map[string]interface{}{
|
||||||
|
"id": 1,
|
||||||
|
"uuid": "0f657aa8-bfbe-415d-b700-5750090d3af3",
|
||||||
|
"username": "dummy",
|
||||||
|
"email": "dummy@ely.by",
|
||||||
|
})
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
gock.InterceptClient(client)
|
||||||
|
|
||||||
|
testConfig := &Config{}
|
||||||
|
*testConfig = *config
|
||||||
|
testConfig.Client = client
|
||||||
|
|
||||||
|
autoRefresher := testConfig.GetTokenWithAutoRefresh()
|
||||||
|
result, err := autoRefresher.AccountInfo("id", "1")
|
||||||
|
if assert.NoError(err) {
|
||||||
|
assert.Equal(1, result.Id)
|
||||||
|
assert.Equal("0f657aa8-bfbe-415d-b700-5750090d3af3", result.Uuid)
|
||||||
|
assert.Equal("dummy", result.Username)
|
||||||
|
assert.Equal("dummy@ely.by", result.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
result2, err2 := autoRefresher.AccountInfo("id", "1")
|
||||||
|
if assert.NoError(err2) {
|
||||||
|
assert.Equal(result, result2, "Results should still be same with refreshed token")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAutoRefresh_AccountInfo3(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
defer gock.Off()
|
||||||
|
gock.New("https://account.ely.by").
|
||||||
|
Post("/api/oauth2/v1/token").
|
||||||
|
Body(strings.NewReader("client_id=mock-id&client_secret=mock-secret&grant_type=client_credentials&scope=scope1%2Cscope2")).
|
||||||
|
Reply(200).
|
||||||
|
JSON(map[string]interface{}{
|
||||||
|
"access_token": "mocked-token-1",
|
||||||
|
"token_type": "Bearer",
|
||||||
|
"expires_in": 86400,
|
||||||
|
})
|
||||||
|
|
||||||
|
gock.New("https://account.ely.by").
|
||||||
|
Get("/api/internal/accounts/info").
|
||||||
|
MatchParam("id", "1").
|
||||||
|
MatchHeader("Authorization", "Bearer mocked-token-1").
|
||||||
|
Reply(404).
|
||||||
|
JSON(map[string]interface{}{
|
||||||
|
"name": "Not Found",
|
||||||
|
"message": "Page not found.",
|
||||||
|
"code": 0,
|
||||||
|
"status": 404,
|
||||||
|
})
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
gock.InterceptClient(client)
|
||||||
|
|
||||||
|
testConfig := &Config{}
|
||||||
|
*testConfig = *config
|
||||||
|
testConfig.Client = client
|
||||||
|
|
||||||
|
autoRefresher := testConfig.GetTokenWithAutoRefresh()
|
||||||
|
result, err := autoRefresher.AccountInfo("id", "1")
|
||||||
|
assert.Nil(result)
|
||||||
|
assert.Error(err)
|
||||||
|
assert.IsType(&NotFoundResponse{}, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAutoRefresh_AccountInfo4(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
defer gock.Off()
|
||||||
|
gock.New("https://account.ely.by").
|
||||||
|
Post("/api/oauth2/v1/token").
|
||||||
|
Times(3).
|
||||||
|
Body(strings.NewReader("client_id=mock-id&client_secret=mock-secret&grant_type=client_credentials&scope=scope1%2Cscope2")).
|
||||||
|
Reply(200).
|
||||||
|
JSON(map[string]interface{}{
|
||||||
|
"access_token": "mocked-token-1",
|
||||||
|
"token_type": "Bearer",
|
||||||
|
"expires_in": 86400,
|
||||||
|
})
|
||||||
|
|
||||||
|
gock.New("https://account.ely.by").
|
||||||
|
Get("/api/internal/accounts/info").
|
||||||
|
Times(3).
|
||||||
|
MatchParam("id", "1").
|
||||||
|
MatchHeader("Authorization", "Bearer mocked-token-1").
|
||||||
|
Reply(401).
|
||||||
|
JSON(map[string]interface{}{
|
||||||
|
"name": "Unauthorized",
|
||||||
|
"message": "Incorrect token",
|
||||||
|
"code": 0,
|
||||||
|
"status": 401,
|
||||||
|
})
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
gock.InterceptClient(client)
|
||||||
|
|
||||||
|
testConfig := &Config{}
|
||||||
|
*testConfig = *config
|
||||||
|
testConfig.Client = client
|
||||||
|
|
||||||
|
autoRefresher := testConfig.GetTokenWithAutoRefresh()
|
||||||
|
result, err := autoRefresher.AccountInfo("id", "1")
|
||||||
|
assert.Nil(result)
|
||||||
|
assert.Error(err)
|
||||||
|
if !assert.IsType(&UnauthorizedResponse{}, err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
"elyby/minecraft-skinsystem/api/accounts"
|
||||||
"elyby/minecraft-skinsystem/bootstrap"
|
"elyby/minecraft-skinsystem/bootstrap"
|
||||||
"elyby/minecraft-skinsystem/db"
|
"elyby/minecraft-skinsystem/db"
|
||||||
"elyby/minecraft-skinsystem/worker"
|
"elyby/minecraft-skinsystem/worker"
|
||||||
@ -46,10 +47,18 @@ var amqpWorkerCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
logger.Info("AMQP connection successfully initialized")
|
logger.Info("AMQP connection successfully initialized")
|
||||||
|
|
||||||
|
accountsApi := (&accounts.Config{
|
||||||
|
Addr: viper.GetString("api.accounts.host"),
|
||||||
|
Id: viper.GetString("api.accounts.id"),
|
||||||
|
Secret: viper.GetString("api.accounts.secret"),
|
||||||
|
Scopes: viper.GetStringSlice("api.accounts.scopes"),
|
||||||
|
}).GetTokenWithAutoRefresh()
|
||||||
|
|
||||||
services := &worker.Services{
|
services := &worker.Services{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
Channel: amqpChannel,
|
Channel: amqpChannel,
|
||||||
SkinsRepo: skinsRepo,
|
SkinsRepo: skinsRepo,
|
||||||
|
AccountsAPI: accountsApi,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := services.Run(); err != nil {
|
if err := services.Run(); err != nil {
|
||||||
|
@ -2,17 +2,20 @@ package worker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/mono83/slf/wd"
|
"github.com/mono83/slf/wd"
|
||||||
"github.com/streadway/amqp"
|
"github.com/streadway/amqp"
|
||||||
|
|
||||||
"elyby/minecraft-skinsystem/model"
|
|
||||||
"elyby/minecraft-skinsystem/interfaces"
|
"elyby/minecraft-skinsystem/interfaces"
|
||||||
|
"elyby/minecraft-skinsystem/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Services struct {
|
type Services struct {
|
||||||
Channel *amqp.Channel
|
Channel *amqp.Channel
|
||||||
SkinsRepo interfaces.SkinsRepository
|
SkinsRepo interfaces.SkinsRepository
|
||||||
|
AccountsAPI interfaces.AccountsAPI
|
||||||
Logger wd.Watchdog
|
Logger wd.Watchdog
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,24 +71,21 @@ func (service *Services) HandleChangeUsername(event *model.UsernameChanged) bool
|
|||||||
|
|
||||||
record, err := service.SkinsRepo.FindByUserId(event.AccountId)
|
record, err := service.SkinsRepo.FindByUserId(event.AccountId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
/*
|
|
||||||
// TODO: вернуть логику восстановления информации об аккаунте
|
|
||||||
service.Logger.IncCounter("worker.change_username.id_not_found", 1)
|
service.Logger.IncCounter("worker.change_username.id_not_found", 1)
|
||||||
service.Logger.Warning("Cannot find user id. Trying to search.")
|
service.Logger.Warning("Cannot find user id. Trying to search.")
|
||||||
response, err := getById(event.AccountId)
|
response, err := service.AccountsAPI.AccountInfo("id", strconv.Itoa(event.AccountId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
service.Logger.IncCounter("worker.change_username.id_not_restored", 1)
|
service.Logger.IncCounter("worker.change_username.id_not_restored", 1)
|
||||||
service.Logger.Error("Cannot restore user info. %T\n", err)
|
service.Logger.Error(fmt.Sprintf("Cannot restore user info. %+v\n", err))
|
||||||
// TODO: логгировать в какой-нибудь Sentry, если там не 404
|
// TODO: логгировать в какой-нибудь Sentry, если там не 404
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
service.Logger.IncCounter("worker.change_username.id_restored", 1)
|
service.Logger.IncCounter("worker.change_username.id_restored", 1)
|
||||||
fmt.Println("User info successfully restored.")
|
fmt.Println("User info successfully restored.")
|
||||||
record = &event.Skin{
|
record = &model.Skin{
|
||||||
UserId: response.Id,
|
UserId: response.Id,
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
record.Username = event.NewUsername
|
record.Username = event.NewUsername
|
||||||
@ -101,21 +101,19 @@ func (service *Services) HandleSkinChanged(event *model.SkinChanged) bool {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
service.Logger.IncCounter("worker.skin_changed.id_not_found", 1)
|
service.Logger.IncCounter("worker.skin_changed.id_not_found", 1)
|
||||||
service.Logger.Warning("Cannot find user id. Trying to search.")
|
service.Logger.Warning("Cannot find user id. Trying to search.")
|
||||||
/*
|
response, err := service.AccountsAPI.AccountInfo("id", strconv.Itoa(event.AccountId))
|
||||||
// TODO: вернуть логику восстановления информации об аккаунте
|
|
||||||
response, err := getById(event.AccountId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
services.Logger.IncCounter("worker.skin_changed.id_not_restored", 1)
|
service.Logger.IncCounter("worker.skin_changed.id_not_restored", 1)
|
||||||
fmt.Printf("Cannot restore user info. %T\n", err)
|
service.Logger.Error(fmt.Sprintf("Cannot restore user info. %+v\n", err))
|
||||||
// TODO: логгировать в какой-нибудь Sentry, если там не 404
|
// TODO: логгировать в какой-нибудь Sentry, если там не 404
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
services.Logger.IncCounter("worker.skin_changed.id_restored", 1)
|
service.Logger.IncCounter("worker.skin_changed.id_restored", 1)
|
||||||
fmt.Println("User info successfully restored.")
|
service.Logger.Info("User info successfully restored.")
|
||||||
|
|
||||||
record.UserId = response.Id
|
record.UserId = response.Id
|
||||||
record.Username = response.Username
|
record.Username = response.Username
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
record.Uuid = event.Uuid
|
record.Uuid = event.Uuid
|
||||||
|
Loading…
Reference in New Issue
Block a user