init from gitlab

This commit is contained in:
texm
2023-04-25 14:33:14 +08:00
parent 6a85a41ff0
commit c8202a5c82
281 changed files with 19861 additions and 1 deletions

View File

@@ -0,0 +1,151 @@
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
}

View File

@@ -0,0 +1,43 @@
package auth
import (
"net/http"
"time"
)
type authCookieValues struct {
name string
value string
// domain string
httpOnly bool
lifetime time.Duration
}
func makeAuthCookie(values authCookieValues) *http.Cookie {
expiresAt := time.Now().Add(values.lifetime)
cookie := &http.Cookie{
Name: values.name,
Value: values.value,
Expires: expiresAt,
HttpOnly: values.httpOnly,
Path: "/",
// Domain: values.domain,
MaxAge: int(values.lifetime.Seconds()),
Secure: true,
SameSite: http.SameSiteLaxMode,
}
return cookie
}
func clearAuthCookie(name string, httpOnly bool, domain string) *http.Cookie {
vals := authCookieValues{
name: name,
value: "",
// domain: domain,
httpOnly: httpOnly,
lifetime: time.Second,
}
cookie := makeAuthCookie(vals)
return cookie
}

View File

@@ -0,0 +1,16 @@
package auth
type GithubAuthenticator struct {
baseAuthenticator
}
func NewGithubAuthenticator(cfg Config) (*GithubAuthenticator, error) {
ghAuth := &GithubAuthenticator{}
// TODO: check these
ghAuth.signingKey = cfg.SigningKey
ghAuth.tokenLifetime = cfg.TokenLifetime
ghAuth.cookieDomain = cfg.CookieDomain
ghAuth.authMethod = MethodGithub
return ghAuth, nil
}

View File

@@ -0,0 +1,14 @@
package auth
type NoneAuthenticator struct {
baseAuthenticator
}
func NewNoneAuthenticator(cfg Config) (*NoneAuthenticator, error) {
noneAuth := &NoneAuthenticator{}
noneAuth.signingKey = cfg.SigningKey
noneAuth.tokenLifetime = cfg.TokenLifetime
noneAuth.cookieDomain = cfg.CookieDomain
noneAuth.authMethod = MethodNone
return noneAuth, nil
}

View File

@@ -0,0 +1,31 @@
package auth
import (
"golang.org/x/crypto/bcrypt"
)
const DefaultBCryptCost = 14
type PasswordAuthenticator struct {
baseAuthenticator
bcryptCost int
}
func NewPasswordAuthenticator(cfg Config, bCryptCost int) (*PasswordAuthenticator, error) {
pwAuth := &PasswordAuthenticator{}
pwAuth.bcryptCost = bCryptCost
pwAuth.signingKey = cfg.SigningKey
pwAuth.tokenLifetime = cfg.TokenLifetime
pwAuth.cookieDomain = cfg.CookieDomain
pwAuth.authMethod = MethodPassword
return pwAuth, nil
}
func (a *PasswordAuthenticator) HashPassword(password []byte) ([]byte, error) {
return bcrypt.GenerateFromPassword(password, a.bcryptCost)
}
func (a *PasswordAuthenticator) VerifyHash(password []byte, hash []byte) bool {
return bcrypt.CompareHashAndPassword(hash, password) == nil
}