-
Notifications
You must be signed in to change notification settings - Fork 0
/
auth.go
151 lines (127 loc) · 3.48 KB
/
auth.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package backend
import (
"context"
"fmt"
"github.com/mootslive/mono/backend/trace"
"strings"
"time"
"github.com/bufbuild/connect-go"
"github.com/golang-jwt/jwt/v4"
"github.com/mootslive/mono/backend/db"
"github.com/segmentio/ksuid"
)
type userGetter interface {
GetUser(ctx context.Context, id string) (db.User, error)
}
// authEngine manages users and enforcing auth
type authEngine struct {
signingKey []byte
issuer string
queries db.TXQuerier
}
func NewAuthEngine(signingKey []byte, queries db.TXQuerier) *authEngine {
return &authEngine{
signingKey: signingKey,
// TODO: Pass in real URI of service
issuer: "https://api.moots.live",
queries: queries,
}
}
type idTokenClaims struct {
jwt.RegisteredClaims
}
func (ae *authEngine) createIDToken(ctx context.Context, userID string) (string, error) {
ctx, span := trace.Start(ctx, "backend/authEngine.createIDToken")
defer span.End()
idToken := jwt.NewWithClaims(jwt.SigningMethodHS256, idTokenClaims{
RegisteredClaims: jwt.RegisteredClaims{
Issuer: ae.issuer,
Audience: []string{
ae.issuer,
},
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now().Add(time.Second * -5)),
// TODO: Determine sane token expiry
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24 * 7)),
Subject: userID,
ID: ksuid.New().String(),
},
})
tok, err := idToken.SignedString(ae.signingKey)
if err != nil {
return "", fmt.Errorf("signing jwt: %w", err)
}
return tok, nil
}
func (ae *authEngine) validateIDToken(ctx context.Context, idToken string) (*idTokenClaims, error) {
ctx, span := trace.Start(ctx, "backend/authEngine.validateIDToken")
defer span.End()
token, err := jwt.ParseWithClaims(
idToken,
&idTokenClaims{},
func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf(
"unexpected signing method: %v", t.Header["alg"],
)
}
return ae.signingKey, nil
})
if err != nil {
return nil, fmt.Errorf("parsing token: %w", err)
}
if !token.Valid {
return nil, fmt.Errorf("invalid token")
}
claims, ok := token.Claims.(*idTokenClaims)
if !ok {
panic("unexpected token claims content")
}
return claims, nil
}
type handleReqOpts struct {
// noAuth enforces that the user should not be authenticated to access this
// endpoint
noAuth bool
}
type authCtx struct {
user *db.User
}
func (ae *authEngine) handleReq(
ctx context.Context, req connect.AnyRequest, opt handleReqOpts,
) (*authCtx, error) {
ctx, span := trace.Start(ctx, "backend/authEngine.handleReq")
defer span.End()
headers := req.Header()
authHeader := headers.Get("Authorization")
if opt.noAuth {
if authHeader != "" {
return nil, fmt.Errorf(
"authorization header provided on no auth endpoint",
)
}
return nil, nil
} else if authHeader == "" {
return nil, fmt.Errorf("no authorization header provided")
}
splitAuthHeader := strings.Split(authHeader, " ")
if len(splitAuthHeader) != 2 {
return nil, fmt.Errorf(
"did not receive two parts in authorization header",
)
}
if splitAuthHeader[0] != "Bearer" {
return nil, fmt.Errorf("received non-bearer authorization header")
}
claims, err := ae.validateIDToken(ctx, splitAuthHeader[1])
if err != nil {
return nil, fmt.Errorf("validating id token: %w", err)
}
user, err := ae.queries.GetUser(ctx, claims.Subject)
if err != nil {
return nil, fmt.Errorf("fetching user: %w", err)
}
return &authCtx{
user: &user,
}, nil
}