Flashii ID auth provider.
This commit is contained in:
parent
73d9e14e80
commit
1e8940c65d
4 changed files with 276 additions and 0 deletions
1
public/assets/img/svg/gitea-flashii.svg
generated
Normal file
1
public/assets/img/svg/gitea-flashii.svg
generated
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g id="g285"><path id="rect18" d="M13.564,9.491l-1.16,4.327l-0.778,-0.208l1.16,-4.328l0.778,0.209Z" style="fill:#846d77;"/><path id="rect16" d="M12.776,7.254l-1.159,4.328l-0.778,-0.209l1.159,-4.327l0.778,0.208Z" style="fill:#826879;"/><path id="rect14" d="M11.679,6.174l-1.159,4.327l-0.778,-0.208l1.159,-4.327l0.778,0.208Z" style="fill:#7e647a;"/><path id="rect12" d="M10.376,5.865l-1.16,4.327l-0.778,-0.209l1.16,-4.327l0.778,0.209Z" style="fill:#795d7e;"/><path id="rect10" d="M8.969,5.941l-1.16,4.327l-0.778,-0.209l1.16,-4.327l0.778,0.209Z" style="fill:#75567f;"/><path id="rect8" d="M7.562,6.017l-1.16,4.327l-0.778,-0.209l1.16,-4.327l0.778,0.209Z" style="fill:#704f81;"/><path id="rect6" d="M6.258,5.707l-1.159,4.327l-0.778,-0.208l1.159,-4.327l0.778,0.208Z" style="fill:#6b4882;"/><path id="rect4" d="M5.161,4.627l-1.159,4.327l-0.778,-0.208l1.159,-4.328l0.778,0.209Z" style="fill:#674183;"/><path id="rect2" d="M4.374,2.39l-1.16,4.328l-0.778,-0.209l1.16,-4.327l0.778,0.208Z" style="fill:#643b84;"/></g></svg>
|
After Width: | Height: | Size: 1.3 KiB |
186
services/auth/source/oauth2/flashii/flashii.go
Normal file
186
services/auth/source/oauth2/flashii/flashii.go
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
// Basically copy-pasted from https://github.com/markbates/goth/blob/e55b0146e36b7e1fbaa7453d353861136bf4ed28/providers/github/github.go
|
||||||
|
|
||||||
|
package flashii
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/markbates/goth"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
AuthURL = "https://id.flashii.net/oauth2/authorise"
|
||||||
|
TokenURL = "https://api.flashii.net/oauth2/token"
|
||||||
|
ProfileURL = "https://api.flashii.net/v1/me"
|
||||||
|
)
|
||||||
|
|
||||||
|
var generateCodeVerifier = oauth2.GenerateVerifier
|
||||||
|
|
||||||
|
func New(clientKey, secret, callbackURL string, scopes ...string) *Provider {
|
||||||
|
return NewCustomisedURL(clientKey, secret, callbackURL, AuthURL, TokenURL, ProfileURL, scopes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCustomisedURL(clientKey, secret, callbackURL, authURL, tokenURL, profileURL string, scopes ...string) *Provider {
|
||||||
|
p := &Provider{
|
||||||
|
ClientKey: clientKey,
|
||||||
|
Secret: secret,
|
||||||
|
CallbackURL: callbackURL,
|
||||||
|
providerName: "flashii",
|
||||||
|
profileURL: profileURL,
|
||||||
|
GenerateCodeVerifier: generateCodeVerifier,
|
||||||
|
}
|
||||||
|
p.config = newConfig(p, authURL, tokenURL, scopes)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
type Provider struct {
|
||||||
|
ClientKey string
|
||||||
|
Secret string
|
||||||
|
CallbackURL string
|
||||||
|
HTTPClient *http.Client
|
||||||
|
config *oauth2.Config
|
||||||
|
providerName string
|
||||||
|
profileURL string
|
||||||
|
GenerateCodeVerifier func() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) Name() string {
|
||||||
|
return p.providerName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) SetName(name string) {
|
||||||
|
p.providerName = name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) Client() *http.Client {
|
||||||
|
return goth.HTTPClientWithFallBack(p.HTTPClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) Debug(debug bool) {}
|
||||||
|
|
||||||
|
func (p *Provider) BeginAuth(state string) (goth.Session, error) {
|
||||||
|
if p.GenerateCodeVerifier == nil {
|
||||||
|
p.GenerateCodeVerifier = generateCodeVerifier
|
||||||
|
}
|
||||||
|
|
||||||
|
verifier := p.GenerateCodeVerifier()
|
||||||
|
url := p.config.AuthCodeURL(state, oauth2.S256ChallengeOption(verifier))
|
||||||
|
session := &Session{
|
||||||
|
AuthURL: url,
|
||||||
|
CodeVerifier: verifier,
|
||||||
|
}
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.AccessToken == "" {
|
||||||
|
return user, fmt.Errorf("%s cannot get user information without accessToken", p.providerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", p.profileURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("Authorization", "Bearer "+sess.AccessToken)
|
||||||
|
response, err := p.Client().Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
return user, fmt.Errorf("Flashii API responded with a %d trying to fetch user information", 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)
|
||||||
|
if err != nil {
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func userFromReader(reader io.Reader, user *goth.User) error {
|
||||||
|
type FlashiiUserAvatarRes struct {
|
||||||
|
Resolution int32 `json:"res"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
u := struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
AvatarURLs []FlashiiUserAvatarRes `json:"avatar_urls"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
err := json.NewDecoder(reader).Decode(&u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
user.UserID = u.Id
|
||||||
|
user.NickName = u.Name
|
||||||
|
user.Email = u.Email
|
||||||
|
user.Description = u.Title
|
||||||
|
user.AvatarURL = u.AvatarURLs[0].URL
|
||||||
|
|
||||||
|
return 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{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, scope := range scopes {
|
||||||
|
c.Scopes = append(c.Scopes, scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) RefreshTokenAvailable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
74
services/auth/source/oauth2/flashii/session.go
Normal file
74
services/auth/source/oauth2/flashii/session.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
// Basically copy-pasted from https://github.com/markbates/goth/blob/e55b0146e36b7e1fbaa7453d353861136bf4ed28/providers/gitlab/session.go
|
||||||
|
|
||||||
|
package flashii
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/markbates/goth"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
AuthURL string
|
||||||
|
AccessToken string
|
||||||
|
RefreshToken string
|
||||||
|
ExpiresAt time.Time
|
||||||
|
CodeVerifier string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ goth.Session = &Session{}
|
||||||
|
|
||||||
|
func (s Session) GetAuthURL() (string, error) {
|
||||||
|
if s.AuthURL == "" {
|
||||||
|
return "", errors.New(goth.NoAuthUrlErrorMessage)
|
||||||
|
}
|
||||||
|
return s.AuthURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) {
|
||||||
|
p := provider.(*Provider)
|
||||||
|
|
||||||
|
var authParams []oauth2.AuthCodeOption
|
||||||
|
|
||||||
|
redirectURL := params.Get("redirect_uri")
|
||||||
|
if redirectURL != "" {
|
||||||
|
authParams = append(authParams, oauth2.SetAuthURLParam("redirect_uri", redirectURL))
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.CodeVerifier != "" {
|
||||||
|
authParams = append(authParams, oauth2.VerifierOption(s.CodeVerifier))
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := p.config.Exchange(goth.ContextForClient(p.Client()), params.Get("code"), authParams...)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Session) Marshal() string {
|
||||||
|
b, _ := json.Marshal(s)
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Session) String() string {
|
||||||
|
return s.Marshal()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) UnmarshalSession(data string) (goth.Session, error) {
|
||||||
|
s := &Session{}
|
||||||
|
err := json.NewDecoder(strings.NewReader(data)).Decode(s)
|
||||||
|
return s, err
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ package oauth2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/services/auth/source/oauth2/flashii"
|
||||||
|
|
||||||
"github.com/markbates/goth"
|
"github.com/markbates/goth"
|
||||||
"github.com/markbates/goth/providers/azureadv2"
|
"github.com/markbates/goth/providers/azureadv2"
|
||||||
|
@ -52,6 +53,20 @@ func NewCustomProvider(name, displayName string, customURLSetting *CustomURLSett
|
||||||
var _ GothProvider = &CustomProvider{}
|
var _ GothProvider = &CustomProvider{}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
RegisterGothProvider(NewCustomProvider(
|
||||||
|
"flashii", "Flashii ID", &CustomURLSettings{
|
||||||
|
TokenURL: availableAttribute(flashii.TokenURL),
|
||||||
|
AuthURL: availableAttribute(flashii.AuthURL),
|
||||||
|
ProfileURL: availableAttribute(flashii.ProfileURL),
|
||||||
|
},
|
||||||
|
func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) {
|
||||||
|
scopes = append(scopes, "identify")
|
||||||
|
if setting.OAuth2Client.EnableAutoRegistration {
|
||||||
|
scopes = append(scopes, "identify:email")
|
||||||
|
}
|
||||||
|
return flashii.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...), nil
|
||||||
|
}))
|
||||||
|
|
||||||
RegisterGothProvider(NewCustomProvider(
|
RegisterGothProvider(NewCustomProvider(
|
||||||
"github", "GitHub", &CustomURLSettings{
|
"github", "GitHub", &CustomURLSettings{
|
||||||
TokenURL: availableAttribute(github.TokenURL),
|
TokenURL: availableAttribute(github.TokenURL),
|
||||||
|
|
Loading…
Reference in a new issue