-
Notifications
You must be signed in to change notification settings - Fork 47
/
auth.go
171 lines (138 loc) · 4.87 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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT License was not distributed with this
// file, you can obtain one at https://opensource.org/licenses/MIT.
//
// Copyright (c) DUSK NETWORK. All rights reserved.
package server
import (
"context"
"crypto/ed25519"
"encoding/base64"
"encoding/json"
"github.com/dusk-network/dusk-blockchain/pkg/rpc"
"github.com/dusk-network/dusk-blockchain/pkg/util/nativeutils/hashset"
"github.com/dusk-network/dusk-protobuf/autogen/go/node"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
type authField int
const (
edPkField authField = iota
)
type (
// Auth struct is a bit weird since it contains an array of known public keys,
// while the client should just be one. Oh well :).
Auth struct {
store *hashset.SafeSet
jwtMan *JWTManager
}
// AuthInterceptor is the grpc interceptor to authenticate grpc calls
// before they get forwarded to the relevant services.
AuthInterceptor struct {
jwtMan *JWTManager
store *hashset.SafeSet
openMethods *hashset.Set
}
)
// NewAuth is the authorization service to manage the session with a client.
func NewAuth(j *JWTManager) (*Auth, *AuthInterceptor) {
safeSet := hashset.NewSafe()
return &Auth{
store: safeSet,
jwtMan: j,
}, &AuthInterceptor{
store: safeSet,
jwtMan: j,
openMethods: rpc.OpenRoutes,
}
}
// CreateSession as defined from the grpc service.
// Calling createSession from an attached client should refreshes the
// session token (i.e. drop the current one and create a new one).
func (a *Auth) CreateSession(ctx context.Context, req *node.SessionRequest) (*node.Session, error) {
edPk := req.GetEdPk()
edSig := req.GetEdSig()
if !ed25519.Verify(ed25519.PublicKey(edPk), edPk, edSig) {
return nil, status.Error(codes.Internal, errAccessDenied.Error())
}
// delete the session key and recreate one
encoded := base64.StdEncoding.EncodeToString(edPk)
token, err := a.jwtMan.Generate(encoded)
if err != nil {
return nil, status.Errorf(codes.Internal, "cannot generate token: %v", err)
}
// add the PK to the set of known PKs
_ = a.store.Add(edPk)
res := &node.Session{AccessToken: token}
return res, nil
}
// DropSession as defined from the grpc service.
func (a *Auth) DropSession(ctx context.Context, req *node.EmptyRequest) (*node.GenericResponse, error) {
// retrieve client public key from context
clientPk, ok := ctx.Value(edPkField).([]byte)
if !ok {
return nil, status.Error(codes.Internal, "unable to retrieve client pk from context")
}
// remove the PK to the set of known PKs
_ = a.store.Remove(clientPk)
res := &node.GenericResponse{Response: "session successfully dropped"}
return res, nil
}
// Unary returns a UnaryServerInterceptor responsible for authentication.
func (ai *AuthInterceptor) Unary() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
tag := "Unary call " + info.FullMethod
log.Tracef("%s", tag)
vctx, err := ai.authorize(ctx, info.FullMethod)
if err != nil {
return nil, err
}
return handler(vctx, req)
}
}
func (ai *AuthInterceptor) authorize(ctx context.Context, method string) (context.Context, error) {
if ai.openMethods.Has([]byte(method)) {
return ctx, nil
}
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return ctx, status.Errorf(codes.Unauthenticated, "metadata not provided")
}
values := md["authorization"]
if len(values) == 0 {
return ctx, status.Error(codes.Unauthenticated, "token not provided")
}
clientPk, err := ai.extractClientPK(values[0])
if err != nil {
return ctx, status.Errorf(codes.Unauthenticated, "error in extracting the client PK: %v", err)
}
return context.WithValue(ctx, edPkField, clientPk), nil
}
func (ai *AuthInterceptor) extractClientPK(a string) ([]byte, error) {
authToken := &rpc.AuthToken{}
// unmarshaling the authToken in the authentication header field
if err := json.Unmarshal([]byte(a), authToken); err != nil {
return nil, status.Errorf(codes.Unauthenticated, "could not unmarshal auth token struct: %v", err)
}
// verify the JWT session token
claims, err := ai.jwtMan.Verify(authToken.AccessToken)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "invalid access token: %v", err)
}
// extract the edPK of the client
b64EdPk := claims.ClientEdPk
edPk, err := base64.StdEncoding.DecodeString(b64EdPk)
if err != nil {
return nil, status.Errorf(codes.Internal, "could not decode sender")
}
if !ai.store.Has(edPk) {
return nil, status.Errorf(codes.Internal, "client does not have an active session")
}
// verify the client signature with extracted public key
if !authToken.Verify(edPk) {
return nil, status.Error(codes.Internal, "error in signature verification")
}
return edPk, nil
}