@@ -68,8 +68,8 @@ require ( | |||
github.com/lib/pq v1.2.0 | |||
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 | |||
github.com/lunny/levelqueue v0.0.0-20190217115915-02b525a4418e | |||
github.com/markbates/goth v1.56.0 | |||
github.com/mailru/easyjson v0.7.0 // indirect | |||
github.com/markbates/goth v1.49.0 | |||
github.com/mattn/go-isatty v0.0.7 | |||
github.com/mattn/go-oci8 v0.0.0-20190320171441-14ba190cf52d // indirect | |||
github.com/mattn/go-sqlite3 v1.11.0 | |||
@@ -408,8 +408,8 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN | |||
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= | |||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= | |||
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA= | |||
github.com/markbates/goth v1.49.0 h1:qQ4Ti4WaqAxNAggOC+4s5M85sMVfMJwQn/Xkp73wfgI= | |||
github.com/markbates/goth v1.49.0/go.mod h1:zZmAw0Es0Dpm7TT/4AdN14QrkiWLMrrU9Xei1o+/mdA= | |||
github.com/markbates/goth v1.56.0 h1:XEYedCgMNz5pi3ojXI8z2XUmXtBnMeuKUpx4Z6HlNj8= | |||
github.com/markbates/goth v1.56.0/go.mod h1:zZmAw0Es0Dpm7TT/4AdN14QrkiWLMrrU9Xei1o+/mdA= | |||
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= | |||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= | |||
github.com/mattn/go-oci8 v0.0.0-20190320171441-14ba190cf52d h1:m+dSK37rFf2fqppZhg15yI2IwC9BtucBiRwSDm9VL8g= | |||
@@ -44,6 +44,13 @@ var OAuth2Providers = map[string]OAuth2Provider{ | |||
"openidConnect": {Name: "openidConnect", DisplayName: "OpenID Connect", Image: "/img/auth/openid_connect.png"}, | |||
"twitter": {Name: "twitter", DisplayName: "Twitter", Image: "/img/auth/twitter.png"}, | |||
"discord": {Name: "discord", DisplayName: "Discord", Image: "/img/auth/discord.png"}, | |||
"gitea": {Name: "gitea", DisplayName: "Gitea", Image: "/img/auth/gitea.png", | |||
CustomURLMapping: &oauth2.CustomURLMapping{ | |||
TokenURL: oauth2.GetDefaultTokenURL("gitea"), | |||
AuthURL: oauth2.GetDefaultAuthURL("gitea"), | |||
ProfileURL: oauth2.GetDefaultProfileURL("gitea"), | |||
}, | |||
}, | |||
} | |||
// OAuth2DefaultCustomURLMappings contains the map of default URL's for OAuth2 providers that are allowed to have custom urls | |||
@@ -52,6 +59,7 @@ var OAuth2Providers = map[string]OAuth2Provider{ | |||
var OAuth2DefaultCustomURLMappings = map[string]*oauth2.CustomURLMapping{ | |||
"github": OAuth2Providers["github"].CustomURLMapping, | |||
"gitlab": OAuth2Providers["gitlab"].CustomURLMapping, | |||
"gitea": OAuth2Providers["gitea"].CustomURLMapping, | |||
} | |||
// GetActiveOAuth2ProviderLoginSources returns all actived LoginOAuth2 sources | |||
@@ -19,6 +19,7 @@ import ( | |||
"github.com/markbates/goth/providers/discord" | |||
"github.com/markbates/goth/providers/dropbox" | |||
"github.com/markbates/goth/providers/facebook" | |||
"github.com/markbates/goth/providers/gitea" | |||
"github.com/markbates/goth/providers/github" | |||
"github.com/markbates/goth/providers/gitlab" | |||
"github.com/markbates/goth/providers/gplus" | |||
@@ -175,6 +176,22 @@ func createProvider(providerName, providerType, clientID, clientSecret, openIDCo | |||
provider = twitter.NewAuthenticate(clientID, clientSecret, callbackURL) | |||
case "discord": | |||
provider = discord.New(clientID, clientSecret, callbackURL, discord.ScopeIdentify, discord.ScopeEmail) | |||
case "gitea": | |||
authURL := gitea.AuthURL | |||
tokenURL := gitea.TokenURL | |||
profileURL := gitea.ProfileURL | |||
if customURLMapping != nil { | |||
if len(customURLMapping.AuthURL) > 0 { | |||
authURL = customURLMapping.AuthURL | |||
} | |||
if len(customURLMapping.TokenURL) > 0 { | |||
tokenURL = customURLMapping.TokenURL | |||
} | |||
if len(customURLMapping.ProfileURL) > 0 { | |||
profileURL = customURLMapping.ProfileURL | |||
} | |||
} | |||
provider = gitea.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL) | |||
} | |||
// always set the name if provider is created so we can support multiple setups of 1 provider | |||
@@ -192,6 +209,8 @@ func GetDefaultTokenURL(provider string) string { | |||
return github.TokenURL | |||
case "gitlab": | |||
return gitlab.TokenURL | |||
case "gitea": | |||
return gitea.TokenURL | |||
} | |||
return "" | |||
} | |||
@@ -203,6 +222,8 @@ func GetDefaultAuthURL(provider string) string { | |||
return github.AuthURL | |||
case "gitlab": | |||
return gitlab.AuthURL | |||
case "gitea": | |||
return gitea.AuthURL | |||
} | |||
return "" | |||
} | |||
@@ -214,6 +235,8 @@ func GetDefaultProfileURL(provider string) string { | |||
return github.ProfileURL | |||
case "gitlab": | |||
return gitlab.ProfileURL | |||
case "gitea": | |||
return gitea.ProfileURL | |||
} | |||
return "" | |||
} | |||
@@ -1706,6 +1706,7 @@ auths.tip.google_plus = Obtain OAuth2 client credentials from the Google API con | |||
auths.tip.openid_connect = Use the OpenID Connect Discovery URL (<server>/.well-known/openid-configuration) to specify the endpoints | |||
auths.tip.twitter = Go to https://dev.twitter.com/apps, create an application and ensure that the “Allow this application to be used to Sign in with Twitter” option is enabled | |||
auths.tip.discord = Register a new application on https://discordapp.com/developers/applications/me | |||
auths.tip.gitea = Register a new OAuth2 application. Guide can be found at https://docs.gitea.io/en-us/oauth2-provider/ | |||
auths.edit = Edit Authentication Source | |||
auths.activated = This Authentication Source is Activated | |||
auths.new_success = The authentication '%s' has been added. | |||
@@ -1576,6 +1576,7 @@ function initAdmin() { | |||
switch (provider) { | |||
case 'github': | |||
case 'gitlab': | |||
case 'gitea': | |||
$('.oauth2_use_custom_url').show(); | |||
break; | |||
case 'openidConnect': | |||
@@ -1609,6 +1610,7 @@ function initAdmin() { | |||
$('.oauth2_token_url input, .oauth2_auth_url input, .oauth2_profile_url input, .oauth2_email_url input').attr('required', 'required'); | |||
$('.oauth2_token_url, .oauth2_auth_url, .oauth2_profile_url, .oauth2_email_url').show(); | |||
break; | |||
case 'gitea': | |||
case 'gitlab': | |||
$('.oauth2_token_url input, .oauth2_auth_url input, .oauth2_profile_url input').attr('required', 'required'); | |||
$('.oauth2_token_url, .oauth2_auth_url, .oauth2_profile_url').show(); | |||
@@ -110,6 +110,8 @@ | |||
<span>{{.i18n.Tr "admin.auths.tip.twitter"}}</span> | |||
<li>Discord</li> | |||
<span>{{.i18n.Tr "admin.auths.tip.discord"}}</span> | |||
<li>Gitea</li> | |||
<span>{{.i18n.Tr "admin.auths.tip.gitea"}}</span> | |||
</div> | |||
</div> | |||
</div> | |||
@@ -8,6 +8,7 @@ go: | |||
- 1.9 | |||
- "1.10" | |||
- "1.11" | |||
- "1.12" | |||
- tip | |||
matrix: | |||
@@ -31,6 +31,7 @@ $ go get github.com/markbates/goth | |||
* Eve Online | |||
* Fitbit | |||
* Gitea | |||
* GitHub | |||
* Gitlab | |||
@@ -41,13 +42,17 @@ $ go get github.com/markbates/goth | |||
* Intercom | |||
* Lastfm | |||
* LINE | |||
* Mailru | |||
* Meetup | |||
* MicrosoftOnline | |||
* Naver | |||
* Nextcloud | |||
* OneDrive | |||
* OpenID Connect (auto discovery) | |||
* Paypal | |||
* SalesForce | |||
* Shopify | |||
* Slack | |||
* Soundcloud | |||
* Spotify | |||
@@ -245,19 +245,6 @@ var GetProviderName = getProviderName | |||
func getProviderName(req *http.Request) (string, error) { | |||
// get all the used providers | |||
providers := goth.GetProviders() | |||
// loop over the used providers, if we already have a valid session for any provider (ie. user is already logged-in with a provider), then return that provider name | |||
for _, provider := range providers { | |||
p := provider.Name() | |||
session, _ := Store.Get(req, p+SessionName) | |||
value := session.Values[p] | |||
if _, ok := value.(string); ok { | |||
return p, nil | |||
} | |||
} | |||
// try to get it from the url param "provider" | |||
if p := req.URL.Query().Get("provider"); p != "" { | |||
return p, nil | |||
@@ -278,6 +265,17 @@ func getProviderName(req *http.Request) (string, error) { | |||
return p, nil | |||
} | |||
// As a fallback, loop over the used providers, if we already have a valid session for any provider (ie. user has already begun authentication with a provider), then return that provider name | |||
providers := goth.GetProviders() | |||
session, _ := Store.Get(req, SessionName) | |||
for _, provider := range providers { | |||
p := provider.Name() | |||
value := session.Values[p] | |||
if _, ok := value.(string); ok { | |||
return p, nil | |||
} | |||
} | |||
// if not found then return an empty string with the corresponding error | |||
return "", errors.New("you must select a provider") | |||
} | |||
@@ -0,0 +1,186 @@ | |||
// Package gitea implements the OAuth2 protocol for authenticating users through gitea. | |||
// This package can be used as a reference implementation of an OAuth2 provider for Goth. | |||
package gitea | |||
import ( | |||
"bytes" | |||
"encoding/json" | |||
"io" | |||
"io/ioutil" | |||
"net/http" | |||
"net/url" | |||
"strconv" | |||
"fmt" | |||
"github.com/markbates/goth" | |||
"golang.org/x/oauth2" | |||
) | |||
// These vars define the default Authentication, Token, and Profile URLS for Gitea. | |||
// | |||
// Examples: | |||
// gitea.AuthURL = "https://gitea.acme.com/oauth/authorize | |||
// gitea.TokenURL = "https://gitea.acme.com/oauth/token | |||
// gitea.ProfileURL = "https://gitea.acme.com/api/v3/user | |||
var ( | |||
AuthURL = "https://gitea.com/login/oauth/authorize" | |||
TokenURL = "https://gitea.com/login/oauth/access_token" | |||
ProfileURL = "https://gitea.com/api/v1/user" | |||
) | |||
// Provider is the implementation of `goth.Provider` for accessing Gitea. | |||
type Provider struct { | |||
ClientKey string | |||
Secret string | |||
CallbackURL string | |||
HTTPClient *http.Client | |||
config *oauth2.Config | |||
providerName string | |||
authURL string | |||
tokenURL string | |||
profileURL string | |||
} | |||
// New creates a new Gitea provider and sets up important connection details. | |||
// You should always call `gitea.New` to get a new provider. Never try to | |||
// create one manually. | |||
func New(clientKey, secret, callbackURL string, scopes ...string) *Provider { | |||
return NewCustomisedURL(clientKey, secret, callbackURL, AuthURL, TokenURL, ProfileURL, scopes...) | |||
} | |||
// NewCustomisedURL is similar to New(...) but can be used to set custom URLs to connect to | |||
func NewCustomisedURL(clientKey, secret, callbackURL, authURL, tokenURL, profileURL string, scopes ...string) *Provider { | |||
p := &Provider{ | |||
ClientKey: clientKey, | |||
Secret: secret, | |||
CallbackURL: callbackURL, | |||
providerName: "gitea", | |||
profileURL: profileURL, | |||
} | |||
p.config = newConfig(p, authURL, tokenURL, scopes) | |||
return p | |||
} | |||
// Name is the name used to retrieve this provider later. | |||
func (p *Provider) Name() string { | |||
return p.providerName | |||
} | |||
// SetName is to update the name of the provider (needed in case of multiple providers of 1 type) | |||
func (p *Provider) SetName(name string) { | |||
p.providerName = name | |||
} | |||
func (p *Provider) Client() *http.Client { | |||
return goth.HTTPClientWithFallBack(p.HTTPClient) | |||
} | |||
// Debug is a no-op for the gitea package. | |||
func (p *Provider) Debug(debug bool) {} | |||
// BeginAuth asks Gitea for an authentication end-point. | |||
func (p *Provider) BeginAuth(state string) (goth.Session, error) { | |||
return &Session{ | |||
AuthURL: p.config.AuthCodeURL(state), | |||
}, nil | |||
} | |||
// FetchUser will go to Gitea and access basic information about the user. | |||
func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { | |||
sess := session.(*Session) | |||
user := goth.User{ | |||
AccessToken: sess.AccessToken, | |||
Provider: p.Name(), | |||
RefreshToken: sess.RefreshToken, | |||
ExpiresAt: sess.ExpiresAt, | |||
} | |||
if user.AccessToken == "" { | |||
// data is not yet retrieved since accessToken is still empty | |||
return user, fmt.Errorf("%s cannot get user information without accessToken", p.providerName) | |||
} | |||
response, err := p.Client().Get(p.profileURL + "?access_token=" + url.QueryEscape(sess.AccessToken)) | |||
if err != nil { | |||
if response != nil { | |||
response.Body.Close() | |||
} | |||
return user, err | |||
} | |||
defer response.Body.Close() | |||
if response.StatusCode != http.StatusOK { | |||
return user, fmt.Errorf("%s responded with a %d trying to fetch user information", p.providerName, response.StatusCode) | |||
} | |||
bits, err := ioutil.ReadAll(response.Body) | |||
if err != nil { | |||
return user, err | |||
} | |||
err = json.NewDecoder(bytes.NewReader(bits)).Decode(&user.RawData) | |||
if err != nil { | |||
return user, err | |||
} | |||
err = userFromReader(bytes.NewReader(bits), &user) | |||
return user, err | |||
} | |||
func newConfig(provider *Provider, authURL, tokenURL string, scopes []string) *oauth2.Config { | |||
c := &oauth2.Config{ | |||
ClientID: provider.ClientKey, | |||
ClientSecret: provider.Secret, | |||
RedirectURL: provider.CallbackURL, | |||
Endpoint: oauth2.Endpoint{ | |||
AuthURL: authURL, | |||
TokenURL: tokenURL, | |||
}, | |||
Scopes: []string{}, | |||
} | |||
if len(scopes) > 0 { | |||
for _, scope := range scopes { | |||
c.Scopes = append(c.Scopes, scope) | |||
} | |||
} | |||
return c | |||
} | |||
func userFromReader(r io.Reader, user *goth.User) error { | |||
u := struct { | |||
Name string `json:"full_name"` | |||
Email string `json:"email"` | |||
NickName string `json:"login"` | |||
ID int `json:"id"` | |||
AvatarURL string `json:"avatar_url"` | |||
}{} | |||
err := json.NewDecoder(r).Decode(&u) | |||
if err != nil { | |||
return err | |||
} | |||
user.Email = u.Email | |||
user.Name = u.Name | |||
user.NickName = u.NickName | |||
user.UserID = strconv.Itoa(u.ID) | |||
user.AvatarURL = u.AvatarURL | |||
return nil | |||
} | |||
//RefreshTokenAvailable refresh token is provided by auth provider or not | |||
func (p *Provider) RefreshTokenAvailable() bool { | |||
return true | |||
} | |||
//RefreshToken get new access token based on the refresh token | |||
func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { | |||
token := &oauth2.Token{RefreshToken: refreshToken} | |||
ts := p.config.TokenSource(goth.ContextForClient(p.Client()), token) | |||
newToken, err := ts.Token() | |||
if err != nil { | |||
return nil, err | |||
} | |||
return newToken, err | |||
} |
@@ -0,0 +1,63 @@ | |||
package gitea | |||
import ( | |||
"encoding/json" | |||
"errors" | |||
"strings" | |||
"time" | |||
"github.com/markbates/goth" | |||
) | |||
// Session stores data during the auth process with Gitea. | |||
type Session struct { | |||
AuthURL string | |||
AccessToken string | |||
RefreshToken string | |||
ExpiresAt time.Time | |||
} | |||
var _ goth.Session = &Session{} | |||
// GetAuthURL will return the URL set by calling the `BeginAuth` function on the Gitea provider. | |||
func (s Session) GetAuthURL() (string, error) { | |||
if s.AuthURL == "" { | |||
return "", errors.New(goth.NoAuthUrlErrorMessage) | |||
} | |||
return s.AuthURL, nil | |||
} | |||
// Authorize the session with Gitea and return the access token to be stored for future use. | |||
func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) { | |||
p := provider.(*Provider) | |||
token, err := p.config.Exchange(goth.ContextForClient(p.Client()), params.Get("code")) | |||
if err != nil { | |||
return "", err | |||
} | |||
if !token.Valid() { | |||
return "", errors.New("Invalid token received from provider") | |||
} | |||
s.AccessToken = token.AccessToken | |||
s.RefreshToken = token.RefreshToken | |||
s.ExpiresAt = token.Expiry | |||
return token.AccessToken, err | |||
} | |||
// Marshal the session into a string | |||
func (s Session) Marshal() string { | |||
b, _ := json.Marshal(s) | |||
return string(b) | |||
} | |||
func (s Session) String() string { | |||
return s.Marshal() | |||
} | |||
// UnmarshalSession wil unmarshal a JSON string into a session. | |||
func (p *Provider) UnmarshalSession(data string) (goth.Session, error) { | |||
s := &Session{} | |||
err := json.NewDecoder(strings.NewReader(data)).Decode(s) | |||
return s, err | |||
} |
@@ -303,13 +303,14 @@ github.com/magiconair/properties | |||
github.com/mailru/easyjson/buffer | |||
github.com/mailru/easyjson/jlexer | |||
github.com/mailru/easyjson/jwriter | |||
# github.com/markbates/goth v1.49.0 | |||
# github.com/markbates/goth v1.56.0 | |||
github.com/markbates/goth | |||
github.com/markbates/goth/gothic | |||
github.com/markbates/goth/providers/bitbucket | |||
github.com/markbates/goth/providers/discord | |||
github.com/markbates/goth/providers/dropbox | |||
github.com/markbates/goth/providers/facebook | |||
github.com/markbates/goth/providers/gitea | |||
github.com/markbates/goth/providers/github | |||
github.com/markbates/goth/providers/gitlab | |||
github.com/markbates/goth/providers/gplus | |||