oauth2

package module
v1.7.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 19, 2024 License: MIT Imports: 8 Imported by: 7

README

OAuth2 - Open Auth 2.0 Client

PkgGoDev Build Status Go Report Card Coverage Status GitHub issues Release

Installation

To install the package, run:

go get github.com/go-zoox/oauth2

Getting Started

Example 1: Using only one oauth2 provider => doreamon
// step1: create oauth2 middleware/handler
// file: oauth2.go
import (
	"log"
	"net/http"
	"regexp"
	"time"

	"github.com/go-zoox/logger"
	"github.com/go-zoox/oauth2"
	"github.com/go-zoox/oauth2/doreamon"
)

type CreateOAuth2DoreamonHandlerConfig struct {
	ClientID     string
	ClientSecret string
	RedirectURI  string
}

func CreateOAuth2DoreamonHandler(cfg *CreateOAuth2DoreamonHandlerConfig) func(
	w http.ResponseWriter,
	r *http.Request,
	CheckUser func(r *http.Request) error,
	RemeberUser func(user *oauth2.User, token *oauth2.Token) error,
	Next func() error,
) error {
	originPathCookieKey := "login_from"

	client, err := doreamon.New(&doreamon.DoreamonConfig{
		ClientID:     cfg.ClientID,
		ClientSecret: cfg.ClientSecret,
		RedirectURI:  cfg.RedirectURI,
		Scope:        "using_doreamon",
		Version:      "2",
	})
	if err != nil {
		panic(err)
	}

	return func(
		w http.ResponseWriter,
		r *http.Request,
		RestoreUser func(r *http.Request) error,
		SaveUser func(user *oauth2.User, token *oauth2.Token) error,
		Next func() error,
	) error {
		if r.Method != "GET" {
			return Next()
		}
		path := r.URL.Path

		if path == "/login" {
			client.Authorize("memos", func(loginUrl string) {
				http.Redirect(w, r, loginUrl, http.StatusFound)
			})
			return nil
		}

		if path == "/logout" {
			client.Logout(func(logoutUrl string) {
				http.Redirect(w, r, logoutUrl, http.StatusFound)
			})
			return nil
		}

		if path == "/login/doreamon/callback" {
			code := r.FormValue("code")
			state := r.FormValue("state")

			client.Callback(code, state, func(user *oauth2.User, token *oauth2.Token, err error) {
				if err != nil {
					log.Println("[OAUTH2] Login Callback Error", err)
					time.Sleep(3 * time.Second)
					http.Redirect(w, r, "/login", http.StatusFound)
					return
				}

				if err := SaveUser(user, token); err != nil {
					logger.Info("failed to save user: %#v", err)
					time.Sleep(1)

					w.WriteHeader(500)
					w.Write([]byte("Failed to create user: " + user.Email))
					return
				}

				http.Redirect(w, r, "/", http.StatusFound)
			})

			return nil
		}

		if matched, _ := regexp.MatchString("\\.(js|css|json)$", path); err == nil && matched {
			return Next()
		}

		if err := RestoreUser(r); err != nil {
			logger.Info("failed to restart user: %#v", err)
			time.Sleep(1)
			http.SetCookie(w, &http.Cookie{
				Name:  "OriginPath",
				Value: path,
			})

			http.Redirect(w, r, "/login", http.StatusFound)
			return nil
		}

		// success
		if OriginPath, err := r.Cookie(originPathCookieKey); err == nil && OriginPath.Value != "" {
			time.Sleep(1)

			http.SetCookie(w, &http.Cookie{
				Name:    originPathCookieKey,
				Value:   "",
				Expires: time.Unix(0, 0),
			})

			http.Redirect(w, r, OriginPath.Value, http.StatusFound)
			return nil
		}

		return Next()
	}
}
// step 2: use as go http middleware
//  here is memos/echo
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
	if os.Getenv("DOREAMON_CLIENT_ID") == "" {
		panic("env DOREAMON_CLIENT_ID is required")
	}
	if os.Getenv("DOREAMON_CLIENT_SECRET") == "" {
		panic("env DOREAMON_CLIENT_SECRET is required")
	}
	if os.Getenv("DOREAMON_REDIRECT_URI") == "" {
		panic("env DOREAMON_REDIRECT_URI is required")
	}

	handler := CreateOAuth2DoreamonHandler(&CreateOAuth2DoreamonHandlerConfig{
		ClientID:     os.Getenv("DOREAMON_CLIENT_ID"),
		ClientSecret: os.Getenv("DOREAMON_CLIENT_SECRET"),
		RedirectURI:  os.Getenv("DOREAMON_REDIRECT_URI"),
	})

	return func(c echo.Context) error {
		return handler(
			c.Response().Writer,
			c.Request(),
			func(r *http.Request) error {
				userID, ok := getUserSession(c)
				if !ok {
					return fmt.Errorf("no user session found")
				}

				c.Set(getUserIDContextKey(), userID)
				userFind := &api.UserFind{
					ID: &userID,
				}
				_, err := s.Store.FindUser(c.Request().Context(), userFind)
				if err != nil {
					return err
				}

				return nil
			},
			func(user *oauth2.User, token *oauth2.Token) error {
				ctx := c.Request().Context()
				// Get Or Create User
				userFind := &api.UserFind{
					Username: &user.Email,
				}
				dbUser, err := s.Store.FindUser(ctx, userFind)
				if err != nil || dbUser == nil {
					role := api.Host
					hostUserFind := api.UserFind{
						Role: &role,
					}
					hostUser, err := s.Store.FindUser(ctx, &hostUserFind)
					if err != nil {
						return err
					}
					if hostUser != nil {
						role = api.NormalUser
					}

					userCreate := &api.UserCreate{
						Username: user.Email,
						Role:     api.Role(role),
						Nickname: user.Nickname,
						Password: random.String(32),
						OpenID:   common.GenUUID(),
					}
					dbUser, err = s.Store.CreateUser(ctx, userCreate)
					if err != nil {
						return err
					}
				}

				if err = setUserSession(c, dbUser); err != nil {
					return err
				}

				return nil
			},
			func() error {
				return next(c)
			},
		)
	}
})
Example 2: Support multiple oauth2 providers: github, wechat, gitee, doreamon
// @TODO connect

