mirror of
https://github.com/pocketbase/pocketbase.git
synced 2024-11-28 10:03:42 +02:00
Added gitee OAuth
Signed-off-by: Yuxiang Gao <yuxiang-gao@outlook.com>
This commit is contained in:
parent
9033cd109e
commit
19ccc70fe5
@ -59,6 +59,7 @@ func TestSettingsList(t *testing.T) {
|
|||||||
`"kakaoAuth":{`,
|
`"kakaoAuth":{`,
|
||||||
`"twitchAuth":{`,
|
`"twitchAuth":{`,
|
||||||
`"stravaAuth":{`,
|
`"stravaAuth":{`,
|
||||||
|
`"giteeAuth":{`,
|
||||||
`"secret":"******"`,
|
`"secret":"******"`,
|
||||||
`"clientSecret":"******"`,
|
`"clientSecret":"******"`,
|
||||||
},
|
},
|
||||||
@ -127,6 +128,7 @@ func TestSettingsSet(t *testing.T) {
|
|||||||
`"kakaoAuth":{`,
|
`"kakaoAuth":{`,
|
||||||
`"twitchAuth":{`,
|
`"twitchAuth":{`,
|
||||||
`"stravaAuth":{`,
|
`"stravaAuth":{`,
|
||||||
|
`"giteeAuth":{`,
|
||||||
`"secret":"******"`,
|
`"secret":"******"`,
|
||||||
`"clientSecret":"******"`,
|
`"clientSecret":"******"`,
|
||||||
`"appName":"acme_test"`,
|
`"appName":"acme_test"`,
|
||||||
@ -184,6 +186,7 @@ func TestSettingsSet(t *testing.T) {
|
|||||||
`"kakaoAuth":{`,
|
`"kakaoAuth":{`,
|
||||||
`"twitchAuth":{`,
|
`"twitchAuth":{`,
|
||||||
`"stravaAuth":{`,
|
`"stravaAuth":{`,
|
||||||
|
`"giteeAuth":{`,
|
||||||
`"secret":"******"`,
|
`"secret":"******"`,
|
||||||
`"clientSecret":"******"`,
|
`"clientSecret":"******"`,
|
||||||
`"appName":"update_test"`,
|
`"appName":"update_test"`,
|
||||||
|
@ -45,6 +45,7 @@ type Settings struct {
|
|||||||
KakaoAuth AuthProviderConfig `form:"kakaoAuth" json:"kakaoAuth"`
|
KakaoAuth AuthProviderConfig `form:"kakaoAuth" json:"kakaoAuth"`
|
||||||
TwitchAuth AuthProviderConfig `form:"twitchAuth" json:"twitchAuth"`
|
TwitchAuth AuthProviderConfig `form:"twitchAuth" json:"twitchAuth"`
|
||||||
StravaAuth AuthProviderConfig `form:"stravaAuth" json:"stravaAuth"`
|
StravaAuth AuthProviderConfig `form:"stravaAuth" json:"stravaAuth"`
|
||||||
|
GiteeAuth AuthProviderConfig `form:"giteeAuth" json:"giteeAuth"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates and returns a new default Settings instance.
|
// New creates and returns a new default Settings instance.
|
||||||
@ -128,6 +129,9 @@ func New() *Settings {
|
|||||||
StravaAuth: AuthProviderConfig{
|
StravaAuth: AuthProviderConfig{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
},
|
},
|
||||||
|
GiteeAuth: AuthProviderConfig{
|
||||||
|
Enabled: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +162,7 @@ func (s *Settings) Validate() error {
|
|||||||
validation.Field(&s.KakaoAuth),
|
validation.Field(&s.KakaoAuth),
|
||||||
validation.Field(&s.TwitchAuth),
|
validation.Field(&s.TwitchAuth),
|
||||||
validation.Field(&s.StravaAuth),
|
validation.Field(&s.StravaAuth),
|
||||||
|
validation.Field(&s.GiteeAuth),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,6 +218,7 @@ func (s *Settings) RedactClone() (*Settings, error) {
|
|||||||
&clone.KakaoAuth.ClientSecret,
|
&clone.KakaoAuth.ClientSecret,
|
||||||
&clone.TwitchAuth.ClientSecret,
|
&clone.TwitchAuth.ClientSecret,
|
||||||
&clone.StravaAuth.ClientSecret,
|
&clone.StravaAuth.ClientSecret,
|
||||||
|
&clone.GiteeAuth.ClientSecret,
|
||||||
}
|
}
|
||||||
|
|
||||||
// mask all sensitive fields
|
// mask all sensitive fields
|
||||||
@ -243,6 +249,7 @@ func (s *Settings) NamedAuthProviderConfigs() map[string]AuthProviderConfig {
|
|||||||
auth.NameKakao: s.KakaoAuth,
|
auth.NameKakao: s.KakaoAuth,
|
||||||
auth.NameTwitch: s.TwitchAuth,
|
auth.NameTwitch: s.TwitchAuth,
|
||||||
auth.NameStrava: s.StravaAuth,
|
auth.NameStrava: s.StravaAuth,
|
||||||
|
auth.NameGitee: s.GiteeAuth,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +50,8 @@ func TestSettingsValidate(t *testing.T) {
|
|||||||
s.TwitchAuth.ClientId = ""
|
s.TwitchAuth.ClientId = ""
|
||||||
s.StravaAuth.Enabled = true
|
s.StravaAuth.Enabled = true
|
||||||
s.StravaAuth.ClientId = ""
|
s.StravaAuth.ClientId = ""
|
||||||
|
s.GiteeAuth.Enabled = true
|
||||||
|
s.GiteeAuth.ClientId = ""
|
||||||
|
|
||||||
// check if Validate() is triggering the members validate methods.
|
// check if Validate() is triggering the members validate methods.
|
||||||
err := s.Validate()
|
err := s.Validate()
|
||||||
@ -79,6 +81,7 @@ func TestSettingsValidate(t *testing.T) {
|
|||||||
`"kakaoAuth":{`,
|
`"kakaoAuth":{`,
|
||||||
`"twitchAuth":{`,
|
`"twitchAuth":{`,
|
||||||
`"stravaAuth":{`,
|
`"stravaAuth":{`,
|
||||||
|
`"giteeAuth":{`,
|
||||||
}
|
}
|
||||||
|
|
||||||
errBytes, _ := json.Marshal(err)
|
errBytes, _ := json.Marshal(err)
|
||||||
@ -129,6 +132,8 @@ func TestSettingsMerge(t *testing.T) {
|
|||||||
s2.TwitchAuth.ClientId = "twitch_test"
|
s2.TwitchAuth.ClientId = "twitch_test"
|
||||||
s2.StravaAuth.Enabled = true
|
s2.StravaAuth.Enabled = true
|
||||||
s2.StravaAuth.ClientId = "strava_test"
|
s2.StravaAuth.ClientId = "strava_test"
|
||||||
|
s2.GiteeAuth.Enabled = true
|
||||||
|
s2.GiteeAuth.ClientId = "gitee_test"
|
||||||
|
|
||||||
if err := s1.Merge(s2); err != nil {
|
if err := s1.Merge(s2); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -201,6 +206,7 @@ func TestSettingsRedactClone(t *testing.T) {
|
|||||||
s1.KakaoAuth.ClientSecret = "test123"
|
s1.KakaoAuth.ClientSecret = "test123"
|
||||||
s1.TwitchAuth.ClientSecret = "test123"
|
s1.TwitchAuth.ClientSecret = "test123"
|
||||||
s1.StravaAuth.ClientSecret = "test123"
|
s1.StravaAuth.ClientSecret = "test123"
|
||||||
|
s1.GiteeAuth.ClientSecret = "test123"
|
||||||
|
|
||||||
s2, err := s1.RedactClone()
|
s2, err := s1.RedactClone()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -212,7 +218,7 @@ func TestSettingsRedactClone(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := `{"meta":{"appName":"test123","appUrl":"http://localhost:8090","hideControls":false,"senderName":"Support","senderAddress":"support@example.com","verificationTemplate":{"body":"\u003cp\u003eHello,\u003c/p\u003e\n\u003cp\u003eThank you for joining us at {APP_NAME}.\u003c/p\u003e\n\u003cp\u003eClick on the button below to verify your email address.\u003c/p\u003e\n\u003cp\u003e\n \u003ca class=\"btn\" href=\"{ACTION_URL}\" target=\"_blank\" rel=\"noopener\"\u003eVerify\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e\n Thanks,\u003cbr/\u003e\n {APP_NAME} team\n\u003c/p\u003e","subject":"Verify your {APP_NAME} email","actionUrl":"{APP_URL}/_/#/auth/confirm-verification/{TOKEN}"},"resetPasswordTemplate":{"body":"\u003cp\u003eHello,\u003c/p\u003e\n\u003cp\u003eClick on the button below to reset your password.\u003c/p\u003e\n\u003cp\u003e\n \u003ca class=\"btn\" href=\"{ACTION_URL}\" target=\"_blank\" rel=\"noopener\"\u003eReset password\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e\u003ci\u003eIf you didn't ask to reset your password, you can ignore this email.\u003c/i\u003e\u003c/p\u003e\n\u003cp\u003e\n Thanks,\u003cbr/\u003e\n {APP_NAME} team\n\u003c/p\u003e","subject":"Reset your {APP_NAME} password","actionUrl":"{APP_URL}/_/#/auth/confirm-password-reset/{TOKEN}"},"confirmEmailChangeTemplate":{"body":"\u003cp\u003eHello,\u003c/p\u003e\n\u003cp\u003eClick on the button below to confirm your new email address.\u003c/p\u003e\n\u003cp\u003e\n \u003ca class=\"btn\" href=\"{ACTION_URL}\" target=\"_blank\" rel=\"noopener\"\u003eConfirm new email\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e\u003ci\u003eIf you didn't ask to change your email address, you can ignore this email.\u003c/i\u003e\u003c/p\u003e\n\u003cp\u003e\n Thanks,\u003cbr/\u003e\n {APP_NAME} team\n\u003c/p\u003e","subject":"Confirm your {APP_NAME} new email address","actionUrl":"{APP_URL}/_/#/auth/confirm-email-change/{TOKEN}"}},"logs":{"maxDays":5},"smtp":{"enabled":false,"host":"smtp.example.com","port":587,"username":"","password":"******","authMethod":"","tls":true},"s3":{"enabled":false,"bucket":"","region":"","endpoint":"","accessKey":"","secret":"******","forcePathStyle":false},"adminAuthToken":{"secret":"******","duration":1209600},"adminPasswordResetToken":{"secret":"******","duration":1800},"recordAuthToken":{"secret":"******","duration":1209600},"recordPasswordResetToken":{"secret":"******","duration":1800},"recordEmailChangeToken":{"secret":"******","duration":1800},"recordVerificationToken":{"secret":"******","duration":604800},"emailAuth":{"enabled":false,"exceptDomains":null,"onlyDomains":null,"minPasswordLength":0},"googleAuth":{"enabled":false,"clientSecret":"******"},"facebookAuth":{"enabled":false,"clientSecret":"******"},"githubAuth":{"enabled":false,"clientSecret":"******"},"gitlabAuth":{"enabled":false,"clientSecret":"******"},"discordAuth":{"enabled":false,"clientSecret":"******"},"twitterAuth":{"enabled":false,"clientSecret":"******"},"microsoftAuth":{"enabled":false,"clientSecret":"******"},"spotifyAuth":{"enabled":false,"clientSecret":"******"},"kakaoAuth":{"enabled":false,"clientSecret":"******"},"twitchAuth":{"enabled":false,"clientSecret":"******"},"stravaAuth":{"enabled":false,"clientSecret":"******"}}`
|
expected := `{"meta":{"appName":"test123","appUrl":"http://localhost:8090","hideControls":false,"senderName":"Support","senderAddress":"support@example.com","verificationTemplate":{"body":"\u003cp\u003eHello,\u003c/p\u003e\n\u003cp\u003eThank you for joining us at {APP_NAME}.\u003c/p\u003e\n\u003cp\u003eClick on the button below to verify your email address.\u003c/p\u003e\n\u003cp\u003e\n \u003ca class=\"btn\" href=\"{ACTION_URL}\" target=\"_blank\" rel=\"noopener\"\u003eVerify\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e\n Thanks,\u003cbr/\u003e\n {APP_NAME} team\n\u003c/p\u003e","subject":"Verify your {APP_NAME} email","actionUrl":"{APP_URL}/_/#/auth/confirm-verification/{TOKEN}"},"resetPasswordTemplate":{"body":"\u003cp\u003eHello,\u003c/p\u003e\n\u003cp\u003eClick on the button below to reset your password.\u003c/p\u003e\n\u003cp\u003e\n \u003ca class=\"btn\" href=\"{ACTION_URL}\" target=\"_blank\" rel=\"noopener\"\u003eReset password\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e\u003ci\u003eIf you didn't ask to reset your password, you can ignore this email.\u003c/i\u003e\u003c/p\u003e\n\u003cp\u003e\n Thanks,\u003cbr/\u003e\n {APP_NAME} team\n\u003c/p\u003e","subject":"Reset your {APP_NAME} password","actionUrl":"{APP_URL}/_/#/auth/confirm-password-reset/{TOKEN}"},"confirmEmailChangeTemplate":{"body":"\u003cp\u003eHello,\u003c/p\u003e\n\u003cp\u003eClick on the button below to confirm your new email address.\u003c/p\u003e\n\u003cp\u003e\n \u003ca class=\"btn\" href=\"{ACTION_URL}\" target=\"_blank\" rel=\"noopener\"\u003eConfirm new email\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e\u003ci\u003eIf you didn't ask to change your email address, you can ignore this email.\u003c/i\u003e\u003c/p\u003e\n\u003cp\u003e\n Thanks,\u003cbr/\u003e\n {APP_NAME} team\n\u003c/p\u003e","subject":"Confirm your {APP_NAME} new email address","actionUrl":"{APP_URL}/_/#/auth/confirm-email-change/{TOKEN}"}},"logs":{"maxDays":5},"smtp":{"enabled":false,"host":"smtp.example.com","port":587,"username":"","password":"******","authMethod":"","tls":true},"s3":{"enabled":false,"bucket":"","region":"","endpoint":"","accessKey":"","secret":"******","forcePathStyle":false},"adminAuthToken":{"secret":"******","duration":1209600},"adminPasswordResetToken":{"secret":"******","duration":1800},"recordAuthToken":{"secret":"******","duration":1209600},"recordPasswordResetToken":{"secret":"******","duration":1800},"recordEmailChangeToken":{"secret":"******","duration":1800},"recordVerificationToken":{"secret":"******","duration":604800},"emailAuth":{"enabled":false,"exceptDomains":null,"onlyDomains":null,"minPasswordLength":0},"googleAuth":{"enabled":false,"clientSecret":"******"},"facebookAuth":{"enabled":false,"clientSecret":"******"},"githubAuth":{"enabled":false,"clientSecret":"******"},"gitlabAuth":{"enabled":false,"clientSecret":"******"},"discordAuth":{"enabled":false,"clientSecret":"******"},"twitterAuth":{"enabled":false,"clientSecret":"******"},"microsoftAuth":{"enabled":false,"clientSecret":"******"},"spotifyAuth":{"enabled":false,"clientSecret":"******"},"kakaoAuth":{"enabled":false,"clientSecret":"******"},"twitchAuth":{"enabled":false,"clientSecret":"******"},"stravaAuth":{"enabled":false,"clientSecret":"******"},"giteeAuth":{"enabled":false,"clientSecret":"******"}}`
|
||||||
|
|
||||||
if encodedStr := string(encoded); encodedStr != expected {
|
if encodedStr := string(encoded); encodedStr != expected {
|
||||||
t.Fatalf("Expected\n%v\ngot\n%v", expected, encodedStr)
|
t.Fatalf("Expected\n%v\ngot\n%v", expected, encodedStr)
|
||||||
@ -234,6 +240,7 @@ func TestNamedAuthProviderConfigs(t *testing.T) {
|
|||||||
s.KakaoAuth.ClientId = "kakao_test"
|
s.KakaoAuth.ClientId = "kakao_test"
|
||||||
s.TwitchAuth.ClientId = "twitch_test"
|
s.TwitchAuth.ClientId = "twitch_test"
|
||||||
s.StravaAuth.ClientId = "strava_test"
|
s.StravaAuth.ClientId = "strava_test"
|
||||||
|
s.GiteeAuth.ClientId = "gitee_test"
|
||||||
|
|
||||||
result := s.NamedAuthProviderConfigs()
|
result := s.NamedAuthProviderConfigs()
|
||||||
|
|
||||||
@ -255,6 +262,7 @@ func TestNamedAuthProviderConfigs(t *testing.T) {
|
|||||||
`"kakao":{"enabled":false,"clientId":"kakao_test"}`,
|
`"kakao":{"enabled":false,"clientId":"kakao_test"}`,
|
||||||
`"twitch":{"enabled":false,"clientId":"twitch_test"}`,
|
`"twitch":{"enabled":false,"clientId":"twitch_test"}`,
|
||||||
`"strava":{"enabled":false,"clientId":"strava_test"}`,
|
`"strava":{"enabled":false,"clientId":"strava_test"}`,
|
||||||
|
`"gitee":{"enabled":false,"clientId":"gitee_test"}`,
|
||||||
}
|
}
|
||||||
for _, p := range expectedParts {
|
for _, p := range expectedParts {
|
||||||
if !strings.Contains(encodedStr, p) {
|
if !strings.Contains(encodedStr, p) {
|
||||||
|
@ -107,6 +107,8 @@ func NewProviderByName(name string) (Provider, error) {
|
|||||||
return NewTwitchProvider(), nil
|
return NewTwitchProvider(), nil
|
||||||
case NameStrava:
|
case NameStrava:
|
||||||
return NewStravaProvider(), nil
|
return NewStravaProvider(), nil
|
||||||
|
case NameGitee:
|
||||||
|
return NewGiteeProvider(), nil
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("Missing provider " + name)
|
return nil, errors.New("Missing provider " + name)
|
||||||
}
|
}
|
||||||
|
@ -117,4 +117,13 @@ func TestNewProviderByName(t *testing.T) {
|
|||||||
if _, ok := p.(*auth.Strava); !ok {
|
if _, ok := p.(*auth.Strava); !ok {
|
||||||
t.Error("Expected to be instance of *auth.Strava")
|
t.Error("Expected to be instance of *auth.Strava")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gitee
|
||||||
|
p, err = auth.NewProviderByName(auth.NameGitee)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected nil, got error %v", err)
|
||||||
|
}
|
||||||
|
if _, ok := p.(*auth.Gitee); !ok {
|
||||||
|
t.Error("Expected to be instance of *auth.Gitee")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
107
tools/auth/gitee.go
Normal file
107
tools/auth/gitee.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Provider = (*Gitee)(nil)
|
||||||
|
|
||||||
|
// NameGitee is the unique name of the Gitee provider.
|
||||||
|
const NameGitee string = "gitee"
|
||||||
|
|
||||||
|
// Gitee allows authentication via Gitee OAuth2.
|
||||||
|
type Gitee struct {
|
||||||
|
*baseProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGiteeProvider creates new Gitee provider instance with some defaults.
|
||||||
|
func NewGiteeProvider() *Gitee {
|
||||||
|
return &Gitee{&baseProvider{
|
||||||
|
scopes: []string{"user_info", "emails"},
|
||||||
|
authUrl: "https://gitee.com/oauth/authorize",
|
||||||
|
tokenUrl: "https://gitee.com/oauth/token",
|
||||||
|
userApiUrl: "https://gitee.com/api/v5/user",
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchAuthUser returns an AuthUser instance based the Gitee's user api.
|
||||||
|
//
|
||||||
|
// API reference: https://gitee.com/api/v5/swagger#/getV5User
|
||||||
|
func (p *Gitee) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
||||||
|
data, err := p.FetchRawUserData(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rawUser := map[string]any{}
|
||||||
|
if err := json.Unmarshal(data, &rawUser); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
extracted := struct {
|
||||||
|
Login string `json:"login"`
|
||||||
|
Id int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
AvatarUrl string `json:"avatar_url"`
|
||||||
|
}{}
|
||||||
|
if err := json.Unmarshal(data, &extracted); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &AuthUser{
|
||||||
|
Id: strconv.Itoa(extracted.Id),
|
||||||
|
Name: extracted.Name,
|
||||||
|
Username: extracted.Login,
|
||||||
|
Email: extracted.Email,
|
||||||
|
AvatarUrl: extracted.AvatarUrl,
|
||||||
|
RawUser: rawUser,
|
||||||
|
AccessToken: token.AccessToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
// in case user set "Keep my email address private",
|
||||||
|
// email should be retrieved via extra API request
|
||||||
|
if user.Email == "" {
|
||||||
|
client := p.Client(token)
|
||||||
|
|
||||||
|
response, err := client.Get("https://gitee.com/api/v5/emails")
|
||||||
|
if err != nil {
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
content, err := io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
emails := []struct {
|
||||||
|
Email string
|
||||||
|
State string
|
||||||
|
Scope []string
|
||||||
|
}{}
|
||||||
|
if err := json.Unmarshal(content, &emails); err != nil {
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract the verified primary email
|
||||||
|
|
||||||
|
//
|
||||||
|
// API reference: https://gitee.com/api/v5/swagger#/getV5Emails
|
||||||
|
outer:
|
||||||
|
for _, email := range emails {
|
||||||
|
for _, scope := range email.Scope {
|
||||||
|
if email.State == "confirmed" && scope == "primary" {
|
||||||
|
user.Email = email.Email
|
||||||
|
break outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user