feat: add GitHub OAuth2 provider branch
This commit is contained in:
@@ -2,19 +2,97 @@ package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/models"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// githubOAuthConfig and githubIdentity are temporary stubs. Task 7 replaces
|
||||
// this file with the real GitHub OAuth2 provider implementation.
|
||||
// githubAPIBase is a variable rather than a constant so tests can point it at
|
||||
// an httptest server. Nothing in production reassigns it.
|
||||
var githubAPIBase = "https://api.github.com"
|
||||
|
||||
const (
|
||||
githubAuthURL = "https://github.com/login/oauth/authorize"
|
||||
githubTokenURL = "https://github.com/login/oauth/access_token"
|
||||
)
|
||||
|
||||
type githubEmail struct {
|
||||
Email string `json:"email"`
|
||||
Primary bool `json:"primary"`
|
||||
Verified bool `json:"verified"`
|
||||
}
|
||||
|
||||
// githubOAuthConfig builds the OAuth2 config for a GitHub provider. GitHub has
|
||||
// no discovery document, so the endpoints are constants rather than fetched.
|
||||
func githubOAuthConfig(p *models.AuthProvider, secret, redirectURL string) *oauth2.Config {
|
||||
return nil
|
||||
return &oauth2.Config{
|
||||
ClientID: p.ClientID,
|
||||
ClientSecret: secret,
|
||||
RedirectURL: redirectURL,
|
||||
Scopes: p.Scopes,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: githubAuthURL,
|
||||
TokenURL: githubTokenURL,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func githubIdentity(ctx context.Context, cfg *oauth2.Config, token *oauth2.Token) (string, string, error) {
|
||||
return "", "", errors.New("not implemented")
|
||||
// selectGitHubEmail requires an address that is both primary and verified.
|
||||
//
|
||||
// Verified alone is not enough: a non-primary address is one the person happens
|
||||
// to have proved, not the one they present as themselves. Primary alone is far
|
||||
// worse — an unverified address is not proof of control at all, and accepting
|
||||
// one would let anyone with a GitHub account claim any address in the instance.
|
||||
func selectGitHubEmail(emails []githubEmail) (string, error) {
|
||||
for _, e := range emails {
|
||||
if e.Primary && e.Verified && e.Email != "" {
|
||||
return e.Email, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("no primary verified email address on the GitHub account")
|
||||
}
|
||||
|
||||
func githubGetJSON(ctx context.Context, client *http.Client, url string, out any) error {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Accept", "application/vnd.github+json")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New("github api returned " + resp.Status)
|
||||
}
|
||||
return json.NewDecoder(resp.Body).Decode(out)
|
||||
}
|
||||
|
||||
// githubIdentity resolves the signed-in GitHub account to an email and a name.
|
||||
//
|
||||
// The name is best-effort: it is cosmetic, and a failing /user must not fail a
|
||||
// sign-in whose identity is already established.
|
||||
func githubIdentity(ctx context.Context, cfg *oauth2.Config, token *oauth2.Token) (string, string, error) {
|
||||
client := cfg.Client(ctx, token)
|
||||
|
||||
var emails []githubEmail
|
||||
if err := githubGetJSON(ctx, client, githubAPIBase+"/user/emails", &emails); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
email, err := selectGitHubEmail(emails)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var user struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := githubGetJSON(ctx, client, githubAPIBase+"/user", &user); err != nil {
|
||||
return email, "", nil
|
||||
}
|
||||
return email, user.Name, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user