License

GoZoox is released under the MIT License.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrConfigAuthURLEmpty = errors.New("oauth2: config auth url is empty")

ErrConfigAuthURLEmpty is the error of AuthURL is empty.

View Source
var ErrConfigClientIDEmpty = errors.New("oauth2: config client id is empty")

ErrConfigClientIDEmpty is the error of ClientID is empty.

View Source
var ErrConfigClientSecretEmpty = errors.New("oauth2: config client secret is empty")

ErrConfigClientSecretEmpty is the error of ClientSecret is empty.

View Source
var ErrConfigRedirectURIEmpty = errors.New("oauth2: config redirect uri is empty")

ErrConfigRedirectURIEmpty is the error of RedirectURI is empty.

View Source
var ErrConfigTokenURLEmpty = errors.New("oauth2: config token url is empty")

ErrConfigTokenURLEmpty is the error of TokenURL is empty.

View Source
var ErrConfigUserInfoURLEmpty = errors.New("oauth2: config user info url is empty")

ErrConfigUserInfoURLEmpty is the error of UserInfoURL is empty.

View Source
var Version = "1.7.2"

Version is the version of this package.

Functions

func ApplyDefaultConfig

func ApplyDefaultConfig(config *Config) (err error)

ApplyDefaultConfig applies the default config.

func Register added in v1.4.0

func Register(provider string, cfg *Config) error

Register registers a new oauth2 service provider.

func ValidateConfig

func ValidateConfig(config *Config) error

ValidateConfig validates the config.

Types

type Client

type Client interface {
	Authorize(state string, callback func(loginUrl string))
	Callback(code, state string, cb func(user *User, token *Token, err error))
	Logout(callback func(logoutUrl string))
	Register(callback func(registerUrl string))
	//
	RefreshToken(refreshToken string) (*Token, error)
}

