updated bitbucket,github and gitea oauth2 providers
This commit is contained in:
@@ -8,6 +8,10 @@
|
|||||||
|
|
||||||
- Fixed autocomplete selection not properly updating the underlying input value ([#7664](https://github.com/pocketbase/pocketbase/issues/7664)).
|
- Fixed autocomplete selection not properly updating the underlying input value ([#7664](https://github.com/pocketbase/pocketbase/issues/7664)).
|
||||||
|
|
||||||
|
- Adjusted Bitbucket, GitHub and Gitea/Forgejo OAuth2 providers to better reflect recent API updates and doc references.
|
||||||
|
_The providers also now always send an extra emails_list request to fetch only the explicitly verified primary email in order to eliminate eventual security issues caused by misconfigured onpremise setups._
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## v0.37.3
|
## v0.37.3
|
||||||
|
|
||||||
|
|||||||
@@ -120,8 +120,9 @@ func (p *Bitbucket) fetchPrimaryEmail(token *oauth2.Token) (string, error) {
|
|||||||
|
|
||||||
expected := struct {
|
expected := struct {
|
||||||
Values []struct {
|
Values []struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
IsPrimary bool `json:"is_primary"`
|
IsPrimary bool `json:"is_primary"`
|
||||||
|
IsConfirmed bool `json:"is_confirmed"`
|
||||||
} `json:"values"`
|
} `json:"values"`
|
||||||
}{}
|
}{}
|
||||||
if err := json.Unmarshal(data, &expected); err != nil {
|
if err := json.Unmarshal(data, &expected); err != nil {
|
||||||
@@ -129,7 +130,7 @@ func (p *Bitbucket) fetchPrimaryEmail(token *oauth2.Token) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range expected.Values {
|
for _, v := range expected.Values {
|
||||||
if v.IsPrimary {
|
if v.IsPrimary && v.IsConfirmed {
|
||||||
return v.Email, nil
|
return v.Email, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase/tools/types"
|
"github.com/pocketbase/pocketbase/tools/types"
|
||||||
@@ -15,10 +18,10 @@ func init() {
|
|||||||
|
|
||||||
var _ Provider = (*Gitea)(nil)
|
var _ Provider = (*Gitea)(nil)
|
||||||
|
|
||||||
// NameGitea is the unique name of the Gitea provider.
|
// NameGitea is the unique name of the Gitea/Forgejo provider.
|
||||||
const NameGitea string = "gitea"
|
const NameGitea string = "gitea"
|
||||||
|
|
||||||
// Gitea allows authentication via Gitea OAuth2.
|
// Gitea allows authentication via Gitea/Forgejo OAuth2.
|
||||||
type Gitea struct {
|
type Gitea struct {
|
||||||
BaseProvider
|
BaseProvider
|
||||||
}
|
}
|
||||||
@@ -38,9 +41,9 @@ func NewGiteaProvider() *Gitea {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchAuthUser returns an AuthUser instance based on Gitea's user api.
|
// FetchAuthUser returns an AuthUser instance based on Gitea/Forgejo's user api.
|
||||||
//
|
//
|
||||||
// API reference: https://try.gitea.io/api/swagger#/user/userGetCurrent
|
// API reference: https://codeberg.org/api/swagger#/user/userGetCurrent
|
||||||
func (p *Gitea) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
func (p *Gitea) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
||||||
data, err := p.FetchRawUserInfo(token)
|
data, err := p.FetchRawUserInfo(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -55,26 +58,81 @@ func (p *Gitea) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
|||||||
extracted := struct {
|
extracted := struct {
|
||||||
Name string `json:"full_name"`
|
Name string `json:"full_name"`
|
||||||
Username string `json:"login"`
|
Username string `json:"login"`
|
||||||
Email string `json:"email"`
|
|
||||||
AvatarURL string `json:"avatar_url"`
|
AvatarURL string `json:"avatar_url"`
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
|
Active bool `json:"active"`
|
||||||
}{}
|
}{}
|
||||||
if err := json.Unmarshal(data, &extracted); err != nil {
|
if err := json.Unmarshal(data, &extracted); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !extracted.Active {
|
||||||
|
return nil, errors.New("user account is not active")
|
||||||
|
}
|
||||||
|
|
||||||
user := &AuthUser{
|
user := &AuthUser{
|
||||||
Id: strconv.FormatInt(extracted.Id, 10),
|
Id: strconv.FormatInt(extracted.Id, 10),
|
||||||
Name: extracted.Name,
|
Name: extracted.Name,
|
||||||
Username: extracted.Username,
|
Username: extracted.Username,
|
||||||
Email: extracted.Email,
|
|
||||||
AvatarURL: extracted.AvatarURL,
|
AvatarURL: extracted.AvatarURL,
|
||||||
RawUser: rawUser,
|
RawUser: rawUser,
|
||||||
AccessToken: token.AccessToken,
|
AccessToken: token.AccessToken,
|
||||||
RefreshToken: token.RefreshToken,
|
RefreshToken: token.RefreshToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
email, err := p.fetchVerifiedPrimaryEmail(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch primary email: %w", err)
|
||||||
|
}
|
||||||
|
user.Email = email
|
||||||
|
|
||||||
user.Expiry, _ = types.ParseDateTime(token.Expiry)
|
user.Expiry, _ = types.ParseDateTime(token.Expiry)
|
||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetchVerifiedPrimaryEmail sends an API request to retrieve the verified
|
||||||
|
// primary email, in case "Keep my email address private" was set.
|
||||||
|
//
|
||||||
|
// NB! This method can succeed and still return an empty email.
|
||||||
|
// Error responses that are result of insufficient scopes permissions are ignored.
|
||||||
|
//
|
||||||
|
// API reference: https://codeberg.org/api/swagger#/user/userListEmails
|
||||||
|
func (p *Gitea) fetchVerifiedPrimaryEmail(token *oauth2.Token) (string, error) {
|
||||||
|
client := p.Client(token)
|
||||||
|
|
||||||
|
response, err := client.Get(p.userInfoURL + "/emails")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
// ignore common http errors caused by insufficient scope permissions
|
||||||
|
// (the email field is optional, aka. return the auth user without it)
|
||||||
|
if response.StatusCode == 401 || response.StatusCode == 403 || response.StatusCode == 404 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
emails := []struct {
|
||||||
|
Email string
|
||||||
|
Verified bool
|
||||||
|
Primary bool
|
||||||
|
}{}
|
||||||
|
if err := json.Unmarshal(content, &emails); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract the verified primary email
|
||||||
|
for _, email := range emails {
|
||||||
|
if email.Verified && email.Primary {
|
||||||
|
return email.Email, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func NewGithubProvider() *Github {
|
|||||||
|
|
||||||
// FetchAuthUser returns an AuthUser instance based the Github's user api.
|
// FetchAuthUser returns an AuthUser instance based the Github's user api.
|
||||||
//
|
//
|
||||||
// API reference: https://docs.github.com/en/rest/reference/users#get-the-authenticated-user
|
// API reference: https://docs.github.com/en/rest/users/users?apiVersion=2026-03-10#get-the-authenticated-user
|
||||||
func (p *Github) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
func (p *Github) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
||||||
data, err := p.FetchRawUserInfo(token)
|
data, err := p.FetchRawUserInfo(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -55,9 +55,8 @@ func (p *Github) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extracted := struct {
|
extracted := struct {
|
||||||
Login string `json:"login"`
|
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Email string `json:"email"`
|
Login string `json:"login"`
|
||||||
AvatarURL string `json:"avatar_url"`
|
AvatarURL string `json:"avatar_url"`
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
}{}
|
}{}
|
||||||
@@ -69,7 +68,6 @@ func (p *Github) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
|||||||
Id: strconv.FormatInt(extracted.Id, 10),
|
Id: strconv.FormatInt(extracted.Id, 10),
|
||||||
Name: extracted.Name,
|
Name: extracted.Name,
|
||||||
Username: extracted.Login,
|
Username: extracted.Login,
|
||||||
Email: extracted.Email,
|
|
||||||
AvatarURL: extracted.AvatarURL,
|
AvatarURL: extracted.AvatarURL,
|
||||||
RawUser: rawUser,
|
RawUser: rawUser,
|
||||||
AccessToken: token.AccessToken,
|
AccessToken: token.AccessToken,
|
||||||
@@ -78,27 +76,26 @@ func (p *Github) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
|||||||
|
|
||||||
user.Expiry, _ = types.ParseDateTime(token.Expiry)
|
user.Expiry, _ = types.ParseDateTime(token.Expiry)
|
||||||
|
|
||||||
// in case user has set "Keep my email address private", send an
|
// always send a primary email request even though the email is
|
||||||
// **optional** API request to retrieve the verified primary email
|
// returned in the userinfo endpoint since the API may change and
|
||||||
if user.Email == "" {
|
// enterprise setups may have configuration that could allow unverified emails
|
||||||
email, err := p.fetchPrimaryEmail(token)
|
email, err := p.fetchVerifiedPrimaryEmail(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
|
||||||
user.Email = email
|
|
||||||
}
|
}
|
||||||
|
user.Email = email
|
||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchPrimaryEmail sends an API request to retrieve the verified
|
// fetchVerifiedPrimaryEmail sends an API request to retrieve the verified
|
||||||
// primary email, in case "Keep my email address private" was set.
|
// primary email, in case "Keep my email address private" was set.
|
||||||
//
|
//
|
||||||
// NB! This method can succeed and still return an empty email.
|
// NB! This method can succeed and still return an empty email.
|
||||||
// Error responses that are result of insufficient scopes permissions are ignored.
|
// Error responses that are result of insufficient scopes permissions are ignored.
|
||||||
//
|
//
|
||||||
// API reference: https://docs.github.com/en/rest/users/emails?apiVersion=2022-11-28
|
// API reference: https://docs.github.com/en/rest/users/emails?apiVersion=2022-11-28#list-email-addresses-for-the-authenticated-user
|
||||||
func (p *Github) fetchPrimaryEmail(token *oauth2.Token) (string, error) {
|
func (p *Github) fetchVerifiedPrimaryEmail(token *oauth2.Token) (string, error) {
|
||||||
client := p.Client(token)
|
client := p.Client(token)
|
||||||
|
|
||||||
response, err := client.Get(p.userInfoURL + "/emails")
|
response, err := client.Get(p.userInfoURL + "/emails")
|
||||||
|
|||||||
Reference in New Issue
Block a user