To install the package, run:
go get github.com/go-zoox/oauth2
// 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)
},
)
}
})
// @TODO connect
GoZoox is released under the MIT License.