Client is the oauth2 client interface.

func New

func New(config Config, options ...interface{}) (Client, error)

New creates a OAuth2 client.

type Config

type Config struct {
	Name        string
	AuthURL     string
	TokenURL    string
	UserInfoURL string
	//
	RefershTokenURL string
	//
	LogoutURL string
	//
	RegisterURL string
	// callback url = server url + callback path, example: https://example.com/login/callback
	RedirectURI string
	Scope       string
	//
	ClientID     string
	ClientSecret string

	//
	ClientIDAttributeName     string
	ClientSecretAttributeName string
	RedirectURIAttributeName  string
	ResponseTypeAttributeName string
	ScopeAttributeName        string
	StateAttributeName        string

	// Token.access_token, default: access_token
	AccessTokenAttributeName string
	// Token.refresh_token, default: refresh_token
	RefreshTokenAttributeName string
	// Token.expires_in, default: expires_in
	ExpiresInAttributeName string
	// Token.id_token, default: id_token
	TokenTypeAttributeName string

	// User.username, default: username
	UsernameAttributeName string
	// User.email, default: email
	EmailAttributeName string
	// User.id, default: id
	IDAttributeName string
	// User.nickname, default: nickname
	NicknameAttributeName string
	// User.avatar, default: avatar
	AvatarAttributeName string
	// User.homepage, default: homepage
	HomepageAttributeName string
	// User.permissions, default: permissions
	PermissionsAttributeName string
	// User.groups, default: groups
	GroupsAttributeName string

	// url: login(authorize) + logout
	GetLoginURL    func(cfg *Config, state string) string
	GetLogoutURL   func(cfg *Config) string
	GetRegisterURL func(cfg *Config) string

	// token
	GetAccessTokenResponse func(cfg *Config, code string, state string) (*fetch.Response, error)
	// user
	GetUserResponse func(cfg *Config, token *Token, code string) (*fetch.Response, error)
	//
	RefreshToken func(cfg *Config, refreshToken string) (*fetch.Response, error)

	// base url for identity providers, such as auth0, authing
	BaseURL string
}

Config is the OAuth2 config.

func Get added in v1.4.0

func Get(provider string) (*Config, error)

Get gets the oauth2 service provider by name.

type StepCallback

type StepCallback struct {
}

StepCallback is the callback ...

func (*StepCallback) GetToken

func (oa *StepCallback) GetToken(config *Config, code, state string) (*Token, error)

GetToken gets the token by code and state.

func (*StepCallback) GetUser

func (oa *StepCallback) GetUser(config *Config, token *Token, code string) (*User, error)

GetUser gets the user by token.

type Token

type Token struct {
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
	ExpiresIn    int64  `json:"expires_in"`
	TokenType    string `json:"token_type"`
	// contains filtered or unexported fields
}

Token is the oauth2 token.

func GetToken

func GetToken(config *Config, code string, state string) (*Token, error)

GetToken gets the token by code and state.

func RefreshToken added in v1.7.0

func RefreshToken(config *Config, refreshTokenString string) (*Token, error)

RefreshToken refresh the token by refresh token.

func (*Token) Raw added in v1.5.0

func (u *Token) Raw() *fetch.Response

Raw gets raw data with *fetch.Response.

type User

type User struct {
	ID          string   `json:"id"`
	Username    string   `json:"username"`
	Email       string   `json:"email"`
	Avatar      string   `json:"avatar"`
	Nickname    string   `json:"nickname"`
	Groups      []string `json:"groups"`
	Permissions []string `json:"permissions"`
	// contains filtered or unexported fields
}

User is the oauth2 user.

func GetUser

func GetUser(config *Config, token *Token, code string) (*User, error)

GetUser gets the user by token.

func (*User) Raw added in v1.5.0

func (u *User) Raw() *fetch.Response

Raw gets raw data with *fetch.Response.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL