Skip to content

Commit 503859d

Browse files
authored
Merge pull request #118 from suborbital/connor/rcap-headers
Authentication headers for Wasm capabilities
2 parents 8a214a9 + 1ee15e2 commit 503859d

File tree

20 files changed

+491
-49
lines changed

20 files changed

+491
-49
lines changed

.github/workflows/sanity.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ jobs:
3333
3434
- name: Run test
3535
run: |
36+
export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}
3637
go test -v ./...

rcap/authentication.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package rcap
2+
3+
import (
4+
"os"
5+
"strings"
6+
)
7+
8+
// AuthProvider is a provider for various kinds of auth
9+
type AuthProvider interface {
10+
HeaderForDomain(string) *AuthHeader
11+
}
12+
13+
// AuthHeader is an HTTP header designed to authenticate requests
14+
type AuthHeader struct {
15+
HeaderType string `json:"headerType"`
16+
Value string `json:"value"`
17+
}
18+
19+
// AuthProviderConfig is a config for the default auth provider
20+
type AuthProviderConfig struct {
21+
// Headers is a map between domains and auth header that should be added to requests to those domains
22+
Headers map[string]AuthHeader `json:"headers"`
23+
}
24+
25+
type defaultAuthProvider struct {
26+
config *AuthProviderConfig
27+
28+
augmentedHeaders map[string]AuthHeader
29+
}
30+
31+
// DefaultAuthProvider creates the default static auth provider
32+
func DefaultAuthProvider(config *AuthProviderConfig) AuthProvider {
33+
ap := &defaultAuthProvider{
34+
config: config,
35+
augmentedHeaders: map[string]AuthHeader{},
36+
}
37+
38+
return ap
39+
}
40+
41+
// HeadersForDomain returns the appropriate auth headers for the given domain
42+
func (ap *defaultAuthProvider) HeaderForDomain(domain string) *AuthHeader {
43+
header, ok := ap.augmentedHeaders[domain]
44+
if !ok {
45+
if ap.config == nil {
46+
return nil
47+
}
48+
49+
origignalHeader, exists := ap.config.Headers[domain]
50+
if !exists {
51+
return nil
52+
}
53+
54+
augmented := augmentHeaderFromEnv(origignalHeader)
55+
56+
ap.augmentedHeaders[domain] = augmented
57+
header = augmented
58+
}
59+
60+
return &header
61+
}
62+
63+
// augmentHeadersFromEnv takes a an AuthHeader and replaces and `env()` values with their representative values from the environment
64+
func augmentHeaderFromEnv(header AuthHeader) AuthHeader {
65+
// turn env(SOME_HEADER_KEY) into SOME_HEADER_KEY
66+
if strings.HasPrefix(header.Value, "env(") && strings.HasSuffix(header.Value, ")") {
67+
headerKey := strings.TrimPrefix(header.Value, "env(")
68+
headerKey = strings.TrimSuffix(headerKey, ")")
69+
70+
val := os.Getenv(headerKey)
71+
72+
augmentedHeader := AuthHeader{
73+
HeaderType: header.HeaderType,
74+
Value: val,
75+
}
76+
77+
return augmentedHeader
78+
}
79+
80+
return header
81+
}

rcap/graphql.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import (
66
"fmt"
77
"io/ioutil"
88
"net/http"
9+
"net/url"
910

1011
"github.com/pkg/errors"
1112
)
1213

1314
// GraphQLClient is a GraphQL capability for Reactr Modules
1415
type GraphQLClient interface {
15-
Do(endpoint, query string) (*GraphQLResponse, error)
16+
Do(auth AuthProvider, endpoint, query string) (*GraphQLResponse, error)
1617
}
1718

1819
// defaultGraphQLClient is the default implementation of the GraphQL capability
@@ -48,7 +49,7 @@ type GraphQLError struct {
4849
Path string `json:"path"`
4950
}
5051

