✨ Added a profile module to retrieve a profile from 3rd-party providers
* Added repository interface * Added GitHub provider * Added Mastodon provider
This commit is contained in:
parent
8f7dec0fb0
commit
1e7249b18e
|
@ -0,0 +1,13 @@
|
|||
package profile
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"source.toby3d.me/website/oauth/internal/domain"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
Get(ctx context.Context, token oauth2.Token) (*domain.Profile, error)
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
json "github.com/goccy/go-json"
|
||||
"github.com/pkg/errors"
|
||||
http "github.com/valyala/fasthttp"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"source.toby3d.me/website/oauth/internal/domain"
|
||||
"source.toby3d.me/website/oauth/internal/profile"
|
||||
)
|
||||
|
||||
type (
|
||||
//nolint: tagliatelle
|
||||
Response struct {
|
||||
Name string `json:"name"`
|
||||
Blog string `json:"blog"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
githubProfileRepository struct {
|
||||
request *http.Request
|
||||
client *http.Client
|
||||
}
|
||||
)
|
||||
|
||||
func NewGitHubProfileRepository(client *http.Client) profile.Repository {
|
||||
req := http.AcquireRequest()
|
||||
req.SetRequestURI("https://api.github.com/user")
|
||||
req.Header.SetMethod(http.MethodGet)
|
||||
req.Header.Set(http.HeaderAccept, "application/vnd.github.v3+json")
|
||||
|
||||
return &githubProfileRepository{
|
||||
request: req,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (repo *githubProfileRepository) Get(ctx context.Context, token oauth2.Token) (*domain.Profile, error) {
|
||||
req := http.AcquireRequest()
|
||||
defer http.ReleaseRequest(req)
|
||||
repo.request.CopyTo(req)
|
||||
req.Header.Set(http.HeaderAuthorization, token.TokenType+" "+token.AccessToken)
|
||||
|
||||
resp := http.AcquireResponse()
|
||||
defer http.ReleaseResponse(resp)
|
||||
|
||||
if err := repo.client.Do(req, resp); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to fetch authenticated user")
|
||||
}
|
||||
|
||||
result := new(Response)
|
||||
if err := json.Unmarshal(resp.Body(), result); err != nil {
|
||||
return nil, errors.Wrap(err, "cannot unmarshal GitHub response")
|
||||
}
|
||||
|
||||
return &domain.Profile{
|
||||
Name: result.Name,
|
||||
URL: result.Blog,
|
||||
Photo: result.AvatarURL,
|
||||
Email: result.Email,
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package github_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
json "github.com/goccy/go-json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
http "github.com/valyala/fasthttp"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"source.toby3d.me/website/oauth/internal/common"
|
||||
"source.toby3d.me/website/oauth/internal/domain"
|
||||
"source.toby3d.me/website/oauth/internal/profile/repository/github"
|
||||
"source.toby3d.me/website/oauth/internal/util"
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := domain.TestProfile(t)
|
||||
client, _, cleanup := util.TestServe(t, func(ctx *http.RequestCtx) {
|
||||
ctx.SetStatusCode(http.StatusOK)
|
||||
ctx.SetContentType(common.MIMEApplicationJSON)
|
||||
_ = json.NewEncoder(ctx).Encode(&github.Response{
|
||||
Name: p.Name,
|
||||
Blog: p.URL,
|
||||
AvatarURL: p.Photo,
|
||||
Email: p.Email,
|
||||
})
|
||||
})
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
result, err := github.NewGitHubProfileRepository(client).Get(context.TODO(), oauth2.Token{
|
||||
AccessToken: "hackme",
|
||||
TokenType: "Bearer",
|
||||
RefreshToken: "",
|
||||
Expiry: time.Time{},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, p, result)
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package mastodon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path"
|
||||
|
||||
json "github.com/goccy/go-json"
|
||||
"github.com/pkg/errors"
|
||||
http "github.com/valyala/fasthttp"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"source.toby3d.me/website/oauth/internal/common"
|
||||
"source.toby3d.me/website/oauth/internal/domain"
|
||||
"source.toby3d.me/website/oauth/internal/profile"
|
||||
)
|
||||
|
||||
type (
|
||||
Response struct {
|
||||
DisplayName string `json:"display_name"`
|
||||
Avatar string `json:"avatar"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
mastodonProfileRepository struct {
|
||||
request *http.Request
|
||||
client *http.Client
|
||||
}
|
||||
)
|
||||
|
||||
func NewMastodonProfileRepository(client *http.Client, baseURL string) profile.Repository {
|
||||
req := http.AcquireRequest()
|
||||
req.SetRequestURI(baseURL)
|
||||
req.URI().SetPath(path.Join("api", "v1", "accounts", "verify_credentials"))
|
||||
req.Header.SetMethod(http.MethodGet)
|
||||
req.Header.Set(http.HeaderAccept, common.MIMEApplicationJSON)
|
||||
|
||||
return &mastodonProfileRepository{
|
||||
request: req,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (repo *mastodonProfileRepository) Get(ctx context.Context, token oauth2.Token) (*domain.Profile, error) {
|
||||
req := http.AcquireRequest()
|
||||
defer http.ReleaseRequest(req)
|
||||
repo.request.CopyTo(req)
|
||||
req.Header.Set(http.HeaderAuthorization, token.TokenType+" "+token.AccessToken)
|
||||
|
||||
resp := http.AcquireResponse()
|
||||
defer http.ReleaseResponse(resp)
|
||||
|
||||
if err := repo.client.Do(req, resp); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to fetch authenticated user")
|
||||
}
|
||||
|
||||
result := new(Response)
|
||||
if err := json.Unmarshal(resp.Body(), result); err != nil {
|
||||
return nil, errors.Wrap(err, "cannot unmarshal GitHub response")
|
||||
}
|
||||
|
||||
return &domain.Profile{
|
||||
Name: result.DisplayName,
|
||||
URL: result.URL,
|
||||
Photo: result.Avatar,
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package mastodon_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
json "github.com/goccy/go-json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
http "github.com/valyala/fasthttp"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"source.toby3d.me/website/oauth/internal/common"
|
||||
"source.toby3d.me/website/oauth/internal/domain"
|
||||
"source.toby3d.me/website/oauth/internal/profile/repository/mastodon"
|
||||
"source.toby3d.me/website/oauth/internal/util"
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := domain.TestProfile(t)
|
||||
p.Email = "" // WARN(toby3d): Mastodon does not provide user email information
|
||||
|
||||
client, _, cleanup := util.TestServe(t, func(ctx *http.RequestCtx) {
|
||||
ctx.SetStatusCode(http.StatusOK)
|
||||
ctx.SetContentType(common.MIMEApplicationJSON)
|
||||
_ = json.NewEncoder(ctx).Encode(&mastodon.Response{
|
||||
DisplayName: p.Name,
|
||||
Avatar: p.Photo,
|
||||
URL: p.URL,
|
||||
})
|
||||
})
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
result, err := mastodon.NewMastodonProfileRepository(client, "https://mstdn.io/").
|
||||
Get(context.TODO(), oauth2.Token{
|
||||
AccessToken: "hackme",
|
||||
TokenType: "Bearer",
|
||||
RefreshToken: "",
|
||||
Expiry: time.Time{},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, p, result)
|
||||
}
|
Loading…
Reference in New Issue