working on auth
This commit is contained in:
		| @@ -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<no-reply@my-app.com> | ||||
| MAILER_FROM_ADDRESS = NoReply<no-reply@my-app.com> | ||||
							
								
								
									
										3
									
								
								config/certs/auth
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								config/certs/auth
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| -----BEGIN PUBLIC KEY----- | ||||
| MCowBQYDK2VwAyEA3EQvTeaEjR5CMk2Ka6/tUl9NaPRpvRggeto+vmReWB4= | ||||
| -----END PUBLIC KEY----- | ||||
							
								
								
									
										3
									
								
								config/certs/auth.pub
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								config/certs/auth.pub
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| -----BEGIN PUBLIC KEY----- | ||||
| MCowBQYDK2VwAyEA3EQvTeaEjR5CMk2Ka6/tUl9NaPRpvRggeto+vmReWB4= | ||||
| -----END PUBLIC KEY----- | ||||
| @@ -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"` | ||||
|   | ||||
							
								
								
									
										63
									
								
								db/client.go
									
									
									
									
									
								
							
							
						
						
									
										63
									
								
								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()) | ||||
| } | ||||
|   | ||||
							
								
								
									
										11
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								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 | ||||
|   | ||||
							
								
								
									
										19
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								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= | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										99
									
								
								util/crypto/ed25519.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								util/crypto/ed25519.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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") | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										16
									
								
								util/crypto/ed25519_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								util/crypto/ed25519_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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)) | ||||
| 	} | ||||
|  | ||||
| } | ||||
							
								
								
									
										58
									
								
								util/jwt/jwt.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								util/jwt/jwt.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user