package auth import ( "context" "encoding/json" "fmt" "io" "net/http" "net/url" "path" "golang.org/x/oauth2" ) // issuerBase resolves ".." to get the base OAuth path from the issuer URL. // e.g. "https://auth.example.com/application/o/app/" -> "https://auth.example.com/application/o/" func issuerBase(issuer string) string { u, err := url.Parse(issuer) if err != nil { return issuer } u.Path = path.Dir(path.Clean(u.Path)) + "/" return u.String() } type OAuthClient struct { config *oauth2.Config cfg *Config } func NewOAuthClient(cfg *Config) *OAuthClient { return &OAuthClient{ config: &oauth2.Config{ ClientID: cfg.OAuthClientID, ClientSecret: cfg.OAuthClientSecret, RedirectURL: cfg.OAuthRedirectURI, Endpoint: oauth2.Endpoint{ AuthURL: issuerBase(cfg.OAuthIssuer) + "authorize/", TokenURL: issuerBase(cfg.OAuthIssuer) + "token/", }, Scopes: []string{"openid", "profile", "email"}, }, cfg: cfg, } } func (c *OAuthClient) GetAuthURL(state string) string { return c.config.AuthCodeURL(state) } func (c *OAuthClient) ExchangeCode(ctx context.Context, code string) (*oauth2.Token, error) { return c.config.Exchange(ctx, code) } type UserInfo struct { Sub string `json:"sub"` Username string `json:"preferred_username"` Email string `json:"email"` Name string `json:"name"` } func (c *OAuthClient) GetUserInfo(ctx context.Context, accessToken string) (*UserInfo, error) { req, err := http.NewRequestWithContext(ctx, "GET", issuerBase(c.cfg.OAuthIssuer)+"userinfo/", nil) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+accessToken) resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("userinfo request failed: %s", string(body)) } var userInfo UserInfo if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil { return nil, err } return &userInfo, nil }