@@ -3,16 +3,107 @@ package api
3
3
import (
4
4
"net/http"
5
5
"strconv"
6
+ "time"
6
7
7
8
"github.com/SkynetLabs/skynet-accounts/database"
8
9
"github.com/julienschmidt/httprouter"
9
10
"gitlab.com/NebulousLabs/errors"
11
+ "go.mongodb.org/mongo-driver/bson/primitive"
10
12
"go.mongodb.org/mongo-driver/mongo"
11
13
)
12
14
15
+ type (
16
+ // APIKeyPOST describes the body of a POST request that creates an API key
17
+ APIKeyPOST struct {
18
+ Public bool `json:"public,string"`
19
+ Skylinks []string `json:"skylinks"`
20
+ }
21
+ // APIKeyPUT describes the request body for updating an API key
22
+ APIKeyPUT struct {
23
+ Skylinks []string
24
+ }
25
+ // APIKeyPATCH describes the request body for updating an API key by
26
+ // providing only the requested changes
27
+ APIKeyPATCH struct {
28
+ Add []string
29
+ Remove []string
30
+ }
31
+ // APIKeyResponse is an API DTO which mirrors database.APIKey.
32
+ APIKeyResponse struct {
33
+ ID primitive.ObjectID `json:"id"`
34
+ UserID primitive.ObjectID `json:"-"`
35
+ Public bool `json:"public,string"`
36
+ Key database.APIKey `json:"-"`
37
+ Skylinks []string `json:"skylinks"`
38
+ CreatedAt time.Time `json:"createdAt"`
39
+ }
40
+ // APIKeyResponseWithKey is an API DTO which mirrors database.APIKey but
41
+ // also reveals the value of the Key field. This should only be used on key
42
+ // creation.
43
+ APIKeyResponseWithKey struct {
44
+ APIKeyResponse
45
+ Key database.APIKey `json:"key"`
46
+ }
47
+ )
48
+
49
+ // Validate checks if the request and its parts are valid.
50
+ func (akp APIKeyPOST ) Validate () error {
51
+ if ! akp .Public && len (akp .Skylinks ) > 0 {
52
+ return errors .New ("public API keys cannot refer to skylinks" )
53
+ }
54
+ var errs []error
55
+ for _ , s := range akp .Skylinks {
56
+ if ! database .ValidSkylinkHash (s ) {
57
+ errs = append (errs , errors .New ("invalid skylink: " + s ))
58
+ }
59
+ }
60
+ if len (errs ) > 0 {
61
+ return errors .Compose (errs ... )
62
+ }
63
+ return nil
64
+ }
65
+
66
+ // APIKeyResponseFromAPIKey creates a new APIKeyResponse from the given API key.
67
+ func APIKeyResponseFromAPIKey (ak database.APIKeyRecord ) * APIKeyResponse {
68
+ return & APIKeyResponse {
69
+ ID : ak .ID ,
70
+ UserID : ak .UserID ,
71
+ Public : ak .Public ,
72
+ Key : ak .Key ,
73
+ Skylinks : ak .Skylinks ,
74
+ CreatedAt : ak .CreatedAt ,
75
+ }
76
+ }
77
+
78
+ // APIKeyResponseWithKeyFromAPIKey creates a new APIKeyResponseWithKey from the
79
+ // given API key.
80
+ func APIKeyResponseWithKeyFromAPIKey (ak database.APIKeyRecord ) * APIKeyResponseWithKey {
81
+ return & APIKeyResponseWithKey {
82
+ APIKeyResponse : APIKeyResponse {
83
+ ID : ak .ID ,
84
+ UserID : ak .UserID ,
85
+ Public : ak .Public ,
86
+ Key : ak .Key ,
87
+ Skylinks : ak .Skylinks ,
88
+ CreatedAt : ak .CreatedAt ,
89
+ },
90
+ Key : ak .Key ,
91
+ }
92
+ }
93
+
13
94
// userAPIKeyPOST creates a new API key for the user.
14
95
func (api * API ) userAPIKeyPOST (u * database.User , w http.ResponseWriter , req * http.Request , _ httprouter.Params ) {
15
- ak , err := api .staticDB .APIKeyCreate (req .Context (), * u )
96
+ var body APIKeyPOST
97
+ err := parseRequestBodyJSON (req .Body , LimitBodySizeLarge , & body )
98
+ if err != nil {
99
+ api .WriteError (w , err , http .StatusBadRequest )
100
+ return
101
+ }
102
+ if err := body .Validate (); err != nil {
103
+ api .WriteError (w , err , http .StatusBadRequest )
104
+ return
105
+ }
106
+ ak , err := api .staticDB .APIKeyCreate (req .Context (), * u , body .Public , body .Skylinks )
16
107
if errors .Contains (err , database .ErrMaxNumAPIKeysExceeded ) {
17
108
err = errors .AddContext (err , "the maximum number of API keys a user can create is " + strconv .Itoa (database .MaxNumAPIKeysPerUser ))
18
109
api .WriteError (w , err , http .StatusBadRequest )
@@ -22,36 +113,107 @@ func (api *API) userAPIKeyPOST(u *database.User, w http.ResponseWriter, req *htt
22
113
api .WriteError (w , err , http .StatusInternalServerError )
23
114
return
24
115
}
25
- // Make the Key visible in JSON form. We do that with an anonymous struct
26
- // because we don't envision that being needed anywhere else in the project.
27
- akWithKey := struct {
28
- database.APIKeyRecord
29
- Key database.APIKey `bson:"key" json:"key"`
30
- }{
31
- * ak ,
32
- ak .Key ,
116
+ api .WriteJSON (w , APIKeyResponseWithKeyFromAPIKey (* ak ))
117
+ }
118
+
119
+ // userAPIKeyGET returns a single API key.
120
+ func (api * API ) userAPIKeyGET (u * database.User , w http.ResponseWriter , req * http.Request , ps httprouter.Params ) {
121
+ akID , err := primitive .ObjectIDFromHex (ps .ByName ("id" ))
122
+ if err != nil {
123
+ api .WriteError (w , err , http .StatusBadRequest )
124
+ return
125
+ }
126
+ ak , err := api .staticDB .APIKeyGet (req .Context (), akID )
127
+ // If there is no such API key or it doesn't exist, return a 404.
128
+ if errors .Contains (err , mongo .ErrNoDocuments ) || (err == nil && ak .UserID != u .ID ) {
129
+ api .WriteError (w , nil , http .StatusNotFound )
130
+ return
33
131
}
34
- api .WriteJSON (w , akWithKey )
132
+ if err != nil {
133
+ api .WriteError (w , err , http .StatusInternalServerError )
134
+ return
135
+ }
136
+ api .WriteJSON (w , APIKeyResponseFromAPIKey (ak ))
35
137
}
36
138
37
- // userAPIKeyGET lists all API keys associated with the user.
38
- func (api * API ) userAPIKeyGET (u * database.User , w http.ResponseWriter , req * http.Request , _ httprouter.Params ) {
139
+ // userAPIKeyLIST lists all API keys associated with the user.
140
+ func (api * API ) userAPIKeyLIST (u * database.User , w http.ResponseWriter , req * http.Request , _ httprouter.Params ) {
39
141
aks , err := api .staticDB .APIKeyList (req .Context (), * u )
40
142
if err != nil {
41
143
api .WriteError (w , err , http .StatusInternalServerError )
42
144
return
43
145
}
44
- api .WriteJSON (w , aks )
146
+ resp := make ([]* APIKeyResponse , 0 , len (aks ))
147
+ for _ , ak := range aks {
148
+ resp = append (resp , APIKeyResponseFromAPIKey (ak ))
149
+ }
150
+ api .WriteJSON (w , resp )
45
151
}
46
152
47
153
// userAPIKeyDELETE removes an API key.
48
154
func (api * API ) userAPIKeyDELETE (u * database.User , w http.ResponseWriter , req * http.Request , ps httprouter.Params ) {
49
- akID := ps .ByName ("id" )
50
- err := api .staticDB .APIKeyDelete (req .Context (), * u , akID )
155
+ akID , err := primitive .ObjectIDFromHex (ps .ByName ("id" ))
156
+ if err != nil {
157
+ api .WriteError (w , err , http .StatusBadRequest )
158
+ return
159
+ }
160
+ err = api .staticDB .APIKeyDelete (req .Context (), * u , akID )
51
161
if err == mongo .ErrNoDocuments {
162
+ api .WriteError (w , err , http .StatusNotFound )
163
+ return
164
+ }
165
+ if err != nil {
166
+ api .WriteError (w , err , http .StatusInternalServerError )
167
+ return
168
+ }
169
+ api .WriteSuccess (w )
170
+ }
171
+
172
+ // userAPIKeyPUT updates an API key. Only possible for public API keys.
173
+ func (api * API ) userAPIKeyPUT (u * database.User , w http.ResponseWriter , req * http.Request , ps httprouter.Params ) {
174
+ akID , err := primitive .ObjectIDFromHex (ps .ByName ("id" ))
175
+ if err != nil {
52
176
api .WriteError (w , err , http .StatusBadRequest )
53
177
return
54
178
}
179
+ var body APIKeyPUT
180
+ err = parseRequestBodyJSON (req .Body , LimitBodySizeLarge , & body )
181
+ if err != nil {
182
+ api .WriteError (w , err , http .StatusBadRequest )
183
+ return
184
+ }
185
+ err = api .staticDB .APIKeyUpdate (req .Context (), * u , akID , body .Skylinks )
186
+ if errors .Contains (err , mongo .ErrNoDocuments ) {
187
+ api .WriteError (w , err , http .StatusNotFound )
188
+ return
189
+ }
190
+ if err != nil {
191
+ api .WriteError (w , err , http .StatusInternalServerError )
192
+ return
193
+ }
194
+ api .WriteSuccess (w )
195
+ }
196
+
197
+ // userAPIKeyPATCH patches an API key. The difference between PUT and PATCH is
198
+ // that PATCH only specifies the changes while PUT provides the expected list of
199
+ // covered skylinks. Only possible for public API keys.
200
+ func (api * API ) userAPIKeyPATCH (u * database.User , w http.ResponseWriter , req * http.Request , ps httprouter.Params ) {
201
+ akID , err := primitive .ObjectIDFromHex (ps .ByName ("id" ))
202
+ if err != nil {
203
+ api .WriteError (w , err , http .StatusBadRequest )
204
+ return
205
+ }
206
+ var body APIKeyPATCH
207
+ err = parseRequestBodyJSON (req .Body , LimitBodySizeLarge , & body )
208
+ if err != nil {
209
+ api .WriteError (w , err , http .StatusBadRequest )
210
+ return
211
+ }
212
+ err = api .staticDB .APIKeyPatch (req .Context (), * u , akID , body .Add , body .Remove )
213
+ if errors .Contains (err , mongo .ErrNoDocuments ) {
214
+ api .WriteError (w , err , http .StatusNotFound )
215
+ return
216
+ }
55
217
if err != nil {
56
218
api .WriteError (w , err , http .StatusInternalServerError )
57
219
return
0 commit comments