feat(backend): add OAuth2/JWT authentication support
- Add OAuth2 client for Authentik integration - Implement JWT token generation and validation - Add refresh token support with database storage - Update database schema with oauth_subject, oauth_provider, and refresh_tokens table - Create auth package with config, jwt, oauth, and token management - Add OAuth endpoints: /auth/login, /auth/callback, /auth/refresh, /auth/logout - Update AuthMiddleware to support both JWT and API key authentication - Add user helper functions for OAuth user creation and retrieval - Add .env.example with OAuth configuration template API keys still work for CLI compatibility while JWT tokens support web/mobile clients.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
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: cfg.OAuthIssuer + "../authorize/",
|
||||
TokenURL: 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", 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
|
||||
}
|
||||
Reference in New Issue
Block a user