Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/public-api-server/pkg/auth/middleware.go
2500 views
1
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package auth
6
7
import (
8
"context"
9
"fmt"
10
"net/http"
11
12
"github.com/bufbuild/connect-go"
13
"github.com/gitpod-io/gitpod/components/public-api/go/config"
14
"github.com/gitpod-io/gitpod/public-api-server/pkg/jws"
15
)
16
17
type Interceptor struct {
18
accessToken string
19
20
sessionCfg config.SessionConfig
21
verifier jws.Verifier
22
}
23
24
func (i *Interceptor) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc {
25
return connect.UnaryFunc(func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
26
if req.Spec().IsClient {
27
ctx = TokenToContext(ctx, NewAccessToken(i.accessToken))
28
29
req.Header().Add(authorizationHeaderKey, bearerPrefix+i.accessToken)
30
return next(ctx, req)
31
}
32
33
token, err := i.tokenFromHeaders(ctx, req.Header())
34
if err != nil {
35
return nil, err
36
}
37
38
return next(TokenToContext(ctx, token), req)
39
})
40
}
41
42
func (i *Interceptor) WrapStreamingClient(next connect.StreamingClientFunc) connect.StreamingClientFunc {
43
return func(ctx context.Context, s connect.Spec) connect.StreamingClientConn {
44
ctx = TokenToContext(ctx, NewAccessToken(i.accessToken))
45
conn := next(ctx, s)
46
conn.RequestHeader().Add(authorizationHeaderKey, bearerPrefix+i.accessToken)
47
return conn
48
}
49
}
50
51
func (i *Interceptor) WrapStreamingHandler(next connect.StreamingHandlerFunc) connect.StreamingHandlerFunc {
52
return func(ctx context.Context, conn connect.StreamingHandlerConn) error {
53
token, err := i.tokenFromHeaders(ctx, conn.RequestHeader())
54
if err != nil {
55
return err
56
}
57
return next(TokenToContext(ctx, token), conn)
58
}
59
}
60
61
// NewServerInterceptor creates a server-side interceptor which validates that an incoming request contains a valid Authorization header
62
func NewServerInterceptor(sessionCfg config.SessionConfig, verifier jws.Verifier) connect.Interceptor {
63
return &Interceptor{
64
sessionCfg: sessionCfg,
65
verifier: verifier,
66
}
67
}
68
69
func (i *Interceptor) tokenFromHeaders(ctx context.Context, headers http.Header) (Token, error) {
70
bearerToken, err := BearerTokenFromHeaders(headers)
71
if err == nil {
72
return NewAccessToken(bearerToken), nil
73
}
74
75
rawCookie := headers.Get("Cookie")
76
if rawCookie == "" {
77
return Token{}, connect.NewError(connect.CodeUnauthenticated, fmt.Errorf("No access token or cookie credentials available on request."))
78
}
79
80
// Extract the JWT token from Cookies
81
cookies := cookiesFromString(rawCookie, i.sessionCfg.Cookie.Name)
82
if len(cookies) == 0 {
83
return Token{}, connect.NewError(connect.CodeUnauthenticated, fmt.Errorf("No cookie credentials present on request."))
84
}
85
86
var cookie *http.Cookie
87
for _, c := range cookies {
88
_, err := VerifySessionJWT(c.Value, i.verifier, i.sessionCfg.Issuer)
89
if err == nil {
90
cookie = c
91
break
92
}
93
}
94
if cookie == nil {
95
return Token{}, connect.NewError(connect.CodeUnauthenticated, fmt.Errorf("JWT session could not be verified."))
96
}
97
98
return NewCookieToken(cookie.String()), nil
99
}
100
101
// NewClientInterceptor creates a client-side interceptor which injects token as a Bearer Authorization header
102
func NewClientInterceptor(accessToken string) connect.Interceptor {
103
return &Interceptor{
104
accessToken: accessToken,
105
}
106
}
107
108
func cookiesFromString(rawCookieHeader, name string) []*http.Cookie {
109
// To access the cookie as an http.Cookie, we sadly have to construct a request with the appropriate header such
110
// that we can then extract the cookie.
111
header := http.Header{}
112
header.Add("Cookie", rawCookieHeader)
113
req := http.Request{Header: header}
114
115
var cookies []*http.Cookie
116
for _, c := range req.Cookies() {
117
if c.Name == name {
118
cookies = append(cookies, c)
119
}
120
}
121
return cookies
122
}
123
124