mirror of
https://github.com/tinyauthapp/tinyauth.git
synced 2026-08-31 18:43:31 +08:00
feat: support for custom claims in oauth (#1087)
This commit is contained in:
@@ -171,6 +171,14 @@ TINYAUTH_OAUTH_PROVIDERS_name_USERINFOURL=
|
|||||||
TINYAUTH_OAUTH_PROVIDERS_name_INSECURE=false
|
TINYAUTH_OAUTH_PROVIDERS_name_INSECURE=false
|
||||||
# Provider name in UI.
|
# Provider name in UI.
|
||||||
TINYAUTH_OAUTH_PROVIDERS_name_NAME=
|
TINYAUTH_OAUTH_PROVIDERS_name_NAME=
|
||||||
|
# Username claim.
|
||||||
|
TINYAUTH_OAUTH_PROVIDERS_name_CLAIMS_USERNAME=
|
||||||
|
# Email claim.
|
||||||
|
TINYAUTH_OAUTH_PROVIDERS_name_CLAIMS_EMAIL=
|
||||||
|
# Name claim.
|
||||||
|
TINYAUTH_OAUTH_PROVIDERS_name_CLAIMS_NAME=
|
||||||
|
# Groups claim.
|
||||||
|
TINYAUTH_OAUTH_PROVIDERS_name_CLAIMS_GROUPS=
|
||||||
|
|
||||||
# oidc config
|
# oidc config
|
||||||
|
|
||||||
|
|||||||
+20
-12
@@ -253,18 +253,26 @@ type TailscaleConfig struct {
|
|||||||
// OAuth/OIDC config
|
// OAuth/OIDC config
|
||||||
|
|
||||||
type OAuthServiceConfig struct {
|
type OAuthServiceConfig struct {
|
||||||
ClientID string `description:"OAuth client ID." yaml:"clientId,omitempty"`
|
ClientID string `description:"OAuth client ID." yaml:"clientId,omitempty"`
|
||||||
ClientSecret string `description:"OAuth client secret." yaml:"clientSecret,omitempty"`
|
ClientSecret string `description:"OAuth client secret." yaml:"clientSecret,omitempty"`
|
||||||
ClientSecretFile string `description:"Path to the file containing the OAuth client secret." yaml:"clientSecretFile,omitempty"`
|
ClientSecretFile string `description:"Path to the file containing the OAuth client secret." yaml:"clientSecretFile,omitempty"`
|
||||||
Whitelist []string `description:"Comma-separated list of allowed OAuth domains for this provider." yaml:"whitelist,omitempty"`
|
Whitelist []string `description:"Comma-separated list of allowed OAuth domains for this provider." yaml:"whitelist,omitempty"`
|
||||||
WhitelistFile string `description:"Path to the OAuth whitelist file for this provider." yaml:"whitelistFile,omitempty"`
|
WhitelistFile string `description:"Path to the OAuth whitelist file for this provider." yaml:"whitelistFile,omitempty"`
|
||||||
Scopes []string `description:"OAuth scopes." yaml:"scopes,omitempty"`
|
Scopes []string `description:"OAuth scopes." yaml:"scopes,omitempty"`
|
||||||
RedirectURL string `description:"OAuth redirect URL." yaml:"redirectUrl,omitempty"`
|
RedirectURL string `description:"OAuth redirect URL." yaml:"redirectUrl,omitempty"`
|
||||||
AuthURL string `description:"OAuth authorization URL." yaml:"authUrl,omitempty"`
|
AuthURL string `description:"OAuth authorization URL." yaml:"authUrl,omitempty"`
|
||||||
TokenURL string `description:"OAuth token URL." yaml:"tokenUrl,omitempty"`
|
TokenURL string `description:"OAuth token URL." yaml:"tokenUrl,omitempty"`
|
||||||
UserinfoURL string `description:"OAuth userinfo URL." yaml:"userinfoUrl,omitempty"`
|
UserinfoURL string `description:"OAuth userinfo URL." yaml:"userinfoUrl,omitempty"`
|
||||||
Insecure bool `description:"Allow insecure OAuth connections." yaml:"insecure,omitempty"`
|
Insecure bool `description:"Allow insecure OAuth connections." yaml:"insecure,omitempty"`
|
||||||
Name string `description:"Provider name in UI." yaml:"name,omitempty"`
|
Name string `description:"Provider name in UI." yaml:"name,omitempty"`
|
||||||
|
Claims OAuthServiceClaimsMap `description:"Map of claims to extract from the userinfo response." yaml:"claims,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OAuthServiceClaimsMap struct {
|
||||||
|
Username string `description:"Username claim." yaml:"username,omitempty"`
|
||||||
|
Email string `description:"Email claim." yaml:"email,omitempty"`
|
||||||
|
Name string `description:"Name claim." yaml:"name,omitempty"`
|
||||||
|
Groups string `description:"Groups claim." yaml:"groups,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OIDCClientConfig struct {
|
type OIDCClientConfig struct {
|
||||||
|
|||||||
@@ -21,11 +21,15 @@ type GithubUserinfoResponse struct {
|
|||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultExtractor(client *http.Client, ctx context.Context, url string) (*model.Claims, error) {
|
func defaultExtractor(client *http.Client, ctx context.Context, url string, mapClaims MapClaims) (*model.Claims, error) {
|
||||||
return simpleReq[model.Claims](client, ctx, url, nil)
|
claims, err := simpleReq[map[string]any](client, ctx, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return new(mapClaims(*claims)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func githubExtractor(client *http.Client, ctx context.Context, _ string) (*model.Claims, error) {
|
func githubExtractor(client *http.Client, ctx context.Context, _ string, _ MapClaims) (*model.Claims, error) {
|
||||||
var user model.Claims
|
var user model.Claims
|
||||||
|
|
||||||
userInfo, err := simpleReq[GithubUserinfoResponse](client, ctx, "https://api.github.com/user", map[string]string{
|
userInfo, err := simpleReq[GithubUserinfoResponse](client, ctx, "https://api.github.com/user", map[string]string{
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import (
|
|||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OAuthUserinfoExtractor func(client *http.Client, ctx context.Context, url string) (*model.Claims, error)
|
type MapClaims func(claims map[string]any) model.Claims
|
||||||
|
type OAuthUserinfoExtractor func(client *http.Client, ctx context.Context, url string, mapClaims MapClaims) (*model.Claims, error)
|
||||||
|
|
||||||
type OAuthService struct {
|
type OAuthService struct {
|
||||||
serviceCfg model.OAuthServiceConfig
|
serviceCfg model.OAuthServiceConfig
|
||||||
@@ -81,7 +82,7 @@ func (s *OAuthService) GetToken(code string, verifier string) (*oauth2.Token, er
|
|||||||
|
|
||||||
func (s *OAuthService) GetUserinfo(token *oauth2.Token) (*model.Claims, error) {
|
func (s *OAuthService) GetUserinfo(token *oauth2.Token) (*model.Claims, error) {
|
||||||
client := oauth2.NewClient(s.ctx, oauth2.StaticTokenSource(token))
|
client := oauth2.NewClient(s.ctx, oauth2.StaticTokenSource(token))
|
||||||
return s.userinfoExtractor(client, s.ctx, s.serviceCfg.UserinfoURL)
|
return s.userinfoExtractor(client, s.ctx, s.serviceCfg.UserinfoURL, s.mapClaims)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OAuthService) GetConfig() model.OAuthServiceConfig {
|
func (s *OAuthService) GetConfig() model.OAuthServiceConfig {
|
||||||
@@ -97,3 +98,26 @@ func (s *OAuthService) UpdateConfig(config model.OAuthServiceConfig) {
|
|||||||
s.config.Endpoint.TokenURL = config.TokenURL
|
s.config.Endpoint.TokenURL = config.TokenURL
|
||||||
s.config.RedirectURL = config.RedirectURL
|
s.config.RedirectURL = config.RedirectURL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *OAuthService) mapClaims(claims map[string]any) model.Claims {
|
||||||
|
return model.Claims{
|
||||||
|
Sub: mapClaim[string]("sub", "", claims),
|
||||||
|
Name: mapClaim[string]("name", s.serviceCfg.Claims.Name, claims),
|
||||||
|
PreferredUsername: mapClaim[string]("preferred_username", s.serviceCfg.Claims.Username, claims),
|
||||||
|
Email: mapClaim[string]("email", s.serviceCfg.Claims.Email, claims),
|
||||||
|
Groups: mapClaim[any]("groups", s.serviceCfg.Claims.Groups, claims),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapClaim[T any](fallback, override string, kv map[string]any) T {
|
||||||
|
key := fallback
|
||||||
|
if override != "" {
|
||||||
|
key = override
|
||||||
|
}
|
||||||
|
v, ok := kv[key].(T)
|
||||||
|
if !ok {
|
||||||
|
var zero T
|
||||||
|
return zero
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user