Categorygithub.com/go-zoox/oauth2
modulepackage
1.8.0
Repository: https://github.com/go-zoox/oauth2.git
Documentation: pkg.go.dev

# 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.

# Packages

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author

# Functions

ApplyDefaultConfig applies the default config.
Get gets the oauth2 service provider by name.
GetToken gets the token by code and state.
GetUser gets the user by token.
New creates a OAuth2 client.
RefreshToken refresh the token by refresh token.
Register registers a new oauth2 service provider.
ValidateConfig validates the config.

# Variables

ErrConfigAuthURLEmpty is the error of AuthURL is empty.
ErrConfigClientIDEmpty is the error of ClientID is empty.
ErrConfigClientSecretEmpty is the error of ClientSecret is empty.
ErrConfigRedirectURIEmpty is the error of RedirectURI is empty.
ErrConfigTokenURLEmpty is the error of TokenURL is empty.
ErrConfigUserInfoURLEmpty is the error of UserInfoURL is empty.
Version is the version of this package.

# Structs

Config is the OAuth2 config.
StepCallback is the callback ...
Token is the oauth2 token.
User is the oauth2 user.

# Interfaces

Client is the oauth2 client interface.