added OAuth2 displayName and pkce options

This commit is contained in:
Gani Georgiev
2023-11-29 20:19:54 +02:00
parent 995733000f
commit b283ee2263
65 changed files with 421 additions and 226 deletions
+6 -4
View File
@@ -36,10 +36,12 @@ type Apple struct {
func NewAppleProvider() *Apple {
return &Apple{
baseProvider: &baseProvider{
scopes: nil, // custom scopes are currently not supported since they require a POST redirect
ctx: context.Background(),
authUrl: "https://appleid.apple.com/auth/authorize",
tokenUrl: "https://appleid.apple.com/auth/token",
ctx: context.Background(),
displayName: "Apple",
pkce: true,
scopes: nil, // custom scopes are currently not supported since they require a POST redirect
authUrl: "https://appleid.apple.com/auth/authorize",
tokenUrl: "https://appleid.apple.com/auth/token",
},
jwksUrl: "https://appleid.apple.com/auth/keys",
}
+14 -1
View File
@@ -16,10 +16,10 @@ type AuthUser struct {
Username string `json:"username"`
Email string `json:"email"`
AvatarUrl string `json:"avatarUrl"`
RawUser map[string]any `json:"rawUser"`
AccessToken string `json:"accessToken"`
RefreshToken string `json:"refreshToken"`
Expiry types.DateTime `json:"expiry"`
RawUser map[string]any `json:"rawUser"`
}
// Provider defines a common interface for an OAuth2 client.
@@ -30,6 +30,19 @@ type Provider interface {
// SetContext assigns the specified context to the current provider.
SetContext(ctx context.Context)
// PKCE indicates whether the provider can use the PKCE flow.
PKCE() bool
// SetPKCE toggles the state whether the provider can use the PKCE flow or not.
SetPKCE(enable bool)
// DisplayName usually returns provider name as it is officially written
// and it could be used directly in the UI.
DisplayName() string
// SetDisplayName sets the provider's display name.
SetDisplayName(displayName string)
// Scopes returns the provider access permissions that will be requested.
Scopes() []string
+23 -1
View File
@@ -12,13 +12,15 @@ import (
// baseProvider defines common fields and methods used by OAuth2 client providers.
type baseProvider struct {
ctx context.Context
scopes []string
clientId string
clientSecret string
displayName string
redirectUrl string
authUrl string
tokenUrl string
userApiUrl string
scopes []string
pkce bool
}
// Context implements Provider.Context() interface method.
@@ -31,6 +33,26 @@ func (p *baseProvider) SetContext(ctx context.Context) {
p.ctx = ctx
}
// PKCE implements Provider.PKCE() interface method.
func (p *baseProvider) PKCE() bool {
return p.pkce
}
// SetPKCE implements Provider.SetPKCE() interface method.
func (p *baseProvider) SetPKCE(enable bool) {
p.pkce = enable
}
// DisplayName implements Provider.DisplayName() interface method.
func (p *baseProvider) DisplayName() string {
return p.displayName
}
// SetDisplayName implements Provider.SetDisplayName() interface method.
func (p *baseProvider) SetDisplayName(displayName string) {
p.displayName = displayName
}
// Scopes implements Provider.Scopes() interface method.
func (p *baseProvider) Scopes() []string {
return p.scopes
+46 -14
View File
@@ -23,19 +23,51 @@ func TestContext(t *testing.T) {
}
}
func TestDisplayName(t *testing.T) {
b := baseProvider{}
before := b.DisplayName()
if before != "" {
t.Fatalf("Expected displayName to be empty, got %v", before)
}
b.SetDisplayName("test")
after := b.DisplayName()
if after != "test" {
t.Fatalf("Expected displayName to be 'test', got %v", after)
}
}
func TestPKCE(t *testing.T) {
b := baseProvider{}
before := b.PKCE()
if before != false {
t.Fatalf("Expected pkce to be %v, got %v", false, before)
}
b.SetPKCE(true)
after := b.PKCE()
if after != true {
t.Fatalf("Expected pkce to be %v, got %v", true, after)
}
}
func TestScopes(t *testing.T) {
b := baseProvider{}
before := b.Scopes()
if len(before) != 0 {
t.Errorf("Expected 0 scopes, got %v", before)
t.Fatalf("Expected 0 scopes, got %v", before)
}
b.SetScopes([]string{"test1", "test2"})
after := b.Scopes()
if len(after) != 2 {
t.Errorf("Expected 2 scopes, got %v", after)
t.Fatalf("Expected 2 scopes, got %v", after)
}
}
@@ -44,14 +76,14 @@ func TestClientId(t *testing.T) {
before := b.ClientId()
if before != "" {
t.Errorf("Expected clientId to be empty, got %v", before)
t.Fatalf("Expected clientId to be empty, got %v", before)
}
b.SetClientId("test")
after := b.ClientId()
if after != "test" {
t.Errorf("Expected clientId to be 'test', got %v", after)
t.Fatalf("Expected clientId to be 'test', got %v", after)
}
}
@@ -60,14 +92,14 @@ func TestClientSecret(t *testing.T) {
before := b.ClientSecret()
if before != "" {
t.Errorf("Expected clientSecret to be empty, got %v", before)
t.Fatalf("Expected clientSecret to be empty, got %v", before)
}
b.SetClientSecret("test")
after := b.ClientSecret()
if after != "test" {
t.Errorf("Expected clientSecret to be 'test', got %v", after)
t.Fatalf("Expected clientSecret to be 'test', got %v", after)
}
}
@@ -76,14 +108,14 @@ func TestRedirectUrl(t *testing.T) {
before := b.RedirectUrl()
if before != "" {
t.Errorf("Expected RedirectUrl to be empty, got %v", before)
t.Fatalf("Expected RedirectUrl to be empty, got %v", before)
}
b.SetRedirectUrl("test")
after := b.RedirectUrl()
if after != "test" {
t.Errorf("Expected RedirectUrl to be 'test', got %v", after)
t.Fatalf("Expected RedirectUrl to be 'test', got %v", after)
}
}
@@ -92,14 +124,14 @@ func TestAuthUrl(t *testing.T) {
before := b.AuthUrl()
if before != "" {
t.Errorf("Expected authUrl to be empty, got %v", before)
t.Fatalf("Expected authUrl to be empty, got %v", before)
}
b.SetAuthUrl("test")
after := b.AuthUrl()
if after != "test" {
t.Errorf("Expected authUrl to be 'test', got %v", after)
t.Fatalf("Expected authUrl to be 'test', got %v", after)
}
}
@@ -108,14 +140,14 @@ func TestTokenUrl(t *testing.T) {
before := b.TokenUrl()
if before != "" {
t.Errorf("Expected tokenUrl to be empty, got %v", before)
t.Fatalf("Expected tokenUrl to be empty, got %v", before)
}
b.SetTokenUrl("test")
after := b.TokenUrl()
if after != "test" {
t.Errorf("Expected tokenUrl to be 'test', got %v", after)
t.Fatalf("Expected tokenUrl to be 'test', got %v", after)
}
}
@@ -124,14 +156,14 @@ func TestUserApiUrl(t *testing.T) {
before := b.UserApiUrl()
if before != "" {
t.Errorf("Expected userApiUrl to be empty, got %v", before)
t.Fatalf("Expected userApiUrl to be empty, got %v", before)
}
b.SetUserApiUrl("test")
after := b.UserApiUrl()
if after != "test" {
t.Errorf("Expected userApiUrl to be 'test', got %v", after)
t.Fatalf("Expected userApiUrl to be 'test', got %v", after)
}
}
+8 -6
View File
@@ -24,11 +24,13 @@ func NewDiscordProvider() *Discord {
// https://discord.com/developers/docs/topics/oauth2
// https://discord.com/developers/docs/resources/user#get-current-user
return &Discord{&baseProvider{
ctx: context.Background(),
scopes: []string{"identify", "email"},
authUrl: "https://discord.com/api/oauth2/authorize",
tokenUrl: "https://discord.com/api/oauth2/token",
userApiUrl: "https://discord.com/api/users/@me",
ctx: context.Background(),
displayName: "Discord",
pkce: true,
scopes: []string{"identify", "email"},
authUrl: "https://discord.com/api/oauth2/authorize",
tokenUrl: "https://discord.com/api/oauth2/token",
userApiUrl: "https://discord.com/api/users/@me",
}}
}
@@ -50,9 +52,9 @@ func (p *Discord) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
Id string `json:"id"`
Username string `json:"username"`
Discriminator string `json:"discriminator"`
Avatar string `json:"avatar"`
Email string `json:"email"`
Verified bool `json:"verified"`
Avatar string `json:"avatar"`
}{}
if err := json.Unmarshal(data, &extracted); err != nil {
return nil, err
+7 -5
View File
@@ -22,11 +22,13 @@ type Facebook struct {
// NewFacebookProvider creates new Facebook provider instance with some defaults.
func NewFacebookProvider() *Facebook {
return &Facebook{&baseProvider{
ctx: context.Background(),
scopes: []string{"email"},
authUrl: facebook.Endpoint.AuthURL,
tokenUrl: facebook.Endpoint.TokenURL,
userApiUrl: "https://graph.facebook.com/me?fields=name,email,picture.type(large)",
ctx: context.Background(),
displayName: "Facebook",
pkce: true,
scopes: []string{"email"},
authUrl: facebook.Endpoint.AuthURL,
tokenUrl: facebook.Endpoint.TokenURL,
userApiUrl: "https://graph.facebook.com/me?fields=name,email,picture.type(large)",
}}
}
+7 -5
View File
@@ -22,11 +22,13 @@ type Gitea struct {
// NewGiteaProvider creates new Gitea provider instance with some defaults.
func NewGiteaProvider() *Gitea {
return &Gitea{&baseProvider{
ctx: context.Background(),
scopes: []string{"read:user", "user:email"},
authUrl: "https://gitea.com/login/oauth/authorize",
tokenUrl: "https://gitea.com/login/oauth/access_token",
userApiUrl: "https://gitea.com/api/v1/user",
ctx: context.Background(),
displayName: "Gitea",
pkce: true,
scopes: []string{"read:user", "user:email"},
authUrl: "https://gitea.com/login/oauth/authorize",
tokenUrl: "https://gitea.com/login/oauth/access_token",
userApiUrl: "https://gitea.com/api/v1/user",
}}
}
+7 -5
View File
@@ -24,11 +24,13 @@ type Gitee struct {
// NewGiteeProvider creates new Gitee provider instance with some defaults.
func NewGiteeProvider() *Gitee {
return &Gitee{&baseProvider{
ctx: context.Background(),
scopes: []string{"user_info", "emails"},
authUrl: "https://gitee.com/oauth/authorize",
tokenUrl: "https://gitee.com/oauth/token",
userApiUrl: "https://gitee.com/api/v5/user",
ctx: context.Background(),
displayName: "Gitee",
pkce: true,
scopes: []string{"user_info", "emails"},
authUrl: "https://gitee.com/oauth/authorize",
tokenUrl: "https://gitee.com/oauth/token",
userApiUrl: "https://gitee.com/api/v5/user",
}}
}
+7 -5
View File
@@ -24,11 +24,13 @@ type Github struct {
// NewGithubProvider creates new Github provider instance with some defaults.
func NewGithubProvider() *Github {
return &Github{&baseProvider{
ctx: context.Background(),
scopes: []string{"read:user", "user:email"},
authUrl: github.Endpoint.AuthURL,
tokenUrl: github.Endpoint.TokenURL,
userApiUrl: "https://api.github.com/user",
ctx: context.Background(),
displayName: "GitHub",
pkce: true, // technically is not suppoted yet but it is safe as the PKCE params are just ignored
scopes: []string{"read:user", "user:email"},
authUrl: github.Endpoint.AuthURL,
tokenUrl: github.Endpoint.TokenURL,
userApiUrl: "https://api.github.com/user",
}}
}
+7 -5
View File
@@ -22,11 +22,13 @@ type Gitlab struct {
// NewGitlabProvider creates new Gitlab provider instance with some defaults.
func NewGitlabProvider() *Gitlab {
return &Gitlab{&baseProvider{
ctx: context.Background(),
scopes: []string{"read_user"},
authUrl: "https://gitlab.com/oauth/authorize",
tokenUrl: "https://gitlab.com/oauth/token",
userApiUrl: "https://gitlab.com/api/v4/user",
ctx: context.Background(),
displayName: "GitLab",
pkce: true,
scopes: []string{"read_user"},
authUrl: "https://gitlab.com/oauth/authorize",
tokenUrl: "https://gitlab.com/oauth/token",
userApiUrl: "https://gitlab.com/api/v4/user",
}}
}
+3 -1
View File
@@ -21,7 +21,9 @@ type Google struct {
// NewGoogleProvider creates new Google provider instance with some defaults.
func NewGoogleProvider() *Google {
return &Google{&baseProvider{
ctx: context.Background(),
ctx: context.Background(),
displayName: "Google",
pkce: true,
scopes: []string{
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email",
+7 -5
View File
@@ -22,11 +22,13 @@ type Instagram struct {
// NewInstagramProvider creates new Instagram provider instance with some defaults.
func NewInstagramProvider() *Instagram {
return &Instagram{&baseProvider{
ctx: context.Background(),
scopes: []string{"user_profile"},
authUrl: instagram.Endpoint.AuthURL,
tokenUrl: instagram.Endpoint.TokenURL,
userApiUrl: "https://graph.instagram.com/me?fields=id,username,account_type",
ctx: context.Background(),
displayName: "Instagram",
pkce: true,
scopes: []string{"user_profile"},
authUrl: instagram.Endpoint.AuthURL,
tokenUrl: instagram.Endpoint.TokenURL,
userApiUrl: "https://graph.instagram.com/me?fields=id,username,account_type",
}}
}
+7 -5
View File
@@ -23,11 +23,13 @@ type Kakao struct {
// NewKakaoProvider creates a new Kakao provider instance with some defaults.
func NewKakaoProvider() *Kakao {
return &Kakao{&baseProvider{
ctx: context.Background(),
scopes: []string{"account_email", "profile_nickname", "profile_image"},
authUrl: kakao.Endpoint.AuthURL,
tokenUrl: kakao.Endpoint.TokenURL,
userApiUrl: "https://kapi.kakao.com/v2/user/me",
ctx: context.Background(),
displayName: "Kakao",
pkce: true,
scopes: []string{"account_email", "profile_nickname", "profile_image"},
authUrl: kakao.Endpoint.AuthURL,
tokenUrl: kakao.Endpoint.TokenURL,
userApiUrl: "https://kapi.kakao.com/v2/user/me",
}}
}
+7 -5
View File
@@ -21,11 +21,13 @@ type Livechat struct {
// NewLivechatProvider creates new Livechat provider instance with some defaults.
func NewLivechatProvider() *Livechat {
return &Livechat{&baseProvider{
ctx: context.Background(),
scopes: []string{}, // default scopes are specified from the provider dashboard
authUrl: "https://accounts.livechat.com/",
tokenUrl: "https://accounts.livechat.com/token",
userApiUrl: "https://accounts.livechat.com/v2/accounts/me",
ctx: context.Background(),
displayName: "LiveChat",
pkce: true,
scopes: []string{}, // default scopes are specified from the provider dashboard
authUrl: "https://accounts.livechat.com/",
tokenUrl: "https://accounts.livechat.com/token",
userApiUrl: "https://accounts.livechat.com/v2/accounts/me",
}}
}
+4 -2
View File
@@ -23,8 +23,10 @@ type Mailcow struct {
// NewMailcowProvider creates a new mailcow provider instance with some defaults.
func NewMailcowProvider() *Mailcow {
return &Mailcow{&baseProvider{
ctx: context.Background(),
scopes: []string{"profile"},
ctx: context.Background(),
displayName: "mailcow",
pkce: true,
scopes: []string{"profile"},
}}
}
+7 -5
View File
@@ -23,11 +23,13 @@ type Microsoft struct {
func NewMicrosoftProvider() *Microsoft {
endpoints := microsoft.AzureADEndpoint("")
return &Microsoft{&baseProvider{
ctx: context.Background(),
scopes: []string{"User.Read"},
authUrl: endpoints.AuthURL,
tokenUrl: endpoints.TokenURL,
userApiUrl: "https://graph.microsoft.com/v1.0/me",
ctx: context.Background(),
displayName: "Microsoft",
pkce: true,
scopes: []string{"User.Read"},
authUrl: endpoints.AuthURL,
tokenUrl: endpoints.TokenURL,
userApiUrl: "https://graph.microsoft.com/v1.0/me",
}}
}
+3 -1
View File
@@ -21,7 +21,9 @@ type OIDC struct {
// NewOIDCProvider creates new OpenID Connect (OIDC) provider instance with some defaults.
func NewOIDCProvider() *OIDC {
return &OIDC{&baseProvider{
ctx: context.Background(),
ctx: context.Background(),
displayName: "OIDC",
pkce: true,
scopes: []string{
"openid", // minimal requirement to return the id
"email",
+7 -5
View File
@@ -21,11 +21,13 @@ type Patreon struct {
// NewPatreonProvider creates new Patreon provider instance with some defaults.
func NewPatreonProvider() *Patreon {
return &Patreon{&baseProvider{
ctx: context.Background(),
scopes: []string{"identity", "identity[email]"},
authUrl: "https://www.patreon.com/oauth2/authorize",
tokenUrl: "https://www.patreon.com/api/oauth2/token",
userApiUrl: "https://www.patreon.com/api/oauth2/v2/identity?fields%5Buser%5D=full_name,email,vanity,image_url,is_email_verified",
ctx: context.Background(),
displayName: "Patreon",
pkce: true,
scopes: []string{"identity", "identity[email]"},
authUrl: "https://www.patreon.com/oauth2/authorize",
tokenUrl: "https://www.patreon.com/api/oauth2/token",
userApiUrl: "https://www.patreon.com/api/oauth2/v2/identity?fields%5Buser%5D=full_name,email,vanity,image_url,is_email_verified",
}}
}
+3 -1
View File
@@ -22,7 +22,9 @@ type Spotify struct {
// NewSpotifyProvider creates a new Spotify provider instance with some defaults.
func NewSpotifyProvider() *Spotify {
return &Spotify{&baseProvider{
ctx: context.Background(),
ctx: context.Background(),
displayName: "Spotify",
pkce: true,
scopes: []string{
"user-read-private",
// currently Spotify doesn't return information whether the email is verified or not
+3 -1
View File
@@ -22,7 +22,9 @@ type Strava struct {
// NewStravaProvider creates new Strava provider instance with some defaults.
func NewStravaProvider() *Strava {
return &Strava{&baseProvider{
ctx: context.Background(),
ctx: context.Background(),
displayName: "Strava",
pkce: true,
scopes: []string{
"profile:read_all",
},
+7 -5
View File
@@ -24,11 +24,13 @@ type Twitch struct {
// NewTwitchProvider creates new Twitch provider instance with some defaults.
func NewTwitchProvider() *Twitch {
return &Twitch{&baseProvider{
ctx: context.Background(),
scopes: []string{"user:read:email"},
authUrl: twitch.Endpoint.AuthURL,
tokenUrl: twitch.Endpoint.TokenURL,
userApiUrl: "https://api.twitch.tv/helix/users",
ctx: context.Background(),
displayName: "Twitch",
pkce: true,
scopes: []string{"user:read:email"},
authUrl: twitch.Endpoint.AuthURL,
tokenUrl: twitch.Endpoint.TokenURL,
userApiUrl: "https://api.twitch.tv/helix/users",
}}
}
+3 -1
View File
@@ -21,7 +21,9 @@ type Twitter struct {
// NewTwitterProvider creates new Twitter provider instance with some defaults.
func NewTwitterProvider() *Twitter {
return &Twitter{&baseProvider{
ctx: context.Background(),
ctx: context.Background(),
displayName: "Twitter",
pkce: true,
scopes: []string{
"users.read",
+7 -5
View File
@@ -28,11 +28,13 @@ type VK struct {
// Docs: https://dev.vk.com/api/oauth-parameters
func NewVKProvider() *VK {
return &VK{&baseProvider{
ctx: context.Background(),
scopes: []string{"email"},
authUrl: vk.Endpoint.AuthURL,
tokenUrl: vk.Endpoint.TokenURL,
userApiUrl: "https://api.vk.com/method/users.get?fields=photo_max,screen_name&v=5.131",
ctx: context.Background(),
displayName: "ВКонтакте",
pkce: false, // VK currently doesn't support PKCE and throws an error if PKCE params are send
scopes: []string{"email"},
authUrl: vk.Endpoint.AuthURL,
tokenUrl: vk.Endpoint.TokenURL,
userApiUrl: "https://api.vk.com/method/users.get?fields=photo_max,screen_name&v=5.131",
}}
}
+7 -5
View File
@@ -24,11 +24,13 @@ type Yandex struct {
// Docs: https://yandex.ru/dev/id/doc/en/
func NewYandexProvider() *Yandex {
return &Yandex{&baseProvider{
ctx: context.Background(),
scopes: []string{"login:email", "login:avatar", "login:info"},
authUrl: yandex.Endpoint.AuthURL,
tokenUrl: yandex.Endpoint.TokenURL,
userApiUrl: "https://login.yandex.ru/info",
ctx: context.Background(),
displayName: "Yandex",
pkce: true,
scopes: []string{"login:email", "login:avatar", "login:info"},
authUrl: yandex.Endpoint.AuthURL,
tokenUrl: yandex.Endpoint.TokenURL,
userApiUrl: "https://login.yandex.ru/info",
}}
}