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

# 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

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