@@ -7,8 +7,10 @@ import (
7
7
"errors"
8
8
"fmt"
9
9
"io"
10
+ "mime"
10
11
"net"
11
12
"net/http"
13
+ "net/url"
12
14
"strings"
13
15
"time"
14
16
@@ -31,7 +33,7 @@ const (
31
33
PayloadLimit = 200 * 1024 // 200KB
32
34
)
33
35
34
- func (a * API ) runPostgresHook (ctx context.Context , tx * storage.Connection , name string , input , output any ) ([]byte , error ) {
36
+ func (a * API ) runPostgresHook (ctx context.Context , tx * storage.Connection , hookConfig conf. ExtensibilityPointConfiguration , input , output any ) ([]byte , error ) {
35
37
db := a .db .WithContext (ctx )
36
38
37
39
request , err := json .Marshal (input )
@@ -46,7 +48,7 @@ func (a *API) runPostgresHook(ctx context.Context, tx *storage.Connection, name
46
48
return terr
47
49
}
48
50
49
- if terr := tx .RawQuery (fmt .Sprintf ("select %s(?);" , name ), request ).First (& response ); terr != nil {
51
+ if terr := tx .RawQuery (fmt .Sprintf ("select %s(?);" , hookConfig . HookName ), request ).First (& response ); terr != nil {
50
52
return terr
51
53
}
52
54
@@ -75,7 +77,8 @@ func (a *API) runPostgresHook(ctx context.Context, tx *storage.Connection, name
75
77
return response , nil
76
78
}
77
79
78
- func (a * API ) runHTTPHook (ctx context.Context , r * http.Request , hookConfig conf.ExtensibilityPointConfiguration , input , output any ) ([]byte , error ) {
80
+ func (a * API ) runHTTPHook (r * http.Request , hookConfig conf.ExtensibilityPointConfiguration , input , output any ) ([]byte , error ) {
81
+ ctx := r .Context ()
79
82
client := http.Client {
80
83
Timeout : DefaultHTTPHookTimeout ,
81
84
}
@@ -135,6 +138,15 @@ func (a *API) runHTTPHook(ctx context.Context, r *http.Request, hookConfig conf.
135
138
}
136
139
137
140
defer rsp .Body .Close ()
141
+ // Header.Get is case insensitive
142
+ contentType := rsp .Header .Get ("Content-Type" )
143
+ mediaType , _ , err := mime .ParseMediaType (contentType )
144
+ if err != nil {
145
+ return nil , internalServerError ("Invalid Content-Type header" )
146
+ }
147
+ if mediaType != "application/json" {
148
+ return nil , internalServerError ("Invalid JSON response. Received content-type: " + contentType )
149
+ }
138
150
139
151
switch rsp .StatusCode {
140
152
case http .StatusOK , http .StatusNoContent , http .StatusAccepted :
@@ -172,67 +184,80 @@ func (a *API) runHTTPHook(ctx context.Context, r *http.Request, hookConfig conf.
172
184
return nil , nil
173
185
}
174
186
175
- func (a * API ) invokeHTTPHook (ctx context.Context , r * http.Request , input , output any ) error {
187
+ // invokePostgresHook invokes the hook code. conn can be nil, in which case a new
188
+ // transaction is opened. If calling invokeHook within a transaction, always
189
+ // pass the current transaction, as pool-exhaustion deadlocks are very easy to
190
+ // trigger.
191
+ func (a * API ) invokeHook (conn * storage.Connection , r * http.Request , input , output any , uri string ) error {
192
+ var err error
193
+ var response []byte
194
+ u , err := url .Parse (uri )
195
+ if err != nil {
196
+ return err
197
+ }
198
+
176
199
switch input .(type ) {
177
200
case * hooks.SendSMSInput :
178
201
hookOutput , ok := output .(* hooks.SendSMSOutput )
179
202
if ! ok {
180
203
panic ("output should be *hooks.SendSMSOutput" )
181
204
}
182
- var response []byte
183
- var err error
184
-
185
- if response , err = a .runHTTPHook (ctx , r , a .config .Hook .SendSMS , input , output ); err != nil {
186
- return internalServerError ("Error invoking Send SMS hook." ).WithInternalError (err )
205
+ if response , err = a .runHook (r , conn , a .config .Hook .SendSMS , input , output , u .Scheme ); err != nil {
206
+ return err
187
207
}
188
-
189
208
if err := json .Unmarshal (response , hookOutput ); err != nil {
190
209
return internalServerError ("Error unmarshaling Send SMS output." ).WithInternalError (err )
191
210
}
211
+ if hookOutput .IsError () {
212
+ httpCode := hookOutput .HookError .HTTPCode
213
+
214
+ if httpCode == 0 {
215
+ httpCode = http .StatusInternalServerError
216
+ }
217
+ httpError := & HTTPError {
218
+ HTTPStatus : httpCode ,
219
+ Message : hookOutput .HookError .Message ,
220
+ }
221
+ return httpError .WithInternalError (& hookOutput .HookError )
222
+ }
223
+ return nil
192
224
case * hooks.SendEmailInput :
193
225
hookOutput , ok := output .(* hooks.SendEmailOutput )
194
226
if ! ok {
195
227
panic ("output should be *hooks.SendEmailOutput" )
196
228
}
197
-
198
- var response []byte
199
- var err error
200
-
201
- if response , err = a .runHTTPHook (ctx , r , a .config .Hook .SendEmail , input , output ); err != nil {
202
- return internalServerError ("Error invoking Send Email hook." ).WithInternalError (err )
203
- }
204
- if err != nil {
229
+ if response , err = a .runHook (r , conn , a .config .Hook .SendEmail , input , output , u .Scheme ); err != nil {
205
230
return err
206
231
}
207
-
208
232
if err := json .Unmarshal (response , hookOutput ); err != nil {
209
- return internalServerError ("Error unmarshaling Send Email hook output." ).WithInternalError (err )
233
+ return internalServerError ("Error unmarshaling Send Email output." ).WithInternalError (err )
210
234
}
235
+ if hookOutput .IsError () {
236
+ httpCode := hookOutput .HookError .HTTPCode
211
237
212
- default :
213
- panic ("unknown HTTP hook type" )
214
- }
215
- return nil
216
- }
238
+ if httpCode == 0 {
239
+ httpCode = http .StatusInternalServerError
240
+ }
217
241
218
- // invokePostgresHook invokes the hook code. tx can be nil, in which case a new
219
- // transaction is opened. If calling invokeHook within a transaction, always
220
- // pass the current transaction, as pool-exhaustion deadlocks are very easy to
221
- // trigger.
222
- func ( a * API ) invokePostgresHook ( ctx context. Context , conn * storage. Connection , input , output any ) error {
223
- config := a . config
224
- // Switch based on hook type
225
- switch input .( type ) {
242
+ httpError := & HTTPError {
243
+ HTTPStatus : httpCode ,
244
+ Message : hookOutput . HookError . Message ,
245
+ }
246
+
247
+ return httpError . WithInternalError ( & hookOutput . HookError )
248
+ }
249
+ return nil
226
250
case * hooks.MFAVerificationAttemptInput :
227
251
hookOutput , ok := output .(* hooks.MFAVerificationAttemptOutput )
228
252
if ! ok {
229
253
panic ("output should be *hooks.MFAVerificationAttemptOutput" )
230
254
}
231
-
232
- if _ , err := a .runPostgresHook (ctx , conn , config .Hook .MFAVerificationAttempt .HookName , input , output ); err != nil {
233
- return internalServerError ("Error invoking MFA verification hook." ).WithInternalError (err )
255
+ if response , err = a .runHook (r , conn , a .config .Hook .MFAVerificationAttempt , input , output , u .Scheme ); err != nil {
256
+ return err
257
+ }
258
+ if err := json .Unmarshal (response , hookOutput ); err != nil {
259
+ return internalServerError ("Error unmarshaling MFA Verification Attempt output." ).WithInternalError (err )
234
260
}
235
-
236
261
if hookOutput .IsError () {
237
262
httpCode := hookOutput .HookError .HTTPCode
238
263
@@ -247,18 +272,19 @@ func (a *API) invokePostgresHook(ctx context.Context, conn *storage.Connection,
247
272
248
273
return httpError .WithInternalError (& hookOutput .HookError )
249
274
}
250
-
251
275
return nil
252
276
case * hooks.PasswordVerificationAttemptInput :
253
277
hookOutput , ok := output .(* hooks.PasswordVerificationAttemptOutput )
254
278
if ! ok {
255
279
panic ("output should be *hooks.PasswordVerificationAttemptOutput" )
256
280
}
257
281
258
- if _ , err := a .runPostgresHook (ctx , conn , config .Hook .PasswordVerificationAttempt .HookName , input , output ); err != nil {
259
- return internalServerError ("Error invoking password verification hook." ).WithInternalError (err )
282
+ if response , err = a .runHook (r , conn , a .config .Hook .PasswordVerificationAttempt , input , output , u .Scheme ); err != nil {
283
+ return err
284
+ }
285
+ if err := json .Unmarshal (response , hookOutput ); err != nil {
286
+ return internalServerError ("Error unmarshaling Password Verification Attempt output." ).WithInternalError (err )
260
287
}
261
-
262
288
if hookOutput .IsError () {
263
289
httpCode := hookOutput .HookError .HTTPCode
264
290
@@ -280,9 +306,11 @@ func (a *API) invokePostgresHook(ctx context.Context, conn *storage.Connection,
280
306
if ! ok {
281
307
panic ("output should be *hooks.CustomAccessTokenOutput" )
282
308
}
283
-
284
- if _ , err := a .runPostgresHook (ctx , conn , config .Hook .CustomAccessToken .HookName , input , output ); err != nil {
285
- return internalServerError ("Error invoking access token hook." ).WithInternalError (err )
309
+ if response , err = a .runHook (r , conn , a .config .Hook .CustomAccessToken , input , output , u .Scheme ); err != nil {
310
+ return err
311
+ }
312
+ if err := json .Unmarshal (response , hookOutput ); err != nil {
313
+ return internalServerError ("Error unmarshaling Custom Access Token output." ).WithInternalError (err )
286
314
}
287
315
288
316
if hookOutput .IsError () {
@@ -305,7 +333,6 @@ func (a *API) invokePostgresHook(ctx context.Context, conn *storage.Connection,
305
333
if httpCode == 0 {
306
334
httpCode = http .StatusInternalServerError
307
335
}
308
-
309
336
httpError := & HTTPError {
310
337
HTTPStatus : httpCode ,
311
338
Message : err .Error (),
@@ -314,8 +341,24 @@ func (a *API) invokePostgresHook(ctx context.Context, conn *storage.Connection,
314
341
return httpError
315
342
}
316
343
return nil
344
+ }
345
+ return nil
346
+ }
317
347
348
+ func (a * API ) runHook (r * http.Request , conn * storage.Connection , hookConfig conf.ExtensibilityPointConfiguration , input , output any , scheme string ) ([]byte , error ) {
349
+ ctx := r .Context ()
350
+ var response []byte
351
+ var err error
352
+ switch strings .ToLower (scheme ) {
353
+ case "http" , "https" :
354
+ response , err = a .runHTTPHook (r , hookConfig , input , output )
355
+ case "pg-functions" :
356
+ response , err = a .runPostgresHook (ctx , conn , hookConfig , input , output )
318
357
default :
319
- panic ("unknown Postgres hook input type" )
358
+ return nil , fmt .Errorf ("unsupported protocol: %v only postgres hooks and HTTPS functions are supported at the moment" , scheme )
359
+ }
360
+ if err != nil {
361
+ return nil , internalServerError ("Error running hook URI: %v" , hookConfig .URI ).WithInternalError (err )
320
362
}
363
+ return response , nil
321
364
}
0 commit comments