Merge branch 'update/jwt-package' of https://github.com/hyperyuri/go-base into hyperyuri-update/jwt-package

This commit is contained in:
dhax 2021-11-15 15:03:15 +01:00
commit d8d770478f
7 changed files with 70 additions and 49 deletions

1
.gitignore vendored
View file

@ -1,6 +1,7 @@
.DS_STORE .DS_STORE
.vscode .vscode
.realize.yaml .realize.yaml
.idea
debug debug
go-base go-base

View file

@ -4,6 +4,8 @@ import (
"context" "context"
"net/http" "net/http"
"github.com/lestrrat-go/jwx/jwt"
"github.com/go-chi/jwtauth/v5" "github.com/go-chi/jwtauth/v5"
"github.com/go-chi/render" "github.com/go-chi/render"
@ -32,7 +34,7 @@ func RefreshTokenFromCtx(ctx context.Context) string {
// response for any unverified tokens and passes the good ones through. // response for any unverified tokens and passes the good ones through.
func Authenticator(next http.Handler) http.Handler { func Authenticator(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, claims, err := jwtauth.FromContext(r.Context()) token, claims, err := jwtauth.FromContext(r.Context())
if err != nil { if err != nil {
logging.GetLogEntry(r).Warn(err) logging.GetLogEntry(r).Warn(err)
@ -40,6 +42,11 @@ func Authenticator(next http.Handler) http.Handler {
return return
} }
if err := jwt.Validate(token); err != nil {
render.Render(w, r, ErrUnauthorized(ErrTokenExpired))
return
}
// Token is authenticated, parse claims // Token is authenticated, parse claims
var c AppClaims var c AppClaims
err = c.ParseClaims(claims) err = c.ParseClaims(claims)
@ -58,13 +65,18 @@ func Authenticator(next http.Handler) http.Handler {
// AuthenticateRefreshJWT checks validity of refresh tokens and is only used for access token refresh and logout requests. It responds with 401 Unauthorized for invalid or expired refresh tokens. // AuthenticateRefreshJWT checks validity of refresh tokens and is only used for access token refresh and logout requests. It responds with 401 Unauthorized for invalid or expired refresh tokens.
func AuthenticateRefreshJWT(next http.Handler) http.Handler { func AuthenticateRefreshJWT(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, claims, err := jwtauth.FromContext(r.Context()) token, claims, err := jwtauth.FromContext(r.Context())
if err != nil { if err != nil {
logging.GetLogEntry(r).Warn(err) logging.GetLogEntry(r).Warn(err)
render.Render(w, r, ErrUnauthorized(ErrTokenUnauthorized)) render.Render(w, r, ErrUnauthorized(ErrTokenUnauthorized))
return return
} }
if err := jwt.Validate(token); err != nil {
render.Render(w, r, ErrUnauthorized(ErrTokenExpired))
return
}
// Token is authenticated, parse refresh token string // Token is authenticated, parse refresh token string
var c RefreshClaims var c RefreshClaims
err = c.ParseClaims(claims) err = c.ParseClaims(claims)

View file

@ -6,11 +6,17 @@ import (
"github.com/lestrrat-go/jwx/jwt" "github.com/lestrrat-go/jwx/jwt"
) )
type CommonClaims struct {
ExpiresAt int64 `json:"exp,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
}
// AppClaims represent the claims parsed from JWT access token. // AppClaims represent the claims parsed from JWT access token.
type AppClaims struct { type AppClaims struct {
ID int `json:"id,omitempty"` ID int `json:"id,omitempty"`
Sub string `json:"sub,omitempty"` Sub string `json:"sub,omitempty"`
Roles []string `json:"roles,omitempty"` Roles []string `json:"roles,omitempty"`
CommonClaims
} }
// ParseClaims parses JWT claims into AppClaims. // ParseClaims parses JWT claims into AppClaims.
@ -47,6 +53,7 @@ func (c *AppClaims) ParseClaims(claims map[string]interface{}) error {
type RefreshClaims struct { type RefreshClaims struct {
ID int `json:"id,omitempty"` ID int `json:"id,omitempty"`
Token string `json:"token,omitempty"` Token string `json:"token,omitempty"`
CommonClaims
} }
// ParseClaims parses the JWT claims into RefreshClaims. // ParseClaims parses the JWT claims into RefreshClaims.

View file

@ -1,13 +1,12 @@
package jwt package jwt
import ( import (
"context"
"crypto/rand" "crypto/rand"
"encoding/json"
"net/http" "net/http"
"time" "time"
"github.com/go-chi/jwtauth/v5" "github.com/go-chi/jwtauth/v5"
"github.com/lestrrat-go/jwx/jwt"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -54,35 +53,40 @@ func (a *TokenAuth) GenTokenPair(accessClaims AppClaims, refreshClaims RefreshCl
// CreateJWT returns an access token for provided account claims. // CreateJWT returns an access token for provided account claims.
func (a *TokenAuth) CreateJWT(c AppClaims) (string, error) { func (a *TokenAuth) CreateJWT(c AppClaims) (string, error) {
token := jwt.New() c.IssuedAt = time.Now().Unix()
token.Set(jwt.IssuedAtKey, time.Now().Unix()) c.ExpiresAt = time.Now().Add(a.JwtExpiry).Unix()
token.Set(jwt.ExpirationKey, time.Now().Add(a.JwtExpiry).Unix())
token.Set(jwt.SubjectKey, c.Sub) claims, err := ParseStructToMap(c)
token.Set(`id`, c.ID)
token.Set(`roles`, c.Roles)
tokenMap, err := token.AsMap(context.Background())
if err != nil { if err != nil {
return "", err return "", err
} }
_, tokenString, err := a.JwtAuth.Encode(tokenMap)
_, tokenString, err := a.JwtAuth.Encode(claims)
return tokenString, err return tokenString, err
} }
func ParseStructToMap(c interface{}) (map[string]interface{}, error) {
var claims map[string]interface{}
inrec, _ := json.Marshal(c)
err := json.Unmarshal(inrec, &claims)
if err != nil {
return nil, err
}
return claims, err
}
// CreateRefreshJWT returns a refresh token for provided token Claims. // CreateRefreshJWT returns a refresh token for provided token Claims.
func (a *TokenAuth) CreateRefreshJWT(c RefreshClaims) (string, error) { func (a *TokenAuth) CreateRefreshJWT(c RefreshClaims) (string, error) {
token := jwt.New() c.IssuedAt = time.Now().Unix()
token.Set(jwt.IssuedAtKey, time.Now().Unix()) c.ExpiresAt = time.Now().Add(a.JwtRefreshExpiry).Unix()
token.Set(jwt.ExpirationKey, time.Now().Add(a.JwtRefreshExpiry).Unix())
token.Set(`token`, c.Token) claims, err := ParseStructToMap(c)
tokenMap, err := token.AsMap(context.Background())
if err != nil { if err != nil {
return "", err return "", err
} }
_, tokenString, err := a.JwtAuth.Encode(tokenMap)
_, tokenString, err := a.JwtAuth.Encode(claims)
return tokenString, err return tokenString, err
} }

View file

@ -2,7 +2,6 @@ package pwdless
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -16,7 +15,6 @@ import (
"time" "time"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
jwx_jwt "github.com/lestrrat-go/jwx/jwt"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/dhax/go-base/auth/jwt" "github.com/dhax/go-base/auth/jwt"
@ -247,6 +245,9 @@ func TestAuthResource_refresh(t *testing.T) {
// } // }
refreshJWT := genRefreshJWT(jwt.RefreshClaims{ refreshJWT := genRefreshJWT(jwt.RefreshClaims{
Token: tc.token, Token: tc.token,
CommonClaims: jwt.CommonClaims{
ExpiresAt: time.Now().Add(time.Minute * tc.exp).UnixNano(),
},
}) })
res, body := testRequest(t, ts, "POST", "/refresh", nil, refreshJWT) res, body := testRequest(t, ts, "POST", "/refresh", nil, refreshJWT)
@ -310,6 +311,9 @@ func TestAuthResource_logout(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
refreshJWT := genRefreshJWT(jwt.RefreshClaims{ refreshJWT := genRefreshJWT(jwt.RefreshClaims{
Token: tc.token, Token: tc.token,
CommonClaims: jwt.CommonClaims{
ExpiresAt: time.Now().Add(time.Minute * tc.exp).UnixNano(),
},
}) })
res, body := testRequest(t, ts, "POST", "/logout", nil, refreshJWT) res, body := testRequest(t, ts, "POST", "/logout", nil, refreshJWT)
@ -355,29 +359,16 @@ func testRequest(t *testing.T, ts *httptest.Server, method, path string, body io
} }
// func genJWT(c jwt.AppClaims) string { // func genJWT(c jwt.AppClaims) string {
// token := jwx_jwt.New() // claims, _ := jwt.ParseStructToMap(c)
// token.Set(jwx_jwt.IssuedAtKey, time.Now().Unix())
// token.Set(jwx_jwt.ExpirationKey, time.Now().Add(time.Duration(time.Minute)).Unix()) // _, tokenString, _ := auth.TokenAuth.JwtAuth.Encode(claims)
// tokenMap, err := token.AsMap(context.Background())
// if err != nil {
// return ""
// }
// _, tokenString, _ := auth.TokenAuth.JwtAuth.Encode(tokenMap)
// return tokenString // return tokenString
// } // }
func genRefreshJWT(c jwt.RefreshClaims) string { func genRefreshJWT(c jwt.RefreshClaims) string {
token := jwx_jwt.New() claims, _ := jwt.ParseStructToMap(c)
token.Set(jwx_jwt.IssuedAtKey, time.Now())
token.Set(jwx_jwt.ExpirationKey, time.Now().Add(time.Duration(time.Minute)))
token.Set(`token`, c.Token) _, tokenString, _ := auth.TokenAuth.JwtAuth.Encode(claims)
tokenMap, err := token.AsMap(context.Background())
if err != nil {
return ""
}
_, tokenString, _ := auth.TokenAuth.JwtAuth.Encode(tokenMap)
return tokenString return tokenString
} }

9
go.mod
View file

@ -17,7 +17,7 @@ require (
github.com/gorilla/css v1.0.0 // indirect github.com/gorilla/css v1.0.0 // indirect
github.com/jaytaylor/html2text v0.0.0-20200220170450-61d9dc4d7195 github.com/jaytaylor/html2text v0.0.0-20200220170450-61d9dc4d7195
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect
github.com/lestrrat-go/jwx v1.1.6 github.com/lestrrat-go/jwx v1.2.10
github.com/mattn/go-runewidth v0.0.8 // indirect github.com/mattn/go-runewidth v0.0.8 // indirect
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/mssola/user_agent v0.5.1 github.com/mssola/user_agent v0.5.1
@ -45,12 +45,13 @@ require (
) )
require ( require (
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/goccy/go-json v0.4.8 // indirect github.com/goccy/go-json v0.7.10 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/lestrrat-go/backoff/v2 v2.0.7 // indirect github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
github.com/lestrrat-go/blackmagic v1.0.0 // indirect
github.com/lestrrat-go/httpcc v1.0.0 // indirect github.com/lestrrat-go/httpcc v1.0.0 // indirect
github.com/lestrrat-go/iter v1.0.1 // indirect github.com/lestrrat-go/iter v1.0.1 // indirect
github.com/lestrrat-go/option v1.0.0 // indirect github.com/lestrrat-go/option v1.0.0 // indirect

15
go.sum
View file

@ -79,8 +79,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0 h1:sgNeV1VRMDzs6rzyPpxyM0jp317hnwiq58Filgag2xw=
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0/go.mod h1:J70FGZSbzsjecRTiTzER+3f1KZLNaXkuv+yeFTKoxM8= github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0/go.mod h1:J70FGZSbzsjecRTiTzER+3f1KZLNaXkuv+yeFTKoxM8=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d h1:1iy2qD6JEhHKKhUOA9IWs7mjco7lnw2qx8FsRI2wirE=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -122,8 +123,9 @@ github.com/go-pg/migrations v6.2.0+incompatible/go.mod h1:DtFiob3rFxsj0He8fye6Ta
github.com/go-pg/pg v7.1.7+incompatible h1:MXeUtzJtt9hie8LSANh0FvaS7ANQ535qD3Q5dtXy8q8= github.com/go-pg/pg v7.1.7+incompatible h1:MXeUtzJtt9hie8LSANh0FvaS7ANQ535qD3Q5dtXy8q8=
github.com/go-pg/pg v7.1.7+incompatible/go.mod h1:a2oXow+aFOrvwcKs3eIA0lNFmMilrxK2sOkB5NWe0vA= github.com/go-pg/pg v7.1.7+incompatible/go.mod h1:a2oXow+aFOrvwcKs3eIA0lNFmMilrxK2sOkB5NWe0vA=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-json v0.4.8 h1:TfwOxfSp8hXH+ivoOk36RyDNmXATUETRdaNWDaZglf8=
github.com/goccy/go-json v0.4.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.4.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.7.10 h1:ulhbuNe1JqE68nMRXXTJRrUu0uhouf0VevLINxQq4Ec=
github.com/goccy/go-json v0.7.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
@ -250,19 +252,22 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lestrrat-go/backoff/v2 v2.0.7 h1:i2SeK33aOFJlUNJZzf2IpXRBvqBBnaGXfY5Xaop/GsE=
github.com/lestrrat-go/backoff/v2 v2.0.7/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= github.com/lestrrat-go/backoff/v2 v2.0.7/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4=
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
github.com/lestrrat-go/codegen v1.0.0/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM= github.com/lestrrat-go/codegen v1.0.0/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM=
github.com/lestrrat-go/httpcc v1.0.0 h1:FszVC6cKfDvBKcJv646+lkh4GydQg2Z29scgUfkOpYc= github.com/lestrrat-go/httpcc v1.0.0 h1:FszVC6cKfDvBKcJv646+lkh4GydQg2Z29scgUfkOpYc=
github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE= github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A= github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A=
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
github.com/lestrrat-go/jwx v1.1.6 h1:VfyUo2PAU4lO/liwhdwiSZ55/QZDLTT3EYY5z9KfwZs=
github.com/lestrrat-go/jwx v1.1.6/go.mod h1:c+R8G7qsaFNmTzYjU98A+sMh8Bo/MJqO9GnpqR+X024= github.com/lestrrat-go/jwx v1.1.6/go.mod h1:c+R8G7qsaFNmTzYjU98A+sMh8Bo/MJqO9GnpqR+X024=
github.com/lestrrat-go/jwx v1.2.10 h1:rz6Ywm3wCRWsy2lyRZ7uHzE4E09m7X9eINaoAEVXCKw=
github.com/lestrrat-go/jwx v1.2.10/go.mod h1:25DcLbNWArPA/Ew5CcBmewl32cJKxOk5cbepBsIJFzw=
github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/pdebug/v3 v3.0.1 h1:3G5sX/aw/TbMTtVc9U7IHBWRZtMvwvBziF1e4HoQtv8=
github.com/lestrrat-go/pdebug/v3 v3.0.1/go.mod h1:za+m+Ve24yCxTEhR59N7UlnJomWwCiIqbJRmKeiADU4= github.com/lestrrat-go/pdebug/v3 v3.0.1/go.mod h1:za+m+Ve24yCxTEhR59N7UlnJomWwCiIqbJRmKeiADU4=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=