150 lines
3.6 KiB
Go

package providers
import (
"errors"
"fmt"
"github.com/1f349/lavender/auth"
"github.com/1f349/lavender/auth/authContext"
"github.com/1f349/lavender/auth/process"
"github.com/1f349/lavender/database"
"github.com/1f349/lavender/issuer"
"github.com/1f349/lavender/logger"
"github.com/1f349/lavender/utils"
"github.com/google/uuid"
"golang.org/x/oauth2"
"net/http"
"strings"
"time"
)
const memoryCookieName = "lavender-user-memory"
var _ auth.Provider = (*Base)(nil)
var _ auth.Form = (*Base)(nil)
type Base struct {
DB *database.Queries
MyNamespace string
Manager *issuer.Manager
OAuth *OAuthLogin
}
func (b *Base) AccessState() process.State { return process.StateUnauthorized }
func (b *Base) Name() string { return "base" }
func (b *Base) String() string { return "%Provider(base)" }
func (b *Base) RenderTemplate(ctx authContext.TemplateContext) error {
type s struct {
LoginNameMemory bool
UserEmail string
Redirect string
}
req := ctx.Request()
q := req.URL.Query()
userEmail := ctx.LoginProcessData().Email
if userEmail == "" {
cookie, err := req.Cookie(memoryCookieName)
if err == nil && cookie.Valid() == nil {
userEmail = cookie.Value
}
}
ctx.Render(s{
UserEmail: userEmail,
Redirect: q.Get("redirect"),
})
return nil
}
func (b *Base) AttemptLogin(ctx authContext.FormContext) error {
req := ctx.Request()
rw := ctx.ResponseWriter()
if req.FormValue("not-you") != "" {
http.SetCookie(rw, &http.Cookie{
Name: memoryCookieName,
Value: "",
Path: "/",
MaxAge: -1,
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
return auth.RedirectError{
Target: "/login",
Code: http.StatusFound,
}
}
loginName := req.FormValue("email")
if loginName == "" {
return errors.New("email required")
}
rememberMe := req.FormValue("remember-me") != ""
logger.Logger.Debug("Hi", "login", loginName, "remember", rememberMe)
// Set the remember me cookie
if rememberMe {
now := time.Now()
future := now.AddDate(1, 0, 0)
http.SetCookie(rw, &http.Cookie{
Name: memoryCookieName,
Value: loginName,
Path: "/",
Expires: future,
MaxAge: int(future.Sub(now).Seconds()),
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
}
// append local namespace if @ is missing
if !strings.ContainsRune(loginName, '@') {
loginName += "@" + b.MyNamespace
}
user, namespace, err := utils.ParseLoginName(loginName)
if err != nil {
http.Error(rw, "Invalid login name", http.StatusBadRequest)
return fmt.Errorf("invalid login name %s", loginName)
}
login := b.Manager.GetService(namespace)
if login == nil {
http.Error(rw, "No login service defined for this username", http.StatusBadRequest)
return errors.New("no login service defined for this username")
}
// the login is not for this namespace
if login != issuer.MeWellKnown {
// TODO: this is oauth request code start oauth request
// TODO: this could all be 1 function call into oauth
// save state for use later
state := login.Config.Namespace + ":" + uuid.NewString()
b.OAuth.flow.Set(state, flowStateData{loginName, login, req.PostFormValue("redirect")}, time.Now().Add(15*time.Minute))
// generate oauth2 config and redirect to authorize URL
oa2conf := login.OAuth2Config
oa2conf.RedirectURL = b.OAuth.BaseUrl.JoinPath("callback").String()
nextUrl := oa2conf.AuthCodeURL(state, oauth2.SetAuthURLParam("login_name", user))
return auth.RedirectError{
Target: nextUrl,
Code: http.StatusFound,
}
}
ctx.UpdateSession(process.LoginProcessData{
Email: loginName,
State: process.StateBase,
})
return nil
}