mirror of
https://github.com/linka-cloud/grpc.git
synced 2024-11-25 12:26:26 +00:00
wip: add token & session forwarding
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
This commit is contained in:
parent
abe69f1c80
commit
e5d84975a2
24
auth/context.go
Normal file
24
auth/context.go
Normal file
@ -0,0 +1,24 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type authKey struct{}
|
||||
|
||||
func Context[T any](ctx context.Context, auth T) context.Context {
|
||||
return context.WithValue(ctx, authKey{}, auth)
|
||||
}
|
||||
|
||||
func FromContext[T any](ctx context.Context) (T, bool) {
|
||||
auth, ok := ctx.Value(authKey{}).(T)
|
||||
return auth, ok
|
||||
}
|
||||
|
||||
func MustTokenFromContext[T any](ctx context.Context) T {
|
||||
auth, ok := FromContext[T](ctx)
|
||||
if !ok {
|
||||
panic("no auth in context")
|
||||
}
|
||||
return auth
|
||||
}
|
116
auth/session.go
Normal file
116
auth/session.go
Normal file
@ -0,0 +1,116 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/gorilla/sessions"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// TokenResponse is a proto message that contains a token.
|
||||
// It typically is a response from a login service.
|
||||
type TokenResponse interface {
|
||||
proto.Message
|
||||
GetToken() string
|
||||
}
|
||||
|
||||
// Handler is an interface that can be used to handle token forwarding.
|
||||
// It can be used to forward tokens from grpc messages to cookies and vice versa.
|
||||
// T is the token response type that creates the session and contains the token to be forwarded as a cookie.
|
||||
// U is the message that closes the session.
|
||||
type Handler interface {
|
||||
CookieToAuth(ctx context.Context) context.Context
|
||||
ForwardTokenAsCookie(ctx context.Context, w http.ResponseWriter, p proto.Message) error
|
||||
ForwardCookieAsToken(ctx context.Context, request *http.Request) metadata.MD
|
||||
SendAuthCookie(ctx context.Context, token string) error
|
||||
}
|
||||
|
||||
func NewHandler(sessionName string, sessions *sessions.CookieStore) Handler {
|
||||
return &handler{sessionName: sessionName, sessions: sessions}
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
sessionName string
|
||||
sessions *sessions.CookieStore
|
||||
}
|
||||
|
||||
// ForwardTokenAsCookie forwards the token from the message to the cookie store.
|
||||
func (h *handler) ForwardTokenAsCookie(_ context.Context, w http.ResponseWriter, p proto.Message) error {
|
||||
switch m := any(p).(type) {
|
||||
case TokenResponse:
|
||||
sess := h.newSession(h.sessionName)
|
||||
sess.Values["token"] = m.GetToken()
|
||||
// request is not available in the context, but session.Save does not actually use it
|
||||
return h.sessions.Save(nil, w, sess)
|
||||
default:
|
||||
sess := h.newSession(h.sessionName)
|
||||
sess.Options.MaxAge = -1
|
||||
return h.sessions.Save(nil, w, sess)
|
||||
}
|
||||
}
|
||||
|
||||
// ForwardCookieAsToken forwards the token from the cookie store to the grpc metadata.
|
||||
func (h *handler) ForwardCookieAsToken(_ context.Context, request *http.Request) metadata.MD {
|
||||
sess, err := h.sessions.Get(request, h.sessionName)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
tk, ok := sess.Values["token"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return metadata.Pairs("authorization", fmt.Sprintf("Bearer %v", tk))
|
||||
}
|
||||
|
||||
// SendAuthCookie sets the cookie in the response.
|
||||
func (h *handler) SendAuthCookie(ctx context.Context, token string) error {
|
||||
sess := h.newSession(h.sessionName)
|
||||
sess.Values["token"] = token
|
||||
if token == "" {
|
||||
sess.Options.MaxAge = -1
|
||||
}
|
||||
encoded, err := securecookie.EncodeMulti(sess.Name(), sess.Values, h.sessions.Codecs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return grpc.SetHeader(ctx, metadata.Pairs("set-cookie", sessions.NewCookie(sess.Name(), encoded, sess.Options).String()))
|
||||
}
|
||||
|
||||
func (h *handler) newSession(name string) *sessions.Session {
|
||||
session := sessions.NewSession(h.sessions, name)
|
||||
opts := *h.sessions.Options
|
||||
session.Options = &opts
|
||||
session.IsNew = true
|
||||
return session
|
||||
}
|
||||
|
||||
// CookieToAuth forwards the cookie authorization to the grpc metadata.
|
||||
func (h *handler) CookieToAuth(ctx context.Context) context.Context {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return ctx
|
||||
}
|
||||
// if we have an authorization header, we don't need to check for cookies here
|
||||
if len(md.Get("authorization")) > 0 {
|
||||
return ctx
|
||||
}
|
||||
// no other way to easily parse cookies
|
||||
header := http.Header{}
|
||||
for _, v := range md.Get("cookie") {
|
||||
header.Add("Cookie", v)
|
||||
}
|
||||
md.Delete("cookie")
|
||||
sess, err := h.sessions.Get(&http.Request{Header: header}, h.sessionName)
|
||||
if err != nil {
|
||||
return ctx
|
||||
}
|
||||
if v, ok := sess.Values["token"]; ok {
|
||||
md["authorization"] = []string{fmt.Sprintf("Bearer %s", v)}
|
||||
}
|
||||
return metadata.NewIncomingContext(ctx, md)
|
||||
}
|
4
go.mod
4
go.mod
@ -1,6 +1,6 @@
|
||||
module go.linka.cloud/grpc-toolkit
|
||||
|
||||
go 1.22.0
|
||||
go 1.23
|
||||
|
||||
toolchain go1.23.2
|
||||
|
||||
@ -15,6 +15,8 @@ require (
|
||||
github.com/go-logr/logr v1.4.2
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/securecookie v1.1.2
|
||||
github.com/gorilla/sessions v1.4.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0
|
||||
|
6
go.sum
6
go.sum
@ -278,6 +278,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
@ -309,6 +311,10 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
||||
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
|
Loading…
Reference in New Issue
Block a user