Flashii ID auth provider.

This commit is contained in:
flash 2024-11-21 22:46:55 +00:00
parent 73d9e14e80
commit 1e8940c65d
4 changed files with 276 additions and 0 deletions

1
public/assets/img/svg/gitea-flashii.svg generated Normal file
View 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

View 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
}

View 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
}

View file

@ -5,6 +5,7 @@ package oauth2
import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/auth/source/oauth2/flashii"
"github.com/markbates/goth"
"github.com/markbates/goth/providers/azureadv2"
@ -52,6 +53,20 @@ func NewCustomProvider(name, displayName string, customURLSetting *CustomURLSett
var _ GothProvider = &CustomProvider{}
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(
"github", "GitHub", &CustomURLSettings{
TokenURL: availableAttribute(github.TokenURL),