From 5954ec2501f93cd2b0fb4bf6669d1da3511922a3 Mon Sep 17 00:00:00 2001 From: Ankit Patial Date: Tue, 19 Nov 2024 10:40:30 +0530 Subject: [PATCH] working on auth --- .env.development | 2 +- config/certs/auth | 3 ++ config/certs/auth.pub | 3 ++ config/config.go | 2 +- db/client.go | 63 ++++++++++++++++------- go.mod | 11 ++++- go.sum | 19 +++++++ pkg/user/session.go | 40 ++++++++++----- pkg/user/token.go | 4 +- util/crypto/ed25519.go | 99 +++++++++++++++++++++++++++++++++++++ util/crypto/ed25519_test.go | 16 ++++++ util/jwt/jwt.go | 58 ++++++++++++++++++++++ util/uid/sqid.go | 2 +- 13 files changed, 284 insertions(+), 38 deletions(-) create mode 100644 config/certs/auth create mode 100644 config/certs/auth.pub create mode 100644 util/crypto/ed25519.go create mode 100644 util/crypto/ed25519_test.go create mode 100644 util/jwt/jwt.go diff --git a/.env.development b/.env.development index d412a4b..2e16a2b 100644 --- a/.env.development +++ b/.env.development @@ -5,4 +5,4 @@ GRAPH_URL = http://localhost:3009 VITE_GRAPH_URL = http://localhost:3009 DB_URL = postgresql://root:root@127.0.0.1/rano_dev?search_path=public&sslmode=disable MAILER_TEMPLATES_DIR = mailer/templates -MAILER_FROM_ADDRESS = NoReply +MAILER_FROM_ADDRESS = NoReply \ No newline at end of file diff --git a/config/certs/auth b/config/certs/auth new file mode 100644 index 0000000..20bcc40 --- /dev/null +++ b/config/certs/auth @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEA3EQvTeaEjR5CMk2Ka6/tUl9NaPRpvRggeto+vmReWB4= +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/config/certs/auth.pub b/config/certs/auth.pub new file mode 100644 index 0000000..20bcc40 --- /dev/null +++ b/config/certs/auth.pub @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEA3EQvTeaEjR5CMk2Ka6/tUl9NaPRpvRggeto+vmReWB4= +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/config/config.go b/config/config.go index f5b75ab..2ffac1e 100644 --- a/config/config.go +++ b/config/config.go @@ -38,7 +38,7 @@ type ( WebPort int `env:"WEB_PORT"` WebURL string `env:"WEB_URL"` GraphPort int `env:"GRAPH_PORT"` - GrapURL string `env:"GRAPH_URL"` + GraphURL string `env:"GRAPH_URL"` DbURL string `env:"DB_URL"` MailerTplDir string `env:"MAILER_TEMPLATES_DIR"` MailerFrom string `env:"MAILER_FROM_ADDRESS"` diff --git a/db/client.go b/db/client.go index 3180cf2..64143b0 100644 --- a/db/client.go +++ b/db/client.go @@ -8,6 +8,9 @@ import ( "context" "database/sql" "database/sql/driver" + "fmt" + "reflect" + "strings" "time" "contrib.go.opencensus.io/integrations/ocsql" @@ -58,24 +61,31 @@ func Client() *ent.Client { return cl } -type entity interface { - GetID() (int64, error) -} - // A AuditHook is an example for audit-log hook. func AuditHook(next ent.Mutator) ent.Mutator { return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (v ent.Value, err error) { + // start timer start := time.Now() - defer func() { - saveAudit(ctx, m.Type(), m.Op(), v, err, time.Since(start)) - }() - + // do the operation v, err = next.Mutate(ctx, m) + + // audit log + var id int64 + if v != nil { + ev := reflect.Indirect(reflect.ValueOf(v)) + f := ev.FieldByName("ID") + if f.IsValid() { + id = f.Int() + } + } + + saveAudit(ctx, m.Type(), m.Op(), id, err, time.Since(start)) + return }) } -func saveAudit(ctx context.Context, t string, op ent.Op, v ent.Value, err error, d time.Duration) { +func saveAudit(ctx context.Context, t string, op ent.Op, id int64, err error, d time.Duration) { if t == "Audit" { // skip Audit table operations return @@ -95,15 +105,30 @@ func saveAudit(ctx context.Context, t string, op ent.Op, v ent.Value, err error, entOp = "UpdateOne" } - big. - reqID := ctx.Value("RequestID") - ip := ctx.Value("RequestIP") - ua := ctx.Value("RequestUA") - - if en, ok := v.(entity); ok { - id, _ := en.GetID() - logger.Info("%s %s %s-%s(%d) ip=%s ua=%s t=%v error=%e", reqID, status, op.String(), t, id, ip, ua, d, err) - } else { - logger.Info("%s %s %s-%s ip=%s ua=%s t=%v error=%e", reqID, status, op.String(), t, ip, ua, d, err) + var sb strings.Builder + if reqID, ok := ctx.Value("RequestID").(string); ok { + sb.WriteString(reqID + " ") } + + if err != nil { + sb.WriteString("failed ") + } + + sb.WriteString(fmt.Sprintf("%s:%s:%d ", entOp, t, id)) + + if ip, ok := ctx.Value("RequestIP").(string); ok { + sb.WriteString(fmt.Sprintf("ip=%s ", ip)) + } + + if ua, ok := ctx.Value("RequestUA").(string); ok { + sb.WriteString(fmt.Sprintf("ip=%s ", ua)) + } + + if err != nil { + sb.WriteString(fmt.Sprintf("error=%s ", err)) + } + + sb.WriteString(fmt.Sprintf("t=%s", d)) + + logger.Info(sb.String()) } diff --git a/go.mod b/go.mod index 8ac70e2..0b9e04a 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/99designs/gqlgen v0.17.56 github.com/brianvoe/gofakeit/v7 v7.1.2 github.com/jackc/pgx/v5 v5.7.1 + github.com/lestrrat-go/jwx/v2 v2.1.2 github.com/sqids/sqids-go v0.4.1 github.com/vektah/gqlparser/v2 v2.5.19 gitserver.in/patialtech/mux v0.3.1 @@ -17,11 +18,13 @@ require ( github.com/agext/levenshtein v1.2.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/bmatcuk/doublestar v1.3.4 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-openapi/inflect v0.21.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -31,8 +34,14 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc v1.0.6 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/segmentio/asm v1.2.0 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/zclconf/go-cty v1.15.0 // indirect @@ -51,7 +60,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/go-playground/validator/v10 v10.22.1 github.com/golang-migrate/migrate/v4 v4.18.1 - github.com/google/uuid v1.6.0 // indirect + github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/lib/pq v1.10.9 // indirect diff --git a/go.sum b/go.sum index 6b85742..09dc610 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dhui/dktest v0.4.3 h1:wquqUxAFdcUgabAVLvSCOKOlag5cIZuaOjYIBOWdsR0= @@ -80,6 +82,8 @@ github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y= @@ -134,6 +138,18 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= +github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx/v2 v2.1.2 h1:6poete4MPsO8+LAEVhpdrNI4Xp2xdiafgl2RD89moBc= +github.com/lestrrat-go/jwx/v2 v2.1.2/go.mod h1:pO+Gz9whn7MPdbsqSJzG8TlEpMZCwQDXnFJ+zsUVh8Y= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= @@ -159,6 +175,8 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= @@ -171,6 +189,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/pkg/user/session.go b/pkg/user/session.go index 4861268..f96df72 100644 --- a/pkg/user/session.go +++ b/pkg/user/session.go @@ -17,15 +17,10 @@ import ( "gitserver.in/patialtech/rano/graph/model" "gitserver.in/patialtech/rano/util/crypto" "gitserver.in/patialtech/rano/util/logger" + "gitserver.in/patialtech/rano/util/uid" ) type ( - SessionUser struct { - ID string - Email string - Name string - RoleID int - } AuthUser = model.AuthUser ) @@ -37,7 +32,7 @@ var ( ) func CtxWithUser(ctx context.Context, u *AuthUser) context.Context { - return context.WithValue(ctx, config.AuthUserCtxKey, &SessionUser{ + return context.WithValue(ctx, config.AuthUserCtxKey, &AuthUser{ ID: u.ID, Email: u.Email, Name: u.Name, @@ -45,8 +40,8 @@ func CtxWithUser(ctx context.Context, u *AuthUser) context.Context { }) } -func CtxUser(ctx context.Context) *SessionUser { - u, _ := ctx.Value(config.AuthUserCtxKey).(*SessionUser) +func CtxUser(ctx context.Context) *AuthUser { + u, _ := ctx.Value(config.AuthUserCtxKey).(*AuthUser) return u } @@ -62,16 +57,35 @@ func NewSession(ctx context.Context, email, pwd string) (*AuthUser, error) { // 30 day token life until := time.Now().Add(time.Hour * 24 * 30).UTC() + // user IP + ip, _ := ctx.Value("RequestIP").(string) + // user Agent + ua, _ := ctx.Value("RequestUA").(string) - // create sesion entry in db - db.Client().UserSession.Create(). + // create session entry in db + s, err := db.Client().UserSession.Create(). SetUserID(u.ID). SetIssuedAt(time.Now().UTC()). SetExpiresAt(until). - SetIP(""). - SetUserAgent("") + SetIP(ip). + SetUserAgent(ua). + Save(ctx) + if err != nil { + logger.Error(err) + return nil, ErrUnexpected + } + + sid, err := uid.Encode( + uint64(u.ID), + uint64(s.ID), + ) + if err != nil { + logger.Error(err) + return nil, ErrUnexpected + } return &AuthUser{ + ID: sid, Name: fullName(u.FirstName, *u.MiddleName, u.LastName), }, nil } diff --git a/pkg/user/token.go b/pkg/user/token.go index eca4689..b232736 100644 --- a/pkg/user/token.go +++ b/pkg/user/token.go @@ -19,11 +19,11 @@ var ( // newTokenToVerifyEmail for a user for given duration func newTokenToVerifyEmail(userID int64, d time.Duration) (string, error) { expiresAt := time.Now().Add(d).UTC().UnixMilli() - return uid.Encode([]uint64{ + return uid.Encode( uint64(userID), 1, // identifies that its token to verify email uint64(expiresAt), - }) + ) } // tokenToVerifyEmail will check for valid email token that is yet not expired diff --git a/util/crypto/ed25519.go b/util/crypto/ed25519.go new file mode 100644 index 0000000..c210916 --- /dev/null +++ b/util/crypto/ed25519.go @@ -0,0 +1,99 @@ +package crypto + +import ( + "crypto/ed25519" + "crypto/rand" + "crypto/x509" + "encoding/pem" + "errors" +) + +// NewPersistedED25519 public & private keys +func NewPersistedED25519() ([]byte, []byte, error) { + pub, prv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, nil, err + } + + // marshal public key + pubB, err := marshalEdPublicKey(pub) + if err != nil { + return nil, nil, err + } + + // marshal private key + prvB, err := marshalEdPrivateKey(prv) + if err != nil { + return nil, nil, err + } + + // done + return pubB, prvB, nil +} + +func marshalEdPrivateKey(key ed25519.PrivateKey) ([]byte, error) { + b, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + return nil, err + } + enc := pem.EncodeToMemory( + &pem.Block{ + Type: "PRIVATE KEY", + Bytes: b, + }, + ) + return enc, nil +} + +func parseEdPrivateKey(d []byte) (ed25519.PrivateKey, error) { + block, _ := pem.Decode(d) + if block == nil { + return nil, errors.New("failed to parse PEM block containing the key") + } + + b, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + + switch pub := b.(type) { + case ed25519.PrivateKey: + return pub, nil + default: + return nil, errors.New("key type is not RSA") + } + +} + +func marshalEdPublicKey(key ed25519.PublicKey) ([]byte, error) { + b, err := x509.MarshalPKIXPublicKey(key) + if err != nil { + return nil, err + } + enc := pem.EncodeToMemory( + &pem.Block{ + Type: "PUBLIC KEY", + Bytes: b, + }, + ) + return enc, nil +} + +func parseEdPublicKey(d []byte) (ed25519.PublicKey, error) { + block, _ := pem.Decode(d) + if block == nil { + return nil, errors.New("failed to parse PEM block containing the key") + } + + b, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, err + } + + switch pub := b.(type) { + case ed25519.PublicKey: + return pub, nil + default: + return nil, errors.New("key type is not RSA") + } +} diff --git a/util/crypto/ed25519_test.go b/util/crypto/ed25519_test.go new file mode 100644 index 0000000..1f7ccac --- /dev/null +++ b/util/crypto/ed25519_test.go @@ -0,0 +1,16 @@ +package crypto + +import ( + "testing" +) + +func TestGenerate(t *testing.T) { + public, private, err := NewPersistedED25519() + if err != nil { + t.Error(err) + } else { + t.Log(string(public)) + t.Log(string(private)) + } + +} diff --git a/util/jwt/jwt.go b/util/jwt/jwt.go new file mode 100644 index 0000000..cc111b9 --- /dev/null +++ b/util/jwt/jwt.go @@ -0,0 +1,58 @@ +// Copyright 2024 Patial Tech. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package jwt + +import ( + "crypto/ed25519" + "fmt" + "time" + + "github.com/lestrrat-go/jwx/v2/jwa" + "github.com/lestrrat-go/jwx/v2/jwk" + j "github.com/lestrrat-go/jwx/v2/jwt" +) + +func Sign(key ed25519.PrivateKey, claims map[string]interface{}, issuer string, d time.Duration) (string, error) { + prv, err := jwk.FromRaw(key) + if err != nil { + return "", fmt.Errorf("failed to create JWK, %w", err) + } + + builder := j.NewBuilder(). + Issuer(issuer). + IssuedAt(time.Now()). + Expiration(time.Now().Add(d)) + + for k, v := range claims { + builder = builder.Claim(k, v) + } + + token, err := builder.Build() + signed, err := j.Sign(token, j.WithKey(jwa.EdDSA, prv)) + if err != nil { + return "", fmt.Errorf("failed to generate signed payload, %w", err) + } + + return string(signed), nil +} + +func Parse(key ed25519.PrivateKey, payload string) (j.Token, error) { + prv, err := jwk.FromRaw(key) + if err != nil { + return nil, fmt.Errorf("failed to create JWK, %w", err) + } + + pub, err := jwk.PublicKeyOf(prv) + if err != nil { + return nil, fmt.Errorf("failed on jwk.FromRaw, %w", err) + } + + token, err := j.Parse([]byte(payload), j.WithKey(jwa.EdDSA, pub)) + if err != nil { + return nil, fmt.Errorf("failed on jwk.FromRaw, %w", err) + } + + return token, nil +} diff --git a/util/uid/sqid.go b/util/uid/sqid.go index 70e016e..858884c 100644 --- a/util/uid/sqid.go +++ b/util/uid/sqid.go @@ -13,7 +13,7 @@ var opts sqids.Options = sqids.Options{ } // Encode a slice of IDs into one unique ID -func Encode(ids []uint64) (string, error) { +func Encode(ids ...uint64) (string, error) { s, err := sqids.New() if err != nil { return "", err