Files
dokku-ui/internal/server/auth/auth.go
2023-04-25 14:33:14 +08:00

152 lines
3.4 KiB
Go

package auth
import (
"errors"
"github.com/golang-jwt/jwt"
"github.com/labstack/echo/v4"
"github.com/rs/zerolog/log"
"strings"
"time"
)
const (
ContextUserKey = "user"
DataCookieName = "auth_data"
SignatureCookieName = "auth_sig"
)
type Method string
const (
MethodNone = Method("none")
MethodPassword = Method("password")
MethodGithub = Method("github")
)
var (
ErrNoTokenInContext = errors.New("no token value in context")
ErrNoUserInContext = errors.New("failed to retrieve user from context")
ErrClaimsInvalid = errors.New("failed to cast jwt claims")
)
type Authenticator interface {
NewToken(claims UserClaims) (string, error)
SetUserContext(c echo.Context, contextKey string) error
GetUserFromContext(c echo.Context) (*User, error)
SetTokenCookies(c echo.Context, jwt string) string
ClearTokenCookies(c echo.Context)
GetSigningKey() []byte
GetCookieDomain() string
GetTokenLifetime() time.Duration
GetMethod() Method
}
type User struct {
UserClaims
jwt.StandardClaims
}
type UserClaims struct {
Name string `json:"name"`
}
type baseAuthenticator struct {
signingKey []byte
authMethod Method
cookieDomain string
tokenLifetime time.Duration
}
type Config struct {
SigningKey []byte
CookieDomain string
TokenLifetime time.Duration
}
func (a *baseAuthenticator) NewToken(claims UserClaims) (string, error) {
expiry := time.Now().Add(a.tokenLifetime)
stdClaims := jwt.StandardClaims{
ExpiresAt: expiry.Unix(),
}
user := &User{
UserClaims: claims,
StandardClaims: stdClaims,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, user)
return token.SignedString(a.signingKey)
}
func (a *baseAuthenticator) SetUserContext(c echo.Context, contextKey string) error {
token, ok := c.Get(contextKey).(*jwt.Token)
if !ok {
return ErrNoTokenInContext
}
u, ok := token.Claims.(*User)
if !ok {
return ErrClaimsInvalid
}
c.Set(ContextUserKey, u)
return nil
}
func (a *baseAuthenticator) GetUserFromContext(c echo.Context) (*User, error) {
user, ok := c.Get(ContextUserKey).(*User)
if !ok {
log.Error().Msg("failed to retrieve user from context")
return nil, ErrNoUserInContext
}
return user, nil
}
func (a *baseAuthenticator) SetTokenCookies(c echo.Context, jwt string) string {
splitToken := strings.Split(jwt, ".")
dataCookieValue := strings.Join(splitToken[:2], ".")
signatureCookieValue := splitToken[2]
// accessible to the js frontend
dataCookieValues := authCookieValues{
name: DataCookieName,
value: dataCookieValue,
httpOnly: false,
lifetime: a.tokenLifetime,
}
c.SetCookie(makeAuthCookie(dataCookieValues))
// inaccessible to the js frontend
signatureCookieValues := authCookieValues{
name: SignatureCookieName,
value: signatureCookieValue,
httpOnly: true,
lifetime: a.tokenLifetime,
}
c.SetCookie(makeAuthCookie(signatureCookieValues))
return dataCookieValue
}
func (a *baseAuthenticator) ClearTokenCookies(c echo.Context) {
c.SetCookie(clearAuthCookie(DataCookieName, false, a.cookieDomain))
c.SetCookie(clearAuthCookie(SignatureCookieName, true, a.cookieDomain))
}
func (a *baseAuthenticator) GetSigningKey() []byte {
return a.signingKey
}
func (a *baseAuthenticator) GetMethod() Method {
return a.authMethod
}
func (a *baseAuthenticator) GetCookieDomain() string {
return a.cookieDomain
}
func (a *baseAuthenticator) GetTokenLifetime() time.Duration {
return a.tokenLifetime
}