Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Vercel marketplace OIDC #1731

Merged
merged 5 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions internal/api/external.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,8 @@ func (a *API) Provider(ctx context.Context, name string, scopes string) (provide
return provider.NewTwitchProvider(config.External.Twitch, scopes)
case "twitter":
return provider.NewTwitterProvider(config.External.Twitter, scopes)
case "vercel_marketplace":
return provider.NewVercelMarketplaceProvider(config.External.VercelMarketplace, scopes)
case "workos":
return provider.NewWorkOSProvider(config.External.WorkOS)
case "zoom":
Expand Down
36 changes: 36 additions & 0 deletions internal/api/provider/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ func ParseIDToken(ctx context.Context, provider *oidc.Provider, config *oidc.Con
token, data, err = parseLinkedinIDToken(token)
case IssuerKakao:
token, data, err = parseKakaoIDToken(token)
case IssuerVercelMarketplace:
token, data, err = parseVercelMarketplaceIDToken(token)
default:
if IsAzureIssuer(token.Issuer) {
token, data, err = parseAzureIDToken(token)
Expand Down Expand Up @@ -351,6 +353,40 @@ func parseKakaoIDToken(token *oidc.IDToken) (*oidc.IDToken, *UserProvidedData, e
return token, &data, nil
}

type VercelMarketplaceIDTokenClaims struct {
jwt.RegisteredClaims

UserEmail string `json:"user_email"`
UserName string `json:"user_name"`
UserAvatarUrl string `json:"user_avatar_url"`
}

func parseVercelMarketplaceIDToken(token *oidc.IDToken) (*oidc.IDToken, *UserProvidedData, error) {
var claims VercelMarketplaceIDTokenClaims

if err := token.Claims(&claims); err != nil {
return nil, nil, err
}

var data UserProvidedData

data.Emails = append(data.Emails, Email{
Email: claims.UserEmail,
Verified: true,
Primary: true,
})

data.Metadata = &Claims{
Issuer: token.Issuer,
Subject: token.Subject,
ProviderId: token.Subject,
Name: claims.UserName,
Picture: claims.UserAvatarUrl,
}

return token, &data, nil
}

func parseGenericIDToken(token *oidc.IDToken) (*oidc.IDToken, *UserProvidedData, error) {
var data UserProvidedData

Expand Down
78 changes: 78 additions & 0 deletions internal/api/provider/vercel_marketplace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package provider

import (
"context"
"errors"
"strings"

"github.com/coreos/go-oidc/v3/oidc"
"github.com/supabase/auth/internal/conf"
"golang.org/x/oauth2"
)

const (
defaultVercelMarketplaceAPIBase = "api.vercel.com"
IssuerVercelMarketplace = "https://marketplace.vercel.com"
)

type vercelMarketplaceProvider struct {
*oauth2.Config
oidc *oidc.Provider
APIPath string
}

// NewVercelMarketplaceProvider creates a VercelMarketplace account provider via OIDC.
func NewVercelMarketplaceProvider(ext conf.OAuthProviderConfiguration, scopes string) (OAuthProvider, error) {
if err := ext.ValidateOAuth(); err != nil {
return nil, err
}

apiPath := chooseHost(ext.URL, defaultVercelMarketplaceAPIBase)

oauthScopes := []string{}

if scopes != "" {
oauthScopes = append(oauthScopes, strings.Split(scopes, ",")...)
}

oidcProvider, err := oidc.NewProvider(context.Background(), IssuerVercelMarketplace)
if err != nil {
return nil, err
}

return &vercelMarketplaceProvider{
oidc: oidcProvider,
Config: &oauth2.Config{
ClientID: ext.ClientID[0],
ClientSecret: ext.Secret,
Endpoint: oauth2.Endpoint{
AuthURL: apiPath + "/oauth/v2/authorization",
TokenURL: apiPath + "/oauth/v2/accessToken",
},
Scopes: oauthScopes,
RedirectURL: ext.RedirectURI,
},
APIPath: apiPath,
}, nil
}

func (g vercelMarketplaceProvider) GetOAuthToken(code string) (*oauth2.Token, error) {
return g.Exchange(context.Background(), code)
}

func (g vercelMarketplaceProvider) GetUserData(ctx context.Context, tok *oauth2.Token) (*UserProvidedData, error) {
idToken := tok.Extra("id_token")
if tok.AccessToken == "" || idToken == nil {
return nil, errors.New("vercel_marketplace: no OIDC ID token present in response")
}

_, data, err := ParseIDToken(ctx, g.oidc, &oidc.Config{
ClientID: g.ClientID,
}, idToken.(string), ParseIDTokenOptions{
AccessToken: tok.AccessToken,
})
if err != nil {
return nil, err
}
return data, nil
}
6 changes: 6 additions & 0 deletions internal/api/token_oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ func (p *IdTokenGrantParams) getProvider(ctx context.Context, config *conf.Globa
issuer = provider.IssuerKakao
acceptableClientIDs = append(acceptableClientIDs, config.External.Kakao.ClientID...)

case p.Provider == "vercel_marketplace" || p.Issuer == provider.IssuerVercelMarketplace:
cfg = &config.External.VercelMarketplace
providerType = "vercel_marketplace"
issuer = provider.IssuerVercelMarketplace
acceptableClientIDs = append(acceptableClientIDs, config.External.VercelMarketplace.ClientID...)

default:
log.WithField("issuer", p.Issuer).WithField("client_id", p.ClientID).Warn("Use of POST /token with arbitrary issuer and client_id is deprecated for security reasons. Please switch to using the API with provider only!")

Expand Down
1 change: 1 addition & 0 deletions internal/conf/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ type ProviderConfiguration struct {
SlackOIDC OAuthProviderConfiguration `json:"slack_oidc" envconfig:"SLACK_OIDC"`
Twitter OAuthProviderConfiguration `json:"twitter"`
Twitch OAuthProviderConfiguration `json:"twitch"`
VercelMarketplace OAuthProviderConfiguration `json:"vercel_marketplace" split_words:"true"`
WorkOS OAuthProviderConfiguration `json:"workos"`
Email EmailProviderConfiguration `json:"email"`
Phone PhoneProviderConfiguration `json:"phone"`
Expand Down
Loading