51-
func (g *defaultGraphQLClient) Do(endpoint, query string) (*GraphQLResponse, error) {
52+
func (g *defaultGraphQLClient) Do(auth AuthProvider, endpoint, query string) (*GraphQLResponse, error) {
5253
r := &GraphQLRequest{
5354
Query: query,
5455
Variables: map[string]string{},
@@ -59,13 +60,23 @@ func (g *defaultGraphQLClient) Do(endpoint, query string) (*GraphQLResponse, err
5960
return nil, errors.Wrap(err, "failed to Marshal request")
6061
}
6162

63+
endpointURL, err := url.Parse(endpoint)
64+
if err != nil {
65+
return nil, errors.Wrap(err, "failed to Parse endpoint")
66+
}
67+
6268
req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(reqBytes))
6369
if err != nil {
6470
return nil, errors.Wrap(err, "failed to NewRequest")
6571
}
6672

6773
req.Header.Add("Content-Type", "application/json")
6874

75+
authHeader := auth.HeaderForDomain(endpointURL.Host)
76+
if authHeader.Value != "" {
77+
req.Header.Add("Authorization", fmt.Sprintf("%s %s", authHeader.HeaderType, authHeader.Value))
78+
}
79+
6980
resp, err := g.client.Do(req)
7081
if err != nil {
7182
return nil, errors.Wrap(err, "failed to Do")
@@ -84,7 +95,7 @@ func (g *defaultGraphQLClient) Do(endpoint, query string) (*GraphQLResponse, err
8495
}
8596

8697
if resp.StatusCode > 299 {
87-
return gqlResp, errors.New("non-200 HTTP response code")
98+
return gqlResp, fmt.Errorf("non-200 HTTP response code; %s", string(respJSON))
8899
}
89100

90101
if gqlResp.Errors != nil && len(gqlResp.Errors) > 0 {

rcap/tester/main.go

Lines changed: 0 additions & 30 deletions
This file was deleted.

rt/capabilities.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ var ErrCapabilityNotAvailable = errors.New("capability not available")
1010

1111
// Capabilities define the capabilities available to a Runnable
1212
type Capabilities struct {
13+
Auth rcap.AuthProvider
1314
LoggerSource rcap.LoggerSource
1415
HTTPClient rcap.HTTPClient
1516
GraphQLClient rcap.GraphQLClient
@@ -25,6 +26,7 @@ type Capabilities struct {
2526

2627
func defaultCaps(logger *vlog.Logger) Capabilities {
2728
caps := Capabilities{
29+
Auth: rcap.DefaultAuthProvider(nil), // no authentication config is set up by default
2830
LoggerSource: rcap.DefaultLoggerSource(logger),
2931
HTTPClient: rcap.DefaultHTTPClient(),
3032
GraphQLClient: rcap.DefaultGraphQLClient(),

rwasm/api_graphql.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func graphql_query(endpointPointer int32, endpointSize int32, queryPointer int32
3636
queryBytes := inst.readMemory(queryPointer, querySize)
3737
query := string(queryBytes)
3838

39-
resp, err := inst.ctx.GraphQLClient.Do(endpoint, query)
39+
resp, err := inst.ctx.GraphQLClient.Do(inst.ctx.Auth, endpoint, query)
4040
if err != nil {
4141
internalLogger.Error(errors.Wrap(err, "failed to GraphQLClient.Do"))
4242
return -1
91 Bytes
Binary file not shown.

rwasm/testdata/as-graphql/src/lib.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { graphQLQuery, logInfo } from "@suborbital/suborbital"
22

33
export function run(_: ArrayBuffer): ArrayBuffer {
4-
let result = graphQLQuery("https://api.rawkode.dev", "{ allProfiles { forename, surname } }")
4+
let result = graphQLQuery("https://api.github.com/graphql", "{ repository (owner: \"suborbital\", name: \"reactr\") { name, nameWithOwner }}")
55
if (result.byteLength == 0) {
66
return String.UTF8.encode("failed")
77
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
name: as-json
2+
namespace: default
3+
lang: assemblyscript
4+
version: ""
5+
apiVersion: 0.10.0
16.8 KB
Binary file not shown.

0 commit comments

Comments
 (0)